summaryrefslogtreecommitdiff
path: root/packages/frontend/src/components/global
diff options
context:
space:
mode:
Diffstat (limited to 'packages/frontend/src/components/global')
-rw-r--r--packages/frontend/src/components/global/I18n.vue5
-rw-r--r--packages/frontend/src/components/global/MkA.vue24
-rw-r--r--packages/frontend/src/components/global/MkAd.stories.impl.ts28
-rw-r--r--packages/frontend/src/components/global/MkAd.vue21
-rw-r--r--packages/frontend/src/components/global/MkAvatar.stories.impl.ts3
-rw-r--r--packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkError.stories.meta.ts7
-rw-r--r--packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts7
-rw-r--r--packages/frontend/src/components/global/MkPageHeader.stories.impl.ts5
-rw-r--r--packages/frontend/src/components/global/MkPageHeader.tabs.vue1
-rw-r--r--packages/frontend/src/components/global/MkTime.stories.impl.ts14
-rw-r--r--packages/frontend/src/components/global/MkTime.vue4
-rw-r--r--packages/frontend/src/components/global/MkUrl.vue8
-rw-r--r--packages/frontend/src/components/global/MkUserName.stories.impl.ts2
14 files changed, 88 insertions, 43 deletions
diff --git a/packages/frontend/src/components/global/I18n.vue b/packages/frontend/src/components/global/I18n.vue
index 162aa2bcf8..6b7723e6ac 100644
--- a/packages/frontend/src/components/global/I18n.vue
+++ b/packages/frontend/src/components/global/I18n.vue
@@ -1,3 +1,8 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
<template>
<render/>
</template>
diff --git a/packages/frontend/src/components/global/MkA.vue b/packages/frontend/src/components/global/MkA.vue
index 61d7ac17d9..d1e9113c48 100644
--- a/packages/frontend/src/components/global/MkA.vue
+++ b/packages/frontend/src/components/global/MkA.vue
@@ -4,13 +4,17 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<a :href="to" :class="active ? activeClass : null" @click.prevent="nav" @contextmenu.prevent.stop="onContextmenu">
+<a ref="el" :href="to" :class="active ? activeClass : null" @click.prevent="nav" @contextmenu.prevent.stop="onContextmenu">
<slot></slot>
</a>
</template>
+<script lang="ts">
+export type MkABehavior = 'window' | 'browser' | null;
+</script>
+
<script lang="ts" setup>
-import { computed } from 'vue';
+import { computed, inject, shallowRef } from 'vue';
import * as os from '@/os.js';
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
import { url } from '@/config.js';
@@ -20,12 +24,18 @@ import { useRouter } from '@/router/supplier.js';
const props = withDefaults(defineProps<{
to: string;
activeClass?: null | string;
- behavior?: null | 'window' | 'browser';
+ behavior?: MkABehavior;
}>(), {
activeClass: null,
behavior: null,
});
+const behavior = props.behavior ?? inject<MkABehavior>('linkNavigationBehavior', null);
+
+const el = shallowRef<HTMLElement>();
+
+defineExpose({ $el: el });
+
const router = useRouter();
const active = computed(() => {
@@ -76,15 +86,13 @@ function openWindow() {
}
function nav(ev: MouseEvent) {
- if (props.behavior === 'browser') {
+ if (behavior === 'browser') {
location.href = props.to;
return;
}
- if (props.behavior) {
- if (props.behavior === 'window') {
- return openWindow();
- }
+ if (behavior === 'window') {
+ return openWindow();
}
if (ev.shiftKey) {
diff --git a/packages/frontend/src/components/global/MkAd.stories.impl.ts b/packages/frontend/src/components/global/MkAd.stories.impl.ts
index f6cdc2bf23..aef26ab92d 100644
--- a/packages/frontend/src/components/global/MkAd.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkAd.stories.impl.ts
@@ -4,11 +4,17 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { expect, userEvent, waitFor, within } from '@storybook/test';
import { StoryObj } from '@storybook/vue3';
import MkAd from './MkAd.vue';
+import { i18n } from '@/i18n.js';
let lock: Promise<undefined> | undefined;
+function sleep(ms: number) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
+
const common = {
render(args) {
return {
@@ -30,7 +36,6 @@ const common = {
template: '<MkAd v-bind="props" />',
};
},
- /* FIXME: disabled because it still didn’t pass after applying #11267
async play({ canvasElement, args }) {
if (lock) {
console.warn('This test is unexpectedly running twice in parallel, fix it!');
@@ -42,9 +47,11 @@ const common = {
lock = new Promise(r => resolve = r);
try {
+ // NOTE: sleep しないと何故か落ちる
+ await sleep(100);
const canvas = within(canvasElement);
const a = canvas.getByRole<HTMLAnchorElement>('link');
- await expect(a.href).toMatch(/^https?:\/\/.*#test$/);
+ // await expect(a.href).toMatch(/^https?:\/\/.*#test$/);
const img = within(a).getByRole('img');
await expect(img).toBeInTheDocument();
let buttons = canvas.getAllByRole<HTMLButtonElement>('button');
@@ -52,13 +59,14 @@ const common = {
const i = buttons[0];
await expect(i).toBeInTheDocument();
await userEvent.click(i);
- await waitFor(() => expect(canvasElement).toHaveTextContent(i18n.ts._ad.back));
+ await expect(canvasElement).toHaveTextContent(i18n.ts._ad.back);
await expect(a).not.toBeInTheDocument();
await expect(i).not.toBeInTheDocument();
buttons = canvas.getAllByRole<HTMLButtonElement>('button');
- await expect(buttons).toHaveLength(args.__hasReduce ? 2 : 1);
- const reduce = args.__hasReduce ? buttons[0] : null;
- const back = buttons[args.__hasReduce ? 1 : 0];
+ const hasReduceFrequency = args.specify?.ratio !== 0;
+ await expect(buttons).toHaveLength(hasReduceFrequency ? 2 : 1);
+ const reduce = hasReduceFrequency ? buttons[0] : null;
+ const back = buttons[hasReduceFrequency ? 1 : 0];
if (reduce) {
await expect(reduce).toBeInTheDocument();
await expect(reduce).toHaveTextContent(i18n.ts._ad.reduceFrequencyOfThisAd);
@@ -80,15 +88,16 @@ const common = {
lock = undefined;
}
},
- */
args: {
prefer: [],
specify: {
id: 'someadid',
- radio: 1,
+ ratio: 1,
url: '#test',
+ place: '',
+ imageUrl: '',
+ dayOfWeek: 7,
},
- __hasReduce: true,
},
parameters: {
layout: 'centered',
@@ -138,6 +147,5 @@ export const ZeroRatio = {
...Square.args.specify,
ratio: 0,
},
- __hasReduce: false,
},
} satisfies StoryObj<typeof MkAd>;
diff --git a/packages/frontend/src/components/global/MkAd.vue b/packages/frontend/src/components/global/MkAd.vue
index 8f5ed760d5..bdaa8a809f 100644
--- a/packages/frontend/src/components/global/MkAd.vue
+++ b/packages/frontend/src/components/global/MkAd.vue
@@ -14,10 +14,20 @@ SPDX-License-Identifier: AGPL-3.0-only
[$style.form_vertical]: chosen.place === 'vertical',
}]"
>
- <a :href="chosen.url" target="_blank" :class="$style.link">
+ <component
+ :is="self ? 'MkA' : 'a'"
+ :class="$style.link"
+ v-bind="self ? {
+ to: chosen.url.substring(local.length),
+ } : {
+ href: chosen.url,
+ rel: 'nofollow noopener',
+ target: '_blank',
+ }"
+ >
<img :src="chosen.imageUrl" :class="$style.img">
<button class="_button" :class="$style.i" @click.prevent.stop="toggleMenu"><i :class="$style.iIcon" class="ti ti-info-circle"></i></button>
- </a>
+ </component>
</div>
<div v-else :class="$style.menu">
<div :class="$style.menuContainer">
@@ -32,10 +42,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { ref } from 'vue';
+import { ref, computed } from 'vue';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
-import { host } from '@/config.js';
+import { url as local, host } from '@/config.js';
import MkButton from '@/components/MkButton.vue';
import { defaultStore } from '@/store.js';
import * as os from '@/os.js';
@@ -96,6 +106,9 @@ const choseAd = (): Ad | null => {
};
const chosen = ref(choseAd());
+
+const self = computed(() => chosen.value?.url.startsWith(local));
+
const shouldHide = ref(!defaultStore.state.forceShowAds && $i && $i.policies.canHideAds && (props.specify == null));
function reduceFrequency(): void {
diff --git a/packages/frontend/src/components/global/MkAvatar.stories.impl.ts b/packages/frontend/src/components/global/MkAvatar.stories.impl.ts
index 933754ec4c..9d2de9f0be 100644
--- a/packages/frontend/src/components/global/MkAvatar.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkAvatar.stories.impl.ts
@@ -33,7 +33,7 @@ const common = {
},
decorators: [
(Story, context) => ({
- // eslint-disable-next-line quotes
+ // @ts-expect-error size is for test
template: `<div :style="{ display: 'grid', width: '${context.args.size}px', height: '${context.args.size}px' }"><story/></div>`,
}),
],
@@ -45,6 +45,7 @@ export const ProfilePage = {
...common,
args: {
...common.args,
+ // @ts-expect-error size is for test
size: 120,
indicator: true,
},
diff --git a/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts b/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts
index e4e90cddd5..e15dcba760 100644
--- a/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts
@@ -28,6 +28,7 @@ export const Default = {
};
},
args: {
+ // @ts-expect-error text is for test
text: 'This is a condensed line.',
},
parameters: {
@@ -41,4 +42,5 @@ export const ContainerIs100px = {
template: '<div style="width: 100px;"><story/></div>',
}),
],
+ // @ts-expect-error text is for test
} satisfies StoryObj<typeof MkCondensedLine>;
diff --git a/packages/frontend/src/components/global/MkError.stories.meta.ts b/packages/frontend/src/components/global/MkError.stories.meta.ts
index 1abbc56f50..cd7fada189 100644
--- a/packages/frontend/src/components/global/MkError.stories.meta.ts
+++ b/packages/frontend/src/components/global/MkError.stories.meta.ts
@@ -3,8 +3,11 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
+import { Meta } from '@storybook/vue3';
+import MkError from './MkError.vue';
+
export const argTypes = {
- retry: {
+ onRetry: {
action: 'retry',
},
-};
+} satisfies Meta<typeof MkError>['argTypes'];
diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts
index 4ed76f6bc4..cab8d9c704 100644
--- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts
+++ b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { VNode, h, SetupContext } from 'vue';
+import { VNode, h, SetupContext, provide } from 'vue';
import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js';
import MkUrl from '@/components/global/MkUrl.vue';
@@ -16,7 +16,7 @@ import MkCode from '@/components/MkCode.vue';
import MkCodeInline from '@/components/MkCodeInline.vue';
import MkGoogle from '@/components/MkGoogle.vue';
import MkSparkle from '@/components/MkSparkle.vue';
-import MkA from '@/components/global/MkA.vue';
+import MkA, { MkABehavior } from '@/components/global/MkA.vue';
import { host } from '@/config.js';
import { defaultStore } from '@/store.js';
import { nyaize as doNyaize } from '@/scripts/nyaize.js';
@@ -43,6 +43,7 @@ type MfmProps = {
parsedNodes?: mfm.MfmNode[] | null;
enableEmojiMenu?: boolean;
enableEmojiMenuReaction?: boolean;
+ linkNavigationBehavior?: MkABehavior;
};
type MfmEvents = {
@@ -51,6 +52,8 @@ type MfmEvents = {
// eslint-disable-next-line import/no-default-export
export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEvents>['emit'] }) {
+ provide('linkNavigationBehavior', props.linkNavigationBehavior);
+
const isNote = props.isNote ?? true;
const shouldNyaize = props.nyaize ? props.nyaize === 'respect' ? props.author?.isCat : false : false;
diff --git a/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts b/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts
index eb74e874dd..1d079edd2c 100644
--- a/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts
@@ -33,7 +33,6 @@ export const Empty = {
await waitFor(async () => await wait);
},
args: {
- static: true,
tabs: [],
},
parameters: {
@@ -71,8 +70,8 @@ export const IconOnly = {
...Icon.args,
tabs: [
{
- ...Icon.args.tabs[0],
- title: undefined,
+ key: Icon.args.tabs[0].key,
+ icon: Icon.args.tabs[0].icon,
iconOnly: true,
},
],
diff --git a/packages/frontend/src/components/global/MkPageHeader.tabs.vue b/packages/frontend/src/components/global/MkPageHeader.tabs.vue
index e93b09721a..fcc46cc345 100644
--- a/packages/frontend/src/components/global/MkPageHeader.tabs.vue
+++ b/packages/frontend/src/components/global/MkPageHeader.tabs.vue
@@ -38,7 +38,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts">
export type Tab = {
key: string;
- title: string;
onClick?: (ev: MouseEvent) => void;
} & (
| {
diff --git a/packages/frontend/src/components/global/MkTime.stories.impl.ts b/packages/frontend/src/components/global/MkTime.stories.impl.ts
index 355c839113..ffd4a849a2 100644
--- a/packages/frontend/src/components/global/MkTime.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkTime.stories.impl.ts
@@ -60,7 +60,7 @@ export const RelativeFuture = {
export const AbsoluteFuture = {
...Empty,
async play({ canvasElement, args }) {
- await expect(canvasElement).toHaveTextContent(dateTimeFormat.format(args.time));
+ await expect(canvasElement).toHaveTextContent(dateTimeFormat.format(typeof args.time === 'string' ? new Date(args.time) : args.time ?? undefined));
},
args: {
...Empty.args,
@@ -97,7 +97,7 @@ export const RelativeNow = {
export const AbsoluteNow = {
...Empty,
async play({ canvasElement, args }) {
- await expect(canvasElement).toHaveTextContent(dateTimeFormat.format(args.time));
+ await expect(canvasElement).toHaveTextContent(dateTimeFormat.format(typeof args.time === 'string' ? new Date(args.time) : args.time ?? undefined));
},
args: {
...Empty.args,
@@ -136,7 +136,7 @@ export const RelativeOneHourAgo = {
export const AbsoluteOneHourAgo = {
...Empty,
async play({ canvasElement, args }) {
- await expect(canvasElement).toHaveTextContent(dateTimeFormat.format(args.time));
+ await expect(canvasElement).toHaveTextContent(dateTimeFormat.format(typeof args.time === 'string' ? new Date(args.time) : args.time ?? undefined));
},
args: {
...Empty.args,
@@ -175,7 +175,7 @@ export const RelativeOneDayAgo = {
export const AbsoluteOneDayAgo = {
...Empty,
async play({ canvasElement, args }) {
- await expect(canvasElement).toHaveTextContent(dateTimeFormat.format(args.time));
+ await expect(canvasElement).toHaveTextContent(dateTimeFormat.format(typeof args.time === 'string' ? new Date(args.time) : args.time ?? undefined));
},
args: {
...Empty.args,
@@ -214,7 +214,7 @@ export const RelativeOneWeekAgo = {
export const AbsoluteOneWeekAgo = {
...Empty,
async play({ canvasElement, args }) {
- await expect(canvasElement).toHaveTextContent(dateTimeFormat.format(args.time));
+ await expect(canvasElement).toHaveTextContent(dateTimeFormat.format(typeof args.time === 'string' ? new Date(args.time) : args.time ?? undefined));
},
args: {
...Empty.args,
@@ -253,7 +253,7 @@ export const RelativeOneMonthAgo = {
export const AbsoluteOneMonthAgo = {
...Empty,
async play({ canvasElement, args }) {
- await expect(canvasElement).toHaveTextContent(dateTimeFormat.format(args.time));
+ await expect(canvasElement).toHaveTextContent(dateTimeFormat.format(typeof args.time === 'string' ? new Date(args.time) : args.time ?? undefined));
},
args: {
...Empty.args,
@@ -292,7 +292,7 @@ export const RelativeOneYearAgo = {
export const AbsoluteOneYearAgo = {
...Empty,
async play({ canvasElement, args }) {
- await expect(canvasElement).toHaveTextContent(dateTimeFormat.format(args.time));
+ await expect(canvasElement).toHaveTextContent(dateTimeFormat.format(typeof args.time === 'string' ? new Date(args.time) : args.time ?? undefined));
},
args: {
...Empty.args,
diff --git a/packages/frontend/src/components/global/MkTime.vue b/packages/frontend/src/components/global/MkTime.vue
index 67532268d3..23fe99bd9c 100644
--- a/packages/frontend/src/components/global/MkTime.vue
+++ b/packages/frontend/src/components/global/MkTime.vue
@@ -47,7 +47,7 @@ const invalid = Number.isNaN(_time);
const absolute = !invalid ? dateTimeFormat.format(_time) : i18n.ts._ago.invalid;
// eslint-disable-next-line vue/no-setup-props-destructure
-const now = ref((props.origin ?? new Date()).getTime());
+const now = ref(props.origin?.getTime() ?? Date.now());
const ago = computed(() => (now.value - _time) / 1000/*ms*/);
const relative = computed<string>(() => {
@@ -77,7 +77,7 @@ let tickId: number;
let currentInterval: number;
function tick() {
- now.value = (new Date()).getTime();
+ now.value = Date.now();
const nextInterval = ago.value < 60 ? 10000 : ago.value < 3600 ? 60000 : 180000;
if (currentInterval !== nextInterval) {
diff --git a/packages/frontend/src/components/global/MkUrl.vue b/packages/frontend/src/components/global/MkUrl.vue
index 0c3eee63ff..9d4cd559d9 100644
--- a/packages/frontend/src/components/global/MkUrl.vue
+++ b/packages/frontend/src/components/global/MkUrl.vue
@@ -6,6 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<component
:is="self ? 'MkA' : 'a'" ref="el" :class="$style.root" class="_link" :[attr]="self ? props.url.substring(local.length) : props.url" :rel="rel ?? 'nofollow noopener'" :target="target"
+ :behavior="props.navigationBehavior"
@contextmenu.stop="() => {}"
>
<template v-if="!self">
@@ -30,11 +31,14 @@ import { url as local } from '@/config.js';
import * as os from '@/os.js';
import { useTooltip } from '@/scripts/use-tooltip.js';
import { safeURIDecode } from '@/scripts/safe-uri-decode.js';
+import { isEnabledUrlPreview } from '@/instance.js';
+import { MkABehavior } from '@/components/global/MkA.vue';
const props = withDefaults(defineProps<{
url: string;
rel?: string;
showUrlPreview?: boolean;
+ navigationBehavior?: MkABehavior;
}>(), {
showUrlPreview: true,
});
@@ -44,12 +48,12 @@ const url = new URL(props.url);
if (!['http:', 'https:'].includes(url.protocol)) throw new Error('invalid url');
const el = ref();
-if (props.showUrlPreview) {
+if (props.showUrlPreview && isEnabledUrlPreview.value) {
useTooltip(el, (showing) => {
os.popup(defineAsyncComponent(() => import('@/components/MkUrlPreviewPopup.vue')), {
showing,
url: props.url,
- source: el.value,
+ source: el.value instanceof HTMLElement ? el.value : el.value?.$el,
}, {}, 'closed');
});
}
diff --git a/packages/frontend/src/components/global/MkUserName.stories.impl.ts b/packages/frontend/src/components/global/MkUserName.stories.impl.ts
index 88bf4f4e6c..e39061c291 100644
--- a/packages/frontend/src/components/global/MkUserName.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkUserName.stories.impl.ts
@@ -30,7 +30,7 @@ export const Default = {
};
},
async play({ canvasElement }) {
- await expect(canvasElement).toHaveTextContent(userDetailed().name);
+ await expect(canvasElement).toHaveTextContent(userDetailed().name as string);
},
args: {
user: userDetailed(),