diff options
32 files changed, 164 insertions, 93 deletions
diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts index 3fa49e3cd1..cf233defd9 100644 --- a/packages/backend/src/misc/json-schema.ts +++ b/packages/backend/src/misc/json-schema.ts @@ -64,6 +64,7 @@ import { packedMetaDetailedOnlySchema, packedMetaDetailedSchema, packedMetaLiteSchema, + packedMetaClientOptionsSchema, } from '@/models/json-schema/meta.js'; import { packedUserWebhookSchema } from '@/models/json-schema/user-webhook.js'; import { packedSystemWebhookSchema } from '@/models/json-schema/system-webhook.js'; @@ -135,6 +136,7 @@ export const refs = { MetaLite: packedMetaLiteSchema, MetaDetailedOnly: packedMetaDetailedOnlySchema, MetaDetailed: packedMetaDetailedSchema, + MetaClientOptions: packedMetaClientOptionsSchema, UserWebhook: packedUserWebhookSchema, SystemWebhook: packedSystemWebhookSchema, AbuseReportNotificationRecipient: packedAbuseReportNotificationRecipientSchema, diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index a6f68194c5..620853450c 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -725,7 +725,11 @@ export class MiMeta { @Column('jsonb', { default: { }, }) - public clientOptions: Record<string, any>; + public clientOptions: { + entrancePageStyle: 'classic' | 'simple'; + showTimelineForVisitor: boolean; + showActivitiesForVisitor: boolean; + }; } export type SoftwareSuspension = { diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts index a0e7d490b3..0c3ec141bc 100644 --- a/packages/backend/src/models/json-schema/meta.ts +++ b/packages/backend/src/models/json-schema/meta.ts @@ -72,8 +72,7 @@ export const packedMetaLiteSchema = { optional: false, nullable: true, }, clientOptions: { - type: 'object', - optional: false, nullable: false, + ref: 'MetaClientOptions', }, disableRegistration: { type: 'boolean', @@ -397,3 +396,23 @@ export const packedMetaDetailedSchema = { }, ], } as const; + +export const packedMetaClientOptionsSchema = { + type: 'object', + optional: false, nullable: false, + properties: { + entrancePageStyle: { + type: 'string', + enum: ['classic', 'simple'], + optional: false, nullable: false, + }, + showTimelineForVisitor: { + type: 'boolean', + optional: false, nullable: false, + }, + showActivitiesForVisitor: { + type: 'boolean', + optional: false, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 2c7f793584..5beed3a7e8 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -428,8 +428,7 @@ export const meta = { optional: false, nullable: true, }, clientOptions: { - type: 'object', - optional: false, nullable: false, + ref: 'MetaClientOptions', }, description: { type: 'string', diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index b3c2cecc67..7a8dfc4555 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -3,7 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Injectable } from '@nestjs/common'; +import { Injectable, Inject } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; import type { MiMeta } from '@/models/Meta.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; @@ -67,7 +68,14 @@ export const paramDef = { description: { type: 'string', nullable: true }, defaultLightTheme: { type: 'string', nullable: true }, defaultDarkTheme: { type: 'string', nullable: true }, - clientOptions: { type: 'object', nullable: false }, + clientOptions: { + type: 'object', nullable: false, + properties: { + entrancePageStyle: { type: 'string', nullable: false, enum: ['classic', 'simple'] }, + showTimelineForVisitor: { type: 'boolean', nullable: false }, + showActivitiesForVisitor: { type: 'boolean', nullable: false }, + }, + }, cacheRemoteFiles: { type: 'boolean' }, cacheRemoteSensitiveFiles: { type: 'boolean' }, emailRequiredForSignup: { type: 'boolean' }, @@ -217,6 +225,9 @@ export const paramDef = { @Injectable() export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + private metaService: MetaService, private moderationLogService: ModerationLogService, ) { @@ -329,7 +340,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- } if (ps.clientOptions !== undefined) { - set.clientOptions = ps.clientOptions; + set.clientOptions = { + ...serverSettings.clientOptions, + ...ps.clientOptions, + }; } if (ps.cacheRemoteFiles !== undefined) { diff --git a/packages/frontend/src/components/MkAchievements.vue b/packages/frontend/src/components/MkAchievements.vue index d0e138c229..fe6415eabb 100644 --- a/packages/frontend/src/components/MkAchievements.vue +++ b/packages/frontend/src/components/MkAchievements.vue @@ -23,13 +23,13 @@ SPDX-License-Identifier: AGPL-3.0-only </div> <div :class="$style.body"> <div :class="$style.header"> - <span :class="$style.title">{{ (i18n.ts._achievements._types as any)['_' + achievement.name].title }}</span> + <span :class="$style.title">{{ i18n.ts._achievements._types[`_${achievement.name}`].title }}</span> <span :class="$style.time"> <time v-tooltip="new Date(achievement.unlockedAt).toLocaleString()">{{ new Date(achievement.unlockedAt).getFullYear() }}/{{ new Date(achievement.unlockedAt).getMonth() + 1 }}/{{ new Date(achievement.unlockedAt).getDate() }}</time> </span> </div> - <div :class="$style.description">{{ withDescription ? (i18n.ts._achievements._types as any)['_' + achievement.name].description : '???' }}</div> - <div v-if="(i18n.ts._achievements._types as any)['_' + achievement.name].flavor && withDescription" :class="$style.flavor">{{ (i18n.ts._achievements._types as any)['_' + achievement.name].flavor }}</div> + <div :class="$style.description">{{ withDescription ? i18n.ts._achievements._types[`_${achievement.name}`].description : '???' }}</div> + <div v-if="'flavor' in i18n.ts._achievements._types[`_${achievement.name}`] && withDescription" :class="$style.flavor">{{ (i18n.ts._achievements._types[`_${achievement.name}`] as { flavor: string; }).flavor }}</div> </div> </div> <template v-if="withLocked"> diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index 5e860351af..bfe66cdf8f 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -45,6 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts"> import { markRaw, ref, useTemplateRef, computed, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'; +import * as Misskey from 'misskey-js'; import sanitizeHtml from 'sanitize-html'; import { emojilist, getEmojiName } from '@@/js/emojilist.js'; import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@@/js/emoji-base.js'; @@ -63,7 +64,7 @@ import { prefer } from '@/preferences.js'; export type CompleteInfo = { user: { - payload: any; + payload: Misskey.entities.User; query: string | null; }, hashtag: { @@ -185,9 +186,9 @@ const suggests = ref<Element>(); const rootEl = useTemplateRef('rootEl'); const fetching = ref(true); -const users = ref<any[]>([]); -const hashtags = ref<any[]>([]); -const emojis = ref<(EmojiDef)[]>([]); +const users = ref<Misskey.entities.User[]>([]); +const hashtags = ref<string[]>([]); +const emojis = ref<EmojiDef[]>([]); const items = ref<Element[] | HTMLCollection>([]); const mfmTags = ref<string[]>([]); const mfmParams = ref<string[]>([]); @@ -204,8 +205,8 @@ function complete<T extends keyof CompleteInfo>(type: T, value: CompleteInfo[T][ emit('closed'); if (type === 'emoji' || type === 'emojiComplete') { let recents = store.s.recentlyUsedEmojis; - recents = recents.filter((emoji: any) => emoji !== value); - recents.unshift(value); + recents = recents.filter((emoji) => emoji !== value); + recents.unshift(value as string); store.set('recentlyUsedEmojis', recents.splice(0, 32)); } } @@ -254,7 +255,7 @@ function exec() { limit: 10, detail: false, }).then(searchedUsers => { - users.value = searchedUsers as any[]; + users.value = searchedUsers; fetching.value = false; // キャッシュ sessionStorage.setItem(cacheKey, JSON.stringify(searchedUsers)); @@ -276,7 +277,7 @@ function exec() { query: props.q, limit: 30, }).then(searchedHashtags => { - hashtags.value = searchedHashtags as any[]; + hashtags.value = searchedHashtags; fetching.value = false; // キャッシュ sessionStorage.setItem(cacheKey, JSON.stringify(searchedHashtags)); diff --git a/packages/frontend/src/components/MkExtensionInstaller.vue b/packages/frontend/src/components/MkExtensionInstaller.vue index 9c0cce7689..3f7eb9bccd 100644 --- a/packages/frontend/src/components/MkExtensionInstaller.vue +++ b/packages/frontend/src/components/MkExtensionInstaller.vue @@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #key>{{ i18n.ts.permission }}</template> <template #value> <ul v-if="extension.meta.permissions && extension.meta.permissions.length > 0" :class="$style.extInstallerKVList"> - <li v-for="permission in extension.meta.permissions" :key="permission">{{ (i18n.ts._permissions as any)[permission] ?? permission }}</li> + <li v-for="permission in extension.meta.permissions" :key="permission">{{ i18n.ts._permissions[permission] ?? permission }}</li> </ul> <template v-else>{{ i18n.ts.none }}</template> </template> @@ -91,7 +91,7 @@ SPDX-License-Identifier: AGPL-3.0-only </MkFolder> </div> - <slot name="additionalInfo"/> + <slot name="additionalInfo"></slot> <div class="_buttonsCenter"> <MkButton danger rounded large @click="emits('cancel')"><i class="ti ti-x"></i> {{ i18n.ts.cancel }}</MkButton> @@ -101,6 +101,8 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts"> +import * as Misskey from 'misskey-js'; + export type Extension = { type: 'plugin'; raw: string; @@ -109,7 +111,7 @@ export type Extension = { version: string; author: string; description?: string; - permissions?: string[]; + permissions?: (typeof Misskey.permissions)[number][]; config?: Record<string, unknown>; }; } | { @@ -125,7 +127,6 @@ export type Extension = { <script lang="ts" setup> import { computed } from 'vue'; import MkButton from '@/components/MkButton.vue'; -import FormSection from '@/components/form/section.vue'; import FormSplit from '@/components/form/split.vue'; import MkCode from '@/components/MkCode.vue'; import MkInfo from '@/components/MkInfo.vue'; diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue index 143ca7fd2f..a22fb7db01 100644 --- a/packages/frontend/src/components/MkNotification.vue +++ b/packages/frontend/src/components/MkNotification.vue @@ -121,7 +121,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ notification.invitation.room.name }} </div> <MkA v-else-if="notification.type === 'achievementEarned'" :class="$style.text" to="/my/achievements"> - {{ (i18n.ts._achievements._types as any)['_' + notification.achievement].title }} + {{ i18n.ts._achievements._types[`_${notification.achievement}`].title }} </MkA> <MkA v-else-if="notification.type === 'exportCompleted'" :class="$style.text" :to="`/my/drive/file/${notification.fileId}`"> {{ i18n.ts.showFile }} diff --git a/packages/frontend/src/components/MkPushNotificationAllowButton.vue b/packages/frontend/src/components/MkPushNotificationAllowButton.vue index 38441b0ea6..cba9b47c56 100644 --- a/packages/frontend/src/components/MkPushNotificationAllowButton.vue +++ b/packages/frontend/src/components/MkPushNotificationAllowButton.vue @@ -144,7 +144,7 @@ async function unsubscribe() { } function encode(buffer: ArrayBuffer | null) { - return btoa(String.fromCharCode.apply(null, buffer ? new Uint8Array(buffer) as any : [])); + return btoa(String.fromCharCode(...(buffer != null ? new Uint8Array(buffer) : []))); } /** diff --git a/packages/frontend/src/components/MkRetentionLineChart.vue b/packages/frontend/src/components/MkRetentionLineChart.vue index 21c20f944b..5b18bab8c9 100644 --- a/packages/frontend/src/components/MkRetentionLineChart.vue +++ b/packages/frontend/src/components/MkRetentionLineChart.vue @@ -10,6 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { onMounted, useTemplateRef } from 'vue'; import { Chart } from 'chart.js'; +import type { ScatterDataPoint } from 'chart.js'; import tinycolor from 'tinycolor2'; import { store } from '@/store.js'; import { useChartTooltip } from '@/composables/use-chart-tooltip.js'; @@ -18,6 +19,12 @@ import { alpha } from '@/utility/color.js'; import { initChart } from '@/utility/init-chart.js'; import { misskeyApi } from '@/utility/misskey-api.js'; +interface RetentionPoint extends ScatterDataPoint { + x: number; + y: number; + d: string; +} + initChart(); const chartEl = useTemplateRef('chartEl'); @@ -62,14 +69,14 @@ onMounted(async () => { fill: false, tension: 0.4, data: [{ - x: '0', + x: 0, y: 100, d: getYYYYMMDD(new Date(record.createdAt)), }, ...Object.entries(record.data).sort((a, b) => getDate(a[0]) > getDate(b[0]) ? 1 : -1).map(([k, v], i) => ({ - x: (i + 1).toString(), + x: i + 1, y: (v / record.users) * 100, d: getYYYYMMDD(new Date(record.createdAt)), - }))] as any, + }))], })), }, options: { @@ -111,11 +118,11 @@ onMounted(async () => { enabled: false, callbacks: { title(context) { - const v = context[0].dataset.data[context[0].dataIndex] as unknown as { x: string, y: number, d: string }; + const v = context[0].dataset.data[context[0].dataIndex] as RetentionPoint; return `${v.x} days later`; }, label(context) { - const v = context.dataset.data[context.dataIndex] as unknown as { x: string, y: number, d: string }; + const v = context.dataset.data[context.dataIndex] as RetentionPoint; const p = Math.round(v.y) + '%'; return `${v.d} ${p}`; }, diff --git a/packages/frontend/src/components/MkServerSetupWizard.vue b/packages/frontend/src/components/MkServerSetupWizard.vue index 796c909be9..462ded6de3 100644 --- a/packages/frontend/src/components/MkServerSetupWizard.vue +++ b/packages/frontend/src/components/MkServerSetupWizard.vue @@ -245,7 +245,7 @@ const serverSettings = computed<Misskey.entities.AdminUpdateMetaRequest>(() => { enableReactionsBuffering, clientOptions: { entrancePageStyle: q_use.value === 'open' ? 'classic' : 'simple', - } as any, + }, }; }); diff --git a/packages/frontend/src/components/MkWidgetSettingsDialog.vue b/packages/frontend/src/components/MkWidgetSettingsDialog.vue index 41fec2a9e0..292b4010ff 100644 --- a/packages/frontend/src/components/MkWidgetSettingsDialog.vue +++ b/packages/frontend/src/components/MkWidgetSettingsDialog.vue @@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only @ok="save()" @closed="emit('closed')" > - <template #header><i class="ti ti-icons"></i> {{ (i18n.ts._widgets as any)[widgetName] ?? widgetName }}</template> + <template #header><i class="ti ti-icons"></i> {{ i18n.ts._widgets[widgetName] ?? widgetName }}</template> <MkPreviewWithControls> <template #preview> @@ -50,13 +50,14 @@ SPDX-License-Identifier: AGPL-3.0-only import { useTemplateRef, ref, computed, onBeforeUnmount, onMounted } from 'vue'; import MkPreviewWithControls from './MkPreviewWithControls.vue'; import type { Form } from '@/utility/form.js'; +import type { WidgetName } from '@/widgets/index.js'; import { deepClone } from '@/utility/clone.js'; import { i18n } from '@/i18n.js'; import MkModalWindow from '@/components/MkModalWindow.vue'; import MkForm from '@/components/MkForm.vue'; const props = defineProps<{ - widgetName: string; + widgetName: WidgetName; form: Form; currentSettings: Record<string, any>; }>(); diff --git a/packages/frontend/src/components/global/MkCondensedLine.vue b/packages/frontend/src/components/global/MkCondensedLine.vue index e1fbec4b6f..baa8d783f1 100644 --- a/packages/frontend/src/components/global/MkCondensedLine.vue +++ b/packages/frontend/src/components/global/MkCondensedLine.vue @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <span :class="$style.container"> <span ref="content" :class="$style.content" :style="{ maxWidth: `${100 / minScale}%` }"> - <slot/> + <slot></slot> </span> </span> </template> diff --git a/packages/frontend/src/instance.ts b/packages/frontend/src/instance.ts index 547db12e34..6f15826aaa 100644 --- a/packages/frontend/src/instance.ts +++ b/packages/frontend/src/instance.ts @@ -51,9 +51,3 @@ export async function fetchInstance(force = false): Promise<Misskey.entities.Met return instance; } - -export type ClientOptions = { - entrancePageStyle: 'classic' | 'simple'; - showTimelineForVisitor: boolean; - showActivitiesForVisitor: boolean; -}; diff --git a/packages/frontend/src/pages/admin/branding.vue b/packages/frontend/src/pages/admin/branding.vue index 957526b073..016d1b6a89 100644 --- a/packages/frontend/src/pages/admin/branding.vue +++ b/packages/frontend/src/pages/admin/branding.vue @@ -155,8 +155,8 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref, computed } from 'vue'; import JSON5 from 'json5'; +import * as Misskey from 'misskey-js'; import { host } from '@@/js/config.js'; -import type { ClientOptions } from '@/instance.js'; import MkInput from '@/components/MkInput.vue'; import MkTextarea from '@/components/MkTextarea.vue'; import * as os from '@/os.js'; @@ -172,11 +172,11 @@ import MkSwitch from '@/components/MkSwitch.vue'; const meta = await misskeyApi('admin/meta'); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -const entrancePageStyle = ref<ClientOptions['entrancePageStyle']>(meta.clientOptions.entrancePageStyle ?? 'classic'); +const entrancePageStyle = ref<Misskey.entities.MetaClientOptions['entrancePageStyle']>(meta.clientOptions.entrancePageStyle ?? 'classic'); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -const showTimelineForVisitor = ref<ClientOptions['showTimelineForVisitor']>(meta.clientOptions.showTimelineForVisitor ?? true); +const showTimelineForVisitor = ref<Misskey.entities.MetaClientOptions['showTimelineForVisitor']>(meta.clientOptions.showTimelineForVisitor ?? true); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -const showActivitiesForVisitor = ref<ClientOptions['showActivitiesForVisitor']>(meta.clientOptions.showActivitiesForVisitor ?? true); +const showActivitiesForVisitor = ref<Misskey.entities.MetaClientOptions['showActivitiesForVisitor']>(meta.clientOptions.showActivitiesForVisitor ?? true); const iconUrl = ref(meta.iconUrl); const app192IconUrl = ref(meta.app192IconUrl); @@ -195,11 +195,11 @@ const manifestJsonOverride = ref(meta.manifestJsonOverride === '' ? '{}' : JSON. function save() { os.apiWithDialog('admin/update-meta', { - clientOptions: ({ + clientOptions: { entrancePageStyle: entrancePageStyle.value, showTimelineForVisitor: showTimelineForVisitor.value, showActivitiesForVisitor: showActivitiesForVisitor.value, - } as ClientOptions) as any, + }, iconUrl: iconUrl.value, app192IconUrl: app192IconUrl.value, app512IconUrl: app512IconUrl.value, diff --git a/packages/frontend/src/pages/auth.form.vue b/packages/frontend/src/pages/auth.form.vue index 6e9f5dd1eb..bc585950b4 100644 --- a/packages/frontend/src/pages/auth.form.vue +++ b/packages/frontend/src/pages/auth.form.vue @@ -5,10 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <section> - <div v-if="app.permission.length > 0"> + <div v-if="permissions.length > 0"> <p>{{ i18n.tsx._auth.permission({ name }) }}</p> <ul> - <li v-for="p in app.permission" :key="p">{{ (i18n.ts._permissions as any)[p] ?? p }}</li> + <li v-for="p in permissions" :key="p">{{ i18n.ts._permissions[p] ?? p }}</li> </ul> </div> <div>{{ i18n.tsx._auth.shareAccess({ name: `${name} (${app.id})` }) }}</div> @@ -37,6 +37,10 @@ const emit = defineEmits<{ const app = computed(() => props.session.app); +const permissions = computed(() => { + return props.session.app.permission.filter((p): p is typeof Misskey.permissions[number] => typeof p === 'string'); +}); + const name = computed(() => { const el = window.document.createElement('div'); el.textContent = app.value.name; diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue index 43a1e8e426..449f1af60a 100644 --- a/packages/frontend/src/pages/flash/flash.vue +++ b/packages/frontend/src/pages/flash/flash.vue @@ -212,7 +212,7 @@ async function run() { const version = utils.getLangVersion(flash.value.script); const isLegacy = getIsLegacy(version); - const { Interpreter, Parser, values } = isLegacy ? (await import('@syuilo/aiscript-0-19-0') as any) : await import('@syuilo/aiscript'); + const { Interpreter, Parser, values } = (isLegacy ? (await import('@syuilo/aiscript-0-19-0')) : await import('@syuilo/aiscript')) as typeof import('@syuilo/aiscript'); const parser = new Parser(); diff --git a/packages/frontend/src/pages/settings/plugin.vue b/packages/frontend/src/pages/settings/plugin.vue index 16c764001e..89f457cf69 100644 --- a/packages/frontend/src/pages/settings/plugin.vue +++ b/packages/frontend/src/pages/settings/plugin.vue @@ -58,7 +58,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #key>{{ i18n.ts.permission }}</template> <template #value> <ul style="margin-top: 0; margin-bottom: 0;"> - <li v-for="permission in plugin.permissions" :key="permission">{{ (i18n.ts._permissions as any)[permission] ?? permission }}</li> + <li v-for="permission in plugin.permissions" :key="permission">{{ i18n.ts._permissions[permission] ?? permission }}</li> <li v-if="!plugin.permissions || plugin.permissions.length === 0">{{ i18n.ts.none }}</li> </ul> </template> diff --git a/packages/frontend/src/pages/settings/preferences.vue b/packages/frontend/src/pages/settings/preferences.vue index 7d85565ced..1a613466db 100644 --- a/packages/frontend/src/pages/settings/preferences.vue +++ b/packages/frontend/src/pages/settings/preferences.vue @@ -1083,7 +1083,7 @@ function removePinnedList() { function enableAllDataSaver() { const g = { ...prefer.s.dataSaver }; - Object.keys(g).forEach((key) => { (g as any)[key] = true; }); + (Object.keys(g) as (keyof typeof g)[]).forEach((key) => { g[key] = true; }); dataSaver.value = g; } @@ -1091,7 +1091,7 @@ function enableAllDataSaver() { function disableAllDataSaver() { const g = { ...prefer.s.dataSaver }; - Object.keys(g).forEach((key) => { (g as any)[key] = false; }); + (Object.keys(g) as (keyof typeof g)[]).forEach((key) => { g[key] = false; }); dataSaver.value = g; } diff --git a/packages/frontend/src/plugin.ts b/packages/frontend/src/plugin.ts index d33e158ad9..a41d0bc217 100644 --- a/packages/frontend/src/plugin.ts +++ b/packages/frontend/src/plugin.ts @@ -26,7 +26,7 @@ export type Plugin = { version: string; author?: string; description?: string; - permissions?: string[]; + permissions?: (typeof Misskey.permissions)[number][]; }; export type AiScriptPluginMeta = { @@ -34,7 +34,7 @@ export type AiScriptPluginMeta = { version: string; author: string; description?: string; - permissions?: string[]; + permissions?: (typeof Misskey.permissions)[number][]; config?: Record<string, any>; }; @@ -97,7 +97,7 @@ export async function parsePluginMeta(code: string): Promise<AiScriptPluginMeta> version: version as string, author: author as string, description: description as string | undefined, - permissions: permissions as string[] | undefined, + permissions: permissions as (typeof Misskey.permissions)[number][] | undefined, config: config as Record<string, any> | undefined, }; } diff --git a/packages/frontend/src/pref-migrate.ts b/packages/frontend/src/pref-migrate.ts index 7470271627..bf86f6954e 100644 --- a/packages/frontend/src/pref-migrate.ts +++ b/packages/frontend/src/pref-migrate.ts @@ -12,6 +12,7 @@ import { deckStore } from '@/ui/deck/deck-store.js'; import { unisonReload } from '@/utility/unison-reload.js'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; +import type { SoundStore } from '@/preferences/def.js'; // TODO: そのうち消す export function migrateOldSettings() { @@ -126,10 +127,10 @@ export function migrateOldSettings() { prefer.commit('sound.masterVolume', store.s.sound_masterVolume); prefer.commit('sound.notUseSound', store.s.sound_notUseSound); prefer.commit('sound.useSoundOnlyWhenActive', store.s.sound_useSoundOnlyWhenActive); - prefer.commit('sound.on.note', store.s.sound_note as any); - prefer.commit('sound.on.noteMy', store.s.sound_noteMy as any); - prefer.commit('sound.on.notification', store.s.sound_notification as any); - prefer.commit('sound.on.reaction', store.s.sound_reaction as any); + prefer.commit('sound.on.note', store.s.sound_note as SoundStore); + prefer.commit('sound.on.noteMy', store.s.sound_noteMy as SoundStore); + prefer.commit('sound.on.notification', store.s.sound_notification as SoundStore); + prefer.commit('sound.on.reaction', store.s.sound_reaction as SoundStore); prefer.commit('defaultNoteVisibility', store.s.defaultNoteVisibility); prefer.commit('defaultNoteLocalOnly', store.s.defaultNoteLocalOnly); diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index 4674293254..e7cdc98415 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -3,16 +3,12 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { markRaw, ref } from 'vue'; +import { markRaw } from 'vue'; import * as Misskey from 'misskey-js'; -import lightTheme from '@@/themes/l-light.json5'; -import darkTheme from '@@/themes/d-green-lime.json5'; import { prefersReducedMotion } from '@@/js/config.js'; import { hemisphere } from '@@/js/intl-const.js'; import type { DeviceKind } from '@/utility/device-kind.js'; -import type { Plugin } from '@/plugin.js'; import type { TIPS } from '@/tips.js'; -import { miLocalStorage } from '@/local-storage.js'; import { Pizzax } from '@/lib/pizzax.js'; import { DEFAULT_DEVICE_KIND } from '@/utility/device-kind.js'; diff --git a/packages/frontend/src/utility/autocomplete.ts b/packages/frontend/src/utility/autocomplete.ts index 4eb1e29d07..a44bf7c1ae 100644 --- a/packages/frontend/src/utility/autocomplete.ts +++ b/packages/frontend/src/utility/autocomplete.ts @@ -12,6 +12,15 @@ import { popup } from '@/os.js'; export type SuggestionType = 'user' | 'hashtag' | 'emoji' | 'mfmTag' | 'mfmParam'; +type CompleteProps<T extends keyof CompleteInfo> = { + type: T; + value: CompleteInfo[T]['payload']; +}; + +function isCompleteType<T extends keyof CompleteInfo>(expectedType: T, props: CompleteProps<keyof CompleteInfo>): props is CompleteProps<T> { + return props.type === expectedType; +} + export class Autocomplete { private suggestion: { x: Ref<number>; @@ -253,19 +262,19 @@ export class Autocomplete { /** * オートコンプリートする */ - private complete<T extends keyof CompleteInfo>({ type, value }: { type: T; value: CompleteInfo[T]['payload'] }) { + private complete<T extends keyof CompleteInfo>(props: CompleteProps<T>) { this.close(); const caret = Number(this.textarea.selectionStart); - if (type === 'user') { + if (isCompleteType('user', props)) { const source = this.text; const before = source.substring(0, caret); const trimmedBefore = before.substring(0, before.lastIndexOf('@')); const after = source.substring(caret); - const acct = value.host === null ? value.username : `${value.username}@${toASCII(value.host)}`; + const acct = props.value.host === null ? props.value.username : `${props.value.username}@${toASCII(props.value.host)}`; // 挿入 this.text = `${trimmedBefore}@${acct} ${after}`; @@ -276,7 +285,7 @@ export class Autocomplete { const pos = trimmedBefore.length + (acct.length + 2); this.textarea.setSelectionRange(pos, pos); }); - } else if (type === 'hashtag') { + } else if (isCompleteType('hashtag', props)) { const source = this.text; const before = source.substring(0, caret); @@ -284,15 +293,15 @@ export class Autocomplete { const after = source.substring(caret); // 挿入 - this.text = `${trimmedBefore}#${value} ${after}`; + this.text = `${trimmedBefore}#${props.value} ${after}`; // キャレットを戻す nextTick(() => { this.textarea.focus(); - const pos = trimmedBefore.length + (value.length + 2); + const pos = trimmedBefore.length + (props.value.length + 2); this.textarea.setSelectionRange(pos, pos); }); - } else if (type === 'emoji') { + } else if (isCompleteType('emoji', props)) { const source = this.text; const before = source.substring(0, caret); @@ -300,15 +309,15 @@ export class Autocomplete { const after = source.substring(caret); // 挿入 - this.text = trimmedBefore + value + after; + this.text = trimmedBefore + props.value + after; // キャレットを戻す nextTick(() => { this.textarea.focus(); - const pos = trimmedBefore.length + value.length; + const pos = trimmedBefore.length + props.value.length; this.textarea.setSelectionRange(pos, pos); }); - } else if (type === 'emojiComplete') { + } else if (isCompleteType('emojiComplete', props)) { const source = this.text; const before = source.substring(0, caret); @@ -316,15 +325,15 @@ export class Autocomplete { const after = source.substring(caret); // 挿入 - this.text = trimmedBefore + value + after; + this.text = trimmedBefore + props.value + after; // キャレットを戻す nextTick(() => { this.textarea.focus(); - const pos = trimmedBefore.length + value.length; + const pos = trimmedBefore.length + props.value.length; this.textarea.setSelectionRange(pos, pos); }); - } else if (type === 'mfmTag') { + } else if (isCompleteType('mfmTag', props)) { const source = this.text; const before = source.substring(0, caret); @@ -332,15 +341,15 @@ export class Autocomplete { const after = source.substring(caret); // 挿入 - this.text = `${trimmedBefore}$[${value} ]${after}`; + this.text = `${trimmedBefore}$[${props.value} ]${after}`; // キャレットを戻す nextTick(() => { this.textarea.focus(); - const pos = trimmedBefore.length + (value.length + 3); + const pos = trimmedBefore.length + (props.value.length + 3); this.textarea.setSelectionRange(pos, pos); }); - } else if (type === 'mfmParam') { + } else if (isCompleteType('mfmParam', props)) { const source = this.text; const before = source.substring(0, caret); @@ -348,12 +357,12 @@ export class Autocomplete { const after = source.substring(caret); // 挿入 - this.text = `${trimmedBefore}.${value}${after}`; + this.text = `${trimmedBefore}.${props.value}${after}`; // キャレットを戻す nextTick(() => { this.textarea.focus(); - const pos = trimmedBefore.length + (value.length + 1); + const pos = trimmedBefore.length + (props.value.length + 1); this.textarea.setSelectionRange(pos, pos); }); } diff --git a/packages/frontend/src/widgets/WidgetAichan.vue b/packages/frontend/src/widgets/WidgetAichan.vue index 5dde75e4f2..c2a41b6257 100644 --- a/packages/frontend/src/widgets/WidgetAichan.vue +++ b/packages/frontend/src/widgets/WidgetAichan.vue @@ -16,7 +16,7 @@ import { i18n } from '@/i18n.js'; import type { WidgetComponentProps, WidgetComponentEmits, WidgetComponentExpose } from './widget.js'; import type { FormWithDefault, GetFormResultType } from '@/utility/form.js'; -const name = 'ai'; +const name = 'aichan'; const widgetPropsDef = { transparent: { diff --git a/packages/frontend/src/widgets/WidgetTimeline.vue b/packages/frontend/src/widgets/WidgetTimeline.vue index 5d65b001e0..83b8e7ccbc 100644 --- a/packages/frontend/src/widgets/WidgetTimeline.vue +++ b/packages/frontend/src/widgets/WidgetTimeline.vue @@ -93,12 +93,12 @@ const { widgetProps, configure, save } = useWidgetPropsManager(name, const menuOpened = ref(false); const headerTitle = computed<string>(() => { - if (widgetProps.src === 'list' && widgetProps.list != null) { - return widgetProps.list.name; - } else if (widgetProps.src === 'antenna' && widgetProps.antenna != null) { - return widgetProps.antenna.name; + if (widgetProps.src === 'list') { + return widgetProps.list != null ? widgetProps.list.name : '?'; + } else if (widgetProps.src === 'antenna') { + return widgetProps.antenna != null ? widgetProps.antenna.name : '?'; } else { - return (i18n.ts._timelines as any)[widgetProps.src] ?? '?'; + return i18n.ts._timelines[widgetProps.src] ?? '?'; } }); diff --git a/packages/frontend/src/widgets/WidgetTrends.vue b/packages/frontend/src/widgets/WidgetTrends.vue index 1984750cd7..498129305b 100644 --- a/packages/frontend/src/widgets/WidgetTrends.vue +++ b/packages/frontend/src/widgets/WidgetTrends.vue @@ -36,7 +36,7 @@ import { misskeyApiGet } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; import { prefer } from '@/preferences.js'; -const name = 'hashtags'; +const name = 'trends'; const widgetPropsDef = { showHeader: { diff --git a/packages/frontend/src/widgets/index.ts b/packages/frontend/src/widgets/index.ts index b3351be45f..79bae68d71 100644 --- a/packages/frontend/src/widgets/index.ts +++ b/packages/frontend/src/widgets/index.ts @@ -75,3 +75,5 @@ export const widgets = [ ...federationWidgets, ] as const; + +export type WidgetName = typeof widgets[number]; diff --git a/packages/frontend/src/widgets/widget.ts b/packages/frontend/src/widgets/widget.ts index cab6177247..bfb724ff72 100644 --- a/packages/frontend/src/widgets/widget.ts +++ b/packages/frontend/src/widgets/widget.ts @@ -10,6 +10,7 @@ import type { FormWithDefault, GetFormResultType } from '@/utility/form.js'; import { getDefaultFormValues } from '@/utility/form.js'; import * as os from '@/os.js'; import { deepClone } from '@/utility/clone.js'; +import type { WidgetName } from './index.js'; export type Widget<P extends Record<string, unknown>> = { id: string; @@ -31,7 +32,7 @@ export type WidgetComponentExpose = { }; export const useWidgetPropsManager = <F extends FormWithDefault>( - name: string, + name: WidgetName, propsDef: F, props: Readonly<WidgetComponentProps<GetFormResultType<F>>>, emit: WidgetComponentEmits<GetFormResultType<F>>, diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index ede6025457..b84fdc7e31 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -2230,6 +2230,7 @@ declare namespace entities { MetaLite, MetaDetailedOnly, MetaDetailed, + MetaClientOptions, UserWebhook, SystemWebhook, AbuseReportNotificationRecipient, @@ -2821,6 +2822,9 @@ type MeDetailed = components['schemas']['MeDetailed']; type MeDetailedOnly = components['schemas']['MeDetailedOnly']; // @public (undocumented) +type MetaClientOptions = components['schemas']['MetaClientOptions']; + +// @public (undocumented) type MetaDetailed = components['schemas']['MetaDetailed']; // @public (undocumented) diff --git a/packages/misskey-js/src/autogen/models.ts b/packages/misskey-js/src/autogen/models.ts index 01b48442d6..f45ae39da4 100644 --- a/packages/misskey-js/src/autogen/models.ts +++ b/packages/misskey-js/src/autogen/models.ts @@ -58,6 +58,7 @@ export type ReversiGameDetailed = components['schemas']['ReversiGameDetailed']; export type MetaLite = components['schemas']['MetaLite']; export type MetaDetailedOnly = components['schemas']['MetaDetailedOnly']; export type MetaDetailed = components['schemas']['MetaDetailed']; +export type MetaClientOptions = components['schemas']['MetaClientOptions']; export type UserWebhook = components['schemas']['UserWebhook']; export type SystemWebhook = components['schemas']['SystemWebhook']; export type AbuseReportNotificationRecipient = components['schemas']['AbuseReportNotificationRecipient']; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index dde55e06a0..3aecc765d9 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -5441,7 +5441,7 @@ export type components = { feedbackUrl: string | null; defaultDarkTheme: string | null; defaultLightTheme: string | null; - clientOptions: Record<string, never>; + clientOptions: components['schemas']['MetaClientOptions']; disableRegistration: boolean; emailRequiredForSignup: boolean; enableHcaptcha: boolean; @@ -5540,6 +5540,12 @@ export type components = { cacheRemoteSensitiveFiles: boolean; }; MetaDetailed: components['schemas']['MetaLite'] & components['schemas']['MetaDetailedOnly']; + MetaClientOptions: { + /** @enum {string} */ + entrancePageStyle: 'classic' | 'simple'; + showTimelineForVisitor: boolean; + showActivitiesForVisitor: boolean; + }; UserWebhook: { /** Format: id */ id: string; @@ -9468,7 +9474,7 @@ export interface operations { deeplIsPro: boolean; defaultDarkTheme: string | null; defaultLightTheme: string | null; - clientOptions: Record<string, never>; + clientOptions: components['schemas']['MetaClientOptions']; description: string | null; disableRegistration: boolean; impressumUrl: string | null; @@ -12724,7 +12730,12 @@ export interface operations { description?: string | null; defaultLightTheme?: string | null; defaultDarkTheme?: string | null; - clientOptions?: Record<string, never>; + clientOptions?: { + /** @enum {string} */ + entrancePageStyle?: 'classic' | 'simple'; + showTimelineForVisitor?: boolean; + showActivitiesForVisitor?: boolean; + }; cacheRemoteFiles?: boolean; cacheRemoteSensitiveFiles?: boolean; emailRequiredForSignup?: boolean; |