diff options
| author | syuilo <4439005+syuilo@users.noreply.github.com> | 2025-08-26 13:34:41 +0900 |
|---|---|---|
| committer | syuilo <4439005+syuilo@users.noreply.github.com> | 2025-08-26 13:34:41 +0900 |
| commit | d6a1046361d3d38726f2a86588960c3614f72a9f (patch) | |
| tree | d0a501710c36b8e9488e6756adc906c08447910b /packages/frontend/src | |
| parent | refactor and fix (diff) | |
| download | misskey-d6a1046361d3d38726f2a86588960c3614f72a9f.tar.gz misskey-d6a1046361d3d38726f2a86588960c3614f72a9f.tar.bz2 misskey-d6a1046361d3d38726f2a86588960c3614f72a9f.zip | |
refactor
Diffstat (limited to 'packages/frontend/src')
35 files changed, 178 insertions, 125 deletions
diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index 6ae8379801..18817d3f79 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -368,11 +368,6 @@ export async function mainBoot() { }); }); - main.on('unreadAntenna', () => { - updateCurrentAccountPartial({ hasUnreadAntenna: true }); - sound.playMisskeySfx('antenna'); - }); - main.on('newChatMessage', () => { updateCurrentAccountPartial({ hasUnreadChatMessages: true }); sound.playMisskeySfx('chatMessage'); diff --git a/packages/frontend/src/components/MkAchievements.vue b/packages/frontend/src/components/MkAchievements.vue index 3b7b59b4d3..bf39c1e983 100644 --- a/packages/frontend/src/components/MkAchievements.vue +++ b/packages/frontend/src/components/MkAchievements.vue @@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only [$style.iconFrame_platinum]: ACHIEVEMENT_BADGES[achievement.name].frame === 'platinum', }]" > - <div :class="[$style.iconInner]" :style="{ background: ACHIEVEMENT_BADGES[achievement.name].bg }"> + <div :class="[$style.iconInner]" :style="{ background: ACHIEVEMENT_BADGES[achievement.name].bg ?? '' }"> <img :class="$style.iconImg" :src="ACHIEVEMENT_BADGES[achievement.name].img"> </div> </div> diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue index 4d67bba70d..c54081ad42 100644 --- a/packages/frontend/src/components/MkChart.vue +++ b/packages/frontend/src/components/MkChart.vue @@ -589,7 +589,10 @@ const fetchDriveFilesChart = async (): Promise<typeof chartData> => { }; const fetchInstanceRequestsChart = async (): Promise<typeof chartData> => { - const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span }); + const host = props.args?.host; + if (host == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span }); return { series: [{ name: 'In', @@ -611,7 +614,10 @@ const fetchInstanceRequestsChart = async (): Promise<typeof chartData> => { }; const fetchInstanceUsersChart = async (total: boolean): Promise<typeof chartData> => { - const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span }); + const host = props.args?.host; + if (host == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span }); return { series: [{ name: 'Users', @@ -626,7 +632,10 @@ const fetchInstanceUsersChart = async (total: boolean): Promise<typeof chartData }; const fetchInstanceNotesChart = async (total: boolean): Promise<typeof chartData> => { - const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span }); + const host = props.args?.host; + if (host == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span }); return { series: [{ name: 'Notes', @@ -641,7 +650,10 @@ const fetchInstanceNotesChart = async (total: boolean): Promise<typeof chartData }; const fetchInstanceFfChart = async (total: boolean): Promise<typeof chartData> => { - const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span }); + const host = props.args?.host; + if (host == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span }); return { series: [{ name: 'Following', @@ -664,7 +676,10 @@ const fetchInstanceFfChart = async (total: boolean): Promise<typeof chartData> = }; const fetchInstanceDriveUsageChart = async (total: boolean): Promise<typeof chartData> => { - const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span }); + const host = props.args?.host; + if (host == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span }); return { bytes: true, series: [{ @@ -680,7 +695,10 @@ const fetchInstanceDriveUsageChart = async (total: boolean): Promise<typeof char }; const fetchInstanceDriveFilesChart = async (total: boolean): Promise<typeof chartData> => { - const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span }); + const host = props.args?.host; + if (host == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span }); return { series: [{ name: 'Drive files', @@ -695,7 +713,10 @@ const fetchInstanceDriveFilesChart = async (total: boolean): Promise<typeof char }; const fetchPerUserNotesChart = async (): Promise<typeof chartData> => { - const raw = await misskeyApiGet('charts/user/notes', { userId: props.args?.user?.id, limit: props.limit, span: props.span }); + const userId = props.args?.user?.id; + if (userId == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/user/notes', { userId: userId, limit: props.limit, span: props.span }); return { series: [...(props.args?.withoutAll ? [] : [{ name: 'All', @@ -727,7 +748,10 @@ const fetchPerUserNotesChart = async (): Promise<typeof chartData> => { }; const fetchPerUserPvChart = async (): Promise<typeof chartData> => { - const raw = await misskeyApiGet('charts/user/pv', { userId: props.args?.user?.id, limit: props.limit, span: props.span }); + const userId = props.args?.user?.id; + if (userId == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/user/pv', { userId: userId, limit: props.limit, span: props.span }); return { series: [{ name: 'Unique PV (user)', @@ -754,7 +778,10 @@ const fetchPerUserPvChart = async (): Promise<typeof chartData> => { }; const fetchPerUserFollowingChart = async (): Promise<typeof chartData> => { - const raw = await misskeyApiGet('charts/user/following', { userId: props.args?.user?.id, limit: props.limit, span: props.span }); + const userId = props.args?.user?.id; + if (userId == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/user/following', { userId: userId, limit: props.limit, span: props.span }); return { series: [{ name: 'Local', @@ -769,7 +796,10 @@ const fetchPerUserFollowingChart = async (): Promise<typeof chartData> => { }; const fetchPerUserFollowersChart = async (): Promise<typeof chartData> => { - const raw = await misskeyApiGet('charts/user/following', { userId: props.args?.user?.id, limit: props.limit, span: props.span }); + const userId = props.args?.user?.id; + if (userId == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/user/following', { userId: userId, limit: props.limit, span: props.span }); return { series: [{ name: 'Local', @@ -784,7 +814,10 @@ const fetchPerUserFollowersChart = async (): Promise<typeof chartData> => { }; const fetchPerUserDriveChart = async (): Promise<typeof chartData> => { - const raw = await misskeyApiGet('charts/user/drive', { userId: props.args?.user?.id, limit: props.limit, span: props.span }); + const userId = props.args?.user?.id; + if (userId == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/user/drive', { userId: userId, limit: props.limit, span: props.span }); return { bytes: true, series: [{ diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue index 7f592fba79..6c07eac47a 100644 --- a/packages/frontend/src/components/MkCropperDialog.vue +++ b/packages/frontend/src/components/MkCropperDialog.vue @@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { onMounted, useTemplateRef, ref } from 'vue'; +import { onMounted, useTemplateRef, ref, onUnmounted } from 'vue'; import * as Misskey from 'misskey-js'; import Cropper from 'cropperjs'; import tinycolor from 'tinycolor2'; @@ -55,17 +55,19 @@ const imgEl = useTemplateRef('imgEl'); let cropper: Cropper | null = null; const loading = ref(true); -const ok = async () => { - const promise = new Promise<Misskey.entities.DriveFile>(async (res) => { - const croppedImage = await cropper?.getCropperImage(); - const croppedSection = await cropper?.getCropperSelection(); +async function ok() { + const promise = new Promise<Blob>(async (res) => { + if (cropper == null) throw new Error('Cropper is not initialized'); + + const croppedImage = await cropper.getCropperImage()!; + const croppedSection = await cropper.getCropperSelection()!; // 拡大率を計算し、(ほぼ)元の大きさに戻す const zoomedRate = croppedImage.getBoundingClientRect().width / croppedImage.clientWidth; const widthToRender = croppedSection.getBoundingClientRect().width / zoomedRate; - const croppedCanvas = await croppedSection?.$toCanvas({ width: widthToRender }); - croppedCanvas?.toBlob(blob => { + const croppedCanvas = await croppedSection.$toCanvas({ width: widthToRender }); + croppedCanvas.toBlob(blob => { if (!blob) return; res(blob); }); @@ -74,25 +76,27 @@ const ok = async () => { const f = await promise; emit('ok', f); - dialogEl.value!.close(); -}; + if (dialogEl.value != null) dialogEl.value.close(); +} -const cancel = () => { +function cancel() { emit('cancel'); - dialogEl.value!.close(); -}; + if (dialogEl.value != null) dialogEl.value.close(); +} -const onImageLoad = () => { +function onImageLoad() { loading.value = false; if (cropper) { cropper.getCropperImage()!.$center('contain'); cropper.getCropperSelection()!.$center(); } -}; +} onMounted(() => { - cropper = new Cropper(imgEl.value!, { + if (imgEl.value == null) return; // TSを黙らすため + + cropper = new Cropper(imgEl.value, { }); const computedStyle = getComputedStyle(window.document.documentElement); @@ -104,16 +108,22 @@ onMounted(() => { selection.outlined = true; window.setTimeout(() => { - cropper!.getCropperImage()!.$center('contain'); + if (cropper == null) return; + cropper.getCropperImage()!.$center('contain'); selection.$center(); }, 100); // モーダルオープンアニメーションが終わったあとで再度調整 window.setTimeout(() => { - cropper!.getCropperImage()!.$center('contain'); + if (cropper == null) return; + cropper.getCropperImage()!.$center('contain'); selection.$center(); }, 500); }); + +onUnmounted(() => { + URL.revokeObjectURL(imgUrl); +}); </script> <style lang="scss" scoped> diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index df05bcc94c..6904c417ce 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -152,7 +152,7 @@ const props = withDefaults(defineProps<{ asDrawer?: boolean; asWindow?: boolean; asReactionPicker?: boolean; // 今は使われてないが将来的に使いそう - targetNote?: Misskey.entities.Note; + targetNote?: Misskey.entities.Note | null; }>(), { showPinned: true, }); diff --git a/packages/frontend/src/components/MkEmojiPickerDialog.vue b/packages/frontend/src/components/MkEmojiPickerDialog.vue index 1627dc8760..0ff4e8f38d 100644 --- a/packages/frontend/src/components/MkEmojiPickerDialog.vue +++ b/packages/frontend/src/components/MkEmojiPickerDialog.vue @@ -44,11 +44,11 @@ import { prefer } from '@/preferences.js'; const props = withDefaults(defineProps<{ manualShowing?: boolean | null; - anchorElement?: HTMLElement; + anchorElement?: HTMLElement | null; showPinned?: boolean; pinnedEmojis?: string[], asReactionPicker?: boolean; - targetNote?: Misskey.entities.Note; + targetNote?: Misskey.entities.Note | null; choseAndClose?: boolean; }>(), { manualShowing: null, diff --git a/packages/frontend/src/components/MkModal.vue b/packages/frontend/src/components/MkModal.vue index 06686ddfc0..660d5a26be 100644 --- a/packages/frontend/src/components/MkModal.vue +++ b/packages/frontend/src/components/MkModal.vue @@ -91,7 +91,7 @@ const emit = defineEmits<{ (ev: 'opened'): void; (ev: 'click'): void; (ev: 'esc'): void; - (ev: 'close'): void; + (ev: 'close'): void; // TODO: (refactor) closing に改名する (ev: 'closed'): void; }>(); @@ -148,7 +148,6 @@ function close(opts: { useSendAnimation?: boolean } = {}) { useSendAnime.value = true; } - // eslint-disable-next-line vue/no-mutating-props if (props.anchorElement) props.anchorElement.style.pointerEvents = 'auto'; showing.value = false; emit('close'); @@ -319,7 +318,6 @@ const alignObserver = new ResizeObserver((entries, observer) => { onMounted(() => { watch(() => props.anchorElement, async () => { if (props.anchorElement) { - // eslint-disable-next-line vue/no-mutating-props props.anchorElement.style.pointerEvents = 'none'; } fixed.value = (type.value === 'drawer') || (getFixedContainer(props.anchorElement) != null); diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue index e0490404ac..d96f0e2420 100644 --- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue +++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue @@ -58,18 +58,22 @@ const emit = defineEmits<{ const buttonEl = useTemplateRef('buttonEl'); const emojiName = computed(() => props.reaction.replace(/:/g, '').replace(/@\./, '')); -const emoji = computed(() => customEmojisMap.get(emojiName.value) ?? getUnicodeEmoji(props.reaction)); const canToggle = computed(() => { + const emoji = customEmojisMap.get(emojiName.value) ?? getUnicodeEmoji(props.reaction); + // TODO - //return !props.reaction.match(/@\w/) && $i && emoji.value && checkReactionPermissions($i, props.note, emoji.value); - return !props.reaction.match(/@\w/) && $i && emoji.value; + //return !props.reaction.match(/@\w/) && $i && emoji && checkReactionPermissions($i, props.note, emoji); + return !props.reaction.match(/@\w/) && $i && emoji; }); const canGetInfo = computed(() => !props.reaction.match(/@\w/) && props.reaction.includes(':')); const isLocalCustomEmoji = props.reaction[0] === ':' && props.reaction.includes('@.'); async function toggleReaction() { if (!canToggle.value) return; + if ($i == null) return; + + const me = $i; const oldReaction = props.myReaction; if (oldReaction) { @@ -93,7 +97,7 @@ async function toggleReaction() { noteId: props.noteId, }).then(() => { noteEvents.emit(`unreacted:${props.noteId}`, { - userId: $i!.id, + userId: me.id, reaction: oldReaction, }); if (oldReaction !== props.reaction) { @@ -101,10 +105,12 @@ async function toggleReaction() { noteId: props.noteId, reaction: props.reaction, }).then(() => { + const emoji = customEmojisMap.get(emojiName.value); + if (emoji == null) return; noteEvents.emit(`reacted:${props.noteId}`, { - userId: $i!.id, + userId: me.id, reaction: props.reaction, - emoji: emoji.value, + emoji: emoji, }); }); } @@ -131,10 +137,13 @@ async function toggleReaction() { noteId: props.noteId, reaction: props.reaction, }).then(() => { + const emoji = customEmojisMap.get(emojiName.value); + if (emoji == null) return; + noteEvents.emit(`reacted:${props.noteId}`, { - userId: $i!.id, + userId: me.id, reaction: props.reaction, - emoji: emoji.value, + emoji: emoji, }); }); // TODO: 上位コンポーネントでやる @@ -217,6 +226,8 @@ onMounted(() => { if (!mock) { useTooltip(buttonEl, async (showing) => { + if (buttonEl.value == null) return; + const reactions = await misskeyApiGet('notes/reactions', { noteId: props.noteId, type: props.reaction, diff --git a/packages/frontend/src/components/global/MkPageHeader.tabs.vue b/packages/frontend/src/components/global/MkPageHeader.tabs.vue index f2173b2e22..ae051eaf2c 100644 --- a/packages/frontend/src/components/global/MkPageHeader.tabs.vue +++ b/packages/frontend/src/components/global/MkPageHeader.tabs.vue @@ -59,7 +59,7 @@ import { prefer } from '@/preferences.js'; const props = withDefaults(defineProps<{ tabs?: Tab[]; tab?: string; - rootEl?: HTMLElement; + rootEl?: HTMLElement | null; }>(), { tabs: () => ([] as Tab[]), }); diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue index 437bf22363..38e3c7a11b 100644 --- a/packages/frontend/src/pages/admin-user.vue +++ b/packages/frontend/src/pages/admin-user.vue @@ -143,8 +143,8 @@ SPDX-License-Identifier: AGPL-3.0-only <button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ti ti-ban"></i></button> </div> <div v-if="expandedRoleIds.includes(role.id)" :class="$style.roleItemSub"> - <div>Assigned: <MkTime :time="info.roleAssigns.find(a => a.roleId === role.id).createdAt" mode="detail"/></div> - <div v-if="info.roleAssigns.find(a => a.roleId === role.id).expiresAt">Period: {{ new Date(info.roleAssigns.find(a => a.roleId === role.id).expiresAt).toLocaleString() }}</div> + <div>Assigned: <MkTime :time="info.roleAssigns.find(a => a.roleId === role.id)!.createdAt" mode="detail"/></div> + <div v-if="info.roleAssigns.find(a => a.roleId === role.id)!.expiresAt">Period: {{ new Date(info.roleAssigns.find(a => a.roleId === role.id)!.expiresAt!).toLocaleString() }}</div> <div v-else>Period: {{ i18n.ts.indefinitely }}</div> </div> </div> diff --git a/packages/frontend/src/pages/drive.file.info.vue b/packages/frontend/src/pages/drive.file.info.vue index 79c249413a..e3cc1d988e 100644 --- a/packages/frontend/src/pages/drive.file.info.vue +++ b/packages/frontend/src/pages/drive.file.info.vue @@ -119,7 +119,7 @@ async function _fetch_() { } function postThis() { - if (!file.value) return; + if (file.value == null) return; os.post({ initialFiles: [file.value], @@ -127,11 +127,13 @@ function postThis() { } function move() { - if (!file.value) return; + if (file.value == null) return; + + const f = file.value; selectDriveFolder(null).then(folder => { misskeyApi('drive/files/update', { - fileId: file.value.id, + fileId: f.id, folderId: folder[0] ? folder[0].id : null, }).then(async () => { await _fetch_(); @@ -140,7 +142,7 @@ function move() { } function toggleSensitive() { - if (!file.value) return; + if (file.value == null) return; os.apiWithDialog('drive/files/update', { fileId: file.value.id, @@ -157,7 +159,9 @@ function toggleSensitive() { } function rename() { - if (!file.value) return; + if (file.value == null) return; + + const f = file.value; os.inputText({ title: i18n.ts.renameFile, @@ -166,7 +170,7 @@ function rename() { }).then(({ canceled, result: name }) => { if (canceled) return; os.apiWithDialog('drive/files/update', { - fileId: file.value.id, + fileId: f.id, name: name, }).then(async () => { await _fetch_(); @@ -175,7 +179,9 @@ function rename() { } async function describe() { - if (!file.value) return; + if (file.value == null) return; + + const f = file.value; const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkFileCaptionEditWindow.vue').then(x => x.default), { default: file.value.comment ?? '', @@ -183,7 +189,7 @@ async function describe() { }, { done: caption => { os.apiWithDialog('drive/files/update', { - fileId: file.value.id, + fileId: f.id, comment: caption.length === 0 ? null : caption, }).then(async () => { await _fetch_(); @@ -194,7 +200,7 @@ async function describe() { } async function deleteFile() { - if (!file.value) return; + if (file.value == null) return; const { canceled } = await os.confirm({ type: 'warning', diff --git a/packages/frontend/src/pages/explore.vue b/packages/frontend/src/pages/explore.vue index c4f6ddc33e..6416816e6c 100644 --- a/packages/frontend/src/pages/explore.vue +++ b/packages/frontend/src/pages/explore.vue @@ -26,18 +26,12 @@ import { definePage } from '@/page.js'; import { i18n } from '@/i18n.js'; const props = withDefaults(defineProps<{ - tag?: string; initialTab?: string; }>(), { initialTab: 'featured', }); const tab = ref(props.initialTab); -const tagsEl = useTemplateRef('tagsEl'); - -watch(() => props.tag, () => { - if (tagsEl.value) tagsEl.value.toggleContent(props.tag == null); -}); const headerActions = computed(() => []); diff --git a/packages/frontend/src/pages/list.vue b/packages/frontend/src/pages/list.vue index 1261428c1c..a52b562c7f 100644 --- a/packages/frontend/src/pages/list.vue +++ b/packages/frontend/src/pages/list.vue @@ -103,6 +103,7 @@ definePage(() => ({ icon: 'ti ti-list', })); </script> + <style lang="scss" module> .userItem { display: flex; diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue index 89ae3d2d83..abd2a5d8a1 100644 --- a/packages/frontend/src/pages/note.vue +++ b/packages/frontend/src/pages/note.vue @@ -126,7 +126,7 @@ function fetchNote() { noteId: props.noteId, }).then(res => { note.value = res; - const appearNote = getAppearNote(res); + const appearNote = getAppearNote(res) ?? res; // 古いノートは被クリップ数をカウントしていないので、2023-10-01以前のものは強制的にnotes/clipsを叩く if ((appearNote.clippedCount ?? 0) > 0 || new Date(appearNote.createdAt).getTime() < new Date('2023-10-01').getTime()) { misskeyApi('notes/clips', { diff --git a/packages/frontend/src/pages/registry.value.vue b/packages/frontend/src/pages/registry.value.vue index 5c5bbfba39..827866ae2a 100644 --- a/packages/frontend/src/pages/registry.value.vue +++ b/packages/frontend/src/pages/registry.value.vue @@ -62,10 +62,10 @@ const props = defineProps<{ }>(); const scope = computed(() => props.path.split('/').slice(0, -1)); -const key = computed(() => props.path.split('/').at(-1)); +const key = computed(() => props.path.split('/').at(-1)!); const value = ref<any>(null); -const valueForEditor = ref<string | null>(null); +const valueForEditor = ref<string>(''); function fetchValue() { misskeyApi('i/registry/get-detail', { diff --git a/packages/frontend/src/pages/settings/avatar-decoration.vue b/packages/frontend/src/pages/settings/avatar-decoration.vue index 4b8ac9a26c..d3d642f156 100644 --- a/packages/frontend/src/pages/settings/avatar-decoration.vue +++ b/packages/frontend/src/pages/settings/avatar-decoration.vue @@ -108,7 +108,7 @@ async function openDecoration(avatarDecoration: { offsetY: payload.offsetY, }; const update = [...$i.avatarDecorations]; - update[index] = decoration; + update[index!] = decoration; await os.apiWithDialog('i/update', { avatarDecorations: update, }); @@ -116,7 +116,7 @@ async function openDecoration(avatarDecoration: { }, 'detach': async () => { const update = [...$i.avatarDecorations]; - update.splice(index, 1); + update.splice(index!, 1); await os.apiWithDialog('i/update', { avatarDecorations: update, }); diff --git a/packages/frontend/src/pages/settings/notifications.vue b/packages/frontend/src/pages/settings/notifications.vue index 3ddfb81c33..2f2b57bdaf 100644 --- a/packages/frontend/src/pages/settings/notifications.vue +++ b/packages/frontend/src/pages/settings/notifications.vue @@ -43,9 +43,9 @@ SPDX-License-Identifier: AGPL-3.0-only </FormSection> <FormSection> <div class="_gaps_s"> - <FormLink @click="readAllNotifications">{{ i18n.ts.markAsReadAllNotifications }}</FormLink> - <FormLink @click="testNotification">{{ i18n.ts._notification.sendTestNotification }}</FormLink> - <FormLink @click="flushNotification">{{ i18n.ts._notification.flushNotification }}</FormLink> + <MkButton @click="readAllNotifications">{{ i18n.ts.markAsReadAllNotifications }}</MkButton> + <MkButton @click="testNotification">{{ i18n.ts._notification.sendTestNotification }}</MkButton> + <MkButton @click="flushNotification">{{ i18n.ts._notification.flushNotification }}</MkButton> </div> </FormSection> <FormSection> @@ -76,6 +76,7 @@ import FormLink from '@/components/form/link.vue'; import FormSection from '@/components/form/section.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkSwitch from '@/components/MkSwitch.vue'; +import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; import { ensureSignin } from '@/i.js'; import { misskeyApi } from '@/utility/misskey-api.js'; @@ -96,7 +97,7 @@ const sendReadMessage = computed(() => pushRegistrationInServer.value?.sendReadM const userLists = await misskeyApi('users/lists/list'); async function readAllNotifications() { - await os.apiWithDialog('notifications/mark-all-as-read'); + await os.apiWithDialog('notifications/mark-all-as-read', {}); } async function updateReceiveConfig(type: typeof notificationTypes[number], value: NotificationConfig) { @@ -134,7 +135,7 @@ async function flushNotification() { if (canceled) return; - os.apiWithDialog('notifications/flush'); + os.apiWithDialog('notifications/flush', {}); } const headerActions = computed(() => []); diff --git a/packages/frontend/src/pages/settings/security.vue b/packages/frontend/src/pages/settings/security.vue index c954b9dd5a..bc77c1f0af 100644 --- a/packages/frontend/src/pages/settings/security.vue +++ b/packages/frontend/src/pages/settings/security.vue @@ -80,14 +80,14 @@ async function change() { type: 'password', autocomplete: 'new-password', }); - if (canceled2) return; + if (canceled2 || newPassword == null) return; const { canceled: canceled3, result: newPassword2 } = await os.inputText({ title: i18n.ts.newPasswordRetype, type: 'password', autocomplete: 'new-password', }); - if (canceled3) return; + if (canceled3 || newPassword2 == null) return; if (newPassword !== newPassword2) { os.alert({ diff --git a/packages/frontend/src/pages/settings/theme.vue b/packages/frontend/src/pages/settings/theme.vue index d8ae356f6b..beae1224e4 100644 --- a/packages/frontend/src/pages/settings/theme.vue +++ b/packages/frontend/src/pages/settings/theme.vue @@ -205,8 +205,8 @@ import { computed, ref, watch } from 'vue'; import JSON5 from 'json5'; import defaultLightTheme from '@@/themes/l-light.json5'; import defaultDarkTheme from '@@/themes/d-green-lime.json5'; -import type { Theme } from '@/theme.js'; import { isSafeMode } from '@@/js/config.js'; +import type { Theme } from '@/theme.js'; import * as os from '@/os.js'; import MkSwitch from '@/components/MkSwitch.vue'; import FormSection from '@/components/form/section.vue'; @@ -275,6 +275,7 @@ async function toggleDarkMode() { const value = !store.r.darkMode.value; if (syncDeviceDarkMode.value) { const { canceled } = await os.confirm({ + type: 'question', text: i18n.tsx.switchDarkModeManuallyWhenSyncEnabledConfirm({ x: i18n.ts.syncDeviceDarkMode }), }); if (canceled) return; diff --git a/packages/frontend/src/pages/settings/webhook.edit.vue b/packages/frontend/src/pages/settings/webhook.edit.vue index ee387fb20c..a92d3dc457 100644 --- a/packages/frontend/src/pages/settings/webhook.edit.vue +++ b/packages/frontend/src/pages/settings/webhook.edit.vue @@ -149,10 +149,8 @@ async function test(type: Misskey.entities.UserWebhook['on'][number]): Promise<v }); } -// eslint-disable-next-line @typescript-eslint/no-unused-vars const headerActions = computed(() => []); -// eslint-disable-next-line @typescript-eslint/no-unused-vars const headerTabs = computed(() => []); definePage(() => ({ diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue index 1a5f3edc9e..f72549df07 100644 --- a/packages/frontend/src/pages/timeline.vue +++ b/packages/frontend/src/pages/timeline.vue @@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only ref="tlComponent" :key="src + withRenotes + withReplies + onlyFiles + withSensitive" :class="$style.tl" - :src="src.split(':')[0]" + :src="(src.split(':')[0] as (BasicTimelineType | 'list'))" :list="src.split(':')[1]" :withRenotes="withRenotes" :withReplies="withReplies" diff --git a/packages/frontend/src/pages/user/activity.following.vue b/packages/frontend/src/pages/user/activity.following.vue index 2cd825f3dc..882b45080e 100644 --- a/packages/frontend/src/pages/user/activity.following.vue +++ b/packages/frontend/src/pages/user/activity.following.vue @@ -43,6 +43,8 @@ const fetching = ref(true); const { handler: externalTooltipHandler } = useChartTooltip(); async function renderChart() { + if (chartEl.value == null) return; + if (chartInstance) { chartInstance.destroy(); } diff --git a/packages/frontend/src/pages/user/activity.notes.vue b/packages/frontend/src/pages/user/activity.notes.vue index ddde84ef25..39c9fd7950 100644 --- a/packages/frontend/src/pages/user/activity.notes.vue +++ b/packages/frontend/src/pages/user/activity.notes.vue @@ -36,13 +36,15 @@ const props = defineProps<{ const chartEl = useTemplateRef('chartEl'); const legendEl = useTemplateRef('legendEl'); const now = new Date(); -let chartInstance: Chart = null; +let chartInstance: Chart | null = null; const chartLimit = 50; const fetching = ref(true); const { handler: externalTooltipHandler } = useChartTooltip(); async function renderChart() { + if (chartEl.value == null) return; + if (chartInstance) { chartInstance.destroy(); } diff --git a/packages/frontend/src/pages/user/activity.pv.vue b/packages/frontend/src/pages/user/activity.pv.vue index 34e1fe3abf..9e1b92058b 100644 --- a/packages/frontend/src/pages/user/activity.pv.vue +++ b/packages/frontend/src/pages/user/activity.pv.vue @@ -36,13 +36,15 @@ const props = defineProps<{ const chartEl = useTemplateRef('chartEl'); const legendEl = useTemplateRef('legendEl'); const now = new Date(); -let chartInstance: Chart = null; +let chartInstance: Chart | null = null; const chartLimit = 30; const fetching = ref(true); const { handler: externalTooltipHandler } = useChartTooltip(); async function renderChart() { + if (chartEl.value == null) return; + if (chartInstance) { chartInstance.destroy(); } diff --git a/packages/frontend/src/preferences/def.ts b/packages/frontend/src/preferences/def.ts index 7b045687d6..702d9a4acf 100644 --- a/packages/frontend/src/preferences/def.ts +++ b/packages/frontend/src/preferences/def.ts @@ -32,6 +32,8 @@ export type SoundStore = { volume: number; }; +type OmitStrict<T, K extends keyof T> = T extends any ? Pick<T, Exclude<keyof T, K>> : never; + // NOTE: デフォルト値は他の設定の状態に依存してはならない(依存していた場合、ユーザーがその設定項目単体で「初期値にリセット」した場合不具合の原因になる) export const PREF_DEF = definePreferences({ @@ -385,7 +387,7 @@ export const PREF_DEF = definePreferences({ default: false, }, plugins: { - default: [] as Plugin[], + default: [] as (OmitStrict<Plugin, 'config'> & { config: Record<string, any> })[], mergeStrategy: (a, b) => { const sameIdExists = a.some(x => b.some(y => x.installId === y.installId)); if (sameIdExists) throw new Error(); diff --git a/packages/frontend/src/preferences/manager.ts b/packages/frontend/src/preferences/manager.ts index 0389cf612a..9157ba05d4 100644 --- a/packages/frontend/src/preferences/manager.ts +++ b/packages/frontend/src/preferences/manager.ts @@ -109,10 +109,11 @@ export function definePreferences<T extends Record<string, unknown>>(x: { } export function getInitialPrefValue<K extends keyof PREF>(k: K): ValueOf<K> { - if (typeof PREF_DEF[k].default === 'function') { // factory - return PREF_DEF[k].default(); + const _default = PREF_DEF[k as string].default; + if (typeof _default === 'function') { // factory + return _default(); } else { - return PREF_DEF[k].default; + return _default; } } diff --git a/packages/frontend/src/ui/_common_/announcements.vue b/packages/frontend/src/ui/_common_/announcements.vue index f9af8e1ee7..c299e1b1c1 100644 --- a/packages/frontend/src/ui/_common_/announcements.vue +++ b/packages/frontend/src/ui/_common_/announcements.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<div :class="$style.root"> +<div v-if="$i" :class="$style.root"> <MkA v-for="announcement in $i.unreadAnnouncements.filter(x => x.display === 'banner')" :key="announcement.id" diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts index 9872937d21..a9ad36c97a 100644 --- a/packages/frontend/src/ui/_common_/common.ts +++ b/packages/frontend/src/ui/_common_/common.ts @@ -12,7 +12,7 @@ import { i18n } from '@/i18n.js'; import { $i } from '@/i.js'; function toolsMenuItems(): MenuItem[] { - return [{ + const items: MenuItem[] = [{ type: 'link', to: '/scratchpad', text: i18n.ts.scratchpad, @@ -27,17 +27,27 @@ function toolsMenuItems(): MenuItem[] { to: '/clicker', text: '🍪👈', icon: 'ti ti-cookie', - }, ($i && ($i.isAdmin || $i.policies.canManageCustomEmojis)) ? { - type: 'link', - to: '/custom-emojis-manager', - text: i18n.ts.manageCustomEmojis, - icon: 'ti ti-icons', - } : undefined, ($i && ($i.isAdmin || $i.policies.canManageAvatarDecorations)) ? { - type: 'link', - to: '/avatar-decorations', - text: i18n.ts.manageAvatarDecorations, - icon: 'ti ti-sparkles', - } : undefined]; + }]; + + if ($i && ($i.isAdmin || $i.policies.canManageCustomEmojis)) { + items.push({ + type: 'link', + to: '/custom-emojis-manager', + text: i18n.ts.manageCustomEmojis, + icon: 'ti ti-icons', + }); + } + + if ($i && ($i.isAdmin || $i.policies.canManageAvatarDecorations)) { + items.push({ + type: 'link' as const, + to: '/avatar-decorations', + text: i18n.ts.manageAvatarDecorations, + icon: 'ti ti-sparkles', + }); + } + + return items; } export function openInstanceMenu(ev: MouseEvent) { diff --git a/packages/frontend/src/ui/_common_/statusbar-rss.vue b/packages/frontend/src/ui/_common_/statusbar-rss.vue index 7db0d5267d..202972ed46 100644 --- a/packages/frontend/src/ui/_common_/statusbar-rss.vue +++ b/packages/frontend/src/ui/_common_/statusbar-rss.vue @@ -29,18 +29,18 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref } from 'vue'; import * as Misskey from 'misskey-js'; -import MkMarqueeText from '@/components/MkMarqueeText.vue'; import { useInterval } from '@@/js/use-interval.js'; +import MkMarqueeText from '@/components/MkMarqueeText.vue'; import { shuffle } from '@/utility/shuffle.js'; const props = defineProps<{ - url?: string; + url: string; shuffle?: boolean; display?: 'marquee' | 'oneByOne'; marqueeDuration?: number; marqueeReverse?: boolean; oneByOneInterval?: number; - refreshIntervalSec?: number; + refreshIntervalSec: number; }>(); const items = ref<Misskey.entities.FetchRssResponse['items']>([]); diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue index 226981cca8..9f6d8267f7 100644 --- a/packages/frontend/src/ui/deck.vue +++ b/packages/frontend/src/ui/deck.vue @@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div :class="$style.nonTitlebarArea"> <XSidebar v-if="!isMobile && prefer.r['deck.navbarPosition'].value === 'left'"/> - <div :class="[$style.main, { [$style.withWallpaper]: withWallpaper, [$style.withSidebarAndTitlebar]: !isMobile && prefer.r['deck.navbarPosition'].value === 'left' && prefer.r.showTitlebar.value }]" :style="{ backgroundImage: prefer.s['deck.wallpaper'] != null ? `url(${ prefer.s['deck.wallpaper'] })` : null }"> + <div :class="[$style.main, { [$style.withWallpaper]: withWallpaper, [$style.withSidebarAndTitlebar]: !isMobile && prefer.r['deck.navbarPosition'].value === 'left' && prefer.r.showTitlebar.value }]" :style="{ backgroundImage: prefer.s['deck.wallpaper'] != null ? `url(${ prefer.s['deck.wallpaper'] })` : '' }"> <XNavbarH v-if="!isMobile && prefer.r['deck.navbarPosition'].value === 'top'"/> <XReloadSuggestion v-if="shouldSuggestReload"/> diff --git a/packages/frontend/src/utility/admin-lookup.ts b/packages/frontend/src/utility/admin-lookup.ts index eccc88d8a9..18eebaa8f8 100644 --- a/packages/frontend/src/utility/admin-lookup.ts +++ b/packages/frontend/src/utility/admin-lookup.ts @@ -52,7 +52,7 @@ export async function lookupUserByEmail() { const user = await os.apiWithDialog('admin/accounts/find-by-email', { email: result }); os.pageWindow(`/admin/user/${user.id}`); - } catch (err) { + } catch (err: any) { if (err.code === 'USER_NOT_FOUND') { os.alert({ type: 'error', diff --git a/packages/frontend/src/utility/chart-legend.ts b/packages/frontend/src/utility/chart-legend.ts index e701d18dd2..fcbddf5669 100644 --- a/packages/frontend/src/utility/chart-legend.ts +++ b/packages/frontend/src/utility/chart-legend.ts @@ -10,7 +10,7 @@ export const chartLegend = (legend: InstanceType<typeof MkChartLegend>) => ({ id: 'htmlLegend', afterUpdate(chart, args, options) { // Reuse the built-in legendItems generator - const items = chart.options.plugins.legend.labels.generateLabels(chart); + const items = chart.options.plugins!.legend!.labels!.generateLabels!(chart); legend.update(chart, items); }, diff --git a/packages/frontend/src/utility/clicker-game.ts b/packages/frontend/src/utility/clicker-game.ts index 0544be7757..4360c58455 100644 --- a/packages/frontend/src/utility/clicker-game.ts +++ b/packages/frontend/src/utility/clicker-game.ts @@ -27,7 +27,7 @@ export async function load() { scope: ['clickerGame'], key: 'saveData', }); - } catch (err) { + } catch (err: any) { if (err.code === 'NO_SUCH_KEY') { saveData.value = { gameVersion: 2, @@ -43,20 +43,6 @@ export async function load() { } throw err; } - - // migration - if (saveData.value.gameVersion === 1) { - saveData.value = { - gameVersion: 2, - cookies: saveData.value.cookies, - totalCookies: saveData.value.cookies, - totalHandmadeCookies: saveData.value.cookies, - clicked: saveData.value.clicked, - achievements: [], - facilities: [], - }; - save(); - } } export async function save() { diff --git a/packages/frontend/src/utility/get-note-menu.ts b/packages/frontend/src/utility/get-note-menu.ts index f7b56040cc..90de952a91 100644 --- a/packages/frontend/src/utility/get-note-menu.ts +++ b/packages/frontend/src/utility/get-note-menu.ts @@ -39,7 +39,7 @@ export async function getNoteClipMenu(props: { } } - const appearNote = getAppearNote(props.note); + const appearNote = getAppearNote(props.note) ?? props.note; const clips = await clipsCache.fetch(); const menu: MenuItem[] = [...clips.map(clip => ({ diff --git a/packages/frontend/src/widgets/WidgetInstanceInfo.vue b/packages/frontend/src/widgets/WidgetInstanceInfo.vue index 722e6fadb2..7e6a779cf0 100644 --- a/packages/frontend/src/widgets/WidgetInstanceInfo.vue +++ b/packages/frontend/src/widgets/WidgetInstanceInfo.vue @@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div class="_panel"> <div :class="$style.container" :style="{ backgroundImage: instance.bannerUrl ? `url(${ instance.bannerUrl })` : undefined }"> <div :class="$style.iconContainer"> - <img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" alt="" :class="$style.icon"/> + <img :src="instance.iconUrl ?? '/favicon.ico'" alt="" :class="$style.icon"/> </div> <div :class="$style.bodyContainer"> <div :class="$style.body"> @@ -20,10 +20,10 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> +import { host } from '@@/js/config.js'; import { useWidgetPropsManager } from './widget.js'; import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js'; import type { FormWithDefault, GetFormResultType } from '@/utility/form.js'; -import { host } from '@@/js/config.js'; import { instance } from '@/instance.js'; const name = 'instanceInfo'; |