summaryrefslogtreecommitdiff
path: root/packages/frontend
diff options
context:
space:
mode:
authorかっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>2025-10-19 11:36:00 +0900
committerGitHub <noreply@github.com>2025-10-19 11:36:00 +0900
commitd98bf012b59b343559dd679f0b4ae370ebd75079 (patch)
tree00aa135936d6836e8d051ab9f5648464ba057e41 /packages/frontend
parentRevert typeorm patches (#16664) (diff)
downloadmisskey-d98bf012b59b343559dd679f0b4ae370ebd75079.tar.gz
misskey-d98bf012b59b343559dd679f0b4ae370ebd75079.tar.bz2
misskey-d98bf012b59b343559dd679f0b4ae370ebd75079.zip
refactor(frontend): カスタムディレクティブの型付け (#16659)
* refactor(frontend): カスタムディレクティブの型付け * fix
Diffstat (limited to 'packages/frontend')
-rw-r--r--packages/frontend/src/directives/adaptive-bg.ts6
-rw-r--r--packages/frontend/src/directives/adaptive-border.ts8
-rw-r--r--packages/frontend/src/directives/anim.ts8
-rw-r--r--packages/frontend/src/directives/appear.ts14
-rw-r--r--packages/frontend/src/directives/click-anime.ts6
-rw-r--r--packages/frontend/src/directives/follow-append.ts12
-rw-r--r--packages/frontend/src/directives/get-size.ts10
-rw-r--r--packages/frontend/src/directives/hotkey.ts11
-rw-r--r--packages/frontend/src/directives/index.ts66
-rw-r--r--packages/frontend/src/directives/panel.ts6
-rw-r--r--packages/frontend/src/directives/ripple.ts7
-rw-r--r--packages/frontend/src/directives/tooltip.ts48
-rw-r--r--packages/frontend/src/directives/user-preview.ts50
13 files changed, 158 insertions, 94 deletions
diff --git a/packages/frontend/src/directives/adaptive-bg.ts b/packages/frontend/src/directives/adaptive-bg.ts
index a68cd1b18b..25e9ae1c9e 100644
--- a/packages/frontend/src/directives/adaptive-bg.ts
+++ b/packages/frontend/src/directives/adaptive-bg.ts
@@ -6,8 +6,8 @@
import type { Directive } from 'vue';
import { getBgColor } from '@/utility/get-bg-color.js';
-export default {
- mounted(src, binding, vn) {
+export const adaptiveBgDirective = {
+ mounted(src) {
const parentBg = getBgColor(src.parentElement) ?? 'transparent';
const myBg = window.getComputedStyle(src).backgroundColor;
@@ -18,4 +18,4 @@ export default {
src.style.backgroundColor = myBg;
}
},
-} as Directive;
+} as Directive<HTMLElement>;
diff --git a/packages/frontend/src/directives/adaptive-border.ts b/packages/frontend/src/directives/adaptive-border.ts
index 8072a1ffd9..749861fd94 100644
--- a/packages/frontend/src/directives/adaptive-border.ts
+++ b/packages/frontend/src/directives/adaptive-border.ts
@@ -9,8 +9,8 @@ import { globalEvents } from '@/events.js';
const handlerMap = new WeakMap<any, any>();
-export default {
- mounted(src, binding, vn) {
+export const adaptiveBorderDirective = {
+ mounted(src) {
function calc() {
const parentBg = getBgColor(src.parentElement) ?? 'transparent';
@@ -30,7 +30,7 @@ export default {
globalEvents.on('themeChanged', calc);
},
- unmounted(src, binding, vn) {
+ unmounted(src) {
globalEvents.off('themeChanged', handlerMap.get(src));
},
-} as Directive;
+} as Directive<HTMLElement>;
diff --git a/packages/frontend/src/directives/anim.ts b/packages/frontend/src/directives/anim.ts
index ad0cb5ed81..a165fa11e0 100644
--- a/packages/frontend/src/directives/anim.ts
+++ b/packages/frontend/src/directives/anim.ts
@@ -5,8 +5,8 @@
import type { Directive } from 'vue';
-export default {
- beforeMount(src, binding, vn) {
+export const animDirective = {
+ beforeMount(src) {
src.style.opacity = '0';
src.style.transform = 'scale(0.9)';
// ページネーションと相性が悪いので
@@ -14,10 +14,10 @@ export default {
src.classList.add('_zoom');
},
- mounted(src, binding, vn) {
+ mounted(src) {
window.setTimeout(() => {
src.style.opacity = '1';
src.style.transform = 'none';
}, 1);
},
-} as Directive;
+} as Directive<HTMLElement>;
diff --git a/packages/frontend/src/directives/appear.ts b/packages/frontend/src/directives/appear.ts
index f5fec108dc..f714871420 100644
--- a/packages/frontend/src/directives/appear.ts
+++ b/packages/frontend/src/directives/appear.ts
@@ -6,12 +6,16 @@
import { throttle } from 'throttle-debounce';
import type { Directive } from 'vue';
-export default {
- mounted(src, binding, vn) {
+interface HTMLElementWithObserver extends HTMLElement {
+ _observer_?: IntersectionObserver;
+}
+
+export const appearDirective = {
+ mounted(src, binding) {
const fn = binding.value;
if (fn == null) return;
- const check = throttle(1000, (entries) => {
+ const check = throttle<IntersectionObserverCallback>(1000, (entries) => {
if (entries.some(entry => entry.isIntersecting)) {
fn();
}
@@ -24,7 +28,7 @@ export default {
src._observer_ = observer;
},
- unmounted(src, binding, vn) {
+ unmounted(src) {
if (src._observer_) src._observer_.disconnect();
},
-} as Directive;
+} as Directive<HTMLElementWithObserver, () => void>;
diff --git a/packages/frontend/src/directives/click-anime.ts b/packages/frontend/src/directives/click-anime.ts
index c34f351fb3..7891e8092c 100644
--- a/packages/frontend/src/directives/click-anime.ts
+++ b/packages/frontend/src/directives/click-anime.ts
@@ -6,8 +6,8 @@
import type { Directive } from 'vue';
import { prefer } from '@/preferences.js';
-export default {
- mounted(el: HTMLElement, binding, vn) {
+export const clickAnimeDirective = {
+ mounted(el) {
if (!prefer.s.animation) return;
const target = el.children[0];
@@ -37,4 +37,4 @@ export default {
target.classList.add('_anime_bounce_standBy');
});
},
-} as Directive;
+} as Directive<HTMLElement>;
diff --git a/packages/frontend/src/directives/follow-append.ts b/packages/frontend/src/directives/follow-append.ts
index f3eaac10e3..303dcb842a 100644
--- a/packages/frontend/src/directives/follow-append.ts
+++ b/packages/frontend/src/directives/follow-append.ts
@@ -6,8 +6,12 @@
import type { Directive } from 'vue';
import { getScrollContainer, getScrollPosition } from '@@/js/scroll.js';
-export default {
- mounted(src, binding, vn) {
+interface HTMLElementWithRO extends HTMLElement {
+ _ro_?: ResizeObserver;
+}
+
+export const followAppendDirective = {
+ mounted(src, binding) {
if (binding.value === false) return;
let isBottom = true;
@@ -34,7 +38,7 @@ export default {
src._ro_ = ro;
},
- unmounted(src, binding, vn) {
+ unmounted(src) {
if (src._ro_) src._ro_.unobserve(src);
},
-} as Directive;
+} as Directive<HTMLElementWithRO, boolean>;
diff --git a/packages/frontend/src/directives/get-size.ts b/packages/frontend/src/directives/get-size.ts
index 488f201a0d..42660987dd 100644
--- a/packages/frontend/src/directives/get-size.ts
+++ b/packages/frontend/src/directives/get-size.ts
@@ -37,8 +37,10 @@ function calc(src: Element) {
info.fn(width, height);
}
-export default {
- mounted(src, binding, vn) {
+type SizeCallback = (w: number, h: number) => void;
+
+export const getSizeDirective = {
+ mounted(src, binding) {
const resize = new ResizeObserver((entries, observer) => {
calc(src);
});
@@ -48,7 +50,7 @@ export default {
calc(src);
},
- unmounted(src, binding, vn) {
+ unmounted(src, binding) {
binding.value(0, 0);
const info = mountings.get(src);
if (!info) return;
@@ -56,4 +58,4 @@ export default {
if (info.intersection) info.intersection.disconnect();
mountings.delete(src);
},
-} as Directive<Element, (w: number, h: number) => void>;
+} as Directive<Element, SizeCallback>;
diff --git a/packages/frontend/src/directives/hotkey.ts b/packages/frontend/src/directives/hotkey.ts
index 63637ab2ba..d8fdfe647a 100644
--- a/packages/frontend/src/directives/hotkey.ts
+++ b/packages/frontend/src/directives/hotkey.ts
@@ -5,8 +5,14 @@
import type { Directive } from 'vue';
import { makeHotkey } from '@/utility/hotkey.js';
+import type { Keymap } from '@/utility/hotkey.js';
-export default {
+interface HTMLElementWithHotkey extends HTMLElement {
+ _hotkey_global?: boolean;
+ _keyHandler?: (ev: KeyboardEvent) => void;
+}
+
+export const hotkeyDirective = {
mounted(el, binding) {
el._hotkey_global = binding.modifiers.global === true;
@@ -20,10 +26,11 @@ export default {
},
unmounted(el) {
+ if (el._keyHandler == null) return;
if (el._hotkey_global) {
window.document.removeEventListener('keydown', el._keyHandler);
} else {
el.removeEventListener('keydown', el._keyHandler);
}
},
-} as Directive;
+} as Directive<HTMLElementWithHotkey, Keymap>;
diff --git a/packages/frontend/src/directives/index.ts b/packages/frontend/src/directives/index.ts
index 9555045afe..07b756b95d 100644
--- a/packages/frontend/src/directives/index.ts
+++ b/packages/frontend/src/directives/index.ts
@@ -3,19 +3,19 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import type { App } from 'vue';
+import type { App, Directive } from 'vue';
-import userPreview from './user-preview.js';
-import getSize from './get-size.js';
-import ripple from './ripple.js';
-import tooltip from './tooltip.js';
-import hotkey from './hotkey.js';
-import appear from './appear.js';
-import anim from './anim.js';
-import clickAnime from './click-anime.js';
-import panel from './panel.js';
-import adaptiveBorder from './adaptive-border.js';
-import adaptiveBg from './adaptive-bg.js';
+import { userPreviewDirective } from './user-preview.js';
+import { getSizeDirective } from './get-size.js';
+import { rippleDirective } from './ripple.js';
+import { tooltipDirective } from './tooltip.js';
+import { hotkeyDirective } from './hotkey.js';
+import { appearDirective } from './appear.js';
+import { animDirective } from './anim.js';
+import { clickAnimeDirective } from './click-anime.js';
+import { panelDirective } from './panel.js';
+import { adaptiveBorderDirective } from './adaptive-border.js';
+import { adaptiveBgDirective } from './adaptive-bg.js';
export default function(app: App) {
for (const [key, value] of Object.entries(directives)) {
@@ -24,16 +24,32 @@ export default function(app: App) {
}
export const directives = {
- 'userPreview': userPreview,
- 'user-preview': userPreview,
- 'get-size': getSize,
- 'ripple': ripple,
- 'tooltip': tooltip,
- 'hotkey': hotkey,
- 'appear': appear,
- 'anim': anim,
- 'click-anime': clickAnime,
- 'panel': panel,
- 'adaptive-border': adaptiveBorder,
- 'adaptive-bg': adaptiveBg,
-};
+ 'userPreview': userPreviewDirective,
+ 'user-preview': userPreviewDirective,
+ 'get-size': getSizeDirective,
+ 'ripple': rippleDirective,
+ 'tooltip': tooltipDirective,
+ 'hotkey': hotkeyDirective,
+ 'appear': appearDirective,
+ 'anim': animDirective,
+ 'click-anime': clickAnimeDirective,
+ 'panel': panelDirective,
+ 'adaptive-border': adaptiveBorderDirective,
+ 'adaptive-bg': adaptiveBgDirective,
+} as Record<string, Directive>;
+
+declare module 'vue' {
+ export interface ComponentCustomProperties {
+ vUserPreview: typeof userPreviewDirective;
+ vGetSize: typeof getSizeDirective;
+ vRipple: typeof rippleDirective;
+ vTooltip: typeof tooltipDirective;
+ vHotkey: typeof hotkeyDirective;
+ vAppear: typeof appearDirective;
+ vAnim: typeof animDirective;
+ vClickAnime: typeof clickAnimeDirective;
+ vPanel: typeof panelDirective;
+ vAdaptiveBorder: typeof adaptiveBorderDirective;
+ vAdaptiveBg: typeof adaptiveBgDirective;
+ }
+}
diff --git a/packages/frontend/src/directives/panel.ts b/packages/frontend/src/directives/panel.ts
index 0af19e6ca3..7913baefe4 100644
--- a/packages/frontend/src/directives/panel.ts
+++ b/packages/frontend/src/directives/panel.ts
@@ -6,8 +6,8 @@
import type { Directive } from 'vue';
import { getBgColor } from '@/utility/get-bg-color.js';
-export default {
- mounted(src, binding, vn) {
+export const panelDirective = {
+ mounted(src) {
const parentBg = getBgColor(src.parentElement) ?? 'transparent';
const myBg = getComputedStyle(window.document.documentElement).getPropertyValue('--MI_THEME-panel');
@@ -18,4 +18,4 @@ export default {
src.style.backgroundColor = 'var(--MI_THEME-panel)';
}
},
-} as Directive;
+} as Directive<HTMLElement>;
diff --git a/packages/frontend/src/directives/ripple.ts b/packages/frontend/src/directives/ripple.ts
index 614cd37011..bacf49ab72 100644
--- a/packages/frontend/src/directives/ripple.ts
+++ b/packages/frontend/src/directives/ripple.ts
@@ -3,12 +3,13 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
+import type { Directive } from 'vue';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
import { prefer } from '@/preferences.js';
import { popup } from '@/os.js';
-export default {
- mounted(el, binding, vn) {
+export const rippleDirective = {
+ mounted(el, binding) {
// 明示的に false であればバインドしない
if (binding.value === false) return;
if (!prefer.s.animation) return;
@@ -24,4 +25,4 @@ export default {
});
});
},
-};
+} as Directive<HTMLElement, boolean | undefined>;
diff --git a/packages/frontend/src/directives/tooltip.ts b/packages/frontend/src/directives/tooltip.ts
index 62aecbc87c..9cfa8d657d 100644
--- a/packages/frontend/src/directives/tooltip.ts
+++ b/packages/frontend/src/directives/tooltip.ts
@@ -14,13 +14,30 @@ import { popup, alert } from '@/os.js';
const start = isTouchUsing ? 'touchstart' : 'mouseenter';
const end = isTouchUsing ? 'touchend' : 'mouseleave';
-export default {
- mounted(el: HTMLElement, binding, vn) {
+type TooltipDirectiveState = {
+ text: string;
+ _close: null | (() => void);
+ showTimer: number | null;
+ hideTimer: number | null;
+ checkTimer: number | null;
+ show: () => void;
+ close: () => void;
+};
+
+interface TooltipDirectiveElement extends HTMLElement {
+ _tooltipDirective_?: TooltipDirectiveState;
+}
+
+type TooltipDirectiveModifiers = 'left' | 'right' | 'top' | 'bottom' | 'mfm' | 'noDelay';
+type TooltipDirectiveArg = 'dialog';
+
+export const tooltipDirective = {
+ mounted(el, binding) {
const delay = binding.modifiers.noDelay ? 0 : 100;
- const self = (el as any)._tooltipDirective_ = {} as any;
+ const self = el._tooltipDirective_ = {} as TooltipDirectiveState;
- self.text = binding.value as string;
+ self.text = binding.value;
self._close = null;
self.showTimer = null;
self.hideTimer = null;
@@ -28,7 +45,7 @@ export default {
self.close = () => {
if (self._close) {
- window.clearInterval(self.checkTimer);
+ if (self.checkTimer) window.clearInterval(self.checkTimer);
self._close();
self._close = null;
}
@@ -72,8 +89,8 @@ export default {
});
el.addEventListener(start, (ev) => {
- window.clearTimeout(self.showTimer);
- window.clearTimeout(self.hideTimer);
+ if (self.showTimer) window.clearTimeout(self.showTimer);
+ if (self.hideTimer) window.clearTimeout(self.hideTimer);
if (delay === 0) {
self.show();
} else {
@@ -82,8 +99,8 @@ export default {
}, { passive: true });
el.addEventListener(end, () => {
- window.clearTimeout(self.showTimer);
- window.clearTimeout(self.hideTimer);
+ if (self.showTimer) window.clearTimeout(self.showTimer);
+ if (self.hideTimer) window.clearTimeout(self.hideTimer);
if (delay === 0) {
self.close();
} else {
@@ -92,18 +109,23 @@ export default {
}, { passive: true });
el.addEventListener('click', () => {
- window.clearTimeout(self.showTimer);
+ if (self.showTimer) window.clearTimeout(self.showTimer);
self.close();
});
},
updated(el, binding) {
const self = el._tooltipDirective_;
+ if (self == null) return;
self.text = binding.value as string;
},
- unmounted(el, binding, vn) {
+ unmounted(el) {
const self = el._tooltipDirective_;
- window.clearInterval(self.checkTimer);
+ if (self == null) return;
+ if (self.showTimer) window.clearTimeout(self.showTimer);
+ if (self.hideTimer) window.clearTimeout(self.hideTimer);
+ if (self.checkTimer) window.clearTimeout(self.checkTimer);
+ self.close();
},
-} as Directive;
+} as Directive<TooltipDirectiveElement, string, TooltipDirectiveModifiers, TooltipDirectiveArg>;
diff --git a/packages/frontend/src/directives/user-preview.ts b/packages/frontend/src/directives/user-preview.ts
index b11ef8f088..76e345a108 100644
--- a/packages/frontend/src/directives/user-preview.ts
+++ b/packages/frontend/src/directives/user-preview.ts
@@ -5,18 +5,19 @@
import { defineAsyncComponent, ref } from 'vue';
import type { Directive } from 'vue';
+import * as Misskey from 'misskey-js';
import { popup } from '@/os.js';
import { isTouchUsing } from '@/utility/touch.js';
export class UserPreview {
- private el;
- private user;
- private showTimer;
- private hideTimer;
- private checkTimer;
- private promise;
+ private el: HTMLElement;
+ private user: string | Misskey.entities.UserDetailed;
+ private showTimer: number | null = null;
+ private hideTimer: number | null = null;
+ private checkTimer: number | null = null;
+ private promise: null | { cancel: () => void } = null;
- constructor(el, user) {
+ constructor(el: HTMLElement, user: string | Misskey.entities.UserDetailed) {
this.el = el;
this.user = user;
@@ -43,10 +44,10 @@ export class UserPreview {
source: this.el,
}, {
mouseover: () => {
- window.clearTimeout(this.hideTimer);
+ if (this.hideTimer) window.clearTimeout(this.hideTimer);
},
mouseleave: () => {
- window.clearTimeout(this.showTimer);
+ if (this.showTimer) window.clearTimeout(this.showTimer);
this.hideTimer = window.setTimeout(this.close, 500);
},
closed: () => dispose(),
@@ -60,8 +61,8 @@ export class UserPreview {
this.checkTimer = window.setInterval(() => {
if (!window.document.body.contains(this.el)) {
- window.clearTimeout(this.showTimer);
- window.clearTimeout(this.hideTimer);
+ if (this.showTimer) window.clearTimeout(this.showTimer);
+ if (this.hideTimer) window.clearTimeout(this.hideTimer);
this.close();
}
}, 1000);
@@ -69,26 +70,26 @@ export class UserPreview {
private close() {
if (this.promise) {
- window.clearInterval(this.checkTimer);
+ if (this.checkTimer) window.clearInterval(this.checkTimer);
this.promise.cancel();
this.promise = null;
}
}
private onMouseover() {
- window.clearTimeout(this.showTimer);
- window.clearTimeout(this.hideTimer);
+ if (this.showTimer) window.clearTimeout(this.showTimer);
+ if (this.hideTimer) window.clearTimeout(this.hideTimer);
this.showTimer = window.setTimeout(this.show, 500);
}
private onMouseleave() {
- window.clearTimeout(this.showTimer);
- window.clearTimeout(this.hideTimer);
+ if (this.showTimer) window.clearTimeout(this.showTimer);
+ if (this.hideTimer) window.clearTimeout(this.hideTimer);
this.hideTimer = window.setTimeout(this.close, 500);
}
private onClick() {
- window.clearTimeout(this.showTimer);
+ if (this.showTimer) window.clearTimeout(this.showTimer);
this.close();
}
@@ -105,8 +106,14 @@ export class UserPreview {
}
}
-export default {
- mounted(el: HTMLElement, binding, vn) {
+interface UserPreviewDirectiveElement extends HTMLElement {
+ _userPreviewDirective_?: {
+ preview: UserPreview;
+ };
+}
+
+export const userPreviewDirective = {
+ mounted(el, binding) {
if (binding.value == null) return;
if (isTouchUsing) return;
@@ -117,10 +124,11 @@ export default {
self.preview = new UserPreview(el, binding.value);
},
- unmounted(el, binding, vn) {
+ unmounted(el, binding) {
if (binding.value == null) return;
const self = el._userPreviewDirective_;
+ if (self == null) return;
self.preview.detach();
},
-} as Directive;
+} as Directive<UserPreviewDirectiveElement, string | Misskey.entities.UserDetailed>;