summaryrefslogtreecommitdiff
path: root/packages/frontend/src
diff options
context:
space:
mode:
authorsyuilo <4439005+syuilo@users.noreply.github.com>2025-08-26 13:34:41 +0900
committersyuilo <4439005+syuilo@users.noreply.github.com>2025-08-26 13:34:41 +0900
commitd6a1046361d3d38726f2a86588960c3614f72a9f (patch)
treed0a501710c36b8e9488e6756adc906c08447910b /packages/frontend/src
parentrefactor and fix (diff)
downloadmisskey-d6a1046361d3d38726f2a86588960c3614f72a9f.tar.gz
misskey-d6a1046361d3d38726f2a86588960c3614f72a9f.tar.bz2
misskey-d6a1046361d3d38726f2a86588960c3614f72a9f.zip
refactor
Diffstat (limited to 'packages/frontend/src')
-rw-r--r--packages/frontend/src/boot/main-boot.ts5
-rw-r--r--packages/frontend/src/components/MkAchievements.vue2
-rw-r--r--packages/frontend/src/components/MkChart.vue55
-rw-r--r--packages/frontend/src/components/MkCropperDialog.vue44
-rw-r--r--packages/frontend/src/components/MkEmojiPicker.vue2
-rw-r--r--packages/frontend/src/components/MkEmojiPickerDialog.vue4
-rw-r--r--packages/frontend/src/components/MkModal.vue4
-rw-r--r--packages/frontend/src/components/MkReactionsViewer.reaction.vue27
-rw-r--r--packages/frontend/src/components/global/MkPageHeader.tabs.vue2
-rw-r--r--packages/frontend/src/pages/admin-user.vue4
-rw-r--r--packages/frontend/src/pages/drive.file.info.vue24
-rw-r--r--packages/frontend/src/pages/explore.vue6
-rw-r--r--packages/frontend/src/pages/list.vue1
-rw-r--r--packages/frontend/src/pages/note.vue2
-rw-r--r--packages/frontend/src/pages/registry.value.vue4
-rw-r--r--packages/frontend/src/pages/settings/avatar-decoration.vue4
-rw-r--r--packages/frontend/src/pages/settings/notifications.vue11
-rw-r--r--packages/frontend/src/pages/settings/security.vue4
-rw-r--r--packages/frontend/src/pages/settings/theme.vue3
-rw-r--r--packages/frontend/src/pages/settings/webhook.edit.vue2
-rw-r--r--packages/frontend/src/pages/timeline.vue2
-rw-r--r--packages/frontend/src/pages/user/activity.following.vue2
-rw-r--r--packages/frontend/src/pages/user/activity.notes.vue4
-rw-r--r--packages/frontend/src/pages/user/activity.pv.vue4
-rw-r--r--packages/frontend/src/preferences/def.ts4
-rw-r--r--packages/frontend/src/preferences/manager.ts7
-rw-r--r--packages/frontend/src/ui/_common_/announcements.vue2
-rw-r--r--packages/frontend/src/ui/_common_/common.ts34
-rw-r--r--packages/frontend/src/ui/_common_/statusbar-rss.vue6
-rw-r--r--packages/frontend/src/ui/deck.vue2
-rw-r--r--packages/frontend/src/utility/admin-lookup.ts2
-rw-r--r--packages/frontend/src/utility/chart-legend.ts2
-rw-r--r--packages/frontend/src/utility/clicker-game.ts16
-rw-r--r--packages/frontend/src/utility/get-note-menu.ts2
-rw-r--r--packages/frontend/src/widgets/WidgetInstanceInfo.vue4
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';