/* * SPDX-FileCopyrightText: syuilo and misskey-project * SPDX-License-Identifier: AGPL-3.0-only */ 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) { this.el = el; this.user = user; this.show = this.show.bind(this); this.close = this.close.bind(this); this.onMouseover = this.onMouseover.bind(this); this.onMouseleave = this.onMouseleave.bind(this); this.onClick = this.onClick.bind(this); this.attach = this.attach.bind(this); this.detach = this.detach.bind(this); this.attach(); } private show() { if (!document.body.contains(this.el)) return; if (this.promise) return; const showing = ref(true); const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkUserPopup.vue')), { showing, q: this.user, source: this.el, }, { mouseover: () => { window.clearTimeout(this.hideTimer); }, mouseleave: () => { window.clearTimeout(this.showTimer); this.hideTimer = window.setTimeout(this.close, 500); }, closed: () => dispose(), }); this.promise = { cancel: () => { showing.value = false; }, }; this.checkTimer = window.setInterval(() => { if (!document.body.contains(this.el)) { window.clearTimeout(this.showTimer); window.clearTimeout(this.hideTimer); this.close(); } }, 1000); } private close() { if (this.promise) { window.clearInterval(this.checkTimer); this.promise.cancel(); this.promise = null; } } private onMouseover() { window.clearTimeout(this.showTimer); window.clearTimeout(this.hideTimer); this.showTimer = window.setTimeout(this.show, 500); } private onMouseleave() { window.clearTimeout(this.showTimer); window.clearTimeout(this.hideTimer); this.hideTimer = window.setTimeout(this.close, 500); } private onClick() { window.clearTimeout(this.showTimer); this.close(); } public attach() { this.el.addEventListener('mouseover', this.onMouseover); this.el.addEventListener('mouseleave', this.onMouseleave); this.el.addEventListener('click', this.onClick); } public detach() { this.el.removeEventListener('mouseover', this.onMouseover); this.el.removeEventListener('mouseleave', this.onMouseleave); this.el.removeEventListener('click', this.onClick); } } export default { mounted(el: HTMLElement, binding, vn) { if (binding.value == null) return; // TODO: 新たにプロパティを作るのをやめMapを使う // ただメモリ的には↓の方が省メモリかもしれないので検討中 const self = (el as any)._userPreviewDirective_ = {} as any; self.preview = new UserPreview(el, binding.value); }, unmounted(el, binding, vn) { if (binding.value == null) return; const self = el._userPreviewDirective_; self.preview.detach(); }, } as Directive;