From 4c9ff82e64ea13e0460dbeda70bdb6039fa65ece Mon Sep 17 00:00:00 2001 From: laonmofu Date: Wed, 15 May 2024 01:56:19 +0900 Subject: [PATCH 1/9] =?UTF-8?q?about.vue=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 스폰서 항목 삭제 2. 팬박스 링크 변경 --- packages/frontend/src/pages/about.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/frontend/src/pages/about.vue b/packages/frontend/src/pages/about.vue index e4391568008a..261e4e116bdf 100644 --- a/packages/frontend/src/pages/about.vue +++ b/packages/frontend/src/pages/about.vue @@ -28,12 +28,12 @@ SPDX-License-Identifier: AGPL-3.0-only 特定商取引法に基づく表記 - +
@@ -97,7 +97,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.support }} - + {{ i18n.tsx.supportThisInstance({ name: instance.name ?? host }) }} From 53695acbc1bfd046da0ddac270831286240520eb Mon Sep 17 00:00:00 2001 From: Hoto Ras Date: Sat, 25 May 2024 12:13:11 +0900 Subject: [PATCH 2/9] refactor: frontend: types on Directives --- ...lugin-unwind-css-module-class-name.test.ts | 4 +- ...lup-plugin-unwind-css-module-class-name.ts | 1 + .../frontend/src/components/form/suspense.vue | 2 +- .../frontend/src/directives/adaptive-bg.ts | 5 +- .../src/directives/adaptive-border.ts | 5 +- packages/frontend/src/directives/anim.ts | 4 +- packages/frontend/src/directives/appear.ts | 8 ++- .../frontend/src/directives/click-anime.ts | 4 +- .../frontend/src/directives/follow-append.ts | 8 ++- packages/frontend/src/directives/get-size.ts | 6 +- packages/frontend/src/directives/hotkey.ts | 10 ++- packages/frontend/src/directives/panel.ts | 5 +- packages/frontend/src/directives/ripple.ts | 6 +- packages/frontend/src/directives/tooltip.ts | 64 +++++++++---------- .../frontend/src/directives/user-preview.ts | 38 ++++++----- packages/frontend/src/filters/bytes.ts | 2 +- packages/frontend/src/filters/date.ts | 2 +- packages/frontend/src/filters/kmg.ts | 2 +- packages/frontend/src/filters/note.ts | 3 +- packages/frontend/src/filters/number.ts | 2 +- 20 files changed, 104 insertions(+), 77 deletions(-) diff --git a/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.test.ts b/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.test.ts index 99d7978d68fd..08fb6fab1fc5 100644 --- a/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.test.ts +++ b/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.test.ts @@ -170,7 +170,7 @@ const cssModules = { const index_photos = /* @__PURE__ */ _export_sfc(_sfc_main, [["__cssModules", cssModules]]); export { index_photos as default }; -`.slice(1), { ecmaVersion: 'latest', sourceType: 'module' }); +`.slice(1), { ecmaVersion: 'latest', sourceType: 'module' }) /* as unknown */ as estree.Program; unwindCssModuleClassName(ast); expect(generate(ast)).toBe(` import {c as api, d as defaultStore, i as i18n, aD as notePage, bN as ImgWithBlurhash, bY as getStaticImageUrl, _ as _export_sfc} from './app-!~{001}~.js'; @@ -437,7 +437,7 @@ const cssModules = { const MkDateSeparatedList = /* @__PURE__ */ _export_sfc(_sfc_main, [["__cssModules", cssModules]]); export { MkDateSeparatedList as M }; -`.slice(1), { ecmaVersion: 'latest', sourceType: 'module' }); +`.slice(1), { ecmaVersion: 'latest', sourceType: 'module' }) as unknown as estree.Program; unwindCssModuleClassName(ast); expect(generate(ast)).toBe(` import {a7 as getCurrentInstance, b as defineComponent, G as useCssModule, a1 as h, H as TransitionGroup} from './!~{002}~.js'; diff --git a/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.ts b/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.ts index 0ed2e14d2a81..89e99077d869 100644 --- a/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.ts +++ b/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.ts @@ -432,6 +432,7 @@ export function unwindCssModuleClassName(ast: estree.Node): void { type: 'ArrayExpression', elements: node.declarations[0].init.arguments[1].elements.slice(0, __cssModulesIndex).concat(node.declarations[0].init.arguments[1].elements.slice(__cssModulesIndex + 1)), }], + optional: false, }, }], kind: 'const', diff --git a/packages/frontend/src/components/form/suspense.vue b/packages/frontend/src/components/form/suspense.vue index 5226c61d6836..94136b41e464 100644 --- a/packages/frontend/src/components/form/suspense.vue +++ b/packages/frontend/src/components/form/suspense.vue @@ -24,7 +24,7 @@ import MkButton from '@/components/MkButton.vue'; import { i18n } from '@/i18n.js'; const props = defineProps<{ - p: () => Promise; + p: (() => Promise) | null; }>(); const pending = ref(true); diff --git a/packages/frontend/src/directives/adaptive-bg.ts b/packages/frontend/src/directives/adaptive-bg.ts index 23fd1bddf4a2..9a7ff416abf5 100644 --- a/packages/frontend/src/directives/adaptive-bg.ts +++ b/packages/frontend/src/directives/adaptive-bg.ts @@ -6,8 +6,9 @@ import { Directive } from 'vue'; export default { - mounted(src, binding, vn) { - const getBgColor = (el: HTMLElement) => { + mounted(src: HTMLElement) { + const getBgColor = (el: HTMLElement | null): string => { + if (el === null) return 'transparent'; const style = window.getComputedStyle(el); if (style.backgroundColor && !['rgba(0, 0, 0, 0)', 'rgba(0,0,0,0)', 'transparent'].includes(style.backgroundColor)) { return style.backgroundColor; diff --git a/packages/frontend/src/directives/adaptive-border.ts b/packages/frontend/src/directives/adaptive-border.ts index b436075fcd4a..4a96dcded881 100644 --- a/packages/frontend/src/directives/adaptive-border.ts +++ b/packages/frontend/src/directives/adaptive-border.ts @@ -6,8 +6,9 @@ import { Directive } from 'vue'; export default { - mounted(src, binding, vn) { - const getBgColor = (el: HTMLElement) => { + mounted(src: HTMLElement) { + const getBgColor = (el: HTMLElement | null) => { + if (el === null) return 'transparent'; const style = window.getComputedStyle(el); if (style.backgroundColor && !['rgba(0, 0, 0, 0)', 'rgba(0,0,0,0)', 'transparent'].includes(style.backgroundColor)) { return style.backgroundColor; diff --git a/packages/frontend/src/directives/anim.ts b/packages/frontend/src/directives/anim.ts index d5b6ae428799..b814f7ce3a14 100644 --- a/packages/frontend/src/directives/anim.ts +++ b/packages/frontend/src/directives/anim.ts @@ -6,7 +6,7 @@ import { Directive } from 'vue'; export default { - beforeMount(src, binding, vn) { + beforeMount(src: HTMLElement) { src.style.opacity = '0'; src.style.transform = 'scale(0.9)'; // ページネーションと相性が悪いので @@ -14,7 +14,7 @@ export default { src.classList.add('_zoom'); }, - mounted(src, binding, vn) { + mounted(src: HTMLElement) { window.setTimeout(() => { src.style.opacity = '1'; src.style.transform = 'none'; diff --git a/packages/frontend/src/directives/appear.ts b/packages/frontend/src/directives/appear.ts index 706d4a9ee4b6..1b77dc86218e 100644 --- a/packages/frontend/src/directives/appear.ts +++ b/packages/frontend/src/directives/appear.ts @@ -5,8 +5,12 @@ import { Directive } from 'vue'; +export interface MkObserverHTMLElement extends HTMLElement { + _observer_?: IntersectionObserver, +} + export default { - mounted(src, binding, vn) { + mounted(src: MkObserverHTMLElement, binding) { const fn = binding.value; if (fn == null) return; @@ -21,7 +25,7 @@ export default { src._observer_ = observer; }, - unmounted(src, binding, vn) { + unmounted(src: MkObserverHTMLElement) { if (src._observer_) src._observer_.disconnect(); }, } as Directive; diff --git a/packages/frontend/src/directives/click-anime.ts b/packages/frontend/src/directives/click-anime.ts index 5bb48bbcdd48..ac05d6132040 100644 --- a/packages/frontend/src/directives/click-anime.ts +++ b/packages/frontend/src/directives/click-anime.ts @@ -7,12 +7,12 @@ import { Directive } from 'vue'; import { defaultStore } from '@/store.js'; export default { - mounted(el: HTMLElement, binding, vn) { + mounted(el: HTMLElement) { if (!defaultStore.state.animation) return; const target = el.children[0]; - if (target == null) return; + //if (target == null) return; target.classList.add('_anime_bounce_standBy'); diff --git a/packages/frontend/src/directives/follow-append.ts b/packages/frontend/src/directives/follow-append.ts index f200f242ed23..200887d3fed0 100644 --- a/packages/frontend/src/directives/follow-append.ts +++ b/packages/frontend/src/directives/follow-append.ts @@ -6,8 +6,12 @@ import { Directive } from 'vue'; import { getScrollContainer, getScrollPosition } from '@/scripts/scroll.js'; +export interface MkRoHTMLElement extends HTMLElement { + _ro_?: ResizeObserver, +} + export default { - mounted(src, binding, vn) { + mounted(src: MkRoHTMLElement, binding) { if (binding.value === false) return; let isBottom = true; @@ -34,7 +38,7 @@ export default { src._ro_ = ro; }, - unmounted(src, binding, vn) { + unmounted(src: MkRoHTMLElement) { if (src._ro_) src._ro_.unobserve(src); }, } as Directive; diff --git a/packages/frontend/src/directives/get-size.ts b/packages/frontend/src/directives/get-size.ts index 2655c76c482e..4e43617e6f44 100644 --- a/packages/frontend/src/directives/get-size.ts +++ b/packages/frontend/src/directives/get-size.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Directive } from 'vue'; +import { Directive, DirectiveBinding } from 'vue'; const mountings = new Map void>) { const resize = new ResizeObserver((entries, observer) => { calc(src); }); @@ -48,7 +48,7 @@ export default { calc(src); }, - unmounted(src, binding, vn) { + unmounted(src: Element, binding: DirectiveBinding<(w: number, h: number) => void>) { binding.value(0, 0); const info = mountings.get(src); if (!info) return; diff --git a/packages/frontend/src/directives/hotkey.ts b/packages/frontend/src/directives/hotkey.ts index b082b6edf2d4..309a3649a3d7 100644 --- a/packages/frontend/src/directives/hotkey.ts +++ b/packages/frontend/src/directives/hotkey.ts @@ -6,8 +6,13 @@ import { Directive } from 'vue'; import { makeHotkey } from '../scripts/hotkey.js'; +export interface MkHotkeyHTMLElement extends HTMLElement { + _hotkey_global?: boolean, + _keyHandler?: (event: KeyboardEvent) => void, +} + export default { - mounted(el, binding) { + mounted(el: MkHotkeyHTMLElement, binding) { el._hotkey_global = binding.modifiers.global === true; el._keyHandler = makeHotkey(binding.value); @@ -19,7 +24,8 @@ export default { } }, - unmounted(el) { + unmounted(el: MkHotkeyHTMLElement) { + if (el._keyHandler === undefined) return; if (el._hotkey_global) { document.removeEventListener('keydown', el._keyHandler); } else { diff --git a/packages/frontend/src/directives/panel.ts b/packages/frontend/src/directives/panel.ts index bbcc220e0941..3f226d85d45c 100644 --- a/packages/frontend/src/directives/panel.ts +++ b/packages/frontend/src/directives/panel.ts @@ -6,8 +6,9 @@ import { Directive } from 'vue'; export default { - mounted(src, binding, vn) { - const getBgColor = (el: HTMLElement) => { + mounted(src: HTMLElement) { + const getBgColor = (el: HTMLElement | null) => { + if (el === null) return 'transparent'; const style = window.getComputedStyle(el); if (style.backgroundColor && !['rgba(0, 0, 0, 0)', 'rgba(0,0,0,0)', 'transparent'].includes(style.backgroundColor)) { return style.backgroundColor; diff --git a/packages/frontend/src/directives/ripple.ts b/packages/frontend/src/directives/ripple.ts index 2d724f771e40..47ee66e26880 100644 --- a/packages/frontend/src/directives/ripple.ts +++ b/packages/frontend/src/directives/ripple.ts @@ -2,12 +2,12 @@ * SPDX-FileCopyrightText: syuilo and misskey-project * SPDX-License-Identifier: AGPL-3.0-only */ - +import { Directive, DirectiveBinding } from 'vue'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; import { popup } from '@/os.js'; export default { - mounted(el, binding, vn) { + mounted(el: HTMLElement, binding: DirectiveBinding) { // 明示的に false であればバインドしない if (binding.value === false) return; @@ -20,4 +20,4 @@ export default { popup(MkRippleEffect, { x, y }, {}, 'end'); }); }, -}; +} as Directive; diff --git a/packages/frontend/src/directives/tooltip.ts b/packages/frontend/src/directives/tooltip.ts index b1c1b19907d6..9b9c8f8e61db 100644 --- a/packages/frontend/src/directives/tooltip.ts +++ b/packages/frontend/src/directives/tooltip.ts @@ -6,49 +6,47 @@ // TODO: useTooltip関数使うようにしたい // ただディレクティブ内でonUnmountedなどのcomposition api使えるのか不明 -import { defineAsyncComponent, Directive, ref } from 'vue'; +import { defineAsyncComponent, Directive, ref, DirectiveBinding } from 'vue'; import { isTouchUsing } from '@/scripts/touch.js'; import { popup, alert } from '@/os.js'; const start = isTouchUsing ? 'touchstart' : 'mouseenter'; const end = isTouchUsing ? 'touchend' : 'mouseleave'; +export type MkTooltip = { + text?: string; + _close?: () => void; + showTimer?: number; + hideTimer?: number; + checkTimer?: number; + close?: () => void; + show?: () => void; +}; + +export interface MkTooltipDirectiveHTMLElement extends HTMLElement { + _tooltipDirective_?: MkTooltip, +} + export default { - mounted(el: HTMLElement, binding, vn) { + mounted(el: MkTooltipDirectiveHTMLElement, binding: DirectiveBinding) { const delay = binding.modifiers.noDelay ? 0 : 100; - const self = (el as any)._tooltipDirective_ = {} as any; - - self.text = binding.value as string; - self._close = null; - self.showTimer = null; - self.hideTimer = null; - self.checkTimer = null; + const self: MkTooltip = { + text: binding.value, + }; self.close = () => { if (self._close) { window.clearInterval(self.checkTimer); self._close(); - self._close = null; + self._close = undefined; } }; - if (binding.arg === 'dialog') { - el.addEventListener('click', (ev) => { - ev.preventDefault(); - ev.stopPropagation(); - alert({ - type: 'info', - text: binding.value, - }); - return false; - }); - } - self.show = () => { if (!document.body.contains(el)) return; if (self._close) return; - if (self.text == null) return; + if (self.text === undefined) return; const showing = ref(true); popup(defineAsyncComponent(() => import('@/components/MkTooltip.vue')), { @@ -64,6 +62,8 @@ export default { }; }; + (el)._tooltipDirective_ = self; + el.addEventListener('selectstart', ev => { ev.preventDefault(); }); @@ -72,9 +72,9 @@ export default { window.clearTimeout(self.showTimer); window.clearTimeout(self.hideTimer); if (delay === 0) { - self.show(); + self.show!(); } else { - self.showTimer = window.setTimeout(self.show, delay); + self.showTimer = window.setTimeout(self.show!, delay); } }, { passive: true }); @@ -82,25 +82,25 @@ export default { window.clearTimeout(self.showTimer); window.clearTimeout(self.hideTimer); if (delay === 0) { - self.close(); + self.close!(); } else { - self.hideTimer = window.setTimeout(self.close, delay); + self.hideTimer = window.setTimeout(self.close!, delay); } }, { passive: true }); el.addEventListener('click', () => { window.clearTimeout(self.showTimer); - self.close(); + self.close!(); }); }, - updated(el, binding) { + updated(el: MkTooltipDirectiveHTMLElement, binding: DirectiveBinding) { const self = el._tooltipDirective_; - self.text = binding.value as string; + self!.text = binding.value; }, - unmounted(el, binding, vn) { + unmounted(el: MkTooltipDirectiveHTMLElement) { const self = el._tooltipDirective_; - window.clearInterval(self.checkTimer); + window.clearInterval(self!.checkTimer); }, } as Directive; diff --git a/packages/frontend/src/directives/user-preview.ts b/packages/frontend/src/directives/user-preview.ts index 7a008a44865e..0c62fe33d242 100644 --- a/packages/frontend/src/directives/user-preview.ts +++ b/packages/frontend/src/directives/user-preview.ts @@ -7,14 +7,16 @@ import { defineAsyncComponent, Directive, ref } from 'vue'; import { popup } from '@/os.js'; export class UserPreview { - private el; - private user; - private showTimer; - private hideTimer; - private checkTimer; - private promise; - - constructor(el, user) { + private el: HTMLElement; + private user: string; + private showTimer?: number; + private hideTimer?: number; + private checkTimer?: number; + private promise?: { + cancel: () => void, + }; + + constructor(el: HTMLElement, user: string) { this.el = el; this.user = user; @@ -68,7 +70,7 @@ export class UserPreview { if (this.promise) { window.clearInterval(this.checkTimer); this.promise.cancel(); - this.promise = null; + this.promise = undefined; } } @@ -102,21 +104,27 @@ export class UserPreview { } } +export type MkUserPreviewDirective = { + preview: UserPreview, +}; + +export interface MkUserPreviewHTMLElement extends HTMLElement { + _userPreviewDirective_?: MkUserPreviewDirective, +} + export default { - mounted(el: HTMLElement, binding, vn) { + mounted(el: MkUserPreviewHTMLElement, binding) { if (binding.value == null) return; // TODO: 新たにプロパティを作るのをやめMapを使う // ただメモリ的には↓の方が省メモリかもしれないので検討中 - const self = (el as any)._userPreviewDirective_ = {} as any; - - self.preview = new UserPreview(el, binding.value); + el._userPreviewDirective_ = { preview: new UserPreview(el, binding.value) }; }, - unmounted(el, binding, vn) { + unmounted(el: MkUserPreviewHTMLElement, binding) { if (binding.value == null) return; const self = el._userPreviewDirective_; - self.preview.detach(); + self!.preview.detach(); }, } as Directive; diff --git a/packages/frontend/src/filters/bytes.ts b/packages/frontend/src/filters/bytes.ts index 49b44167d41e..de97e5ca9d30 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, 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/date.ts b/packages/frontend/src/filters/date.ts index 2ffe93e868a2..0e975677f412 100644 --- a/packages/frontend/src/filters/date.ts +++ b/packages/frontend/src/filters/date.ts @@ -5,5 +5,5 @@ import { dateTimeFormat } from '@/scripts/intl-const.js'; -export default (d: Date | number | undefined) => dateTimeFormat.format(d); +export default (d?: Date | number) => dateTimeFormat.format(d); export const dateString = (d: string) => dateTimeFormat.format(new Date(d)); diff --git a/packages/frontend/src/filters/kmg.ts b/packages/frontend/src/filters/kmg.ts index 9608e420f602..e71771013a07 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, 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 ce3102146956..a9fdcef43215 100644 --- a/packages/frontend/src/filters/note.ts +++ b/packages/frontend/src/filters/note.ts @@ -2,7 +2,8 @@ * SPDX-FileCopyrightText: syuilo and misskey-project * SPDX-License-Identifier: AGPL-3.0-only */ +import * as Misskey from 'misskey-js'; -export const notePage = note => { +export const notePage = (note: Misskey.entities.Note) => { return `/notes/${note.id}`; }; diff --git a/packages/frontend/src/filters/number.ts b/packages/frontend/src/filters/number.ts index 2e7cc60ff43d..41a26772950c 100644 --- a/packages/frontend/src/filters/number.ts +++ b/packages/frontend/src/filters/number.ts @@ -5,4 +5,4 @@ import { numberFormat } from '@/scripts/intl-const.js'; -export default n => n == null ? 'N/A' : numberFormat.format(n); +export default (n?: number | bigint) => n == null ? 'N/A' : numberFormat.format(n); From 5e58cc4e65fd9a86e25e4c187e4515c65ec4a244 Mon Sep 17 00:00:00 2001 From: Hoto Ras Date: Sat, 25 May 2024 14:15:43 +0900 Subject: [PATCH 3/9] refactor: frontend: have them typed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ...2시간. --- packages/frontend/src/nirax.ts | 2 +- packages/frontend/src/pizzax.ts | 6 ++--- packages/frontend/src/plugin.ts | 4 ++-- packages/frontend/src/scripts/aiscript/api.ts | 6 ++--- packages/frontend/src/scripts/aiscript/ui.ts | 11 +++++---- packages/frontend/src/scripts/chart-legend.ts | 1 + packages/frontend/src/scripts/clicker-game.ts | 2 +- .../frontend/src/scripts/code-highlighter.ts | 2 +- .../frontend/src/scripts/collect-page-vars.ts | 2 +- packages/frontend/src/scripts/confetti.ts | 2 +- packages/frontend/src/scripts/contains.ts | 2 +- .../frontend/src/scripts/copy-to-clipboard.ts | 4 ++-- packages/frontend/src/scripts/emoji-picker.ts | 4 ++-- .../frontend/src/scripts/extract-mentions.ts | 2 +- .../frontend/src/scripts/gen-search-query.ts | 2 +- .../frontend/src/scripts/get-note-menu.ts | 24 +++++++++++-------- .../frontend/src/scripts/get-note-summary.ts | 2 +- .../frontend/src/scripts/get-user-menu.ts | 6 ++--- packages/frontend/src/scripts/idb-proxy.ts | 2 +- .../frontend/src/scripts/install-plugin.ts | 7 +++--- packages/frontend/src/scripts/lookup-user.ts | 4 +++- packages/frontend/src/scripts/lookup.ts | 2 +- .../src/scripts/mfm-function-picker.ts | 2 +- packages/frontend/src/scripts/nyaize.ts | 6 ++--- packages/frontend/src/scripts/popout.ts | 4 ++-- .../frontend/src/scripts/popup-position.ts | 18 +++++++------- .../frontend/src/scripts/reaction-picker.ts | 9 +++---- packages/frontend/src/scripts/select-file.ts | 2 +- .../frontend/src/scripts/sticky-sidebar.ts | 4 ++-- packages/frontend/src/scripts/theme.ts | 6 ++--- packages/frontend/src/scripts/upload.ts | 2 +- .../frontend/src/scripts/use-chart-tooltip.ts | 6 ++--- .../frontend/src/scripts/use-note-capture.ts | 1 + .../src/scripts/warning-external-website.ts | 2 +- packages/frontend/src/store.ts | 2 +- packages/frontend/src/stream.ts | 6 ++--- packages/frontend/src/theme-store.ts | 2 +- packages/frontend/test/note.test.ts | 1 + packages/frontend/test/scroll.test.ts | 10 ++++---- 39 files changed, 97 insertions(+), 85 deletions(-) diff --git a/packages/frontend/src/nirax.ts b/packages/frontend/src/nirax.ts index 6a8ea09ed6af..10b349fdcdcd 100644 --- a/packages/frontend/src/nirax.ts +++ b/packages/frontend/src/nirax.ts @@ -415,7 +415,7 @@ export class Router extends EventEmitter implements IRouter { beforePath, path: res._parsedRoute.fullPath, route: res.route, - props: res.props, + props: res.props as Map, key: this.currentKey, }); } diff --git a/packages/frontend/src/pizzax.ts b/packages/frontend/src/pizzax.ts index ac325e923f36..89d08ad65552 100644 --- a/packages/frontend/src/pizzax.ts +++ b/packages/frontend/src/pizzax.ts @@ -5,7 +5,7 @@ // PIZZAX --- A lightweight store -import { onUnmounted, Ref, ref, watch } from 'vue'; +import { onUnmounted, Ref, ref, UnwrapRef, watch } from 'vue'; import { BroadcastChannel } from 'broadcast-channel'; import { $i } from '@/account.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; @@ -87,7 +87,7 @@ export class Storage { private mergeState(value: X, def: X): X { if (this.isPureObject(value) && this.isPureObject(def)) { - const merged = deepMerge(value, def); + const merged = deepMerge(value as any, def); if (_DEV_) console.log('Merging state. Incoming: ', value, ' Default: ', def, ' Result: ', merged); @@ -268,7 +268,7 @@ export class Storage { set: (value: unknown) => { const val = setter ? setter(value) : value; this.set(key, val); - valueRef.value = val; + valueRef.value = val as UnwrapRef[K]>; }, }; } diff --git a/packages/frontend/src/plugin.ts b/packages/frontend/src/plugin.ts index a7b3d9705d31..4c31516a809d 100644 --- a/packages/frontend/src/plugin.ts +++ b/packages/frontend/src/plugin.ts @@ -23,12 +23,12 @@ export async function install(plugin: Plugin): Promise { in: aiScriptReadline, out: (value): void => { console.log(value); - pluginLogs.value.get(plugin.id).push(utils.reprValue(value)); + pluginLogs.value.get(plugin.id)?.push(utils.reprValue(value)); }, log: (): void => { }, err: (err): void => { - pluginLogs.value.get(plugin.id).push(`${err}`); + pluginLogs.value.get(plugin.id)?.push(`${err}`); throw err; // install時のtry-catchに反応させる }, }); diff --git a/packages/frontend/src/scripts/aiscript/api.ts b/packages/frontend/src/scripts/aiscript/api.ts index 75d862f1fa00..e26d3f691eba 100644 --- a/packages/frontend/src/scripts/aiscript/api.ts +++ b/packages/frontend/src/scripts/aiscript/api.ts @@ -59,7 +59,7 @@ export function createAiScriptEnv(opts) { } const actualToken: string|null = token?.value ?? opts.token ?? null; if (!rateLimiter.hit(ep.value)) return values.ERROR('rate_limited', values.NULL); - return misskeyApi(ep.value, utils.valToJs(param), actualToken).then(res => { + return misskeyApi(ep.value, utils.valToJs(param!), actualToken).then(res => { return utils.jsToVal(res); }, err => { return values.ERROR('request_failed', utils.jsToVal(err)); @@ -67,12 +67,12 @@ export function createAiScriptEnv(opts) { }), 'Mk:save': values.FN_NATIVE(([key, value]) => { utils.assertString(key); - miLocalStorage.setItem(`aiscript:${opts.storageKey}:${key.value}`, JSON.stringify(utils.valToJs(value))); + miLocalStorage.setItem(`aiscript:${opts.storageKey}:${key.value}`, JSON.stringify(utils.valToJs(value!))); return values.NULL; }), 'Mk:load': values.FN_NATIVE(([key]) => { utils.assertString(key); - return utils.jsToVal(JSON.parse(miLocalStorage.getItem(`aiscript:${opts.storageKey}:${key.value}`))); + return utils.jsToVal(JSON.parse(miLocalStorage.getItem(`aiscript:${opts.storageKey}:${key.value}`) as string)); }), 'Mk:url': values.FN_NATIVE(() => { return values.STR(window.location.href); diff --git a/packages/frontend/src/scripts/aiscript/ui.ts b/packages/frontend/src/scripts/aiscript/ui.ts index fa3fcac2e79a..534b875ac63e 100644 --- a/packages/frontend/src/scripts/aiscript/ui.ts +++ b/packages/frontend/src/scripts/aiscript/ui.ts @@ -7,6 +7,7 @@ import { utils, values } from '@syuilo/aiscript'; import { v4 as uuid } from 'uuid'; import { ref, Ref } from 'vue'; import * as Misskey from 'misskey-js'; +import { VFn, VStr } from '@syuilo/aiscript/interpreter/value.js'; export type AsUiComponentBase = { id: string; @@ -151,7 +152,7 @@ function getRootOptions(def: values.Value | undefined): Omit { utils.assertObject(v); - return v.value.get('id').value; + return v.value.get('id')!.value; }), }; } @@ -183,7 +184,7 @@ function getContainerOptions(def: values.Value | undefined): Omit { utils.assertObject(v); - return v.value.get('id').value; + return v.value.get('id')!.value; }) : [], align: align?.value, fgColor: fgColor?.value, @@ -442,7 +443,7 @@ function getFolderOptions(def: values.Value | undefined): Omit { utils.assertObject(v); - return v.value.get('id').value; + return v.value.get('id')!.value; }) : [], title: title?.value ?? '', opened: opened?.value ?? true, @@ -514,7 +515,7 @@ export function registerAsUiLib(components: Ref[], done: (root: R id: _id, }); components.push(component); - const instance = values.OBJ(new Map([ + const instance = values.OBJ(new Map([ ['id', values.STR(_id)], ['update', values.FN_NATIVE(([def], opts) => { utils.assertObject(def); @@ -558,7 +559,7 @@ export function registerAsUiLib(components: Ref[], done: (root: R rootComponent.value.children = children.value.map(v => { utils.assertObject(v); - return v.value.get('id').value; + return v.value.get('id')!.value; }); }), diff --git a/packages/frontend/src/scripts/chart-legend.ts b/packages/frontend/src/scripts/chart-legend.ts index 2d534f60c1e8..c14cf4840b91 100644 --- a/packages/frontend/src/scripts/chart-legend.ts +++ b/packages/frontend/src/scripts/chart-legend.ts @@ -9,6 +9,7 @@ import MkChartLegend from '@/components/MkChartLegend.vue'; export const chartLegend = (legend: InstanceType) => ({ id: 'htmlLegend', afterUpdate(chart, args, options) { + if (chart.options.plugins?.legend?.labels?.generateLabels === undefined) return; // Reuse the built-in legendItems generator const items = chart.options.plugins.legend.labels.generateLabels(chart); diff --git a/packages/frontend/src/scripts/clicker-game.ts b/packages/frontend/src/scripts/clicker-game.ts index f9c4bc182957..7b0f9f3cb211 100644 --- a/packages/frontend/src/scripts/clicker-game.ts +++ b/packages/frontend/src/scripts/clicker-game.ts @@ -45,7 +45,7 @@ export async function load() { } // migration - if (saveData.value.gameVersion === 1) { + if (saveData.value?.gameVersion === 1) { saveData.value = { gameVersion: 2, cookies: saveData.value.cookies, diff --git a/packages/frontend/src/scripts/code-highlighter.ts b/packages/frontend/src/scripts/code-highlighter.ts index 51345daccd1d..d0ea8800b36a 100644 --- a/packages/frontend/src/scripts/code-highlighter.ts +++ b/packages/frontend/src/scripts/code-highlighter.ts @@ -33,7 +33,7 @@ export async function getTheme(mode: 'light' | 'dark', getName = false): Promise _res = deepClone(theme.codeHighlighter.overrides); } else { const base = await bundledThemesInfo.find(t => t.id === theme.codeHighlighter!.base)?.import() ?? darkPlus; - _res = deepMerge(theme.codeHighlighter.overrides ?? {}, 'default' in base ? base.default : base); + _res = deepMerge(theme.codeHighlighter.overrides ?? {}, ('default' in base ? base.default : base) as Record); } if (_res.name == null) { _res.name = theme.id; diff --git a/packages/frontend/src/scripts/collect-page-vars.ts b/packages/frontend/src/scripts/collect-page-vars.ts index 5096c0669ee1..5eab1cba5f06 100644 --- a/packages/frontend/src/scripts/collect-page-vars.ts +++ b/packages/frontend/src/scripts/collect-page-vars.ts @@ -23,7 +23,7 @@ interface BooleanPageVar { type PageVar = StringPageVar | NumberPageVar | BooleanPageVar; -export function collectPageVars(content): PageVar[] { +export function collectPageVars(content: any[]): PageVar[] { const pageVars: PageVar[] = []; const collect = (xs: any[]): void => { for (const x of xs) { diff --git a/packages/frontend/src/scripts/confetti.ts b/packages/frontend/src/scripts/confetti.ts index 8e53a6ceeb00..461557f9a840 100644 --- a/packages/frontend/src/scripts/confetti.ts +++ b/packages/frontend/src/scripts/confetti.ts @@ -11,7 +11,7 @@ export function confetti(options: { duration?: number; } = {}) { const animationEnd = Date.now() + duration; const defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: os.claimZIndex('high') }; - function randomInRange(min, max) { + function randomInRange(min: number, max: number) { return Math.random() * (max - min) + min; } diff --git a/packages/frontend/src/scripts/contains.ts b/packages/frontend/src/scripts/contains.ts index 6137c06e8574..cd963d5f0ec6 100644 --- a/packages/frontend/src/scripts/contains.ts +++ b/packages/frontend/src/scripts/contains.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -export default (parent, child, checkSame = true) => { +export default (parent: Node, child: Node, checkSame = true) => { if (checkSame && parent === child) return true; let node = child.parentNode; while (node) { diff --git a/packages/frontend/src/scripts/copy-to-clipboard.ts b/packages/frontend/src/scripts/copy-to-clipboard.ts index 216c0464b3d5..cc486a30e35e 100644 --- a/packages/frontend/src/scripts/copy-to-clipboard.ts +++ b/packages/frontend/src/scripts/copy-to-clipboard.ts @@ -6,7 +6,7 @@ /** * Clipboardに値をコピー(TODO: 文字列以外も対応) */ -export default val => { +export default (val: string | null) => { // 空div 生成 const tmp = document.createElement('div'); // 選択用のタグ生成 @@ -26,7 +26,7 @@ export default val => { // body に追加 document.body.appendChild(tmp); // 要素を選択 - document.getSelection().selectAllChildren(tmp); + document.getSelection()?.selectAllChildren(tmp); // クリップボードにコピー const result = document.execCommand('copy'); diff --git a/packages/frontend/src/scripts/emoji-picker.ts b/packages/frontend/src/scripts/emoji-picker.ts index 14b5cbf35e0e..0b4dd2061915 100644 --- a/packages/frontend/src/scripts/emoji-picker.ts +++ b/packages/frontend/src/scripts/emoji-picker.ts @@ -14,7 +14,7 @@ import { defaultStore } from '@/store.js'; * 一度表示したダイアログを連続で使用できることが望ましいシーンでの利用が想定される。 */ class EmojiPicker { - private src: Ref = ref(null); + private src: Ref = ref(undefined); private manualShowing = ref(false); private onChosen?: (emoji: string) => void; private onClosed?: () => void; @@ -39,7 +39,7 @@ class EmojiPicker { this.manualShowing.value = false; }, closed: () => { - this.src.value = null; + this.src.value = undefined; if (this.onClosed) this.onClosed(); }, }); diff --git a/packages/frontend/src/scripts/extract-mentions.ts b/packages/frontend/src/scripts/extract-mentions.ts index d51856205300..172d554fadc0 100644 --- a/packages/frontend/src/scripts/extract-mentions.ts +++ b/packages/frontend/src/scripts/extract-mentions.ts @@ -12,5 +12,5 @@ export function extractMentions(nodes: mfm.MfmNode[]): mfm.MfmMention['props'][] const mentionNodes = mfm.extract(nodes, (node) => node.type === 'mention'); const mentions = mentionNodes.map(x => x.props); - return mentions; + return mentions as mfm.MfmMention['props'][]; } diff --git a/packages/frontend/src/scripts/gen-search-query.ts b/packages/frontend/src/scripts/gen-search-query.ts index 60884d08d39b..f0bd6cac4db6 100644 --- a/packages/frontend/src/scripts/gen-search-query.ts +++ b/packages/frontend/src/scripts/gen-search-query.ts @@ -7,7 +7,7 @@ import * as Misskey from 'misskey-js'; import { host as localHost } from '@/config.js'; export async function genSearchQuery(v: any, q: string) { - let host: string; + let host: string | null; let userId: string; if (q.split(' ').some(x => x.startsWith('@'))) { for (const at of q.split(' ').filter(x => x.startsWith('@')).map(x => x.substring(1))) { diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts index 2cd21c1edc2e..b844df3353a6 100644 --- a/packages/frontend/src/scripts/get-note-menu.ts +++ b/packages/frontend/src/scripts/get-note-menu.ts @@ -21,6 +21,7 @@ import { MenuItem } from '@/types/menu.js'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; import { isSupportShare } from '@/scripts/navigator.js'; + export async function getNoteClipMenu(props: { note: Misskey.entities.Note; isDeleted: Ref; @@ -37,7 +38,7 @@ export async function getNoteClipMenu(props: { const isRenote = ( props.note.renote != null && props.note.text == null && - props.note.fileIds.length === 0 && + props.note.fileIds?.length === 0 && props.note.poll == null ); @@ -100,12 +101,14 @@ export async function getNoteClipMenu(props: { name: { type: 'string', label: i18n.ts.name, + default: null, }, description: { type: 'string', required: false, multiline: true, label: i18n.ts.description, + default: null, }, isPublic: { type: 'boolean', @@ -165,7 +168,7 @@ export function getNoteMenu(props: { const isRenote = ( props.note.renote != null && props.note.text == null && - props.note.fileIds.length === 0 && + props.note.fileIds?.length === 0 && props.note.poll == null ); @@ -246,7 +249,7 @@ export function getNoteMenu(props: { } async function unclip(): Promise { - os.apiWithDialog('clips/remove-note', { clipId: props.currentClip.id, noteId: appearNote.id }); + os.apiWithDialog('clips/remove-note', { clipId: props.currentClip!.id, noteId: appearNote.id }); props.isDeleted.value = true; } @@ -256,6 +259,7 @@ export function getNoteMenu(props: { }); if (canceled) return; + if (days === null) return; os.apiWithDialog('admin/promo/create', { noteId: appearNote.id, @@ -265,8 +269,8 @@ export function getNoteMenu(props: { function share(): void { navigator.share({ - title: i18n.tsx.noteOf({ user: appearNote.user.name }), - text: appearNote.text, + title: i18n.tsx.noteOf({ user: appearNote.user.name as string }), + text: appearNote.text as string, url: `${url}/notes/${appearNote.id}`, }); } @@ -309,7 +313,7 @@ export function getNoteMenu(props: { text: i18n.ts.copyContent, action: copyContent, }, getCopyNoteLinkMenu(appearNote, i18n.ts.copyLink) - , (appearNote.url || appearNote.uri) ? { + , (appearNote.url ?? appearNote.uri) ? { icon: 'ti ti-external-link', text: i18n.ts.showOnRemote, action: () => { @@ -337,7 +341,7 @@ export function getNoteMenu(props: { action: () => toggleFavorite(true), }), { - type: 'parent' as const, + type: 'parent', icon: 'ti ti-paperclip', text: i18n.ts.clip, children: () => getNoteClipMenu(props), @@ -361,7 +365,7 @@ export function getNoteMenu(props: { action: () => togglePin(true), } : undefined, { - type: 'parent' as const, + type: 'parent', icon: 'ti ti-user', text: i18n.ts.user, children: async () => { @@ -390,7 +394,7 @@ export function getNoteMenu(props: { ...(appearNote.channel && (appearNote.channel.userId === $i.id || $i.isModerator || $i.isAdmin) ? [ { type: 'divider' }, { - type: 'parent' as const, + type: 'parent', icon: 'ti ti-device-tv', text: i18n.ts.channel, children: async () => { @@ -510,7 +514,7 @@ export function getRenoteMenu(props: { const isRenote = ( props.note.renote != null && props.note.text == null && - props.note.fileIds.length === 0 && + props.note.fileIds?.length === 0 && props.note.poll == null ); diff --git a/packages/frontend/src/scripts/get-note-summary.ts b/packages/frontend/src/scripts/get-note-summary.ts index 6fd9947ac1e7..219f603c96d2 100644 --- a/packages/frontend/src/scripts/get-note-summary.ts +++ b/packages/frontend/src/scripts/get-note-summary.ts @@ -33,7 +33,7 @@ export const getNoteSummary = (note?: Misskey.entities.Note | null): string => { } // ファイルが添付されているとき - if ((note.files || []).length !== 0) { + if (note.files && note.files.length > 0) { summary += ` (${i18n.tsx.withNFiles({ n: note.files.length })})`; } diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts index 3e031d232ff9..ce687e95424a 100644 --- a/packages/frontend/src/scripts/get-user-menu.ts +++ b/packages/frontend/src/scripts/get-user-menu.ts @@ -204,21 +204,21 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter children: async () => { const lists = await userListsCache.fetch(); return lists.map(list => { - const isListed = ref(list.userIds.includes(user.id)); + const isListed = ref(list.userIds?.includes(user.id)); cleanups.push(watch(isListed, () => { if (isListed.value) { os.apiWithDialog('users/lists/push', { listId: list.id, userId: user.id, }).then(() => { - list.userIds.push(user.id); + list.userIds!.push(user.id); }); } else { os.apiWithDialog('users/lists/pull', { listId: list.id, userId: user.id, }).then(() => { - list.userIds.splice(list.userIds.indexOf(user.id), 1); + list.userIds!.splice(list.userIds!.indexOf(user.id), 1); }); } })); diff --git a/packages/frontend/src/scripts/idb-proxy.ts b/packages/frontend/src/scripts/idb-proxy.ts index 459e3d166c4f..ad8aa940cf20 100644 --- a/packages/frontend/src/scripts/idb-proxy.ts +++ b/packages/frontend/src/scripts/idb-proxy.ts @@ -39,7 +39,7 @@ if (idbAvailable) { export async function get(key: string) { if (idbAvailable) return iget(key); - return JSON.parse(window.localStorage.getItem(fallbackName(key))); + return JSON.parse(window.localStorage.getItem(fallbackName(key)) as string); } export async function set(key: string, val: any) { diff --git a/packages/frontend/src/scripts/install-plugin.ts b/packages/frontend/src/scripts/install-plugin.ts index d0a8675b191d..a293797001a9 100644 --- a/packages/frontend/src/scripts/install-plugin.ts +++ b/packages/frontend/src/scripts/install-plugin.ts @@ -7,6 +7,7 @@ import { defineAsyncComponent } from 'vue'; import { compareVersions } from 'compare-versions'; import { v4 as uuid } from 'uuid'; import { Interpreter, Parser, utils } from '@syuilo/aiscript'; +import * as Misskey from 'misskey-js'; import type { Plugin } from '@/store.js'; import { ColdDeviceStorage } from '@/store.js'; import * as os from '@/os.js'; @@ -102,12 +103,12 @@ export async function installPlugin(code: string, meta?: AiScriptPluginMeta) { realMeta = meta; } - const token = realMeta.permissions == null || realMeta.permissions.length === 0 ? null : await new Promise((res, rej) => { + const token = (realMeta.permissions == null || realMeta.permissions.length === 0 ? null : await new Promise((res, rej) => { os.popup(defineAsyncComponent(() => import('@/components/MkTokenGenerateWindow.vue')), { title: i18n.ts.tokenRequested, information: i18n.ts.pluginTokenRequestedDescription, initialName: realMeta.name, - initialPermissions: realMeta.permissions, + initialPermissions: realMeta.permissions as ((typeof Misskey.permissions)[number][] | null), }, { done: async result => { const { name, permissions } = result; @@ -119,7 +120,7 @@ export async function installPlugin(code: string, meta?: AiScriptPluginMeta) { res(token); }, }, 'closed'); - }); + })) as string; savePlugin({ id: uuid(), diff --git a/packages/frontend/src/scripts/lookup-user.ts b/packages/frontend/src/scripts/lookup-user.ts index efc9132e757c..e6c877297895 100644 --- a/packages/frontend/src/scripts/lookup-user.ts +++ b/packages/frontend/src/scripts/lookup-user.ts @@ -13,6 +13,7 @@ export async function lookupUser() { title: i18n.ts.usernameOrUserId, }); if (canceled) return; + if (result === null) return; const show = (user) => { os.pageWindow(`/admin/user/${user.id}`); @@ -47,12 +48,13 @@ export async function lookupUserByEmail() { type: 'email', }); if (canceled) return; + if (result === null) return; try { const user = await os.apiWithDialog('admin/accounts/find-by-email', { email: result }); os.pageWindow(`/admin/user/${user.id}`); - } catch (err) { + } catch (err: any) { if (err.code === 'USER_NOT_FOUND') { os.alert({ type: 'error', diff --git a/packages/frontend/src/scripts/lookup.ts b/packages/frontend/src/scripts/lookup.ts index 7f020b15cc2b..8dcde5c46559 100644 --- a/packages/frontend/src/scripts/lookup.ts +++ b/packages/frontend/src/scripts/lookup.ts @@ -39,7 +39,7 @@ export async function lookup(router?: Router) { if (res.type === 'User') { _router.push(`/@${res.object.username}@${res.object.host}`); - } else if (res.type === 'Note') { + } else { _router.push(`/notes/${res.object.id}`); } diff --git a/packages/frontend/src/scripts/mfm-function-picker.ts b/packages/frontend/src/scripts/mfm-function-picker.ts index 8867a8c50ff6..e7d6cf21f9ce 100644 --- a/packages/frontend/src/scripts/mfm-function-picker.ts +++ b/packages/frontend/src/scripts/mfm-function-picker.ts @@ -16,7 +16,7 @@ export function mfmFunctionPicker(src: any, textArea: HTMLInputElement | HTMLTex os.popupMenu([{ text: i18n.ts.addMfmFunction, type: 'label', - }, ...getFunctionList(textArea, textRef)], src); + }, ...getFunctionList(textArea, textRef) as any], src); }); } diff --git a/packages/frontend/src/scripts/nyaize.ts b/packages/frontend/src/scripts/nyaize.ts index 833a354e7a94..87ee3ee70724 100644 --- a/packages/frontend/src/scripts/nyaize.ts +++ b/packages/frontend/src/scripts/nyaize.ts @@ -9,7 +9,7 @@ let enRegex3: RegExp; let koRegex1: RegExp; let koRegex2: RegExp; let koRegex3: RegExp; -let fallback: boolean = true; +let fallback = true; try { enRegex1 = new RegExp('(?<=n)a', 'gi'); @@ -38,7 +38,7 @@ function convertNormal(text: string): string { .replace(enRegex3, x => x === 'ONE' ? 'NYAN' : 'nyan') // ko-KR .replace(koRegex1, match => String.fromCharCode( - match.charCodeAt(0)! + '냐'.charCodeAt(0) - '나'.charCodeAt(0), + match.charCodeAt(0) + '냐'.charCodeAt(0) - '나'.charCodeAt(0), )) .replace(koRegex2, '다냥') .replace(koRegex3, '냥'); @@ -54,7 +54,7 @@ function convertFallback(text: string): string { .replace(enRegex3, x => x === 'EVERYONE' ? 'EVERYNYAN' : 'everynyan') // ko-KR .replace(koRegex1, match => String.fromCharCode( - match.charCodeAt(0)! + '냐'.charCodeAt(0) - '나'.charCodeAt(0), + match.charCodeAt(0) + '냐'.charCodeAt(0) - '나'.charCodeAt(0), )) .replace(koRegex2, '다냥').replaceAll('다 ', '다냥 ').replaceAll('다!', '다냥!').replaceAll('다?', '다냥?').replaceAll('다.', '다냥.') .replace(koRegex3, '냥').replaceAll('야 ', '냥 ').replaceAll('야!', '냥!').replaceAll('야?', '냥?').replaceAll('야.', '냥.'); diff --git a/packages/frontend/src/scripts/popout.ts b/packages/frontend/src/scripts/popout.ts index 1caa2dfc2101..b1ffaf8c3521 100644 --- a/packages/frontend/src/scripts/popout.ts +++ b/packages/frontend/src/scripts/popout.ts @@ -20,8 +20,8 @@ export function popout(path: string, w?: HTMLElement) { } else { const width = 400; const height = 500; - const x = window.top.outerHeight / 2 + window.top.screenY - (height / 2); - const y = window.top.outerWidth / 2 + window.top.screenX - (width / 2); + const x = window.top!.outerHeight / 2 + window.top!.screenY - (height / 2); + const y = window.top!.outerWidth / 2 + window.top!.screenX - (width / 2); window.open(url, url, `width=${width}, height=${height}, top=${x}, left=${y}`); } diff --git a/packages/frontend/src/scripts/popup-position.ts b/packages/frontend/src/scripts/popup-position.ts index 3dad41a8b31f..d1ba6f9f13ee 100644 --- a/packages/frontend/src/scripts/popup-position.ts +++ b/packages/frontend/src/scripts/popup-position.ts @@ -29,8 +29,8 @@ export function calcPopupPosition(el: HTMLElement, props: { left = rect.left + window.scrollX + (props.anchorElement.offsetWidth / 2); top = (rect.top + window.scrollY - contentHeight) - props.innerMargin; } else { - left = props.x; - top = (props.y - contentHeight) - props.innerMargin; + left = props.x ?? 0; + top = ((props.y ?? 0) - contentHeight) - props.innerMargin; } left -= (el.offsetWidth / 2); @@ -50,8 +50,8 @@ export function calcPopupPosition(el: HTMLElement, props: { left = rect.left + window.scrollX + (props.anchorElement.offsetWidth / 2); top = (rect.top + window.scrollY + props.anchorElement.offsetHeight) + props.innerMargin; } else { - left = props.x; - top = (props.y) + props.innerMargin; + left = props.x ?? 0; + top = (props.y ?? 0) + props.innerMargin; } left -= (el.offsetWidth / 2); @@ -71,8 +71,8 @@ export function calcPopupPosition(el: HTMLElement, props: { left = (rect.left + window.scrollX - contentWidth) - props.innerMargin; top = rect.top + window.scrollY + (props.anchorElement.offsetHeight / 2); } else { - left = (props.x - contentWidth) - props.innerMargin; - top = props.y; + left = ((props.x ?? 0) - contentWidth) - props.innerMargin; + top = props.y ?? 0; } top -= (el.offsetHeight / 2); @@ -86,7 +86,7 @@ export function calcPopupPosition(el: HTMLElement, props: { const calcPosWhenRight = () => { let left: number; - let top: number; + let top = 0; if (props.anchorElement) { left = (rect.left + props.anchorElement.offsetWidth + window.scrollX) + props.innerMargin; @@ -101,8 +101,8 @@ export function calcPopupPosition(el: HTMLElement, props: { top -= (el.offsetHeight / 2); } } else { - left = props.x + props.innerMargin; - top = props.y; + left = (props.x ?? 0) + props.innerMargin; + top = props.y ?? 0; top -= (el.offsetHeight / 2); } diff --git a/packages/frontend/src/scripts/reaction-picker.ts b/packages/frontend/src/scripts/reaction-picker.ts index 7aec05c0cf64..ce17ca201a30 100644 --- a/packages/frontend/src/scripts/reaction-picker.ts +++ b/packages/frontend/src/scripts/reaction-picker.ts @@ -5,13 +5,14 @@ import * as Misskey from 'misskey-js'; import { defineAsyncComponent, Ref, ref } from 'vue'; + import { popup } from '@/os.js'; import { defaultStore } from '@/store.js'; class ReactionPicker { - private src: Ref = ref(null); + private src: Ref = ref(); private manualShowing = ref(false); - private targetNote: Ref = ref(null); + private targetNote: Ref = ref(); private onChosen?: (reaction: string) => void; private onClosed?: () => void; @@ -35,13 +36,13 @@ class ReactionPicker { this.manualShowing.value = false; }, closed: () => { - this.src.value = null; + this.src.value = undefined; if (this.onClosed) this.onClosed(); }, }); } - public show(src: HTMLElement | null, targetNote: Misskey.entities.Note | null, onChosen?: ReactionPicker['onChosen'], onClosed?: ReactionPicker['onClosed']) { + public show(src?: HTMLElement, targetNote?: Misskey.entities.Note, onChosen?: ReactionPicker['onChosen'], onClosed?: ReactionPicker['onClosed']) { this.src.value = src; this.targetNote.value = targetNote; this.manualShowing.value = true; diff --git a/packages/frontend/src/scripts/select-file.ts b/packages/frontend/src/scripts/select-file.ts index 9aa38178b20e..9e3b6bfa13de 100644 --- a/packages/frontend/src/scripts/select-file.ts +++ b/packages/frontend/src/scripts/select-file.ts @@ -67,7 +67,7 @@ export function chooseFileFromUrl(): Promise { }); misskeyApi('drive/files/upload-from-url', { - url: url, + url: url ?? '', folderId: defaultStore.state.uploadFolder, marker, }); diff --git a/packages/frontend/src/scripts/sticky-sidebar.ts b/packages/frontend/src/scripts/sticky-sidebar.ts index 50f1e6ecc864..171f956804a9 100644 --- a/packages/frontend/src/scripts/sticky-sidebar.ts +++ b/packages/frontend/src/scripts/sticky-sidebar.ts @@ -28,7 +28,7 @@ export class StickySidebar { public calc(scrollTop: number) { if (scrollTop > this.lastScrollTop) { // downscroll const overflow = Math.max(0, this.globalHeaderHeight + (this.el.clientHeight + this.marginTop) - window.innerHeight); - this.el.style.bottom = null; + this.el.style.bottom = ''; this.el.style.top = `${-overflow + this.marginTop + this.globalHeaderHeight}px`; this.isBottom = (scrollTop + window.innerHeight) >= (this.el.offsetTop + this.el.clientHeight); @@ -39,7 +39,7 @@ export class StickySidebar { } } else { // upscroll const overflow = this.globalHeaderHeight + (this.el.clientHeight + this.marginTop) - window.innerHeight; - this.el.style.top = null; + this.el.style.top = ''; this.el.style.bottom = `${-overflow}px`; this.isTop = scrollTop + this.marginTop + this.globalHeaderHeight <= this.el.offsetTop; diff --git a/packages/frontend/src/scripts/theme.ts b/packages/frontend/src/scripts/theme.ts index c7f8b3d59663..1df4ad889a9b 100644 --- a/packages/frontend/src/scripts/theme.ts +++ b/packages/frontend/src/scripts/theme.ts @@ -84,7 +84,7 @@ export function applyTheme(theme: Theme, persist = true) { const props = compile(_theme); - for (const tag of document.head.children) { + for (const tag of document.head.children as any) { if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') { tag.setAttribute('content', props['htmlThemeColor']); break; @@ -114,8 +114,8 @@ function compile(theme: Theme): Record { return getColor(theme.props[val]); } else if (val[0] === ':') { // func const parts = val.split('<'); - const func = parts.shift().substring(1); - const arg = parseFloat(parts.shift()); + const func = parts.shift()?.substring(1); + const arg = parseFloat(parts.shift() ?? ''); const color = getColor(parts.join('<')); switch (func) { diff --git a/packages/frontend/src/scripts/upload.ts b/packages/frontend/src/scripts/upload.ts index 3e947183c9ea..2cb220885bd8 100644 --- a/packages/frontend/src/scripts/upload.ts +++ b/packages/frontend/src/scripts/upload.ts @@ -79,7 +79,7 @@ export function uploadFile( } const formData = new FormData(); - formData.append('i', $i.token); + formData.append('i', $i!.token); formData.append('force', 'true'); formData.append('file', resizedImage ?? file); formData.append('name', ctx.name); diff --git a/packages/frontend/src/scripts/use-chart-tooltip.ts b/packages/frontend/src/scripts/use-chart-tooltip.ts index bed221a62255..8e72a19025ed 100644 --- a/packages/frontend/src/scripts/use-chart-tooltip.ts +++ b/packages/frontend/src/scripts/use-chart-tooltip.ts @@ -11,12 +11,12 @@ export function useChartTooltip(opts: { position: 'top' | 'middle' } = { positio const tooltipShowing = ref(false); const tooltipX = ref(0); const tooltipY = ref(0); - const tooltipTitle = ref(null); + const tooltipTitle = ref(); const tooltipSeries = ref<{ backgroundColor: string; borderColor: string; text: string; - }[] | null>(null); + }[] | undefined>(); let disposeTooltipComponent; os.popup(MkChartTooltip, { @@ -56,7 +56,7 @@ export function useChartTooltip(opts: { position: 'top' | 'middle' } = { positio tooltipX.value = rect.left + window.scrollX + context.tooltip.caretX; if (opts.position === 'top') { tooltipY.value = rect.top + window.scrollY; - } else if (opts.position === 'middle') { + } else { tooltipY.value = rect.top + window.scrollY + context.tooltip.caretY; } } diff --git a/packages/frontend/src/scripts/use-note-capture.ts b/packages/frontend/src/scripts/use-note-capture.ts index 542d8ab52b18..b2fd042aa879 100644 --- a/packages/frontend/src/scripts/use-note-capture.ts +++ b/packages/frontend/src/scripts/use-note-capture.ts @@ -60,6 +60,7 @@ export function useNoteCapture(props: { } case 'pollVoted': { + if (note.value.poll === null || note.value.poll === undefined) break; const choice = body.choice; const choices = [...note.value.poll.choices]; diff --git a/packages/frontend/src/scripts/warning-external-website.ts b/packages/frontend/src/scripts/warning-external-website.ts index 199d2693ce0d..57c6c23869ac 100644 --- a/packages/frontend/src/scripts/warning-external-website.ts +++ b/packages/frontend/src/scripts/warning-external-website.ts @@ -8,7 +8,7 @@ const extractDomain = /^(https?:\/\/|\/\/)?([^@/\s]+@)?(www\.)?([^:/\s]+)/i; const isRegExp = /^\/(.+)\/(.*)$/; export async function warningExternalWebsite(ev: MouseEvent, url: string) { - const domain = extractDomain.exec(url)?.[4]; + const domain = extractDomain.exec(url)?.[4] ?? ''; const self = !domain || url.startsWith(local); const isWellKnownWebsite = self || instance.wellKnownWebsites.some(expression => { const r = isRegExp.exec(expression); diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index e813722a6105..112c99b567b2 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -613,7 +613,7 @@ export class ColdDeviceStorage { get: () => { return valueRef.value; }, - set: (value: unknown) => { + set: (value: typeof ColdDeviceStorage.default[K]) => { const val = value; ColdDeviceStorage.set(key, val); }, diff --git a/packages/frontend/src/stream.ts b/packages/frontend/src/stream.ts index 0d5bd78b09af..a365f45b2fd7 100644 --- a/packages/frontend/src/stream.ts +++ b/packages/frontend/src/stream.ts @@ -12,7 +12,7 @@ import { wsOrigin } from '@/config.js'; const HEART_BEAT_INTERVAL = 1000 * 60; let stream: Misskey.Stream | null = null; -let timeoutHeartBeat: ReturnType | null = null; +let timeoutHeartBeat: ReturnType | null = null; let lastHeartbeatCall = 0; export function useStream(): Misskey.Stream { @@ -23,7 +23,7 @@ export function useStream(): Misskey.Stream { } : null)); if (timeoutHeartBeat) window.clearTimeout(timeoutHeartBeat); - timeoutHeartBeat = window.setTimeout(heartbeat, HEART_BEAT_INTERVAL); + timeoutHeartBeat = window.setTimeout(heartbeat, HEART_BEAT_INTERVAL) as any; // send heartbeat right now when last send time is over HEART_BEAT_INTERVAL document.addEventListener('visibilitychange', () => { @@ -44,5 +44,5 @@ function heartbeat(): void { } lastHeartbeatCall = Date.now(); if (timeoutHeartBeat) window.clearTimeout(timeoutHeartBeat); - timeoutHeartBeat = window.setTimeout(heartbeat, HEART_BEAT_INTERVAL); + timeoutHeartBeat = window.setTimeout(heartbeat, HEART_BEAT_INTERVAL) as any; } diff --git a/packages/frontend/src/theme-store.ts b/packages/frontend/src/theme-store.ts index c41cc1765245..af65cc13b776 100644 --- a/packages/frontend/src/theme-store.ts +++ b/packages/frontend/src/theme-store.ts @@ -21,7 +21,7 @@ export async function fetchThemes(): Promise { try { const themes = await misskeyApi('i/registry/get', { scope: ['client'], key: 'themes' }); miLocalStorage.setItem(lsCacheKey!, JSON.stringify(themes)); - } catch (err) { + } catch (err: any) { if (err.code === 'NO_SUCH_KEY') return; throw err; } diff --git a/packages/frontend/test/note.test.ts b/packages/frontend/test/note.test.ts index 7ce5f23e22d4..627c5773938a 100644 --- a/packages/frontend/test/note.test.ts +++ b/packages/frontend/test/note.test.ts @@ -6,6 +6,7 @@ import { describe, test, assert, afterEach } from 'vitest'; import { render, cleanup, type RenderResult } from '@testing-library/vue'; import './init'; +import { DriveFile } from 'misskey-js/entities.js'; import type * as Misskey from 'misskey-js'; import { components } from '@/components/index.js'; import { directives } from '@/directives/index.js'; diff --git a/packages/frontend/test/scroll.test.ts b/packages/frontend/test/scroll.test.ts index a0b56b7221bb..7ddc02c7284d 100644 --- a/packages/frontend/test/scroll.test.ts +++ b/packages/frontend/test/scroll.test.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { describe, test, assert, afterEach } from 'vitest'; +import { describe, test, assert } from 'vitest'; import { Window } from 'happy-dom'; import { onScrollBottom, onScrollTop } from '@/scripts/scroll.js'; @@ -26,11 +26,11 @@ describe('Scroll', () => { test('No onScrollTop callback for disconnected elements', () => { const { document } = new Window(); - const div = document.createElement('div'); + const div = document.createElement('div') as any as HTMLElement; assert.strictEqual(div.scrollTop, 0); let called = false; - onScrollTop(div as any as HTMLElement, () => called = true); + onScrollTop(div, () => called = true); assert.ok(!called); }); @@ -54,11 +54,11 @@ describe('Scroll', () => { test('No onScrollBottom callback for disconnected elements', () => { const { document } = new Window(); - const div = document.createElement('div'); + const div = document.createElement('div') as any as HTMLElement; assert.strictEqual(div.scrollTop, 0); let called = false; - onScrollBottom(div as any as HTMLElement, () => called = true); + onScrollBottom(div, () => called = true); assert.ok(!called); }); From 6d2afc720c7705a544068e6b87cbfdd90e8e7e95 Mon Sep 17 00:00:00 2001 From: Hoto Ras Date: Sat, 25 May 2024 15:00:18 +0900 Subject: [PATCH 4/9] chore: fix less than minor? --- packages/backend/test/unit/activitypub.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/test/unit/activitypub.ts b/packages/backend/test/unit/activitypub.ts index 7f5e06b52f55..0672dc3548c2 100644 --- a/packages/backend/test/unit/activitypub.ts +++ b/packages/backend/test/unit/activitypub.ts @@ -386,7 +386,7 @@ describe('ActivityPub', () => { }); }); - describe('JSON-LD', () =>{ + describe('JSON-LD', () => { test('Compaction', async () => { const jsonLd = jsonLdService.use(); From 2dc15f4ed03faa183566e02199fed4358c296f4a Mon Sep 17 00:00:00 2001 From: Hoto Ras Date: Sat, 25 May 2024 15:58:03 +0900 Subject: [PATCH 5/9] fix (backend-test): FileInfoService: chromium browser mime issue Chromium browser is known to return M4A(audio/mp4) as MP4(video/mp4) and WEBM(audio/webm) to WEBM(video/webm). --- packages/backend/test/unit/FileInfoService.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/backend/test/unit/FileInfoService.ts b/packages/backend/test/unit/FileInfoService.ts index 2fcc4c9109b6..a84f8cbf5fa1 100644 --- a/packages/backend/test/unit/FileInfoService.ts +++ b/packages/backend/test/unit/FileInfoService.ts @@ -338,9 +338,11 @@ describe('FileInfoService', () => { assert.deepStrictEqual(info, { size: 9817, md5: '74c9279a4abe98789565f1dc1a541a42', - type: { - mime: 'audio/mp4', - ext: 'm4a', + type: { // Chromium returns MPEG4-audio (audio/mp4, ext: m4a) to video/mp4, mp4 + //mime: 'audio/mp4', + mime: 'video/mp4', + //ext: 'm4a', + ext: 'mp4', }, }); }); @@ -358,8 +360,9 @@ describe('FileInfoService', () => { assert.deepStrictEqual(info, { size: 8879, md5: '53bc1adcb6acbbda67ff9bd484896438', - type: { - mime: 'audio/webm', + type: { // Chromium returns WEBM Audio (audio/webm) to mime video/webm + //mime: 'audio/webm', + mime: 'video/webm', ext: 'webm', }, }); From 9e8458ff910a3422d2bcf73de3eae02102bc3463 Mon Sep 17 00:00:00 2001 From: Hoto Ras Date: Sun, 26 May 2024 08:30:34 +0900 Subject: [PATCH 6/9] Update ROADMAP.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 이게 왜 변경이 안 돼있을까... --- ROADMAP.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ROADMAP.md b/ROADMAP.md index 509ecb9fe787..9937e8c69276 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -7,9 +7,12 @@ This is the phase we are at now. We need to make a high-maintenance environment - ~~Make the number of type errors zero (backend)~~ → Done ✔️ - Make the number of type errors zero (frontend) + - https://github.com/nekoplanet/misskey-io/pull-request/2 - Improve CI - ~~Fix tests~~ → Done ✔️ - - Fix random test failures - https://github.com/misskey-dev/misskey/issues/7985 and https://github.com/misskey-dev/misskey/issues/7986 + - ~~Fix random test failures~~ → Done ✔️ + - https://github.com/misskey-dev/misskey/issues/7985 + - https://github.com/misskey-dev/misskey/issues/7986 - Add more tests - ~~May need to implement a mechanism that allows for DI~~ → Done ✔️ - https://github.com/misskey-dev/misskey/pull/9085 From 36ebfb64aaa6f168a275c8070af3656846988b64 Mon Sep 17 00:00:00 2001 From: Hoto Ras Date: Sun, 26 May 2024 08:31:07 +0900 Subject: [PATCH 7/9] =?UTF-8?q?ROADMAP.md=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ROADMAP.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ROADMAP.md b/ROADMAP.md index 9937e8c69276..01925d6627b1 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -7,7 +7,7 @@ This is the phase we are at now. We need to make a high-maintenance environment - ~~Make the number of type errors zero (backend)~~ → Done ✔️ - Make the number of type errors zero (frontend) - - https://github.com/nekoplanet/misskey-io/pull-request/2 + - https://github.com/nekoplanet/misskey-io/pulls/2 - Improve CI - ~~Fix tests~~ → Done ✔️ - ~~Fix random test failures~~ → Done ✔️ From 5238422e39e21e2b80da8da1e38e26dda1827449 Mon Sep 17 00:00:00 2001 From: Hoto Ras Date: Sun, 26 May 2024 08:31:48 +0900 Subject: [PATCH 8/9] =?UTF-8?q?ROADMAP.md=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ROADMAP.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ROADMAP.md b/ROADMAP.md index 01925d6627b1..add84faa90c5 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -7,7 +7,7 @@ This is the phase we are at now. We need to make a high-maintenance environment - ~~Make the number of type errors zero (backend)~~ → Done ✔️ - Make the number of type errors zero (frontend) - - https://github.com/nekoplanet/misskey-io/pulls/2 + - https://github.com/nekoplanet/misskey-io/pull/2 - Improve CI - ~~Fix tests~~ → Done ✔️ - ~~Fix random test failures~~ → Done ✔️ From 274f6b892b2b6655d268377c006f9b8845074860 Mon Sep 17 00:00:00 2001 From: Hoto Ras Date: Sun, 26 May 2024 08:36:30 +0900 Subject: [PATCH 9/9] fix (frontend-directives): tooltip: Lost lines recovered --- packages/frontend/src/directives/tooltip.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/frontend/src/directives/tooltip.ts b/packages/frontend/src/directives/tooltip.ts index 9b9c8f8e61db..8f14342c7e30 100644 --- a/packages/frontend/src/directives/tooltip.ts +++ b/packages/frontend/src/directives/tooltip.ts @@ -42,6 +42,17 @@ export default { self._close = undefined; } }; + if (binding.arg === 'dialog') { + el.addEventListener('click', (ev) => { + ev.preventDefault(); + ev.stopPropagation(); + alert({ + type: 'info', + text: binding.value, + }); + return false; + }); + } self.show = () => { if (!document.body.contains(el)) return;