Merge 166fa464a8
into e0a83e9c9e
This commit is contained in:
commit
641b29f997
packages/frontend
|
@ -64,13 +64,13 @@ initialize({
|
|||
initLocalStorage();
|
||||
queueMicrotask(() => {
|
||||
Promise.all([
|
||||
import('../src/components'),
|
||||
import('../src/directives'),
|
||||
import('../src/widgets'),
|
||||
import('../src/scripts/theme'),
|
||||
import('../src/store'),
|
||||
import('../src/os'),
|
||||
]).then(([{ default: components }, { default: directives }, { default: widgets }, { applyTheme }, { defaultStore }, os]) => {
|
||||
import('../src/directives/index.js'),
|
||||
import('../src/components/index.js'),
|
||||
import('../src/widgets/index.js'),
|
||||
import('../src/scripts/theme.js'),
|
||||
import('../src/store.js'),
|
||||
import('../src/os.js'),
|
||||
]).then(([{ default: directives }, { default: components }, { default: widgets }, { applyTheme }, { defaultStore }, os]) => {
|
||||
setup((app) => {
|
||||
moduleInitialized = true;
|
||||
if (app[appInitialized]) {
|
||||
|
@ -78,8 +78,8 @@ queueMicrotask(() => {
|
|||
}
|
||||
app[appInitialized] = true;
|
||||
loadTheme(applyTheme);
|
||||
components(app);
|
||||
directives(app);
|
||||
components(app);
|
||||
widgets(app);
|
||||
misskeyOS = os;
|
||||
if (isChromatic()) {
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
import { computed, watch, version as vueVersion, App } from 'vue';
|
||||
import { compareVersions } from 'compare-versions';
|
||||
import { version, lang, updateLocale, locale } from '@@/js/config.js';
|
||||
import widgets from '@/widgets/index.js';
|
||||
import directives from '@/directives/index.js';
|
||||
import components from '@/components/index.js';
|
||||
import widgets from '@/widgets/index.js';
|
||||
import { applyTheme } from '@/scripts/theme.js';
|
||||
import { isDeviceDarkmode } from '@/scripts/is-device-darkmode.js';
|
||||
import { updateI18n, i18n } from '@/i18n.js';
|
||||
|
@ -243,9 +243,9 @@ export async function common(createVue: () => App<Element>) {
|
|||
app.config.performance = true;
|
||||
}
|
||||
|
||||
widgets(app);
|
||||
directives(app);
|
||||
components(app);
|
||||
widgets(app);
|
||||
|
||||
// https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210
|
||||
// なぜか2回実行されることがあるため、mountするdivを1つに制限する
|
||||
|
|
|
@ -54,7 +54,7 @@ import { defineAsyncComponent, ref } from 'vue';
|
|||
import { v4 as uuid } from 'uuid';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { widgets as widgetDefs } from '@/widgets/index.js';
|
||||
import { widgetDefs } from '@/widgets/index.js';
|
||||
import * as os from '@/os.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { isLink } from '@@/js/is-link.js';
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { App } from 'vue';
|
||||
import type { App } from 'vue';
|
||||
|
||||
import Mfm from './global/MkMfm.js';
|
||||
import MkA from './global/MkA.vue';
|
||||
|
@ -34,28 +34,28 @@ export default function(app: App) {
|
|||
}
|
||||
|
||||
export const components = {
|
||||
I18n: I18n,
|
||||
RouterView: RouterView,
|
||||
Mfm: Mfm,
|
||||
MkA: MkA,
|
||||
MkAcct: MkAcct,
|
||||
MkAvatar: MkAvatar,
|
||||
MkEmoji: MkEmoji,
|
||||
MkCondensedLine: MkCondensedLine,
|
||||
MkCustomEmoji: MkCustomEmoji,
|
||||
MkUserName: MkUserName,
|
||||
MkEllipsis: MkEllipsis,
|
||||
MkTime: MkTime,
|
||||
MkUrl: MkUrl,
|
||||
MkLoading: MkLoading,
|
||||
MkError: MkError,
|
||||
MkAd: MkAd,
|
||||
MkPageHeader: MkPageHeader,
|
||||
MkSpacer: MkSpacer,
|
||||
MkFooterSpacer: MkFooterSpacer,
|
||||
MkStickyContainer: MkStickyContainer,
|
||||
MkLazy: MkLazy,
|
||||
};
|
||||
I18n,
|
||||
RouterView,
|
||||
Mfm,
|
||||
MkA,
|
||||
MkAcct,
|
||||
MkAd,
|
||||
MkAvatar,
|
||||
MkCondensedLine,
|
||||
MkCustomEmoji,
|
||||
MkEllipsis,
|
||||
MkEmoji,
|
||||
MkError,
|
||||
MkFooterSpacer,
|
||||
MkLazy,
|
||||
MkLoading,
|
||||
MkPageHeader,
|
||||
MkSpacer,
|
||||
MkStickyContainer,
|
||||
MkTime,
|
||||
MkUrl,
|
||||
MkUserName,
|
||||
} as const;
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
|
@ -64,21 +64,21 @@ declare module '@vue/runtime-core' {
|
|||
Mfm: typeof Mfm;
|
||||
MkA: typeof MkA;
|
||||
MkAcct: typeof MkAcct;
|
||||
MkAd: typeof MkAd;
|
||||
MkAvatar: typeof MkAvatar;
|
||||
MkEmoji: typeof MkEmoji;
|
||||
MkCondensedLine: typeof MkCondensedLine;
|
||||
MkCustomEmoji: typeof MkCustomEmoji;
|
||||
MkUserName: typeof MkUserName;
|
||||
MkEllipsis: typeof MkEllipsis;
|
||||
MkTime: typeof MkTime;
|
||||
MkUrl: typeof MkUrl;
|
||||
MkLoading: typeof MkLoading;
|
||||
MkEmoji: typeof MkEmoji;
|
||||
MkError: typeof MkError;
|
||||
MkAd: typeof MkAd;
|
||||
MkFooterSpacer: typeof MkFooterSpacer;
|
||||
MkLazy: typeof MkLazy;
|
||||
MkLoading: typeof MkLoading;
|
||||
MkPageHeader: typeof MkPageHeader;
|
||||
MkSpacer: typeof MkSpacer;
|
||||
MkFooterSpacer: typeof MkFooterSpacer;
|
||||
MkStickyContainer: typeof MkStickyContainer;
|
||||
MkLazy: typeof MkLazy;
|
||||
MkTime: typeof MkTime;
|
||||
MkUrl: typeof MkUrl;
|
||||
MkUserName: typeof MkUserName;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,11 +3,18 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Directive } from 'vue';
|
||||
import { getBgColor } from '@/scripts/get-bg-color.js';
|
||||
import type { ObjectDirective } from 'vue';
|
||||
|
||||
type VAdaptiveBg = ObjectDirective<HTMLElement, null | undefined>;
|
||||
|
||||
export const vAdaptiveBg = {
|
||||
async mounted(src) {
|
||||
const [
|
||||
{ getBgColor },
|
||||
] = await Promise.all([
|
||||
import('@/scripts/get-bg-color.js'),
|
||||
]);
|
||||
|
||||
export default {
|
||||
mounted(src, binding, vn) {
|
||||
const parentBg = getBgColor(src.parentElement) ?? 'transparent';
|
||||
|
||||
const myBg = window.getComputedStyle(src).backgroundColor;
|
||||
|
@ -18,4 +25,4 @@ export default {
|
|||
src.style.backgroundColor = myBg;
|
||||
}
|
||||
},
|
||||
} as Directive;
|
||||
} satisfies VAdaptiveBg as VAdaptiveBg;
|
||||
|
|
|
@ -3,11 +3,18 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Directive } from 'vue';
|
||||
import { getBgColor } from '@/scripts/get-bg-color.js';
|
||||
import type { ObjectDirective } from 'vue';
|
||||
|
||||
type VAdaptiveBorder = ObjectDirective<HTMLElement, null | undefined>;
|
||||
|
||||
export const vAdaptiveBorder = {
|
||||
async mounted(src) {
|
||||
const [
|
||||
{ getBgColor },
|
||||
] = await Promise.all([
|
||||
import('@/scripts/get-bg-color.js'),
|
||||
]);
|
||||
|
||||
export default {
|
||||
mounted(src, binding, vn) {
|
||||
const parentBg = getBgColor(src.parentElement) ?? 'transparent';
|
||||
|
||||
const myBg = window.getComputedStyle(src).backgroundColor;
|
||||
|
@ -18,4 +25,4 @@ export default {
|
|||
src.style.borderColor = myBg;
|
||||
}
|
||||
},
|
||||
} as Directive;
|
||||
} satisfies VAdaptiveBorder as VAdaptiveBorder;
|
||||
|
|
|
@ -3,21 +3,25 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Directive } from 'vue';
|
||||
import type { ObjectDirective } from 'vue';
|
||||
|
||||
export default {
|
||||
beforeMount(src, binding, vn) {
|
||||
type VAnim = ObjectDirective<HTMLElement, number | null | undefined>;
|
||||
|
||||
export const vAnim = {
|
||||
async beforeMount(src) {
|
||||
src.style.opacity = '0';
|
||||
src.style.transform = 'scale(0.9)';
|
||||
// ページネーションと相性が悪いので
|
||||
//if (typeof binding.value === 'number') src.style.transitionDelay = `${binding.value * 30}ms`;
|
||||
// if (typeof binding.value === 'number') {
|
||||
// src.style.transitionDelay = `${binding.value * 30}ms`;
|
||||
// }
|
||||
src.classList.add('_zoom');
|
||||
},
|
||||
|
||||
mounted(src, binding, vn) {
|
||||
async mounted(src) {
|
||||
window.setTimeout(() => {
|
||||
src.style.opacity = '1';
|
||||
src.style.transform = 'none';
|
||||
}, 1);
|
||||
},
|
||||
} as Directive;
|
||||
} satisfies VAnim as VAnim;
|
||||
|
|
|
@ -3,25 +3,29 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Directive } from 'vue';
|
||||
import type { ObjectDirective } from 'vue';
|
||||
|
||||
export default {
|
||||
mounted(src, binding, vn) {
|
||||
type VAppear = ObjectDirective<HTMLElement, (() => unknown) | null | undefined>;
|
||||
|
||||
export const vAppear = {
|
||||
async mounted(src, binding) {
|
||||
const fn = binding.value;
|
||||
if (fn == null) return;
|
||||
|
||||
const observer = new IntersectionObserver(entries => {
|
||||
if (entries.some(entry => entry.isIntersecting)) {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
if (entries.some((entry) => entry.isIntersecting)) {
|
||||
fn();
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(src);
|
||||
|
||||
//@ts-expect-error HTMLElementにプロパティを追加している
|
||||
src._observer_ = observer;
|
||||
},
|
||||
|
||||
unmounted(src, binding, vn) {
|
||||
if (src._observer_) src._observer_.disconnect();
|
||||
async unmounted(src) {
|
||||
//@ts-expect-error HTMLElementにプロパティを追加している
|
||||
src._observer_?.disconnect();
|
||||
},
|
||||
} as Directive;
|
||||
} satisfies VAppear as VAppear;
|
||||
|
|
|
@ -3,20 +3,27 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Directive } from 'vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import type { ObjectDirective } from 'vue';
|
||||
|
||||
type VClickAnime = ObjectDirective<HTMLElement, null | undefined>;
|
||||
|
||||
export const vClickAnime = {
|
||||
async mounted(src) {
|
||||
const [
|
||||
{ defaultStore },
|
||||
] = await Promise.all([
|
||||
import('@/store.js'),
|
||||
]);
|
||||
|
||||
export default {
|
||||
mounted(el: HTMLElement, binding, vn) {
|
||||
if (!defaultStore.state.animation) return;
|
||||
|
||||
const target = el.children[0];
|
||||
const target = src.children[0];
|
||||
|
||||
if (target == null) return;
|
||||
|
||||
target.classList.add('_anime_bounce_standBy');
|
||||
|
||||
el.addEventListener('mousedown', () => {
|
||||
src.addEventListener('mousedown', () => {
|
||||
target.classList.remove('_anime_bounce');
|
||||
|
||||
target.classList.add('_anime_bounce_standBy');
|
||||
|
@ -27,14 +34,14 @@ export default {
|
|||
});
|
||||
});
|
||||
|
||||
el.addEventListener('click', () => {
|
||||
src.addEventListener('click', () => {
|
||||
target.classList.add('_anime_bounce');
|
||||
target.classList.remove('_anime_bounce_ready');
|
||||
});
|
||||
|
||||
el.addEventListener('animationend', () => {
|
||||
src.addEventListener('animationend', () => {
|
||||
target.classList.remove('_anime_bounce');
|
||||
target.classList.add('_anime_bounce_standBy');
|
||||
});
|
||||
},
|
||||
} as Directive;
|
||||
} satisfies VClickAnime as VClickAnime;
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Directive } from 'vue';
|
||||
import { getScrollContainer, getScrollPosition } from '@@/js/scroll.js';
|
||||
|
||||
export default {
|
||||
mounted(src, binding, vn) {
|
||||
if (binding.value === false) return;
|
||||
|
||||
let isBottom = true;
|
||||
|
||||
const container = getScrollContainer(src)!;
|
||||
container.addEventListener('scroll', () => {
|
||||
const pos = getScrollPosition(container);
|
||||
const viewHeight = container.clientHeight;
|
||||
const height = container.scrollHeight;
|
||||
isBottom = (pos + viewHeight > height - 32);
|
||||
}, { passive: true });
|
||||
container.scrollTop = container.scrollHeight;
|
||||
|
||||
const ro = new ResizeObserver((entries, observer) => {
|
||||
if (isBottom) {
|
||||
const height = container.scrollHeight;
|
||||
container.scrollTop = height;
|
||||
}
|
||||
});
|
||||
|
||||
ro.observe(src);
|
||||
|
||||
// TODO: 新たにプロパティを作るのをやめMapを使う
|
||||
src._ro_ = ro;
|
||||
},
|
||||
|
||||
unmounted(src, binding, vn) {
|
||||
if (src._ro_) src._ro_.unobserve(src);
|
||||
},
|
||||
} as Directive;
|
|
@ -3,15 +3,41 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Directive } from 'vue';
|
||||
import type { ObjectDirective } from 'vue';
|
||||
|
||||
const mountings = new Map<Element, {
|
||||
const mountings = new Map<HTMLElement, {
|
||||
resize: ResizeObserver;
|
||||
intersection?: IntersectionObserver;
|
||||
fn: (w: number, h: number) => void;
|
||||
}>();
|
||||
|
||||
function calc(src: Element) {
|
||||
type VGetSize = ObjectDirective<HTMLElement, ((w: number, h: number) => unknown) | null | undefined>;
|
||||
|
||||
export const vGetSize = {
|
||||
async mounted(src, binding) {
|
||||
if (!binding.value) return;
|
||||
|
||||
const resize = new ResizeObserver(() => {
|
||||
calc(src);
|
||||
});
|
||||
resize.observe(src);
|
||||
|
||||
mountings.set(src, { resize, fn: binding.value });
|
||||
calc(src);
|
||||
},
|
||||
|
||||
async unmounted(src, binding) {
|
||||
if (!binding.value) return;
|
||||
binding.value(0, 0);
|
||||
const info = mountings.get(src);
|
||||
if (!info) return;
|
||||
info.resize.disconnect();
|
||||
if (info.intersection) info.intersection.disconnect();
|
||||
mountings.delete(src);
|
||||
},
|
||||
} satisfies VGetSize as VGetSize;
|
||||
|
||||
function calc(src: HTMLElement) {
|
||||
const info = mountings.get(src);
|
||||
const height = src.clientHeight;
|
||||
const width = src.clientWidth;
|
||||
|
@ -22,8 +48,8 @@ function calc(src: Element) {
|
|||
if (!height) {
|
||||
// IntersectionObserverで表示検出する
|
||||
if (!info.intersection) {
|
||||
info.intersection = new IntersectionObserver(entries => {
|
||||
if (entries.some(entry => entry.isIntersecting)) calc(src);
|
||||
info.intersection = new IntersectionObserver((entries) => {
|
||||
if (entries.some((entry) => entry.isIntersecting)) calc(src);
|
||||
});
|
||||
}
|
||||
info.intersection.observe(src);
|
||||
|
@ -36,24 +62,3 @@ function calc(src: Element) {
|
|||
|
||||
info.fn(width, height);
|
||||
}
|
||||
|
||||
export default {
|
||||
mounted(src, binding, vn) {
|
||||
const resize = new ResizeObserver((entries, observer) => {
|
||||
calc(src);
|
||||
});
|
||||
resize.observe(src);
|
||||
|
||||
mountings.set(src, { resize, fn: binding.value });
|
||||
calc(src);
|
||||
},
|
||||
|
||||
unmounted(src, binding, vn) {
|
||||
binding.value(0, 0);
|
||||
const info = mountings.get(src);
|
||||
if (!info) return;
|
||||
info.resize.disconnect();
|
||||
if (info.intersection) info.intersection.disconnect();
|
||||
mountings.delete(src);
|
||||
},
|
||||
} as Directive<Element, (w: number, h: number) => void>;
|
||||
|
|
|
@ -3,27 +3,43 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Directive } from 'vue';
|
||||
import { makeHotkey } from '@/scripts/hotkey.js';
|
||||
import type { ObjectDirective } from 'vue';
|
||||
import type { Keymap } from '@/scripts/hotkey.js';
|
||||
|
||||
export default {
|
||||
mounted(el, binding) {
|
||||
el._hotkey_global = binding.modifiers.global === true;
|
||||
type VHotkey = ObjectDirective<HTMLElement, Keymap | null | undefined, 'global'>;
|
||||
|
||||
el._keyHandler = makeHotkey(binding.value);
|
||||
export const vHotkey = {
|
||||
async mounted(src, binding) {
|
||||
const [
|
||||
{ makeHotkey },
|
||||
] = await Promise.all([
|
||||
import('@/scripts/hotkey.js'),
|
||||
]);
|
||||
|
||||
if (el._hotkey_global) {
|
||||
document.addEventListener('keydown', el._keyHandler, { passive: false });
|
||||
//@ts-expect-error HTMLElementにプロパティを追加している
|
||||
src._hotkey_global = binding.modifiers.global === true;
|
||||
|
||||
//@ts-expect-error HTMLElementにプロパティを追加している
|
||||
src._keyHandler = makeHotkey(binding.value);
|
||||
|
||||
//@ts-expect-error HTMLElementにプロパティを追加している
|
||||
if (src._hotkey_global) {
|
||||
//@ts-expect-error HTMLElementにプロパティを追加している
|
||||
document.addEventListener('keydown', src._keyHandler, { passive: false });
|
||||
} else {
|
||||
el.addEventListener('keydown', el._keyHandler, { passive: false });
|
||||
//@ts-expect-error HTMLElementにプロパティを追加している
|
||||
src.addEventListener('keydown', src._keyHandler, { passive: false });
|
||||
}
|
||||
},
|
||||
|
||||
unmounted(el) {
|
||||
if (el._hotkey_global) {
|
||||
document.removeEventListener('keydown', el._keyHandler);
|
||||
async unmounted(src) {
|
||||
//@ts-expect-error HTMLElementにプロパティを追加している
|
||||
if (src._hotkey_global) {
|
||||
//@ts-expect-error HTMLElementにプロパティを追加している
|
||||
document.removeEventListener('keydown', src._keyHandler);
|
||||
} else {
|
||||
el.removeEventListener('keydown', el._keyHandler);
|
||||
//@ts-expect-error HTMLElementにプロパティを追加している
|
||||
src.removeEventListener('keydown', src._keyHandler);
|
||||
}
|
||||
},
|
||||
} as Directive;
|
||||
} satisfies VHotkey as VHotkey;
|
||||
|
|
|
@ -3,19 +3,19 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { App } from 'vue';
|
||||
import type { App } 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 { vAdaptiveBg } from '@/directives/adaptive-bg.js';
|
||||
import { vAdaptiveBorder } from '@/directives/adaptive-border.js';
|
||||
import { vAnim } from '@/directives/anim.js';
|
||||
import { vAppear } from '@/directives/appear.js';
|
||||
import { vClickAnime } from '@/directives/click-anime.js';
|
||||
import { vGetSize } from '@/directives/get-size.js';
|
||||
import { vHotkey } from '@/directives/hotkey.js';
|
||||
import { vPanel } from '@/directives/panel.js';
|
||||
import { vRipple } from '@/directives/ripple.js';
|
||||
import { vTooltip } from '@/directives/tooltip.js';
|
||||
import { vUserPreview } from '@/directives/user-preview.js';
|
||||
|
||||
export default function(app: App) {
|
||||
for (const [key, value] of Object.entries(directives)) {
|
||||
|
@ -24,16 +24,31 @@ 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,
|
||||
};
|
||||
'adaptive-bg': vAdaptiveBg,
|
||||
'adaptive-border': vAdaptiveBorder,
|
||||
'anim': vAnim,
|
||||
'appear': vAppear,
|
||||
'click-anime': vClickAnime,
|
||||
'get-size': vGetSize,
|
||||
'hotkey': vHotkey,
|
||||
'panel': vPanel,
|
||||
'ripple': vRipple,
|
||||
'tooltip': vTooltip,
|
||||
'user-preview': vUserPreview,
|
||||
} as const;
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalDirectives {
|
||||
vAdaptiveBg: typeof vAdaptiveBg;
|
||||
vAdaptiveBorder: typeof vAdaptiveBorder;
|
||||
vAnim: typeof vAnim;
|
||||
vAppear: typeof vAppear;
|
||||
vClickAnime: typeof vClickAnime;
|
||||
vGetSize: typeof vGetSize;
|
||||
vHotkey: typeof vHotkey;
|
||||
vPanel: typeof vPanel;
|
||||
vRipple: typeof vRipple;
|
||||
vTooltip: typeof vTooltip;
|
||||
vUserPreview: typeof vUserPreview;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,11 +3,18 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Directive } from 'vue';
|
||||
import { getBgColor } from '@/scripts/get-bg-color.js';
|
||||
import type { ObjectDirective } from 'vue';
|
||||
|
||||
type VPanel = ObjectDirective<HTMLElement, null | undefined>;
|
||||
|
||||
export const vPanel = {
|
||||
async mounted(src) {
|
||||
const [
|
||||
{ getBgColor },
|
||||
] = await Promise.all([
|
||||
import('@/scripts/get-bg-color.js'),
|
||||
]);
|
||||
|
||||
export default {
|
||||
mounted(src, binding, vn) {
|
||||
const parentBg = getBgColor(src.parentElement) ?? 'transparent';
|
||||
|
||||
const myBg = getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-panel');
|
||||
|
@ -18,4 +25,4 @@ export default {
|
|||
src.style.backgroundColor = 'var(--MI_THEME-panel)';
|
||||
}
|
||||
},
|
||||
} as Directive;
|
||||
} satisfies VPanel as VPanel;
|
||||
|
|
|
@ -3,23 +3,31 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { ObjectDirective } from 'vue';
|
||||
import MkRippleEffect from '@/components/MkRippleEffect.vue';
|
||||
import { popup } from '@/os.js';
|
||||
|
||||
export default {
|
||||
mounted(el, binding, vn) {
|
||||
type VRipple = ObjectDirective<HTMLElement, boolean | null | undefined>;
|
||||
|
||||
export const vRipple = {
|
||||
async mounted(src, binding) {
|
||||
const [
|
||||
{ popup },
|
||||
] = await Promise.all([
|
||||
import('@/os.js'),
|
||||
]);
|
||||
|
||||
// 明示的に false であればバインドしない
|
||||
if (binding.value === false) return;
|
||||
|
||||
el.addEventListener('click', () => {
|
||||
const rect = el.getBoundingClientRect();
|
||||
src.addEventListener('click', () => {
|
||||
const rect = src.getBoundingClientRect();
|
||||
|
||||
const x = rect.left + (el.offsetWidth / 2);
|
||||
const y = rect.top + (el.offsetHeight / 2);
|
||||
const x = rect.left + (src.offsetWidth / 2);
|
||||
const y = rect.top + (src.offsetHeight / 2);
|
||||
|
||||
const { dispose } = popup(MkRippleEffect, { x, y }, {
|
||||
end: () => dispose(),
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
} satisfies VRipple as VRipple;
|
||||
|
|
|
@ -6,18 +6,27 @@
|
|||
// TODO: useTooltip関数使うようにしたい
|
||||
// ただディレクティブ内でonUnmountedなどのcomposition api使えるのか不明
|
||||
|
||||
import { defineAsyncComponent, Directive, ref } from 'vue';
|
||||
import { isTouchUsing } from '@/scripts/touch.js';
|
||||
import { popup, alert } from '@/os.js';
|
||||
import { type ObjectDirective, defineAsyncComponent, ref } from 'vue';
|
||||
|
||||
const start = isTouchUsing ? 'touchstart' : 'mouseenter';
|
||||
const end = isTouchUsing ? 'touchend' : 'mouseleave';
|
||||
type VTooltip = ObjectDirective<HTMLElement, string | null | undefined, 'noDelay' | 'mfm' | 'top' | 'right' | 'bottom' | 'left', 'dialog'>;
|
||||
|
||||
export const vTooltip = {
|
||||
async mounted(src, binding) {
|
||||
const [
|
||||
{ alert, popup },
|
||||
{ isTouchUsing },
|
||||
] = await Promise.all([
|
||||
import('@/os.js'),
|
||||
import('@/scripts/touch.js'),
|
||||
]);
|
||||
|
||||
const start = isTouchUsing ? 'touchstart' : 'mouseenter';
|
||||
const end = isTouchUsing ? 'touchend' : 'mouseleave';
|
||||
|
||||
export default {
|
||||
mounted(el: HTMLElement, binding, vn) {
|
||||
const delay = binding.modifiers.noDelay ? 0 : 100;
|
||||
|
||||
const self = (el as any)._tooltipDirective_ = {} as any;
|
||||
//@ts-expect-error HTMLElementにプロパティを追加している
|
||||
const self = src._tooltipDirective_ = {} as any;
|
||||
|
||||
self.text = binding.value as string;
|
||||
self._close = null;
|
||||
|
@ -34,19 +43,19 @@ export default {
|
|||
};
|
||||
|
||||
if (binding.arg === 'dialog') {
|
||||
el.addEventListener('click', (ev) => {
|
||||
src.addEventListener('click', (ev) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
alert({
|
||||
type: 'info',
|
||||
text: binding.value,
|
||||
text: binding.value ?? '',
|
||||
});
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
self.show = () => {
|
||||
if (!document.body.contains(el)) return;
|
||||
if (!document.body.contains(src)) return;
|
||||
if (self._close) return;
|
||||
if (self.text == null) return;
|
||||
|
||||
|
@ -56,7 +65,7 @@ export default {
|
|||
text: self.text,
|
||||
asMfm: binding.modifiers.mfm,
|
||||
direction: binding.modifiers.left ? 'left' : binding.modifiers.right ? 'right' : binding.modifiers.top ? 'top' : binding.modifiers.bottom ? 'bottom' : 'top',
|
||||
targetElement: el,
|
||||
targetElement: src,
|
||||
}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
|
@ -66,13 +75,13 @@ export default {
|
|||
};
|
||||
};
|
||||
|
||||
el.addEventListener('selectstart', ev => {
|
||||
src.addEventListener('selectstart', (ev) => {
|
||||
ev.preventDefault();
|
||||
});
|
||||
|
||||
el.addEventListener(start, (ev) => {
|
||||
window.clearTimeout(self.showTimer);
|
||||
window.clearTimeout(self.hideTimer);
|
||||
src.addEventListener(start, () => {
|
||||
if (self.showTimer != null) window.clearTimeout(self.showTimer);
|
||||
if (self.hideTimer != null) window.clearTimeout(self.hideTimer);
|
||||
if (delay === 0) {
|
||||
self.show();
|
||||
} else {
|
||||
|
@ -80,9 +89,9 @@ export default {
|
|||
}
|
||||
}, { passive: true });
|
||||
|
||||
el.addEventListener(end, () => {
|
||||
window.clearTimeout(self.showTimer);
|
||||
window.clearTimeout(self.hideTimer);
|
||||
src.addEventListener(end, () => {
|
||||
if (self.showTimer != null) window.clearTimeout(self.showTimer);
|
||||
if (self.hideTimer != null) window.clearTimeout(self.hideTimer);
|
||||
if (delay === 0) {
|
||||
self.close();
|
||||
} else {
|
||||
|
@ -90,19 +99,21 @@ export default {
|
|||
}
|
||||
}, { passive: true });
|
||||
|
||||
el.addEventListener('click', () => {
|
||||
window.clearTimeout(self.showTimer);
|
||||
src.addEventListener('click', () => {
|
||||
if (self.showTimer != null) window.clearTimeout(self.showTimer);
|
||||
self.close();
|
||||
});
|
||||
},
|
||||
|
||||
updated(el, binding) {
|
||||
const self = el._tooltipDirective_;
|
||||
async updated(src, binding) {
|
||||
//@ts-expect-error HTMLElementにプロパティを追加している
|
||||
const self = src._tooltipDirective_;
|
||||
self.text = binding.value as string;
|
||||
},
|
||||
|
||||
unmounted(el, binding, vn) {
|
||||
const self = el._tooltipDirective_;
|
||||
window.clearInterval(self.checkTimer);
|
||||
async unmounted(src) {
|
||||
//@ts-expect-error HTMLElementにプロパティを追加している
|
||||
const self = src._tooltipDirective_;
|
||||
if (self.checkTimer != null) window.clearInterval(self.checkTimer);
|
||||
},
|
||||
} as Directive;
|
||||
} satisfies VTooltip as VTooltip;
|
||||
|
|
|
@ -3,10 +3,32 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { defineAsyncComponent, Directive, ref } from 'vue';
|
||||
import { type ObjectDirective, defineAsyncComponent, ref } from 'vue';
|
||||
import { popup } from '@/os.js';
|
||||
|
||||
export class UserPreview {
|
||||
type VUserPreview = ObjectDirective<HTMLElement, string | null | undefined>;
|
||||
|
||||
export const vUserPreview = {
|
||||
async mounted(src, binding) {
|
||||
if (binding.value == null) return;
|
||||
|
||||
// TODO: 新たにプロパティを作るのをやめMapを使う
|
||||
// ただメモリ的には↓の方が省メモリかもしれないので検討中
|
||||
const self = (src as any)._userPreviewDirective_ = {} as any;
|
||||
|
||||
self.preview = new UserPreview(src, binding.value);
|
||||
},
|
||||
|
||||
async unmounted(src, binding) {
|
||||
if (binding.value == null) return;
|
||||
|
||||
//@ts-expect-error HTMLElementにプロパティを追加している
|
||||
const self = src._userPreviewDirective_;
|
||||
self.preview.detach();
|
||||
},
|
||||
} satisfies VUserPreview as VUserPreview;
|
||||
|
||||
class UserPreview {
|
||||
private el;
|
||||
private user;
|
||||
private showTimer;
|
||||
|
@ -41,10 +63,10 @@ export class UserPreview {
|
|||
source: this.el,
|
||||
}, {
|
||||
mouseover: () => {
|
||||
window.clearTimeout(this.hideTimer);
|
||||
if (this.hideTimer != null) window.clearTimeout(this.hideTimer);
|
||||
},
|
||||
mouseleave: () => {
|
||||
window.clearTimeout(this.showTimer);
|
||||
if (this.showTimer != null) window.clearTimeout(this.showTimer);
|
||||
this.hideTimer = window.setTimeout(this.close, 500);
|
||||
},
|
||||
closed: () => dispose(),
|
||||
|
@ -58,8 +80,8 @@ export class UserPreview {
|
|||
|
||||
this.checkTimer = window.setInterval(() => {
|
||||
if (!document.body.contains(this.el)) {
|
||||
window.clearTimeout(this.showTimer);
|
||||
window.clearTimeout(this.hideTimer);
|
||||
if (this.showTimer != null) window.clearTimeout(this.showTimer);
|
||||
if (this.hideTimer != null) window.clearTimeout(this.hideTimer);
|
||||
this.close();
|
||||
}
|
||||
}, 1000);
|
||||
|
@ -67,26 +89,26 @@ export class UserPreview {
|
|||
|
||||
private close() {
|
||||
if (this.promise) {
|
||||
window.clearInterval(this.checkTimer);
|
||||
if (this.checkTimer != null) window.clearInterval(this.checkTimer);
|
||||
this.promise.cancel();
|
||||
this.promise = null;
|
||||
}
|
||||
}
|
||||
|
||||
private onMouseover() {
|
||||
window.clearTimeout(this.showTimer);
|
||||
window.clearTimeout(this.hideTimer);
|
||||
if (this.showTimer != null) window.clearTimeout(this.showTimer);
|
||||
if (this.hideTimer != null) window.clearTimeout(this.hideTimer);
|
||||
this.showTimer = window.setTimeout(this.show, 500);
|
||||
}
|
||||
|
||||
private onMouseleave() {
|
||||
window.clearTimeout(this.showTimer);
|
||||
window.clearTimeout(this.hideTimer);
|
||||
if (this.showTimer != null) window.clearTimeout(this.showTimer);
|
||||
if (this.hideTimer != null) window.clearTimeout(this.hideTimer);
|
||||
this.hideTimer = window.setTimeout(this.close, 500);
|
||||
}
|
||||
|
||||
private onClick() {
|
||||
window.clearTimeout(this.showTimer);
|
||||
if (this.showTimer != null) window.clearTimeout(this.showTimer);
|
||||
this.close();
|
||||
}
|
||||
|
||||
|
@ -102,22 +124,3 @@ export class UserPreview {
|
|||
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;
|
||||
|
|
|
@ -3,40 +3,75 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { App, defineAsyncComponent } from 'vue';
|
||||
import { type App, defineAsyncComponent } from 'vue';
|
||||
|
||||
const WidgetProfile = defineAsyncComponent(() => import('@/widgets/WidgetProfile.vue'));
|
||||
const WidgetInstanceInfo = defineAsyncComponent(() => import('@/widgets/WidgetInstanceInfo.vue'));
|
||||
const WidgetMemo = defineAsyncComponent(() => import('@/widgets/WidgetMemo.vue'));
|
||||
const WidgetNotifications = defineAsyncComponent(() => import('@/widgets/WidgetNotifications.vue'));
|
||||
const WidgetTimeline = defineAsyncComponent(() => import('@/widgets/WidgetTimeline.vue'));
|
||||
const WidgetCalendar = defineAsyncComponent(() => import('@/widgets/WidgetCalendar.vue'));
|
||||
const WidgetRss = defineAsyncComponent(() => import('@/widgets/WidgetRss.vue'));
|
||||
const WidgetRssTicker = defineAsyncComponent(() => import('@/widgets/WidgetRssTicker.vue'));
|
||||
const WidgetTrends = defineAsyncComponent(() => import('@/widgets/WidgetTrends.vue'));
|
||||
const WidgetClock = defineAsyncComponent(() => import('@/widgets/WidgetClock.vue'));
|
||||
const WidgetActivity = defineAsyncComponent(() => import('@/widgets/WidgetActivity.vue'));
|
||||
const WidgetPhotos = defineAsyncComponent(() => import('@/widgets/WidgetPhotos.vue'));
|
||||
const WidgetDigitalClock = defineAsyncComponent(() => import('@/widgets/WidgetDigitalClock.vue'));
|
||||
const WidgetUnixClock = defineAsyncComponent(() => import('@/widgets/WidgetUnixClock.vue'));
|
||||
const WidgetFederation = defineAsyncComponent(() => import('@/widgets/WidgetFederation.vue'));
|
||||
const WidgetInstanceCloud = defineAsyncComponent(() => import('@/widgets/WidgetInstanceCloud.vue'));
|
||||
const WidgetPostForm = defineAsyncComponent(() => import('@/widgets/WidgetPostForm.vue'));
|
||||
const WidgetSlideshow = defineAsyncComponent(() => import('@/widgets/WidgetSlideshow.vue'));
|
||||
const WidgetServerMetric = defineAsyncComponent(() => import('@/widgets/server-metric/index.vue'));
|
||||
const WidgetOnlineUsers = defineAsyncComponent(() => import('@/widgets/WidgetOnlineUsers.vue'));
|
||||
const WidgetJobQueue = defineAsyncComponent(() => import('@/widgets/WidgetJobQueue.vue'));
|
||||
const WidgetButton = defineAsyncComponent(() => import('@/widgets/WidgetButton.vue'));
|
||||
const WidgetAiscript = defineAsyncComponent(() => import('@/widgets/WidgetAiscript.vue'));
|
||||
const WidgetAiscriptApp = defineAsyncComponent(() => import('@/widgets/WidgetAiscriptApp.vue'));
|
||||
const WidgetAichan = defineAsyncComponent(() => import('@/widgets/WidgetAichan.vue'));
|
||||
const WidgetUserList = defineAsyncComponent(() => import('@/widgets/WidgetUserList.vue'));
|
||||
const WidgetClicker = defineAsyncComponent(() => import('@/widgets/WidgetClicker.vue'));
|
||||
const WidgetBirthdayFollowings = defineAsyncComponent(() => import('@/widgets/WidgetBirthdayFollowings.vue'));
|
||||
|
||||
export default function(app: App) {
|
||||
app.component('WidgetProfile', defineAsyncComponent(() => import('./WidgetProfile.vue')));
|
||||
app.component('WidgetInstanceInfo', defineAsyncComponent(() => import('./WidgetInstanceInfo.vue')));
|
||||
app.component('WidgetMemo', defineAsyncComponent(() => import('./WidgetMemo.vue')));
|
||||
app.component('WidgetNotifications', defineAsyncComponent(() => import('./WidgetNotifications.vue')));
|
||||
app.component('WidgetTimeline', defineAsyncComponent(() => import('./WidgetTimeline.vue')));
|
||||
app.component('WidgetCalendar', defineAsyncComponent(() => import('./WidgetCalendar.vue')));
|
||||
app.component('WidgetRss', defineAsyncComponent(() => import('./WidgetRss.vue')));
|
||||
app.component('WidgetRssTicker', defineAsyncComponent(() => import('./WidgetRssTicker.vue')));
|
||||
app.component('WidgetTrends', defineAsyncComponent(() => import('./WidgetTrends.vue')));
|
||||
app.component('WidgetClock', defineAsyncComponent(() => import('./WidgetClock.vue')));
|
||||
app.component('WidgetActivity', defineAsyncComponent(() => import('./WidgetActivity.vue')));
|
||||
app.component('WidgetPhotos', defineAsyncComponent(() => import('./WidgetPhotos.vue')));
|
||||
app.component('WidgetDigitalClock', defineAsyncComponent(() => import('./WidgetDigitalClock.vue')));
|
||||
app.component('WidgetUnixClock', defineAsyncComponent(() => import('./WidgetUnixClock.vue')));
|
||||
app.component('WidgetFederation', defineAsyncComponent(() => import('./WidgetFederation.vue')));
|
||||
app.component('WidgetPostForm', defineAsyncComponent(() => import('./WidgetPostForm.vue')));
|
||||
app.component('WidgetSlideshow', defineAsyncComponent(() => import('./WidgetSlideshow.vue')));
|
||||
app.component('WidgetServerMetric', defineAsyncComponent(() => import('./server-metric/index.vue')));
|
||||
app.component('WidgetOnlineUsers', defineAsyncComponent(() => import('./WidgetOnlineUsers.vue')));
|
||||
app.component('WidgetJobQueue', defineAsyncComponent(() => import('./WidgetJobQueue.vue')));
|
||||
app.component('WidgetInstanceCloud', defineAsyncComponent(() => import('./WidgetInstanceCloud.vue')));
|
||||
app.component('WidgetButton', defineAsyncComponent(() => import('./WidgetButton.vue')));
|
||||
app.component('WidgetAiscript', defineAsyncComponent(() => import('./WidgetAiscript.vue')));
|
||||
app.component('WidgetAiscriptApp', defineAsyncComponent(() => import('./WidgetAiscriptApp.vue')));
|
||||
app.component('WidgetAichan', defineAsyncComponent(() => import('./WidgetAichan.vue')));
|
||||
app.component('WidgetUserList', defineAsyncComponent(() => import('./WidgetUserList.vue')));
|
||||
app.component('WidgetClicker', defineAsyncComponent(() => import('./WidgetClicker.vue')));
|
||||
app.component('WidgetBirthdayFollowings', defineAsyncComponent(() => import('./WidgetBirthdayFollowings.vue')));
|
||||
for (const [key, value] of Object.entries(widgets)) {
|
||||
app.component(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
export const widgets = [
|
||||
const widgets = {
|
||||
WidgetProfile,
|
||||
WidgetInstanceInfo,
|
||||
WidgetMemo,
|
||||
WidgetNotifications,
|
||||
WidgetTimeline,
|
||||
WidgetCalendar,
|
||||
WidgetRss,
|
||||
WidgetRssTicker,
|
||||
WidgetTrends,
|
||||
WidgetClock,
|
||||
WidgetActivity,
|
||||
WidgetPhotos,
|
||||
WidgetDigitalClock,
|
||||
WidgetUnixClock,
|
||||
WidgetFederation,
|
||||
WidgetInstanceCloud,
|
||||
WidgetPostForm,
|
||||
WidgetSlideshow,
|
||||
WidgetServerMetric,
|
||||
WidgetOnlineUsers,
|
||||
WidgetJobQueue,
|
||||
WidgetButton,
|
||||
WidgetAiscript,
|
||||
WidgetAiscriptApp,
|
||||
WidgetAichan,
|
||||
WidgetUserList,
|
||||
WidgetClicker,
|
||||
WidgetBirthdayFollowings,
|
||||
} as const;
|
||||
|
||||
export const widgetDefs = [
|
||||
'profile',
|
||||
'instanceInfo',
|
||||
'memo',
|
||||
|
@ -65,4 +100,37 @@ export const widgets = [
|
|||
'userList',
|
||||
'clicker',
|
||||
'birthdayFollowings',
|
||||
];
|
||||
] as const;
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
WidgetProfile: typeof WidgetProfile;
|
||||
WidgetInstanceInfo: typeof WidgetInstanceInfo;
|
||||
WidgetMemo: typeof WidgetMemo;
|
||||
WidgetNotifications: typeof WidgetNotifications;
|
||||
WidgetTimeline: typeof WidgetTimeline;
|
||||
WidgetCalendar: typeof WidgetCalendar;
|
||||
WidgetRss: typeof WidgetRss;
|
||||
WidgetRssTicker: typeof WidgetRssTicker;
|
||||
WidgetTrends: typeof WidgetTrends;
|
||||
WidgetClock: typeof WidgetClock;
|
||||
WidgetActivity: typeof WidgetActivity;
|
||||
WidgetPhotos: typeof WidgetPhotos;
|
||||
WidgetDigitalClock: typeof WidgetDigitalClock;
|
||||
WidgetUnixClock: typeof WidgetUnixClock;
|
||||
WidgetFederation: typeof WidgetFederation;
|
||||
WidgetInstanceCloud: typeof WidgetInstanceCloud;
|
||||
WidgetPostForm: typeof WidgetPostForm;
|
||||
WidgetSlideshow: typeof WidgetSlideshow;
|
||||
WidgetServerMetric: typeof WidgetServerMetric;
|
||||
WidgetOnlineUsers: typeof WidgetOnlineUsers;
|
||||
WidgetJobQueue: typeof WidgetJobQueue;
|
||||
WidgetButton: typeof WidgetButton;
|
||||
WidgetAiscript: typeof WidgetAiscript;
|
||||
WidgetAiscriptApp: typeof WidgetAiscriptApp;
|
||||
WidgetAichan: typeof WidgetAichan;
|
||||
WidgetUserList: typeof WidgetUserList;
|
||||
WidgetClicker: typeof WidgetClicker;
|
||||
WidgetBirthdayFollowings: typeof WidgetBirthdayFollowings;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
import { describe, test, assert, afterEach } from 'vitest';
|
||||
import { render, cleanup, type RenderResult } from '@testing-library/vue';
|
||||
import { defaultStoreState } from './init.js';
|
||||
import { getEmojiName } from '@@/js/emojilist.js';
|
||||
import { components } from '@/components/index.js';
|
||||
import { getEmojiName } from '../../frontend-shared/js/emojilist.js';
|
||||
import { directives } from '@/directives/index.js';
|
||||
import { components } from '@/components/index.js';
|
||||
import MkEmoji from '@/components/global/MkEmoji.vue';
|
||||
|
||||
describe('Emoji', () => {
|
||||
|
|
|
@ -7,8 +7,8 @@ import { describe, test, assert, afterEach } from 'vitest';
|
|||
import { render, cleanup, type RenderResult } from '@testing-library/vue';
|
||||
import './init';
|
||||
import type * as Misskey from 'misskey-js';
|
||||
import { components } from '@/components/index.js';
|
||||
import { directives } from '@/directives/index.js';
|
||||
import { components } from '@/components/index.js';
|
||||
import MkMediaImage from '@/components/MkMediaImage.vue';
|
||||
|
||||
describe('MkMediaImage', () => {
|
||||
|
|
|
@ -7,8 +7,8 @@ import { describe, test, assert, afterEach } from 'vitest';
|
|||
import { render, cleanup, type RenderResult } from '@testing-library/vue';
|
||||
import './init';
|
||||
import type { summaly } from '@misskey-dev/summaly';
|
||||
import { components } from '@/components/index.js';
|
||||
import { directives } from '@/directives/index.js';
|
||||
import { components } from '@/components/index.js';
|
||||
import MkUrlPreview from '@/components/MkUrlPreview.vue';
|
||||
|
||||
type SummalyResult = Awaited<ReturnType<typeof summaly>>;
|
||||
|
|
Loading…
Reference in a new issue