diff --git a/packages/frontend/src/components/MkEmojiPicker.section.vue b/packages/frontend/src/components/MkEmojiPicker.section.vue index 14f3f5770f..43a6664d11 100644 --- a/packages/frontend/src/components/MkEmojiPicker.section.vue +++ b/packages/frontend/src/components/MkEmojiPicker.section.vue @@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only <!-- フォルダの中にはカスタム絵文字やフォルダがある --> <section v-else v-panel style="border-radius: 6px; border-bottom: 0.5px solid var(--divider);"> <header class="_acrylic" @click="shown = !shown"> - <i class="toggle ti-fw" :class="shown ? 'ti ti-chevron-down' : 'ti ti-chevron-up'"></i> <slot></slot> (<i class="ti ti-folder ti-fw"></i>:{{ customEmojiTree.length }} <i class="ti ti-icons ti-fw"></i>:{{ emojis.length }}) + <i class="toggle ti-fw" :class="shown ? 'ti ti-chevron-down' : 'ti ti-chevron-up'"></i> <slot></slot> (<i class="ti ti-folder ti-fw"></i>:{{ customEmojiTree?.length }} <i class="ti ti-icons ti-fw"></i>:{{ emojis.length }}) </header> <div v-if="shown" style="padding-left: 9px;"> <MkEmojiPickerSection @@ -61,7 +61,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref, computed, Ref } from 'vue'; import { CustomEmojiFolderTree, getEmojiName } from '@/scripts/emojilist.js'; -import { i18n } from '../i18n.js'; +import { i18n } from '@/i18n.js'; import { customEmojis } from '@/custom-emojis.js'; import MkEmojiPickerSection from '@/components/MkEmojiPicker.section.vue'; @@ -87,7 +87,7 @@ function computeButtonTitle(ev: MouseEvent): void { elm.title = getEmojiName(emoji) ?? emoji; } -function nestedChosen(emoji: any, ev?: MouseEvent) { +function nestedChosen(emoji: any, ev: MouseEvent) { emit('chosen', emoji, ev); } </script> diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index f36d46506f..2db4195b98 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only </section> <div v-if="tab === 'index'" class="group index"> - <section v-if="showPinned && pinned.length > 0"> + <section v-if="showPinned && (pinned && pinned.length > 0)"> <div class="body"> <button v-for="emoji in pinned" @@ -327,7 +327,7 @@ watch(q, () => { }); function filterAvailable(emoji: Misskey.entities.EmojiSimple): boolean { - return (emoji.roleIdsThatCanBeUsedThisEmojiAsReaction == null || emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0) || ($i && $i.roles.some(r => emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.includes(r.id))); + return ((emoji.roleIdsThatCanBeUsedThisEmojiAsReaction == null || emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0) || ($i && $i.roles.some(r => emoji.roleIdsThatCanBeUsedThisEmojiAsReaction?.includes(r.id)))) ?? false; } function focus() { diff --git a/packages/frontend/src/components/MkFileCaptionEditWindow.vue b/packages/frontend/src/components/MkFileCaptionEditWindow.vue index 922089a78b..b9d0e34809 100644 --- a/packages/frontend/src/components/MkFileCaptionEditWindow.vue +++ b/packages/frontend/src/components/MkFileCaptionEditWindow.vue @@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only :withOkButton="true" :okButtonDisabled="false" @ok="ok()" - @close="dialog.close()" + @close="dialog?.close()" @closed="emit('closed')" > <template #header>{{ i18n.ts.describeFile }}</template> @@ -48,6 +48,6 @@ const caption = ref(props.default); async function ok() { emit('done', caption.value); - dialog.value.close(); + dialog.value?.close(); } </script> diff --git a/packages/frontend/src/components/MkFileListForAdmin.vue b/packages/frontend/src/components/MkFileListForAdmin.vue index 3edd30bc37..955a90a50d 100644 --- a/packages/frontend/src/components/MkFileListForAdmin.vue +++ b/packages/frontend/src/components/MkFileListForAdmin.vue @@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div> <MkPagination v-slot="{items}" :pagination="pagination" class="urempief" :class="{ grid: viewMode === 'grid' }"> <MkA - v-for="file in items" + v-for="file in (items as Misskey.entities.DriveFile[])" :key="file.id" v-tooltip.mfm="`${file.type}\n${bytes(file.size)}\n${dateString(file.createdAt)}\nby ${file.user ? '@' + Misskey.acct.toString(file.user) : 'system'}`" :to="`/admin/file/${file.id}`" diff --git a/packages/frontend/src/components/MkFoldableSection.vue b/packages/frontend/src/components/MkFoldableSection.vue index 1ffc95d944..1df2df085f 100644 --- a/packages/frontend/src/components/MkFoldableSection.vue +++ b/packages/frontend/src/components/MkFoldableSection.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<div ref="el" :class="$style.root"> +<div ref="div" :class="$style.root"> <header :class="$style.header" class="_button" :style="{ background: bg }" @click="showBody = !showBody"> <div :class="$style.title"><div><slot name="header"></slot></div></div> <div :class="$style.divider"></div> @@ -42,8 +42,8 @@ const props = withDefaults(defineProps<{ expanded: true, }); -const el = shallowRef<HTMLDivElement>(); -const bg = ref<string | null>(null); +const div = shallowRef<HTMLDivElement>(); +const bg = ref<string>(); const showBody = ref((props.persistKey && miLocalStorage.getItem(`${miLocalStoragePrefix}${props.persistKey}`)) ? (miLocalStorage.getItem(`${miLocalStoragePrefix}${props.persistKey}`) === 't') : props.expanded); watch(showBody, () => { @@ -52,40 +52,44 @@ watch(showBody, () => { } }); -function enter(el: Element) { +function enter(element: Element) { + const el = element as HTMLElement; const elementHeight = el.getBoundingClientRect().height; - el.style.height = 0; + el.style.height = 'unset'; el.offsetHeight; // reflow el.style.height = elementHeight + 'px'; } -function afterEnter(el: Element) { - el.style.height = null; +function afterEnter(element: Element) { + const el = element as HTMLElement; + el.style.height = 'unset'; } -function leave(el: Element) { +function leave(element: Element) { + const el = element as HTMLElement; const elementHeight = el.getBoundingClientRect().height; el.style.height = elementHeight + 'px'; el.offsetHeight; // reflow - el.style.height = 0; + el.style.height = '0'; } -function afterLeave(el: Element) { - el.style.height = null; +function afterLeave(element: Element) { + const el = element as HTMLElement; + el.style.height = 'unset'; } onMounted(() => { - function getParentBg(el: HTMLElement | null): string { + function getParentBg(el?: HTMLElement | null): string { if (el == null || el.tagName === 'BODY') return 'var(--bg)'; - const bg = el.style.background || el.style.backgroundColor; - if (bg) { - return bg; + const background = el.style.background || el.style.backgroundColor; + if (background) { + return background; } else { return getParentBg(el.parentElement); } } - const rawBg = getParentBg(el.value); + const rawBg = getParentBg(div.value); const _bg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg); _bg.setAlpha(0.85); bg.value = _bg.toRgbString(); diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue index 6b7dfb20e3..c143c2af1f 100644 --- a/packages/frontend/src/components/MkFolder.vue +++ b/packages/frontend/src/components/MkFolder.vue @@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </template> - <div v-if="openedAtLeastOnce" :class="[$style.body, { [$style.bgSame]: bgSame }]" :style="{ maxHeight: maxHeight ? `${maxHeight}px` : null, overflow: maxHeight ? `auto` : null }" :aria-hidden="!opened"> + <div v-if="openedAtLeastOnce" :class="[$style.body, { [$style.bgSame]: bgSame }]" :style="{ maxHeight: maxHeight ? `${maxHeight}px` : undefined, overflow: maxHeight ? `auto` : undefined }" :aria-hidden="!opened"> <Transition :enterActiveClass="defaultStore.state.animation ? $style.transition_toggle_enterActive : ''" :leaveActiveClass="defaultStore.state.animation ? $style.transition_toggle_leaveActive : ''" @@ -109,7 +109,7 @@ function toggle() { onMounted(() => { const computedStyle = getComputedStyle(document.documentElement); - const parentBg = getBgColor(rootEl.value.parentElement); + const parentBg = getBgColor(rootEl.value!.parentElement!); const myBg = computedStyle.getPropertyValue('--panel'); bgSame.value = parentBg === myBg; }); diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue index 78c4fb3cd2..90a5dcb332 100644 --- a/packages/frontend/src/components/MkFollowButton.vue +++ b/packages/frontend/src/components/MkFollowButton.vue @@ -111,17 +111,19 @@ async function onClick() { claimAchievement('following1'); - if ($i.followingCount >= 10) { - claimAchievement('following10'); - } - if ($i.followingCount >= 50) { - claimAchievement('following50'); - } - if ($i.followingCount >= 100) { - claimAchievement('following100'); - } - if ($i.followingCount >= 300) { - claimAchievement('following300'); + if ($i) { + if ($i.followingCount >= 10) { + claimAchievement('following10'); + } + if ($i.followingCount >= 50) { + claimAchievement('following50'); + } + if ($i.followingCount >= 100) { + claimAchievement('following100'); + } + if ($i.followingCount >= 300) { + claimAchievement('following300'); + } } } } diff --git a/packages/frontend/src/components/MkForgotPassword.vue b/packages/frontend/src/components/MkForgotPassword.vue index 9b57688a02..4a0a35b4cf 100644 --- a/packages/frontend/src/components/MkForgotPassword.vue +++ b/packages/frontend/src/components/MkForgotPassword.vue @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only ref="dialog" :width="370" :height="400" - @close="dialog.close()" + @close="dialog?.close()" @closed="emit('closed')" > <template #header>{{ i18n.ts.forgotPassword }}</template> @@ -66,6 +66,6 @@ async function onSubmit() { email: email.value, }); emit('done'); - dialog.value.close(); + dialog.value?.close(); } </script> diff --git a/packages/frontend/src/components/MkFormDialog.vue b/packages/frontend/src/components/MkFormDialog.vue index 2095a1dcea..61e23a0a42 100644 --- a/packages/frontend/src/components/MkFormDialog.vue +++ b/packages/frontend/src/components/MkFormDialog.vue @@ -40,11 +40,11 @@ SPDX-License-Identifier: AGPL-3.0-only </MkSwitch> <MkSelect v-else-if="form[item].type === 'enum'" v-model="values[item]"> <template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ i18n.ts.optional }})</span></template> - <option v-for="item in form[item].enum" :key="item.value" :value="item.value">{{ item.label }}</option> + <option v-for="option in form[item].enum" :key="option.value" :value="option.value">{{ option.label }}</option> </MkSelect> <MkRadios v-else-if="form[item].type === 'radio'" v-model="values[item]"> <template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ i18n.ts.optional }})</span></template> - <option v-for="item in form[item].options" :key="item.value" :value="item.value">{{ item.label }}</option> + <option v-for="option in form[item].options" :key="option.value" :value="option.value">{{ option.label }}</option> </MkRadios> <MkRange v-else-if="form[item].type === 'range'" v-model="values[item]" :min="form[item].min" :max="form[item].max" :step="form[item].step" :textConverter="form[item].textConverter"> <template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ i18n.ts.optional }})</span></template> @@ -86,6 +86,7 @@ const emit = defineEmits<{ canceled?: boolean; result?: any; }): void; + (ev: 'closed'): void; }>(); const dialog = shallowRef<InstanceType<typeof MkModalWindow>>(); @@ -99,13 +100,13 @@ function ok() { emit('done', { result: values, }); - dialog.value.close(); + dialog.value?.close(); } function cancel() { emit('done', { canceled: true, }); - dialog.value.close(); + dialog.value?.close(); } </script> diff --git a/packages/frontend/src/components/MkGalleryPostPreview.vue b/packages/frontend/src/components/MkGalleryPostPreview.vue index 316632b1a6..0d8612fe26 100644 --- a/packages/frontend/src/components/MkGalleryPostPreview.vue +++ b/packages/frontend/src/components/MkGalleryPostPreview.vue @@ -14,8 +14,8 @@ SPDX-License-Identifier: AGPL-3.0-only leaveActiveClass: $style.transition_toggle_leaveActive, leaveToClass: $style.transition_toggle_leaveTo, }" - :src="post.files[0].thumbnailUrl" - :hash="post.files[0].blurhash" + :src="post.files?.[0]?.thumbnailUrl" + :hash="post.files?.[0]?.blurhash" :forceBlurhash="!show" /> </Transition> diff --git a/packages/frontend/src/components/MkHeatmap.vue b/packages/frontend/src/components/MkHeatmap.vue index f47b680f83..1913fd4108 100644 --- a/packages/frontend/src/components/MkHeatmap.vue +++ b/packages/frontend/src/components/MkHeatmap.vue @@ -27,10 +27,10 @@ const props = defineProps<{ src: string; }>(); -const rootEl = shallowRef<HTMLDivElement>(null); -const chartEl = shallowRef<HTMLCanvasElement>(null); +const rootEl = shallowRef<HTMLDivElement | null>(null); +const chartEl = shallowRef<HTMLCanvasElement | null>(null); const now = new Date(); -let chartInstance: Chart = null; +let chartInstance: Chart | null = null; const fetching = ref(true); const { handler: externalTooltipHandler } = useChartTooltip({ @@ -38,6 +38,7 @@ const { handler: externalTooltipHandler } = useChartTooltip({ }); async function renderChart() { + if (!rootEl.value || !chartEl.value) return; if (chartInstance) { chartInstance.destroy(); } @@ -56,7 +57,7 @@ async function renderChart() { return new Date(y, m, d - ago); }; - const format = (arr) => { + const format = (arr: number[]) => { return arr.map((v, i) => { const dt = getDate(i); const iso = `${dt.getFullYear()}-${(dt.getMonth() + 1).toString().padStart(2, '0')}-${dt.getDate().toString().padStart(2, '0')}`; @@ -69,7 +70,7 @@ async function renderChart() { }); }; - let values; + let values: number[] = []; if (props.src === 'active-users') { const raw = await misskeyApi('charts/active-users', { limit: chartLimit, span: 'day' }); @@ -106,20 +107,18 @@ async function renderChart() { data: { datasets: [{ label: 'Read & Write', - data: format(values), - pointRadius: 0, + data: format(values) as any, borderWidth: 0, - borderJoinStyle: 'round', borderRadius: 3, backgroundColor(c) { - const value = c.dataset.data[c.dataIndex].v; + // @ts-expect-error TS(2339) + const value = c.dataset.data[c.dataIndex].v as number; let a = (value - min) / max; if (value !== 0) { // 0でない限りは完全に不可視にはしない a = Math.max(a, 0.05); } return alpha(color, a); }, - fill: true, width(c) { const a = c.chart.chartArea ?? {}; return (a.right - a.left) / weeks - marginEachCell; @@ -190,11 +189,13 @@ async function renderChart() { enabled: false, callbacks: { title(context) { - const v = context[0].dataset.data[context[0].dataIndex]; - return v.d; + // @ts-expect-error TS(2339) + return context[0].dataset.data[context[0].dataIndex].d; }, label(context) { const v = context.dataset.data[context.dataIndex]; + + // @ts-expect-error TS(2339) return ['Active: ' + v.v]; }, }, diff --git a/packages/frontend/src/components/MkInstanceStats.vue b/packages/frontend/src/components/MkInstanceStats.vue index 1576089657..50d3ec2779 100644 --- a/packages/frontend/src/components/MkInstanceStats.vue +++ b/packages/frontend/src/components/MkInstanceStats.vue @@ -138,7 +138,8 @@ function createDoughnut(chartEl, tooltip, data) { }, }, onClick: (ev) => { - const hit = chartInstance.getElementsAtEventForMode(ev, 'nearest', { intersect: true }, false)[0]; + if (!ev.native) return; + const hit = chartInstance.getElementsAtEventForMode(ev.native, 'nearest', { intersect: true }, false)[0]; if (hit && data[hit.index].onClick) { data[hit.index].onClick(); } @@ -164,23 +165,46 @@ function createDoughnut(chartEl, tooltip, data) { onMounted(() => { misskeyApiGet('federation/stats', { limit: 30 }).then(fedStats => { - createDoughnut(subDoughnutEl.value, externalTooltipHandler1, fedStats.topSubInstances.map(x => ({ + type ChartData = { + name: string, + color: string | null, + value: number, + onClick?: () => void, + }[]; + + const subs: ChartData = fedStats.topSubInstances.map(x => ({ name: x.host, color: x.themeColor, value: x.followersCount, onClick: () => { os.pageWindow(`/instance-info/${x.host}`); }, - })).concat([{ name: '(other)', color: '#80808080', value: fedStats.otherFollowersCount }])); + })); - createDoughnut(pubDoughnutEl.value, externalTooltipHandler2, fedStats.topPubInstances.map(x => ({ + subs.push({ + name: '(other)', + color: '#80808080', + value: fedStats.otherFollowersCount, + }); + + createDoughnut(subDoughnutEl.value, externalTooltipHandler1, subs); + + const pubs: ChartData = fedStats.topPubInstances.map(x => ({ name: x.host, color: x.themeColor, value: x.followingCount, onClick: () => { os.pageWindow(`/instance-info/${x.host}`); }, - })).concat([{ name: '(other)', color: '#80808080', value: fedStats.otherFollowingCount }])); + })); + + pubs.push({ + name: '(other)', + color: '#80808080', + value: fedStats.otherFollowingCount, + }); + + createDoughnut(pubDoughnutEl.value, externalTooltipHandler2, pubs); }); }); </script> diff --git a/packages/frontend/src/components/MkInstanceTicker.vue b/packages/frontend/src/components/MkInstanceTicker.vue index 3ee2aa7174..b16838dcd5 100644 --- a/packages/frontend/src/components/MkInstanceTicker.vue +++ b/packages/frontend/src/components/MkInstanceTicker.vue @@ -30,7 +30,7 @@ const instance = props.instance ?? { themeColor: (document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement).content, }; -const faviconUrl = computed(() => props.instance ? getProxiedImageUrlNullable(props.instance.faviconUrl, 'preview') : getProxiedImageUrlNullable(Instance.iconUrl, 'preview') ?? getProxiedImageUrlNullable(Instance.faviconUrl, 'preview') ?? '/favicon.ico'); +const faviconUrl = computed(() => props.instance ? getProxiedImageUrlNullable(props.instance.faviconUrl, 'preview') : getProxiedImageUrlNullable(Instance.iconUrl, 'preview') ?? '/favicon.ico'); const themeColor = instance.themeColor ?? '#777777'; diff --git a/packages/frontend/src/components/MkLaunchPad.vue b/packages/frontend/src/components/MkLaunchPad.vue index 120ed7a86c..c3ece1ebc2 100644 --- a/packages/frontend/src/components/MkLaunchPad.vue +++ b/packages/frontend/src/components/MkLaunchPad.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<MkModal ref="modal" v-slot="{ type, maxHeight }" :preferType="preferedModalType" :anchor="anchor" :transparentBg="true" :src="src" @click="modal.close()" @closed="emit('closed')"> +<MkModal ref="modal" v-slot="{ type, maxHeight }" :preferType="preferedModalType" :anchor="anchor" :transparentBg="true" :src="src" @click="modal?.close()" @closed="emit('closed')"> <div class="szkkfdyq _popup _shadow" :class="{ asDrawer: type === 'drawer' }" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : '' }"> <div class="main"> <template v-for="item in items" :key="item.text"> @@ -63,7 +63,7 @@ const items = Object.keys(navbarItemDef).filter(k => !menu.includes(k)).map(k => })); function close() { - modal.value.close(); + modal.value?.close(); } </script> diff --git a/packages/frontend/src/components/MkMarquee.vue b/packages/frontend/src/components/MkMarquee.vue index 145b60c8e7..0ea75d4973 100644 --- a/packages/frontend/src/components/MkMarquee.vue +++ b/packages/frontend/src/components/MkMarquee.vue @@ -30,6 +30,7 @@ export default { const contentEl = ref<HTMLElement>(); function calc() { + if (!contentEl.value) return; const eachLength = contentEl.value.offsetWidth / props.repeat; const factor = 3000; const duration = props.duration / ((1 / eachLength) * factor); diff --git a/packages/frontend/src/components/global/MkAcct.vue b/packages/frontend/src/components/global/MkAcct.vue index 594494f3c8..0171e22c79 100644 --- a/packages/frontend/src/components/global/MkAcct.vue +++ b/packages/frontend/src/components/global/MkAcct.vue @@ -21,7 +21,7 @@ import { host as hostRaw } from '@/config.js'; import { defaultStore } from '@/store.js'; defineProps<{ - user: Misskey.entities.UserDetailed; + user: Misskey.entities.User; detail?: boolean; }>();