diff options
| author | かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> | 2025-09-13 08:33:14 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-09-13 08:33:14 +0900 |
| commit | 5b4115e21a6822a434a9bfbbd53f22b3ca961239 (patch) | |
| tree | eaac22e870a95feccc47ccc2693b794d0ccd299f /packages/frontend/src | |
| parent | Update CHANGELOG.md (diff) | |
| download | misskey-5b4115e21a6822a434a9bfbbd53f22b3ca961239.tar.gz misskey-5b4115e21a6822a434a9bfbbd53f22b3ca961239.tar.bz2 misskey-5b4115e21a6822a434a9bfbbd53f22b3ca961239.zip | |
refactor(frontend): フロントエンドの型エラー解消(途中まで) (#16539)
* fix(frontend): FormLinkをボタンとして使用した際にエラーが出る問題を修正
* refactor(frontend): フロントエンドの型エラー解消
* remove unused ts-expect-error
* migrate
* remove unrelated changes
* fix lint
* more type fixes
Diffstat (limited to 'packages/frontend/src')
49 files changed, 253 insertions, 215 deletions
diff --git a/packages/frontend/src/components/MkAvatars.vue b/packages/frontend/src/components/MkAvatars.vue index 1c44ed60d8..4bd6c62a5f 100644 --- a/packages/frontend/src/components/MkAvatars.vue +++ b/packages/frontend/src/components/MkAvatars.vue @@ -29,6 +29,6 @@ const users = ref<Misskey.entities.UserLite[]>([]); onMounted(async () => { users.value = await misskeyApi('users/show', { userIds: props.userIds, - }) as unknown as Misskey.entities.UserLite[]; + }); }); </script> diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index 56683b8f8c..a3ff89fc4d 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -823,17 +823,15 @@ async function saveServerDraft(clearLocal = false) { return await os.apiWithDialog(serverDraftId.value == null ? 'notes/drafts/create' : 'notes/drafts/update', { ...(serverDraftId.value == null ? {} : { draftId: serverDraftId.value }), text: text.value, - useCw: useCw.value, - cw: cw.value, + cw: useCw.value ? cw.value || null : null, visibility: visibility.value, localOnly: localOnly.value, hashtag: hashtags.value, ...(files.value.length > 0 ? { fileIds: files.value.map(f => f.id) } : {}), poll: poll.value, ...(visibleUsers.value.length > 0 ? { visibleUserIds: visibleUsers.value.map(x => x.id) } : {}), - renoteId: renoteTargetNote.value ? renoteTargetNote.value.id : undefined, + renoteId: renoteTargetNote.value ? renoteTargetNote.value.id : quoteId.value ? quoteId.value : undefined, replyId: replyTargetNote.value ? replyTargetNote.value.id : undefined, - quoteId: quoteId.value, channelId: targetChannel.value ? targetChannel.value.id : undefined, reactionAcceptance: reactionAcceptance.value, }).then(() => { diff --git a/packages/frontend/src/components/MkPushNotificationAllowButton.vue b/packages/frontend/src/components/MkPushNotificationAllowButton.vue index 9c37eb5e72..697346020c 100644 --- a/packages/frontend/src/components/MkPushNotificationAllowButton.vue +++ b/packages/frontend/src/components/MkPushNotificationAllowButton.vue @@ -90,7 +90,7 @@ function subscribe() { publickey: encode(subscription.getKey('p256dh')), }); }, async err => { // When subscribe failed - // 通知が許可されていなかったとき + // 通知が許可されていなかったとき if (err?.name === 'NotAllowedError') { console.info('User denied the notification permission request.'); return; @@ -114,14 +114,13 @@ async function unsubscribe() { if ($i && accounts.length >= 2) { apiWithDialog('sw/unregister', { - i: $i.token, endpoint, - }); + }, $i.token); } else { pushSubscription.value.unsubscribe(); apiWithDialog('sw/unregister', { endpoint, - }); + }, null); pushSubscription.value = null; } } @@ -134,7 +133,7 @@ function encode(buffer: ArrayBuffer | null) { * Convert the URL safe base64 string to a Uint8Array * @param base64String base64 string */ -function urlBase64ToUint8Array(base64String: string): Uint8Array { +function urlBase64ToUint8Array(base64String: string): BufferSource { const padding = '='.repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding) .replace(/-/g, '+') diff --git a/packages/frontend/src/components/MkRolePreview.vue b/packages/frontend/src/components/MkRolePreview.vue index 15149b3f0c..8e5cbde8c3 100644 --- a/packages/frontend/src/components/MkRolePreview.vue +++ b/packages/frontend/src/components/MkRolePreview.vue @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <MkA :to="forModeration ? `/admin/roles/${role.id}` : `/roles/${role.id}`" :class="$style.root" tabindex="-1" :style="{ '--color': role.color }"> <template v-if="forModeration"> - <i v-if="role.isPublic" class="ti ti-world" :class="$style.icon" style="color: var(--MI_THEME-success)"></i> + <i v-if="'isPublic' in role && role.isPublic" class="ti ti-world" :class="$style.icon" style="color: var(--MI_THEME-success)"></i> <i v-else class="ti ti-lock" :class="$style.icon" style="color: var(--MI_THEME-warn)"></i> </template> @@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> </span> <span :class="$style.bodyName">{{ role.name }}</span> - <template v-if="detailed"> + <template v-if="detailed && 'target' in role && 'usersCount' in role"> <span v-if="role.target === 'manual'" :class="$style.bodyUsers">{{ role.usersCount }} users</span> <span v-else-if="role.target === 'conditional'" :class="$style.bodyUsers">? users</span> </template> @@ -39,7 +39,7 @@ import * as Misskey from 'misskey-js'; import { i18n } from '@/i18n.js'; const props = withDefaults(defineProps<{ - role: Misskey.entities.Role; + role: Misskey.entities.Role | Misskey.entities.IResponse['roles'][number]; forModeration: boolean; detailed?: boolean; }>(), { diff --git a/packages/frontend/src/components/MkUserPopup.vue b/packages/frontend/src/components/MkUserPopup.vue index 2a423bfa55..9b587178fe 100644 --- a/packages/frontend/src/components/MkUserPopup.vue +++ b/packages/frontend/src/components/MkUserPopup.vue @@ -72,7 +72,7 @@ import { getStaticImageUrl } from '@/utility/media-proxy.js'; const props = defineProps<{ showing: boolean; - q: string; + q: string | Misskey.entities.UserDetailed; source: HTMLElement; }>(); @@ -99,10 +99,11 @@ async function fetchUser() { user.value = props.q; error.value = false; } else { - const query: Omit<Misskey.entities.UsersShowRequest, 'userIds'> = props.q.startsWith('@') ? + const query: Misskey.entities.UsersShowRequest = props.q.startsWith('@') ? Misskey.acct.parse(props.q.substring(1)) : { userId: props.q }; + // @ts-expect-error payloadの引数側の型が正常に解決されない misskeyApi('users/show', query).then(res => { if (!props.showing) return; user.value = res; diff --git a/packages/frontend/src/components/form/link.vue b/packages/frontend/src/components/form/link.vue index e60155f4af..63cf1815c0 100644 --- a/packages/frontend/src/components/form/link.vue +++ b/packages/frontend/src/components/form/link.vue @@ -4,31 +4,39 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<div :class="[$style.root, { [$style.inline]: inline }]"> - <a v-if="external" :class="$style.main" class="_button" :href="to" target="_blank"> +<component + :is="to ? 'div' : 'button'" + :class="[ + $style.root, + { + [$style.inline]: inline, + '_button': !to, + }, + ]" +> + <component + :is="to ? (external ? 'a' : 'MkA') : 'div'" + :class="[$style.main, { [$style.active]: active }]" + class="_button" + v-bind="to ? (external ? { href: to, target: '_blank' } : { to, behavior }) : {}" + > <span :class="$style.icon"><slot name="icon"></slot></span> - <span :class="$style.text"><slot></slot></span> + <div :class="$style.headerText"> + <div> + <MkCondensedLine :minScale="2 / 3"><slot></slot></MkCondensedLine> + </div> + </div> <span :class="$style.suffix"> <span :class="$style.suffixText"><slot name="suffix"></slot></span> - <i class="ti ti-external-link"></i> + <i :class="to && external ? 'ti ti-external-link' : 'ti ti-chevron-right'"></i> </span> - </a> - <MkA v-else :class="[$style.main, { [$style.active]: active }]" class="_button" :to="to" :behavior="behavior"> - <span :class="$style.icon"><slot name="icon"></slot></span> - <span :class="$style.text"><slot></slot></span> - <span :class="$style.suffix"> - <span :class="$style.suffixText"><slot name="suffix"></slot></span> - <i class="ti ti-chevron-right"></i> - </span> - </MkA> -</div> + </component> +</component> </template> <script lang="ts" setup> -import { } from 'vue'; - -const props = defineProps<{ - to: string; +defineProps<{ + to?: string; active?: boolean; external?: boolean; behavior?: null | 'window' | 'browser'; @@ -75,17 +83,18 @@ const props = defineProps<{ &:empty { display: none; - & + .text { + & + .headerText { padding-left: 4px; } } } -.text { - flex-shrink: 1; - white-space: normal; +.headerText { + white-space: nowrap; + text-overflow: ellipsis; + text-align: start; + overflow: hidden; padding-right: 12px; - text-align: center; } .suffix { diff --git a/packages/frontend/src/components/global/PageWithHeader.vue b/packages/frontend/src/components/global/PageWithHeader.vue index d368dee88a..aac87b7669 100644 --- a/packages/frontend/src/components/global/PageWithHeader.vue +++ b/packages/frontend/src/components/global/PageWithHeader.vue @@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkPageHeader v-else v-model:tab="tab" v-bind="pageHeaderProps"/> </template> <div :class="$style.body"> - <MkSwiper v-if="prefer.s.enableHorizontalSwipe && swipable && (props.tabs?.length ?? 1) > 1" v-model:tab="tab" :class="$style.swiper" :tabs="props.tabs"> + <MkSwiper v-if="prefer.s.enableHorizontalSwipe && swipable && (props.tabs?.length ?? 1) > 1" v-model:tab="tab" :class="$style.swiper" :tabs="props.tabs ?? []"> <slot></slot> </MkSwiper> <slot v-else></slot> @@ -45,7 +45,7 @@ const props = withDefaults(defineProps<PageHeaderProps & { }); const pageHeaderProps = computed(() => { - const { reversed, ...rest } = props; + const { reversed, tab, ...rest } = props; return rest; }); @@ -75,10 +75,6 @@ defineExpose({ </script> <style lang="scss" module> -.root { - -} - .body, .swiper { min-height: calc(100cqh - (var(--MI-stickyTop, 0px) + var(--MI-stickyBottom, 0px))); } diff --git a/packages/frontend/src/events.ts b/packages/frontend/src/events.ts index 649561cd75..8cac1b6d2a 100644 --- a/packages/frontend/src/events.ts +++ b/packages/frontend/src/events.ts @@ -24,7 +24,7 @@ export const globalEvents = new EventEmitter<Events>(); export function useGlobalEvent<T extends keyof Events>( event: T, - callback: Events[T], + callback: EventEmitter.EventListener<Events, T>, ): void { globalEvents.on(event, callback); onBeforeUnmount(() => { diff --git a/packages/frontend/src/lib/pizzax.ts b/packages/frontend/src/lib/pizzax.ts index 20d44032df..6dffcf9478 100644 --- a/packages/frontend/src/lib/pizzax.ts +++ b/packages/frontend/src/lib/pizzax.ts @@ -94,7 +94,7 @@ export class Pizzax<T extends StateDef> { private mergeState<X>(value: X, def: X): X { if (this.isPureObject(value) && this.isPureObject(def)) { - const merged = deepMerge(value, def); + const merged = deepMerge<Record<PropertyKey, unknown>>(value, def); if (_DEV_) console.log('Merging state. Incoming: ', value, ' Default: ', def, ' Result: ', merged); diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts index 56a2b8d269..504ae19f1d 100644 --- a/packages/frontend/src/os.ts +++ b/packages/frontend/src/os.ts @@ -35,9 +35,9 @@ import { focusParent } from '@/utility/focus.js'; export const openingWindowsCount = ref(0); export type ApiWithDialogCustomErrors = Record<string, { title?: string; text: string; }>; -export const apiWithDialog = (<E extends keyof Misskey.Endpoints, P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req']>( +export const apiWithDialog = (<E extends keyof Misskey.Endpoints>( endpoint: E, - data: P, + data: Misskey.Endpoints[E]['req'], token?: string | null | undefined, customErrors?: ApiWithDialogCustomErrors, ) => { diff --git a/packages/frontend/src/pages/about.emojis.vue b/packages/frontend/src/pages/about.emojis.vue index 7e514c5a73..4640812756 100644 --- a/packages/frontend/src/pages/about.emojis.vue +++ b/packages/frontend/src/pages/about.emojis.vue @@ -11,12 +11,6 @@ SPDX-License-Identifier: AGPL-3.0-only <MkInput v-model="q" class="" :placeholder="i18n.ts.search" autocapitalize="off"> <template #prefix><i class="ti ti-search"></i></template> </MkInput> - - <!-- たくさんあると邪魔 - <div class="tags"> - <span class="tag _button" v-for="tag in customEmojiTags" :class="{ active: selectedTags.has(tag) }" @click="toggleTag(tag)">{{ tag }}</span> - </div> - --> </div> <MkFoldableSection v-if="searchEmojis"> @@ -42,51 +36,33 @@ import XEmoji from './emojis.emoji.vue'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; -import { customEmojis, customEmojiCategories, getCustomEmojiTags } from '@/custom-emojis.js'; +import { customEmojis, customEmojiCategories } from '@/custom-emojis.js'; import { i18n } from '@/i18n.js'; import { $i } from '@/i.js'; -const customEmojiTags = getCustomEmojiTags(); const q = ref(''); const searchEmojis = ref<Misskey.entities.EmojiSimple[] | null>(null); -const selectedTags = ref(new Set()); function search() { - if ((q.value === '' || q.value == null) && selectedTags.value.size === 0) { + if (q.value === '' || q.value == null) { searchEmojis.value = null; return; } - if (selectedTags.value.size === 0) { - const queryarry = q.value.match(/\:([a-z0-9_]*)\:/g); - - if (queryarry) { - searchEmojis.value = customEmojis.value.filter(emoji => - queryarry.includes(`:${emoji.name}:`), - ); - } else { - searchEmojis.value = customEmojis.value.filter(emoji => emoji.name.includes(q.value) || emoji.aliases.includes(q.value)); - } - } else { - searchEmojis.value = customEmojis.value.filter(emoji => (emoji.name.includes(q.value) || emoji.aliases.includes(q.value)) && [...selectedTags.value].every(t => emoji.aliases.includes(t))); - } -} + const queryarry = q.value.match(/\:([a-z0-9_]*)\:/g); -function toggleTag(tag) { - if (selectedTags.value.has(tag)) { - selectedTags.value.delete(tag); + if (queryarry) { + searchEmojis.value = customEmojis.value.filter(emoji => + queryarry.includes(`:${emoji.name}:`), + ); } else { - selectedTags.value.add(tag); + searchEmojis.value = customEmojis.value.filter(emoji => emoji.name.includes(q.value) || emoji.aliases.includes(q.value)); } } watch(q, () => { search(); }); - -watch(selectedTags, () => { - search(); -}, { deep: true }); </script> <style lang="scss" module> diff --git a/packages/frontend/src/pages/about.federation.vue b/packages/frontend/src/pages/about.federation.vue index fd5e061d52..568bf9fd1a 100644 --- a/packages/frontend/src/pages/about.federation.vue +++ b/packages/frontend/src/pages/about.federation.vue @@ -59,7 +59,7 @@ SPDX-License-Identifier: AGPL-3.0-only }, { label: `${i18n.ts.registeredAt} (${i18n.ts.ascendingOrder})`, value: '-firstRetrievedAt', - }] as const" + }]" > <template #label>{{ i18n.ts.sort }}</template> </MkSelect> diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue index 38e3c7a11b..86dfd38d81 100644 --- a/packages/frontend/src/pages/admin-user.vue +++ b/packages/frontend/src/pages/admin-user.vue @@ -233,6 +233,7 @@ import { ensureSignin, iAmAdmin, iAmModerator } from '@/i.js'; import MkRolePreview from '@/components/MkRolePreview.vue'; import MkPagination from '@/components/MkPagination.vue'; import { Paginator } from '@/utility/paginator.js'; +import type { ChartSrc } from '@/components/MkChart.vue'; const $i = ensureSignin(); @@ -246,7 +247,7 @@ const props = withDefaults(defineProps<{ const result = await _fetch_(); const tab = ref(props.initialTab); -const chartSrc = ref('per-user-notes'); +const chartSrc = ref<ChartSrc>('per-user-notes'); const user = ref(result.user); const info = ref(result.info); const ips = ref(result.ips); @@ -429,7 +430,7 @@ async function assignRole() { title: i18n.ts._role.chooseRoleToAssign, items: roles.map(r => ({ text: r.name, value: r.id })), }); - if (canceled) return; + if (canceled || roleId == null) return; const { canceled: canceled2, result: period } = await os.select({ title: i18n.ts.period + ': ' + roles.find(r => r.id === roleId)!.name, diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.register.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.register.vue index 621ec8a6a8..176d1121c5 100644 --- a/packages/frontend/src/pages/admin/custom-emojis-manager.register.vue +++ b/packages/frontend/src/pages/admin/custom-emojis-manager.register.vue @@ -303,8 +303,8 @@ async function onFileSelectClicked() { const driveFiles = await chooseFileFromPcAndUpload({ multiple: true, folderId: selectedFolderId.value, - // 拡張子は消す - nameConverter: (file) => file.name.replace(/\.[a-zA-Z0-9]+$/, ''), + // // 拡張子は消す + // nameConverter: (file) => file.name.replace(/\.[a-zA-Z0-9]+$/, ''), }); gridItems.value.push(...driveFiles.map(fromDriveFile)); diff --git a/packages/frontend/src/pages/admin/federation-job-queue.chart.chart.vue b/packages/frontend/src/pages/admin/federation-job-queue.chart.chart.vue index 9a311b5772..420219c22c 100644 --- a/packages/frontend/src/pages/admin/federation-job-queue.chart.chart.vue +++ b/packages/frontend/src/pages/admin/federation-job-queue.chart.chart.vue @@ -26,10 +26,10 @@ const chartEl = useTemplateRef('chartEl'); const { handler: externalTooltipHandler } = useChartTooltip(); -let chartInstance: Chart; +let chartInstance: Chart | null = null; function setData(values) { - if (chartInstance == null) return; + if (chartInstance == null || chartInstance.data.labels == null) return; for (const value of values) { chartInstance.data.labels.push(''); chartInstance.data.datasets[0].data.push(value); @@ -42,7 +42,7 @@ function setData(values) { } function pushData(value) { - if (chartInstance == null) return; + if (chartInstance == null || chartInstance.data.labels == null) return; chartInstance.data.labels.push(''); chartInstance.data.datasets[0].data.push(value); if (chartInstance.data.datasets[0].data.length > 200) { @@ -69,6 +69,8 @@ const color = onMounted(() => { const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; + if (chartEl.value == null) return; + chartInstance = new Chart(chartEl.value, { type: 'line', data: { diff --git a/packages/frontend/src/pages/admin/job-queue.vue b/packages/frontend/src/pages/admin/job-queue.vue index 0856bac860..b18049cb11 100644 --- a/packages/frontend/src/pages/admin/job-queue.vue +++ b/packages/frontend/src/pages/admin/job-queue.vue @@ -210,6 +210,7 @@ async function fetchCurrentQueue() { } async function fetchJobs() { + if (tab.value === '-') return; jobsFetching.value = true; const state = jobState.value; jobs.value = await misskeyApi('admin/queue/jobs', { @@ -307,6 +308,7 @@ async function removeJobs() { } async function refreshJob(jobId: string) { + if (tab.value === '-') return; const newJob = await misskeyApi('admin/queue/show-job', { queue: tab.value, jobId }); const index = jobs.value.findIndex((job) => job.id === jobId); if (index !== -1) { diff --git a/packages/frontend/src/pages/admin/overview.active-users.vue b/packages/frontend/src/pages/admin/overview.active-users.vue index 6c85f11cb1..32a5a6976e 100644 --- a/packages/frontend/src/pages/admin/overview.active-users.vue +++ b/packages/frontend/src/pages/admin/overview.active-users.vue @@ -26,7 +26,7 @@ initChart(); const chartEl = useTemplateRef('chartEl'); const now = new Date(); -let chartInstance: Chart = null; +let chartInstance: Chart | null = null; const chartLimit = 7; const fetching = ref(true); diff --git a/packages/frontend/src/pages/admin/overview.federation.vue b/packages/frontend/src/pages/admin/overview.federation.vue index 50f12cbf45..3c737ad32b 100644 --- a/packages/frontend/src/pages/admin/overview.federation.vue +++ b/packages/frontend/src/pages/admin/overview.federation.vue @@ -23,9 +23,9 @@ SPDX-License-Identifier: AGPL-3.0-only <div class="item _panel sub"> <div class="icon"><i class="ti ti-world-download"></i></div> <div class="body"> - <div class="value"> + <div v-if="federationSubActive != null" class="value"> {{ number(federationSubActive) }} - <MkNumberDiff v-tooltip="i18n.ts.dayOverDayChanges" class="diff" :value="federationSubActiveDiff"></MkNumberDiff> + <MkNumberDiff v-if="federationSubActiveDiff != null" v-tooltip="i18n.ts.dayOverDayChanges" class="diff" :value="federationSubActiveDiff"></MkNumberDiff> </div> <div class="label">Sub</div> </div> @@ -33,9 +33,9 @@ SPDX-License-Identifier: AGPL-3.0-only <div class="item _panel pub"> <div class="icon"><i class="ti ti-world-upload"></i></div> <div class="body"> - <div class="value"> + <div v-if="federationPubActive != null" class="value"> {{ number(federationPubActive) }} - <MkNumberDiff v-tooltip="i18n.ts.dayOverDayChanges" class="diff" :value="federationPubActiveDiff"></MkNumberDiff> + <MkNumberDiff v-if="federationPubActiveDiff != null" v-tooltip="i18n.ts.dayOverDayChanges" class="diff" :value="federationPubActiveDiff"></MkNumberDiff> </div> <div class="label">Pub</div> </div> diff --git a/packages/frontend/src/pages/admin/overview.heatmap.vue b/packages/frontend/src/pages/admin/overview.heatmap.vue index 7b2b142b16..6192d6eb0f 100644 --- a/packages/frontend/src/pages/admin/overview.heatmap.vue +++ b/packages/frontend/src/pages/admin/overview.heatmap.vue @@ -20,8 +20,9 @@ SPDX-License-Identifier: AGPL-3.0-only import { ref } from 'vue'; import MkHeatmap from '@/components/MkHeatmap.vue'; import MkSelect from '@/components/MkSelect.vue'; +import type { HeatmapSource } from '@/components/MkHeatmap.vue'; -const src = ref('active-users'); +const src = ref<HeatmapSource>('active-users'); </script> <style lang="scss" module> diff --git a/packages/frontend/src/pages/admin/overview.pie.vue b/packages/frontend/src/pages/admin/overview.pie.vue index ec2b558cee..2e874b3505 100644 --- a/packages/frontend/src/pages/admin/overview.pie.vue +++ b/packages/frontend/src/pages/admin/overview.pie.vue @@ -32,15 +32,17 @@ const { handler: externalTooltipHandler } = useChartTooltip({ position: 'middle', }); -let chartInstance: Chart; +let chartInstance: Chart | null = null; onMounted(() => { + if (chartEl.value == null) return; + chartInstance = new Chart(chartEl.value, { type: 'doughnut', data: { labels: props.data.map(x => x.name), datasets: [{ - backgroundColor: props.data.map(x => x.color), + backgroundColor: props.data.map(x => x.color ?? '#000'), borderColor: getComputedStyle(window.document.documentElement).getPropertyValue('--MI_THEME-panel'), borderWidth: 2, hoverOffset: 0, @@ -57,9 +59,10 @@ onMounted(() => { }, }, onClick: (ev) => { - const hit = chartInstance.getElementsAtEventForMode(ev, 'nearest', { intersect: true }, false)[0]; - if (hit && props.data[hit.index].onClick) { - props.data[hit.index].onClick(); + if (ev.native == null) return; + const hit = chartInstance!.getElementsAtEventForMode(ev.native, 'nearest', { intersect: true }, false)[0]; + if (hit && props.data[hit.index].onClick != null) { + props.data[hit.index].onClick!(); } }, plugins: { diff --git a/packages/frontend/src/pages/admin/overview.queue.chart.vue b/packages/frontend/src/pages/admin/overview.queue.chart.vue index 9b9618c4ac..771b35c09f 100644 --- a/packages/frontend/src/pages/admin/overview.queue.chart.vue +++ b/packages/frontend/src/pages/admin/overview.queue.chart.vue @@ -26,10 +26,10 @@ const chartEl = useTemplateRef('chartEl'); const { handler: externalTooltipHandler } = useChartTooltip(); -let chartInstance: Chart; +let chartInstance: Chart | null = null; -function setData(values) { - if (chartInstance == null) return; +function setData(values: number[]) { + if (chartInstance == null || chartInstance.data.labels == null) return; for (const value of values) { chartInstance.data.labels.push(''); chartInstance.data.datasets[0].data.push(value); @@ -41,8 +41,8 @@ function setData(values) { chartInstance.update(); } -function pushData(value) { - if (chartInstance == null) return; +function pushData(value: number) { + if (chartInstance == null || chartInstance.data.labels == null) return; chartInstance.data.labels.push(''); chartInstance.data.datasets[0].data.push(value); if (chartInstance.data.datasets[0].data.length > 100) { @@ -67,6 +67,8 @@ const color = '?' as never; onMounted(() => { + if (chartEl.value == null) return; + const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; chartInstance = new Chart(chartEl.value, { diff --git a/packages/frontend/src/pages/admin/overview.queue.vue b/packages/frontend/src/pages/admin/overview.queue.vue index e7e139b74d..e57df3744a 100644 --- a/packages/frontend/src/pages/admin/overview.queue.vue +++ b/packages/frontend/src/pages/admin/overview.queue.vue @@ -38,7 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { markRaw, onMounted, onUnmounted, ref, useTemplateRef } from 'vue'; import * as Misskey from 'misskey-js'; import XChart from './overview.queue.chart.vue'; -import type { ApQueueDomain } from '@/pages/admin/queue.vue'; +import type { ApQueueDomain } from '@/pages/admin/federation-job-queue.vue'; import number from '@/filters/number.js'; import { useStream } from '@/stream.js'; import { genId } from '@/utility/id.js'; @@ -64,10 +64,10 @@ function onStats(stats: Misskey.entities.QueueStats) { delayed.value = stats[props.domain].delayed; waiting.value = stats[props.domain].waiting; - chartProcess.value.pushData(stats[props.domain].activeSincePrevTick); - chartActive.value.pushData(stats[props.domain].active); - chartDelayed.value.pushData(stats[props.domain].delayed); - chartWaiting.value.pushData(stats[props.domain].waiting); + chartProcess.value?.pushData(stats[props.domain].activeSincePrevTick); + chartActive.value?.pushData(stats[props.domain].active); + chartDelayed.value?.pushData(stats[props.domain].delayed); + chartWaiting.value?.pushData(stats[props.domain].waiting); } function onStatsLog(statsLog: Misskey.entities.QueueStatsLog) { @@ -83,10 +83,10 @@ function onStatsLog(statsLog: Misskey.entities.QueueStatsLog) { dataWaiting.push(stats[props.domain].waiting); } - chartProcess.value.setData(dataProcess); - chartActive.value.setData(dataActive); - chartDelayed.value.setData(dataDelayed); - chartWaiting.value.setData(dataWaiting); + chartProcess.value?.setData(dataProcess); + chartActive.value?.setData(dataActive); + chartDelayed.value?.setData(dataDelayed); + chartWaiting.value?.setData(dataWaiting); } onMounted(() => { diff --git a/packages/frontend/src/pages/admin/overview.stats.vue b/packages/frontend/src/pages/admin/overview.stats.vue index fd8145b308..b0669bc557 100644 --- a/packages/frontend/src/pages/admin/overview.stats.vue +++ b/packages/frontend/src/pages/admin/overview.stats.vue @@ -7,13 +7,13 @@ SPDX-License-Identifier: AGPL-3.0-only <div> <Transition :name="prefer.s.animation ? '_transition_zoom' : ''" mode="out-in"> <MkLoading v-if="fetching"/> - <div v-else :class="$style.root"> + <div v-else-if="stats != null" :class="$style.root"> <div class="item _panel users"> <div class="icon"><i class="ti ti-users"></i></div> <div class="body"> <div class="value"> <MkNumber :value="stats.originalUsersCount" style="margin-right: 0.5em;"/> - <MkNumberDiff v-tooltip="i18n.ts.dayOverDayChanges" class="diff" :value="usersComparedToThePrevDay"></MkNumberDiff> + <MkNumberDiff v-if="usersComparedToThePrevDay != null" v-tooltip="i18n.ts.dayOverDayChanges" class="diff" :value="usersComparedToThePrevDay"></MkNumberDiff> </div> <div class="label">Users</div> </div> @@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div class="body"> <div class="value"> <MkNumber :value="stats.originalNotesCount" style="margin-right: 0.5em;"/> - <MkNumberDiff v-tooltip="i18n.ts.dayOverDayChanges" class="diff" :value="notesComparedToThePrevDay"></MkNumberDiff> + <MkNumberDiff v-if="notesComparedToThePrevDay != null" v-tooltip="i18n.ts.dayOverDayChanges" class="diff" :value="notesComparedToThePrevDay"></MkNumberDiff> </div> <div class="label">Notes</div> </div> @@ -56,6 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </div> </div> + <MkError v-else/> </Transition> </div> </template> @@ -71,8 +72,8 @@ import { customEmojis } from '@/custom-emojis.js'; import { prefer } from '@/preferences.js'; const stats = ref<Misskey.entities.StatsResponse | null>(null); -const usersComparedToThePrevDay = ref<number>(); -const notesComparedToThePrevDay = ref<number>(); +const usersComparedToThePrevDay = ref<number | null>(null); +const notesComparedToThePrevDay = ref<number | null>(null); const onlineUsersCount = ref(0); const fetching = ref(true); @@ -85,11 +86,11 @@ onMounted(async () => { onlineUsersCount.value = _onlineUsersCount; misskeyApiGet('charts/users', { limit: 2, span: 'day' }).then(chart => { - usersComparedToThePrevDay.value = stats.value.originalUsersCount - chart.local.total[1]; + usersComparedToThePrevDay.value = _stats.originalUsersCount - chart.local.total[1]; }); misskeyApiGet('charts/notes', { limit: 2, span: 'day' }).then(chart => { - notesComparedToThePrevDay.value = stats.value.originalNotesCount - chart.local.total[1]; + notesComparedToThePrevDay.value = _stats.originalNotesCount - chart.local.total[1]; }); fetching.value = false; diff --git a/packages/frontend/src/pages/admin/overview.vue b/packages/frontend/src/pages/admin/overview.vue index 2ad5173618..2c550bd9c3 100644 --- a/packages/frontend/src/pages/admin/overview.vue +++ b/packages/frontend/src/pages/admin/overview.vue @@ -95,7 +95,7 @@ const federationPubActiveDiff = ref<number | null>(null); const federationSubActive = ref<number | null>(null); const federationSubActiveDiff = ref<number | null>(null); const newUsers = ref<Misskey.entities.UserDetailed[] | null>(null); -const activeInstances = shallowRef<Misskey.entities.FederationInstance | null>(null); +const activeInstances = shallowRef<Misskey.entities.FederationInstancesResponse | null>(null); const queueStatsConnection = markRaw(useStream().useChannel('queueStats')); const now = new Date(); const filesPagination = { diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index e98b4f0129..66004a44bb 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -830,7 +830,6 @@ import { watch, ref, computed } from 'vue'; import { throttle } from 'throttle-debounce'; import * as Misskey from 'misskey-js'; import RolesEditorFormula from './RolesEditorFormula.vue'; -import type { GetMkSelectValueTypesFromDef, MkSelectItem } from '@/components/MkSelect.vue'; import MkInput from '@/components/MkInput.vue'; import MkColorInput from '@/components/MkColorInput.vue'; import MkSelect from '@/components/MkSelect.vue'; diff --git a/packages/frontend/src/pages/admin/roles.role.vue b/packages/frontend/src/pages/admin/roles.role.vue index c6c3165828..8e83cdb667 100644 --- a/packages/frontend/src/pages/admin/roles.role.vue +++ b/packages/frontend/src/pages/admin/roles.role.vue @@ -71,7 +71,7 @@ import { Paginator } from '@/utility/paginator.js'; const router = useRouter(); const props = defineProps<{ - id?: string; + id: string; }>(); const usersPaginator = markRaw(new Paginator('admin/roles/users', { diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue index 5323d042cf..6483bf16a8 100644 --- a/packages/frontend/src/pages/admin/roles.vue +++ b/packages/frontend/src/pages/admin/roles.vue @@ -346,6 +346,7 @@ import { definePage } from '@/page.js'; import { instance, fetchInstance } from '@/instance.js'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; import { useRouter } from '@/router.js'; +import { deepClone } from '@/utility/clone.js'; import MkTextarea from '@/components/MkTextarea.vue'; const router = useRouter(); @@ -353,10 +354,7 @@ const baseRoleQ = ref(''); const roles = await misskeyApi('admin/roles/list'); -const policies = reactive<Record<typeof Misskey.rolePolicies[number], any>>({}); -for (const ROLE_POLICY of Misskey.rolePolicies) { - policies[ROLE_POLICY] = instance.policies[ROLE_POLICY]; -} +const policies = reactive(deepClone(instance.policies)); const avatarDecorationLimit = computed({ get: () => Math.min(16, Math.max(0, policies.avatarDecorationLimit)), @@ -376,6 +374,7 @@ function matchQuery(keywords: string[]): boolean { async function updateBaseRole() { await os.apiWithDialog('admin/roles/update-default-policies', { + //@ts-expect-error misskey-js側の型定義が不十分 policies, }); fetchInstance(true); diff --git a/packages/frontend/src/pages/gallery/post.vue b/packages/frontend/src/pages/gallery/post.vue index eab435c002..31a716fb0e 100644 --- a/packages/frontend/src/pages/gallery/post.vue +++ b/packages/frontend/src/pages/gallery/post.vue @@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> <div class="body"> <div class="title">{{ post.title }}</div> - <div class="description"><Mfm :text="post.description"/></div> + <div class="description"><Mfm v-if="post.description != null" :text="post.description"/></div> <div class="info"> <i class="ti ti-clock"></i> <MkTime :time="post.createdAt" mode="detail"/> </div> @@ -93,7 +93,7 @@ const error = ref<any>(null); const otherPostsPaginator = markRaw(new Paginator('users/gallery/posts', { limit: 6, computedParams: computed(() => ({ - userId: post.value.user.id, + userId: post.value!.user.id, })), })); @@ -109,33 +109,38 @@ function fetchPost() { } function copyLink() { + if (!post.value) return; copyToClipboard(`${url}/gallery/${post.value.id}`); } function share() { + if (!post.value) return; navigator.share({ title: post.value.title, - text: post.value.description, + text: post.value.description ?? undefined, url: `${url}/gallery/${post.value.id}`, }); } function shareWithNote() { + if (!post.value) return; os.post({ initialText: `${post.value.title} ${url}/gallery/${post.value.id}`, }); } function like() { + if (!post.value) return; os.apiWithDialog('gallery/posts/like', { postId: props.postId, }).then(() => { - post.value.isLiked = true; - post.value.likedCount++; + post.value!.isLiked = true; + post.value!.likedCount++; }); } async function unlike() { + if (!post.value) return; const confirm = await os.confirm({ type: 'warning', text: i18n.ts.unlikeConfirm, @@ -144,8 +149,8 @@ async function unlike() { os.apiWithDialog('gallery/posts/unlike', { postId: props.postId, }).then(() => { - post.value.isLiked = false; - post.value.likedCount--; + post.value!.isLiked = false; + post.value!.likedCount--; }); } diff --git a/packages/frontend/src/pages/list.vue b/packages/frontend/src/pages/list.vue index a52b562c7f..efb1186fe5 100644 --- a/packages/frontend/src/pages/list.vue +++ b/packages/frontend/src/pages/list.vue @@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </div> </div> - <MkButton v-if="list.isLiked" v-tooltip="i18n.ts.unlike" inline :class="$style.button" asLike primary @click="unlike()"><i class="ti ti-heart-off"></i><span v-if="list.likedCount > 0" class="count">{{ list.likedCount }}</span></MkButton> + <MkButton v-if="list.isLiked" v-tooltip="i18n.ts.unlike" inline :class="$style.button" asLike primary @click="unlike()"><i class="ti ti-heart-off"></i><span v-if="list.likedCount != null && list.likedCount > 0" class="count">{{ list.likedCount }}</span></MkButton> <MkButton v-if="!list.isLiked" v-tooltip="i18n.ts.like" inline :class="$style.button" asLike @click="like()"><i class="ti ti-heart"></i><span v-if="1 > 0" class="count">{{ list.likedCount }}</span></MkButton> <MkButton inline @click="create()"><i class="ti ti-download" :class="$style.import"></i>{{ i18n.ts.import }}</MkButton> </div> @@ -41,7 +41,7 @@ const props = defineProps<{ listId: string; }>(); -const list = ref<Misskey.entities.UserList | null>(null); +const list = ref<Misskey.entities.UsersListsShowResponse | null>(null); const error = ref<unknown | null>(null); const users = ref<Misskey.entities.UserDetailed[]>([]); @@ -51,8 +51,9 @@ function fetchList(): void { forPublic: true, }).then(_list => { list.value = _list; + if (_list.userIds == null || _list.userIds.length === 0) return; misskeyApi('users/show', { - userIds: list.value.userIds, + userIds: _list.userIds, }).then(_users => { users.value = _users; }); @@ -68,7 +69,7 @@ function like() { }).then(() => { if (list.value == null) return; list.value.isLiked = true; - list.value.likedCount++; + list.value.likedCount = (list.value.likedCount != null ? list.value.likedCount + 1 : 1); }); } @@ -79,7 +80,7 @@ function unlike() { }).then(() => { if (list.value == null) return; list.value.isLiked = false; - list.value.likedCount--; + list.value.likedCount = (list.value.likedCount != null ? Math.max(0, list.value.likedCount - 1) : 0); }); } @@ -88,7 +89,7 @@ async function create() { const { canceled, result: name } = await os.inputText({ title: i18n.ts.enterListName, }); - if (canceled) return; + if (canceled || name == null) return; await os.apiWithDialog('users/lists/create-from-public', { name: name, listId: list.value.id }); } diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue index f275ec9517..e596b31b43 100644 --- a/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue +++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue @@ -39,6 +39,7 @@ const props = defineProps<{ const emit = defineEmits<{ (ev: 'update:modelValue', value: Misskey.entities.PageBlock & { type: 'note' }): void; + (ev: 'remove'): void; }>(); const id = ref(props.modelValue.note); diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.section.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.section.vue index cf5712a8e5..bb0841965f 100644 --- a/packages/frontend/src/pages/page-editor/els/page-editor.el.section.vue +++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.section.vue @@ -71,7 +71,7 @@ async function add() { title: i18n.ts._pages.chooseBlock, items: getPageBlockList(), }); - if (canceled) return; + if (canceled || type == null) return; const id = genId(); children.value.push({ id, type }); diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue index 4a980ce472..079a28491b 100644 --- a/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue +++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue @@ -27,6 +27,7 @@ const props = defineProps<{ const emit = defineEmits<{ (ev: 'update:modelValue', value: Misskey.entities.PageBlock & { type: 'text' }): void; + (ev: 'remove'): void; }>(); let autocomplete: Autocomplete; @@ -42,6 +43,7 @@ watch(text, () => { }); onMounted(() => { + if (inputEl.value == null) return; autocomplete = new Autocomplete(inputEl.value, text); }); diff --git a/packages/frontend/src/pages/page-editor/page-editor.vue b/packages/frontend/src/pages/page-editor/page-editor.vue index 9fe03ae981..3c51f25676 100644 --- a/packages/frontend/src/pages/page-editor/page-editor.vue +++ b/packages/frontend/src/pages/page-editor/page-editor.vue @@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only <PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"> <div class="_spacer" style="--MI_SPACER-w: 700px;"> <div class="jqqmcavi"> - <MkButton v-if="pageId" class="button" inline link :to="`/@${ author.username }/pages/${ currentName }`"><i class="ti ti-external-link"></i> {{ i18n.ts._pages.viewPage }}</MkButton> + <MkButton v-if="pageId && author != null" class="button" inline link :to="`/@${ author.username }/pages/${ currentName }`"><i class="ti ti-external-link"></i> {{ i18n.ts._pages.viewPage }}</MkButton> <MkButton v-if="!readonly" inline primary class="button" @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> <MkButton v-if="pageId" inline class="button" @click="duplicate"><i class="ti ti-copy"></i> {{ i18n.ts.duplicate }}</MkButton> <MkButton v-if="pageId && !readonly" inline class="button" danger @click="del"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton> @@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only </MkInput> <MkInput v-model="name"> - <template #prefix>{{ url }}/@{{ author.username }}/pages/</template> + <template #prefix>{{ url }}/@{{ author?.username ?? '???' }}/pages/</template> <template #label>{{ i18n.ts._pages.url }}</template> </MkInput> @@ -85,7 +85,7 @@ const props = defineProps<{ }>(); const tab = ref('settings'); -const author = ref($i); +const author = ref<Misskey.entities.User | null>($i); const readonly = ref(false); const page = ref<Misskey.entities.Page | null>(null); const pageId = ref<string | null>(null); @@ -202,11 +202,10 @@ async function duplicate() { async function add() { const { canceled, result: type } = await os.select({ - type: null, title: i18n.ts._pages.chooseBlock, items: getPageBlockList(), }); - if (canceled) return; + if (canceled || type == null) return; const id = genId(); content.value.push({ id, type }); diff --git a/packages/frontend/src/pages/reversi/game.board.vue b/packages/frontend/src/pages/reversi/game.board.vue index 69429728d0..aae638641a 100644 --- a/packages/frontend/src/pages/reversi/game.board.vue +++ b/packages/frontend/src/pages/reversi/game.board.vue @@ -164,7 +164,7 @@ const $i = ensureSignin(); const props = defineProps<{ game: Misskey.entities.ReversiGameDetailed; - connection?: Misskey.ChannelConnection<Misskey.Channels['reversiGame']> | null; + connection?: Misskey.IChannelConnection<Misskey.Channels['reversiGame']> | null; }>(); const showBoardLabels = ref<boolean>(false); diff --git a/packages/frontend/src/pages/reversi/game.setting.vue b/packages/frontend/src/pages/reversi/game.setting.vue index 8392384963..1e01496bbb 100644 --- a/packages/frontend/src/pages/reversi/game.setting.vue +++ b/packages/frontend/src/pages/reversi/game.setting.vue @@ -132,7 +132,7 @@ const mapCategories = Array.from(new Set(Object.values(Reversi.maps).map(x => x. const props = defineProps<{ game: Misskey.entities.ReversiGameDetailed; - connection: Misskey.ChannelConnection<Misskey.Channels['reversiGame']>; + connection: Misskey.IChannelConnection<Misskey.Channels['reversiGame']>; }>(); const shareWhenStart = defineModel<boolean>('shareWhenStart', { default: false }); diff --git a/packages/frontend/src/pages/reversi/game.vue b/packages/frontend/src/pages/reversi/game.vue index a447572cc0..b1ba4da247 100644 --- a/packages/frontend/src/pages/reversi/game.vue +++ b/packages/frontend/src/pages/reversi/game.vue @@ -33,7 +33,7 @@ const props = defineProps<{ }>(); const game = shallowRef<Misskey.entities.ReversiGameDetailed | null>(null); -const connection = shallowRef<Misskey.ChannelConnection | null>(null); +const connection = shallowRef<Misskey.IChannelConnection<Misskey.Channels['reversiGame']> | null>(null); const shareWhenStart = ref(false); watch(() => props.gameId, () => { diff --git a/packages/frontend/src/pages/settings/2fa.vue b/packages/frontend/src/pages/settings/2fa.vue index ca404b43c4..2cc13744b1 100644 --- a/packages/frontend/src/pages/settings/2fa.vue +++ b/packages/frontend/src/pages/settings/2fa.vue @@ -196,6 +196,7 @@ async function addSecurityKey() { if (auth.canceled) return; const registrationOptions = parseCreationOptionsFromJSON({ + // @ts-expect-error misskey-js側に型がない publicKey: await os.apiWithDialog('i/2fa/register-key', { password: auth.result.password, token: auth.result.token, @@ -226,6 +227,7 @@ async function addSecurityKey() { password: auth.result.password, token: auth.result.token, name: name.result, + // @ts-expect-error misskey-js側に型がない credential: credential.toJSON(), }); } diff --git a/packages/frontend/src/pages/settings/privacy.vue b/packages/frontend/src/pages/settings/privacy.vue index 54a6c0af82..8ae8c79ebd 100644 --- a/packages/frontend/src/pages/settings/privacy.vue +++ b/packages/frontend/src/pages/settings/privacy.vue @@ -119,7 +119,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #label><SearchLabel>{{ i18n.ts._accountSettings.makeNotesFollowersOnlyBefore }}</SearchLabel></template> <div class="_gaps_s"> - <MkSelect :modelValue="makeNotesFollowersOnlyBefore_type" @update:modelValue="makeNotesFollowersOnlyBefore = $event === 'relative' ? -604800 : $event === 'absolute' ? Math.floor(Date.now() / 1000) : null"> + <MkSelect v-model="makeNotesFollowersOnlyBefore_type"> <option :value="null">{{ i18n.ts.none }}</option> <option value="relative">{{ i18n.ts._accountSettings.notesHavePassedSpecifiedPeriod }}</option> <option value="absolute">{{ i18n.ts._accountSettings.notesOlderThanSpecifiedDateAndTime }}</option> @@ -140,7 +140,7 @@ SPDX-License-Identifier: AGPL-3.0-only </MkInput> <MkInput - v-if="makeNotesFollowersOnlyBefore_type === 'absolute'" + v-if="makeNotesFollowersOnlyBefore_type === 'absolute' && makeNotesFollowersOnlyBefore != null" :modelValue="formatDateTimeString(new Date(makeNotesFollowersOnlyBefore * 1000), 'yyyy-MM-dd')" type="date" :manualSave="true" @@ -161,6 +161,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div class="_gaps_s"> <MkSelect + v-model="makeNotesHiddenBefore_type" :items="[{ value: null, label: i18n.ts.none @@ -170,7 +171,7 @@ SPDX-License-Identifier: AGPL-3.0-only }, { value: 'absolute', label: i18n.ts._accountSettings.notesOlderThanSpecifiedDateAndTime - }] as const" :modelValue="makeNotesHiddenBefore_type" @update:modelValue="makeNotesHiddenBefore = $event === 'relative' ? -604800 : $event === 'absolute' ? Math.floor(Date.now() / 1000) : null" + }]" > </MkSelect> @@ -189,7 +190,7 @@ SPDX-License-Identifier: AGPL-3.0-only </MkInput> <MkInput - v-if="makeNotesHiddenBefore_type === 'absolute'" + v-if="makeNotesHiddenBefore_type === 'absolute' && makeNotesHiddenBefore != null" :modelValue="formatDateTimeString(new Date(makeNotesHiddenBefore * 1000), 'yyyy-MM-dd')" type="date" :manualSave="true" @@ -217,7 +218,6 @@ import { ref, computed, watch } from 'vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkSelect from '@/components/MkSelect.vue'; import FormSection from '@/components/form/section.vue'; -import MkFolder from '@/components/MkFolder.vue'; import { misskeyApi } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; @@ -247,14 +247,25 @@ const followingVisibility = ref($i.followingVisibility); const followersVisibility = ref($i.followersVisibility); const chatScope = ref($i.chatScope); -const makeNotesFollowersOnlyBefore_type = computed(() => { - if (makeNotesFollowersOnlyBefore.value == null) { - return null; - } else if (makeNotesFollowersOnlyBefore.value >= 0) { - return 'absolute'; - } else { - return 'relative'; - } +const makeNotesFollowersOnlyBefore_type = computed({ + get: () => { + if (makeNotesFollowersOnlyBefore.value == null) { + return null; + } else if (makeNotesFollowersOnlyBefore.value >= 0) { + return 'absolute'; + } else { + return 'relative'; + } + }, + set(value) { + if (value === 'relative') { + makeNotesFollowersOnlyBefore.value = -604800; + } else if (value === 'absolute') { + makeNotesFollowersOnlyBefore.value = Math.floor(Date.now() / 1000); + } else { + makeNotesFollowersOnlyBefore.value = null; + } + }, }); const makeNotesFollowersOnlyBefore_presets = [ @@ -288,14 +299,25 @@ const makeNotesFollowersOnlyBefore_customMonths = computed({ }, }); -const makeNotesHiddenBefore_type = computed(() => { - if (makeNotesHiddenBefore.value == null) { - return null; - } else if (makeNotesHiddenBefore.value >= 0) { - return 'absolute'; - } else { - return 'relative'; - } +const makeNotesHiddenBefore_type = computed({ + get: () => { + if (makeNotesHiddenBefore.value == null) { + return null; + } else if (makeNotesHiddenBefore.value >= 0) { + return 'absolute'; + } else { + return 'relative'; + } + }, + set(value) { + if (value === 'relative') { + makeNotesHiddenBefore.value = -604800; + } else if (value === 'absolute') { + makeNotesHiddenBefore.value = Math.floor(Date.now() / 1000); + } else { + makeNotesHiddenBefore.value = null; + } + }, }); const makeNotesHiddenBefore_presets = [ diff --git a/packages/frontend/src/pages/settings/sounds.sound.vue b/packages/frontend/src/pages/settings/sounds.sound.vue index 7aad43b1d0..9e9671487e 100644 --- a/packages/frontend/src/pages/settings/sounds.sound.vue +++ b/packages/frontend/src/pages/settings/sounds.sound.vue @@ -41,25 +41,23 @@ import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { playMisskeySfxFile, soundsTypes, getSoundDuration } from '@/utility/sound.js'; import { selectFile } from '@/utility/drive.js'; +import type { SoundStore } from '@/preferences/def.js'; const props = defineProps<{ - type: SoundType; - fileId?: string; - fileUrl?: string; - volume: number; + def: SoundStore; }>(); const emit = defineEmits<{ (ev: 'update', result: { type: SoundType; fileId?: string; fileUrl?: string; volume: number; }): void; }>(); -const type = ref<SoundType>(props.type); -const fileId = ref(props.fileId); -const fileUrl = ref(props.fileUrl); +const type = ref<SoundType>(props.def.type); +const fileId = ref('fileId' in props.def ? props.def.fileId : undefined); +const fileUrl = ref('fileUrl' in props.def ? props.def.fileUrl : undefined); const fileName = ref<string>(''); const driveFileError = ref(false); const hasChanged = ref(false); -const volume = ref(props.volume); +const volume = ref(props.def.volume); if (type.value === '_driveFile_' && fileId.value) { await misskeyApi('drive/files/show', { diff --git a/packages/frontend/src/pages/settings/sounds.vue b/packages/frontend/src/pages/settings/sounds.vue index ea5b347525..1b851825d6 100644 --- a/packages/frontend/src/pages/settings/sounds.vue +++ b/packages/frontend/src/pages/settings/sounds.vue @@ -42,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #suffix>{{ getSoundTypeName(sounds[type].type) }}</template> <Suspense> <template #default> - <XSound :type="sounds[type].type" :volume="sounds[type].volume" :fileId="sounds[type].fileId" :fileUrl="sounds[type].fileUrl" @update="(res) => updated(type, res)"/> + <XSound :def="sounds[type]" @update="(res) => updated(type, res)"/> </template> <template #fallback> <MkLoading/> diff --git a/packages/frontend/src/pages/share.vue b/packages/frontend/src/pages/share.vue index 51ac9d66f0..368537ec91 100644 --- a/packages/frontend/src/pages/share.vue +++ b/packages/frontend/src/pages/share.vue @@ -112,8 +112,7 @@ async function init() { ...(visibleUserIds ? visibleUserIds.split(',').map(userId => ({ userId })) : []), ...(visibleAccts ? visibleAccts.split(',').map(Misskey.acct.parse) : []), ] - // TypeScriptの指示通りに変換する - .map(q => 'username' in q ? { username: q.username, host: q.host === null ? undefined : q.host } : q) + // @ts-expect-error payloadの引数側の型が正常に解決されない .map(q => misskeyApi('users/show', q) .then(user => { visibleUsers.value.push(user); diff --git a/packages/frontend/src/pages/user/lists.vue b/packages/frontend/src/pages/user/lists.vue index 6c9204ae22..8824acb33e 100644 --- a/packages/frontend/src/pages/user/lists.vue +++ b/packages/frontend/src/pages/user/lists.vue @@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkPagination v-slot="{items}" :paginator="paginator" withControl> <MkA v-for="list in items" :key="list.id" class="_panel" :class="$style.list" :to="`/list/${ list.id }`"> <div>{{ list.name }}</div> - <MkAvatars :userIds="list.userIds"/> + <MkAvatars v-if="list.userIds != null" :userIds="list.userIds"/> </MkA> </MkPagination> </div> diff --git a/packages/frontend/src/plugin.ts b/packages/frontend/src/plugin.ts index e6545bb8e7..f32c991828 100644 --- a/packages/frontend/src/plugin.ts +++ b/packages/frontend/src/plugin.ts @@ -7,7 +7,7 @@ import { ref } from 'vue'; import { compareVersions } from 'compare-versions'; import { isSafeMode } from '@@/js/config.js'; import * as Misskey from 'misskey-js'; -import type { Parser, Interpreter, values } from '@syuilo/aiscript'; +import type { Parser, Interpreter, values, utils as utils_TypeReferenceOnly } from '@syuilo/aiscript'; import type { FormWithDefault } from '@/utility/form.js'; import { genId } from '@/utility/id.js'; import { store } from '@/store.js'; @@ -82,22 +82,23 @@ export async function parsePluginMeta(code: string): Promise<AiScriptPluginMeta> } const metadata = meta.get(null); - if (metadata == null) { - throw new Error('Metadata not found'); + if (metadata == null || typeof metadata !== 'object' || Array.isArray(metadata)) { + throw new Error('Metadata not found or invalid'); } const { name, version, author, description, permissions, config } = metadata; + if (name == null || version == null || author == null) { throw new Error('Required property not found'); } return { - name, - version, - author, - description, - permissions, - config, + name: name as string, + version: version as string, + author: author as string, + description: description as string | undefined, + permissions: permissions as string[] | undefined, + config: config as Record<string, any> | undefined, }; } @@ -110,7 +111,7 @@ export async function authorizePlugin(plugin: Plugin) { title: i18n.ts.tokenRequested, information: i18n.ts.pluginTokenRequestedDescription, initialName: plugin.name, - initialPermissions: plugin.permissions, + initialPermissions: plugin.permissions as typeof Misskey.permissions[number][], }, { done: async result => { const { name, permissions } = result; @@ -149,6 +150,7 @@ export async function installPlugin(code: string, meta?: AiScriptPluginMeta) { const plugin = { ...realMeta, + config: realMeta.config ?? {}, installId, active: true, configData: {}, @@ -353,7 +355,9 @@ export function changePluginActive(plugin: Plugin, active: boolean) { async function createPluginEnv(opts: { plugin: Plugin; storageKey: string }): Promise<Record<string, values.Value>> { const id = opts.plugin.installId; - const { utils, values } = await import('@syuilo/aiscript'); + const ais = await import('@syuilo/aiscript'); + const values = ais.values; + const utils: typeof utils_TypeReferenceOnly = ais.utils; const { createAiScriptEnv } = await import('@/aiscript/api.js'); const config = new Map<string, values.Value>(); @@ -375,7 +379,7 @@ async function createPluginEnv(opts: { plugin: Plugin; storageKey: string }): Pr utils.assertFunction(handler); addPluginHandler(id, 'post_form_action', { title: title.value, - handler: withContext(ctx => (form, update) => { + handler: (form, update) => withContext(ctx => { ctx.execFn(handler, [utils.jsToVal(form), values.FN_NATIVE(([key, value]) => { if (!key || !value) { return; @@ -391,7 +395,7 @@ async function createPluginEnv(opts: { plugin: Plugin; storageKey: string }): Pr utils.assertFunction(handler); addPluginHandler(id, 'user_action', { title: title.value, - handler: withContext(ctx => (user) => { + handler: (user) => withContext(ctx => { ctx.execFn(handler, [utils.jsToVal(user)]); }), }); @@ -402,7 +406,7 @@ async function createPluginEnv(opts: { plugin: Plugin; storageKey: string }): Pr utils.assertFunction(handler); addPluginHandler(id, 'note_action', { title: title.value, - handler: withContext(ctx => (note) => { + handler: (note) => withContext(ctx => { ctx.execFn(handler, [utils.jsToVal(note)]); }), }); @@ -411,8 +415,8 @@ async function createPluginEnv(opts: { plugin: Plugin; storageKey: string }): Pr 'Plugin:register:note_view_interruptor': values.FN_NATIVE(([handler]) => { utils.assertFunction(handler); addPluginHandler(id, 'note_view_interruptor', { - handler: withContext(ctx => (note) => { - return utils.valToJs(ctx.execFnSync(handler, [utils.jsToVal(note)])); + handler: (note) => withContext(ctx => { + return utils.valToJs(ctx.execFnSync(handler, [utils.jsToVal(note)])) as Misskey.entities.Note | null; }), }); }), @@ -420,8 +424,8 @@ async function createPluginEnv(opts: { plugin: Plugin; storageKey: string }): Pr 'Plugin:register:note_post_interruptor': values.FN_NATIVE(([handler]) => { utils.assertFunction(handler); addPluginHandler(id, 'note_post_interruptor', { - handler: withContext(ctx => async (note) => { - return utils.valToJs(await ctx.execFn(handler, [utils.jsToVal(note)])); + handler: (note) => withContext(ctx => { + return utils.valToJs(ctx.execFnSync(handler, [utils.jsToVal(note)])); }), }); }), @@ -429,8 +433,8 @@ async function createPluginEnv(opts: { plugin: Plugin; storageKey: string }): Pr 'Plugin:register:page_view_interruptor': values.FN_NATIVE(([handler]) => { utils.assertFunction(handler); addPluginHandler(id, 'page_view_interruptor', { - handler: withContext(ctx => async (page) => { - return utils.valToJs(await ctx.execFn(handler, [utils.jsToVal(page)])); + handler: (page) => withContext(ctx => { + return utils.valToJs(ctx.execFnSync(handler, [utils.jsToVal(page)])) as Misskey.entities.Page; }), }); }), diff --git a/packages/frontend/src/pref-migrate.ts b/packages/frontend/src/pref-migrate.ts index 3054978ae4..8258bbb846 100644 --- a/packages/frontend/src/pref-migrate.ts +++ b/packages/frontend/src/pref-migrate.ts @@ -25,11 +25,14 @@ export function migrateOldSettings() { }); const plugins = ColdDeviceStorage.get('plugins'); - prefer.commit('plugins', plugins.map(p => ({ - ...p, - installId: (p as any).id, - id: undefined, - }))); + prefer.commit('plugins', plugins.map(p => { + const { id, ...rest } = p; + return { + ...rest, + config: rest.config ?? {}, + installId: id, + }; + })); prefer.commit('deck.profile', deckStore.s.profile); misskeyApi('i/registry/keys', { @@ -115,7 +118,13 @@ export function migrateOldSettings() { prefer.commit('enableCondensedLine', store.s.enableCondensedLine); prefer.commit('keepScreenOn', store.s.keepScreenOn); prefer.commit('useGroupedNotifications', store.s.useGroupedNotifications); - prefer.commit('dataSaver', store.s.dataSaver); + prefer.commit('dataSaver', { + ...prefer.s.dataSaver, + media: store.s.dataSaver.media, + avatar: store.s.dataSaver.avatar, + urlPreviewThumbnail: store.s.dataSaver.urlPreview, + code: store.s.dataSaver.code, + }); prefer.commit('enableSeasonalScreenEffect', store.s.enableSeasonalScreenEffect); prefer.commit('enableHorizontalSwipe', store.s.enableHorizontalSwipe); prefer.commit('useNativeUiForVideoAudioPlayer', store.s.useNativeUIForVideoAudioPlayer); diff --git a/packages/frontend/src/preferences/def.ts b/packages/frontend/src/preferences/def.ts index 414aa34753..8fd71aa51a 100644 --- a/packages/frontend/src/preferences/def.ts +++ b/packages/frontend/src/preferences/def.ts @@ -41,6 +41,14 @@ export type StatusbarStore = { props: Record<string, any>; }; +export type DataSaverStore = { + media: boolean; + avatar: boolean; + urlPreviewThumbnail: boolean; + disableUrlPreview: boolean; + code: boolean; +}; + type OmitStrict<T, K extends keyof T> = T extends any ? Pick<T, Exclude<keyof T, K>> : never; // NOTE: デフォルト値は他の設定の状態に依存してはならない(依存していた場合、ユーザーがその設定項目単体で「初期値にリセット」した場合不具合の原因になる) @@ -332,7 +340,7 @@ export const PREF_DEF = definePreferences({ urlPreviewThumbnail: false, disableUrlPreview: false, code: false, - } satisfies Record<string, boolean>, + } as DataSaverStore, }, hemisphere: { default: hemisphere as 'N' | 'S', diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index 750ca69133..87b2637a64 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -381,7 +381,7 @@ export const store = markRaw(new Pizzax('base', { avatar: false, urlPreview: false, code: false, - } as Record<string, boolean>, + }, }, enableSeasonalScreenEffect: { where: 'device', @@ -483,7 +483,7 @@ export class ColdDeviceStorage { lightTheme, // TODO: 消す(preferに移行済みのため) darkTheme, // TODO: 消す(preferに移行済みのため) syncDeviceDarkMode: true, // TODO: 消す(preferに移行済みのため) - plugins: [] as Plugin[], // TODO: 消す(preferに移行済みのため) + plugins: [] as (Omit<Plugin, 'installId'> & { id: string })[], // TODO: 消す(preferに移行済みのため) }; public static watchers: Watcher[] = []; diff --git a/packages/frontend/src/utility/code-highlighter.ts b/packages/frontend/src/utility/code-highlighter.ts index 7dca18d58f..4fdaf24202 100644 --- a/packages/frontend/src/utility/code-highlighter.ts +++ b/packages/frontend/src/utility/code-highlighter.ts @@ -36,7 +36,7 @@ export async function getTheme(mode: 'light' | 'dark', getName = false): Promise _res = deepClone(theme.codeHighlighter.overrides); } else { const base = await bundledThemesInfo.find(t => t.id === theme.codeHighlighter!.base)?.import() ?? darkPlus; - _res = deepMerge(theme.codeHighlighter.overrides ?? {}, 'default' in base ? base.default : base); + _res = deepMerge<ThemeRegistration>(theme.codeHighlighter.overrides ?? {}, 'default' in base ? base.default : base); } if (_res.name == null) { _res.name = theme.id; diff --git a/packages/frontend/src/utility/form.ts b/packages/frontend/src/utility/form.ts index 2b765dc714..b0b09f0f6b 100644 --- a/packages/frontend/src/utility/form.ts +++ b/packages/frontend/src/utility/form.ts @@ -130,11 +130,11 @@ type GetItemType<Item extends FormItem> = : Item extends RadioFormItem ? GetRadioItemType<Item> : Item extends RangeFormItem - ? NonNullableIfRequired<InferDefault<RangeFormItem, number>, Item> + ? NonNullableIfRequired<InferDefault<Item, number>, Item> : Item extends EnumFormItem ? GetEnumItemType<Item> : Item extends ArrayFormItem - ? NonNullableIfRequired<InferDefault<ArrayFormItem, unknown[]>, Item> + ? NonNullableIfRequired<InferDefault<Item, unknown[]>, Item> : Item extends ObjectFormItem ? NonNullableIfRequired<InferDefault<Item, Record<string, unknown>>, Item> : Item extends DriveFileFormItem diff --git a/packages/frontend/src/utility/get-user-menu.ts b/packages/frontend/src/utility/get-user-menu.ts index d4407dadec..d6ddebc868 100644 --- a/packages/frontend/src/utility/get-user-menu.ts +++ b/packages/frontend/src/utility/get-user-menu.ts @@ -289,7 +289,6 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router caseSensitive: antenna.caseSensitive, withReplies: antenna.withReplies, withFile: antenna.withFile, - notify: antenna.notify, }); antennasCache.delete(); }, |