diff options
| author | syuilo <4439005+syuilo@users.noreply.github.com> | 2026-01-09 22:06:40 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-09 22:06:40 +0900 |
| commit | 41592eafb363e3c62ab2d3e5f41b38d7d083d3fb (patch) | |
| tree | 8f69243a5482ad4161eb28b69769684a221aa05c | |
| parent | fix(frontend): popupのemit型が正しく利用できるように修正 (#16... (diff) | |
| download | misskey-41592eafb363e3c62ab2d3e5f41b38d7d083d3fb.tar.gz misskey-41592eafb363e3c62ab2d3e5f41b38d7d083d3fb.tar.bz2 misskey-41592eafb363e3c62ab2d3e5f41b38d7d083d3fb.zip | |
refactor: make noImplicitAny true (#17083)
* wip
* Update emojis.emoji.vue
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* Update manager.ts
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* Update analytics.ts
233 files changed, 964 insertions, 961 deletions
diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index b5fd38a7d7..f71ec1d023 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -618,6 +618,9 @@ export const packedMeDetailedOnlySchema = { achievementEarned: { optional: true, ...notificationRecieveConfig }, app: { optional: true, ...notificationRecieveConfig }, test: { optional: true, ...notificationRecieveConfig }, + login: { optional: true, ...notificationRecieveConfig }, + createToken: { optional: true, ...notificationRecieveConfig }, + exportCompleted: { optional: true, ...notificationRecieveConfig }, }, }, emailNotificationTypes: { diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts index 804bd5d9b9..aeebceed5a 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts @@ -51,11 +51,13 @@ export const meta = { }, icon: { type: 'string', - optional: false, nullable: true, + optional: false, nullable: false, + enum: ['info', 'warning', 'error', 'success'], }, display: { type: 'string', optional: false, nullable: false, + enum: ['normal', 'banner', 'dialog'], }, isActive: { type: 'boolean', diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts index 660aa55bf8..b9448b4bc2 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts @@ -24,39 +24,7 @@ export const meta = { optional: false, nullable: false, items: { type: 'object', - optional: false, nullable: false, - properties: { - id: { - type: 'string', - optional: false, nullable: false, - format: 'id', - }, - aliases: { - type: 'array', - optional: false, nullable: false, - items: { - type: 'string', - optional: false, nullable: false, - }, - }, - name: { - type: 'string', - optional: false, nullable: false, - }, - category: { - type: 'string', - optional: false, nullable: true, - }, - host: { - type: 'string', - optional: false, nullable: true, - description: 'The local host is represented with `null`.', - }, - url: { - type: 'string', - optional: false, nullable: false, - }, - }, + ref: 'EmojiDetailed', }, }, } as const; diff --git a/packages/frontend-embed/src/components/EmMediaImage.vue b/packages/frontend-embed/src/components/EmMediaImage.vue index 94f0268da4..8cb90247fc 100644 --- a/packages/frontend-embed/src/components/EmMediaImage.vue +++ b/packages/frontend-embed/src/components/EmMediaImage.vue @@ -64,7 +64,7 @@ const url = computed(() => (props.raw) : props.image.thumbnailUrl, ); -async function onclick(ev: MouseEvent) { +async function onclick(ev: PointerEvent) { if (hide.value) { ev.stopPropagation(); hide.value = false; diff --git a/packages/frontend-embed/src/components/EmMfm.ts b/packages/frontend-embed/src/components/EmMfm.ts index 1f9ce9d4f4..5b9a53bbc2 100644 --- a/packages/frontend-embed/src/components/EmMfm.ts +++ b/packages/frontend-embed/src/components/EmMfm.ts @@ -299,7 +299,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven ]); } case 'clickable': { - return h('span', { onClick(ev: MouseEvent): void { + return h('span', { onClick(ev: PointerEvent): void { ev.stopPropagation(); ev.preventDefault(); const clickEv = typeof token.props.args.ev === 'string' ? token.props.args.ev : ''; diff --git a/packages/frontend-embed/src/pages/clip.vue b/packages/frontend-embed/src/pages/clip.vue index f4d4e8cf6f..a65f38aa7d 100644 --- a/packages/frontend-embed/src/pages/clip.vue +++ b/packages/frontend-embed/src/pages/clip.vue @@ -84,7 +84,7 @@ const pagination = computed(() => ({ const notesEl = useTemplateRef('notesEl'); -function top(ev: MouseEvent) { +function top(ev: PointerEvent) { const target = ev.target as HTMLElement | null; if (target && isLink(target)) return; diff --git a/packages/frontend-embed/src/pages/tag.vue b/packages/frontend-embed/src/pages/tag.vue index 4b00ae7c2d..8c6cd32bab 100644 --- a/packages/frontend-embed/src/pages/tag.vue +++ b/packages/frontend-embed/src/pages/tag.vue @@ -40,15 +40,15 @@ SPDX-License-Identifier: AGPL-3.0-only <script setup lang="ts"> import { computed, inject, useTemplateRef } from 'vue'; import { scrollToTop } from '@@/js/scroll.js'; +import { url, instanceName } from '@@/js/config.js'; +import { isLink } from '@@/js/is-link.js'; +import { defaultEmbedParams } from '@@/js/embed-page.js'; import type { Paging } from '@/components/EmPagination.vue'; import EmNotes from '@/components/EmNotes.vue'; import XNotFound from '@/pages/not-found.vue'; import EmTimelineContainer from '@/components/EmTimelineContainer.vue'; import { i18n } from '@/i18n.js'; -import { url, instanceName } from '@@/js/config.js'; -import { isLink } from '@@/js/is-link.js'; import { DI } from '@/di.js'; -import { defaultEmbedParams } from '@@/js/embed-page.js'; const props = defineProps<{ tag: string; @@ -67,7 +67,7 @@ const pagination = computed(() => ({ const notesEl = useTemplateRef('notesEl'); -function top(ev: MouseEvent) { +function top(ev: PointerEvent) { const target = ev.target as HTMLElement | null; if (target && isLink(target)) return; diff --git a/packages/frontend-embed/src/pages/user-timeline.vue b/packages/frontend-embed/src/pages/user-timeline.vue index 348b1a7622..4b5daf54da 100644 --- a/packages/frontend-embed/src/pages/user-timeline.vue +++ b/packages/frontend-embed/src/pages/user-timeline.vue @@ -49,6 +49,8 @@ import { ref, computed, inject, useTemplateRef } from 'vue'; import * as Misskey from 'misskey-js'; import { url, instanceName } from '@@/js/config.js'; import { defaultEmbedParams } from '@@/js/embed-page.js'; +import { scrollToTop } from '@@/js/scroll.js'; +import { isLink } from '@@/js/is-link.js'; import type { Paging } from '@/components/EmPagination.vue'; import EmNotes from '@/components/EmNotes.vue'; import EmAvatar from '@/components/EmAvatar.vue'; @@ -56,8 +58,6 @@ import EmUserName from '@/components/EmUserName.vue'; import I18n from '@/components/I18n.vue'; import XNotFound from '@/pages/not-found.vue'; import EmTimelineContainer from '@/components/EmTimelineContainer.vue'; -import { scrollToTop } from '@@/js/scroll.js'; -import { isLink } from '@@/js/is-link.js'; import { misskeyApi } from '@/misskey-api.js'; import { i18n } from '@/i18n.js'; import { assertServerContext } from '@/server-context.js'; @@ -101,7 +101,7 @@ const pagination = computed(() => ({ const notesEl = useTemplateRef('notesEl'); -function top(ev: MouseEvent) { +function top(ev: PointerEvent) { const target = ev.target as HTMLElement | null; if (target && isLink(target)) return; diff --git a/packages/frontend-embed/tsconfig.json b/packages/frontend-embed/tsconfig.json index 6694a90716..24fa71de19 100644 --- a/packages/frontend-embed/tsconfig.json +++ b/packages/frontend-embed/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "allowJs": true, "noEmitOnError": false, - "noImplicitAny": false, + "noImplicitAny": true, "noImplicitReturns": true, "noUnusedParameters": false, "noUnusedLocals": false, diff --git a/packages/frontend/.storybook/charts.ts b/packages/frontend/.storybook/charts.ts index 93e1287d69..7a92df8e86 100644 --- a/packages/frontend/.storybook/charts.ts +++ b/packages/frontend/.storybook/charts.ts @@ -33,7 +33,7 @@ export function getChartResolver(fields: string[], option?: { accumulate?: boole const res = {}; for (const field of fields) { const layers = field.split('.'); - let current = res; + let current = res as any; while (layers.length > 1) { const currentKey = layers.shift()!; if (current[currentKey] == null) current[currentKey] = {}; diff --git a/packages/frontend/package.json b/packages/frontend/package.json index d5fdeed249..195a8879d0 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -20,7 +20,6 @@ "@discordapp/twemoji": "16.0.1", "@github/webauthn-json": "2.1.1", "@mcaptcha/vanilla-glue": "0.1.0-rc2", - "i18n": "workspace:*", "@misskey-dev/browser-image-resizer": "2024.1.0", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-replace": "6.0.3", @@ -48,6 +47,7 @@ "execa": "9.6.1", "exifreader": "4.33.1", "frontend-shared": "workspace:*", + "i18n": "workspace:*", "icons-subsetter": "workspace:*", "idb-keyval": "6.2.2", "insert-text-at-cursor": "0.3.0", @@ -100,12 +100,14 @@ "@testing-library/vue": "8.1.0", "@types/canvas-confetti": "1.9.0", "@types/estree": "1.0.8", + "@types/insert-text-at-cursor": "0.3.2", "@types/matter-js": "0.20.2", "@types/micromatch": "4.0.10", "@types/node": "24.10.4", "@types/punycode.js": "npm:@types/punycode@2.1.4", "@types/sanitize-html": "2.16.0", "@types/seedrandom": "3.0.8", + "@types/textarea-caret": "3.0.4", "@types/throttle-debounce": "5.0.2", "@types/tinycolor2": "1.4.6", "@typescript-eslint/eslint-plugin": "8.50.1", diff --git a/packages/frontend/src/accounts.ts b/packages/frontend/src/accounts.ts index 79086c2b39..862ef4e113 100644 --- a/packages/frontend/src/accounts.ts +++ b/packages/frontend/src/accounts.ts @@ -126,10 +126,10 @@ export function updateCurrentAccount(accountData: Misskey.entities.MeDetailed) { if (!$i) return; const token = $i.token; for (const key of Object.keys($i)) { - delete $i[key]; + delete $i[key as keyof typeof $i]; } for (const [key, value] of Object.entries(accountData)) { - $i[key] = value; + ($i[key as keyof typeof accountData] as any) = value; } store.set('accountInfos', { ...store.s.accountInfos, [host + '/' + $i.id]: $i }); $i.token = token; @@ -139,7 +139,7 @@ export function updateCurrentAccount(accountData: Misskey.entities.MeDetailed) { export function updateCurrentAccountPartial(accountData: Partial<Misskey.entities.MeDetailed>) { if (!$i) return; for (const [key, value] of Object.entries(accountData)) { - $i[key] = value; + ($i[key as keyof typeof accountData] as any) = value; } store.set('accountInfos', { ...store.s.accountInfos, [host + '/' + $i.id]: $i }); diff --git a/packages/frontend/src/aiscript/ui.ts b/packages/frontend/src/aiscript/ui.ts index 9c330da3c5..768425746d 100644 --- a/packages/frontend/src/aiscript/ui.ts +++ b/packages/frontend/src/aiscript/ui.ts @@ -531,7 +531,7 @@ function getPostFormOptions(def: values.Value | undefined, call: (fn: values.VFn export function registerAsUiLib(components: Ref<AsUiComponent>[], done: (root: Ref<AsUiRoot>) => void) { type OptionsConverter<T extends AsUiComponent, C> = (def: values.Value | undefined, call: C) => Options<T>; - const instances = {}; + const instances = {} as Record<string, values.VObj>; function createComponentInstance<T extends AsUiComponent, C>( type: T['type'], @@ -555,7 +555,7 @@ export function registerAsUiLib(components: Ref<AsUiComponent>[], done: (root: R const updates = getOptions(def, call); for (const update of def.value.keys()) { if (!Object.hasOwn(updates, update)) continue; - component.value[update] = updates[update]; + component.value[update] = updates[update as keyof Options<T>]; } })], ])); diff --git a/packages/frontend/src/analytics.ts b/packages/frontend/src/analytics.ts index e07a4e9258..9889018ddb 100644 --- a/packages/frontend/src/analytics.ts +++ b/packages/frontend/src/analytics.ts @@ -90,6 +90,7 @@ export async function initAnalytics(instance: Misskey.entities.MetaDetailed) { // Google Analytics if (instance.googleAnalyticsMeasurementId) { + //@ts-expect-error Dynamic import const { default: googleAnalytics } = await import('@analytics/google-analytics'); plugins.push(googleAnalytics({ diff --git a/packages/frontend/src/components/MkAbuseReport.vue b/packages/frontend/src/components/MkAbuseReport.vue index c7252e7c98..cbc5b27fca 100644 --- a/packages/frontend/src/components/MkAbuseReport.vue +++ b/packages/frontend/src/components/MkAbuseReport.vue @@ -115,7 +115,7 @@ watch(moderationNote, async () => { }); }); -function resolve(resolvedAs) { +function resolve(resolvedAs: 'accept' | 'reject' | null) { os.apiWithDialog('admin/resolve-abuse-user-report', { reportId: props.report.id, resolvedAs, @@ -132,7 +132,7 @@ function forward() { }); } -function showMenu(ev: MouseEvent) { +function showMenu(ev: PointerEvent) { os.popupMenu([{ icon: 'ti ti-hash', text: 'Copy ID', diff --git a/packages/frontend/src/components/MkAchievements.vue b/packages/frontend/src/components/MkAchievements.vue index c786e9fe9f..d0e138c229 100644 --- a/packages/frontend/src/components/MkAchievements.vue +++ b/packages/frontend/src/components/MkAchievements.vue @@ -23,13 +23,13 @@ SPDX-License-Identifier: AGPL-3.0-only </div> <div :class="$style.body"> <div :class="$style.header"> - <span :class="$style.title">{{ i18n.ts._achievements._types['_' + achievement.name].title }}</span> + <span :class="$style.title">{{ (i18n.ts._achievements._types as any)['_' + achievement.name].title }}</span> <span :class="$style.time"> <time v-tooltip="new Date(achievement.unlockedAt).toLocaleString()">{{ new Date(achievement.unlockedAt).getFullYear() }}/{{ new Date(achievement.unlockedAt).getMonth() + 1 }}/{{ new Date(achievement.unlockedAt).getDate() }}</time> </span> </div> - <div :class="$style.description">{{ withDescription ? i18n.ts._achievements._types['_' + achievement.name].description : '???' }}</div> - <div v-if="i18n.ts._achievements._types['_' + achievement.name].flavor && withDescription" :class="$style.flavor">{{ i18n.ts._achievements._types['_' + achievement.name].flavor }}</div> + <div :class="$style.description">{{ withDescription ? (i18n.ts._achievements._types as any)['_' + achievement.name].description : '???' }}</div> + <div v-if="(i18n.ts._achievements._types as any)['_' + achievement.name].flavor && withDescription" :class="$style.flavor">{{ (i18n.ts._achievements._types as any)['_' + achievement.name].flavor }}</div> </div> </div> <template v-if="withLocked"> @@ -54,7 +54,6 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import * as Misskey from 'misskey-js'; import { onMounted, ref, computed } from 'vue'; -import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; import { ACHIEVEMENT_TYPES, ACHIEVEMENT_BADGES, claimAchievement } from '@/utility/achievements.js'; diff --git a/packages/frontend/src/components/MkAsUi.vue b/packages/frontend/src/components/MkAsUi.vue index a3b6112629..d44e7724b3 100644 --- a/packages/frontend/src/components/MkAsUi.vue +++ b/packages/frontend/src/components/MkAsUi.vue @@ -64,13 +64,13 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref, computed } from 'vue'; import type { Ref } from 'vue'; +import type { AsUiComponent, AsUiRoot, AsUiPostFormButton } from '@/aiscript/ui.js'; import * as os from '@/os.js'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkTextarea from '@/components/MkTextarea.vue'; import MkSelect from '@/components/MkSelect.vue'; -import type { AsUiComponent, AsUiRoot, AsUiPostFormButton } from '@/aiscript/ui.js'; import MkFolder from '@/components/MkFolder.vue'; import MkPostForm from '@/components/MkPostForm.vue'; import { useMkSelect } from '@/composables/use-mkselect.js'; @@ -144,7 +144,7 @@ const { initialValue: (c.type === 'select' && 'default' in c && typeof c.default !== 'boolean') ? c.default ?? null : null, }); -function onSelectUpdate(v) { +function onSelectUpdate(v: string | null) { valueForSelect.value = v; if ('onChange' in c && c.onChange) { c.onChange(v as never); diff --git a/packages/frontend/src/components/MkAuthConfirm.vue b/packages/frontend/src/components/MkAuthConfirm.vue index 8744b50926..b1a29660ad 100644 --- a/packages/frontend/src/components/MkAuthConfirm.vue +++ b/packages/frontend/src/components/MkAuthConfirm.vue @@ -183,7 +183,7 @@ async function init() { init(); -function clickAddAccount(ev: MouseEvent) { +function clickAddAccount(ev: PointerEvent) { selectedUser.value = null; os.popupMenu([{ diff --git a/packages/frontend/src/components/MkAutocomplete.stories.impl.ts b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts index 15aab8daed..9104650752 100644 --- a/packages/frontend/src/components/MkAutocomplete.stories.impl.ts +++ b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts @@ -3,15 +3,14 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ import { action } from 'storybook/actions'; import { expect, userEvent, waitFor, within } from '@storybook/test'; -import type { StoryObj } from '@storybook/vue3'; import { HttpResponse, http } from 'msw'; import { userDetailed } from '../../.storybook/fakes.js'; import { commonHandlers } from '../../.storybook/mocks.js'; import MkAutocomplete from './MkAutocomplete.vue'; import MkInput from './MkInput.vue'; +import type { StoryObj } from '@storybook/vue3'; import { tick } from '@/utility/test-utils.js'; const common = { render(args) { @@ -81,7 +80,7 @@ export const User = { ...common.args, type: 'user', }, - async play({ canvasElement }) { + async play({ canvasElement }: { canvasElement: HTMLElement }) { const canvas = within(canvasElement); const input = canvas.getByRole('combobox'); await waitFor(() => userEvent.hover(input)); @@ -114,7 +113,7 @@ export const Hashtag = { ...common.args, type: 'hashtag', }, - async play({ canvasElement }) { + async play({ canvasElement }: { canvasElement: HTMLElement }) { const canvas = within(canvasElement); const input = canvas.getByRole('combobox'); await waitFor(() => userEvent.hover(input)); diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index cf5d95e11b..5e860351af 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -50,7 +50,7 @@ import { emojilist, getEmojiName } from '@@/js/emojilist.js'; import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@@/js/emoji-base.js'; import { MFM_TAGS, MFM_PARAMS } from '@@/js/const.js'; import type { EmojiDef } from '@/utility/search-emoji.js'; -import contains from '@/utility/contains.js'; +import { elementContains } from '@/utility/element-contains.js'; import { acct } from '@/filters/user.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; @@ -310,8 +310,8 @@ function exec() { } } -function onMousedown(event: Event) { - if (!contains(rootEl.value, event.target) && (rootEl.value !== event.target)) props.close(); +function onMousedown(event: MouseEvent) { + if (!elementContains(rootEl.value, event.target as Element) && (rootEl.value !== event.target)) props.close(); } function onKeydown(event: KeyboardEvent) { diff --git a/packages/frontend/src/components/MkButton.vue b/packages/frontend/src/components/MkButton.vue index b729128a21..854ed31ed5 100644 --- a/packages/frontend/src/components/MkButton.vue +++ b/packages/frontend/src/components/MkButton.vue @@ -63,7 +63,7 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (ev: 'click', payload: MouseEvent): void; + (ev: 'click', payload: PointerEvent): void; }>(); const el = useTemplateRef('el'); @@ -77,11 +77,11 @@ onMounted(() => { } }); -function distance(p, q): number { +function distance(p: { x: number; y: number }, q: { x: number; y: number }): number { return Math.hypot(p.x - q.x, p.y - q.y); } -function calcCircleScale(boxW, boxH, circleCenterX, circleCenterY): number { +function calcCircleScale(boxW: number, boxH: number, circleCenterX: number, circleCenterY: number): number { const origin = { x: circleCenterX, y: circleCenterY }; const dist1 = distance({ x: 0, y: 0 }, origin); const dist2 = distance({ x: boxW, y: 0 }, origin); diff --git a/packages/frontend/src/components/MkCaptcha.vue b/packages/frontend/src/components/MkCaptcha.vue index 30940a34a9..6e4a8c13d2 100644 --- a/packages/frontend/src/components/MkCaptcha.vue +++ b/packages/frontend/src/components/MkCaptcha.vue @@ -84,7 +84,7 @@ const variable = computed(() => { } }); -const loaded = !!window[variable.value]; +const loaded = !!(window as any)[variable.value]; const src = computed(() => { switch (props.provider) { @@ -98,7 +98,7 @@ const src = computed(() => { const scriptId = computed(() => `script-${props.provider}`); -const captcha = computed<Captcha>(() => window[variable.value] || {} as unknown as Captcha); +const captcha = computed<Captcha>(() => (window as any)[variable.value] ?? {} as unknown as Captcha); watch(() => [props.instanceUrl, props.sitekey, props.secretKey], async () => { // 変更があったときはリフレッシュと再レンダリングをしておかないと、変更後の値で再検証が出来ない diff --git a/packages/frontend/src/components/MkChannelList.vue b/packages/frontend/src/components/MkChannelList.vue index 23bb32c6b9..af89ec8252 100644 --- a/packages/frontend/src/components/MkChannelList.vue +++ b/packages/frontend/src/components/MkChannelList.vue @@ -24,6 +24,6 @@ const props = withDefaults(defineProps<{ noGap?: boolean; extractor?: ExtractorFunction<P, Misskey.entities.Channel>; }>(), { - extractor: (item) => item, + extractor: (item: any) => item as Misskey.entities.Channel, }); </script> diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue index c54081ad42..e418e729ca 100644 --- a/packages/frontend/src/components/MkChart.vue +++ b/packages/frontend/src/components/MkChart.vue @@ -94,8 +94,8 @@ const props = withDefaults(defineProps<{ const legendEl = useTemplateRef('legendEl'); -const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b)); -const negate = arr => arr.map(x => -x); +const sum = (...arr: number[][]) => arr.reduce((r, a) => r.map((b, i) => a[i] + b)); +const negate = (arr: number[]) => arr.map((x) => -x); const colors = { blue: '#008FFB', @@ -108,7 +108,7 @@ const colors = { cyan: '#00e0e0', }; const colorSets = [colors.blue, colors.green, colors.yellow, colors.red, colors.purple]; -const getColor = (i) => { +const getColor = (i: number) => { return colorSets[i % colorSets.length]; }; @@ -142,7 +142,7 @@ const getDate = (ago: number) => { return props.span === 'day' ? new Date(y, m, d - ago) : new Date(y, m, d, h - ago); }; -const format = (arr) => { +const format = (arr: number[]) => { return arr.map((v, i) => ({ x: getDate(i).getTime(), y: v, @@ -371,7 +371,7 @@ const fetchApRequestChart = async (): Promise<typeof chartData> => { }; }; -const fetchNotesChart = async (type: string): Promise<typeof chartData> => { +const fetchNotesChart = async (type: 'local' | 'remote' | 'combined'): Promise<typeof chartData> => { const raw = await misskeyApiGet('charts/notes', { limit: props.limit, span: props.span }); return { series: [{ diff --git a/packages/frontend/src/components/MkClickerGame.vue b/packages/frontend/src/components/MkClickerGame.vue index 775964af50..0c856c57eb 100644 --- a/packages/frontend/src/components/MkClickerGame.vue +++ b/packages/frontend/src/components/MkClickerGame.vue @@ -20,9 +20,9 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, onMounted, onUnmounted, ref } from 'vue'; +import { useInterval } from '@@/js/use-interval.js'; import MkPlusOneEffect from '@/components/MkPlusOneEffect.vue'; import * as os from '@/os.js'; -import { useInterval } from '@@/js/use-interval.js'; import * as game from '@/utility/clicker-game.js'; import number from '@/filters/number.js'; import { claimAchievement } from '@/utility/achievements.js'; @@ -32,7 +32,7 @@ const cookies = computed(() => saveData.value?.cookies); const cps = ref(0); const prevCookies = ref(0); -function onClick(ev: MouseEvent) { +function onClick(ev: PointerEvent) { const x = ev.clientX; const y = ev.clientY; const { dispose } = os.popup(MkPlusOneEffect, { x, y }, { diff --git a/packages/frontend/src/components/MkCodeEditor.vue b/packages/frontend/src/components/MkCodeEditor.vue index bdb2ba6a44..dda5a14716 100644 --- a/packages/frontend/src/components/MkCodeEditor.vue +++ b/packages/frontend/src/components/MkCodeEditor.vue @@ -40,7 +40,7 @@ import XCode from '@/components/MkCode.core.vue'; const props = withDefaults(defineProps<{ modelValue: string | null; - lang: string; + lang?: string; required?: boolean; readonly?: boolean; disabled?: boolean; @@ -51,7 +51,7 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (ev: 'change', _ev: KeyboardEvent): void; + (ev: 'change', _ev: InputEvent): void; (ev: 'keydown', _ev: KeyboardEvent): void; (ev: 'enter'): void; (ev: 'update:modelValue', value: string): void; @@ -63,15 +63,17 @@ const focused = ref(false); const changed = ref(false); const inputEl = useTemplateRef('inputEl'); -const focus = () => inputEl.value?.focus(); +function focus() { + inputEl.value?.focus(); +} -const onInput = (ev) => { - v.value = ev.target?.value ?? v.value; +function onInput(ev: InputEvent) { + v.value = (inputEl.value?.value) ?? ''; changed.value = true; emit('change', ev); -}; +} -const onKeydown = (ev: KeyboardEvent) => { +function onKeydown(ev: KeyboardEvent) { if (ev.isComposing || ev.key === 'Process' || ev.keyCode === 229) return; emit('keydown', ev); @@ -102,12 +104,12 @@ const onKeydown = (ev: KeyboardEvent) => { }); ev.preventDefault(); } -}; +} -const updated = () => { +function updated() { changed.value = false; emit('update:modelValue', v.value); -}; +} const debouncedUpdated = debounce(1000, updated); diff --git a/packages/frontend/src/components/MkContextMenu.stories.impl.ts b/packages/frontend/src/components/MkContextMenu.stories.impl.ts index 7a5e36131b..fc9fd9bc49 100644 --- a/packages/frontend/src/components/MkContextMenu.stories.impl.ts +++ b/packages/frontend/src/components/MkContextMenu.stories.impl.ts @@ -3,11 +3,9 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable import/no-default-export */ -import type { StoryObj } from '@storybook/vue3'; import { userEvent, within } from '@storybook/test'; import MkContextMenu from './MkContextMenu.vue'; +import type { StoryObj } from '@storybook/vue3'; import * as os from '@/os.js'; export const Empty = { render(args) { @@ -25,7 +23,7 @@ export const Empty = { }, }, methods: { - onContextmenu(ev: MouseEvent) { + onContextmenu(ev: PointerEvent) { os.contextMenu(args.items, ev); }, }, diff --git a/packages/frontend/src/components/MkContextMenu.vue b/packages/frontend/src/components/MkContextMenu.vue index 9c6397a72c..6678c8fb91 100644 --- a/packages/frontend/src/components/MkContextMenu.vue +++ b/packages/frontend/src/components/MkContextMenu.vue @@ -21,13 +21,13 @@ SPDX-License-Identifier: AGPL-3.0-only import { onMounted, onBeforeUnmount, useTemplateRef, ref } from 'vue'; import MkMenu from './MkMenu.vue'; import type { MenuItem } from '@/types/menu.js'; -import contains from '@/utility/contains.js'; +import { elementContains } from '@/utility/element-contains.js'; import { prefer } from '@/preferences.js'; import * as os from '@/os.js'; const props = defineProps<{ items: MenuItem[]; - ev: MouseEvent; + ev: PointerEvent; }>(); const emit = defineEmits<{ @@ -75,8 +75,8 @@ onBeforeUnmount(() => { window.document.body.removeEventListener('mousedown', onMousedown); }); -function onMousedown(evt: Event) { - if (!contains(rootEl.value, evt.target) && (rootEl.value !== evt.target)) emit('closed'); +function onMousedown(evt: MouseEvent) { + if (!elementContains(rootEl.value, evt.target as Element) && (rootEl.value !== evt.target)) emit('closed'); } </script> diff --git a/packages/frontend/src/components/MkDrive.file.vue b/packages/frontend/src/components/MkDrive.file.vue index 0eca85b3a6..e2858084c0 100644 --- a/packages/frontend/src/components/MkDrive.file.vue +++ b/packages/frontend/src/components/MkDrive.file.vue @@ -64,7 +64,7 @@ const isDragging = ref(false); const title = computed(() => `${props.file.name}\n${props.file.type} ${bytes(props.file.size)}`); -function onContextmenu(ev: MouseEvent) { +function onContextmenu(ev: PointerEvent) { os.contextMenu(getDriveFileMenu(props.file, props.folder), ev); } diff --git a/packages/frontend/src/components/MkDrive.folder.vue b/packages/frontend/src/components/MkDrive.folder.vue index 8b2609852c..6d93dfc0d4 100644 --- a/packages/frontend/src/components/MkDrive.folder.vue +++ b/packages/frontend/src/components/MkDrive.folder.vue @@ -57,7 +57,7 @@ const props = withDefaults(defineProps<{ const emit = defineEmits<{ (ev: 'chosen', v: Misskey.entities.DriveFolder): void; (ev: 'unchose', v: Misskey.entities.DriveFolder): void; - (ev: 'upload', files: File[], folder: Misskey.entities.DriveFolder); + (ev: 'upload', files: File[], folder: Misskey.entities.DriveFolder): void; (ev: 'dragstart'): void; (ev: 'dragend'): void; }>(); @@ -277,7 +277,7 @@ function setAsUploadFolder() { prefer.commit('uploadFolder', props.folder.id); } -function onContextmenu(ev: MouseEvent) { +function onContextmenu(ev: PointerEvent) { let menu: MenuItem[]; menu = [{ text: i18n.ts.openInWindow, diff --git a/packages/frontend/src/components/MkDrive.vue b/packages/frontend/src/components/MkDrive.vue index 6e286f4882..2961bc5032 100644 --- a/packages/frontend/src/components/MkDrive.vue +++ b/packages/frontend/src/components/MkDrive.vue @@ -139,7 +139,9 @@ SPDX-License-Identifier: AGPL-3.0-only primary rounded @click="fetchMoreFiles" - >{{ i18n.ts.loadMore }}</MkButton> + > + {{ i18n.ts.loadMore }} + </MkButton> <div v-if="filesPaginator.items.value.length == 0 && foldersPaginator.items.value.length == 0 && !fetching" :class="$style.empty"> <div v-if="draghover">{{ i18n.ts.dropHereToUpload }}</div> @@ -491,7 +493,7 @@ function deleteFolder(folderToDelete: Misskey.entities.DriveFolder) { }); } -function onFileClick(ev: MouseEvent, file: Misskey.entities.DriveFile) { +function onFileClick(ev: PointerEvent, file: Misskey.entities.DriveFile) { if (ev.shiftKey) { isEditMode.value = true; } @@ -563,7 +565,7 @@ function cd(target?: Misskey.entities.DriveFolder | Misskey.entities.DriveFolder folder.value = folderToMove; hierarchyFolders.value = []; - const dive = folderToDive => { + const dive = (folderToDive: Misskey.entities.DriveFolder) => { hierarchyFolders.value.unshift(folderToDive); if (folderToDive.parent) dive(folderToDive.parent); }; @@ -689,11 +691,11 @@ function getMenu() { return menu; } -function showMenu(ev: MouseEvent) { +function showMenu(ev: PointerEvent) { os.popupMenu(getMenu(), (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined); } -function onContextmenu(ev: MouseEvent) { +function onContextmenu(ev: PointerEvent) { os.contextMenu(getMenu(), ev); } diff --git a/packages/frontend/src/components/MkEmojiPicker.section.vue b/packages/frontend/src/components/MkEmojiPicker.section.vue index ef515e471f..3ee32710e5 100644 --- a/packages/frontend/src/components/MkEmojiPicker.section.vue +++ b/packages/frontend/src/components/MkEmojiPicker.section.vue @@ -62,8 +62,8 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref, computed } from 'vue'; -import type { Ref } from 'vue'; import { getEmojiName } from '@@/js/emojilist.js'; +import type { Ref } from 'vue'; import type { CustomEmojiFolderTree } from '@@/js/emojilist.js'; import { i18n } from '@/i18n.js'; import { customEmojis } from '@/custom-emojis.js'; @@ -78,7 +78,7 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (ev: 'chosen', v: string, event: MouseEvent): void; + (ev: 'chosen', v: string, event: PointerEvent): void; }>(); const emojis = computed(() => Array.isArray(props.emojis) ? props.emojis : props.emojis.value); @@ -86,13 +86,13 @@ const emojis = computed(() => Array.isArray(props.emojis) ? props.emojis : props const shown = ref(!!props.initialShown); /** @see MkEmojiPicker.vue */ -function computeButtonTitle(ev: MouseEvent): void { +function computeButtonTitle(ev: PointerEvent): void { const elm = ev.target as HTMLElement; const emoji = elm.dataset.emoji as string; elm.title = getEmojiName(emoji); } -function nestedChosen(emoji: string, ev: MouseEvent) { +function nestedChosen(emoji: string, ev: PointerEvent) { emit('chosen', emoji, ev); } </script> diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index 33e9137c2f..bf0f9d0130 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -412,13 +412,13 @@ function getDef(emoji: string): string | Misskey.entities.EmojiSimple | UnicodeE } /** @see MkEmojiPicker.section.vue */ -function computeButtonTitle(ev: MouseEvent): void { +function computeButtonTitle(ev: PointerEvent): void { const elm = ev.target as HTMLElement; const emoji = elm.dataset.emoji as string; elm.title = getEmojiName(emoji); } -function chosen(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef, ev?: MouseEvent) { +function chosen(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef, ev?: PointerEvent) { const el = ev && (ev.currentTarget ?? ev.target) as HTMLElement | null | undefined; if (el && prefer.s.animation) { const rect = el.getBoundingClientRect(); diff --git a/packages/frontend/src/components/MkExtensionInstaller.vue b/packages/frontend/src/components/MkExtensionInstaller.vue index c9d18ee731..9c0cce7689 100644 --- a/packages/frontend/src/components/MkExtensionInstaller.vue +++ b/packages/frontend/src/components/MkExtensionInstaller.vue @@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #key>{{ i18n.ts.permission }}</template> <template #value> <ul v-if="extension.meta.permissions && extension.meta.permissions.length > 0" :class="$style.extInstallerKVList"> - <li v-for="permission in extension.meta.permissions" :key="permission">{{ i18n.ts._permissions[permission] }}</li> + <li v-for="permission in extension.meta.permissions" :key="permission">{{ (i18n.ts._permissions as any)[permission] ?? permission }}</li> </ul> <template v-else>{{ i18n.ts.none }}</template> </template> diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue index 94fdf6da36..864f53d09c 100644 --- a/packages/frontend/src/components/MkFolder.vue +++ b/packages/frontend/src/components/MkFolder.vue @@ -169,7 +169,7 @@ function afterLeave(el: Element) { let pageId = pageFolderTeleportCount.value; pageFolderTeleportCount.value += 1000; -async function toggle(ev: MouseEvent) { +async function toggle(ev: PointerEvent) { if (asPage && !opened.value) { pageId++; const { dispose } = await popup(MkFolderPage, { diff --git a/packages/frontend/src/components/MkForm.file.vue b/packages/frontend/src/components/MkForm.file.vue index 182ff3ccf5..d233467e8b 100644 --- a/packages/frontend/src/components/MkForm.file.vue +++ b/packages/frontend/src/components/MkForm.file.vue @@ -50,7 +50,7 @@ if (props.fileId) { }); } -function selectButton(ev: MouseEvent) { +function selectButton(ev: PointerEvent) { selectFile({ anchorElement: ev.currentTarget ?? ev.target, multiple: false, diff --git a/packages/frontend/src/components/MkHeatmap.vue b/packages/frontend/src/components/MkHeatmap.vue index abbf86004b..03780bf3ba 100644 --- a/packages/frontend/src/components/MkHeatmap.vue +++ b/packages/frontend/src/components/MkHeatmap.vue @@ -125,8 +125,7 @@ async function renderChart() { data: format(values) as any, borderWidth: 0, borderRadius: 3, - backgroundColor(c) { - // @ts-expect-error TS(2339) + backgroundColor(c: any) { const value = c.dataset.data[c.dataIndex].v as number; let a = (value - min) / max; if (value !== 0) { // 0でない限りは完全に不可視にはしない @@ -195,7 +194,7 @@ async function renderChart() { font: { size: 9, }, - callback: (value, index, values) => ['', 'Mon', '', 'Wed', '', 'Fri', ''][value], + callback: (value, index, values) => ['', 'Mon', '', 'Wed', '', 'Fri', ''][value as any], }, }, }, diff --git a/packages/frontend/src/components/MkImageEffectorDialog.vue b/packages/frontend/src/components/MkImageEffectorDialog.vue index 01df7d7496..f740002088 100644 --- a/packages/frontend/src/components/MkImageEffectorDialog.vue +++ b/packages/frontend/src/components/MkImageEffectorDialog.vue @@ -95,7 +95,7 @@ watch(layers, async () => { } }, { deep: true }); -function addEffect(ev: MouseEvent) { +function addEffect(ev: PointerEvent) { os.popupMenu(Object.entries(FXS).map(([id, fx]) => ({ text: fx.uiDefinition.name, action: () => { @@ -219,7 +219,7 @@ watch(enabled, () => { const penMode = ref<'fill' | 'blur' | 'pixelate' | null>(null); -function showPenMenu(ev: MouseEvent) { +function showPenMenu(ev: PointerEvent) { os.popupMenu([{ text: i18n.ts._imageEffector._fxs.fill, action: () => { diff --git a/packages/frontend/src/components/MkInput.vue b/packages/frontend/src/components/MkInput.vue index 7f052dff94..4f6ca083a3 100644 --- a/packages/frontend/src/components/MkInput.vue +++ b/packages/frontend/src/components/MkInput.vue @@ -88,7 +88,7 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (ev: 'change', _ev: KeyboardEvent): void; + (ev: 'change', _ev: InputEvent): void; (ev: 'keydown', _ev: KeyboardEvent): void; (ev: 'enter', _ev: KeyboardEvent): void; (ev: 'update:modelValue', value: ModelValueType<T>): void; @@ -111,10 +111,9 @@ const height = let autocompleteWorker: Autocomplete | null = null; const focus = () => inputEl.value?.focus(); -const onInput = (event: Event) => { - const ev = event as KeyboardEvent; +const onInput = (event: InputEvent) => { changed.value = true; - emit('change', ev); + emit('change', event); }; const onKeydown = (ev: KeyboardEvent) => { if (ev.isComposing || ev.key === 'Process' || ev.keyCode === 229) return; diff --git a/packages/frontend/src/components/MkInstanceCardMini.vue b/packages/frontend/src/components/MkInstanceCardMini.vue index 7902151921..130a0e9986 100644 --- a/packages/frontend/src/components/MkInstanceCardMini.vue +++ b/packages/frontend/src/components/MkInstanceCardMini.vue @@ -33,7 +33,7 @@ misskeyApiGet('charts/instance', { host: props.instance.host, limit: 16 + 1, spa chartValues.value = res.requests.received; }); -function getInstanceIcon(instance): string { +function getInstanceIcon(instance: Misskey.entities.FederationInstance): string { return getProxiedImageUrlNullable(instance.iconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.faviconUrl, 'preview') ?? '/client-assets/dummy.png'; } </script> diff --git a/packages/frontend/src/components/MkInstanceStats.vue b/packages/frontend/src/components/MkInstanceStats.vue index 13048a2e1b..368fa5be27 100644 --- a/packages/frontend/src/components/MkInstanceStats.vue +++ b/packages/frontend/src/components/MkInstanceStats.vue @@ -57,10 +57,10 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { onMounted, computed, useTemplateRef } from 'vue'; import { Chart } from 'chart.js'; -import MkSelect from '@/components/MkSelect.vue'; import type { MkSelectItem, ItemOption } from '@/components/MkSelect.vue'; -import MkChart from '@/components/MkChart.vue'; import type { ChartSrc } from '@/components/MkChart.vue'; +import MkSelect from '@/components/MkSelect.vue'; +import MkChart from '@/components/MkChart.vue'; import { useChartTooltip } from '@/composables/use-chart-tooltip.js'; import { $i } from '@/i.js'; import * as os from '@/os.js'; @@ -172,7 +172,14 @@ const { handler: externalTooltipHandler2 } = useChartTooltip({ position: 'middle', }); -function createDoughnut(chartEl, tooltip, data) { +type ChartData = { + name: string, + color: string, + value: number, + onClick?: () => void, +}[]; + +function createDoughnut(chartEl: HTMLCanvasElement, tooltip: ReturnType<typeof useChartTooltip>['handler'], data: ChartData) { const chartInstance = new Chart(chartEl, { type: 'doughnut', data: { @@ -198,8 +205,8 @@ function createDoughnut(chartEl, tooltip, data) { onClick: (ev) => { if (ev.native == null) return; const hit = chartInstance.getElementsAtEventForMode(ev.native, 'nearest', { intersect: true }, false)[0]; - if (hit && data[hit.index].onClick) { - data[hit.index].onClick(); + if (hit != null) { + data[hit.index].onClick?.(); } }, plugins: { @@ -223,16 +230,9 @@ function createDoughnut(chartEl, tooltip, data) { onMounted(() => { misskeyApiGet('federation/stats', { limit: 30 }).then(fedStats => { - type ChartData = { - name: string, - color: string | null, - value: number, - onClick?: () => void, - }[]; - const subs: ChartData = fedStats.topSubInstances.map(x => ({ name: x.host, - color: x.themeColor, + color: x.themeColor ?? '#888888', value: x.followersCount, onClick: () => { os.pageWindow(`/instance-info/${x.host}`); @@ -245,11 +245,11 @@ onMounted(() => { value: fedStats.otherFollowersCount, }); - createDoughnut(subDoughnutEl.value, externalTooltipHandler1, subs); + if (subDoughnutEl.value != null) createDoughnut(subDoughnutEl.value, externalTooltipHandler1, subs); const pubs: ChartData = fedStats.topPubInstances.map(x => ({ name: x.host, - color: x.themeColor, + color: x.themeColor ?? '#888888', value: x.followingCount, onClick: () => { os.pageWindow(`/instance-info/${x.host}`); @@ -262,7 +262,7 @@ onMounted(() => { value: fedStats.otherFollowingCount, }); - createDoughnut(pubDoughnutEl.value, externalTooltipHandler2, pubs); + if (pubDoughnutEl.value != null) createDoughnut(pubDoughnutEl.value, externalTooltipHandler2, pubs); }); }); </script> diff --git a/packages/frontend/src/components/MkLaunchPad.vue b/packages/frontend/src/components/MkLaunchPad.vue index d8725ade0b..0c73df4e2d 100644 --- a/packages/frontend/src/components/MkLaunchPad.vue +++ b/packages/frontend/src/components/MkLaunchPad.vue @@ -8,13 +8,13 @@ SPDX-License-Identifier: AGPL-3.0-only <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"> - <button v-if="item.action" v-click-anime class="_button item" @click="$event => { item.action($event); close(); }"> + <button v-if="item.action != null" v-click-anime class="_button item" @click="$event => { item.action!($event); close(); }"> <i class="icon" :class="item.icon"></i> <div class="text">{{ item.text }}</div> <span v-if="item.indicate && item.indicateValue" class="_indicateCounter indicatorWithValue">{{ item.indicateValue }}</span> <span v-else-if="item.indicate" class="indicator _blink"><i class="_indicatorCircle"></i></span> </button> - <MkA v-else v-click-anime :to="item.to" class="item" @click.passive="close()"> + <MkA v-else-if="item.to != null" v-click-anime :to="item.to" class="item" @click.passive="close()"> <i class="icon" :class="item.icon"></i> <div class="text">{{ item.text }}</div> <span v-if="item.indicate && item.indicateValue" class="_indicateCounter indicatorWithValue">{{ item.indicateValue }}</span> diff --git a/packages/frontend/src/components/MkMediaImage.vue b/packages/frontend/src/components/MkMediaImage.vue index 345c261776..c7d8c95b7b 100644 --- a/packages/frontend/src/components/MkMediaImage.vue +++ b/packages/frontend/src/components/MkMediaImage.vue @@ -100,7 +100,7 @@ const url = computed(() => (props.raw || prefer.s.loadRawImages) : props.image.thumbnailUrl!, ); -async function reveal(ev: MouseEvent) { +async function reveal(ev: PointerEvent) { if (!props.controls) { return; } @@ -123,7 +123,7 @@ watch(() => props.image, (newImage) => { immediate: true, }); -function showMenu(ev: MouseEvent) { +function showMenu(ev: PointerEvent) { const menuItems: MenuItem[] = []; menuItems.push({ diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue index 63db3c3ab5..aac52bfa26 100644 --- a/packages/frontend/src/components/MkMediaVideo.vue +++ b/packages/frontend/src/components/MkMediaVideo.vue @@ -190,7 +190,7 @@ async function reveal() { // Menu const menuShowing = ref(false); -function showMenu(ev: MouseEvent) { +function showMenu(ev: PointerEvent) { const menu: MenuItem[] = [ // TODO: 再生キューに追加 { diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue index 6efaa655bc..22d5802596 100644 --- a/packages/frontend/src/components/MkMenu.vue +++ b/packages/frontend/src/components/MkMenu.vue @@ -316,7 +316,7 @@ function onItemMouseLeave() { if (childCloseTimer) window.clearTimeout(childCloseTimer); } -async function showRadioOptions(item: MenuRadio, ev: Event) { +async function showRadioOptions(item: MenuRadio, ev: MouseEvent | PointerEvent | KeyboardEvent) { const children: MenuItem[] = Object.keys(item.options).map<MenuRadioOption>(key => { const value = item.options[key]; return { @@ -341,7 +341,7 @@ async function showRadioOptions(item: MenuRadio, ev: Event) { } } -async function showChildren(item: MenuParent, ev: Event) { +async function showChildren(item: MenuParent, ev: MouseEvent | PointerEvent | KeyboardEvent) { ev.stopPropagation(); const children: MenuItem[] = await (async () => { @@ -371,7 +371,7 @@ async function showChildren(item: MenuParent, ev: Event) { } } -function clicked(fn: MenuAction, ev: MouseEvent, doClose = true) { +function clicked(fn: MenuAction, ev: PointerEvent, doClose = true) { fn(ev); if (!doClose) return; diff --git a/packages/frontend/src/components/MkModal.vue b/packages/frontend/src/components/MkModal.vue index 660d5a26be..92174d8ef7 100644 --- a/packages/frontend/src/components/MkModal.vue +++ b/packages/frontend/src/components/MkModal.vue @@ -184,8 +184,8 @@ const align = () => { const width = content.value!.offsetWidth; const height = content.value!.offsetHeight; - let left; - let top; + let left = 0; + let top = 0; const x = anchorRect.left + (fixed.value ? 0 : window.scrollX); const y = anchorRect.top + (fixed.value ? 0 : window.scrollY); diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 56def64d3d..c78cc44425 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -594,7 +594,7 @@ function toggleReact() { } } -function onContextmenu(ev: MouseEvent): void { +function onContextmenu(ev: PointerEvent): void { if (props.mock) { return; } diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index febf909f42..cfb65cd9b7 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> <MkNoteSub v-for="note in conversation" :key="note.id" :class="$style.replyToMore" :note="note"/> </div> - <MkNoteSub v-if="appearNote.replyId" :note="appearNote.reply" :class="$style.replyTo"/> + <MkNoteSub v-if="appearNote.replyId" :note="appearNote?.reply ?? null" :class="$style.replyTo"/> <div v-if="isRenote" :class="$style.renote"> <MkAvatar :class="$style.renoteAvatar" :user="note.user" link preview/> <i class="ti ti-repeat" style="margin-right: 4px;"></i> @@ -111,7 +111,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </div> <div v-if="appearNote.files && appearNote.files.length > 0"> - <MkMediaList ref="galleryEl" :mediaList="appearNote.files"/> + <MkMediaList :mediaList="appearNote.files"/> </div> <MkPoll v-if="appearNote.poll" @@ -144,7 +144,6 @@ SPDX-License-Identifier: AGPL-3.0-only :myReaction="$appearNote.myReaction" :noteId="appearNote.id" :maxNumber="16" - @mockUpdateMyReaction="emitUpdReaction" /> <button class="_button" :class="$style.noteFooterButton" @click="reply()"> <i class="ti ti-arrow-back-up"></i> @@ -233,7 +232,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { computed, inject, markRaw, onMounted, provide, ref, useTemplateRef } from 'vue'; +import { computed, inject, markRaw, provide, ref, useTemplateRef } from 'vue'; import * as mfm from 'mfm-js'; import * as Misskey from 'misskey-js'; import { isLink } from '@@/js/is-link.js'; @@ -324,7 +323,6 @@ const renoteButton = useTemplateRef('renoteButton'); const renoteTime = useTemplateRef('renoteTime'); const reactButton = useTemplateRef('reactButton'); const clipButton = useTemplateRef('clipButton'); -const galleryEl = useTemplateRef('galleryEl'); const isMyRenote = $i && ($i.id === note.userId); const showContent = ref(false); const isDeleted = ref(false); @@ -358,7 +356,7 @@ const keymap = { if (!prefer.s.showClipButtonInNoteFooter) return; clip(); }, - 'o': () => galleryEl.value?.openGallery(), + //'o': () => galleryEl.value?.openGallery(), 'v|enter': () => { if (appearNote.cw != null) { showContent.value = !showContent.value; @@ -553,7 +551,7 @@ function toggleReact() { } } -function onContextmenu(ev: MouseEvent): void { +function onContextmenu(ev: PointerEvent): void { if (ev.target && isLink(ev.target as HTMLElement)) return; if (window.getSelection()?.toString() !== '') return; diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue index 45a74e3f02..143ca7fd2f 100644 --- a/packages/frontend/src/components/MkNotification.vue +++ b/packages/frontend/src/components/MkNotification.vue @@ -121,7 +121,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ notification.invitation.room.name }} </div> <MkA v-else-if="notification.type === 'achievementEarned'" :class="$style.text" to="/my/achievements"> - {{ i18n.ts._achievements._types['_' + notification.achievement].title }} + {{ (i18n.ts._achievements._types as any)['_' + notification.achievement].title }} </MkA> <MkA v-else-if="notification.type === 'exportCompleted'" :class="$style.text" :to="`/my/drive/file/${notification.fileId}`"> {{ i18n.ts.showFile }} diff --git a/packages/frontend/src/components/MkObjectView.value.vue b/packages/frontend/src/components/MkObjectView.value.vue index 7fa8c23c6c..abc4407d2a 100644 --- a/packages/frontend/src/components/MkObjectView.value.vue +++ b/packages/frontend/src/components/MkObjectView.value.vue @@ -42,7 +42,7 @@ const props = defineProps<{ value: unknown; }>(); -const collapsed = reactive({}); +const collapsed = reactive<Record<string, boolean>>({}); if (isObject(props.value)) { for (const key in props.value) { diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue index a4c8ca0095..ad8fcf283c 100644 --- a/packages/frontend/src/components/MkPagination.vue +++ b/packages/frontend/src/components/MkPagination.vue @@ -88,7 +88,7 @@ const shouldEnableInfiniteScroll = computed(() => { return prefer.r.enableInfiniteScroll.value && !props.forceDisableInfiniteScroll; }); -function onContextmenu(ev: MouseEvent) { +function onContextmenu(ev: PointerEvent) { if (ev.target && isLink(ev.target as HTMLElement)) return; if (window.getSelection()?.toString() !== '') return; diff --git a/packages/frontend/src/components/MkPollEditor.vue b/packages/frontend/src/components/MkPollEditor.vue index b7c3d1f42d..bd36a0b97a 100644 --- a/packages/frontend/src/components/MkPollEditor.vue +++ b/packages/frontend/src/components/MkPollEditor.vue @@ -110,7 +110,7 @@ if (props.modelValue.expiresAt) { expiration.value = 'infinite'; } -function onInput(i, value) { +function onInput(i: number, value: string) { choices.value[i] = value; } @@ -122,7 +122,7 @@ function add() { // }); } -function remove(i) { +function remove(i: number) { choices.value = choices.value.filter((_, _i) => _i !== i); } diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index 9734b51927..c869eeb3fd 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -55,7 +55,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div :class="$style.visibleUsers"> <span v-for="u in visibleUsers" :key="u.id" :class="$style.visibleUser"> <MkAcct :user="u"/> - <button class="_button" style="padding: 4px 8px;" @click="removeVisibleUser(u)"><i class="ti ti-x"></i></button> + <button class="_button" style="padding: 4px 8px;" @click="removeVisibleUser(u.id)"><i class="ti ti-x"></i></button> </span> <button class="_buttonPrimary" style="padding: 4px; border-radius: 8px;" @click="addVisibleUser"><i class="ti ti-plus ti-fw"></i></button> </div> @@ -476,6 +476,7 @@ function togglePoll() { } function addTag(tag: string) { + if (textareaEl.value == null) return; insertTextAtCursor(textareaEl.value, ` #${tag} `); } @@ -486,7 +487,7 @@ function focus() { } } -function chooseFileFromPc(ev: MouseEvent) { +function chooseFileFromPc(ev: PointerEvent) { if (props.mock) return; os.chooseFileFromPc({ multiple: true }).then(files => { @@ -495,7 +496,7 @@ function chooseFileFromPc(ev: MouseEvent) { }); } -function chooseFileFromDrive(ev: MouseEvent) { +function chooseFileFromDrive(ev: PointerEvent) { if (props.mock) return; chooseDriveFile({ multiple: true }).then(driveFiles => { @@ -503,18 +504,18 @@ function chooseFileFromDrive(ev: MouseEvent) { }); } -function detachFile(id) { +function detachFile(id: Misskey.entities.DriveFile['id']) { files.value = files.value.filter(x => x.id !== id); } -function updateFileSensitive(file, sensitive) { +function updateFileSensitive(file: Misskey.entities.DriveFile, isSensitive: boolean) { if (props.mock) { - emit('fileChangeSensitive', file.id, sensitive); + emit('fileChangeSensitive', file.id, isSensitive); } - files.value[files.value.findIndex(x => x.id === file.id)].isSensitive = sensitive; + files.value[files.value.findIndex(x => x.id === file.id)].isSensitive = isSensitive; } -function updateFileName(file, name) { +function updateFileName(file: Misskey.entities.DriveFile, name: Misskey.entities.DriveFile['name']) { files.value[files.value.findIndex(x => x.id === file.id)].name = name; } @@ -704,8 +705,8 @@ function addVisibleUser() { }); } -function removeVisibleUser(user) { - visibleUsers.value = erase(user, visibleUsers.value); +function removeVisibleUser(id: string) { + visibleUsers.value = visibleUsers.value.filter(u => u.id !== id); } function clear() { @@ -742,7 +743,8 @@ const pastedFileName = 'yyyy-MM-dd HH-mm-ss [{{number}}]'; async function onPaste(ev: ClipboardEvent) { if (props.mock) return; - if (!ev.clipboardData) return; + if (ev.clipboardData == null) return; + if (textareaEl.value == null) return; let pastedFiles: File[] = []; for (const { item, i } of Array.from(ev.clipboardData.items, (data, x) => ({ item: data, i: x }))) { @@ -767,39 +769,42 @@ async function onPaste(ev: ClipboardEvent) { if (!renoteTargetNote.value && !quoteId.value && paste.startsWith(url + '/notes/')) { ev.preventDefault(); - os.confirm({ + const { canceled } = await os.confirm({ type: 'info', text: i18n.ts.quoteQuestion, - }).then(({ canceled }) => { - if (canceled) { - insertTextAtCursor(textareaEl.value, paste); - return; - } - - quoteId.value = paste.substring(url.length).match(/^\/notes\/(.+?)\/?$/)?.[1] ?? null; }); + + if (canceled) { + insertTextAtCursor(textareaEl.value, paste); + return; + } + + quoteId.value = paste.substring(url.length).match(/^\/notes\/(.+?)\/?$/)?.[1] ?? null; } if (paste.length > 1000) { ev.preventDefault(); - os.confirm({ + + const { canceled } = await os.confirm({ type: 'info', text: i18n.ts.attachAsFileQuestion, - }).then(({ canceled }) => { - if (canceled) { - insertTextAtCursor(textareaEl.value, paste); - return; - } - - const fileName = formatTimeString(new Date(), pastedFileName).replace(/{{number}}/g, '0'); - const file = new File([paste], `${fileName}.txt`, { type: 'text/plain' }); - uploader.addFiles([file]); }); + + if (canceled) { + insertTextAtCursor(textareaEl.value, paste); + return; + } + + const fileName = formatTimeString(new Date(), pastedFileName).replace(/{{number}}/g, '0'); + const file = new File([paste], `${fileName}.txt`, { type: 'text/plain' }); + uploader.addFiles([file]); } } -function onDragover(ev) { - if (!ev.dataTransfer.items[0]) return; +function onDragover(ev: DragEvent) { + if (ev.dataTransfer == null) return; + if (ev.dataTransfer.items[0] == null) return; + const isFile = ev.dataTransfer.items[0].kind === 'file'; if (isFile || checkDragDataType(ev, ['driveFiles'])) { ev.preventDefault(); @@ -852,13 +857,32 @@ function onDrop(ev: DragEvent): void { //#endregion } +type StoredDrafts = { + [key: string]: { + updatedAt: string; + data: { + text: string; + useCw: boolean; + cw: string | null; + visibility: 'public' | 'home' | 'followers' | 'specified'; + localOnly: boolean; + files: Misskey.entities.DriveFile[]; + poll: PollEditorModelValue | null; + visibleUserIds?: string[]; + quoteId: string | null; + reactionAcceptance: 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null; + scheduledAt: number | null; + }; + }; +}; + function saveDraft() { if (props.instant || props.mock) return; - const draftData = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}'); + const draftsData = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}') as StoredDrafts; - draftData[draftKey.value] = { - updatedAt: new Date(), + draftsData[draftKey.value] = { + updatedAt: new Date().toISOString(), data: { text: text.value, useCw: useCw.value, @@ -874,15 +898,15 @@ function saveDraft() { }, }; - miLocalStorage.setItem('drafts', JSON.stringify(draftData)); + miLocalStorage.setItem('drafts', JSON.stringify(draftsData)); } function deleteDraft() { - const draftData = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}'); + const draftsData = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}') as StoredDrafts; - delete draftData[draftKey.value]; + delete draftsData[draftKey.value]; - miLocalStorage.setItem('drafts', JSON.stringify(draftData)); + miLocalStorage.setItem('drafts', JSON.stringify(draftsData)); } async function saveServerDraft(options: { @@ -924,8 +948,8 @@ async function uploadFiles() { } } -async function post(ev?: MouseEvent) { - if (ev) { +async function post(ev?: PointerEvent) { + if (ev != null) { const el = (ev.currentTarget ?? ev.target) as HTMLElement | null; if (el && prefer.s.animation) { @@ -1138,11 +1162,12 @@ function cancel() { function insertMention() { os.selectUser({ localOnly: localOnly.value, includeSelf: true }).then(user => { + if (textareaEl.value == null) return; insertTextAtCursor(textareaEl.value, '@' + Misskey.acct.toString(user) + ' '); }); } -async function insertEmoji(ev: MouseEvent) { +async function insertEmoji(ev: PointerEvent) { textAreaReadOnly.value = true; const target = ev.currentTarget ?? ev.target; if (target == null) return; @@ -1176,7 +1201,7 @@ async function insertEmoji(ev: MouseEvent) { ); } -async function insertMfmFunction(ev: MouseEvent) { +async function insertMfmFunction(ev: PointerEvent) { if (textareaEl.value == null) return; let pos = textareaEl.value.selectionStart ?? 0; let posEnd = textareaEl.value.selectionEnd ?? text.value.length; @@ -1204,7 +1229,7 @@ async function insertMfmFunction(ev: MouseEvent) { ); } -function showActions(ev: MouseEvent) { +function showActions(ev: PointerEvent) { os.popupMenu(postFormActions.map(action => ({ text: action.title, action: () => { @@ -1222,7 +1247,7 @@ function showActions(ev: MouseEvent) { const postAccount = ref<Misskey.entities.UserDetailed | null>(null); -async function openAccountMenu(ev: MouseEvent) { +async function openAccountMenu(ev: PointerEvent) { if (props.mock) return; function showDraftsDialog(scheduled: boolean) { @@ -1312,12 +1337,12 @@ async function openAccountMenu(ev: MouseEvent) { }, { type: 'divider' }, ...items], (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined); } -function showPerUploadItemMenu(item: UploaderItem, ev: MouseEvent) { +function showPerUploadItemMenu(item: UploaderItem, ev: PointerEvent) { const menu = uploader.getMenu(item); os.popupMenu(menu, ev.currentTarget ?? ev.target); } -function showPerUploadItemMenuViaContextmenu(item: UploaderItem, ev: MouseEvent) { +function showPerUploadItemMenuViaContextmenu(item: UploaderItem, ev: PointerEvent) { const menu = uploader.getMenu(item); os.contextMenu(menu, ev); } @@ -1392,8 +1417,8 @@ onMounted(() => { nextTick(() => { // 書きかけの投稿を復元 if (!props.instant && !props.mention && !props.specified && !props.mock) { - const draft = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}')[draftKey.value]; - if (draft) { + const draft = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}')[draftKey.value] as StoredDrafts[string] | undefined; + if (draft != null) { text.value = draft.data.text; useCw.value = draft.data.useCw; cw.value = draft.data.cw; diff --git a/packages/frontend/src/components/MkPostFormAttaches.vue b/packages/frontend/src/components/MkPostFormAttaches.vue index d198c98404..2d6e7ec6b3 100644 --- a/packages/frontend/src/components/MkPostFormAttaches.vue +++ b/packages/frontend/src/components/MkPostFormAttaches.vue @@ -97,7 +97,7 @@ async function detachAndDeleteMedia(file: Misskey.entities.DriveFile) { globalEvents.emit('driveFilesDeleted', [file]); } -function toggleSensitive(file) { +function toggleSensitive(file: Misskey.entities.DriveFile) { if (mock) { emit('changeSensitive', file, !file.isSensitive); return; @@ -111,7 +111,7 @@ function toggleSensitive(file) { }); } -async function rename(file) { +async function rename(file: Misskey.entities.DriveFile) { if (mock) return; const { canceled, result } = await os.inputText({ @@ -149,7 +149,7 @@ async function describe(file: Misskey.entities.DriveFile) { }); } -function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent | KeyboardEvent): void { +function showFileMenu(file: Misskey.entities.DriveFile, ev: PointerEvent | KeyboardEvent): void { if (menuShowing) return; const isImage = file.type.startsWith('image/'); diff --git a/packages/frontend/src/components/MkPreferenceContainer.vue b/packages/frontend/src/components/MkPreferenceContainer.vue index 70b111513c..1ce608dda9 100644 --- a/packages/frontend/src/components/MkPreferenceContainer.vue +++ b/packages/frontend/src/components/MkPreferenceContainer.vue @@ -32,7 +32,7 @@ const props = withDefaults(defineProps<{ const isAccountOverrided = ref(prefer.isAccountOverrided(props.k)); const isSyncEnabled = ref(prefer.isSyncEnabled(props.k)); -function showMenu(ev: MouseEvent, contextmenu?: boolean) { +function showMenu(ev: PointerEvent, contextmenu?: boolean) { const i = window.setInterval(() => { isAccountOverrided.value = prefer.isAccountOverrided(props.k); isSyncEnabled.value = prefer.isSyncEnabled(props.k); diff --git a/packages/frontend/src/components/MkPreview.vue b/packages/frontend/src/components/MkPreview.vue index 6c7bf6be6b..c25f9ab0a9 100644 --- a/packages/frontend/src/components/MkPreview.vue +++ b/packages/frontend/src/components/MkPreview.vue @@ -89,7 +89,7 @@ const selectUser = async () => { await os.selectUser(); }; -const openMenu = async (ev: Event) => { +const openMenu = async (ev: PointerEvent) => { os.popupMenu([{ type: 'label', text: 'Fruits', diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue index 7c60288883..a89f947fa7 100644 --- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue +++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue @@ -153,7 +153,7 @@ async function toggleReaction() { } } -async function menu(ev) { +async function menu(ev: PointerEvent) { let menuItems: MenuItem[] = []; if (canGetInfo.value) { diff --git a/packages/frontend/src/components/MkReactionsViewer.vue b/packages/frontend/src/components/MkReactionsViewer.vue index bd9ef50157..ab7f86842c 100644 --- a/packages/frontend/src/components/MkReactionsViewer.vue +++ b/packages/frontend/src/components/MkReactionsViewer.vue @@ -32,11 +32,11 @@ SPDX-License-Identifier: AGPL-3.0-only import * as Misskey from 'misskey-js'; import { inject, watch, ref } from 'vue'; import { TransitionGroup } from 'vue'; +import { isSupportedEmoji } from '@@/js/emojilist.js'; import XReaction from '@/components/MkReactionsViewer.reaction.vue'; import { $i } from '@/i.js'; import { prefer } from '@/preferences.js'; import { customEmojisMap } from '@/custom-emojis.js'; -import { isSupportedEmoji } from '@@/js/emojilist.js'; import { DI } from '@/di.js'; const props = withDefaults(defineProps<{ @@ -60,8 +60,8 @@ const initialReactions = new Set(Object.keys(props.reactions)); const _reactions = ref<[string, number][]>([]); const hasMoreReactions = ref(false); -if (props.myReaction && !Object.keys(_reactions.value).includes(props.myReaction)) { - _reactions.value[props.myReaction] = props.reactions[props.myReaction]; +if (props.myReaction != null && !(props.myReaction in props.reactions)) { + _reactions.value.push([props.myReaction, props.reactions[props.myReaction]]); } function onMockToggleReaction(emoji: string, count: number) { diff --git a/packages/frontend/src/components/MkRetentionHeatmap.vue b/packages/frontend/src/components/MkRetentionHeatmap.vue index a204bc3bf1..6ab7a01ce7 100644 --- a/packages/frontend/src/components/MkRetentionHeatmap.vue +++ b/packages/frontend/src/components/MkRetentionHeatmap.vue @@ -98,7 +98,7 @@ async function renderChart() { data: data as any, borderWidth: 0, borderRadius: 3, - backgroundColor(c) { + backgroundColor(c: any) { const v = c.dataset.data[c.dataIndex] as unknown as typeof data[0]; const value = v.v; const m = max(v.y); @@ -179,7 +179,7 @@ async function renderChart() { enabled: false, callbacks: { title(context) { - const v = context[0].dataset.data[context[0].dataIndex]; + const v = context[0].dataset.data[context[0].dataIndex] as unknown as typeof data[0]; return getYYYYMMDD(new Date(new Date(v.y).getTime() + (v.x * 86400000))); }, label(context) { diff --git a/packages/frontend/src/components/MkRoleSelectDialog.vue b/packages/frontend/src/components/MkRoleSelectDialog.vue index 937804703d..651165136a 100644 --- a/packages/frontend/src/components/MkRoleSelectDialog.vue +++ b/packages/frontend/src/components/MkRoleSelectDialog.vue @@ -55,9 +55,9 @@ import MkModalWindow from '@/components/MkModalWindow.vue'; import MkLoading from '@/components/global/MkLoading.vue'; const emit = defineEmits<{ - (ev: 'done', value: Misskey.entities.Role[]), - (ev: 'close'), - (ev: 'closed'), + (ev: 'done', value: Misskey.entities.Role[]): void; + (ev: 'close'): void; + (ev: 'closed'): void; }>(); const props = withDefaults(defineProps<{ diff --git a/packages/frontend/src/components/MkServerSetupWizardDialog.vue b/packages/frontend/src/components/MkServerSetupWizardDialog.vue index ea2c5dd47f..1d03438f83 100644 --- a/packages/frontend/src/components/MkServerSetupWizardDialog.vue +++ b/packages/frontend/src/components/MkServerSetupWizardDialog.vue @@ -33,7 +33,7 @@ import MkModalWindow from '@/components/MkModalWindow.vue'; import MkServerSetupWizard from '@/components/MkServerSetupWizard.vue'; const emit = defineEmits<{ - (ev: 'closed'), + (ev: 'closed'): void; }>(); const windowEl = useTemplateRef('windowEl'); diff --git a/packages/frontend/src/components/MkSignin.input.vue b/packages/frontend/src/components/MkSignin.input.vue index 4c73eab3f5..89ec6373cf 100644 --- a/packages/frontend/src/components/MkSignin.input.vue +++ b/packages/frontend/src/components/MkSignin.input.vue @@ -78,7 +78,7 @@ const props = withDefaults(defineProps<{ const emit = defineEmits<{ (ev: 'usernameSubmitted', v: string): void; - (ev: 'passkeyClick', v: MouseEvent): void; + (ev: 'passkeyClick', v: PointerEvent): void; }>(); const host = toUnicode(configHost); diff --git a/packages/frontend/src/components/MkSortOrderEditor.vue b/packages/frontend/src/components/MkSortOrderEditor.vue index 27ffc724ae..3ac809cdbf 100644 --- a/packages/frontend/src/components/MkSortOrderEditor.vue +++ b/packages/frontend/src/components/MkSortOrderEditor.vue @@ -25,11 +25,11 @@ SPDX-License-Identifier: AGPL-3.0-only <script setup lang="ts" generic="T extends string"> import { toRefs } from 'vue'; +import type { MenuItem } from '@/types/menu.js'; +import type { SortOrder } from '@/components/MkSortOrderEditor.define.js'; import MkTagItem from '@/components/MkTagItem.vue'; import MkButton from '@/components/MkButton.vue'; -import type { MenuItem } from '@/types/menu.js'; import * as os from '@/os.js'; -import type { SortOrder } from '@/components/MkSortOrderEditor.define.js'; const emit = defineEmits<{ (ev: 'update', sortOrders: SortOrder<T>[]): void; @@ -55,7 +55,7 @@ function onToggleSortOrderButtonClicked(order: SortOrder<T>) { emitOrder(currentOrders.value); } -function onAddSortOrderButtonClicked(ev: MouseEvent) { +function onAddSortOrderButtonClicked(ev: PointerEvent) { const menuItems: MenuItem[] = props.baseOrderKeyNames .filter(baseKey => !currentOrders.value.map(it => it.key).includes(baseKey)) .map(it => { diff --git a/packages/frontend/src/components/MkSpot.vue b/packages/frontend/src/components/MkSpot.vue index 4a8ebb5f94..4bd11fe938 100644 --- a/packages/frontend/src/components/MkSpot.vue +++ b/packages/frontend/src/components/MkSpot.vue @@ -88,7 +88,7 @@ function setPosition() { bodyEl.value.style.top = data.top + 'px'; } -let loopHandler; +let loopHandler: number | null = null; onMounted(() => { nextTick(() => { @@ -104,7 +104,7 @@ onMounted(() => { }); onUnmounted(() => { - window.cancelAnimationFrame(loopHandler); + if (loopHandler != null) window.cancelAnimationFrame(loopHandler); }); </script> diff --git a/packages/frontend/src/components/MkStreamingNotesTimeline.vue b/packages/frontend/src/components/MkStreamingNotesTimeline.vue index bc6ebf0918..9784d8e017 100644 --- a/packages/frontend/src/components/MkStreamingNotesTimeline.vue +++ b/packages/frontend/src/components/MkStreamingNotesTimeline.vue @@ -350,13 +350,12 @@ function connectChannel() { connections.main = stream.useChannel('main'); connections.main.on('mention', prepend); } else if (props.src === 'directs') { - const onNote = note => { + connections.main = stream.useChannel('main'); + connections.main.on('mention', note => { if (note.visibility === 'specified') { prepend(note); } - }; - connections.main = stream.useChannel('main'); - connections.main.on('mention', onNote); + }); } else if (props.src === 'list') { if (props.list == null) return; connections.userList = stream.useChannel('userList', { diff --git a/packages/frontend/src/components/MkStreamingNotificationsTimeline.vue b/packages/frontend/src/components/MkStreamingNotificationsTimeline.vue index 6ee2e347a5..91f071fe63 100644 --- a/packages/frontend/src/components/MkStreamingNotificationsTimeline.vue +++ b/packages/frontend/src/components/MkStreamingNotificationsTimeline.vue @@ -137,8 +137,8 @@ watch(visibility, () => { } }); -function onNotification(notification) { - const isMuted = props.excludeTypes ? props.excludeTypes.includes(notification.type) : false; +function onNotification(notification: Misskey.entities.Notification) { + const isMuted = props.excludeTypes ? props.excludeTypes.includes(notification.type as typeof notificationTypes[number]) : false; if (isMuted || window.document.visibilityState === 'visible') { if (store.s.realtimeMode) { useStream().send('readNotification'); diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue index 236afa127c..585a628a96 100644 --- a/packages/frontend/src/components/MkSuperMenu.vue +++ b/packages/frontend/src/components/MkSuperMenu.vue @@ -82,7 +82,7 @@ export type SuperMenuDef = { text: string; danger?: boolean; active?: boolean; - action: (ev: MouseEvent) => Awaitable<void>; + action: (ev: PointerEvent) => Awaitable<void>; } | { type?: 'link'; to: string; diff --git a/packages/frontend/src/components/MkSystemWebhookEditor.vue b/packages/frontend/src/components/MkSystemWebhookEditor.vue index cd72204fce..1536b14455 100644 --- a/packages/frontend/src/components/MkSystemWebhookEditor.vue +++ b/packages/frontend/src/components/MkSystemWebhookEditor.vue @@ -245,7 +245,7 @@ onMounted(async () => { secret.value = res.secret; isActive.value = res.isActive; for (const ev of Object.keys(events.value)) { - events.value[ev] = res.on.includes(ev as SystemWebhookEventType); + events.value[ev as SystemWebhookEventType] = res.on.includes(ev as SystemWebhookEventType); } // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (ex: any) { diff --git a/packages/frontend/src/components/MkTabs.vue b/packages/frontend/src/components/MkTabs.vue index 9798e2c3b3..a6342ec2e1 100644 --- a/packages/frontend/src/components/MkTabs.vue +++ b/packages/frontend/src/components/MkTabs.vue @@ -50,7 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts"> export type Tab<K = string> = { key: K; - onClick?: (ev: MouseEvent) => void; + onClick?: (ev: PointerEvent) => void; iconOnly?: boolean; title: string; icon?: string; @@ -74,7 +74,7 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (ev: 'tabClick', key: string); + (ev: 'tabClick', key: string): void; }>(); const tab = defineModel<T['key']>('tab'); @@ -100,7 +100,7 @@ function onTabMousedown(selectedTab: Tab, ev: MouseEvent): void { } } -function onTabClick(t: Tab, ev: MouseEvent): void { +function onTabClick(t: Tab, ev: PointerEvent): void { emit('tabClick', t.key); if (t.onClick) { diff --git a/packages/frontend/src/components/MkTagItem.vue b/packages/frontend/src/components/MkTagItem.vue index be735e6407..5cd2113e59 100644 --- a/packages/frontend/src/components/MkTagItem.vue +++ b/packages/frontend/src/components/MkTagItem.vue @@ -17,8 +17,8 @@ SPDX-License-Identifier: AGPL-3.0-only import MkButton from '@/components/MkButton.vue'; const emit = defineEmits<{ - (ev: 'click', payload: MouseEvent): void; - (ev: 'exButtonClick', payload: MouseEvent): void; + (ev: 'click', payload: PointerEvent): void; + (ev: 'exButtonClick', payload: PointerEvent): void; }>(); defineProps<{ diff --git a/packages/frontend/src/components/MkTextarea.vue b/packages/frontend/src/components/MkTextarea.vue index 407ac33add..d53d4ec018 100644 --- a/packages/frontend/src/components/MkTextarea.vue +++ b/packages/frontend/src/components/MkTextarea.vue @@ -63,7 +63,7 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (ev: 'change', _ev: KeyboardEvent): void; + (ev: 'change', _ev: InputEvent): void; (ev: 'keydown', _ev: KeyboardEvent): void; (ev: 'enter'): void; (ev: 'update:modelValue', value: string): void; @@ -79,12 +79,16 @@ const inputEl = useTemplateRef('inputEl'); const preview = ref(false); let autocompleteWorker: Autocomplete | null = null; -const focus = () => inputEl.value?.focus(); -const onInput = (ev) => { +function focus() { + inputEl.value?.focus(); +} + +function onInput(ev: InputEvent) { changed.value = true; emit('change', ev); -}; -const onKeydown = (ev: KeyboardEvent) => { +} + +function onKeydown(ev: KeyboardEvent) { if (ev.isComposing || ev.key === 'Process' || ev.keyCode === 229) return; emit('keydown', ev); @@ -102,12 +106,12 @@ const onKeydown = (ev: KeyboardEvent) => { }); ev.preventDefault(); } -}; +} -const updated = () => { +function updated() { changed.value = false; emit('update:modelValue', v.value ?? ''); -}; +} const debouncedUpdated = debounce(1000, updated); diff --git a/packages/frontend/src/components/MkTokenGenerateWindow.vue b/packages/frontend/src/components/MkTokenGenerateWindow.vue index 42cb6f1e82..8d51e1fa87 100644 --- a/packages/frontend/src/components/MkTokenGenerateWindow.vue +++ b/packages/frontend/src/components/MkTokenGenerateWindow.vue @@ -33,12 +33,12 @@ SPDX-License-Identifier: AGPL-3.0-only <MkButton inline @click="enableAll">{{ i18n.ts.enableAll }}</MkButton> </div> <div class="_gaps_s"> - <MkSwitch v-for="kind in Object.keys(permissionSwitches)" :key="kind" v-model="permissionSwitches[kind]">{{ i18n.ts._permissions[kind] }}</MkSwitch> + <MkSwitch v-for="kind in Object.keys(permissionSwitches)" :key="kind" v-model="permissionSwitches[kind as keyof typeof permissionSwitches]">{{ i18n.ts._permissions[kind as keyof typeof permissionSwitches] }}</MkSwitch> </div> <div v-if="iAmAdmin" :class="$style.adminPermissions"> <div :class="$style.adminPermissionsHeader"><b>{{ i18n.ts.adminPermission }}</b></div> <div class="_gaps_s"> - <MkSwitch v-for="kind in Object.keys(permissionSwitchesForAdmin)" :key="kind" v-model="permissionSwitchesForAdmin[kind]">{{ i18n.ts._permissions[kind] }}</MkSwitch> + <MkSwitch v-for="kind in Object.keys(permissionSwitchesForAdmin)" :key="kind" v-model="permissionSwitchesForAdmin[kind as keyof typeof permissionSwitchesForAdmin]">{{ i18n.ts._permissions[kind as keyof typeof permissionSwitchesForAdmin] }}</MkSwitch> </div> </div> </div> @@ -102,8 +102,8 @@ function ok(): void { emit('done', { name: name.value, permissions: [ - ...Object.keys(permissionSwitches.value).filter(p => permissionSwitches.value[p]), - ...(iAmAdmin ? Object.keys(permissionSwitchesForAdmin.value).filter(p => permissionSwitchesForAdmin.value[p]) : []), + ...Object.keys(permissionSwitches.value).filter(p => permissionSwitches.value[p as (typeof Misskey.permissions)[number]]), + ...(iAmAdmin ? Object.keys(permissionSwitchesForAdmin.value).filter(p => permissionSwitchesForAdmin.value[p as (typeof Misskey.permissions)[number]]) : []), ], }); dialog.value?.close(); @@ -111,22 +111,22 @@ function ok(): void { function disableAll(): void { for (const p in permissionSwitches.value) { - permissionSwitches.value[p] = false; + permissionSwitches.value[p as (typeof Misskey.permissions)[number]] = false; } if (iAmAdmin) { for (const p in permissionSwitchesForAdmin.value) { - permissionSwitchesForAdmin.value[p] = false; + permissionSwitchesForAdmin.value[p as (typeof Misskey.permissions)[number]] = false; } } } function enableAll(): void { for (const p in permissionSwitches.value) { - permissionSwitches.value[p] = true; + permissionSwitches.value[p as (typeof Misskey.permissions)[number]] = true; } if (iAmAdmin) { for (const p in permissionSwitchesForAdmin.value) { - permissionSwitchesForAdmin.value[p] = true; + permissionSwitchesForAdmin.value[p as (typeof Misskey.permissions)[number]] = true; } } } diff --git a/packages/frontend/src/components/MkTooltip.vue b/packages/frontend/src/components/MkTooltip.vue index aa041c88e5..08a3f02f65 100644 --- a/packages/frontend/src/components/MkTooltip.vue +++ b/packages/frontend/src/components/MkTooltip.vue @@ -71,7 +71,7 @@ function setPosition() { el.value.style.top = data.top + 'px'; } -let loopHandler; +let loopHandler: number | null = null; onMounted(() => { nextTick(() => { @@ -87,7 +87,7 @@ onMounted(() => { }); onUnmounted(() => { - window.cancelAnimationFrame(loopHandler); + if (loopHandler != null) window.cancelAnimationFrame(loopHandler); }); </script> diff --git a/packages/frontend/src/components/MkTutorialDialog.Note.vue b/packages/frontend/src/components/MkTutorialDialog.Note.vue index b77e67e9c6..3ab2c5f0d4 100644 --- a/packages/frontend/src/components/MkTutorialDialog.Note.vue +++ b/packages/frontend/src/components/MkTutorialDialog.Note.vue @@ -74,7 +74,7 @@ const exampleNote = reactive<Misskey.entities.Note>({ }); const onceReacted = ref<boolean>(false); -function addReaction(emoji) { +function addReaction(emoji: string) { onceReacted.value = true; emit('reacted'); doNotification(emoji); @@ -96,7 +96,7 @@ function doNotification(emoji: string): void { globalEvents.emit('clientNotification', notification); } -function removeReaction(emoji) { +function removeReaction(emoji: string) { delete exampleNote.reactions[emoji]; exampleNote.myReaction = undefined; } diff --git a/packages/frontend/src/components/MkUploaderDialog.vue b/packages/frontend/src/components/MkUploaderDialog.vue index 8849fa447d..69de56d45c 100644 --- a/packages/frontend/src/components/MkUploaderDialog.vue +++ b/packages/frontend/src/components/MkUploaderDialog.vue @@ -166,17 +166,17 @@ async function done() { dialog.value?.close(); } -async function chooseFile(ev: MouseEvent) { +async function chooseFile(ev: PointerEvent) { const newFiles = await os.chooseFileFromPc({ multiple: true }); uploader.addFiles(newFiles); } -function showPerItemMenu(item: UploaderItem, ev: MouseEvent) { +function showPerItemMenu(item: UploaderItem, ev: PointerEvent) { const menu = uploader.getMenu(item); os.popupMenu(menu, ev.currentTarget ?? ev.target); } -function showPerItemMenuViaContextmenu(item: UploaderItem, ev: MouseEvent) { +function showPerItemMenuViaContextmenu(item: UploaderItem, ev: PointerEvent) { const menu = uploader.getMenu(item); os.contextMenu(menu, ev); } diff --git a/packages/frontend/src/components/MkUploaderItems.vue b/packages/frontend/src/components/MkUploaderItems.vue index f31c717ad5..51f7ac2d09 100644 --- a/packages/frontend/src/components/MkUploaderItems.vue +++ b/packages/frontend/src/components/MkUploaderItems.vue @@ -57,18 +57,18 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (ev: 'showMenu', item: UploaderItem, event: MouseEvent): void; - (ev: 'showMenuViaContextmenu', item: UploaderItem, event: MouseEvent): void; + (ev: 'showMenu', item: UploaderItem, event: PointerEvent): void; + (ev: 'showMenuViaContextmenu', item: UploaderItem, event: PointerEvent): void; }>(); -function onContextmenu(item: UploaderItem, ev: MouseEvent) { +function onContextmenu(item: UploaderItem, ev: PointerEvent) { if (ev.target && isLink(ev.target as HTMLElement)) return; if (window.getSelection()?.toString() !== '') return; emit('showMenuViaContextmenu', item, ev); } -function onThumbnailClick(item: UploaderItem, ev: MouseEvent) { +function onThumbnailClick(item: UploaderItem, ev: PointerEvent) { // TODO: preview when item is image } </script> diff --git a/packages/frontend/src/components/MkUserList.vue b/packages/frontend/src/components/MkUserList.vue index f47d9b56dc..8ce929fff3 100644 --- a/packages/frontend/src/components/MkUserList.vue +++ b/packages/frontend/src/components/MkUserList.vue @@ -27,7 +27,7 @@ const props = withDefaults(defineProps<{ noGap?: boolean; extractor?: ExtractorFunction<P, Misskey.entities.UserDetailed>; }>(), { - extractor: (item) => item, + extractor: (item: any) => item as Misskey.entities.UserDetailed, }); </script> diff --git a/packages/frontend/src/components/MkUserPopup.vue b/packages/frontend/src/components/MkUserPopup.vue index f794899281..9f196ac2c1 100644 --- a/packages/frontend/src/components/MkUserPopup.vue +++ b/packages/frontend/src/components/MkUserPopup.vue @@ -90,7 +90,7 @@ const top = ref(0); const left = ref(0); const error = ref(false); -function showMenu(ev: MouseEvent) { +function showMenu(ev: PointerEvent) { if (user.value == null) return; const { menu, cleanup } = getUserMenu(user.value); os.popupMenu(menu, ev.currentTarget ?? ev.target).finally(cleanup); diff --git a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue index 4e96eff82e..95449dd0eb 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue +++ b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue @@ -66,7 +66,7 @@ watch(description, () => { }); }); -async function setAvatar(ev) { +async function setAvatar(ev: PointerEvent) { const files = await os.chooseFileFromPc({ multiple: false }); const file = files[0]; diff --git a/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue index 6aaee76565..6513ca385d 100644 --- a/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue +++ b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue @@ -46,7 +46,7 @@ async function renderChart() { return new Date(y, m, d - ago); }; - const format = (arr) => { + const format = (arr: number[]) => { return arr.map((v, i) => ({ x: getDate(i).getTime(), y: v, diff --git a/packages/frontend/src/components/MkVisitorDashboard.vue b/packages/frontend/src/components/MkVisitorDashboard.vue index 8bef225de5..2ce1912b86 100644 --- a/packages/frontend/src/components/MkVisitorDashboard.vue +++ b/packages/frontend/src/components/MkVisitorDashboard.vue @@ -94,7 +94,7 @@ function signup() { }); } -function showMenu(ev: MouseEvent) { +function showMenu(ev: PointerEvent) { openInstanceMenu(ev); } </script> diff --git a/packages/frontend/src/components/MkWaitingDialog.vue b/packages/frontend/src/components/MkWaitingDialog.vue index 820cf05e1f..18f2b3e189 100644 --- a/packages/frontend/src/components/MkWaitingDialog.vue +++ b/packages/frontend/src/components/MkWaitingDialog.vue @@ -26,8 +26,8 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (ev: 'done'); - (ev: 'closed'); + (ev: 'done'): void; + (ev: 'closed'): void; }>(); function done() { diff --git a/packages/frontend/src/components/MkWatermarkEditorDialog.Layer.vue b/packages/frontend/src/components/MkWatermarkEditorDialog.Layer.vue index 154b3ffc27..8e5bb6221d 100644 --- a/packages/frontend/src/components/MkWatermarkEditorDialog.Layer.vue +++ b/packages/frontend/src/components/MkWatermarkEditorDialog.Layer.vue @@ -387,7 +387,7 @@ onMounted(async () => { } }); -function chooseFile(ev: MouseEvent) { +function chooseFile(ev: PointerEvent) { selectFile({ anchorElement: ev.currentTarget ?? ev.target, multiple: false, diff --git a/packages/frontend/src/components/MkWatermarkEditorDialog.vue b/packages/frontend/src/components/MkWatermarkEditorDialog.vue index 7fe497e455..cadf9ba522 100644 --- a/packages/frontend/src/components/MkWatermarkEditorDialog.vue +++ b/packages/frontend/src/components/MkWatermarkEditorDialog.vue @@ -350,7 +350,7 @@ async function save() { } } -function addLayer(ev: MouseEvent) { +function addLayer(ev: PointerEvent) { os.popupMenu([{ text: i18n.ts._watermarkEditor.text, action: () => { diff --git a/packages/frontend/src/components/MkWidgetSettingsDialog.vue b/packages/frontend/src/components/MkWidgetSettingsDialog.vue index 951ac88465..63f294770c 100644 --- a/packages/frontend/src/components/MkWidgetSettingsDialog.vue +++ b/packages/frontend/src/components/MkWidgetSettingsDialog.vue @@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only @ok="save()" @closed="emit('closed')" > - <template #header><i class="ti ti-icons"></i> {{ i18n.ts._widgets[widgetName] ?? widgetName }}</template> + <template #header><i class="ti ti-icons"></i> {{ (i18n.ts._widgets as any)[widgetName] ?? widgetName }}</template> <MkPreviewWithControls> <template #preview> diff --git a/packages/frontend/src/components/MkWidgets.vue b/packages/frontend/src/components/MkWidgets.vue index e7712e8aae..a27613c24c 100644 --- a/packages/frontend/src/components/MkWidgets.vue +++ b/packages/frontend/src/components/MkWidgets.vue @@ -24,12 +24,12 @@ SPDX-License-Identifier: AGPL-3.0-only <div :class="[$style.widget, $style.customizeContainer]" data-cy-customize-container> <button :class="$style.customizeContainerConfig" class="_button" @click.prevent.stop="configWidget(item.id)"><i class="ti ti-settings"></i></button> <button :class="$style.customizeContainerRemove" data-cy-customize-container-remove class="_button" @click.prevent.stop="removeWidget(item)"><i class="ti ti-x"></i></button> - <component :is="`widget-${item.name}`" :ref="el => widgetRefs[item.id] = el" :class="$style.customizeContainerHandleWidget" :widget="item" @updateProps="updateWidget(item.id, $event)"/> + <component :is="`widget-${item.name}`" :ref="(el: any) => widgetRefs[item.id] = el" :class="$style.customizeContainerHandleWidget" :widget="item" @updateProps="updateWidget(item.id, $event)"/> </div> </template> </MkDraggable> </template> - <component :is="`widget-${widget.name}`" v-for="widget in _widgets" v-else :key="widget.id" :ref="el => widgetRefs[widget.id] = el" :class="$style.widget" :widget="widget" @updateProps="updateWidget(widget.id, $event)" @contextmenu.stop="onContextmenu(widget, $event)"/> + <component :is="`widget-${widget.name}`" v-for="widget in _widgets" v-else :key="widget.id" :ref="(el: any) => widgetRefs[widget.id] = el" :class="$style.widget" :widget="widget" @updateProps="updateWidget(widget.id, $event)" @contextmenu.stop="onContextmenu(widget, $event)"/> </div> </template> @@ -47,6 +47,7 @@ export type DefaultStoredWidget = { <script lang="ts" setup> import { computed } from 'vue'; import { isLink } from '@@/js/is-link.js'; +import type { Component } from 'vue'; import { genId } from '@/utility/id.js'; import MkSelect from '@/components/MkSelect.vue'; import MkButton from '@/components/MkButton.vue'; @@ -64,13 +65,13 @@ const props = defineProps<{ const _widgetDefs = computed(() => { if (instance.federation === 'none') { - return widgetDefs.filter(x => !federationWidgets.includes(x)); + return widgetDefs.filter(x => !federationWidgets.includes(x as any)); } else { return widgetDefs; } }); -const _widgets = computed(() => props.widgets.filter(x => _widgetDefs.value.includes(x.name))); +const _widgets = computed(() => props.widgets.filter(x => _widgetDefs.value.includes(x.name as any))); const emit = defineEmits<{ (ev: 'updateWidgets', widgets: Widget[]): void; @@ -80,10 +81,11 @@ const emit = defineEmits<{ (ev: 'exit'): void; }>(); -const widgetRefs = {}; -const configWidget = (id: string) => { +const widgetRefs = {} as Record<string, Component & { configure: () => void }>; + +function configWidget(id: string) { widgetRefs[id].configure(); -}; +} const { model: widgetAdderSelected, @@ -93,7 +95,7 @@ const { initialValue: null, }); -const addWidget = () => { +function addWidget() { if (widgetAdderSelected.value == null) return; emit('addWidget', { @@ -103,23 +105,25 @@ const addWidget = () => { }); widgetAdderSelected.value = null; -}; -const removeWidget = (widget) => { +} + +function removeWidget(widget: Widget) { emit('removeWidget', widget); -}; -const updateWidget = (id: Widget['id'], data: Widget['data']) => { +} + +function updateWidget(id: Widget['id'], data: Widget['data']) { emit('updateWidget', { id, data }); -}; +} -function onContextmenu(widget: Widget, ev: MouseEvent) { +function onContextmenu(widget: Widget, ev: PointerEvent) { const element = ev.target as HTMLElement | null; if (element && isLink(element)) return; - if (element && (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(element.tagName) || element.attributes['contenteditable'])) return; + if (element && (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(element.tagName) || element.attributes.getNamedItem('contenteditable') != null)) return; if (window.getSelection()?.toString() !== '') return; os.contextMenu([{ type: 'label', - text: i18n.ts._widgets[widget.name], + text: i18n.ts._widgets[widget.name as typeof widgetDefs[number]], }, { icon: 'ti ti-settings', text: i18n.ts.settings, diff --git a/packages/frontend/src/components/MkWindow.vue b/packages/frontend/src/components/MkWindow.vue index e5ac791d0b..7fb77da476 100644 --- a/packages/frontend/src/components/MkWindow.vue +++ b/packages/frontend/src/components/MkWindow.vue @@ -55,7 +55,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { onBeforeUnmount, onMounted, provide, useTemplateRef, ref } from 'vue'; import type { MenuItem } from '@/types/menu.js'; -import contains from '@/utility/contains.js'; +import { elementContains } from '@/utility/element-contains.js'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; import { prefer } from '@/preferences.js'; @@ -78,12 +78,12 @@ function dragListen(fn: (ev: MouseEvent | TouchEvent) => void) { window.addEventListener('touchend', dragClear.bind(null, fn)); } -function dragClear(fn) { +function dragClear(fn: (ev: MouseEvent | TouchEvent) => void) { window.removeEventListener('mousemove', fn); window.removeEventListener('touchmove', fn); - window.removeEventListener('mouseleave', dragClear); - window.removeEventListener('mouseup', dragClear); - window.removeEventListener('touchend', dragClear); + window.removeEventListener('mouseleave', dragClear as any); + window.removeEventListener('mouseup', dragClear as any); + window.removeEventListener('touchend', dragClear as any); } const props = withDefaults(defineProps<{ @@ -128,7 +128,7 @@ function close() { showing.value = false; } -function onKeydown(evt) { +function onKeydown(evt: KeyboardEvent) { if (evt.which === 27) { // Esc evt.preventDefault(); evt.stopPropagation(); @@ -136,7 +136,7 @@ function onKeydown(evt) { } } -function onContextmenu(ev: MouseEvent) { +function onContextmenu(ev: PointerEvent) { if (props.contextmenu) { os.contextMenu(props.contextmenu, ev); } @@ -240,7 +240,7 @@ function onHeaderMousedown(evt: MouseEvent | TouchEvent) { const main = rootEl.value; if (main == null) return; - if (!contains(main, window.document.activeElement)) main.focus(); + if (!elementContains(main, window.document.activeElement)) main.focus(); const position = main.getBoundingClientRect(); @@ -418,24 +418,24 @@ function onBottomLeftHandleMousedown(evt: MouseEvent | TouchEvent) { } // 高さを適用 -function applyTransformHeight(height) { +function applyTransformHeight(height: number) { if (height > window.innerHeight) height = window.innerHeight; if (rootEl.value) rootEl.value.style.height = height + 'px'; } // 幅を適用 -function applyTransformWidth(width) { +function applyTransformWidth(width: number) { if (width > window.innerWidth) width = window.innerWidth; if (rootEl.value) rootEl.value.style.width = width + 'px'; } // Y座標を適用 -function applyTransformTop(top) { +function applyTransformTop(top: number) { if (rootEl.value) rootEl.value.style.top = top + 'px'; } // X座標を適用 -function applyTransformLeft(left) { +function applyTransformLeft(left: number) { if (rootEl.value) rootEl.value.style.left = left + 'px'; } diff --git a/packages/frontend/src/components/global/I18n.vue b/packages/frontend/src/components/global/I18n.vue index 9866e50958..1fad1ee9e6 100644 --- a/packages/frontend/src/components/global/I18n.vue +++ b/packages/frontend/src/components/global/I18n.vue @@ -46,6 +46,6 @@ const parsed = computed(() => { }); const render = () => { - return h(props.tag, parsed.value.map(x => typeof x === 'string' ? (props.textTag ? h(props.textTag, x) : x) : slots[x.arg]())); + return h(props.tag, parsed.value.map(x => typeof x === 'string' ? (props.textTag ? h(props.textTag, x) : x) : (slots as any)[x.arg]())); }; </script> diff --git a/packages/frontend/src/components/global/MkA.vue b/packages/frontend/src/components/global/MkA.vue index 99693a4c00..7d2908d4be 100644 --- a/packages/frontend/src/components/global/MkA.vue +++ b/packages/frontend/src/components/global/MkA.vue @@ -48,7 +48,7 @@ const active = computed(() => { return resolved.route.name === router.currentRoute.value.name; }); -function onContextmenu(ev) { +function onContextmenu(ev: PointerEvent) { const selection = window.getSelection(); if (selection && selection.toString() !== '') return; os.contextMenu([{ @@ -85,7 +85,7 @@ function openWindow() { os.pageWindow(props.to); } -function nav(ev: MouseEvent) { +function nav(ev: PointerEvent) { // 制御キーとの組み合わせは無視(shiftを除く) if (ev.metaKey || ev.altKey || ev.ctrlKey) return; diff --git a/packages/frontend/src/components/global/MkAvatar.vue b/packages/frontend/src/components/global/MkAvatar.vue index e7208ed574..fd1d0c2b63 100644 --- a/packages/frontend/src/components/global/MkAvatar.vue +++ b/packages/frontend/src/components/global/MkAvatar.vue @@ -77,7 +77,7 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (ev: 'click', v: MouseEvent): void; + (ev: 'click', v: PointerEvent): void; }>(); const showDecoration = props.forceShowDecoration || prefer.s.showAvatarDecorations; @@ -91,7 +91,7 @@ const url = computed(() => { return props.user.avatarUrl; }); -function onClick(ev: MouseEvent): void { +function onClick(ev: PointerEvent): void { if (props.link) return; emit('click', ev); } diff --git a/packages/frontend/src/components/global/MkCondensedLine.vue b/packages/frontend/src/components/global/MkCondensedLine.vue index 473d444c16..e1fbec4b6f 100644 --- a/packages/frontend/src/components/global/MkCondensedLine.vue +++ b/packages/frontend/src/components/global/MkCondensedLine.vue @@ -23,8 +23,8 @@ const observer = new ResizeObserver((entries) => { transform: string; }[] = []; for (const entry of entries) { - const content = (entry.target[contentSymbol] ? entry.target : entry.target.firstElementChild) as HTMLSpanElement; - const props: Required<Props> = content[contentSymbol]; + const content = ((entry.target as any)[contentSymbol] ? entry.target : entry.target.firstElementChild) as HTMLSpanElement; + const props: Required<Props> = (content as any)[contentSymbol]; const container = content.parentElement as HTMLSpanElement; const contentWidth = content.getBoundingClientRect().width; const containerWidth = container.getBoundingClientRect().width; @@ -46,15 +46,15 @@ const props = withDefaults(defineProps<Props>(), { const content = ref<HTMLSpanElement>(); watch(content, (value, oldValue) => { - if (oldValue) { - delete oldValue[contentSymbol]; + if (oldValue != null) { + delete (oldValue as any)[contentSymbol]; observer.unobserve(oldValue); if (oldValue.parentElement) { observer.unobserve(oldValue.parentElement); } } - if (value) { - value[contentSymbol] = props; + if (value != null) { + (value as any)[contentSymbol] = props; observer.observe(value); if (value.parentElement) { observer.observe(value.parentElement); diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue index 31c358eee7..9a171876a0 100644 --- a/packages/frontend/src/components/global/MkCustomEmoji.vue +++ b/packages/frontend/src/components/global/MkCustomEmoji.vue @@ -102,7 +102,7 @@ const url = computed(() => { const alt = computed(() => `:${customEmojiName.value}:`); const errored = ref(url.value == null); -function onClick(ev: MouseEvent) { +function onClick(ev: PointerEvent) { if (props.menu) { const menuItems: MenuItem[] = []; diff --git a/packages/frontend/src/components/global/MkEmoji.vue b/packages/frontend/src/components/global/MkEmoji.vue index 792f9c7d6f..686720cec2 100644 --- a/packages/frontend/src/components/global/MkEmoji.vue +++ b/packages/frontend/src/components/global/MkEmoji.vue @@ -67,7 +67,7 @@ function unmute() { }); } -function onClick(ev: MouseEvent) { +function onClick(ev: PointerEvent) { if (props.menu) { const menuItems: MenuItem[] = []; diff --git a/packages/frontend/src/components/global/MkMfm.ts b/packages/frontend/src/components/global/MkMfm.ts index 3ad2fda0ee..d270571d4a 100644 --- a/packages/frontend/src/components/global/MkMfm.ts +++ b/packages/frontend/src/components/global/MkMfm.ts @@ -319,7 +319,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven ]); } case 'clickable': { - return h('span', { onClick(ev: MouseEvent): void { + return h('span', { onClick(ev: PointerEvent): void { ev.stopPropagation(); ev.preventDefault(); const clickEv = typeof token.props.args.ev === 'string' ? token.props.args.ev : ''; diff --git a/packages/frontend/src/components/global/MkPageHeader.tabs.vue b/packages/frontend/src/components/global/MkPageHeader.tabs.vue index 1ef75281fd..857fd3d8b4 100644 --- a/packages/frontend/src/components/global/MkPageHeader.tabs.vue +++ b/packages/frontend/src/components/global/MkPageHeader.tabs.vue @@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts"> export type Tab = { key: string; - onClick?: (ev: MouseEvent) => void; + onClick?: (ev: PointerEvent) => void; iconOnly?: boolean; title: string; icon?: string; @@ -70,8 +70,8 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (ev: 'update:tab', key: string); - (ev: 'tabClick', key: string); + (ev: 'update:tab', key: string): void; + (ev: 'tabClick', key: string): void; }>(); const el = useTemplateRef('el'); @@ -96,7 +96,7 @@ function onTabMousedown(tab: Tab, ev: MouseEvent): void { } } -function onTabClick(t: Tab, ev: MouseEvent): void { +function onTabClick(t: Tab, ev: PointerEvent): void { emit('tabClick', t.key); if (t.onClick) { diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue index 2445f8e0ef..e8c93b7092 100644 --- a/packages/frontend/src/components/global/MkPageHeader.vue +++ b/packages/frontend/src/components/global/MkPageHeader.vue @@ -61,7 +61,6 @@ export type PageHeaderProps = { import { onMounted, onUnmounted, ref, inject, useTemplateRef, computed } from 'vue'; import { scrollToTop } from '@@/js/scroll.js'; import XTabs from './MkPageHeader.tabs.vue'; -import { globalEvents } from '@/events.js'; import { getAccountMenu } from '@/accounts.js'; import { $i } from '@/i.js'; import { DI } from '@/di.js'; @@ -72,7 +71,7 @@ const props = withDefaults(defineProps<PageHeaderProps>(), { }); const emit = defineEmits<{ - (ev: 'update:tab', key: string); + (ev: 'update:tab', key: string): void; }>(); //const viewId = inject(DI.viewId); @@ -100,7 +99,7 @@ const top = () => { } }; -async function openAccountMenu(ev: MouseEvent) { +async function openAccountMenu(ev: PointerEvent) { const menuItems = await getAccountMenu({ withExtraOperation: true, }); diff --git a/packages/frontend/src/components/global/MkTip.vue b/packages/frontend/src/components/global/MkTip.vue index 231957a232..1827c16c89 100644 --- a/packages/frontend/src/components/global/MkTip.vue +++ b/packages/frontend/src/components/global/MkTip.vue @@ -32,7 +32,7 @@ function _closeTip() { closeTip(props.k); } -function showMenu(ev: MouseEvent) { +function showMenu(ev: PointerEvent) { os.popupMenu([{ icon: 'ti ti-bulb-off', text: i18n.ts.hideAllTips, diff --git a/packages/frontend/src/components/grid/MkDataCell.vue b/packages/frontend/src/components/grid/MkDataCell.vue index 6f1dae8398..8745146ccf 100644 --- a/packages/frontend/src/components/grid/MkDataCell.vue +++ b/packages/frontend/src/components/grid/MkDataCell.vue @@ -188,7 +188,7 @@ function onCellKeyDown(ev: KeyboardEvent) { } } -function onInputText(ev: Event) { +function onInputText(ev: InputEvent) { editingValue.value = (ev.target as HTMLInputElement).value; } diff --git a/packages/frontend/src/components/grid/MkGrid.vue b/packages/frontend/src/components/grid/MkGrid.vue index 96d9e35773..097a91bad5 100644 --- a/packages/frontend/src/components/grid/MkGrid.vue +++ b/packages/frontend/src/components/grid/MkGrid.vue @@ -715,7 +715,7 @@ function onMouseUp(ev: MouseEvent) { } } -function onContextMenu(ev: MouseEvent) { +function onContextMenu(ev: PointerEvent) { const cellAddress = getCellAddress(ev.target as HTMLElement); if (_DEV_) { console.log(`[grid][context-menu] button: ${ev.button}, cell: ${cellAddress.row}x${cellAddress.col}`); diff --git a/packages/frontend/src/composables/use-chart-tooltip.ts b/packages/frontend/src/composables/use-chart-tooltip.ts index a42f70ba02..9816de7c14 100644 --- a/packages/frontend/src/composables/use-chart-tooltip.ts +++ b/packages/frontend/src/composables/use-chart-tooltip.ts @@ -4,6 +4,7 @@ */ import { onUnmounted, onDeactivated, ref } from 'vue'; +import type { Chart, ChartType, TooltipModel } from 'chart.js'; import * as os from '@/os.js'; import MkChartTooltip from '@/components/MkChartTooltip.vue'; @@ -40,7 +41,7 @@ export function useChartTooltip(opts: { position: 'top' | 'middle' } = { positio tooltipShowing.value = false; }); - function handler(context) { + function handler(context: { chart: Chart; tooltip: TooltipModel<ChartType> }) { if (context.tooltip.opacity === 0) { tooltipShowing.value = false; return; @@ -48,8 +49,8 @@ export function useChartTooltip(opts: { position: 'top' | 'middle' } = { positio tooltipTitle.value = context.tooltip.title[0]; tooltipSeries.value = context.tooltip.body.map((b, i) => ({ - backgroundColor: context.tooltip.labelColors[i].backgroundColor, - borderColor: context.tooltip.labelColors[i].borderColor, + backgroundColor: context.tooltip.labelColors[i].backgroundColor as string, + borderColor: context.tooltip.labelColors[i].borderColor as string, text: b.lines[0], })); diff --git a/packages/frontend/src/composables/use-form.ts b/packages/frontend/src/composables/use-form.ts index 38e9b40e20..812b66d1f0 100644 --- a/packages/frontend/src/composables/use-form.ts +++ b/packages/frontend/src/composables/use-form.ts @@ -31,7 +31,7 @@ export function useForm<T extends Record<string, any>>(initialState: T, save: (n watch([currentState, previousState], () => { for (const key in modifiedStates) { - modifiedStates[key] = !deepEqual(currentState[key], previousState[key]); + (modifiedStates as any)[key] = !deepEqual(currentState[key], previousState[key]); } }, { deep: true }); diff --git a/packages/frontend/src/composables/use-note-capture.ts b/packages/frontend/src/composables/use-note-capture.ts index 2aeb9074e5..25a9383cd5 100644 --- a/packages/frontend/src/composables/use-note-capture.ts +++ b/packages/frontend/src/composables/use-note-capture.ts @@ -7,6 +7,7 @@ import { onUnmounted, reactive } from 'vue'; import * as Misskey from 'misskey-js'; import { EventEmitter } from 'eventemitter3'; import type { Reactive } from 'vue'; +import type { NoteUpdatedEvent } from 'misskey-js/streaming.types.js'; import { useStream } from '@/stream.js'; import { $i } from '@/i.js'; import { store } from '@/store.js'; @@ -15,9 +16,9 @@ import { prefer } from '@/preferences.js'; import { globalEvents } from '@/events.js'; export const noteEvents = new EventEmitter<{ - [ev: `reacted:${string}`]: (ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; }; }) => void; - [ev: `unreacted:${string}`]: (ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; }; }) => void; - [ev: `pollVoted:${string}`]: (ctx: { userId: Misskey.entities.User['id']; choice: string; }) => void; + [ev: `reacted:${string}`]: (ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; } | null; }) => void; + [ev: `unreacted:${string}`]: (ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; } | null; }) => void; + [ev: `pollVoted:${string}`]: (ctx: { userId: Misskey.entities.User['id']; choice: number; }) => void; }>(); const fetchEvent = new EventEmitter<{ @@ -117,7 +118,7 @@ function realtimeSubscribe(props: { const note = props.note; const connection = useStream(); - function onStreamNoteUpdated(noteData): void { + function onStreamNoteUpdated(noteData: NoteUpdatedEvent): void { const { type, id, body } = noteData; if (id !== note.id) return; @@ -136,7 +137,6 @@ function realtimeSubscribe(props: { noteEvents.emit(`unreacted:${id}`, { userId: body.userId, reaction: body.reaction, - emoji: body.emoji, }); break; } @@ -194,9 +194,9 @@ export function useNoteCapture(props: { parentNote: Misskey.entities.Note | null; mock?: boolean; }): { - $note: Reactive<ReactiveNoteData>; - subscribe: () => void; - } { + $note: Reactive<ReactiveNoteData>; + subscribe: () => void; +} { const { note, parentNote, mock } = props; const $note = reactive<ReactiveNoteData>({ @@ -224,7 +224,7 @@ export function useNoteCapture(props: { const reactionUserMap = new Map<Misskey.entities.User['id'], string | typeof noReaction>(); let latestPollVotedKey: string | null = null; - function onReacted(ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; }; }): void { + function onReacted(ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; } | null; }): void { let normalizedName = ctx.reaction.replace(/^:(\w+):$/, ':$1@.:'); normalizedName = normalizedName.match('\u200d') ? normalizedName : normalizedName.replace(/\ufe0f/g, ''); if (reactionUserMap.has(ctx.userId) && reactionUserMap.get(ctx.userId) === normalizedName) return; @@ -244,7 +244,7 @@ export function useNoteCapture(props: { } } - function onUnreacted(ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; }; }): void { + function onUnreacted(ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; } | null; }): void { let normalizedName = ctx.reaction.replace(/^:(\w+):$/, ':$1@.:'); normalizedName = normalizedName.match('\u200d') ? normalizedName : normalizedName.replace(/\ufe0f/g, ''); @@ -263,7 +263,7 @@ export function useNoteCapture(props: { } } - function onPollVoted(ctx: { userId: Misskey.entities.User['id']; choice: string; }): void { + function onPollVoted(ctx: { userId: Misskey.entities.User['id']; choice: number; }): void { const newPollVotedKey = `${ctx.userId}:${ctx.choice}`; if (newPollVotedKey === latestPollVotedKey) return; latestPollVotedKey = newPollVotedKey; diff --git a/packages/frontend/src/composables/use-tooltip.ts b/packages/frontend/src/composables/use-tooltip.ts index af76a3a1e8..e369928208 100644 --- a/packages/frontend/src/composables/use-tooltip.ts +++ b/packages/frontend/src/composables/use-tooltip.ts @@ -22,7 +22,7 @@ export function useTooltip( let changeShowingState: (() => void) | null; - let autoHidingTimer; + let autoHidingTimer: number | null = null; const open = () => { close(); @@ -43,7 +43,7 @@ export function useTooltip( isHovering = false; window.clearTimeout(timeoutId); close(); - window.clearInterval(autoHidingTimer); + if (autoHidingTimer != null) window.clearInterval(autoHidingTimer); } }, 1000); }; @@ -66,7 +66,7 @@ export function useTooltip( if (!isHovering) return; isHovering = false; window.clearTimeout(timeoutId); - window.clearInterval(autoHidingTimer); + if (autoHidingTimer != null) window.clearInterval(autoHidingTimer); close(); }; @@ -81,7 +81,7 @@ export function useTooltip( if (!isHovering) return; isHovering = false; window.clearTimeout(timeoutId); - window.clearInterval(autoHidingTimer); + if (autoHidingTimer != null) window.clearInterval(autoHidingTimer); close(); }; diff --git a/packages/frontend/src/composables/use-uploader.ts b/packages/frontend/src/composables/use-uploader.ts index 8ffb1e656b..fabc04895e 100644 --- a/packages/frontend/src/composables/use-uploader.ts +++ b/packages/frontend/src/composables/use-uploader.ts @@ -664,7 +664,7 @@ export function useUploader(options: { if (needsCompress) { const config = { - mimeType: isWebpSupported() ? 'image/webp' : 'image/jpeg', + mimeType: (isWebpSupported() ? 'image/webp' : 'image/jpeg') as 'image/webp' | 'image/jpeg', maxWidth: compressionSettings.maxWidth, maxHeight: compressionSettings.maxHeight, quality: isWebpSupported() ? 0.85 : 0.8, diff --git a/packages/frontend/src/custom-emojis.ts b/packages/frontend/src/custom-emojis.ts index 45d4b40fd7..e04d540c38 100644 --- a/packages/frontend/src/custom-emojis.ts +++ b/packages/frontend/src/custom-emojis.ts @@ -60,11 +60,11 @@ export async function fetchCustomEmojis(force = false) { set('lastEmojisFetchedAt', now); } -let cachedTags; +let cachedTags: string[] | null = null; export function getCustomEmojiTags() { if (cachedTags) return cachedTags; - const tags = new Set(); + const tags = new Set<string>(); for (const emoji of customEmojis.value) { for (const tag of emoji.aliases) { tags.add(tag); diff --git a/packages/frontend/src/deck.ts b/packages/frontend/src/deck.ts index 208adae8fe..e85ca76ca1 100644 --- a/packages/frontend/src/deck.ts +++ b/packages/frontend/src/deck.ts @@ -316,14 +316,14 @@ export function updateColumn(id: Column['id'], column: Partial<Column>) { const currentColumn = deepClone(columns.value[columnIndex]); if (currentColumn == null) return; for (const [k, v] of Object.entries(column)) { - currentColumn[k] = v; + (currentColumn[k as keyof typeof column] as any) = v; } newColumns[columnIndex] = currentColumn; columns.value = newColumns; saveCurrentDeckProfile(); } -export function switchProfileMenu(ev: MouseEvent) { +export function switchProfileMenu(ev: PointerEvent) { const items: MenuItem[] = prefer.s['deck.profile'] ? [{ text: prefer.s['deck.profile'], active: true, diff --git a/packages/frontend/src/filters/bytes.ts b/packages/frontend/src/filters/bytes.ts index 49b44167d4..a5b6fad3b1 100644 --- a/packages/frontend/src/filters/bytes.ts +++ b/packages/frontend/src/filters/bytes.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -export default (v, digits = 0) => { +export default (v: number | null, digits = 0) => { if (v == null) return '?'; const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', 'RB', 'QB']; if (v === 0) return '0'; diff --git a/packages/frontend/src/filters/kmg.ts b/packages/frontend/src/filters/kmg.ts index 9608e420f6..0aaa70d50d 100644 --- a/packages/frontend/src/filters/kmg.ts +++ b/packages/frontend/src/filters/kmg.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -export default (v, fractionDigits = 0) => { +export default (v: number | null, fractionDigits = 0) => { if (v == null) return 'N/A'; if (v === 0) return '0'; const sizes = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y', 'R', 'Q']; diff --git a/packages/frontend/src/filters/note.ts b/packages/frontend/src/filters/note.ts index ce31021469..3fcd50c5e3 100644 --- a/packages/frontend/src/filters/note.ts +++ b/packages/frontend/src/filters/note.ts @@ -3,6 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -export const notePage = note => { +export const notePage = (note: { id: string }) => { return `/notes/${note.id}`; }; diff --git a/packages/frontend/src/instance.ts b/packages/frontend/src/instance.ts index c9d83a4dbe..547db12e34 100644 --- a/packages/frontend/src/instance.ts +++ b/packages/frontend/src/instance.ts @@ -43,7 +43,7 @@ export async function fetchInstance(force = false): Promise<Misskey.entities.Met }); for (const [k, v] of Object.entries(meta)) { - instance[k] = v; + (instance[k as keyof typeof meta] as any) = v; } miLocalStorage.setItem('instance', JSON.stringify(instance)); diff --git a/packages/frontend/src/lib/nirax.ts b/packages/frontend/src/lib/nirax.ts index 74dda9decd..ce77a2baa9 100644 --- a/packages/frontend/src/lib/nirax.ts +++ b/packages/frontend/src/lib/nirax.ts @@ -125,7 +125,7 @@ type GetSinglePathQuery<Def extends RouteDef, Path extends FlattenAllPaths<Route ? ChildPath extends FlattenAllPaths<Children> ? GetPathQuery<Children, ChildPath> : Record<string, never> - : never + : never : never : never : Def['path'] extends Path @@ -133,9 +133,9 @@ type GetSinglePathQuery<Def extends RouteDef, Path extends FlattenAllPaths<Route ? Query extends Record<string, string> ? UnwrapReadOnly<{ [Key in keyof Query]?: string; }> : Record<string, never> + : Record<string, never> : Record<string, never> - : Record<string, never> - >; +>; type GetPathQuery<Defs extends RouteDef[], Path extends FlattenAllPaths<Defs>> = GetSinglePathQuery<Defs[number], Path>; @@ -320,11 +320,11 @@ export class Nirax<DEF extends RouteDef[]> extends EventEmitter<RouterEvents> { if (route.query != null && queryString != null) { const queryObject = [...new URLSearchParams(queryString).entries()] - .reduce((obj, entry) => ({ ...obj, [entry[0]]: entry[1] }), {}); + .reduce((obj, entry) => ({ ...obj, [entry[0]]: entry[1] }), {}) as Record<string, string>; for (const q in route.query) { const as = route.query[q]; - if (queryObject[q]) { + if (queryObject[q] != null) { props.set(as, safeURIDecode(queryObject[q])); } } diff --git a/packages/frontend/src/navbar.ts b/packages/frontend/src/navbar.ts index a162b3aa9e..c5b77c498a 100644 --- a/packages/frontend/src/navbar.ts +++ b/packages/frontend/src/navbar.ts @@ -6,6 +6,7 @@ import { computed, reactive } from 'vue'; import { ui } from '@@/js/config.js'; import { clearCache } from './utility/clear-cache.js'; +import type { ComputedRef } from 'vue'; import { $i } from '@/i.js'; import { miLocalStorage } from '@/local-storage.js'; import { openInstanceMenu, openToolsMenu } from '@/ui/_common_/common.js'; @@ -14,7 +15,17 @@ import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; import { unisonReload } from '@/utility/unison-reload.js'; -export const navbarItemDef = reactive({ +export const navbarItemDef = reactive<{ + [key: string]: { + title: string; + icon: string; + show?: ComputedRef<boolean>; + indicated?: ComputedRef<boolean>; + indicateValue?: ComputedRef<string>; + to?: string; + action?: (ev: PointerEvent) => void; + }; +}>({ notifications: { title: i18n.ts.notifications, icon: 'ti ti-bell', @@ -137,7 +148,7 @@ export const navbarItemDef = reactive({ ui: { title: i18n.ts.switchUi, icon: 'ti ti-devices', - action: (ev: MouseEvent) => { + action: (ev) => { os.popupMenu([{ text: i18n.ts.default, active: ui === 'default' || ui === null, diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts index 73f18bc6b5..2e2d0a814e 100644 --- a/packages/frontend/src/os.ts +++ b/packages/frontend/src/os.ts @@ -666,7 +666,7 @@ export function popupMenu(items: (MenuItem | null)[], anchorElement?: HTMLElemen })); } -export function contextMenu(items: MenuItem[], ev: MouseEvent): Promise<void> { +export function contextMenu(items: MenuItem[], ev: PointerEvent): Promise<void> { if ( prefer.s.contextMenu === 'native' || (prefer.s.contextMenu === 'appWithShift' && !ev.shiftKey) diff --git a/packages/frontend/src/pages/about.federation.vue b/packages/frontend/src/pages/about.federation.vue index bbfb9a3b7c..c109000108 100644 --- a/packages/frontend/src/pages/about.federation.vue +++ b/packages/frontend/src/pages/about.federation.vue @@ -97,7 +97,7 @@ const paginator = markRaw(new Paginator('federation/instances', { })), })); -function getStatus(instance) { +function getStatus(instance: Misskey.entities.FederationInstance) { if (instance.isSuspended) return 'Suspended'; if (instance.isBlocked) return 'Blocked'; if (instance.isSilenced) return 'Silenced'; diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue index 22e377c75d..b084eb5ab2 100644 --- a/packages/frontend/src/pages/admin-user.vue +++ b/packages/frontend/src/pages/admin-user.vue @@ -105,7 +105,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #label>{{ i18n.ts._role.policies }}</template> <div class="_gaps"> <div v-for="policy in Object.keys(info.policies)" :key="policy"> - {{ policy }} ... {{ info.policies[policy] }} + {{ policy }} ... {{ info.policies[policy as keyof typeof info.policies] }} </div> </div> </MkFolder> @@ -209,6 +209,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, defineAsyncComponent, watch, ref, markRaw } from 'vue'; import * as Misskey from 'misskey-js'; import { url } from '@@/js/config.js'; +import type { ChartSrc } from '@/components/MkChart.vue'; import MkChart from '@/components/MkChart.vue'; import MkObjectView from '@/components/MkObjectView.vue'; import MkTextarea from '@/components/MkTextarea.vue'; @@ -231,7 +232,6 @@ import { ensureSignin, iAmAdmin, iAmModerator } from '@/i.js'; import MkRolePreview from '@/components/MkRolePreview.vue'; import MkPagination from '@/components/MkPagination.vue'; import { Paginator } from '@/utility/paginator.js'; -import type { ChartSrc } from '@/components/MkChart.vue'; const $i = ensureSignin(); @@ -251,7 +251,7 @@ const { } = useMkSelect({ items: [ { label: i18n.ts.notes, value: 'per-user-notes' }, -], + ], initialValue: 'per-user-notes', }); const user = ref(result.user); @@ -344,7 +344,7 @@ async function resetPassword() { } } -async function toggleSuspend(v) { +async function toggleSuspend(v: boolean) { const confirm = await os.confirm({ type: 'warning', text: v ? i18n.ts.suspendConfirm : i18n.ts.unsuspendConfirm, @@ -475,7 +475,7 @@ async function assignRole() { refreshUser(); } -async function unassignRole(role: typeof info.value.roles[number], ev: MouseEvent) { +async function unassignRole(role: typeof info.value.roles[number], ev: PointerEvent) { os.popupMenu([{ text: i18n.ts.unassign, icon: 'ti ti-x', @@ -503,7 +503,7 @@ async function createAnnouncement() { }); } -async function editAnnouncement(announcement) { +async function editAnnouncement(announcement: Misskey.entities.AdminAnnouncementsListResponse[number]) { const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkUserAnnouncementEditDialog.vue').then(x => x.default), { user: user.value, announcement, diff --git a/packages/frontend/src/pages/admin/RolesEditorFormula.vue b/packages/frontend/src/pages/admin/RolesEditorFormula.vue index 9eeedd5c1d..13f66662d0 100644 --- a/packages/frontend/src/pages/admin/RolesEditorFormula.vue +++ b/packages/frontend/src/pages/admin/RolesEditorFormula.vue @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <div class="_gaps"> <div :class="$style.header"> - <MkSelect v-model="type" :items="typeDef" :class="$style.typeSelect"> + <MkSelect v-model="typeModelForMkSelect" :items="typeDef" :class="$style.typeSelect"> </MkSelect> <button v-if="draggable" class="_button" :class="$style.dragHandle" :draggable="true" @dragstart.stop="dragStartCallback"> <i class="ti ti-menu-2"></i> @@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only </button> </div> - <div v-if="type === 'and' || type === 'or'" class="_gaps"> + <div v-if="v.type === 'and' || v.type === 'or'" class="_gaps"> <MkDraggable v-model="v.values" direction="vertical" @@ -32,33 +32,34 @@ SPDX-License-Identifier: AGPL-3.0-only :modelValue="item" :dragStartCallback="dragStart" draggable - @update:modelValue="updated => valuesItemUpdated(updated)" - @remove="removeItem(item.id)" + @update:modelValue="updated => childValuesItemUpdated(updated)" + @remove="removeChildItem(item.id)" /> </div> </template> </MkDraggable> - <MkButton rounded style="margin: 0 auto;" @click="addValue"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton> + <MkButton rounded style="margin: 0 auto;" @click="addChildValue"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton> </div> - <div v-else-if="type === 'not'" :class="$style.item"> + <div v-else-if="v.type === 'not'" :class="$style.item"> <RolesEditorFormula v-model="v.value"/> </div> - <MkInput v-else-if="type === 'createdLessThan' || type === 'createdMoreThan'" v-model="v.sec" type="number"> + <MkInput v-else-if="v.type === 'createdLessThan' || v.type === 'createdMoreThan'" v-model="v.sec" type="number"> <template #suffix>sec</template> </MkInput> - <MkInput v-else-if="['followersLessThanOrEq', 'followersMoreThanOrEq', 'followingLessThanOrEq', 'followingMoreThanOrEq', 'notesLessThanOrEq', 'notesMoreThanOrEq'].includes(type)" v-model="v.value" type="number"> + <MkInput v-else-if="v.type === 'followersLessThanOrEq' || v.type === 'followersMoreThanOrEq' || v.type === 'followingLessThanOrEq' || v.type === 'followingMoreThanOrEq' || v.type === 'notesLessThanOrEq' || v.type === 'notesMoreThanOrEq'" v-model="v.value" type="number"> </MkInput> - <MkSelect v-else-if="type === 'roleAssignedTo'" v-model="v.roleId" :items="assignedToDef"> + <MkSelect v-else-if="v.type === 'roleAssignedTo'" v-model="v.roleId" :items="assignedToDef"> </MkSelect> </div> </template> <script lang="ts" setup> import { computed, ref, watch } from 'vue'; +import * as Misskey from 'misskey-js'; import type { GetMkSelectValueTypesFromDef, MkSelectItem } from '@/components/MkSelect.vue'; import { genId } from '@/utility/id.js'; import MkInput from '@/components/MkInput.vue'; @@ -70,12 +71,12 @@ import { deepClone } from '@/utility/clone.js'; import { rolesCache } from '@/cache.js'; const emit = defineEmits<{ - (ev: 'update:modelValue', value: any): void; + (ev: 'update:modelValue', value: Misskey.entities.Role['condFormula']): void; (ev: 'remove'): void; }>(); const props = defineProps<{ - modelValue: any; + modelValue: Misskey.entities.Role['condFormula']; draggable?: boolean; dragStartCallback?: (ev: DragEvent) => void; }>(); @@ -115,37 +116,50 @@ const typeDef = [ { label: i18n.ts._role._condition.not, value: 'not' }, ] as const satisfies MkSelectItem[]; -const type = computed<GetMkSelectValueTypesFromDef<typeof typeDef>>({ +type KeyOfUnion<T> = T extends T ? keyof T : never; + +type DistributiveOmit<T, K extends KeyOfUnion<T>> = T extends T + ? Omit<T, K> + : never; + +const typeModelForMkSelect = computed<GetMkSelectValueTypesFromDef<typeof typeDef>>({ get: () => v.value.type, set: (t) => { - if (t === 'and') v.value.values = []; - if (t === 'or') v.value.values = []; - if (t === 'not') v.value.value = { id: genId(), type: 'isRemote' }; - if (t === 'roleAssignedTo') v.value.roleId = ''; - if (t === 'createdLessThan') v.value.sec = 86400; - if (t === 'createdMoreThan') v.value.sec = 86400; - if (t === 'followersLessThanOrEq') v.value.value = 10; - if (t === 'followersMoreThanOrEq') v.value.value = 10; - if (t === 'followingLessThanOrEq') v.value.value = 10; - if (t === 'followingMoreThanOrEq') v.value.value = 10; - if (t === 'notesLessThanOrEq') v.value.value = 10; - if (t === 'notesMoreThanOrEq') v.value.value = 10; - v.value.type = t; + let newValue: DistributiveOmit<Misskey.entities.Role['condFormula'], 'id'>; + switch (t) { + case 'and': newValue = { type: 'and', values: [] }; break; + case 'or': newValue = { type: 'or', values: [] }; break; + case 'not': newValue = { type: 'not', value: { id: genId(), type: 'isRemote' } }; break; + case 'roleAssignedTo': newValue = { type: 'roleAssignedTo', roleId: '' }; break; + case 'createdLessThan': newValue = { type: 'createdLessThan', sec: 86400 }; break; + case 'createdMoreThan': newValue = { type: 'createdMoreThan', sec: 86400 }; break; + case 'followersLessThanOrEq': newValue = { type: 'followersLessThanOrEq', value: 10 }; break; + case 'followersMoreThanOrEq': newValue = { type: 'followersMoreThanOrEq', value: 10 }; break; + case 'followingLessThanOrEq': newValue = { type: 'followingLessThanOrEq', value: 10 }; break; + case 'followingMoreThanOrEq': newValue = { type: 'followingMoreThanOrEq', value: 10 }; break; + case 'notesLessThanOrEq': newValue = { type: 'notesLessThanOrEq', value: 10 }; break; + case 'notesMoreThanOrEq': newValue = { type: 'notesMoreThanOrEq', value: 10 }; break; + default: newValue = { type: t }; break; + } + v.value = { id: v.value.id, ...newValue }; }, }); const assignedToDef = computed(() => roles.filter(r => r.target === 'manual').map(r => ({ label: r.name, value: r.id })) satisfies MkSelectItem[]); -function addValue() { +function addChildValue() { + if (v.value.type !== 'and' && v.value.type !== 'or') return; v.value.values.push({ id: genId(), type: 'isRemote' }); } -function valuesItemUpdated(item) { +function childValuesItemUpdated(item: Misskey.entities.Role['condFormula']) { + if (v.value.type !== 'and' && v.value.type !== 'or') return; const i = v.value.values.findIndex(_item => _item.id === item.id); v.value.values[i] = item; } -function removeItem(itemId) { +function removeChildItem(itemId: string) { + if (v.value.type !== 'and' && v.value.type !== 'or') return; v.value.values = v.value.values.filter(_item => _item.id !== itemId); } diff --git a/packages/frontend/src/pages/admin/abuses.vue b/packages/frontend/src/pages/admin/abuses.vue index 76bf20b409..2d204987cb 100644 --- a/packages/frontend/src/pages/admin/abuses.vue +++ b/packages/frontend/src/pages/admin/abuses.vue @@ -105,7 +105,7 @@ const paginator = markRaw(new Paginator('admin/abuse-user-reports', { })), })); -function resolved(reportId) { +function resolved(reportId: string) { paginator.removeItem(reportId); } diff --git a/packages/frontend/src/pages/admin/announcements.vue b/packages/frontend/src/pages/admin/announcements.vue index 4a5c83e9df..ddf458b268 100644 --- a/packages/frontend/src/pages/admin/announcements.vue +++ b/packages/frontend/src/pages/admin/announcements.vue @@ -83,6 +83,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref, computed, watch } from 'vue'; +import * as Misskey from 'misskey-js'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; import MkSelect from '@/components/MkSelect.vue'; @@ -112,7 +113,12 @@ const { const loading = ref(true); const loadingMore = ref(false); -const announcements = ref<any[]>([]); +const announcements = ref<(Omit<Misskey.entities.AdminAnnouncementsListResponse[number], 'id' | 'createdAt' | 'updatedAt' | 'reads' | 'isActive'> & { + id: string | null; + _id?: string; + isActive?: Misskey.entities.AdminAnnouncementsListResponse[number]['isActive']; + reads?: Misskey.entities.AdminAnnouncementsListResponse[number]['reads']; +})[]>([]); watch(announcementsStatus, (to) => { loading.value = true; @@ -136,42 +142,55 @@ function add() { forExistingUsers: false, silence: false, needConfirmationToRead: false, + userId: null, }); } -function del(announcement) { - os.confirm({ +async function del(announcement: (typeof announcements)['value'][number]) { + if (announcement.id == null) return; + const { canceled } = await os.confirm({ type: 'warning', text: i18n.tsx.deleteAreYouSure({ x: announcement.title }), - }).then(({ canceled }) => { - if (canceled) return; - announcements.value = announcements.value.filter(x => x !== announcement); - misskeyApi('admin/announcements/delete', announcement); + }); + if (canceled) return; + announcements.value = announcements.value.filter(x => x !== announcement); + misskeyApi('admin/announcements/delete', { + id: announcement.id, }); } -async function archive(announcement) { +async function archive(announcement: (typeof announcements)['value'][number]) { + if (announcement.id == null) return; + const { _id, ...data } = announcement; // _idを消す await os.apiWithDialog('admin/announcements/update', { - ...announcement, + ...data, + id: announcement.id, // TSを黙らすため isActive: false, }); refresh(); } -async function unarchive(announcement) { +async function unarchive(announcement: (typeof announcements)['value'][number]) { + if (announcement.id == null) return; + const { _id, ...data } = announcement; // _idを消す await os.apiWithDialog('admin/announcements/update', { - ...announcement, + ...data, + id: announcement.id, // TSを黙らすため isActive: true, }); refresh(); } -async function save(announcement) { +async function save(announcement: (typeof announcements)['value'][number]) { + const { _id, ...data } = announcement; // _idを消す if (announcement.id == null) { - await os.apiWithDialog('admin/announcements/create', announcement); + await os.apiWithDialog('admin/announcements/create', data); refresh(); } else { - os.apiWithDialog('admin/announcements/update', announcement); + os.apiWithDialog('admin/announcements/update', { + ...data, + id: announcement.id, // TSを黙らすため + }); } } @@ -179,7 +198,7 @@ function more() { loadingMore.value = true; misskeyApi('admin/announcements/list', { status: announcementsStatus.value, - untilId: announcements.value.reduce((acc, announcement) => announcement.id != null ? announcement : acc).id, + untilId: announcements.value.reduce((acc, announcement) => announcement.id != null ? announcement : acc).id!, }).then(announcementResponse => { announcements.value = announcements.value.concat(announcementResponse); loadingMore.value = false; diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue index 250abeebe2..6f58ab9857 100644 --- a/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue +++ b/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue @@ -51,6 +51,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts"> import type { SortOrder } from '@/components/MkSortOrderEditor.define.js'; import type { GridSortOrderKey } from './custom-emojis-manager.impl.js'; +import type { PageHeaderItem } from '@/types/page-header.js'; export type EmojiSearchQuery = { name: string | null; @@ -250,7 +251,7 @@ function setupGrid(): GridSetting { icon: 'ti ti-trash', action: () => { removeDataFromGrid(context, (cell) => { - gridItems.value[cell.row.index][cell.column.setting.bindTo] = undefined; + (gridItems.value[cell.row.index] as any)[cell.column.setting.bindTo] = undefined; }); }, }, @@ -454,7 +455,7 @@ function onGridCellValidation(event: GridCellValidationEvent) { function onGridCellValueChange(event: GridCellValueChangeEvent) { const { row, column, newValue } = event; if (gridItems.value.length > row.index && column.setting.bindTo in gridItems.value[row.index]) { - gridItems.value[row.index][column.setting.bindTo] = newValue; + (gridItems.value[row.index] as any)[column.setting.bindTo] = newValue; } } @@ -525,7 +526,7 @@ const headerPageMetadata = computed(() => ({ icon: 'ti ti-icons', })); -const headerActions = computed(() => [{ +const headerActions = computed<PageHeaderItem[]>(() => [{ icon: 'ti ti-search', text: i18n.ts.search, handler: async () => { @@ -552,7 +553,7 @@ const headerActions = computed(() => [{ }, { icon: 'ti ti-list-numbers', text: i18n.ts._customEmojisManager._gridCommon.searchLimit, - handler: (ev: MouseEvent) => { + handler: (ev) => { async function changeSearchLimit(to: number) { if (updatedItemsCount.value > 0) { const { canceled } = await os.confirm({ diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.register.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.register.vue index c343d88eb1..7ccb166481 100644 --- a/packages/frontend/src/pages/admin/custom-emojis-manager.register.vue +++ b/packages/frontend/src/pages/admin/custom-emojis-manager.register.vue @@ -58,7 +58,6 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script setup lang="ts"> -/* eslint-disable @typescript-eslint/no-non-null-assertion */ import * as Misskey from 'misskey-js'; import { computed, onMounted, ref, useCssModule } from 'vue'; import type { RequestLogItem } from '@/pages/admin/custom-emojis-manager.impl.js'; @@ -339,7 +338,7 @@ function onGridCellValidation(event: GridCellValidationEvent) { function onGridCellValueChange(event: GridCellValueChangeEvent) { const { row, column, newValue } = event; if (gridItems.value.length > row.index && column.setting.bindTo in gridItems.value[row.index]) { - gridItems.value[row.index][column.setting.bindTo] = newValue; + (gridItems.value[row.index] as any)[column.setting.bindTo] = newValue; } } diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue index 6317fc0b47..d5bfdffe34 100644 --- a/packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue +++ b/packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue @@ -306,7 +306,7 @@ function onGridEvent(event: GridEvent) { function onGridCellValueChange(event: GridCellValueChangeEvent) { const { row, column, newValue } = event; if (gridItems.value.length > row.index && column.setting.bindTo in gridItems.value[row.index]) { - gridItems.value[row.index][column.setting.bindTo] = newValue; + (gridItems.value[row.index] as any)[column.setting.bindTo] = newValue; } } diff --git a/packages/frontend/src/pages/admin/federation-job-queue.chart.chart.vue b/packages/frontend/src/pages/admin/federation-job-queue.chart.chart.vue index 420219c22c..04de781a28 100644 --- a/packages/frontend/src/pages/admin/federation-job-queue.chart.chart.vue +++ b/packages/frontend/src/pages/admin/federation-job-queue.chart.chart.vue @@ -28,7 +28,7 @@ const { handler: externalTooltipHandler } = useChartTooltip(); let chartInstance: Chart | null = null; -function setData(values) { +function setData(values: number[]) { if (chartInstance == null || chartInstance.data.labels == null) return; for (const value of values) { chartInstance.data.labels.push(''); @@ -41,7 +41,7 @@ function setData(values) { chartInstance.update(); } -function pushData(value) { +function pushData(value: number) { if (chartInstance == null || chartInstance.data.labels == null) return; chartInstance.data.labels.push(''); chartInstance.data.datasets[0].data.push(value); diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue index 94994dc94c..b3a929faf4 100644 --- a/packages/frontend/src/pages/admin/index.vue +++ b/packages/frontend/src/pages/admin/index.vue @@ -294,7 +294,7 @@ function invite() { }); } -function adminLookup(ev: MouseEvent) { +function adminLookup(ev: PointerEvent) { os.popupMenu([{ text: i18n.ts.user, icon: 'ti ti-user', diff --git a/packages/frontend/src/pages/admin/overview.active-users.vue b/packages/frontend/src/pages/admin/overview.active-users.vue index 32a5a6976e..9854ca7fc6 100644 --- a/packages/frontend/src/pages/admin/overview.active-users.vue +++ b/packages/frontend/src/pages/admin/overview.active-users.vue @@ -47,7 +47,7 @@ async function renderChart() { return new Date(y, m, d - ago); }; - const format = (arr) => { + const format = (arr: number[]) => { return arr.map((v, i) => ({ x: getDate(i).getTime(), y: v, diff --git a/packages/frontend/src/pages/admin/overview.vue b/packages/frontend/src/pages/admin/overview.vue index 2c550bd9c3..90799647ff 100644 --- a/packages/frontend/src/pages/admin/overview.vue +++ b/packages/frontend/src/pages/admin/overview.vue @@ -104,7 +104,7 @@ const filesPagination = { noPaging: true, }; -function onInstanceClick(i) { +function onInstanceClick(i: Misskey.entities.FederationInstance) { os.pageWindow(`/instance-info/${i.host}`); } diff --git a/packages/frontend/src/pages/admin/roles.edit.vue b/packages/frontend/src/pages/admin/roles.edit.vue index b24b640527..e806f68162 100644 --- a/packages/frontend/src/pages/admin/roles.edit.vue +++ b/packages/frontend/src/pages/admin/roles.edit.vue @@ -21,8 +21,8 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, ref } from 'vue'; import * as Misskey from 'misskey-js'; -import { genId } from '@/utility/id.js'; import XEditor from './roles.editor.vue'; +import { genId } from '@/utility/id.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; @@ -37,8 +37,13 @@ const props = defineProps<{ id?: string; }>(); +type RoleLike = Pick<Misskey.entities.Role, 'name' | 'description' | 'isAdministrator' | 'isModerator' | 'color' | 'iconUrl' | 'target' | 'isPublic' | 'isExplorable' | 'asBadge' | 'canEditMembersByModerator' | 'displayOrder' | 'preserveAssignmentOnMoveAccount'> & { + condFormula: any; + policies: any; +}; + const role = ref<Misskey.entities.Role | null>(null); -const data = ref<any>(null); +const data = ref<RoleLike | null>(null); if (props.id) { role.value = await misskeyApi('admin/roles/show', { @@ -61,11 +66,13 @@ if (props.id) { asBadge: false, canEditMembersByModerator: false, displayOrder: 0, + preserveAssignmentOnMoveAccount: false, policies: {}, }; } async function save() { + if (data.value === null) return; rolesCache.delete(); if (role.value) { os.apiWithDialog('admin/roles/update', { @@ -75,7 +82,7 @@ async function save() { router.push('/admin/roles/:id', { params: { id: role.value.id, - } + }, }); } else { const created = await os.apiWithDialog('admin/roles/create', { @@ -84,7 +91,7 @@ async function save() { router.push('/admin/roles/:id', { params: { id: created.id, - } + }, }); } } diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index 5f8950f07e..7de973a394 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <div class="_gaps"> - <MkInput v-if="readonly" :modelValue="role.id" :readonly="true"> + <MkInput v-if="readonly && role.id != null" :modelValue="role.id" :readonly="true"> <template #label>ID</template> </MkInput> @@ -866,12 +866,18 @@ import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; import { deepClone } from '@/utility/clone.js'; +type RoleLike = Pick<Misskey.entities.Role, 'name' | 'description' | 'isAdministrator' | 'isModerator' | 'color' | 'iconUrl' | 'target' | 'isPublic' | 'isExplorable' | 'asBadge' | 'canEditMembersByModerator' | 'displayOrder' | 'preserveAssignmentOnMoveAccount'> & { + id?: Misskey.entities.Role['id'] | null; + condFormula: any; + policies: any; +}; + const emit = defineEmits<{ - (ev: 'update:modelValue', v: any): void; + (ev: 'update:modelValue', v: RoleLike): void; }>(); const props = defineProps<{ - modelValue: any; + modelValue: RoleLike; readonly?: boolean; }>(); @@ -910,7 +916,7 @@ const rolePermission = computed<GetMkSelectValueTypesFromDef<typeof rolePermissi const q = ref(''); -function getPriorityIcon(option) { +function getPriorityIcon(option: { priority: number }): string { if (option.priority === 2) return 'ti ti-arrows-up'; if (option.priority === 1) return 'ti ti-arrow-narrow-up'; return 'ti ti-point'; @@ -936,6 +942,7 @@ const save = throttle(100, () => { isExplorable: role.value.isExplorable, asBadge: role.value.asBadge, canEditMembersByModerator: role.value.canEditMembersByModerator, + preserveAssignmentOnMoveAccount: role.value.preserveAssignmentOnMoveAccount, policies: role.value.policies, }; diff --git a/packages/frontend/src/pages/admin/roles.role.vue b/packages/frontend/src/pages/admin/roles.role.vue index 2e249eee50..7fc51979af 100644 --- a/packages/frontend/src/pages/admin/roles.role.vue +++ b/packages/frontend/src/pages/admin/roles.role.vue @@ -28,15 +28,15 @@ SPDX-License-Identifier: AGPL-3.0-only <template #default="{ items }"> <div class="_gaps_s"> - <div v-for="item in items" :key="item.user.id" :class="[$style.userItem, { [$style.userItemOpened]: expandedItems.includes(item.id) }]"> + <div v-for="item in items" :key="item.user.id" :class="[$style.userItem, { [$style.userItemOpened]: expandedItemIds.includes(item.id) }]"> <div :class="$style.userItemMain"> <MkA :class="$style.userItemMainBody" :to="`/admin/user/${item.user.id}`"> <MkUserCardMini :user="item.user"/> </MkA> - <button class="_button" :class="$style.userToggle" @click="toggleItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button> - <button class="_button" :class="$style.unassign" @click="unassign(item.user, $event)"><i class="ti ti-x"></i></button> + <button class="_button" :class="$style.userToggle" @click="toggleItem(item.id)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button> + <button class="_button" :class="$style.unassign" @click="unassign(item.user.id, $event)"><i class="ti ti-x"></i></button> </div> - <div v-if="expandedItems.includes(item.id)" :class="$style.userItemSub"> + <div v-if="expandedItemIds.includes(item.id)" :class="$style.userItemSub"> <div>Assigned: <MkTime :time="item.createdAt" mode="detail"/></div> <div v-if="item.expiresAt">Period: {{ new Date(item.expiresAt).toLocaleString() }}</div> <div v-else>Period: {{ i18n.ts.indefinitely }}</div> @@ -55,6 +55,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, markRaw, reactive, ref } from 'vue'; +import * as Misskey from 'misskey-js'; import XEditor from './roles.editor.vue'; import MkFolder from '@/components/MkFolder.vue'; import * as os from '@/os.js'; @@ -81,7 +82,7 @@ const usersPaginator = markRaw(new Paginator('admin/roles/users', { }) : undefined), })); -const expandedItems = ref<string[]>([]); +const expandedItemIds = ref<Misskey.entities.AdminRolesUsersResponse[number]['id'][]>([]); const role = reactive(await misskeyApi('admin/roles/show', { roleId: props.id, @@ -91,7 +92,7 @@ function edit() { router.push('/admin/roles/:id/edit', { params: { id: role.id, - } + }, }); } @@ -140,23 +141,23 @@ async function assign() { //role.users.push(user); } -async function unassign(user, ev) { +async function unassign(userId: Misskey.entities.User['id'], ev: PointerEvent) { os.popupMenu([{ text: i18n.ts.unassign, icon: 'ti ti-x', danger: true, action: async () => { - await os.apiWithDialog('admin/roles/unassign', { roleId: role.id, userId: user.id }); - //role.users = role.users.filter(u => u.id !== user.id); + await os.apiWithDialog('admin/roles/unassign', { roleId: role.id, userId: userId }); + //role.users = role.users.filter(u => u.id !== userId); }, }], ev.currentTarget ?? ev.target); } -async function toggleItem(item) { - if (expandedItems.value.includes(item.id)) { - expandedItems.value = expandedItems.value.filter(x => x !== item.id); +async function toggleItem(itemId: string) { + if (expandedItemIds.value.includes(itemId)) { + expandedItemIds.value = expandedItemIds.value.filter(x => x !== itemId); } else { - expandedItems.value.push(item.id); + expandedItemIds.value.push(itemId); } } diff --git a/packages/frontend/src/pages/admin/users.vue b/packages/frontend/src/pages/admin/users.vue index 2f7ecca521..eb9806d668 100644 --- a/packages/frontend/src/pages/admin/users.vue +++ b/packages/frontend/src/pages/admin/users.vue @@ -46,6 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, markRaw, ref, watchEffect } from 'vue'; +import * as Misskey from 'misskey-js'; import { defaultMemoryStorage } from '@/memory-storage'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; @@ -146,7 +147,7 @@ async function addUser() { }); } -function show(user) { +function show(user: Misskey.entities.UserDetailed) { os.pageWindow(`/admin/user/${user.id}`); } diff --git a/packages/frontend/src/pages/announcements.vue b/packages/frontend/src/pages/announcements.vue index 4c34c3c74b..150808fcbd 100644 --- a/packages/frontend/src/pages/announcements.vue +++ b/packages/frontend/src/pages/announcements.vue @@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </MkA> </div> - <div v-if="tab !== 'past' && $i && !announcement.silence && !announcement.isRead" :class="$style.footer"> + <div v-if="tab !== 'past' && $i != null && !announcement.silence && !announcement.isRead" :class="$style.footer"> <MkButton primary @click="read(announcement)"><i class="ti ti-check"></i> {{ i18n.ts.gotIt }}</MkButton> </div> </section> @@ -45,6 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref, computed, markRaw } from 'vue'; +import * as Misskey from 'misskey-js'; import MkPagination from '@/components/MkPagination.vue'; import MkButton from '@/components/MkButton.vue'; import MkInfo from '@/components/MkInfo.vue'; @@ -65,7 +66,9 @@ const paginator = markRaw(new Paginator('announcements', { const tab = ref('current'); -async function read(target) { +async function read(target: Misskey.entities.Announcement) { + if ($i == null) return; + if (target.needConfirmationToRead) { const confirm = await os.confirm({ type: 'question', @@ -81,7 +84,7 @@ async function read(target) { })); misskeyApi('i/read-announcement', { announcementId: target.id }); updateCurrentAccountPartial({ - unreadAnnouncements: $i!.unreadAnnouncements.filter(a => a.id !== target.id), + unreadAnnouncements: $i.unreadAnnouncements.filter(a => a.id !== target.id), }); } diff --git a/packages/frontend/src/pages/api-console.vue b/packages/frontend/src/pages/api-console.vue index f436fc72fa..8377dc074d 100644 --- a/packages/frontend/src/pages/api-console.vue +++ b/packages/frontend/src/pages/api-console.vue @@ -73,7 +73,7 @@ function onEndpointChange() { return; } - const endpointBody = {}; + const endpointBody = {} as Record<string, unknown>; for (const p of resp.params) { endpointBody[p.name] = p.type === 'String' ? '' : diff --git a/packages/frontend/src/pages/auth.form.vue b/packages/frontend/src/pages/auth.form.vue index 1a0c9b36c4..6e9f5dd1eb 100644 --- a/packages/frontend/src/pages/auth.form.vue +++ b/packages/frontend/src/pages/auth.form.vue @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div v-if="app.permission.length > 0"> <p>{{ i18n.tsx._auth.permission({ name }) }}</p> <ul> - <li v-for="p in app.permission" :key="p">{{ i18n.ts._permissions[p] }}</li> + <li v-for="p in app.permission" :key="p">{{ (i18n.ts._permissions as any)[p] ?? p }}</li> </ul> </div> <div>{{ i18n.tsx._auth.shareAccess({ name: `${name} (${app.id})` }) }}</div> diff --git a/packages/frontend/src/pages/auth.vue b/packages/frontend/src/pages/auth.vue index 83bf7221d0..14b13e511a 100644 --- a/packages/frontend/src/pages/auth.vue +++ b/packages/frontend/src/pages/auth.vue @@ -67,7 +67,7 @@ function accepted() { } } -function onLogin(res) { +function onLogin(res: Misskey.entities.SigninFlowResponse & { finished: true }) { login(res.i); } diff --git a/packages/frontend/src/pages/avatar-decoration-edit-dialog.vue b/packages/frontend/src/pages/avatar-decoration-edit-dialog.vue index a8ce527523..68e8d6a4d0 100644 --- a/packages/frontend/src/pages/avatar-decoration-edit-dialog.vue +++ b/packages/frontend/src/pages/avatar-decoration-edit-dialog.vue @@ -78,7 +78,7 @@ import { ensureSignin } from '@/i.js'; const $i = ensureSignin(); const props = defineProps<{ - avatarDecoration?: any, + avatarDecoration?: Misskey.entities.AdminAvatarDecorationsListResponse[number], }>(); const emit = defineEmits<{ @@ -109,7 +109,7 @@ async function addRole() { rolesThatCanBeUsedThisDecoration.value.push(roles.find(r => r.id === roleId)!); } -async function removeRole(role, ev) { +async function removeRole(role: Misskey.entities.Role, ev: PointerEvent) { rolesThatCanBeUsedThisDecoration.value = rolesThatCanBeUsedThisDecoration.value.filter(x => x.id !== role.id); } @@ -147,6 +147,8 @@ async function done() { } async function del() { + if (props.avatarDecoration == null) return; + const { canceled } = await os.confirm({ type: 'warning', text: i18n.tsx.removeAreYouSure({ x: name.value }), diff --git a/packages/frontend/src/pages/avatar-decorations.vue b/packages/frontend/src/pages/avatar-decorations.vue index f96c02a567..4c5457504e 100644 --- a/packages/frontend/src/pages/avatar-decorations.vue +++ b/packages/frontend/src/pages/avatar-decorations.vue @@ -45,7 +45,7 @@ function load() { load(); -async function add(ev: MouseEvent) { +async function add(ev: PointerEvent) { const { dispose } = await os.popupAsyncWithDialog(import('./avatar-decoration-edit-dialog.vue').then(x => x.default), { }, { done: result => { @@ -57,7 +57,7 @@ async function add(ev: MouseEvent) { }); } -async function edit(avatarDecoration) { +async function edit(avatarDecoration: Misskey.entities.AdminAvatarDecorationsListResponse[number]) { const { dispose } = await os.popupAsyncWithDialog(import('./avatar-decoration-edit-dialog.vue').then(x => x.default), { avatarDecoration: avatarDecoration, }, { diff --git a/packages/frontend/src/pages/channel-editor.vue b/packages/frontend/src/pages/channel-editor.vue index 16b01937c0..4b73b6c6b3 100644 --- a/packages/frontend/src/pages/channel-editor.vue +++ b/packages/frontend/src/pages/channel-editor.vue @@ -191,7 +191,7 @@ async function archive() { }); } -function setBannerImage(evt) { +function setBannerImage(evt: PointerEvent) { selectFile({ anchorElement: evt.currentTarget ?? evt.target, multiple: false, diff --git a/packages/frontend/src/pages/chat/XMessage.vue b/packages/frontend/src/pages/chat/XMessage.vue index cd984f6fb5..f759e45e48 100644 --- a/packages/frontend/src/pages/chat/XMessage.vue +++ b/packages/frontend/src/pages/chat/XMessage.vue @@ -94,7 +94,7 @@ provide(DI.mfmEmojiReactCallback, (reaction) => { }); }); -function react(ev: MouseEvent) { +function react(ev: PointerEvent) { if ($i.policies.chatAvailability !== 'available') return; const targetEl = getHTMLElementOrNull(ev.currentTarget ?? ev.target); @@ -128,14 +128,14 @@ function onReactionClick(record: Misskey.entities.ChatMessage['reactions'][0]) { } } -function onContextmenu(ev: MouseEvent) { +function onContextmenu(ev: PointerEvent) { if (ev.target && isLink(ev.target as HTMLElement)) return; if (window.getSelection()?.toString() !== '') return; showMenu(ev, true); } -function showMenu(ev: MouseEvent, contextmenu = false) { +function showMenu(ev: PointerEvent, contextmenu = false) { const menu: MenuItem[] = []; if (!isMe.value && $i.policies.chatAvailability === 'available') { diff --git a/packages/frontend/src/pages/chat/home.home.vue b/packages/frontend/src/pages/chat/home.home.vue index 756bf8a342..ed04253046 100644 --- a/packages/frontend/src/pages/chat/home.home.vue +++ b/packages/frontend/src/pages/chat/home.home.vue @@ -64,7 +64,7 @@ const searchQuery = ref(''); const searched = ref(false); const searchResults = ref<Misskey.entities.ChatMessage[]>([]); -function start(ev: MouseEvent) { +function start(ev: PointerEvent) { os.popupMenu([{ text: i18n.ts._chat.individualChat, caption: i18n.ts._chat.individualChat_description, @@ -89,7 +89,7 @@ async function startUser() { router.push('/chat/user/:userId', { params: { userId: user.id, - } + }, }); }); } @@ -108,7 +108,7 @@ async function createRoom() { router.push('/chat/room/:roomId', { params: { roomId: room.id, - } + }, }); } diff --git a/packages/frontend/src/pages/chat/room.form.vue b/packages/frontend/src/pages/chat/room.form.vue index 17b68d6eb9..72aeba0a45 100644 --- a/packages/frontend/src/pages/chat/room.form.vue +++ b/packages/frontend/src/pages/chat/room.form.vue @@ -167,7 +167,7 @@ function onKeydown(ev: KeyboardEvent) { } } -function chooseFile(ev: MouseEvent) { +function chooseFile(ev: PointerEvent) { selectFile({ anchorElement: ev.currentTarget ?? ev.target, multiple: false, diff --git a/packages/frontend/src/pages/chat/room.vue b/packages/frontend/src/pages/chat/room.vue index ef9191b4a5..a4204435b3 100644 --- a/packages/frontend/src/pages/chat/room.vue +++ b/packages/frontend/src/pages/chat/room.vue @@ -391,7 +391,7 @@ async function leaveRoom() { router.push('/chat'); } -function showMenu(ev: MouseEvent) { +function showMenu(ev: PointerEvent) { const menuItems: MenuItem[] = []; if (room.value) { diff --git a/packages/frontend/src/pages/clip.vue b/packages/frontend/src/pages/clip.vue index 8176fb519b..8feddf70b0 100644 --- a/packages/frontend/src/pages/clip.vue +++ b/packages/frontend/src/pages/clip.vue @@ -34,6 +34,7 @@ import { computed, watch, provide, ref, markRaw } from 'vue'; import * as Misskey from 'misskey-js'; import { url } from '@@/js/config.js'; import type { MenuItem } from '@/types/menu.js'; +import type { PageHeaderItem } from '@/types/page-header.js'; import MkNotesTimeline from '@/components/MkNotesTimeline.vue'; import { $i } from '@/i.js'; import { i18n } from '@/i18n.js'; @@ -105,7 +106,7 @@ async function unfavorite() { }); } -const headerActions = computed(() => clip.value && isOwned.value ? [{ +const headerActions = computed<PageHeaderItem[] | null>(() => clip.value && isOwned.value ? [{ icon: 'ti ti-pencil', text: i18n.ts.edit, handler: async (): Promise<void> => { @@ -144,7 +145,7 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{ }, ...(clip.value.isPublic ? [{ icon: 'ti ti-share', text: i18n.ts.share, - handler: (ev: MouseEvent): void => { + handler: (ev): void => { const menuItems: MenuItem[] = []; menuItems.push({ @@ -177,7 +178,7 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{ os.popupMenu(menuItems, ev.currentTarget ?? ev.target); }, -}] : []), { +}] satisfies PageHeaderItem[] : []), { icon: 'ti ti-trash', text: i18n.ts.delete, danger: true, @@ -196,7 +197,7 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{ clipsCache.delete(); }, -}] : null); +}] satisfies PageHeaderItem[] : null); definePage(() => ({ title: clip.value ? clip.value.name : i18n.ts.clip, diff --git a/packages/frontend/src/pages/custom-emojis-manager.vue b/packages/frontend/src/pages/custom-emojis-manager.vue index 9bc8992155..5cb88f0b1a 100644 --- a/packages/frontend/src/pages/custom-emojis-manager.vue +++ b/packages/frontend/src/pages/custom-emojis-manager.vue @@ -54,7 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #empty><span>{{ i18n.ts.noCustomEmojis }}</span></template> <template #default="{items}"> <div class="ldhfsamy"> - <div v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" @click="remoteMenu(emoji, $event)"> + <div v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" @click="remoteMenu(emoji as RemoteEmoji, $event)"> <img :src="getProxiedImageUrl(emoji.url, 'emoji')" class="img" :alt="emoji.name"/> <div class="body"> <div class="name _monospace">{{ emoji.name }}</div> @@ -94,6 +94,8 @@ const host = ref<string | null>(null); const selectMode = ref(false); const selectedEmojis = ref<string[]>([]); +type RemoteEmoji = Misskey.entities.AdminEmojiListRemoteResponse[number] & { host: string }; + const paginator = markRaw(new Paginator('admin/emoji/list', { limit: 30, computedParams: computed(() => ({ @@ -159,7 +161,13 @@ const edit = async (emoji: Misskey.entities.EmojiDetailed) => { }); }; -const detailRemoteEmoji = (emoji) => { +const detailRemoteEmoji = (emoji: { + id: string, + name: string, + host: string, + license: string | null, + url: string +}) => { const { dispose } = os.popup(MkRemoteEmojiEditDialog, { emoji: emoji, }, { @@ -172,13 +180,19 @@ const detailRemoteEmoji = (emoji) => { }); }; -const importEmoji = (emoji) => { +const importEmoji = (emojiId: string) => { os.apiWithDialog('admin/emoji/copy', { - emojiId: emoji.id, + emojiId: emojiId, }); }; -const remoteMenu = (emoji, ev: MouseEvent) => { +const remoteMenu = (emoji: { + id: string, + name: string, + host: string, + license: string | null, + url: string +}, ev: PointerEvent) => { os.popupMenu([{ type: 'label', text: ':' + emoji.name + ':', @@ -189,11 +203,11 @@ const remoteMenu = (emoji, ev: MouseEvent) => { }, { text: i18n.ts.import, icon: 'ti ti-plus', - action: () => { importEmoji(emoji); }, + action: () => { importEmoji(emoji.id); }, }], ev.currentTarget ?? ev.target); }; -const menu = (ev: MouseEvent) => { +const menu = (ev: PointerEvent) => { os.popupMenu([{ icon: 'ti ti-download', text: i18n.ts.export, diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue index 88300d8a74..4c677a8d09 100644 --- a/packages/frontend/src/pages/drop-and-fusion.game.vue +++ b/packages/frontend/src/pages/drop-and-fusion.game.vue @@ -729,7 +729,7 @@ async function start() { }, 1500); } -function onClick(ev: MouseEvent) { +function onClick(ev: PointerEvent) { if (!containerElRect) return; if (replaying.value) return; const x = (ev.clientX - containerElRect.left) / viewScale; diff --git a/packages/frontend/src/pages/emoji-edit-dialog.vue b/packages/frontend/src/pages/emoji-edit-dialog.vue index 4b6c5e1c51..edd3987524 100644 --- a/packages/frontend/src/pages/emoji-edit-dialog.vue +++ b/packages/frontend/src/pages/emoji-edit-dialog.vue @@ -58,7 +58,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div v-for="role in rolesThatCanBeUsedThisEmojiAsReaction" :key="role.id" :class="$style.roleItem"> <MkRolePreview :class="$style.role" :role="role" :forModeration="true" :detailed="false" style="pointer-events: none;"/> - <button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="removeRole(role, $event)"><i class="ti ti-x"></i></button> + <button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="removeRole(role)"><i class="ti ti-x"></i></button> <button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ti ti-ban"></i></button> </div> @@ -120,7 +120,7 @@ watch(roleIdsThatCanBeUsedThisEmojiAsReaction, async () => { const imgUrl = computed(() => file.value ? file.value.url : props.emoji ? props.emoji.url : null); -async function changeImage(ev: Event) { +async function changeImage(ev: PointerEvent) { file.value = await selectFile({ anchorElement: ev.currentTarget ?? ev.target, multiple: false, @@ -143,7 +143,7 @@ async function addRole() { rolesThatCanBeUsedThisEmojiAsReaction.value.push(roles.find(r => r.id === roleId)!); } -async function removeRole(role: Misskey.entities.RoleLite, ev: Event) { +async function removeRole(role: Misskey.entities.RoleLite) { rolesThatCanBeUsedThisEmojiAsReaction.value = rolesThatCanBeUsedThisEmojiAsReaction.value.filter(x => x.id !== role.id); } diff --git a/packages/frontend/src/pages/emojis.emoji.vue b/packages/frontend/src/pages/emojis.emoji.vue index aaf433e78e..bed7f2166a 100644 --- a/packages/frontend/src/pages/emojis.emoji.vue +++ b/packages/frontend/src/pages/emojis.emoji.vue @@ -15,7 +15,6 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import * as Misskey from 'misskey-js'; -import { defineAsyncComponent } from 'vue'; import type { MenuItem } from '@/types/menu.js'; import * as os from '@/os.js'; import { misskeyApiGet } from '@/utility/misskey-api.js'; @@ -28,7 +27,7 @@ const props = defineProps<{ emoji: Misskey.entities.EmojiSimple; }>(); -function menu(ev) { +function menu(ev: PointerEvent) { const menuItems: MenuItem[] = []; menuItems.push({ type: 'label', @@ -57,22 +56,21 @@ function menu(ev) { menuItems.push({ text: i18n.ts.edit, icon: 'ti ti-pencil', - action: () => { - edit(props.emoji); + action: async () => { + const detailedEmoji = await misskeyApiGet('emoji', { + name: props.emoji.name, + }); + const { dispose } = await os.popupAsyncWithDialog(import('@/pages/emoji-edit-dialog.vue').then(x => x.default), { + emoji: detailedEmoji, + }, { + closed: () => dispose(), + }); }, }); } os.popupMenu(menuItems, ev.currentTarget ?? ev.target); } - -const edit = async (emoji) => { - const { dispose } = await os.popupAsyncWithDialog(import('@/pages/emoji-edit-dialog.vue').then(x => x.default), { - emoji: emoji, - }, { - closed: () => dispose(), - }); -}; </script> <style lang="scss" module> diff --git a/packages/frontend/src/pages/flash/flash-edit.vue b/packages/frontend/src/pages/flash/flash-edit.vue index b3e8e88c23..3d9de0584a 100644 --- a/packages/frontend/src/pages/flash/flash-edit.vue +++ b/packages/frontend/src/pages/flash/flash-edit.vue @@ -395,7 +395,7 @@ const { }); const script = ref(flash.value?.script ?? PRESET_DEFAULT); -function selectPreset(ev: MouseEvent) { +function selectPreset(ev: PointerEvent) { os.popupMenu([{ text: 'Omikuji', action: () => { diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue index f5e51dc72f..43a1e8e426 100644 --- a/packages/frontend/src/pages/flash/flash.vue +++ b/packages/frontend/src/pages/flash/flash.vue @@ -104,7 +104,7 @@ function fetchFlash() { }); } -function share(ev: MouseEvent) { +function share(ev: PointerEvent) { if (!flash.value) return; const menuItems: MenuItem[] = []; @@ -229,10 +229,10 @@ async function run() { THIS_URL: values.STR(`${url}/play/${flash.value.id}`), }, { in: aiScriptReadline, - out: (value) => { + out: () => { // nop }, - log: (type, params) => { + log: () => { // nop }, }); @@ -273,7 +273,7 @@ async function reportAbuse() { }); } -function showMenu(ev: MouseEvent) { +function showMenu(ev: PointerEvent) { if (!flash.value) return; const menu: MenuItem[] = [ diff --git a/packages/frontend/src/pages/follow-requests.vue b/packages/frontend/src/pages/follow-requests.vue index ba24d7abc6..1b729837a7 100644 --- a/packages/frontend/src/pages/follow-requests.vue +++ b/packages/frontend/src/pages/follow-requests.vue @@ -89,7 +89,7 @@ async function cancel(user: Misskey.entities.UserLite) { }); } -function displayUser(req) { +function displayUser(req: Misskey.entities.FollowingRequestsListResponse[number]) { return tab.value === 'list' ? req.follower : req.followee; } diff --git a/packages/frontend/src/pages/gallery/edit.root.vue b/packages/frontend/src/pages/gallery/edit.root.vue index 45493ab561..ec0a293494 100644 --- a/packages/frontend/src/pages/gallery/edit.root.vue +++ b/packages/frontend/src/pages/gallery/edit.root.vue @@ -58,7 +58,7 @@ const description = ref(props.post?.description ?? null); const title = ref(props.post?.title ?? ''); const isSensitive = ref(props.post?.isSensitive ?? false); -function chooseFile(evt) { +function chooseFile(evt: MouseEvent) { selectFile({ anchorElement: evt.currentTarget ?? evt.target, multiple: true, @@ -67,7 +67,7 @@ function chooseFile(evt) { }); } -function remove(file) { +function remove(file: NonNullable<Misskey.entities.GalleryPost['files']>[number]) { files.value = files.value.filter(f => f.id !== file.id); } diff --git a/packages/frontend/src/pages/gallery/post.vue b/packages/frontend/src/pages/gallery/post.vue index f60bbc0b74..92cb663ee1 100644 --- a/packages/frontend/src/pages/gallery/post.vue +++ b/packages/frontend/src/pages/gallery/post.vue @@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only <button v-tooltip="i18n.ts.shareWithNote" v-click-anime class="_button" @click="shareWithNote"><i class="ti ti-repeat ti-fw"></i></button> <button v-tooltip="i18n.ts.copyLink" v-click-anime class="_button" @click="copyLink"><i class="ti ti-link ti-fw"></i></button> <button v-if="isSupportShare()" v-tooltip="i18n.ts.share" v-click-anime class="_button" @click="share"><i class="ti ti-share ti-fw"></i></button> - <button v-if="$i && $i.id !== post.user.id" v-click-anime class="_button" @mousedown="showMenu"><i class="ti ti-dots ti-fw"></i></button> + <button v-if="$i && $i.id !== post.user.id" v-click-anime class="_button" @click="showMenu"><i class="ti ti-dots ti-fw"></i></button> </div> </div> <div class="user"> @@ -175,7 +175,7 @@ async function reportAbuse() { }); } -function showMenu(ev: MouseEvent) { +function showMenu(ev: PointerEvent) { if (!post.value) return; const menuItems: MenuItem[] = []; diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue index 132c55571a..92a5d25983 100644 --- a/packages/frontend/src/pages/my-lists/list.vue +++ b/packages/frontend/src/pages/my-lists/list.vue @@ -110,7 +110,7 @@ function addUser() { }); } -async function removeUser(item, ev) { +async function removeUser(item: Misskey.entities.UsersListsGetMembershipsResponse[number], ev: PointerEvent) { os.popupMenu([{ text: i18n.ts.remove, icon: 'ti ti-x', @@ -127,7 +127,7 @@ async function removeUser(item, ev) { }], ev.currentTarget ?? ev.target); } -async function showMembershipMenu(item, ev) { +async function showMembershipMenu(item: Misskey.entities.UsersListsGetMembershipsResponse[number], ev: PointerEvent) { const withRepliesRef = ref(item.withReplies); os.popupMenu([{ diff --git a/packages/frontend/src/pages/notifications.vue b/packages/frontend/src/pages/notifications.vue index 5d308e6b29..37ec6284a3 100644 --- a/packages/frontend/src/pages/notifications.vue +++ b/packages/frontend/src/pages/notifications.vue @@ -22,6 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, markRaw, ref } from 'vue'; import { notificationTypes } from 'misskey-js'; +import type { PageHeaderItem } from '@/types/page-header.js'; import MkStreamingNotificationsTimeline from '@/components/MkStreamingNotificationsTimeline.vue'; import MkNotesTimeline from '@/components/MkNotesTimeline.vue'; import * as os from '@/os.js'; @@ -44,7 +45,7 @@ const directNotesPaginator = markRaw(new Paginator('notes/mentions', { }, })); -function setFilter(ev) { +function setFilter(ev: PointerEvent) { const typeItems = notificationTypes.map(t => ({ text: i18n.ts._notification._types[t], active: (includeTypes.value && includeTypes.value.includes(t)) ?? false, @@ -62,7 +63,7 @@ function setFilter(ev) { os.popupMenu(items, ev.currentTarget ?? ev.target); } -const headerActions = computed(() => [tab.value === 'all' ? { +const headerActions = computed<PageHeaderItem[]>(() => ([tab.value === 'all' ? { text: i18n.ts.filter, icon: 'ti ti-filter', highlighted: includeTypes.value != null, @@ -73,7 +74,7 @@ const headerActions = computed(() => [tab.value === 'all' ? { handler: () => { os.apiWithDialog('notifications/mark-all-as-read', {}); }, -} : undefined].filter(x => x !== undefined)); +} : undefined] as (PageHeaderItem | undefined)[]).filter(x => x !== undefined)); const headerTabs = computed(() => [{ key: 'all', diff --git a/packages/frontend/src/pages/page-editor/page-editor.blocks.vue b/packages/frontend/src/pages/page-editor/page-editor.blocks.vue index f18eb7e4f0..891d29e1df 100644 --- a/packages/frontend/src/pages/page-editor/page-editor.blocks.vue +++ b/packages/frontend/src/pages/page-editor/page-editor.blocks.vue @@ -47,7 +47,7 @@ const emit = defineEmits<{ (ev: 'update:modelValue', value: Misskey.entities.Page['content']): void; }>(); -function updateItem(v) { +function updateItem(v: Misskey.entities.PageBlock) { const i = props.modelValue.findIndex(x => x.id === v.id); const newValue = [ ...props.modelValue.slice(0, i), @@ -57,8 +57,8 @@ function updateItem(v) { emit('update:modelValue', newValue); } -function removeItem(el) { - const i = props.modelValue.findIndex(x => x.id === el.id); +function removeItem(v: Misskey.entities.PageBlock) { + const i = props.modelValue.findIndex(x => x.id === v.id); const newValue = [ ...props.modelValue.slice(0, i), ...props.modelValue.slice(i + 1), diff --git a/packages/frontend/src/pages/page-editor/page-editor.vue b/packages/frontend/src/pages/page-editor/page-editor.vue index 3b36f7fa2d..85871c993c 100644 --- a/packages/frontend/src/pages/page-editor/page-editor.vue +++ b/packages/frontend/src/pages/page-editor/page-editor.vue @@ -247,9 +247,9 @@ async function add() { } } -function setEyeCatchingImage(img: Event) { +function setEyeCatchingImage(ev: PointerEvent) { selectFile({ - anchorElement: img.currentTarget ?? img.target, + anchorElement: ev.currentTarget ?? ev.target, multiple: false, }).then(file => { eyeCatchingImageId.value = file.id; diff --git a/packages/frontend/src/pages/page.vue b/packages/frontend/src/pages/page.vue index c3b52a24fd..212c8140c8 100644 --- a/packages/frontend/src/pages/page.vue +++ b/packages/frontend/src/pages/page.vue @@ -64,7 +64,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkA v-if="page.userId === $i?.id" v-tooltip="i18n.ts._pages.editThisPage" :to="`/pages/edit/${page.id}`" class="_button" :class="$style.generalActionButton"><i class="ti ti-pencil ti-fw"></i></MkA> <button v-tooltip="i18n.ts.copyLink" class="_button" :class="$style.generalActionButton" @click="copyLink"><i class="ti ti-link ti-fw"></i></button> <button v-tooltip="i18n.ts.share" class="_button" :class="$style.generalActionButton" @click="share"><i class="ti ti-share ti-fw"></i></button> - <button v-if="$i" v-click-anime class="_button" :class="$style.generalActionButton" @mousedown="showMenu"><i class="ti ti-dots ti-fw"></i></button> + <button v-if="$i" v-click-anime class="_button" :class="$style.generalActionButton" @click="showMenu"><i class="ti ti-dots ti-fw"></i></button> </div> </div> <div :class="$style.pageUser"> @@ -163,7 +163,7 @@ function fetchPage() { }); } -function share(ev: MouseEvent) { +function share(ev: PointerEvent) { if (!page.value) return; const menuItems: MenuItem[] = []; @@ -237,7 +237,7 @@ async function unlike() { }); } -function pin(pin) { +function pin(pin: boolean) { if (!page.value) return; os.apiWithDialog('i/update', { @@ -258,7 +258,7 @@ async function reportAbuse() { }); } -function showMenu(ev: MouseEvent) { +function showMenu(ev: PointerEvent) { if (!page.value) return; const menuItems: MenuItem[] = []; diff --git a/packages/frontend/src/pages/reversi/game.board.vue b/packages/frontend/src/pages/reversi/game.board.vue index aae638641a..61bd95d051 100644 --- a/packages/frontend/src/pages/reversi/game.board.vue +++ b/packages/frontend/src/pages/reversi/game.board.vue @@ -308,7 +308,7 @@ if (!props.game.isEnded) { }, TIMER_INTERVAL_SEC * 1000, { immediate: false, afterMounted: true }); } -async function onStreamLog(log) { +async function onStreamLog(log: Reversi.Serializer.Log & { id: string | null }) { game.value.logs = Reversi.Serializer.serializeLogs([ ...Reversi.Serializer.deserializeLogs(game.value.logs), log, @@ -348,7 +348,10 @@ async function onStreamLog(log) { } } -function onStreamEnded(x) { +function onStreamEnded(x: { + winnerId: Misskey.entities.User['id'] | null; + game: Misskey.entities.ReversiGameDetailed; +}) { game.value = deepClone(x.game); if (game.value.winnerId === $i.id) { @@ -384,7 +387,7 @@ function checkEnd() { } } -function restoreGame(_game) { +function restoreGame(_game: Misskey.entities.ReversiGameDetailed) { game.value = deepClone(_game); engine.value = Reversi.Serializer.restoreGame({ diff --git a/packages/frontend/src/pages/reversi/game.setting.vue b/packages/frontend/src/pages/reversi/game.setting.vue index 1e01496bbb..b987a8e911 100644 --- a/packages/frontend/src/pages/reversi/game.setting.vue +++ b/packages/frontend/src/pages/reversi/game.setting.vue @@ -113,6 +113,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, watch, ref, onMounted, shallowRef, onUnmounted } from 'vue'; import * as Misskey from 'misskey-js'; import * as Reversi from 'misskey-reversi'; +import type { MenuItem } from '@/types/menu.js'; import { i18n } from '@/i18n.js'; import { ensureSignin } from '@/i.js'; import { deepClone } from '@/utility/clone.js'; @@ -121,7 +122,6 @@ import MkRadios from '@/components/MkRadios.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkFolder from '@/components/MkFolder.vue'; import * as os from '@/os.js'; -import type { MenuItem } from '@/types/menu.js'; import { useRouter } from '@/router.js'; const $i = ensureSignin(); @@ -165,7 +165,7 @@ watch(() => game.value.timeLimitForEachTurn, () => { updateSettings('timeLimitForEachTurn'); }); -function chooseMap(ev: MouseEvent) { +function chooseMap(ev: PointerEvent) { const menu: MenuItem[] = []; for (const c of mapCategories) { @@ -212,7 +212,10 @@ function unready() { props.connection.send('ready', false); } -function onChangeReadyStates(states) { +function onChangeReadyStates(states: { + user1: boolean; + user2: boolean; +}) { game.value.user1Ready = states.user1; game.value.user2Ready = states.user2; } diff --git a/packages/frontend/src/pages/reversi/index.vue b/packages/frontend/src/pages/reversi/index.vue index 8438943126..9a737e93ac 100644 --- a/packages/frontend/src/pages/reversi/index.vue +++ b/packages/frontend/src/pages/reversi/index.vue @@ -208,7 +208,7 @@ async function matchUser() { matchHeatbeat(); } -async function matchAny(ev: MouseEvent) { +async function matchAny(ev: PointerEvent) { const isLoggedIn = await pleaseLogin(); if (!isLoggedIn) return; @@ -239,11 +239,11 @@ function cancelMatching() { } } -async function accept(user) { +async function accept(user: Misskey.entities.UserLite) { const game = await misskeyApi('reversi/match', { userId: user.id, }); - if (game) { + if (game != null) { startGame(game); } } diff --git a/packages/frontend/src/pages/scratchpad.vue b/packages/frontend/src/pages/scratchpad.vue index 4e02556c83..b3b899517e 100644 --- a/packages/frontend/src/pages/scratchpad.vue +++ b/packages/frontend/src/pages/scratchpad.vue @@ -97,7 +97,7 @@ watch(code, () => { miLocalStorage.setItem('scratchpad', code.value); }); -function stringifyUiProps(uiProps) { +function stringifyUiProps(uiProps: AsUiComponent) { return JSON.stringify( { ...uiProps, type: undefined, id: undefined }, (k, v) => typeof v === 'function' ? '<function>' : v, diff --git a/packages/frontend/src/pages/settings/2fa.vue b/packages/frontend/src/pages/settings/2fa.vue index 2cc13744b1..fcd598fe76 100644 --- a/packages/frontend/src/pages/settings/2fa.vue +++ b/packages/frontend/src/pages/settings/2fa.vue @@ -85,6 +85,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { defineAsyncComponent, computed } from 'vue'; import { supported as webAuthnSupported, create as webAuthnCreate, parseCreationOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill'; +import * as Misskey from 'misskey-js'; import MkButton from '@/components/MkButton.vue'; import MkInfo from '@/components/MkInfo.vue'; import MkSwitch from '@/components/MkSwitch.vue'; @@ -156,7 +157,7 @@ function renewTOTP(): void { }); } -async function unregisterKey(key) { +async function unregisterKey(key: NonNullable<Misskey.entities.MeDetailedOnly['securityKeysList']>[number]) { const confirm = await os.confirm({ type: 'question', title: i18n.ts._2fa.removeKey, @@ -175,7 +176,7 @@ async function unregisterKey(key) { os.success(); } -async function renameKey(key) { +async function renameKey(key: NonNullable<Misskey.entities.MeDetailedOnly['securityKeysList']>[number]) { const name = await os.inputText({ title: i18n.ts.rename, default: key.name, diff --git a/packages/frontend/src/pages/settings/account-data.vue b/packages/frontend/src/pages/settings/account-data.vue index c75667b06b..b07515a49a 100644 --- a/packages/frontend/src/pages/settings/account-data.vue +++ b/packages/frontend/src/pages/settings/account-data.vue @@ -189,7 +189,7 @@ const onImportSuccess = () => { }); }; -const onError = (ev) => { +const onError = (ev: Error) => { os.alert({ type: 'error', text: ev.message, @@ -232,7 +232,7 @@ const exportAntennas = () => { misskeyApi('i/export-antennas', {}).then(onExportSuccess).catch(onError); }; -const importFollowing = async (ev) => { +const importFollowing = async (ev: PointerEvent) => { const file = await selectFile({ anchorElement: ev.currentTarget ?? ev.target, multiple: false, @@ -243,7 +243,7 @@ const importFollowing = async (ev) => { }).then(onImportSuccess).catch(onError); }; -const importUserLists = async (ev) => { +const importUserLists = async (ev: PointerEvent) => { const file = await selectFile({ anchorElement: ev.currentTarget ?? ev.target, multiple: false, @@ -251,7 +251,7 @@ const importUserLists = async (ev) => { misskeyApi('i/import-user-lists', { fileId: file.id }).then(onImportSuccess).catch(onError); }; -const importMuting = async (ev) => { +const importMuting = async (ev: PointerEvent) => { const file = await selectFile({ anchorElement: ev.currentTarget ?? ev.target, multiple: false, @@ -259,7 +259,7 @@ const importMuting = async (ev) => { misskeyApi('i/import-muting', { fileId: file.id }).then(onImportSuccess).catch(onError); }; -const importBlocking = async (ev) => { +const importBlocking = async (ev: PointerEvent) => { const file = await selectFile({ anchorElement: ev.currentTarget ?? ev.target, multiple: false, @@ -267,7 +267,7 @@ const importBlocking = async (ev) => { misskeyApi('i/import-blocking', { fileId: file.id }).then(onImportSuccess).catch(onError); }; -const importAntennas = async (ev) => { +const importAntennas = async (ev: PointerEvent) => { const file = await selectFile({ anchorElement: ev.currentTarget ?? ev.target, multiple: false, diff --git a/packages/frontend/src/pages/settings/accounts.vue b/packages/frontend/src/pages/settings/accounts.vue index 764ec72652..55a81bbf38 100644 --- a/packages/frontend/src/pages/settings/accounts.vue +++ b/packages/frontend/src/pages/settings/accounts.vue @@ -38,7 +38,7 @@ function refreshAllAccounts() { // TODO } -function showMenu(host: string, id: string, ev: MouseEvent) { +function showMenu(host: string, id: string, ev: PointerEvent) { let menu: MenuItem[]; menu = [{ @@ -54,7 +54,7 @@ function showMenu(host: string, id: string, ev: MouseEvent) { os.popupMenu(menu, ev.currentTarget ?? ev.target); } -function addAccount(ev: MouseEvent) { +function addAccount(ev: PointerEvent) { os.popupMenu([{ text: i18n.ts.existingAccount, action: () => { addExistingAccount(); }, diff --git a/packages/frontend/src/pages/settings/apps.vue b/packages/frontend/src/pages/settings/apps.vue index 10901f737b..e9857b1e0b 100644 --- a/packages/frontend/src/pages/settings/apps.vue +++ b/packages/frontend/src/pages/settings/apps.vue @@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #label>{{ i18n.ts.permission }}</template> <template #suffix>{{ Object.keys(token.permission).length === 0 ? i18n.ts.none : Object.keys(token.permission).length }}</template> <ul> - <li v-for="p in token.permission" :key="p">{{ i18n.ts._permissions[p] }}</li> + <li v-for="p in token.permission" :key="p">{{ (i18n.ts._permissions as any)[p] ?? p }}</li> </ul> </MkFolder> </div> @@ -68,7 +68,7 @@ const paginator = markRaw(new Paginator('i/apps', { }, })); -function revoke(token) { +function revoke(token: Misskey.entities.IAppsResponse[number]) { misskeyApi('i/revoke-token', { tokenId: token.id }).then(() => { paginator.reload(); }); diff --git a/packages/frontend/src/pages/settings/deck.vue b/packages/frontend/src/pages/settings/deck.vue index 7a19b0495b..6ff08b5499 100644 --- a/packages/frontend/src/pages/settings/deck.vue +++ b/packages/frontend/src/pages/settings/deck.vue @@ -113,7 +113,7 @@ watch(wallpaper, () => { suggestReload(); }); -function setWallpaper(ev: MouseEvent) { +function setWallpaper(ev: PointerEvent) { selectFile({ anchorElement: ev.currentTarget ?? ev.target, multiple: false, diff --git a/packages/frontend/src/pages/settings/drive-cleaner.vue b/packages/frontend/src/pages/settings/drive-cleaner.vue index 3aeb356bd3..7189e19780 100644 --- a/packages/frontend/src/pages/settings/drive-cleaner.vue +++ b/packages/frontend/src/pages/settings/drive-cleaner.vue @@ -116,11 +116,11 @@ function genUsageBar(fsize: number): StyleValue { }; } -function onClick(ev: MouseEvent, file) { +function onClick(ev: PointerEvent, file: Misskey.entities.DriveFile) { os.popupMenu(getDriveFileMenu(file), (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined); } -function onContextMenu(ev: MouseEvent, file): void { +function onContextMenu(ev: PointerEvent, file: Misskey.entities.DriveFile): void { os.contextMenu(getDriveFileMenu(file), ev); } diff --git a/packages/frontend/src/pages/settings/drive.ImageFrameItem.vue b/packages/frontend/src/pages/settings/drive.ImageFrameItem.vue index 62922fc964..f92e87375f 100644 --- a/packages/frontend/src/pages/settings/drive.ImageFrameItem.vue +++ b/packages/frontend/src/pages/settings/drive.ImageFrameItem.vue @@ -52,7 +52,7 @@ async function edit() { }); } -function del(ev: MouseEvent) { +function del(ev: PointerEvent) { os.popupMenu([{ text: i18n.ts.delete, action: () => { diff --git a/packages/frontend/src/pages/settings/drive.WatermarkItem.vue b/packages/frontend/src/pages/settings/drive.WatermarkItem.vue index 0c03a4493a..9e80d719de 100644 --- a/packages/frontend/src/pages/settings/drive.WatermarkItem.vue +++ b/packages/frontend/src/pages/settings/drive.WatermarkItem.vue @@ -52,7 +52,7 @@ async function edit() { }); } -function del(ev: MouseEvent) { +function del(ev: PointerEvent) { os.popupMenu([{ text: i18n.ts.delete, action: () => { diff --git a/packages/frontend/src/pages/settings/email.vue b/packages/frontend/src/pages/settings/email.vue index 469a3c2f1c..85fea7ae66 100644 --- a/packages/frontend/src/pages/settings/email.vue +++ b/packages/frontend/src/pages/settings/email.vue @@ -76,11 +76,11 @@ const $i = ensureSignin(); const emailAddress = ref($i.email ?? ''); -const onChangeReceiveAnnouncementEmail = (v) => { +function onChangeReceiveAnnouncementEmail(v: boolean) { misskeyApi('i/update', { receiveAnnouncementEmail: v, }); -}; +} async function saveEmailAddress() { const auth = await os.authenticateDialog(); diff --git a/packages/frontend/src/pages/settings/emoji-palette.palette.vue b/packages/frontend/src/pages/settings/emoji-palette.palette.vue index 0282f62fb8..d8a5f16b7d 100644 --- a/packages/frontend/src/pages/settings/emoji-palette.palette.vue +++ b/packages/frontend/src/pages/settings/emoji-palette.palette.vue @@ -76,7 +76,7 @@ watch(emojis, () => { emit('updateEmojis', emojis.value); }, { deep: true }); -function remove(reaction: string, ev: MouseEvent) { +function remove(reaction: string, ev: PointerEvent) { os.popupMenu([{ text: i18n.ts.remove, action: () => { @@ -85,7 +85,7 @@ function remove(reaction: string, ev: MouseEvent) { }], getHTMLElement(ev)); } -function pick(ev: MouseEvent) { +function pick(ev: PointerEvent) { os.pickEmoji(getHTMLElement(ev), { showPinned: false, }).then(it => { @@ -96,7 +96,7 @@ function pick(ev: MouseEvent) { }); } -function getHTMLElement(ev: MouseEvent): HTMLElement { +function getHTMLElement(ev: PointerEvent): HTMLElement { const target = ev.currentTarget ?? ev.target; return target as HTMLElement; } @@ -124,7 +124,7 @@ function paste() { }); } -function del(ev: MouseEvent) { +function del(ev: PointerEvent) { os.popupMenu([{ text: i18n.ts.delete, action: () => { diff --git a/packages/frontend/src/pages/settings/emoji-palette.vue b/packages/frontend/src/pages/settings/emoji-palette.vue index 7f31699ed1..0f41d9b777 100644 --- a/packages/frontend/src/pages/settings/emoji-palette.vue +++ b/packages/frontend/src/pages/settings/emoji-palette.vue @@ -226,12 +226,12 @@ function delPalette(id: string) { } } -function getHTMLElement(ev: MouseEvent): HTMLElement { +function getHTMLElement(ev: PointerEvent): HTMLElement { const target = ev.currentTarget ?? ev.target; return target as HTMLElement; } -function previewPicker(ev: MouseEvent) { +function previewPicker(ev: PointerEvent) { emojiPicker.show(getHTMLElement(ev)); } diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue index 39c32d347f..57c7cb6989 100644 --- a/packages/frontend/src/pages/settings/index.vue +++ b/packages/frontend/src/pages/settings/index.vue @@ -166,7 +166,7 @@ const menuDef = computed<SuperMenuDef[]>(() => [{ type: 'button', icon: 'ti ti-settings-2', text: i18n.ts.preferencesProfile, - action: async (ev: MouseEvent) => { + action: async (ev) => { os.popupMenu(getPreferencesProfileMenu(), ev.currentTarget ?? ev.target); }, }, { diff --git a/packages/frontend/src/pages/settings/mute-block.emoji-mute.vue b/packages/frontend/src/pages/settings/mute-block.emoji-mute.vue index ea131381a1..37cd9fa67d 100644 --- a/packages/frontend/src/pages/settings/mute-block.emoji-mute.vue +++ b/packages/frontend/src/pages/settings/mute-block.emoji-mute.vue @@ -55,12 +55,12 @@ import { const emojis = prefer.model('mutingEmojis'); -function getHTMLElement(ev: MouseEvent): HTMLElement { +function getHTMLElement(ev: PointerEvent): HTMLElement { const target = ev.currentTarget ?? ev.target; return target as HTMLElement; } -function add(ev: MouseEvent) { +function add(ev: PointerEvent) { os.pickEmoji(getHTMLElement(ev), { showPinned: false }).then((emoji) => { if (emoji) { muteEmoji(emoji); @@ -68,7 +68,7 @@ function add(ev: MouseEvent) { }); } -function onEmojiClick(ev: MouseEvent, emoji: string) { +function onEmojiClick(ev: PointerEvent, emoji: string) { const menuItems : MenuItem[] = [{ type: 'label', text: emoji, diff --git a/packages/frontend/src/pages/settings/mute-block.vue b/packages/frontend/src/pages/settings/mute-block.vue index 6fd9f07a47..433969f474 100644 --- a/packages/frontend/src/pages/settings/mute-block.vue +++ b/packages/frontend/src/pages/settings/mute-block.vue @@ -173,6 +173,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref, computed, watch, markRaw } from 'vue'; +import * as Misskey from 'misskey-js'; import XEmojiMute from './mute-block.emoji-mute.vue'; import XInstanceMute from './mute-block.instance-mute.vue'; import XWordMute from './mute-block.word-mute.vue'; @@ -218,7 +219,7 @@ watch([ suggestReload(); }); -async function unrenoteMute(user, ev) { +async function unrenoteMute(user: Misskey.entities.UserDetailed, ev: PointerEvent) { os.popupMenu([{ text: i18n.ts.renoteUnmute, icon: 'ti ti-x', @@ -229,7 +230,7 @@ async function unrenoteMute(user, ev) { }], ev.currentTarget ?? ev.target); } -async function unmute(user, ev) { +async function unmute(user: Misskey.entities.UserDetailed, ev: PointerEvent) { os.popupMenu([{ text: i18n.ts.unmute, icon: 'ti ti-x', @@ -240,7 +241,7 @@ async function unmute(user, ev) { }], ev.currentTarget ?? ev.target); } -async function unblock(user, ev) { +async function unblock(user: Misskey.entities.UserDetailed, ev: PointerEvent) { os.popupMenu([{ text: i18n.ts.unblock, icon: 'ti ti-x', diff --git a/packages/frontend/src/pages/settings/mute-block.word-mute.vue b/packages/frontend/src/pages/settings/mute-block.word-mute.vue index f5837abe98..49d8ecd92d 100644 --- a/packages/frontend/src/pages/settings/mute-block.word-mute.vue +++ b/packages/frontend/src/pages/settings/mute-block.word-mute.vue @@ -30,7 +30,7 @@ const emit = defineEmits<{ (ev: 'save', value: (string[] | string)[]): void; }>(); -const render = (mutedWords) => mutedWords.map(x => { +const render = (mutedWords: (string | string[])[]) => mutedWords.map(x => { if (Array.isArray(x)) { return x.join(' '); } else { @@ -46,13 +46,13 @@ watch(mutedWords, () => { }); async function save() { - const parseMutes = (mutes) => { + const parseMutes = (mutes: string) => { // split into lines, remove empty lines and unnecessary whitespace - let lines = mutes.trim().split('\n').map(line => line.trim()).filter(line => line !== ''); + let lines = mutes.trim().split('\n').map(line => line.trim()).filter(line => line !== '') as (string | string[])[]; // check each line if it is a RegExp or not for (let i = 0; i < lines.length; i++) { - const line = lines[i]; + const line = lines[i] as string; const regexp = line.match(/^\/(.+)\/(.*)$/); if (regexp) { // check that the RegExp is valid diff --git a/packages/frontend/src/pages/settings/notifications.vue b/packages/frontend/src/pages/settings/notifications.vue index 2802d3263e..3787e07626 100644 --- a/packages/frontend/src/pages/settings/notifications.vue +++ b/packages/frontend/src/pages/settings/notifications.vue @@ -13,16 +13,16 @@ SPDX-License-Identifier: AGPL-3.0-only <FormSection first> <template #label>{{ i18n.ts.notificationRecieveConfig }}</template> <div class="_gaps_s"> - <MkFolder v-for="type in notificationTypes.filter(x => !nonConfigurableNotificationTypes.includes(x))" :key="type"> + <MkFolder v-for="type in configurableNotificationTypes" :key="type"> <template #label>{{ i18n.ts._notification._types[type] }}</template> <template #suffix> {{ - $i.notificationRecieveConfig[type]?.type === 'never' ? i18n.ts.none : - $i.notificationRecieveConfig[type]?.type === 'following' ? i18n.ts.following : - $i.notificationRecieveConfig[type]?.type === 'follower' ? i18n.ts.followers : - $i.notificationRecieveConfig[type]?.type === 'mutualFollow' ? i18n.ts.mutualFollow : - $i.notificationRecieveConfig[type]?.type === 'followingOrFollower' ? i18n.ts.followingOrFollower : - $i.notificationRecieveConfig[type]?.type === 'list' ? i18n.ts.userList : + $i.notificationRecieveConfig[type as (typeof configurableNotificationTypes)[number]]?.type === 'never' ? i18n.ts.none : + $i.notificationRecieveConfig[type as (typeof configurableNotificationTypes)[number]]?.type === 'following' ? i18n.ts.following : + $i.notificationRecieveConfig[type as (typeof configurableNotificationTypes)[number]]?.type === 'follower' ? i18n.ts.followers : + $i.notificationRecieveConfig[type as (typeof configurableNotificationTypes)[number]]?.type === 'mutualFollow' ? i18n.ts.mutualFollow : + $i.notificationRecieveConfig[type as (typeof configurableNotificationTypes)[number]]?.type === 'followingOrFollower' ? i18n.ts.followingOrFollower : + $i.notificationRecieveConfig[type as (typeof configurableNotificationTypes)[number]]?.type === 'list' ? i18n.ts.userList : i18n.ts.all }} </template> @@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only <XNotificationConfig :userLists="userLists" :value="$i.notificationRecieveConfig[type] ?? { type: 'all' }" - :configurableTypes="onlyOnOrOffNotificationTypes.includes(type) ? ['all', 'never'] : undefined" + :configurableTypes="(onlyOnOrOffNotificationTypes as string[]).includes(type) ? ['all', 'never'] : undefined" @update="(res) => updateReceiveConfig(type, res)" /> </MkFolder> @@ -83,9 +83,11 @@ import MkFeatureBanner from '@/components/MkFeatureBanner.vue'; const $i = ensureSignin(); -const nonConfigurableNotificationTypes = ['note', 'roleAssigned', 'followRequestAccepted', 'test', 'exportCompleted'] satisfies (typeof notificationTypes[number])[] as string[]; +const nonConfigurableNotificationTypes = ['note', 'roleAssigned', 'followRequestAccepted', 'test', 'exportCompleted'] as const satisfies (typeof notificationTypes[number])[]; -const onlyOnOrOffNotificationTypes = ['app', 'achievementEarned', 'login', 'createToken', 'scheduledNotePosted', 'scheduledNotePostFailed'] satisfies (typeof notificationTypes[number])[] as string[]; +const configurableNotificationTypes = notificationTypes.filter(type => !nonConfigurableNotificationTypes.includes(type as any)) as Exclude<typeof notificationTypes[number], typeof nonConfigurableNotificationTypes[number]>[]; + +const onlyOnOrOffNotificationTypes = ['app', 'achievementEarned', 'login', 'createToken', 'scheduledNotePosted', 'scheduledNotePostFailed'] as const satisfies (typeof notificationTypes[number])[]; const allowButton = useTemplateRef('allowButton'); const pushRegistrationInServer = computed(() => allowButton.value?.pushRegistrationInServer); diff --git a/packages/frontend/src/pages/settings/other.vue b/packages/frontend/src/pages/settings/other.vue index d4097bde94..3d149c3ab6 100644 --- a/packages/frontend/src/pages/settings/other.vue +++ b/packages/frontend/src/pages/settings/other.vue @@ -40,7 +40,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div class="_gaps_s"> <div v-for="policy in Object.keys($i.policies)" :key="policy"> - {{ policy }} ... {{ $i.policies[policy] }} + {{ policy }} ... {{ $i.policies[policy as keyof typeof $i.policies] }} </div> </div> </MkFolder> diff --git a/packages/frontend/src/pages/settings/plugin.vue b/packages/frontend/src/pages/settings/plugin.vue index 7c6ce90e7e..16c764001e 100644 --- a/packages/frontend/src/pages/settings/plugin.vue +++ b/packages/frontend/src/pages/settings/plugin.vue @@ -58,7 +58,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #key>{{ i18n.ts.permission }}</template> <template #value> <ul style="margin-top: 0; margin-bottom: 0;"> - <li v-for="permission in plugin.permissions" :key="permission">{{ i18n.ts._permissions[permission] }}</li> + <li v-for="permission in plugin.permissions" :key="permission">{{ (i18n.ts._permissions as any)[permission] ?? permission }}</li> <li v-if="!plugin.permissions || plugin.permissions.length === 0">{{ i18n.ts.none }}</li> </ul> </template> @@ -96,6 +96,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { nextTick, ref, computed } from 'vue'; +import { isSafeMode } from '@@/js/config.js'; import type { Plugin } from '@/plugin.js'; import FormLink from '@/components/form/link.vue'; import MkSwitch from '@/components/MkSwitch.vue'; @@ -110,7 +111,6 @@ import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; import { changePluginActive, configPlugin, pluginLogs, uninstallPlugin, reloadPlugin } from '@/plugin.js'; import { prefer } from '@/preferences.js'; -import { isSafeMode } from '@@/js/config.js'; import * as os from '@/os.js'; const plugins = prefer.r.plugins; diff --git a/packages/frontend/src/pages/settings/preferences.vue b/packages/frontend/src/pages/settings/preferences.vue index aa7f0dabbb..76b84795f6 100644 --- a/packages/frontend/src/pages/settings/preferences.vue +++ b/packages/frontend/src/pages/settings/preferences.vue @@ -1042,7 +1042,7 @@ function removePinnedList() { function enableAllDataSaver() { const g = { ...prefer.s.dataSaver }; - Object.keys(g).forEach((key) => { g[key] = true; }); + Object.keys(g).forEach((key) => { (g as any)[key] = true; }); dataSaver.value = g; } @@ -1050,7 +1050,7 @@ function enableAllDataSaver() { function disableAllDataSaver() { const g = { ...prefer.s.dataSaver }; - Object.keys(g).forEach((key) => { g[key] = false; }); + Object.keys(g).forEach((key) => { (g as any)[key] = false; }); dataSaver.value = g; } diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue index 27a1ed279b..a7aea9bde4 100644 --- a/packages/frontend/src/pages/settings/profile.vue +++ b/packages/frontend/src/pages/settings/profile.vue @@ -163,6 +163,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, reactive, ref, watch } from 'vue'; +import * as Misskey from 'misskey-js'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; import MkSwitch from '@/components/MkSwitch.vue'; @@ -266,8 +267,8 @@ function save() { } } -function changeAvatar(ev) { - async function done(driveFile) { +function changeAvatar(ev: PointerEvent) { + async function done(driveFile: Misskey.entities.DriveFile) { const i = await os.apiWithDialog('i/update', { avatarId: driveFile.id, }); @@ -315,8 +316,8 @@ function changeAvatar(ev) { }], ev.currentTarget ?? ev.target); } -function changeBanner(ev) { - async function done(driveFile) { +function changeBanner(ev: PointerEvent) { + async function done(driveFile: Misskey.entities.DriveFile) { const i = await os.apiWithDialog('i/update', { bannerId: driveFile.id, }); diff --git a/packages/frontend/src/pages/settings/profiles.vue b/packages/frontend/src/pages/settings/profiles.vue index 4804c11f7a..b3d02ba3fe 100644 --- a/packages/frontend/src/pages/settings/profiles.vue +++ b/packages/frontend/src/pages/settings/profiles.vue @@ -15,21 +15,16 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { ref, computed } from 'vue'; -import type { MenuItem } from '@/types/menu.js'; +import { computed } from 'vue'; import MkButton from '@/components/MkButton.vue'; import MkFolder from '@/components/MkFolder.vue'; -import * as os from '@/os.js'; -import { misskeyApi } from '@/utility/misskey-api.js'; -import { $i } from '@/i.js'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; -import { prefer } from '@/preferences.js'; import { deleteCloudBackup, listCloudBackups } from '@/preferences/utility.js'; const backups = await listCloudBackups(); -function del(backup) { +function del(backup: { name: string }): void { deleteCloudBackup(backup.name); } diff --git a/packages/frontend/src/pages/settings/sounds.sound.vue b/packages/frontend/src/pages/settings/sounds.sound.vue index 31fe9a64db..050586c2e1 100644 --- a/packages/frontend/src/pages/settings/sounds.sound.vue +++ b/packages/frontend/src/pages/settings/sounds.sound.vue @@ -32,6 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref, computed, watch } from 'vue'; import type { SoundType } from '@/utility/sound.js'; +import type { SoundStore } from '@/preferences/def.js'; import MkSelect from '@/components/MkSelect.vue'; import MkButton from '@/components/MkButton.vue'; import MkRange from '@/components/MkRange.vue'; @@ -41,7 +42,6 @@ import { useMkSelect } from '@/composables/use-mkselect.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { playMisskeySfxFile, soundsTypes, getSoundDuration } from '@/utility/sound.js'; import { selectFile } from '@/utility/drive.js'; -import type { SoundStore } from '@/preferences/def.js'; const props = defineProps<{ def: SoundStore; @@ -100,7 +100,7 @@ const friendlyFileName = computed<string>(() => { return i18n.ts._soundSettings.driveFileWarn; }); -function selectSound(ev) { +function selectSound(ev: PointerEvent) { selectFile({ anchorElement: ev.currentTarget ?? ev.target, multiple: false, diff --git a/packages/frontend/src/pages/settings/sounds.vue b/packages/frontend/src/pages/settings/sounds.vue index 1b851825d6..0d0623f11f 100644 --- a/packages/frontend/src/pages/settings/sounds.vue +++ b/packages/frontend/src/pages/settings/sounds.vue @@ -100,11 +100,14 @@ function getSoundTypeName(f: SoundType): string { } } -async function updated(type: keyof typeof sounds.value, sound) { - const v: SoundStore = { +async function updated(type: keyof typeof sounds.value, sound: { type: SoundType; fileId?: string; fileUrl?: string; volume: number; }) { + const v: SoundStore = sound.type === '_driveFile_' ? { + type: sound.type, + fileId: sound.fileId!, + fileUrl: sound.fileUrl!, + volume: sound.volume, + } : { type: sound.type, - fileId: sound.fileId, - fileUrl: sound.fileUrl, volume: sound.volume, }; diff --git a/packages/frontend/src/pages/settings/theme.vue b/packages/frontend/src/pages/settings/theme.vue index 0129aebe94..46b537f866 100644 --- a/packages/frontend/src/pages/settings/theme.vue +++ b/packages/frontend/src/pages/settings/theme.vue @@ -306,7 +306,7 @@ function changeThemesSyncEnabled(value: boolean) { } } -function onThemeContextmenu(theme: Theme, ev: MouseEvent) { +function onThemeContextmenu(theme: Theme, ev: PointerEvent) { os.contextMenu([{ type: 'label', text: theme.name, diff --git a/packages/frontend/src/pages/tag.vue b/packages/frontend/src/pages/tag.vue index 047e68f583..1e268e64d2 100644 --- a/packages/frontend/src/pages/tag.vue +++ b/packages/frontend/src/pages/tag.vue @@ -20,6 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, markRaw, ref } from 'vue'; +import type { PageHeaderItem } from '@/types/page-header.js'; import MkNotesTimeline from '@/components/MkNotesTimeline.vue'; import MkButton from '@/components/MkButton.vue'; import { definePage } from '@/page.js'; @@ -50,10 +51,10 @@ async function post() { paginator.reload(); } -const headerActions = computed(() => [{ +const headerActions = computed<PageHeaderItem[]>(() => [{ icon: 'ti ti-dots', text: i18n.ts.more, - handler: (ev: MouseEvent) => { + handler: (ev) => { os.popupMenu([{ text: i18n.ts.embed, icon: 'ti ti-code', diff --git a/packages/frontend/src/pages/theme-editor.vue b/packages/frontend/src/pages/theme-editor.vue index af3891ac8e..2d2b8ed292 100644 --- a/packages/frontend/src/pages/theme-editor.vue +++ b/packages/frontend/src/pages/theme-editor.vue @@ -160,11 +160,11 @@ function setBgColor(color: typeof bgColors[number]) { } } -function setAccentColor(color) { +function setAccentColor(color: string) { theme.value.props.accent = color; } -function setFgColor(color) { +function setFgColor(color: typeof fgColors[number]) { theme.value.props.fg = theme.value.base === 'light' ? color.forLight : color.forDark; } diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue index 89d0991bc0..64c2b2eee3 100644 --- a/packages/frontend/src/pages/timeline.vue +++ b/packages/frontend/src/pages/timeline.vue @@ -31,6 +31,7 @@ import { computed, watch, provide, useTemplateRef, ref, onMounted, onActivated } import type { Tab } from '@/components/global/MkPageHeader.tabs.vue'; import type { MenuItem } from '@/types/menu.js'; import type { BasicTimelineType } from '@/timelines.js'; +import type { PageHeaderItem } from '@/types/page-header.js'; import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue'; import MkPostForm from '@/components/MkPostForm.vue'; import * as os from '@/os.js'; @@ -105,7 +106,7 @@ const withSensitive = computed<boolean>({ const showFixedPostForm = prefer.model('showFixedPostForm'); -async function chooseList(ev: MouseEvent): Promise<void> { +async function chooseList(ev: PointerEvent): Promise<void> { const lists = await userListsCache.fetch(); const items: (MenuItem | undefined)[] = [ ...lists.map(list => ({ @@ -124,7 +125,7 @@ async function chooseList(ev: MouseEvent): Promise<void> { os.popupMenu(items.filter(i => i != null), ev.currentTarget ?? ev.target); } -async function chooseAntenna(ev: MouseEvent): Promise<void> { +async function chooseAntenna(ev: PointerEvent): Promise<void> { const antennas = await antennasCache.fetch(); const items: (MenuItem | undefined)[] = [ ...antennas.map(antenna => ({ @@ -144,7 +145,7 @@ async function chooseAntenna(ev: MouseEvent): Promise<void> { os.popupMenu(items.filter(i => i != null), ev.currentTarget ?? ev.target); } -async function chooseChannel(ev: MouseEvent): Promise<void> { +async function chooseChannel(ev: PointerEvent): Promise<void> { const channels = await favoritedChannelsCache.fetch(); const items: (MenuItem | undefined)[] = [ ...channels.map(channel => { @@ -203,8 +204,8 @@ onActivated(() => { switchTlIfNeeded(); }); -const headerActions = computed(() => { - const items = [{ +const headerActions = computed<PageHeaderItem[]>(() => { + const items: PageHeaderItem[] = [{ icon: 'ti ti-dots', text: i18n.ts.options, handler: (ev) => { @@ -254,7 +255,7 @@ const headerActions = computed(() => { items.unshift({ icon: 'ti ti-refresh', text: i18n.ts.reload, - handler: (ev: Event) => { + handler: () => { tlComponent.value?.reloadTimeline(); }, }); diff --git a/packages/frontend/src/pages/user/activity.following.vue b/packages/frontend/src/pages/user/activity.following.vue index 4310c7ad85..f9a2eed6b9 100644 --- a/packages/frontend/src/pages/user/activity.following.vue +++ b/packages/frontend/src/pages/user/activity.following.vue @@ -57,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) => ({ x: getDate(i).getTime(), y: v, diff --git a/packages/frontend/src/pages/user/activity.notes.vue b/packages/frontend/src/pages/user/activity.notes.vue index 6d9c1bedd9..00bfe25430 100644 --- a/packages/frontend/src/pages/user/activity.notes.vue +++ b/packages/frontend/src/pages/user/activity.notes.vue @@ -57,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) => ({ x: getDate(i).getTime(), y: v, diff --git a/packages/frontend/src/pages/user/activity.pv.vue b/packages/frontend/src/pages/user/activity.pv.vue index 76df53becd..451f8ba0f7 100644 --- a/packages/frontend/src/pages/user/activity.pv.vue +++ b/packages/frontend/src/pages/user/activity.pv.vue @@ -57,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) => ({ x: getDate(i).getTime(), y: v, diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue index ee04ca9e4a..c472c4da7b 100644 --- a/packages/frontend/src/pages/user/home.vue +++ b/packages/frontend/src/pages/user/home.vue @@ -252,7 +252,7 @@ const age = computed(() => { return props.user.birthday ? calcAge(props.user.birthday) : NaN; }); -function menu(ev: MouseEvent) { +function menu(ev: PointerEvent) { const { menu, cleanup } = getUserMenu(user.value, router); os.popupMenu(menu, ev.currentTarget ?? ev.target).finally(cleanup); } diff --git a/packages/frontend/src/pages/user/index.activity.vue b/packages/frontend/src/pages/user/index.activity.vue index 210021618e..10b0582143 100644 --- a/packages/frontend/src/pages/user/index.activity.vue +++ b/packages/frontend/src/pages/user/index.activity.vue @@ -36,7 +36,7 @@ const props = withDefaults(defineProps<{ const chartSrc = ref<'per-user-notes' | 'per-user-pv'>('per-user-notes'); -function showMenu(ev: MouseEvent) { +function showMenu(ev: PointerEvent) { os.popupMenu([{ text: i18n.ts.notes, active: chartSrc.value === 'per-user-notes', diff --git a/packages/frontend/src/pages/welcome.setup.vue b/packages/frontend/src/pages/welcome.setup.vue index 393ba98d30..3a4a558605 100644 --- a/packages/frontend/src/pages/welcome.setup.vue +++ b/packages/frontend/src/pages/welcome.setup.vue @@ -89,7 +89,7 @@ SPDX-License-Identifier: AGPL-3.0-only <Suspense> <template #default> - <MkServerSetupWizard :token="token" @finished="onWizardFinished"/> + <MkServerSetupWizard :token="token!" @finished="onWizardFinished"/> </template> <template #fallback> <MkLoading/> @@ -124,8 +124,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { computed, ref } from 'vue'; -import * as Misskey from 'misskey-js'; +import { ref } from 'vue'; import { host, version } from '@@/js/config.js'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; @@ -143,7 +142,7 @@ const accountCreating = ref(false); const accountCreated = ref(false); const step = ref(0); -let token; +let token: string | null = null; function createAccount() { if (accountCreating.value) return; @@ -191,6 +190,7 @@ function skipSettings() { } function finish() { + if (token == null) return; login(token); } </script> diff --git a/packages/frontend/src/pref-migrate.ts b/packages/frontend/src/pref-migrate.ts index 8258bbb846..7470271627 100644 --- a/packages/frontend/src/pref-migrate.ts +++ b/packages/frontend/src/pref-migrate.ts @@ -5,7 +5,7 @@ import type { DeckProfile } from '@/deck.js'; import { genId } from '@/utility/id.js'; -import { ColdDeviceStorage, store } from '@/store.js'; +import { store } from '@/store.js'; import { prefer } from '@/preferences.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { deckStore } from '@/ui/deck/deck-store.js'; @@ -24,16 +24,6 @@ export function migrateOldSettings() { } }); - const plugins = ColdDeviceStorage.get('plugins'); - prefer.commit('plugins', plugins.map(p => { - const { id, ...rest } = p; - return { - ...rest, - config: rest.config ?? {}, - installId: id, - }; - })); - prefer.commit('deck.profile', deckStore.s.profile); misskeyApi('i/registry/keys', { scope: ['client', 'deck', 'profiles'], @@ -54,9 +44,6 @@ export function migrateOldSettings() { prefer.commit('deck.profiles', profiles); }); - prefer.commit('lightTheme', ColdDeviceStorage.get('lightTheme')); - prefer.commit('darkTheme', ColdDeviceStorage.get('darkTheme')); - prefer.commit('syncDeviceDarkMode', ColdDeviceStorage.get('syncDeviceDarkMode')); prefer.commit('emojiPalettes', [{ id: 'reactions', name: '', diff --git a/packages/frontend/src/preferences/manager.ts b/packages/frontend/src/preferences/manager.ts index 58d8861cef..7f3949f81b 100644 --- a/packages/frontend/src/preferences/manager.ts +++ b/packages/frontend/src/preferences/manager.ts @@ -81,7 +81,7 @@ export type PreferencesProfile = { }; export type PossiblyNonNormalizedPreferencesProfile = Omit<PreferencesProfile, 'preferences'> & { - preferences: Record<string, any>; + preferences: Record<string, [scope: Scope, value: any, meta: ValueMeta][]>; }; export type StorageProvider = { @@ -112,17 +112,17 @@ type PreferencesManagerEvents = { export function definePreferences<T extends Record<string, unknown>>(x: { [K in keyof T]: PreferencesDefinitionRecord<T[K]> }): { - [K in keyof T]: PreferencesDefinitionRecord<T[K]> - } { + [K in keyof T]: PreferencesDefinitionRecord<T[K]> +} { return x; } export function getInitialPrefValue<K extends keyof PREF>(k: K): ValueOf<K> { - const _default = PREF_DEF[k as string].default; + const _default = PREF_DEF[k].default; if (typeof _default === 'function') { // factory - return _default(); + return _default() as ValueOf<K>; } else { - return _default; + return _default as unknown as ValueOf<K>; } } @@ -146,7 +146,7 @@ function createEmptyProfile(): PossiblyNonNormalizedPreferencesProfile { } function normalizePreferences(preferences: PossiblyNonNormalizedPreferencesProfile['preferences'], account: { id: string } | null): PreferencesProfile['preferences'] { - const data = {} as PreferencesProfile['preferences']; + const data = {} as Record<string, [scope: Scope, value: any, meta: ValueMeta][]>; for (const key in PREF_DEF) { const records = preferences[key]; if (records == null || records.length === 0) { @@ -183,7 +183,7 @@ function normalizePreferences(preferences: PossiblyNonNormalizedPreferencesProfi } } - return data; + return data as PreferencesProfile['preferences']; } // TODO: PreferencesManagerForGuest のような非ログイン専用のクラスを分離すればthis.currentAccountのnullチェックやaccountがnullであるスコープのレコード挿入などが不要になり綺麗になるかもしれない @@ -223,9 +223,10 @@ export class PreferencesManager extends EventEmitter<PreferencesManagerEvents> { const states = this.genStates(); + // apply states for (const key in states) { - this.s[key] = states[key]; - this.r[key] = ref(this.s[key]); + (this.s[key as keyof PREF] as any) = states[key as keyof PREF]; + (this.r[key as keyof PREF] as Ref<any>) = ref(this.s[key as keyof PREF]); } // normalizeの結果変わっていたら保存 diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index fb9349c42f..44d8ed7293 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -478,89 +478,3 @@ interface Watcher { key: string; callback: (value: unknown) => void; } - -// TODO: 消す(preferに移行済みのため) -/** - * 常にメモリにロードしておく必要がないような設定情報を保管するストレージ(非リアクティブ) - */ -export class ColdDeviceStorage { - public static default = { - lightTheme, // TODO: 消す(preferに移行済みのため) - darkTheme, // TODO: 消す(preferに移行済みのため) - syncDeviceDarkMode: true, // TODO: 消す(preferに移行済みのため) - plugins: [] as (Omit<Plugin, 'installId'> & { id: string })[], // TODO: 消す(preferに移行済みのため) - }; - - public static watchers: Watcher[] = []; - - public static get<T extends keyof typeof ColdDeviceStorage.default>(key: T): typeof ColdDeviceStorage.default[T] { - // TODO: indexedDBにする - // ただしその際はnullチェックではなくキー存在チェックにしないとダメ - // (indexedDBはnullを保存できるため、ユーザーが意図してnullを格納した可能性がある) - const value = miLocalStorage.getItem(`${PREFIX}${key}`); - if (value == null) { - return ColdDeviceStorage.default[key]; - } else { - return JSON.parse(value); - } - } - - public static getAll(): Partial<typeof this.default> { - return (Object.keys(this.default) as (keyof typeof this.default)[]).reduce<Partial<typeof this.default>>((acc, key) => { - const value = localStorage.getItem(PREFIX + key); - if (value != null) { - acc[key] = JSON.parse(value); - } - return acc; - }, {}); - } - - public static set<T extends keyof typeof ColdDeviceStorage.default>(key: T, value: typeof ColdDeviceStorage.default[T]): void { - // 呼び出し側のバグ等で undefined が来ることがある - // undefined を文字列として miLocalStorage に入れると参照する際の JSON.parse でコケて不具合の元になるため無視 - - if (value === undefined) { - console.error(`attempt to store undefined value for key '${key}'`); - return; - } - - miLocalStorage.setItem(`${PREFIX}${key}`, JSON.stringify(value)); - - for (const watcher of this.watchers) { - if (watcher.key === key) watcher.callback(value); - } - } - - public static watch(key, callback) { - this.watchers.push({ key, callback }); - } - - // TODO: VueのcustomRef使うと良い感じになるかも - public static ref<T extends keyof typeof ColdDeviceStorage.default>(key: T) { - const v = ColdDeviceStorage.get(key); - const r = ref(v); - // TODO: このままではwatcherがリークするので開放する方法を考える - this.watch(key, v => { - r.value = v; - }); - return r; - } - - /** - * 特定のキーの、簡易的なgetter/setterを作ります - * 主にvue場で設定コントロールのmodelとして使う用 - */ - public static makeGetterSetter<K extends keyof typeof ColdDeviceStorage.default>(key: K) { - // TODO: VueのcustomRef使うと良い感じになるかも - const valueRef = ColdDeviceStorage.ref(key); - return { - get: () => { - return valueRef.value; - }, - set: (value: typeof ColdDeviceStorage.default[K]) => { - const val = value; - ColdDeviceStorage.set(key, val); - }, - }; - } -} diff --git a/packages/frontend/src/theme.ts b/packages/frontend/src/theme.ts index a7012a7586..af47402bd6 100644 --- a/packages/frontend/src/theme.ts +++ b/packages/frontend/src/theme.ts @@ -204,7 +204,7 @@ export function compile(theme: Theme): Record<string, string> { return tinycolor(val); } - const props = {}; + const props = {} as Record<string, string>; for (const [k, v] of Object.entries(theme.props)) { if (k.startsWith('$')) continue; // ignore const @@ -247,12 +247,12 @@ export function parseThemeCode(code: string): Theme { export function previewTheme(code: string): void { const theme = parseThemeCode(code); - if (theme) applyTheme(theme, false); + if (theme != null) applyTheme(theme, false); } export async function installTheme(code: string): Promise<void> { const theme = parseThemeCode(code); - if (!theme) return; + if (theme == null) return; await addTheme(theme); } diff --git a/packages/frontend/src/tips.ts b/packages/frontend/src/tips.ts index 6ee7130ee9..d5fc047c2b 100644 --- a/packages/frontend/src/tips.ts +++ b/packages/frontend/src/tips.ts @@ -32,7 +32,7 @@ export function resetAllTips() { } export function hideAllTips() { - const v = {}; + const v = {} as Record<typeof TIPS[number], boolean>; for (const k of TIPS) { v[k] = true; } diff --git a/packages/frontend/src/types/menu.ts b/packages/frontend/src/types/menu.ts index e0bf135ef8..b1e271e245 100644 --- a/packages/frontend/src/types/menu.ts +++ b/packages/frontend/src/types/menu.ts @@ -13,7 +13,7 @@ type MenuRadioOptionsDef = Record<string, any>; type Text = string | ComputedRef<string>; -export type MenuAction = (ev: MouseEvent) => void; +export type MenuAction = (ev: PointerEvent) => void; export interface MenuButton { type?: 'button'; diff --git a/packages/frontend/src/types/page-header.ts b/packages/frontend/src/types/page-header.ts index 7232f17d47..fcfe9d284c 100644 --- a/packages/frontend/src/types/page-header.ts +++ b/packages/frontend/src/types/page-header.ts @@ -7,5 +7,6 @@ export type PageHeaderItem = { text?: string; icon: string; highlighted?: boolean; - handler: (ev: MouseEvent) => void; + danger?: boolean; + handler: (ev: PointerEvent) => void; }; diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts index a9ad36c97a..7ad18fc2a8 100644 --- a/packages/frontend/src/ui/_common_/common.ts +++ b/packages/frontend/src/ui/_common_/common.ts @@ -50,7 +50,7 @@ function toolsMenuItems(): MenuItem[] { return items; } -export function openInstanceMenu(ev: MouseEvent) { +export function openInstanceMenu(ev: PointerEvent) { const menuItems: MenuItem[] = []; menuItems.push({ @@ -175,7 +175,7 @@ export function openInstanceMenu(ev: MouseEvent) { }); } -export function openToolsMenu(ev: MouseEvent) { +export function openToolsMenu(ev: PointerEvent) { os.popupMenu(toolsMenuItems(), ev.currentTarget ?? ev.target, { align: 'left', }); diff --git a/packages/frontend/src/ui/_common_/navbar-h.vue b/packages/frontend/src/ui/_common_/navbar-h.vue index 594c398d8b..eb1d99f0aa 100644 --- a/packages/frontend/src/ui/_common_/navbar-h.vue +++ b/packages/frontend/src/ui/_common_/navbar-h.vue @@ -10,18 +10,18 @@ SPDX-License-Identifier: AGPL-3.0-only <button v-click-anime :class="[$style.item, $style.instance]" class="_button" @click="openInstanceMenu"> <img :class="$style.instanceIcon" :src="instance.iconUrl ?? '/favicon.ico'" draggable="false"/> </button> - <MkA v-click-anime v-tooltip="i18n.ts.timeline" :class="$style.item" activeClass="active" to="/" exact> + <MkA v-click-anime v-tooltip="i18n.ts.timeline" :class="$style.item" :activeClass="$style.active" to="/" exact> <i :class="$style.itemIcon" class="ti ti-home ti-fw"></i> </MkA> <template v-for="item in menu"> <div v-if="item === '-'" :class="$style.divider"></div> - <component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime v-tooltip="navbarItemDef[item].title" class="_button" :class="$style.item" activeClass="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}"> + <component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show == null || navbarItemDef[item].show.value !== false)" v-click-anime v-tooltip="navbarItemDef[item].title" class="_button" :class="$style.item" :activeClass="$style.active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}"> <i :class="[$style.itemIcon, navbarItemDef[item].icon]" class="ti-fw"></i> <span v-if="navbarItemDef[item].indicated" :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span> </component> </template> <div :class="$style.divider"></div> - <MkA v-if="$i && ($i.isAdmin || $i.isModerator)" v-click-anime v-tooltip="i18n.ts.controlPanel" class="item" activeClass="active" to="/admin" :behavior="settingsWindowed ? 'window' : null"> + <MkA v-if="$i && ($i.isAdmin || $i.isModerator)" v-click-anime v-tooltip="i18n.ts.controlPanel" class="item" :activeClass="$style.active" to="/admin" :behavior="settingsWindowed ? 'window' : null"> <i :class="$style.itemIcon" class="ti ti-dashboard ti-fw"></i> </MkA> <button v-click-anime :class="$style.item" class="_button" @click="more"> @@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only </button> </div> <div :class="$style.right"> - <MkA v-click-anime v-tooltip="i18n.ts.settings" :class="$style.item" activeClass="active" to="/settings" :behavior="settingsWindowed ? 'window' : null"> + <MkA v-click-anime v-tooltip="i18n.ts.settings" :class="$style.item" :activeClass="$style.active" to="/settings" :behavior="settingsWindowed ? 'window' : null"> <i :class="$style.itemIcon" class="ti ti-settings ti-fw"></i> </MkA> <button v-if="$i" v-click-anime :class="[$style.item, $style.account]" class="_button" @click="openAccountMenu"> @@ -76,7 +76,7 @@ const otherNavItemIndicated = computed<boolean>(() => { return false; }); -async function more(ev: MouseEvent) { +async function more(ev: PointerEvent) { const target = getHTMLElementOrNull(ev.currentTarget ?? ev.target); if (!target) return; @@ -88,7 +88,7 @@ async function more(ev: MouseEvent) { }); } -async function openAccountMenu(ev: MouseEvent) { +async function openAccountMenu(ev: PointerEvent) { const menuItems = await getAccountMenu({ withExtraOperation: true, }); diff --git a/packages/frontend/src/ui/_common_/navbar.vue b/packages/frontend/src/ui/_common_/navbar.vue index ee76412476..10cbd22809 100644 --- a/packages/frontend/src/ui/_common_/navbar.vue +++ b/packages/frontend/src/ui/_common_/navbar.vue @@ -26,10 +26,10 @@ SPDX-License-Identifier: AGPL-3.0-only <div v-if="item === '-'" :class="$style.divider"></div> <component :is="navbarItemDef[item].to ? 'MkA' : 'button'" - v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" + v-else-if="navbarItemDef[item] && (navbarItemDef[item].show == null || navbarItemDef[item].show.value !== false)" v-tooltip.noDelay.right="navbarItemDef[item].title" class="_button" - :class="[$style.item, { [$style.active]: navbarItemDef[item].active }]" + :class="[$style.item]" :activeClass="$style.active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}" @@ -161,7 +161,7 @@ function toggleIconOnly() { } } -function toggleRealtimeMode(ev: MouseEvent) { +function toggleRealtimeMode(ev: PointerEvent) { os.popupMenu([{ type: 'label', text: i18n.ts.realtimeMode, @@ -175,7 +175,7 @@ function toggleRealtimeMode(ev: MouseEvent) { }], ev.currentTarget ?? ev.target); } -async function openAccountMenu(ev: MouseEvent) { +async function openAccountMenu(ev: PointerEvent) { const menuItems = await getAccountMenu({ withExtraOperation: true, }); @@ -183,7 +183,7 @@ async function openAccountMenu(ev: MouseEvent) { os.popupMenu(menuItems, ev.currentTarget ?? ev.target); } -async function more(ev: MouseEvent) { +async function more(ev: PointerEvent) { const target = getHTMLElementOrNull(ev.currentTarget ?? ev.target); if (!target) return; const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkLaunchPad.vue').then(x => x.default), { diff --git a/packages/frontend/src/ui/_common_/statusbar-federation.vue b/packages/frontend/src/ui/_common_/statusbar-federation.vue index 079f1f92bb..23093e60d7 100644 --- a/packages/frontend/src/ui/_common_/statusbar-federation.vue +++ b/packages/frontend/src/ui/_common_/statusbar-federation.vue @@ -67,7 +67,7 @@ useInterval(tick, Math.max(5000, props.refreshIntervalSec * 1000), { afterMounted: true, }); -function getInstanceIcon(instance): string { +function getInstanceIcon(instance: Misskey.entities.FederationInstance): string { return getProxiedImageUrlNullable(instance.iconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.faviconUrl, 'preview') ?? '/client-assets/dummy.png'; } </script> diff --git a/packages/frontend/src/ui/_common_/widgets.vue b/packages/frontend/src/ui/_common_/widgets.vue index ed084af6ee..4087c4d517 100644 --- a/packages/frontend/src/ui/_common_/widgets.vue +++ b/packages/frontend/src/ui/_common_/widgets.vue @@ -5,7 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <div> - <XWidgets :edit="editMode" :widgets="widgets" @addWidget="addWidget" @removeWidget="removeWidget" @updateWidget="updateWidget" @updateWidgets="updateWidgets" @exit="editMode = false"/> + <XWidgets + :edit="editMode" + :widgets="widgets" + @addWidget="addWidget" + @removeWidget="removeWidget" + @updateWidget="updateWidget" + @updateWidgets="updateWidgets" + @exit="editMode = false" + /> <button v-if="editMode" class="_textButton" style="font-size: 0.9em;" @click="editMode = false"><i class="ti ti-check"></i> {{ i18n.ts.editWidgetsExit }}</button> <button v-else class="_textButton" data-cy-widget-edit :class="$style.edit" style="font-size: 0.9em; margin-top: 16px;" @click="editMode = true"><i class="ti ti-pencil"></i> {{ i18n.ts.editWidgets }}</button> @@ -16,7 +24,9 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, ref } from 'vue'; const editMode = ref(false); </script> + <script lang="ts" setup> +import type { DefaultStoredWidget, Widget } from '@/components/MkWidgets.vue'; import XWidgets from '@/components/MkWidgets.vue'; import { i18n } from '@/i18n.js'; import { prefer } from '@/preferences.js'; @@ -36,30 +46,31 @@ const widgets = computed(() => { return prefer.r.widgets.value.filter(w => w.place !== 'left'); }); -function addWidget(widget) { +function addWidget(widget: Widget) { prefer.commit('widgets', [{ ...widget, place: props.place, }, ...prefer.s.widgets]); } -function removeWidget(widget) { +function removeWidget(widget: Widget) { prefer.commit('widgets', prefer.s.widgets.filter(w => w.id !== widget.id)); } -function updateWidget({ id, data }) { - prefer.commit('widgets', prefer.s.widgets.map(w => w.id === id ? { +function updateWidget(widget: { id: Widget['id']; data: Widget['data']; }) { + prefer.commit('widgets', prefer.s.widgets.map(w => w.id === widget.id ? { ...w, - data, + data: widget.data, place: props.place, } : w)); } -function updateWidgets(thisWidgets) { +function updateWidgets(thisWidgets: Widget[]) { if (props.place === null) { - prefer.commit('widgets', thisWidgets); + prefer.commit('widgets', thisWidgets as DefaultStoredWidget[]); return; } + if (props.place === 'left') { prefer.commit('widgets', [ ...thisWidgets.map(w => ({ ...w, place: 'left' })), @@ -67,6 +78,7 @@ function updateWidgets(thisWidgets) { ]); return; } + prefer.commit('widgets', [ ...prefer.s.widgets.filter(w => w.place === 'left' && !thisWidgets.some(t => w.id === t.id)), ...thisWidgets.map(w => ({ ...w, place: 'right' })), diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue index fabff28eb6..0bafa1074c 100644 --- a/packages/frontend/src/ui/deck.vue +++ b/packages/frontend/src/ui/deck.vue @@ -174,7 +174,7 @@ const addColumnButtonEl = useTemplateRef('addColumnButtonEl'); const settingsButtonEl = useTemplateRef('settingsButtonEl'); const swicthProfileButtonEl = useTemplateRef('swicthProfileButtonEl'); -const addColumn = async (ev) => { +async function addColumn(ev: PointerEvent) { const { canceled, result: column } = await os.select({ title: i18n.ts._deck.addColumn, items: columnTypes.filter(column => column !== 'chat' || $i == null || $i.policies.chatAvailability !== 'unavailable').map(column => ({ @@ -190,14 +190,14 @@ const addColumn = async (ev) => { width: 330, soundSetting: { type: null, volume: 1 }, }); -}; +} -const onContextmenu = (ev) => { +function onContextmenu(ev: PointerEvent) { os.contextMenu([{ text: i18n.ts._deck.addColumn, action: addColumn, }], ev); -}; +} // タッチでスクロールしてるときはスナップスクロールを有効にする function pointerEvent(ev: PointerEvent) { diff --git a/packages/frontend/src/ui/deck/column.vue b/packages/frontend/src/ui/deck/column.vue index 312ca51c83..410faf11f9 100644 --- a/packages/frontend/src/ui/deck/column.vue +++ b/packages/frontend/src/ui/deck/column.vue @@ -247,15 +247,15 @@ function getMenu() { return menuItems; } -function showSettingsMenu(ev: MouseEvent) { +function showSettingsMenu(ev: PointerEvent) { os.popupMenu(getMenu(), ev.currentTarget ?? ev.target); } -function onContextmenu(ev: MouseEvent) { +function onContextmenu(ev: PointerEvent) { os.contextMenu(getMenu(), ev); } -function goTop(ev: MouseEvent) { +function goTop(ev: PointerEvent) { emit('headerClick', ev); if (!props.handleScrollToTop) return; @@ -267,7 +267,9 @@ function goTop(ev: MouseEvent) { } } -function onDragstart(ev) { +function onDragstart(ev: DragEvent) { + if (ev.dataTransfer == null) return; + ev.dataTransfer.effectAllowed = 'move'; setDragData(ev, 'deckColumn', props.column.id); @@ -278,11 +280,13 @@ function onDragstart(ev) { }, 10); } -function onDragend(ev) { +function onDragend(ev: DragEvent) { dragging.value = false; } -function onDragover(ev) { +function onDragover(ev: DragEvent) { + if (ev.dataTransfer == null) return; + // 自分自身がドラッグされている場合 if (dragging.value) { // 自分自身にはドロップさせない @@ -300,7 +304,7 @@ function onDragleave() { draghover.value = false; } -function onDrop(ev) { +function onDrop(ev: DragEvent) { draghover.value = false; os.deckGlobalEvents.emit('column.dragEnd'); diff --git a/packages/frontend/src/ui/deck/main-column.vue b/packages/frontend/src/ui/deck/main-column.vue index 1388cbdc18..8757ec0941 100644 --- a/packages/frontend/src/ui/deck/main-column.vue +++ b/packages/frontend/src/ui/deck/main-column.vue @@ -58,11 +58,11 @@ function back() { history.back(); } */ -function onContextmenu(ev: MouseEvent) { +function onContextmenu(ev: PointerEvent) { if (!ev.target) return; if (isLink(ev.target as HTMLElement)) return; - if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes((ev.target as HTMLElement).tagName) || (ev.target as HTMLElement).attributes['contenteditable']) return; + if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes((ev.target as HTMLElement).tagName) || (ev.target as HTMLElement).attributes.getNamedItem('contenteditable') != null) return; if (window.getSelection()?.toString() !== '') return; const path = mainRouter.currentRoute.value.path; os.contextMenu([{ diff --git a/packages/frontend/src/ui/deck/widgets-column.vue b/packages/frontend/src/ui/deck/widgets-column.vue index 4e84ef0ba0..0985e95653 100644 --- a/packages/frontend/src/ui/deck/widgets-column.vue +++ b/packages/frontend/src/ui/deck/widgets-column.vue @@ -17,8 +17,9 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref } from 'vue'; import XColumn from './column.vue'; -import { addColumnWidget, removeColumnWidget, setColumnWidgets, updateColumnWidget } from '@/deck.js'; import type { Column } from '@/deck.js'; +import type { Widget } from '@/components/MkWidgets.vue'; +import { addColumnWidget, removeColumnWidget, setColumnWidgets, updateColumnWidget } from '@/deck.js'; import XWidgets from '@/components/MkWidgets.vue'; import { i18n } from '@/i18n.js'; @@ -29,19 +30,19 @@ const props = defineProps<{ const edit = ref(false); -function addWidget(widget) { +function addWidget(widget: Widget) { addColumnWidget(props.column.id, widget); } -function removeWidget(widget) { +function removeWidget(widget: Widget) { removeColumnWidget(props.column.id, widget); } -function updateWidget({ id, data }) { - updateColumnWidget(props.column.id, id, data); +function updateWidget(widget: { id: Widget['id']; data: Widget['data']; }) { + updateColumnWidget(props.column.id, widget.id, widget.data); } -function updateWidgets(widgets) { +function updateWidgets(widgets: Widget[]) { setColumnWidgets(props.column.id, widgets); } diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue index 497ef72d04..95582edea1 100644 --- a/packages/frontend/src/ui/universal.vue +++ b/packages/frontend/src/ui/universal.vue @@ -103,9 +103,9 @@ if (window.innerWidth > 1024) { } } -const onContextmenu = (ev) => { - if (isLink(ev.target)) return; - if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return; +function onContextmenu(ev: PointerEvent) { + if (isLink(ev.target as HTMLElement)) return; + if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes((ev.target as HTMLElement).tagName) || (ev.target as HTMLElement).attributes.getNamedItem('contenteditable') != null) return; if (window.getSelection()?.toString() !== '') return; const path = mainRouter.getCurrentFullPath(); os.contextMenu([{ @@ -118,7 +118,7 @@ const onContextmenu = (ev) => { os.pageWindow(path); }, }], ev); -}; +} </script> <style lang="scss" module> diff --git a/packages/frontend/src/utility/admin-lookup.ts b/packages/frontend/src/utility/admin-lookup.ts index f393fd4ae1..74485a11d7 100644 --- a/packages/frontend/src/utility/admin-lookup.ts +++ b/packages/frontend/src/utility/admin-lookup.ts @@ -14,7 +14,7 @@ export async function lookupUser() { }); if (canceled || result == null) return; - const show = (user) => { + const show = (user: Misskey.entities.UserDetailed) => { os.pageWindow(`/admin/user/${user.id}`); }; @@ -71,12 +71,8 @@ export async function lookupFile() { }); if (canceled) return; - const show = (file) => { - os.pageWindow(`/admin/file/${file.id}`); - }; - misskeyApi('admin/drive/show-file', q.startsWith('http://') || q.startsWith('https://') ? { url: q.trim() } : { fileId: q.trim() }).then(file => { - show(file); + os.pageWindow(`/admin/file/${file.id}`); }).catch(err => { if (err.code === 'NO_SUCH_FILE') { os.alert({ diff --git a/packages/frontend/src/utility/autocomplete.ts b/packages/frontend/src/utility/autocomplete.ts index c0b1865617..4eb1e29d07 100644 --- a/packages/frontend/src/utility/autocomplete.ts +++ b/packages/frontend/src/utility/autocomplete.ts @@ -194,7 +194,7 @@ export class Autocomplete { this.currentType = type; //#region サジェストを表示すべき位置を計算 - const caretPosition = getCaretCoordinates(this.textarea, this.textarea.selectionStart); + const caretPosition = getCaretCoordinates(this.textarea, this.textarea.selectionStart ?? 0); const rect = this.textarea.getBoundingClientRect(); diff --git a/packages/frontend/src/utility/chart-vline.ts b/packages/frontend/src/utility/chart-vline.ts index 2fe4bdb83b..1097c66d0e 100644 --- a/packages/frontend/src/utility/chart-vline.ts +++ b/packages/frontend/src/utility/chart-vline.ts @@ -11,7 +11,7 @@ export const chartVLine = (vLineColor: string) => ({ const tooltip = chart.tooltip as any; if (tooltip?._active?.length) { const ctx = chart.ctx; - const xs = tooltip._active.map(a => a.element.x); + const xs = tooltip._active.map((a: any) => a.element.x) as number[]; const x = xs.reduce((a, b) => a + b, 0) / xs.length; const topY = chart.scales.y.top; const bottomY = chart.scales.y.bottom; diff --git a/packages/frontend/src/utility/collect-page-vars.ts b/packages/frontend/src/utility/collect-page-vars.ts deleted file mode 100644 index 5096c0669e..0000000000 --- a/packages/frontend/src/utility/collect-page-vars.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -interface StringPageVar { - name: string, - type: 'string', - value: string -} - -interface NumberPageVar { - name: string, - type: 'number', - value: number -} - -interface BooleanPageVar { - name: string, - type: 'boolean', - value: boolean -} - -type PageVar = StringPageVar | NumberPageVar | BooleanPageVar; - -export function collectPageVars(content): PageVar[] { - const pageVars: PageVar[] = []; - const collect = (xs: any[]): void => { - for (const x of xs) { - if (x.type === 'textInput') { - pageVars.push({ - name: x.name, - type: 'string', - value: x.default || '', - }); - } else if (x.type === 'textareaInput') { - pageVars.push({ - name: x.name, - type: 'string', - value: x.default || '', - }); - } else if (x.type === 'numberInput') { - pageVars.push({ - name: x.name, - type: 'number', - value: x.default || 0, - }); - } else if (x.type === 'switch') { - pageVars.push({ - name: x.name, - type: 'boolean', - value: x.default || false, - }); - } else if (x.type === 'counter') { - pageVars.push({ - name: x.name, - type: 'number', - value: 0, - }); - } else if (x.type === 'radioButton') { - pageVars.push({ - name: x.name, - type: 'string', - value: x.default || '', - }); - } else if (x.children) { - collect(x.children); - } - } - }; - collect(content); - return pageVars; -} diff --git a/packages/frontend/src/utility/deep-equal.ts b/packages/frontend/src/utility/deep-equal.ts index 2859641dc7..ac2c2e68da 100644 --- a/packages/frontend/src/utility/deep-equal.ts +++ b/packages/frontend/src/utility/deep-equal.ts @@ -31,7 +31,7 @@ export function deepEqual(a: JsonLike, b: JsonLike): boolean { if (aks.length !== bks.length) return false; for (let i = 0; i < aks.length; i++) { const k = aks[i]; - if (!deepEqual(a[k], (b as { [key: string]: JsonLike })[k])) return false; + if (!deepEqual((a as { [key: string]: JsonLike })[k], (b as { [key: string]: JsonLike })[k])) return false; } return true; } diff --git a/packages/frontend/src/utility/contains.ts b/packages/frontend/src/utility/element-contains.ts index 6137c06e85..8389d49278 100644 --- a/packages/frontend/src/utility/contains.ts +++ b/packages/frontend/src/utility/element-contains.ts @@ -3,7 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -export default (parent, child, checkSame = true) => { +export function elementContains(parent: Element | null, child: Element | null, checkSame = true) { + if (parent === null || child === null) return false; if (checkSame && parent === child) return true; let node = child.parentNode; while (node) { @@ -11,4 +12,4 @@ export default (parent, child, checkSame = true) => { node = node.parentNode; } return false; -}; +} diff --git a/packages/frontend/src/utility/form.ts b/packages/frontend/src/utility/form.ts index fe89333285..a86ae1d1c4 100644 --- a/packages/frontend/src/utility/form.ts +++ b/packages/frontend/src/utility/form.ts @@ -84,7 +84,7 @@ export interface ArrayFormItem extends FormItemBase { export interface ButtonFormItem extends FormItemBase { type: 'button'; content?: string; - action: (ev: MouseEvent, v: any) => void; + action: (ev: PointerEvent, v: any) => void; } export interface DriveFileFormItem extends FormItemBase { @@ -126,23 +126,23 @@ type NonNullableIfRequired<T, Item extends FormItem> = type GetItemType<Item extends FormItem> = Item extends StringFormItem ? NonNullableIfRequired<InferDefault<Item, string>, Item> - : Item extends NumberFormItem - ? NonNullableIfRequired<InferDefault<Item, number>, Item> - : Item extends BooleanFormItem - ? boolean - : Item extends RadioFormItem - ? GetRadioItemType<Item> - : Item extends RangeFormItem - ? NonNullableIfRequired<InferDefault<Item, number>, Item> - : Item extends EnumFormItem - ? GetEnumItemType<Item> - : Item extends ArrayFormItem - ? NonNullableIfRequired<InferDefault<Item, unknown[]>, Item> - : Item extends ObjectFormItem - ? NonNullableIfRequired<InferDefault<Item, Record<string, unknown>>, Item> - : Item extends DriveFileFormItem - ? Misskey.entities.DriveFile | undefined - : never; + : Item extends NumberFormItem + ? NonNullableIfRequired<InferDefault<Item, number>, Item> + : Item extends BooleanFormItem + ? boolean + : Item extends RadioFormItem + ? GetRadioItemType<Item> + : Item extends RangeFormItem + ? NonNullableIfRequired<InferDefault<Item, number>, Item> + : Item extends EnumFormItem + ? GetEnumItemType<Item> + : Item extends ArrayFormItem + ? NonNullableIfRequired<InferDefault<Item, unknown[]>, Item> + : Item extends ObjectFormItem + ? NonNullableIfRequired<InferDefault<Item, Record<string, unknown>>, Item> + : Item extends DriveFileFormItem + ? Misskey.entities.DriveFile | undefined + : never; export type GetFormResultType<F extends Form> = { [P in keyof F]: GetItemType<F[P]>; diff --git a/packages/frontend/src/utility/get-embed-code.ts b/packages/frontend/src/utility/get-embed-code.ts index 5ccd46cfe2..5817d7ece8 100644 --- a/packages/frontend/src/utility/get-embed-code.ts +++ b/packages/frontend/src/utility/get-embed-code.ts @@ -3,10 +3,10 @@ * SPDX-License-Identifier: AGPL-3.0-only */ import { defineAsyncComponent } from 'vue'; -import { genId } from '@/utility/id.js'; import { url } from '@@/js/config.js'; import { defaultEmbedParams, embedRouteWithScrollbar } from '@@/js/embed-page.js'; import type { EmbedParams, EmbeddableEntity } from '@@/js/embed-page.js'; +import { genId } from '@/utility/id.js'; import * as os from '@/os.js'; import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; @@ -21,19 +21,20 @@ export function normalizeEmbedParams(params: EmbedParams): Record<string, string // paramsのvalueをすべてstringに変換。undefinedやnullはプロパティごと消す const normalizedParams: Record<string, string> = {}; for (const key in params) { + const k = key as keyof EmbedParams; // デフォルトの値と同じならparamsに含めない - if (params[key] == null || params[key] === defaultEmbedParams[key]) { + if (params[k] == null || params[k] === defaultEmbedParams[k]) { continue; } - switch (typeof params[key]) { + switch (typeof params[k]) { case 'number': - normalizedParams[key] = params[key].toString(); + normalizedParams[k] = params[k].toString(); break; case 'boolean': - normalizedParams[key] = params[key] ? 'true' : 'false'; + normalizedParams[k] = params[k] ? 'true' : 'false'; break; default: - normalizedParams[key] = params[key]; + normalizedParams[k] = params[k]; break; } } diff --git a/packages/frontend/src/utility/get-user-environment.ts b/packages/frontend/src/utility/get-user-environment.ts index 3b8d43fb2c..ebae0492b1 100644 --- a/packages/frontend/src/utility/get-user-environment.ts +++ b/packages/frontend/src/utility/get-user-environment.ts @@ -39,7 +39,7 @@ export async function getUserEnvironment(): Promise<UserEnvironment> { } } - const browserData = uaData.fullVersionList.find((item) => !/^\s*not.+a.+brand\s*$/i.test(item.brand)); + const browserData = uaData.fullVersionList.find((item: any) => !/^\s*not.+a.+brand\s*$/i.test(item.brand)); return { os: `${uaData.platform} ${osVersion}`, browser: browserData ? `${browserData.brand} v${browserData.version}` : 'Unknown', diff --git a/packages/frontend/src/utility/snowfall-effect.ts b/packages/frontend/src/utility/snowfall-effect.ts index cefa720ebf..aa86db6bd1 100644 --- a/packages/frontend/src/utility/snowfall-effect.ts +++ b/packages/frontend/src/utility/snowfall-effect.ts @@ -21,7 +21,7 @@ export class SnowfallEffect { }>; private uniforms: Record<string, { type: string; - value: number[] | Float32Array; + value: number | number[] | Float32Array; location: WebGLUniformLocation; }>; private texture: WebGLTexture; @@ -44,9 +44,9 @@ export class SnowfallEffect { start: number; previous: number; } = { - start: 0, - previous: 0, - }; + start: 0, + previous: 0, + }; private raf = 0; private density: number = 1 / 90; @@ -90,7 +90,7 @@ export class SnowfallEffect { mat2: 'uniformMatrix2fv', mat3: 'uniformMatrix3fv', mat4: 'uniformMatrix4fv', - }; + } as const; private CAMERA = { fov: 60, @@ -167,7 +167,7 @@ export class SnowfallEffect { return { ...this.WIND }; } - private initShader(type, source): WebGLShader { + private initShader(type: number, source: string): WebGLShader { const { gl } = this; const shader = gl.createShader(type); if (shader == null) throw new Error('Failed to create shader'); @@ -224,7 +224,7 @@ export class SnowfallEffect { } } - private setBuffer(name: string, value?) { + private setBuffer(name: string, value?: number[] | undefined) { const { gl, buffers } = this; const buffer = buffers[name]; @@ -253,18 +253,18 @@ export class SnowfallEffect { } } - private setUniform(name: string, value?) { + private setUniform(name: string, value?: number | number[] | Float32Array<ArrayBufferLike> | undefined) { const { gl, uniforms } = this; const uniform = uniforms[name]; - const setter = this.UNIFORM_SETTERS[uniform.type]; + const setter = this.UNIFORM_SETTERS[uniform.type as keyof typeof this.UNIFORM_SETTERS]; const isMatrix = /^mat[2-4]$/i.test(uniform.type); uniform.value = value ?? uniform.value; if (isMatrix) { - gl[setter](uniform.location, false, uniform.value); + (gl as any)[setter](uniform.location, false, uniform.value); } else { - gl[setter](uniform.location, uniform.value); + (gl as any)[setter](uniform.location, uniform.value); } } diff --git a/packages/frontend/src/widgets/WidgetActivity.chart.vue b/packages/frontend/src/widgets/WidgetActivity.chart.vue index e708343b3a..bab688f851 100644 --- a/packages/frontend/src/widgets/WidgetActivity.chart.vue +++ b/packages/frontend/src/widgets/WidgetActivity.chart.vue @@ -53,19 +53,27 @@ const pointsReply = ref<string>(); const pointsRenote = ref<string>(); const pointsTotal = ref<string>(); -function dragListen(fn) { +function dragListen(fn: (ev: MouseEvent | TouchEvent) => void) { window.addEventListener('mousemove', fn); window.addEventListener('mouseleave', dragClear.bind(null, fn)); window.addEventListener('mouseup', dragClear.bind(null, fn)); } -function dragClear(fn) { +function dragClear(fn: (ev: MouseEvent | TouchEvent) => void) { window.removeEventListener('mousemove', fn); - window.removeEventListener('mouseleave', dragClear); - window.removeEventListener('mouseup', dragClear); + window.removeEventListener('mouseleave', dragClear as any); + window.removeEventListener('mouseup', dragClear as any); } -function onMousedown(ev) { +function getPositionX(event: MouseEvent | TouchEvent) { + return 'touches' in event && event.touches.length > 0 ? event.touches[0].clientX : 'clientX' in event ? event.clientX : 0; +} + +function getPositionY(event: MouseEvent | TouchEvent) { + return 'touches' in event && event.touches.length > 0 ? event.touches[0].clientY : 'clientY' in event ? event.clientY : 0; +} + +function onMousedown(ev: MouseEvent) { const clickX = ev.clientX; const clickY = ev.clientY; const baseZoom = zoom.value; @@ -73,8 +81,11 @@ function onMousedown(ev) { // 動かした時 dragListen(me => { - let moveLeft = me.clientX - clickX; - let moveTop = me.clientY - clickY; + const x = getPositionX(me); + const y = getPositionY(me); + + let moveLeft = x - clickX; + let moveTop = y - clickY; zoom.value = Math.max(1, baseZoom + (-moveTop / 20)); pos.value = Math.min(0, basePos + moveLeft); diff --git a/packages/frontend/src/widgets/WidgetFederation.vue b/packages/frontend/src/widgets/WidgetFederation.vue index 91a3e61201..cf2c2f0ab7 100644 --- a/packages/frontend/src/widgets/WidgetFederation.vue +++ b/packages/frontend/src/widgets/WidgetFederation.vue @@ -63,7 +63,7 @@ const instances = ref<Misskey.entities.FederationInstance[]>([]); const charts = ref<Misskey.entities.ChartsInstanceResponse[]>([]); const fetching = ref(true); -const fetch = async () => { +async function fetchInstances() { const fetchedInstances = await misskeyApi('federation/instances', { sort: '+latestRequestReceivedAt', limit: 5, @@ -72,14 +72,14 @@ const fetch = async () => { instances.value = fetchedInstances; charts.value = fetchedCharts; fetching.value = false; -}; +} -useInterval(fetch, 1000 * 60, { +useInterval(fetchInstances, 1000 * 60, { immediate: true, afterMounted: true, }); -function getInstanceIcon(instance): string { +function getInstanceIcon(instance: Misskey.entities.FederationInstance): string { return getProxiedImageUrlNullable(instance.iconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.faviconUrl, 'preview') ?? '/client-assets/dummy.png'; } diff --git a/packages/frontend/src/widgets/WidgetInstanceCloud.vue b/packages/frontend/src/widgets/WidgetInstanceCloud.vue index 6a8e30f54a..c1e864bdb3 100644 --- a/packages/frontend/src/widgets/WidgetInstanceCloud.vue +++ b/packages/frontend/src/widgets/WidgetInstanceCloud.vue @@ -55,7 +55,7 @@ const { widgetProps, configure } = useWidgetPropsManager(name, const cloud = useTemplateRef('cloud'); const activeInstances = shallowRef<Misskey.entities.FederationInstance[] | null>(null); -function onInstanceClick(i) { +function onInstanceClick(i: Misskey.entities.FederationInstance) { os.pageWindow(`/instance-info/${i.host}`); } @@ -72,7 +72,7 @@ useInterval(() => { afterMounted: true, }); -function getInstanceIcon(instance): string { +function getInstanceIcon(instance: Misskey.entities.FederationInstance): string { return getProxiedImageUrlNullable(instance.iconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.faviconUrl, 'preview') ?? '/client-assets/dummy.png'; } diff --git a/packages/frontend/src/widgets/WidgetJobQueue.vue b/packages/frontend/src/widgets/WidgetJobQueue.vue index 877baaea02..1727ea9b74 100644 --- a/packages/frontend/src/widgets/WidgetJobQueue.vue +++ b/packages/frontend/src/widgets/WidgetJobQueue.vue @@ -52,6 +52,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { onUnmounted, reactive, ref } from 'vue'; +import * as Misskey from 'misskey-js'; import { useWidgetPropsManager } from './widget.js'; import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js'; import type { FormWithDefault, GetFormResultType } from '@/utility/form.js'; @@ -116,20 +117,22 @@ if (prefer.s['sound.masterVolume']) { } for (const domain of ['inbox', 'deliver']) { - prev[domain] = deepClone(current[domain]); + const d = domain as 'inbox' | 'deliver'; + prev[d] = deepClone(current[d]); } -const onStats = (stats) => { +const onStats = (stats: Misskey.entities.QueueStats) => { for (const domain of ['inbox', 'deliver']) { - prev[domain] = deepClone(current[domain]); - current[domain].activeSincePrevTick = stats[domain].activeSincePrevTick; - current[domain].active = stats[domain].active; - current[domain].waiting = stats[domain].waiting; - current[domain].delayed = stats[domain].delayed; + const d = domain as 'inbox' | 'deliver'; + prev[d] = deepClone(current[d]); + current[d].activeSincePrevTick = stats[d].activeSincePrevTick; + current[d].active = stats[d].active; + current[d].waiting = stats[d].waiting; + current[d].delayed = stats[d].delayed; - if (current[domain].waiting > 0 && widgetProps.sound && jammedAudioBuffer.value && !jammedSoundNodePlaying.value) { + if (current[d].waiting > 0 && widgetProps.sound && jammedAudioBuffer.value && !jammedSoundNodePlaying.value) { const soundNode = sound.createSourceNode(jammedAudioBuffer.value, {}).soundSource; - if (soundNode) { + if (soundNode != null) { jammedSoundNodePlaying.value = true; soundNode.onended = () => jammedSoundNodePlaying.value = false; soundNode.start(); @@ -138,7 +141,7 @@ const onStats = (stats) => { } }; -const onStatsLog = (statsLog) => { +const onStatsLog = (statsLog: Misskey.entities.QueueStatsLog) => { for (const stats of [...statsLog].reverse()) { onStats(stats); } diff --git a/packages/frontend/src/widgets/WidgetMemo.vue b/packages/frontend/src/widgets/WidgetMemo.vue index f96a11f059..fd5b56991e 100644 --- a/packages/frontend/src/widgets/WidgetMemo.vue +++ b/packages/frontend/src/widgets/WidgetMemo.vue @@ -52,7 +52,7 @@ const { widgetProps, configure } = useWidgetPropsManager(name, const text = ref<string | null>(store.s.memo); const changed = ref(false); -let timeoutId; +let timeoutId: number | null = null; const saveMemo = () => { store.set('memo', text.value); @@ -61,7 +61,7 @@ const saveMemo = () => { const onChange = () => { changed.value = true; - window.clearTimeout(timeoutId); + if (timeoutId != null) window.clearTimeout(timeoutId); timeoutId = window.setTimeout(saveMemo, 1000); }; diff --git a/packages/frontend/src/widgets/WidgetPhotos.vue b/packages/frontend/src/widgets/WidgetPhotos.vue index 4f592911d4..670e764c8c 100644 --- a/packages/frontend/src/widgets/WidgetPhotos.vue +++ b/packages/frontend/src/widgets/WidgetPhotos.vue @@ -64,12 +64,12 @@ const connection = useStream().useChannel('main'); const images = ref<Misskey.entities.DriveFile[]>([]); const fetching = ref(true); -const onDriveFileCreated = (file) => { +function onDriveFileCreated(file: Misskey.entities.DriveFile) { if (/^image\/.+$/.test(file.type)) { images.value.unshift(file); if (images.value.length > 9) images.value.pop(); } -}; +} const thumbnail = (image: Misskey.entities.DriveFile): string => { return prefer.s.disableShowingAnimatedImages diff --git a/packages/frontend/src/widgets/WidgetTimeline.vue b/packages/frontend/src/widgets/WidgetTimeline.vue index 6c775fd98c..5d65b001e0 100644 --- a/packages/frontend/src/widgets/WidgetTimeline.vue +++ b/packages/frontend/src/widgets/WidgetTimeline.vue @@ -41,13 +41,13 @@ import * as Misskey from 'misskey-js'; import { useWidgetPropsManager } from './widget.js'; import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js'; import type { FormWithDefault, GetFormResultType } from '@/utility/form.js'; +import type { MenuItem } from '@/types/menu.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import MkContainer from '@/components/MkContainer.vue'; import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue'; import { i18n } from '@/i18n.js'; import { availableBasicTimelines, isAvailableBasicTimeline, isBasicTimeline, basicTimelineIconClass, basicTimelineTypes } from '@/timelines.js'; -import type { MenuItem } from '@/types/menu.js'; const name = 'timeline'; @@ -98,7 +98,7 @@ const headerTitle = computed<string>(() => { } else if (widgetProps.src === 'antenna' && widgetProps.antenna != null) { return widgetProps.antenna.name; } else { - return i18n.ts._timelines[widgetProps.src]; + return (i18n.ts._timelines as any)[widgetProps.src] ?? '?'; } }); @@ -107,7 +107,7 @@ const setSrc = (src: TlSrc) => { save(); }; -const choose = async (ev: MouseEvent) => { +const choose = async (ev: PointerEvent) => { menuOpened.value = true; const [antennas, lists] = await Promise.all([ misskeyApi('antennas/list'), diff --git a/packages/frontend/src/widgets/index.ts b/packages/frontend/src/widgets/index.ts index aea810d1ea..b3351be45f 100644 --- a/packages/frontend/src/widgets/index.ts +++ b/packages/frontend/src/widgets/index.ts @@ -42,7 +42,7 @@ export default function(app: App) { export const federationWidgets = [ 'federation', 'instanceCloud', -]; +] as const; export const widgets = [ 'profile', @@ -74,4 +74,4 @@ export const widgets = [ 'chat', ...federationWidgets, -]; +] as const; diff --git a/packages/frontend/src/widgets/widget.ts b/packages/frontend/src/widgets/widget.ts index 603cf8a31d..cab6177247 100644 --- a/packages/frontend/src/widgets/widget.ts +++ b/packages/frontend/src/widgets/widget.ts @@ -5,12 +5,11 @@ import { defineAsyncComponent, reactive, watch } from 'vue'; import { throttle } from 'throttle-debounce'; -import { getDefaultFormValues } from '@/utility/form.js'; import type { Reactive } from 'vue'; import type { FormWithDefault, GetFormResultType } from '@/utility/form.js'; +import { getDefaultFormValues } from '@/utility/form.js'; import * as os from '@/os.js'; import { deepClone } from '@/utility/clone.js'; -import { i18n } from '@/i18n'; export type Widget<P extends Record<string, unknown>> = { id: string; @@ -22,7 +21,7 @@ export type WidgetComponentProps<P extends Record<string, unknown>> = { }; export type WidgetComponentEmits<P extends Record<string, unknown>> = { - (ev: 'updateProps', props: P); + (ev: 'updateProps', props: P): void; }; export type WidgetComponentExpose = { @@ -54,7 +53,7 @@ export const useWidgetPropsManager = <F extends FormWithDefault>( watch(() => props.widget?.data, (to) => { if (to != null) { for (const key of Object.keys(propsDef)) { - widgetProps[key] = to[key]; + (widgetProps as any)[key] = to[key]; } } }, { deep: true }); @@ -66,7 +65,7 @@ export const useWidgetPropsManager = <F extends FormWithDefault>( const configure = async () => { const form = deepClone(propsDef); for (const item of Object.keys(form)) { - form[item].default = widgetProps[item]; + form[item].default = (widgetProps as any)[item]; } const res = await new Promise<{ @@ -97,7 +96,7 @@ export const useWidgetPropsManager = <F extends FormWithDefault>( } for (const key of Object.keys(res.result)) { - widgetProps[key] = res.result[key]; + (widgetProps as any)[key] = res.result[key]; } save(); diff --git a/packages/frontend/tsconfig.json b/packages/frontend/tsconfig.json index 492082d05b..e80d981bd2 100644 --- a/packages/frontend/tsconfig.json +++ b/packages/frontend/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "allowJs": true, "noEmitOnError": false, - "noImplicitAny": false, + "noImplicitAny": true, "noImplicitReturns": true, "noUnusedParameters": false, "noUnusedLocals": false, diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 6403fb2748..ede6025457 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -854,7 +854,14 @@ export type Channels = { key: K; value: ReversiGameDetailed[K]; }) => void; - log: (payload: Record<string, unknown>) => void; + log: (payload: { + time: number; + player: boolean; + operation: 'put'; + pos: number; + } & { + id: string | null; + }) => void; }; receives: { putStone: { @@ -3872,7 +3879,7 @@ type VerifyEmailRequest = operations['verify-email']['requestBody']['content'][' // src/entities.ts:55:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts // src/streaming.ts:57:3 - (ae-forgotten-export) The symbol "ReconnectingWebSocket" needs to be exported by the entry point index.d.ts // src/streaming.types.ts:226:4 - (ae-forgotten-export) The symbol "ReversiUpdateKey" needs to be exported by the entry point index.d.ts -// src/streaming.types.ts:236:4 - (ae-forgotten-export) The symbol "ReversiUpdateSettings" needs to be exported by the entry point index.d.ts +// src/streaming.types.ts:241:4 - (ae-forgotten-export) The symbol "ReversiUpdateSettings" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 87dc70a4db..5d14facfb3 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -4276,6 +4276,33 @@ export type components = { /** Format: misskey:id */ userListId: string; }; + login?: { + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + } | { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }; + createToken?: { + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + } | { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }; + exportCompleted?: { + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + } | { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }; }; emailNotificationTypes: string[]; achievements: components['schemas']['Achievement'][]; @@ -6786,8 +6813,10 @@ export interface operations { updatedAt: string | null; text: string; title: string; - icon: string | null; - display: string; + /** @enum {string} */ + icon: 'info' | 'warning' | 'error' | 'success'; + /** @enum {string} */ + display: 'normal' | 'banner' | 'dialog'; isActive: boolean; forExistingUsers: boolean; silence: boolean; @@ -8305,16 +8334,7 @@ export interface operations { [name: string]: unknown; }; content: { - 'application/json': { - /** Format: id */ - id: string; - aliases: string[]; - name: string; - category: string | null; - /** @description The local host is represented with `null`. */ - host: string | null; - url: string; - }[]; + 'application/json': components['schemas']['EmojiDetailed'][]; }; }; /** @description Client error */ diff --git a/packages/misskey-js/src/streaming.types.ts b/packages/misskey-js/src/streaming.types.ts index 93838ceed5..63e1f947a5 100644 --- a/packages/misskey-js/src/streaming.types.ts +++ b/packages/misskey-js/src/streaming.types.ts @@ -224,7 +224,12 @@ export type Channels = { canceled: (payload: { userId: User['id']; }) => void; changeReadyStates: (payload: { user1: boolean; user2: boolean; }) => void; updateSettings: <K extends ReversiUpdateKey>(payload: { userId: User['id']; key: K; value: ReversiGameDetailed[K]; }) => void; - log: (payload: Record<string, unknown>) => void; + log: (payload: { + time: number; + player: boolean; + operation: 'put'; + pos: number; + } & { id: string | null }) => void; }; receives: { putStone: { @@ -291,7 +296,10 @@ export type NoteUpdatedEvent = { id: Note['id'] } & ({ type: 'reacted'; body: { reaction: string; - emoji: string | null; + emoji?: { + name: string; + url: string; + } | null; userId: User['id']; }; } | { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4e58e471a3..563e86fab1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -903,6 +903,9 @@ importers: '@types/estree': specifier: 1.0.8 version: 1.0.8 + '@types/insert-text-at-cursor': + specifier: 0.3.2 + version: 0.3.2 '@types/matter-js': specifier: 0.20.2 version: 0.20.2 @@ -921,6 +924,9 @@ importers: '@types/seedrandom': specifier: 3.0.8 version: 3.0.8 + '@types/textarea-caret': + specifier: 3.0.4 + version: 3.0.4 '@types/throttle-debounce': specifier: 5.0.2 version: 5.0.2 @@ -4667,6 +4673,9 @@ packages: '@types/http-link-header@1.0.7': resolution: {integrity: sha512-snm5oLckop0K3cTDAiBnZDy6ncx9DJ3mCRDvs42C884MbVYPP74Tiq2hFsSDRTyjK6RyDYDIulPiW23ge+g5Lw==} + '@types/insert-text-at-cursor@0.3.2': + resolution: {integrity: sha512-S80ZeMGJ1YMwF/CwvvGNCoWS0klVpJmjtyEL1kbYgYXRMPMEfNWXXnnnLQaim3FNp6tJzOhy/yeQXInfC6o1sA==} + '@types/istanbul-lib-coverage@2.0.6': resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} @@ -4850,6 +4859,9 @@ packages: '@types/tedious@4.0.14': resolution: {integrity: sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==} + '@types/textarea-caret@3.0.4': + resolution: {integrity: sha512-epJGYB37/sNrTDbhfyRjHkXsQSAcO6zby0JBDS0QMt6HQ1f1W2E4YpSc7TQkNmWaWmYXv92zOIfN5PHA8CmThg==} + '@types/throttle-debounce@5.0.2': resolution: {integrity: sha512-pDzSNulqooSKvSNcksnV72nk8p7gRqN8As71Sp28nov1IgmPKWbOEIwAWvBME5pPTtaXJAvG3O4oc76HlQ4kqQ==} @@ -15665,6 +15677,8 @@ snapshots: dependencies: '@types/node': 24.10.4 + '@types/insert-text-at-cursor@0.3.2': {} + '@types/istanbul-lib-coverage@2.0.6': {} '@types/istanbul-lib-report@3.0.3': @@ -15860,6 +15874,8 @@ snapshots: dependencies: '@types/node': 24.10.4 + '@types/textarea-caret@3.0.4': {} + '@types/throttle-debounce@5.0.2': {} '@types/tinycolor2@1.4.6': {} |