diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2023-09-24 18:21:31 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-09-24 18:21:31 +0900 |
| commit | f32915b515f4cbc3b1a877cfb8e8e35bf6a31efa (patch) | |
| tree | 0f6f098cbb282e4b6619152b14b9e6f57e6b448f /packages/frontend/src/components | |
| parent | Merge pull request #11384 from misskey-dev/develop (diff) | |
| parent | 2023.9.0 (diff) | |
| download | misskey-f32915b515f4cbc3b1a877cfb8e8e35bf6a31efa.tar.gz misskey-f32915b515f4cbc3b1a877cfb8e8e35bf6a31efa.tar.bz2 misskey-f32915b515f4cbc3b1a877cfb8e8e35bf6a31efa.zip | |
Merge pull request #11874 from misskey-dev/develop
Release: 2023.9.0
Diffstat (limited to 'packages/frontend/src/components')
231 files changed, 2804 insertions, 1049 deletions
diff --git a/packages/frontend/src/components/MkAbuseReport.stories.impl.ts b/packages/frontend/src/components/MkAbuseReport.stories.impl.ts index 7d27adeb04..3b64529620 100644 --- a/packages/frontend/src/components/MkAbuseReport.stories.impl.ts +++ b/packages/frontend/src/components/MkAbuseReport.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { action } from '@storybook/addon-actions'; import { StoryObj } from '@storybook/vue3'; diff --git a/packages/frontend/src/components/MkAbuseReport.vue b/packages/frontend/src/components/MkAbuseReport.vue index dee80378e6..66114b8734 100644 --- a/packages/frontend/src/components/MkAbuseReport.vue +++ b/packages/frontend/src/components/MkAbuseReport.vue @@ -1,7 +1,12 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div class="bcekxzvu _margin _panel"> <div class="target"> - <MkA v-user-preview="report.targetUserId" class="info" :to="`/user-info/${report.targetUserId}`"> + <MkA v-user-preview="report.targetUserId" class="info" :to="`/admin/user/${report.targetUserId}`"> <MkAvatar class="avatar" :user="report.targetUser" indicator/> <div class="names"> <MkUserName class="name" :user="report.targetUser"/> @@ -39,9 +44,9 @@ import MkButton from '@/components/MkButton.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkKeyValue from '@/components/MkKeyValue.vue'; -import * as os from '@/os'; -import { i18n } from '@/i18n'; -import { dateString } from '@/filters/date'; +import * as os from '@/os.js'; +import { i18n } from '@/i18n.js'; +import { dateString } from '@/filters/date.js'; const props = defineProps<{ report: any; diff --git a/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts b/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts index d0877ffd3b..b45d54679b 100644 --- a/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts +++ b/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { action } from '@storybook/addon-actions'; import { StoryObj } from '@storybook/vue3'; diff --git a/packages/frontend/src/components/MkAbuseReportWindow.vue b/packages/frontend/src/components/MkAbuseReportWindow.vue index 48236782d9..7814681ea2 100644 --- a/packages/frontend/src/components/MkAbuseReportWindow.vue +++ b/packages/frontend/src/components/MkAbuseReportWindow.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkWindow ref="uiWindow" :initialWidth="400" :initialHeight="500" :canResize="true" @closed="emit('closed')"> <template #header> @@ -30,8 +35,8 @@ import * as Misskey from 'misskey-js'; import MkWindow from '@/components/MkWindow.vue'; import MkTextarea from '@/components/MkTextarea.vue'; import MkButton from '@/components/MkButton.vue'; -import * as os from '@/os'; -import { i18n } from '@/i18n'; +import * as os from '@/os.js'; +import { i18n } from '@/i18n.js'; const props = defineProps<{ user: Misskey.entities.User; diff --git a/packages/frontend/src/components/MkAccountMoved.stories.impl.ts b/packages/frontend/src/components/MkAccountMoved.stories.impl.ts index bed9d94311..a6d4d18c1b 100644 --- a/packages/frontend/src/components/MkAccountMoved.stories.impl.ts +++ b/packages/frontend/src/components/MkAccountMoved.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; import { userDetailed } from '../../.storybook/fakes'; diff --git a/packages/frontend/src/components/MkAccountMoved.vue b/packages/frontend/src/components/MkAccountMoved.vue index bc07b9ba5f..155d9fe3a9 100644 --- a/packages/frontend/src/components/MkAccountMoved.vue +++ b/packages/frontend/src/components/MkAccountMoved.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div v-if="user" :class="$style.root"> <i class="ti ti-plane-departure" style="margin-right: 8px;"></i> @@ -8,13 +13,13 @@ <script lang="ts" setup> import { ref } from 'vue'; -import { UserLite } from 'misskey-js/built/entities'; +import * as Misskey from 'misskey-js'; import MkMention from './MkMention.vue'; -import { i18n } from '@/i18n'; -import { host as localHost } from '@/config'; -import { api } from '@/os'; +import { i18n } from '@/i18n.js'; +import { host as localHost } from '@/config.js'; +import { api } from '@/os.js'; -const user = ref<UserLite>(); +const user = ref<Misskey.entities.UserLite>(); const props = defineProps<{ movedTo: string; // user id diff --git a/packages/frontend/src/components/MkAchievements.stories.impl.ts b/packages/frontend/src/components/MkAchievements.stories.impl.ts index 477152a47b..a67e1def13 100644 --- a/packages/frontend/src/components/MkAchievements.stories.impl.ts +++ b/packages/frontend/src/components/MkAchievements.stories.impl.ts @@ -1,10 +1,15 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; import { rest } from 'msw'; import { userDetailed } from '../../.storybook/fakes'; import { commonHandlers } from '../../.storybook/mocks'; import MkAchievements from './MkAchievements.vue'; -import { ACHIEVEMENT_TYPES } from '@/scripts/achievements'; +import { ACHIEVEMENT_TYPES } from '@/scripts/achievements.js'; export const Empty = { render(args) { return { diff --git a/packages/frontend/src/components/MkAchievements.vue b/packages/frontend/src/components/MkAchievements.vue index 3fdb261dac..bea0ed26d8 100644 --- a/packages/frontend/src/components/MkAchievements.vue +++ b/packages/frontend/src/components/MkAchievements.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div> <div v-if="achievements" :class="$style.root"> @@ -47,14 +52,14 @@ </template> <script lang="ts" setup> -import * as misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; import { onMounted } from 'vue'; -import * as os from '@/os'; -import { i18n } from '@/i18n'; -import { ACHIEVEMENT_TYPES, ACHIEVEMENT_BADGES, claimAchievement } from '@/scripts/achievements'; +import * as os from '@/os.js'; +import { i18n } from '@/i18n.js'; +import { ACHIEVEMENT_TYPES, ACHIEVEMENT_BADGES, claimAchievement } from '@/scripts/achievements.js'; const props = withDefaults(defineProps<{ - user: misskey.entities.User; + user: Misskey.entities.User; withLocked: boolean; withDescription: boolean; }>(), { diff --git a/packages/frontend/src/components/MkAnalogClock.stories.impl.ts b/packages/frontend/src/components/MkAnalogClock.stories.impl.ts index 0aebdccf4f..f87ad30f9b 100644 --- a/packages/frontend/src/components/MkAnalogClock.stories.impl.ts +++ b/packages/frontend/src/components/MkAnalogClock.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; import isChromatic from 'chromatic/isChromatic'; diff --git a/packages/frontend/src/components/MkAnalogClock.vue b/packages/frontend/src/components/MkAnalogClock.vue index 05caffe7d0..cd2c4d8264 100644 --- a/packages/frontend/src/components/MkAnalogClock.vue +++ b/packages/frontend/src/components/MkAnalogClock.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <svg :class="$style.root" viewBox="0 0 10 10" preserveAspectRatio="none"> <template v-if="props.graduations === 'dots'"> diff --git a/packages/frontend/src/components/MkAnimBg.vue b/packages/frontend/src/components/MkAnimBg.vue index 575ea7c5e3..70d101a9d3 100644 --- a/packages/frontend/src/components/MkAnimBg.vue +++ b/packages/frontend/src/components/MkAnimBg.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <canvas ref="canvasEl" style="width: 100%; height: 100%; pointer-events: none;"></canvas> </template> diff --git a/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts b/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts new file mode 100644 index 0000000000..42cfb90f7c --- /dev/null +++ b/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { StoryObj } from '@storybook/vue3'; +import MkAnnouncementDialog from './MkAnnouncementDialog.vue'; +export const Default = { + render(args) { + return { + components: { + MkAnnouncementDialog, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '<MkAnnouncementDialog v-bind="props" />', + }; + }, + args: { + announcement: { + id: '1', + title: 'Title', + text: 'Text', + createdAt: new Date().toISOString(), + updatedAt: null, + icon: 'info', + imageUrl: null, + display: 'dialog', + needConfirmationToRead: false, + forYou: true, + }, + }, + parameters: { + layout: 'centered', + }, +} satisfies StoryObj<typeof MkAnnouncementDialog>; diff --git a/packages/frontend/src/components/MkAnnouncementDialog.vue b/packages/frontend/src/components/MkAnnouncementDialog.vue new file mode 100644 index 0000000000..aaac3dd29b --- /dev/null +++ b/packages/frontend/src/components/MkAnnouncementDialog.vue @@ -0,0 +1,104 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<MkModal ref="modal" :zPriority="'middle'" @closed="$emit('closed')" @click="onBgClick"> + <div ref="rootEl" :class="$style.root"> + <div :class="$style.header"> + <span :class="$style.icon"> + <i v-if="announcement.icon === 'info'" class="ti ti-info-circle"></i> + <i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--warn);"></i> + <i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--error);"></i> + <i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--success);"></i> + </span> + <span :class="$style.title">{{ announcement.title }}</span> + </div> + <div :class="$style.text"><Mfm :text="announcement.text"/></div> + <MkButton primary full @click="ok">{{ i18n.ts.ok }}</MkButton> + </div> +</MkModal> +</template> + +<script lang="ts" setup> +import { onMounted, shallowRef } from 'vue'; +import * as Misskey from 'misskey-js'; +import * as os from '@/os.js'; +import MkModal from '@/components/MkModal.vue'; +import MkButton from '@/components/MkButton.vue'; +import { i18n } from '@/i18n.js'; +import { $i, updateAccount } from '@/account.js'; + +const props = withDefaults(defineProps<{ + announcement: Misskey.entities.Announcement; +}>(), { +}); + +const rootEl = shallowRef<HTMLDivElement>(); +const modal = shallowRef<InstanceType<typeof MkModal>>(); + +async function ok() { + if (props.announcement.needConfirmationToRead) { + const confirm = await os.confirm({ + type: 'question', + title: i18n.ts._announcement.readConfirmTitle, + text: i18n.t('_announcement.readConfirmText', { title: props.announcement.title }), + }); + if (confirm.canceled) return; + } + + modal.value.close(); + os.api('i/read-announcement', { announcementId: props.announcement.id }); + updateAccount({ + unreadAnnouncements: $i!.unreadAnnouncements.filter(a => a.id !== props.announcement.id), + }); +} + +function onBgClick() { + rootEl.value.animate([{ + offset: 0, + transform: 'scale(1)', + }, { + offset: 0.5, + transform: 'scale(1.1)', + }, { + offset: 1, + transform: 'scale(1)', + }], { + duration: 100, + }); +} + +onMounted(() => { +}); +</script> + +<style lang="scss" module> +.root { + margin: auto; + position: relative; + padding: 32px; + min-width: 320px; + max-width: 480px; + box-sizing: border-box; + background: var(--panel); + border-radius: var(--radius); +} + +.header { + font-size: 120%; +} + +.icon { + margin-right: 0.5em; +} + +.title { + font-weight: bold; +} + +.text { + margin: 1em 0; +} +</style> diff --git a/packages/frontend/src/components/MkAsUi.stories.impl.ts b/packages/frontend/src/components/MkAsUi.stories.impl.ts index b67c0e679d..564fa902ba 100644 --- a/packages/frontend/src/components/MkAsUi.stories.impl.ts +++ b/packages/frontend/src/components/MkAsUi.stories.impl.ts @@ -1,2 +1,7 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + import MkAsUi from './MkAsUi.vue'; void MkAsUi; diff --git a/packages/frontend/src/components/MkAsUi.vue b/packages/frontend/src/components/MkAsUi.vue index 7aa8f94c3b..099baf0d72 100644 --- a/packages/frontend/src/components/MkAsUi.vue +++ b/packages/frontend/src/components/MkAsUi.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div> <div v-if="c.type === 'root'" :class="$style.root"> @@ -33,6 +38,13 @@ <option v-for="item in c.items" :key="item.value" :value="item.value">{{ item.text }}</option> </MkSelect> <MkButton v-else-if="c.type === 'postFormButton'" :primary="c.primary" :rounded="c.rounded" :small="size === 'small'" inline @click="openPostForm">{{ c.text }}</MkButton> + <div v-else-if="c.type === 'postForm'" :class="$style.postForm"> + <MkPostForm + fixed + :instant="true" + :initialText="c.form.text" + /> + </div> <MkFolder v-else-if="c.type === 'folder'" :defaultOpen="c.opened"> <template #label>{{ c.title }}</template> <template v-for="child in c.children" :key="child"> @@ -49,14 +61,15 @@ <script lang="ts" setup> import { Ref } from 'vue'; -import * as os from '@/os'; +import * as os from '@/os.js'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkTextarea from '@/components/MkTextarea.vue'; import MkSelect from '@/components/MkSelect.vue'; -import { AsUiComponent } from '@/scripts/aiscript/ui'; +import { AsUiComponent } from '@/scripts/aiscript/ui.js'; import MkFolder from '@/components/MkFolder.vue'; +import MkPostForm from '@/components/MkPostForm.vue'; const props = withDefaults(defineProps<{ component: AsUiComponent; @@ -109,4 +122,9 @@ function openPostForm() { .fontMonospace { font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace; } + +.postForm { + background: var(--bg); + border-radius: 8px; +} </style> diff --git a/packages/frontend/src/components/MkAutocomplete.stories.impl.ts b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts index 075904d6a3..8232759ba0 100644 --- a/packages/frontend/src/components/MkAutocomplete.stories.impl.ts +++ b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { action } from '@storybook/addon-actions'; import { expect } from '@storybook/jest'; @@ -8,7 +13,7 @@ import { userDetailed } from '../../.storybook/fakes'; import { commonHandlers } from '../../.storybook/mocks'; import MkAutocomplete from './MkAutocomplete.vue'; import MkInput from './MkInput.vue'; -import { tick } from '@/scripts/test-utils'; +import { tick } from '@/scripts/test-utils.js'; const common = { render(args) { return { diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index 9211d92df7..7c4f910559 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div ref="rootEl" :class="$style.root" class="_popup _shadow" :style="{ zIndex }" @contextmenu.prevent="() => {}"> <ol v-if="type === 'user'" ref="suggests" :class="$style.list"> @@ -36,16 +41,16 @@ <script lang="ts"> import { markRaw, ref, shallowRef, computed, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'; import sanitizeHtml from 'sanitize-html'; -import contains from '@/scripts/contains'; -import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@/scripts/emoji-base'; -import { acct } from '@/filters/user'; -import * as os from '@/os'; -import { MFM_TAGS } from '@/scripts/mfm-tags'; -import { defaultStore } from '@/store'; -import { emojilist, getEmojiName } from '@/scripts/emojilist'; -import { i18n } from '@/i18n'; -import { miLocalStorage } from '@/local-storage'; -import { customEmojis } from '@/custom-emojis'; +import contains from '@/scripts/contains.js'; +import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@/scripts/emoji-base.js'; +import { acct } from '@/filters/user.js'; +import * as os from '@/os.js'; +import { MFM_TAGS } from '@/scripts/mfm-tags.js'; +import { defaultStore } from '@/store.js'; +import { emojilist, getEmojiName } from '@/scripts/emojilist.js'; +import { i18n } from '@/i18n.js'; +import { miLocalStorage } from '@/local-storage.js'; +import { customEmojis } from '@/custom-emojis.js'; type EmojiDef = { emoji: string; diff --git a/packages/frontend/src/components/MkAvatars.stories.impl.ts b/packages/frontend/src/components/MkAvatars.stories.impl.ts index 14052c7343..659c0eebdf 100644 --- a/packages/frontend/src/components/MkAvatars.stories.impl.ts +++ b/packages/frontend/src/components/MkAvatars.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; import { rest } from 'msw'; diff --git a/packages/frontend/src/components/MkAvatars.vue b/packages/frontend/src/components/MkAvatars.vue index 437dce0a14..5644a324cf 100644 --- a/packages/frontend/src/components/MkAvatars.vue +++ b/packages/frontend/src/components/MkAvatars.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div> <div v-for="user in users.slice(0, limit)" :key="user.id" style="display:inline-block;width:32px;height:32px;margin-right:8px;"> @@ -9,8 +14,8 @@ <script lang="ts" setup> import { onMounted, ref } from 'vue'; -import * as os from '@/os'; -import { UserLite } from 'misskey-js/built/entities'; +import * as Misskey from 'misskey-js'; +import * as os from '@/os.js'; const props = withDefaults(defineProps<{ userIds: string[]; @@ -19,11 +24,11 @@ const props = withDefaults(defineProps<{ limit: Infinity, }); -const users = ref<UserLite[]>([]); +const users = ref<Misskey.entities.UserLite[]>([]); onMounted(async () => { users.value = await os.api('users/show', { userIds: props.userIds, - }) as unknown as UserLite[]; + }) as unknown as Misskey.entities.UserLite[]; }); </script> diff --git a/packages/frontend/src/components/MkButton.stories.impl.ts b/packages/frontend/src/components/MkButton.stories.impl.ts index 982a8b3be1..e852557b12 100644 --- a/packages/frontend/src/components/MkButton.stories.impl.ts +++ b/packages/frontend/src/components/MkButton.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable import/no-default-export */ import { action } from '@storybook/addon-actions'; diff --git a/packages/frontend/src/components/MkButton.vue b/packages/frontend/src/components/MkButton.vue index 16e44ec618..bcd58ae516 100644 --- a/packages/frontend/src/components/MkButton.vue +++ b/packages/frontend/src/components/MkButton.vue @@ -1,9 +1,16 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <button v-if="!link" ref="el" class="_button" :class="[$style.root, { [$style.inline]: inline, [$style.primary]: primary, [$style.gradate]: gradate, [$style.danger]: danger, [$style.rounded]: rounded, [$style.full]: full, [$style.small]: small, [$style.large]: large, [$style.transparent]: transparent, [$style.asLike]: asLike }]" :type="type" + :name="name" + :value="value" @click="emit('click', $event)" @mousedown="onMousedown" > @@ -44,6 +51,8 @@ const props = defineProps<{ large?: boolean; transparent?: boolean; asLike?: boolean; + name?: string; + value?: string; }>(); const emit = defineEmits<{ diff --git a/packages/frontend/src/components/MkCaptcha.stories.impl.ts b/packages/frontend/src/components/MkCaptcha.stories.impl.ts index 6ac437a277..fb50e50b18 100644 --- a/packages/frontend/src/components/MkCaptcha.stories.impl.ts +++ b/packages/frontend/src/components/MkCaptcha.stories.impl.ts @@ -1,2 +1,7 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + import MkCaptcha from './MkCaptcha.vue'; void MkCaptcha; diff --git a/packages/frontend/src/components/MkCaptcha.vue b/packages/frontend/src/components/MkCaptcha.vue index 1875b507ca..14e59acad2 100644 --- a/packages/frontend/src/components/MkCaptcha.vue +++ b/packages/frontend/src/components/MkCaptcha.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div> <span v-if="!available">{{ i18n.ts.waiting }}<MkEllipsis/></span> @@ -7,8 +12,8 @@ <script lang="ts" setup> import { ref, shallowRef, computed, onMounted, onBeforeUnmount, watch } from 'vue'; -import { defaultStore } from '@/store'; -import { i18n } from '@/i18n'; +import { defaultStore } from '@/store.js'; +import { i18n } from '@/i18n.js'; // APIs provided by Captcha services export type Captcha = { diff --git a/packages/frontend/src/components/MkChannelFollowButton.vue b/packages/frontend/src/components/MkChannelFollowButton.vue index 7b7bef4787..41b02a7e3f 100644 --- a/packages/frontend/src/components/MkChannelFollowButton.vue +++ b/packages/frontend/src/components/MkChannelFollowButton.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <button class="_button" @@ -21,8 +26,8 @@ <script lang="ts" setup> import { ref } from 'vue'; -import * as os from '@/os'; -import { i18n } from '@/i18n'; +import * as os from '@/os.js'; +import { i18n } from '@/i18n.js'; const props = withDefaults(defineProps<{ channel: Record<string, any>; diff --git a/packages/frontend/src/components/MkChannelList.vue b/packages/frontend/src/components/MkChannelList.vue index 2d3ea8d177..83d4401d2e 100644 --- a/packages/frontend/src/components/MkChannelList.vue +++ b/packages/frontend/src/components/MkChannelList.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkPagination :pagination="pagination"> <template #empty> @@ -16,8 +21,8 @@ <script lang="ts" setup> import MkChannelPreview from '@/components/MkChannelPreview.vue'; import MkPagination, { Paging } from '@/components/MkPagination.vue'; -import { i18n } from '@/i18n'; -import { infoImageUrl } from '@/instance'; +import { i18n } from '@/i18n.js'; +import { infoImageUrl } from '@/instance.js'; const props = withDefaults(defineProps<{ pagination: Paging; diff --git a/packages/frontend/src/components/MkChannelPreview.vue b/packages/frontend/src/components/MkChannelPreview.vue index 6ef50bddcf..4512f2dd60 100644 --- a/packages/frontend/src/components/MkChannelPreview.vue +++ b/packages/frontend/src/components/MkChannelPreview.vue @@ -1,8 +1,14 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkA :to="`/channels/${channel.id}`" class="eftoefju _panel" tabindex="-1"> <div class="banner" :style="bannerStyle"> <div class="fade"></div> <div class="name"><i class="ti ti-device-tv"></i> {{ channel.name }}</div> + <div v-if="channel.isSensitive" class="sensitiveIndicator">{{ i18n.ts.sensitive }}</div> <div class="status"> <div> <i class="ti ti-users ti-fw"></i> @@ -35,7 +41,7 @@ <script lang="ts" setup> import { computed } from 'vue'; -import { i18n } from '@/i18n'; +import { i18n } from '@/i18n.js'; const props = defineProps<{ channel: Record<string, any>; @@ -97,6 +103,19 @@ const bannerStyle = computed(() => { border-radius: 6px; color: #fff; } + + > .sensitiveIndicator { + position: absolute; + z-index: 1; + bottom: 16px; + left: 16px; + background: rgba(0, 0, 0, 0.7); + color: var(--warn); + border-radius: 6px; + font-weight: bold; + font-size: 1em; + padding: 4px 7px; + } } > article { diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue index 00ff98774b..fe7077bdbf 100644 --- a/packages/frontend/src/components/MkChart.vue +++ b/packages/frontend/src/components/MkChart.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="$style.root"> <canvas ref="chartEl"></canvas> @@ -17,14 +22,14 @@ import { onMounted, ref, shallowRef, watch, PropType } from 'vue'; import { Chart } from 'chart.js'; import gradient from 'chartjs-plugin-gradient'; -import * as os from '@/os'; -import { defaultStore } from '@/store'; -import { useChartTooltip } from '@/scripts/use-chart-tooltip'; -import { chartVLine } from '@/scripts/chart-vline'; -import { alpha } from '@/scripts/color'; -import date from '@/filters/date'; -import { initChart } from '@/scripts/init-chart'; -import { chartLegend } from '@/scripts/chart-legend'; +import * as os from '@/os.js'; +import { defaultStore } from '@/store.js'; +import { useChartTooltip } from '@/scripts/use-chart-tooltip.js'; +import { chartVLine } from '@/scripts/chart-vline.js'; +import { alpha } from '@/scripts/color.js'; +import date from '@/filters/date.js'; +import { initChart } from '@/scripts/init-chart.js'; +import { chartLegend } from '@/scripts/chart-legend.js'; import MkChartLegend from '@/components/MkChartLegend.vue'; initChart(); diff --git a/packages/frontend/src/components/MkChartLegend.vue b/packages/frontend/src/components/MkChartLegend.vue index 7bc6194b7a..d321114cba 100644 --- a/packages/frontend/src/components/MkChartLegend.vue +++ b/packages/frontend/src/components/MkChartLegend.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="$style.root"> <button v-for="item in items" class="_button item" :class="{ disabled: item.hidden }" @click="onClick(item)"> diff --git a/packages/frontend/src/components/MkChartTooltip.vue b/packages/frontend/src/components/MkChartTooltip.vue index fe5b78754d..c11f516e37 100644 --- a/packages/frontend/src/components/MkChartTooltip.vue +++ b/packages/frontend/src/components/MkChartTooltip.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkTooltip ref="tooltip" :showing="showing" :x="x" :y="y" :maxWidth="340" :direction="'top'" :innerMargin="16" @closed="emit('closed')"> <div v-if="title || series"> diff --git a/packages/frontend/src/components/MkClickerGame.vue b/packages/frontend/src/components/MkClickerGame.vue index a6ab5aded4..1c3920962e 100644 --- a/packages/frontend/src/components/MkClickerGame.vue +++ b/packages/frontend/src/components/MkClickerGame.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div> <div v-if="game.ready" :class="$style.game"> @@ -16,11 +21,11 @@ <script lang="ts" setup> import { computed, onMounted, onUnmounted } from 'vue'; import MkPlusOneEffect from '@/components/MkPlusOneEffect.vue'; -import * as os from '@/os'; -import { useInterval } from '@/scripts/use-interval'; -import * as game from '@/scripts/clicker-game'; -import number from '@/filters/number'; -import { claimAchievement } from '@/scripts/achievements'; +import * as os from '@/os.js'; +import { useInterval } from '@/scripts/use-interval.js'; +import * as game from '@/scripts/clicker-game.js'; +import number from '@/filters/number.js'; +import { claimAchievement } from '@/scripts/achievements.js'; const saveData = game.saveData; const cookies = computed(() => saveData.value?.cookies); diff --git a/packages/frontend/src/components/MkClipPreview.vue b/packages/frontend/src/components/MkClipPreview.vue index c5fb718782..2f6790fa49 100644 --- a/packages/frontend/src/components/MkClipPreview.vue +++ b/packages/frontend/src/components/MkClipPreview.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="$style.root" class="_panel"> <b>{{ clip.name }}</b> @@ -10,7 +15,7 @@ </template> <script lang="ts" setup> -import { i18n } from '@/i18n'; +import { i18n } from '@/i18n.js'; defineProps<{ clip: any; diff --git a/packages/frontend/src/components/MkCode.core.vue b/packages/frontend/src/components/MkCode.core.vue index b656307d90..a1300be1f6 100644 --- a/packages/frontend/src/components/MkCode.core.vue +++ b/packages/frontend/src/components/MkCode.core.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <!-- eslint-disable vue/no-v-html --> <template> <code v-if="inline" :class="`language-${prismLang}`" style="overflow-wrap: anywhere;" v-html="html"></code> diff --git a/packages/frontend/src/components/MkCode.vue b/packages/frontend/src/components/MkCode.vue index 1640258d5b..8972b1863b 100644 --- a/packages/frontend/src/components/MkCode.vue +++ b/packages/frontend/src/components/MkCode.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <XCode :code="code" :lang="lang" :inline="inline"/> </template> diff --git a/packages/frontend/src/components/MkColorInput.vue b/packages/frontend/src/components/MkColorInput.vue index 2471aa958d..983a35103c 100644 --- a/packages/frontend/src/components/MkColorInput.vue +++ b/packages/frontend/src/components/MkColorInput.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div> <div :class="$style.label"><slot name="label"></slot></div> @@ -20,7 +25,7 @@ <script lang="ts" setup> import { onMounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue'; -import { i18n } from '@/i18n'; +import { i18n } from '@/i18n.js'; const props = defineProps<{ modelValue: string | null; diff --git a/packages/frontend/src/components/MkContainer.vue b/packages/frontend/src/components/MkContainer.vue index af1c57b349..659f3a909e 100644 --- a/packages/frontend/src/components/MkContainer.vue +++ b/packages/frontend/src/components/MkContainer.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div ref="rootEl" class="_panel" :class="[$style.root, { [$style.naked]: naked, [$style.thin]: thin, [$style.scrollable]: scrollable }]"> <header v-if="showHeader" ref="headerEl" :class="$style.header"> @@ -35,8 +40,8 @@ <script lang="ts" setup> import { onMounted, onUnmounted, ref, shallowRef, watch } from 'vue'; -import { defaultStore } from '@/store'; -import { i18n } from '@/i18n'; +import { defaultStore } from '@/store.js'; +import { i18n } from '@/i18n.js'; const props = withDefaults(defineProps<{ showHeader?: boolean; diff --git a/packages/frontend/src/components/MkContextMenu.vue b/packages/frontend/src/components/MkContextMenu.vue index f39c944199..6cca7fc353 100644 --- a/packages/frontend/src/components/MkContextMenu.vue +++ b/packages/frontend/src/components/MkContextMenu.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <Transition appear @@ -16,9 +21,9 @@ import { onMounted, onBeforeUnmount } from 'vue'; import MkMenu from './MkMenu.vue'; import { MenuItem } from './types/menu.vue'; -import contains from '@/scripts/contains'; -import { defaultStore } from '@/store'; -import * as os from '@/os'; +import contains from '@/scripts/contains.js'; +import { defaultStore } from '@/store.js'; +import * as os from '@/os.js'; const props = defineProps<{ items: MenuItem[]; diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue index b2d60d36c4..81f3936600 100644 --- a/packages/frontend/src/components/MkCropperDialog.vue +++ b/packages/frontend/src/components/MkCropperDialog.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkModalWindow ref="dialogEl" @@ -27,25 +32,25 @@ <script lang="ts" setup> import { onMounted } from 'vue'; -import * as misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; import Cropper from 'cropperjs'; import tinycolor from 'tinycolor2'; import MkModalWindow from '@/components/MkModalWindow.vue'; -import * as os from '@/os'; -import { $i } from '@/account'; -import { defaultStore } from '@/store'; -import { apiUrl } from '@/config'; -import { i18n } from '@/i18n'; -import { getProxiedImageUrl } from '@/scripts/media-proxy'; +import * as os from '@/os.js'; +import { $i } from '@/account.js'; +import { defaultStore } from '@/store.js'; +import { apiUrl } from '@/config.js'; +import { i18n } from '@/i18n.js'; +import { getProxiedImageUrl } from '@/scripts/media-proxy.js'; const emit = defineEmits<{ - (ev: 'ok', cropped: misskey.entities.DriveFile): void; + (ev: 'ok', cropped: Misskey.entities.DriveFile): void; (ev: 'cancel'): void; (ev: 'closed'): void; }>(); const props = defineProps<{ - file: misskey.entities.DriveFile; + file: Misskey.entities.DriveFile; aspectRatio: number; uploadFolder?: string | null; }>(); @@ -57,7 +62,7 @@ let cropper: Cropper | null = null; let loading = $ref(true); const ok = async () => { - const promise = new Promise<misskey.entities.DriveFile>(async (res) => { + const promise = new Promise<Misskey.entities.DriveFile>(async (res) => { const croppedCanvas = await cropper?.getCropperSelection()?.$toCanvas(); croppedCanvas?.toBlob(blob => { if (!blob) return; diff --git a/packages/frontend/src/components/MkCwButton.vue b/packages/frontend/src/components/MkCwButton.vue index 7d5579040a..54c2159356 100644 --- a/packages/frontend/src/components/MkCwButton.vue +++ b/packages/frontend/src/components/MkCwButton.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <button class="_button" :class="$style.root" @mousedown="toggle"> <b>{{ modelValue ? i18n.ts._cw.hide : i18n.ts._cw.show }}</b> @@ -7,13 +12,13 @@ <script lang="ts" setup> import { computed } from 'vue'; -import * as misskey from 'misskey-js'; -import { concat } from '@/scripts/array'; -import { i18n } from '@/i18n'; +import * as Misskey from 'misskey-js'; +import { concat } from '@/scripts/array.js'; +import { i18n } from '@/i18n.js'; const props = defineProps<{ modelValue: boolean; - note: misskey.entities.Note; + note: Misskey.entities.Note; }>(); const emit = defineEmits<{ diff --git a/packages/frontend/src/components/MkDateSeparatedList.vue b/packages/frontend/src/components/MkDateSeparatedList.vue index 6942a0e6c3..66d4b542be 100644 --- a/packages/frontend/src/components/MkDateSeparatedList.vue +++ b/packages/frontend/src/components/MkDateSeparatedList.vue @@ -1,10 +1,15 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <script lang="ts"> import { defineComponent, h, PropType, TransitionGroup, useCssModule } from 'vue'; import MkAd from '@/components/global/MkAd.vue'; import { isDebuggerEnabled, stackTraceInstances } from '@/debug'; -import { i18n } from '@/i18n'; -import * as os from '@/os'; -import { defaultStore } from '@/store'; +import { i18n } from '@/i18n.js'; +import * as os from '@/os.js'; +import { defaultStore } from '@/store.js'; import { MisskeyEntity } from '@/types/date-separated-list'; export default defineComponent({ @@ -163,11 +168,11 @@ export default defineComponent({ > *:empty { display: none; } + } - > *:not(:last-child) { + &:not(.date-separated-list-nogap) > *:not(:last-child) { margin-bottom: var(--margin); } - } } .date-separated-list-nogap { diff --git a/packages/frontend/src/components/MkDialog.vue b/packages/frontend/src/components/MkDialog.vue index 4d5df0bba4..a83c18c0b3 100644 --- a/packages/frontend/src/components/MkDialog.vue +++ b/packages/frontend/src/components/MkDialog.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkModal ref="modal" :preferType="'dialog'" :zPriority="'high'" @click="done(true)" @closed="emit('closed')"> <div :class="$style.root"> @@ -56,7 +61,7 @@ import MkModal from '@/components/MkModal.vue'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; import MkSelect from '@/components/MkSelect.vue'; -import { i18n } from '@/i18n'; +import { i18n } from '@/i18n.js'; type Input = { type: 'text' | 'number' | 'password' | 'email' | 'url' | 'date' | 'time' | 'search' | 'datetime-local'; diff --git a/packages/frontend/src/components/MkDigitalClock.stories.impl.ts b/packages/frontend/src/components/MkDigitalClock.stories.impl.ts index 344f6de47c..5d16c09bc5 100644 --- a/packages/frontend/src/components/MkDigitalClock.stories.impl.ts +++ b/packages/frontend/src/components/MkDigitalClock.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; import isChromatic from 'chromatic/isChromatic'; diff --git a/packages/frontend/src/components/MkDigitalClock.vue b/packages/frontend/src/components/MkDigitalClock.vue index aea20f2489..dff6e7d4dd 100644 --- a/packages/frontend/src/components/MkDigitalClock.vue +++ b/packages/frontend/src/components/MkDigitalClock.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <span> <span v-text="hh"></span> diff --git a/packages/frontend/src/components/MkDonation.vue b/packages/frontend/src/components/MkDonation.vue index b5ae4c6c48..a2a0b6023b 100644 --- a/packages/frontend/src/components/MkDonation.vue +++ b/packages/frontend/src/components/MkDonation.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div class="_panel _shadow" :class="$style.root"> <!-- TODO: インスタンス運営者が任意のテキストとリンクを設定できるようにする --> @@ -33,11 +38,11 @@ <script lang="ts" setup> import MkButton from '@/components/MkButton.vue'; import MkLink from '@/components/MkLink.vue'; -import { host } from '@/config'; -import { i18n } from '@/i18n'; -import * as os from '@/os'; -import { miLocalStorage } from '@/local-storage'; -import { instance } from '@/instance'; +import { host } from '@/config.js'; +import { i18n } from '@/i18n.js'; +import * as os from '@/os.js'; +import { miLocalStorage } from '@/local-storage.js'; +import { instance } from '@/instance.js'; const emit = defineEmits<{ (ev: 'closed'): void; diff --git a/packages/frontend/src/components/MkDrive.file.vue b/packages/frontend/src/components/MkDrive.file.vue index 8b3f91731a..e3f96724d9 100644 --- a/packages/frontend/src/components/MkDrive.file.vue +++ b/packages/frontend/src/components/MkDrive.file.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="[$style.root, { [$style.isSelected]: isSelected }]" @@ -36,11 +41,11 @@ import { computed, ref } from 'vue'; import * as Misskey from 'misskey-js'; import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue'; -import bytes from '@/filters/bytes'; -import * as os from '@/os'; -import { i18n } from '@/i18n'; -import { $i } from '@/account'; -import { getDriveFileMenu } from '@/scripts/get-drive-file-menu'; +import bytes from '@/filters/bytes.js'; +import * as os from '@/os.js'; +import { i18n } from '@/i18n.js'; +import { $i } from '@/account.js'; +import { getDriveFileMenu } from '@/scripts/get-drive-file-menu.js'; const props = withDefaults(defineProps<{ file: Misskey.entities.DriveFile; diff --git a/packages/frontend/src/components/MkDrive.folder.vue b/packages/frontend/src/components/MkDrive.folder.vue index 13f32ff7af..5322664664 100644 --- a/packages/frontend/src/components/MkDrive.folder.vue +++ b/packages/frontend/src/components/MkDrive.folder.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="[$style.root, { [$style.draghover]: draghover }]" @@ -29,11 +34,11 @@ <script lang="ts" setup> import { computed, defineAsyncComponent, ref } from 'vue'; import * as Misskey from 'misskey-js'; -import * as os from '@/os'; -import { i18n } from '@/i18n'; -import { defaultStore } from '@/store'; -import { claimAchievement } from '@/scripts/achievements'; -import copyToClipboard from '@/scripts/copy-to-clipboard'; +import * as os from '@/os.js'; +import { i18n } from '@/i18n.js'; +import { defaultStore } from '@/store.js'; +import { claimAchievement } from '@/scripts/achievements.js'; +import copyToClipboard from '@/scripts/copy-to-clipboard.js'; const props = withDefaults(defineProps<{ folder: Misskey.entities.DriveFolder; diff --git a/packages/frontend/src/components/MkDrive.navFolder.vue b/packages/frontend/src/components/MkDrive.navFolder.vue index df4c209c2b..59458ad568 100644 --- a/packages/frontend/src/components/MkDrive.navFolder.vue +++ b/packages/frontend/src/components/MkDrive.navFolder.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="[$style.root, { [$style.draghover]: draghover }]" @@ -15,8 +20,8 @@ <script lang="ts" setup> import { ref } from 'vue'; import * as Misskey from 'misskey-js'; -import * as os from '@/os'; -import { i18n } from '@/i18n'; +import * as os from '@/os.js'; +import { i18n } from '@/i18n.js'; const props = defineProps<{ folder?: Misskey.entities.DriveFolder; diff --git a/packages/frontend/src/components/MkDrive.vue b/packages/frontend/src/components/MkDrive.vue index aff227da40..648e4c4e3d 100644 --- a/packages/frontend/src/components/MkDrive.vue +++ b/packages/frontend/src/components/MkDrive.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="$style.root"> <nav :class="$style.nav"> @@ -96,12 +101,12 @@ import MkButton from './MkButton.vue'; import XNavFolder from '@/components/MkDrive.navFolder.vue'; import XFolder from '@/components/MkDrive.folder.vue'; import XFile from '@/components/MkDrive.file.vue'; -import * as os from '@/os'; -import { useStream } from '@/stream'; -import { defaultStore } from '@/store'; -import { i18n } from '@/i18n'; -import { uploadFile, uploads } from '@/scripts/upload'; -import { claimAchievement } from '@/scripts/achievements'; +import * as os from '@/os.js'; +import { useStream } from '@/stream.js'; +import { defaultStore } from '@/store.js'; +import { i18n } from '@/i18n.js'; +import { uploadFile, uploads } from '@/scripts/upload.js'; +import { claimAchievement } from '@/scripts/achievements.js'; const props = withDefaults(defineProps<{ initialFolder?: Misskey.entities.DriveFolder; diff --git a/packages/frontend/src/components/MkDriveFileThumbnail.vue b/packages/frontend/src/components/MkDriveFileThumbnail.vue index 490aed6e04..5b07fed2d1 100644 --- a/packages/frontend/src/components/MkDriveFileThumbnail.vue +++ b/packages/frontend/src/components/MkDriveFileThumbnail.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div ref="thumbnail" :class="$style.root"> <ImgWithBlurhash v-if="isThumbnailAvailable" :hash="file.blurhash" :src="file.thumbnailUrl" :alt="file.name" :title="file.name" :cover="fit !== 'contain'"/> diff --git a/packages/frontend/src/components/MkDriveSelectDialog.vue b/packages/frontend/src/components/MkDriveSelectDialog.vue index da873cb90b..e65f4dd403 100644 --- a/packages/frontend/src/components/MkDriveSelectDialog.vue +++ b/packages/frontend/src/components/MkDriveSelectDialog.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkModalWindow ref="dialog" @@ -23,8 +28,8 @@ import { ref, shallowRef } from 'vue'; import * as Misskey from 'misskey-js'; import XDrive from '@/components/MkDrive.vue'; import MkModalWindow from '@/components/MkModalWindow.vue'; -import number from '@/filters/number'; -import { i18n } from '@/i18n'; +import number from '@/filters/number.js'; +import { i18n } from '@/i18n.js'; withDefaults(defineProps<{ type?: 'file' | 'folder'; diff --git a/packages/frontend/src/components/MkDriveWindow.vue b/packages/frontend/src/components/MkDriveWindow.vue index 64ccbec9c3..72aa79b153 100644 --- a/packages/frontend/src/components/MkDriveWindow.vue +++ b/packages/frontend/src/components/MkDriveWindow.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkWindow ref="window" @@ -18,7 +23,7 @@ import { } from 'vue'; import * as Misskey from 'misskey-js'; import XDrive from '@/components/MkDrive.vue'; import MkWindow from '@/components/MkWindow.vue'; -import { i18n } from '@/i18n'; +import { i18n } from '@/i18n.js'; defineProps<{ initialFolder?: Misskey.entities.DriveFolder; diff --git a/packages/frontend/src/components/MkEmojiPicker.section.vue b/packages/frontend/src/components/MkEmojiPicker.section.vue index 89abf1d946..08297ea5ba 100644 --- a/packages/frontend/src/components/MkEmojiPicker.section.vue +++ b/packages/frontend/src/components/MkEmojiPicker.section.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <!-- このコンポーネントの要素のclassは親から利用されるのでむやみに弄らないこと --> <section> @@ -22,7 +27,7 @@ <script lang="ts" setup> import { ref, computed, Ref } from 'vue'; -import { getEmojiName } from '@/scripts/emojilist'; +import { getEmojiName } from '@/scripts/emojilist.js'; const props = defineProps<{ emojis: string[] | Ref<string[]>; diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index cf856fd31f..32f440ae5d 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div class="omfetrab" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer, asWindow }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }"> <input ref="searchEl" :value="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" @input="input()" @paste.stop="paste" @keydown.stop.prevent.enter="onEnter"> @@ -95,15 +100,15 @@ import { ref, shallowRef, computed, watch, onMounted } from 'vue'; import * as Misskey from 'misskey-js'; import XSection from '@/components/MkEmojiPicker.section.vue'; -import { emojilist, emojiCharByCategory, UnicodeEmojiDef, unicodeEmojiCategories as categories, getEmojiName } from '@/scripts/emojilist'; +import { emojilist, emojiCharByCategory, UnicodeEmojiDef, unicodeEmojiCategories as categories, getEmojiName } from '@/scripts/emojilist.js'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; -import * as os from '@/os'; -import { isTouchUsing } from '@/scripts/touch'; -import { deviceKind } from '@/scripts/device-kind'; -import { i18n } from '@/i18n'; -import { defaultStore } from '@/store'; -import { customEmojiCategories, customEmojis, customEmojisMap } from '@/custom-emojis'; -import { $i } from '@/account'; +import * as os from '@/os.js'; +import { isTouchUsing } from '@/scripts/touch.js'; +import { deviceKind } from '@/scripts/device-kind.js'; +import { i18n } from '@/i18n.js'; +import { defaultStore } from '@/store.js'; +import { customEmojiCategories, customEmojis, customEmojisMap } from '@/custom-emojis.js'; +import { $i } from '@/account.js'; const props = withDefaults(defineProps<{ showPinned?: boolean; @@ -151,7 +156,7 @@ watch(q, () => { const newQ = q.value.replace(/:/g, '').toLowerCase(); const searchCustom = () => { - const max = 8; + const max = 100; const emojis = customEmojis.value; const matches = new Set<Misskey.entities.CustomEmoji>(); @@ -214,7 +219,7 @@ watch(q, () => { }; const searchUnicode = () => { - const max = 8; + const max = 100; const emojis = emojilist; const matches = new Set<UnicodeEmojiDef>(); diff --git a/packages/frontend/src/components/MkEmojiPickerDialog.vue b/packages/frontend/src/components/MkEmojiPickerDialog.vue index cfb65e3b63..9d3132c540 100644 --- a/packages/frontend/src/components/MkEmojiPickerDialog.vue +++ b/packages/frontend/src/components/MkEmojiPickerDialog.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkModal ref="modal" @@ -29,7 +34,7 @@ import { shallowRef } from 'vue'; import MkModal from '@/components/MkModal.vue'; import MkEmojiPicker from '@/components/MkEmojiPicker.vue'; -import { defaultStore } from '@/store'; +import { defaultStore } from '@/store.js'; withDefaults(defineProps<{ manualShowing?: boolean | null; diff --git a/packages/frontend/src/components/MkEmojiPickerWindow.vue b/packages/frontend/src/components/MkEmojiPickerWindow.vue index 9fecfd6082..1a2c55e785 100644 --- a/packages/frontend/src/components/MkEmojiPickerWindow.vue +++ b/packages/frontend/src/components/MkEmojiPickerWindow.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkWindow ref="window" diff --git a/packages/frontend/src/components/MkFeaturedPhotos.vue b/packages/frontend/src/components/MkFeaturedPhotos.vue index 216b3905f7..cef1943d5c 100644 --- a/packages/frontend/src/components/MkFeaturedPhotos.vue +++ b/packages/frontend/src/components/MkFeaturedPhotos.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div v-if="meta" :class="$style.root" :style="{ backgroundImage: `url(${ meta.backgroundImageUrl })` }"></div> </template> @@ -5,7 +10,7 @@ <script lang="ts" setup> import { ref } from 'vue'; import * as Misskey from 'misskey-js'; -import * as os from '@/os'; +import * as os from '@/os.js'; const meta = ref<Misskey.entities.DetailedInstanceMetadata>(); diff --git a/packages/frontend/src/components/MkFileCaptionEditWindow.vue b/packages/frontend/src/components/MkFileCaptionEditWindow.vue index 61b87bda78..28888fb9c8 100644 --- a/packages/frontend/src/components/MkFileCaptionEditWindow.vue +++ b/packages/frontend/src/components/MkFileCaptionEditWindow.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkModalWindow ref="dialog" @@ -25,7 +30,7 @@ import * as Misskey from 'misskey-js'; import MkModalWindow from '@/components/MkModalWindow.vue'; import MkTextarea from '@/components/MkTextarea.vue'; import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue'; -import { i18n } from '@/i18n'; +import { i18n } from '@/i18n.js'; const props = defineProps<{ file: Misskey.entities.DriveFile; diff --git a/packages/frontend/src/components/MkFileListForAdmin.vue b/packages/frontend/src/components/MkFileListForAdmin.vue index 77b38b4bbf..3edd30bc37 100644 --- a/packages/frontend/src/components/MkFileListForAdmin.vue +++ b/packages/frontend/src/components/MkFileListForAdmin.vue @@ -1,10 +1,15 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div> <MkPagination v-slot="{items}" :pagination="pagination" class="urempief" :class="{ grid: viewMode === 'grid' }"> <MkA v-for="file in items" :key="file.id" - v-tooltip.mfm="`${file.type}\n${bytes(file.size)}\n${dateString(file.createdAt)}\nby ${file.user ? '@' + Acct.toString(file.user) : 'system'}`" + v-tooltip.mfm="`${file.type}\n${bytes(file.size)}\n${dateString(file.createdAt)}\nby ${file.user ? '@' + Misskey.acct.toString(file.user) : 'system'}`" :to="`/admin/file/${file.id}`" class="file _button" > @@ -32,12 +37,12 @@ </template> <script lang="ts" setup> -import * as Acct from 'misskey-js/built/acct'; +import * as Misskey from 'misskey-js'; import MkPagination from '@/components/MkPagination.vue'; import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue'; -import bytes from '@/filters/bytes'; -import { i18n } from '@/i18n'; -import { dateString } from '@/filters/date'; +import bytes from '@/filters/bytes.js'; +import { i18n } from '@/i18n.js'; +import { dateString } from '@/filters/date.js'; const props = defineProps<{ pagination: any; diff --git a/packages/frontend/src/components/MkFlashPreview.vue b/packages/frontend/src/components/MkFlashPreview.vue index b5505ac8fd..ab435585d9 100644 --- a/packages/frontend/src/components/MkFlashPreview.vue +++ b/packages/frontend/src/components/MkFlashPreview.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkA :to="`/play/${flash.id}`" class="vhpxefrk _panel" tabindex="-1"> <article> @@ -15,10 +20,10 @@ <script lang="ts" setup> import { } from 'vue'; -import { userName } from '@/filters/user'; +import { userName } from '@/filters/user.js'; const props = defineProps<{ - //flash: misskey.entities.Flash; + //flash: Misskey.entities.Flash; flash: any; }>(); </script> diff --git a/packages/frontend/src/components/MkFoldableSection.vue b/packages/frontend/src/components/MkFoldableSection.vue index 5dd07fc7da..ed3cb0868b 100644 --- a/packages/frontend/src/components/MkFoldableSection.vue +++ b/packages/frontend/src/components/MkFoldableSection.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div ref="el" :class="$style.root"> <header :class="$style.header" class="_button" :style="{ background: bg }" @click="showBody = !showBody"> @@ -25,8 +30,8 @@ <script lang="ts" setup> import { onMounted, ref, shallowRef, watch } from 'vue'; import tinycolor from 'tinycolor2'; -import { miLocalStorage } from '@/local-storage'; -import { defaultStore } from '@/store'; +import { miLocalStorage } from '@/local-storage.js'; +import { defaultStore } from '@/store.js'; const miLocalStoragePrefix = 'ui:folder:' as const; diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue index 70f0cc5cda..60ecc13056 100644 --- a/packages/frontend/src/components/MkFolder.vue +++ b/packages/frontend/src/components/MkFolder.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div ref="rootEl" :class="$style.root" role="group" :aria-expanded="opened"> <MkStickyContainer> @@ -46,7 +51,7 @@ <script lang="ts" setup> import { nextTick, onMounted } from 'vue'; -import { defaultStore } from '@/store'; +import { defaultStore } from '@/store.js'; const props = withDefaults(defineProps<{ defaultOpen?: boolean; diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue index b732fbb2b9..15043fcd0b 100644 --- a/packages/frontend/src/components/MkFollowButton.vue +++ b/packages/frontend/src/components/MkFollowButton.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <button class="_button" @@ -32,11 +37,11 @@ <script lang="ts" setup> import { onBeforeUnmount, onMounted } from 'vue'; import * as Misskey from 'misskey-js'; -import * as os from '@/os'; -import { useStream } from '@/stream'; -import { i18n } from '@/i18n'; -import { claimAchievement } from '@/scripts/achievements'; -import { $i } from '@/account'; +import * as os from '@/os.js'; +import { useStream } from '@/stream.js'; +import { i18n } from '@/i18n.js'; +import { claimAchievement } from '@/scripts/achievements.js'; +import { $i } from '@/account.js'; const props = withDefaults(defineProps<{ user: Misskey.entities.UserDetailed, diff --git a/packages/frontend/src/components/MkForgotPassword.vue b/packages/frontend/src/components/MkForgotPassword.vue index 1264c42331..521ac11d12 100644 --- a/packages/frontend/src/components/MkForgotPassword.vue +++ b/packages/frontend/src/components/MkForgotPassword.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkModalWindow ref="dialog" @@ -39,9 +44,9 @@ import MkModalWindow from '@/components/MkModalWindow.vue'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; import MkInfo from '@/components/MkInfo.vue'; -import * as os from '@/os'; -import { instance } from '@/instance'; -import { i18n } from '@/i18n'; +import * as os from '@/os.js'; +import { instance } from '@/instance.js'; +import { i18n } from '@/i18n.js'; const emit = defineEmits<{ (ev: 'done'): void; diff --git a/packages/frontend/src/components/MkFormDialog.vue b/packages/frontend/src/components/MkFormDialog.vue index 6d2b391e6d..24404728ca 100644 --- a/packages/frontend/src/components/MkFormDialog.vue +++ b/packages/frontend/src/components/MkFormDialog.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkModalWindow ref="dialog" @@ -64,7 +69,7 @@ import MkRange from './MkRange.vue'; import MkButton from './MkButton.vue'; import MkRadios from './MkRadios.vue'; import MkModalWindow from '@/components/MkModalWindow.vue'; -import { i18n } from '@/i18n'; +import { i18n } from '@/i18n.js'; const props = defineProps<{ title: string; diff --git a/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts b/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts index 72ac0a58f9..29e27e1373 100644 --- a/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts +++ b/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { expect } from '@storybook/jest'; import { userEvent, waitFor, within } from '@storybook/testing-library'; diff --git a/packages/frontend/src/components/MkGalleryPostPreview.vue b/packages/frontend/src/components/MkGalleryPostPreview.vue index 3a39ad963b..316632b1a6 100644 --- a/packages/frontend/src/components/MkGalleryPostPreview.vue +++ b/packages/frontend/src/components/MkGalleryPostPreview.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkA :to="`/gallery/${post.id}`" class="ttasepnz _panel" tabindex="-1" @pointerenter="enterHover" @pointerleave="leaveHover"> <div class="thumbnail"> @@ -27,13 +32,13 @@ </template> <script lang="ts" setup> -import * as misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; import { computed, ref } from 'vue'; import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue'; -import { defaultStore } from '@/store'; +import { defaultStore } from '@/store.js'; const props = defineProps<{ - post: misskey.entities.GalleryPost; + post: Misskey.entities.GalleryPost; }>(); const hover = ref(false); diff --git a/packages/frontend/src/components/MkGoogle.vue b/packages/frontend/src/components/MkGoogle.vue index 227054d963..b899656e4f 100644 --- a/packages/frontend/src/components/MkGoogle.vue +++ b/packages/frontend/src/components/MkGoogle.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="$style.root"> <input v-model="query" :class="$style.input" type="search" :placeholder="q"> @@ -7,7 +12,7 @@ <script lang="ts" setup> import { ref } from 'vue'; -import { i18n } from '@/i18n'; +import { i18n } from '@/i18n.js'; const props = defineProps<{ q: string; diff --git a/packages/frontend/src/components/MkHeatmap.vue b/packages/frontend/src/components/MkHeatmap.vue index e5a39a759b..0022531e58 100644 --- a/packages/frontend/src/components/MkHeatmap.vue +++ b/packages/frontend/src/components/MkHeatmap.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div ref="rootEl"> <MkLoading v-if="fetching"/> @@ -10,11 +15,11 @@ <script lang="ts" setup> import { onMounted, nextTick, watch } from 'vue'; import { Chart } from 'chart.js'; -import * as os from '@/os'; -import { defaultStore } from '@/store'; -import { useChartTooltip } from '@/scripts/use-chart-tooltip'; -import { alpha } from '@/scripts/color'; -import { initChart } from '@/scripts/init-chart'; +import * as os from '@/os.js'; +import { defaultStore } from '@/store.js'; +import { useChartTooltip } from '@/scripts/use-chart-tooltip.js'; +import { alpha } from '@/scripts/color.js'; +import { initChart } from '@/scripts/init-chart.js'; initChart(); diff --git a/packages/frontend/src/components/MkImgWithBlurhash.vue b/packages/frontend/src/components/MkImgWithBlurhash.vue index 6dcc890cd3..4fb573fdbc 100644 --- a/packages/frontend/src/components/MkImgWithBlurhash.vue +++ b/packages/frontend/src/components/MkImgWithBlurhash.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div ref="root" :class="['chromatic-ignore', $style.root, { [$style.cover]: cover }]" :title="title ?? ''"> <TransitionGroup @@ -19,8 +24,8 @@ import { $ref } from 'vue/macros'; import DrawBlurhash from '@/workers/draw-blurhash?worker'; import TestWebGL2 from '@/workers/test-webgl2?worker'; -import { WorkerMultiDispatch } from '@/scripts/worker-multi-dispatch'; -import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash'; +import { WorkerMultiDispatch } from '@/scripts/worker-multi-dispatch.js'; +import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash.js'; const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resolve => { // テスト環境で Web Worker インスタンスは作成できない @@ -56,7 +61,7 @@ const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resol import { computed, nextTick, onMounted, onUnmounted, shallowRef, watch } from 'vue'; import { v4 as uuid } from 'uuid'; import { render } from 'buraha'; -import { defaultStore } from '@/store'; +import { defaultStore } from '@/store.js'; const props = withDefaults(defineProps<{ transition?: { diff --git a/packages/frontend/src/components/MkInfo.vue b/packages/frontend/src/components/MkInfo.vue index cda428a77c..37490887e1 100644 --- a/packages/frontend/src/components/MkInfo.vue +++ b/packages/frontend/src/components/MkInfo.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="[$style.root, { [$style.warn]: warn }]"> <i v-if="warn" class="ti ti-alert-triangle" :class="$style.i"></i> diff --git a/packages/frontend/src/components/MkInput.vue b/packages/frontend/src/components/MkInput.vue index e48032d599..315ce958c5 100644 --- a/packages/frontend/src/components/MkInput.vue +++ b/packages/frontend/src/components/MkInput.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div> <div :class="$style.label" @click="focus"><slot name="label"></slot></div> @@ -38,8 +43,8 @@ import { onMounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue'; import { debounce } from 'throttle-debounce'; import MkButton from '@/components/MkButton.vue'; -import { useInterval } from '@/scripts/use-interval'; -import { i18n } from '@/i18n'; +import { useInterval } from '@/scripts/use-interval.js'; +import { i18n } from '@/i18n.js'; const props = defineProps<{ modelValue: string | number | null; @@ -150,6 +155,10 @@ onMounted(() => { } }); }); + +defineExpose({ + focus, +}); </script> <style lang="scss" module> diff --git a/packages/frontend/src/components/MkInstanceCardMini.vue b/packages/frontend/src/components/MkInstanceCardMini.vue index 3de3f8de65..de726e3aa4 100644 --- a/packages/frontend/src/components/MkInstanceCardMini.vue +++ b/packages/frontend/src/components/MkInstanceCardMini.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="[$style.root, { yellow: instance.isNotResponding, red: instance.isBlocked, gray: instance.isSuspended }]"> <img class="icon" :src="getInstanceIcon(instance)" alt="" loading="lazy"/> @@ -10,13 +15,13 @@ </template> <script lang="ts" setup> -import * as misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; import MkMiniChart from '@/components/MkMiniChart.vue'; -import * as os from '@/os'; -import { getProxiedImageUrlNullable } from '@/scripts/media-proxy'; +import * as os from '@/os.js'; +import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js'; const props = defineProps<{ - instance: misskey.entities.Instance; + instance: Misskey.entities.Instance; }>(); let chartValues = $ref<number[] | null>(null); diff --git a/packages/frontend/src/components/MkInstanceStats.vue b/packages/frontend/src/components/MkInstanceStats.vue index 6fcd8f7811..509254de74 100644 --- a/packages/frontend/src/components/MkInstanceStats.vue +++ b/packages/frontend/src/components/MkInstanceStats.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="$style.root"> <MkFoldableSection class="item"> @@ -83,14 +88,14 @@ import { onMounted } from 'vue'; import { Chart } from 'chart.js'; import MkSelect from '@/components/MkSelect.vue'; import MkChart from '@/components/MkChart.vue'; -import { useChartTooltip } from '@/scripts/use-chart-tooltip'; -import * as os from '@/os'; -import { i18n } from '@/i18n'; +import { useChartTooltip } from '@/scripts/use-chart-tooltip.js'; +import * as os from '@/os.js'; +import { i18n } from '@/i18n.js'; import MkHeatmap from '@/components/MkHeatmap.vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; import MkRetentionHeatmap from '@/components/MkRetentionHeatmap.vue'; import MkRetentionLineChart from '@/components/MkRetentionLineChart.vue'; -import { initChart } from '@/scripts/init-chart'; +import { initChart } from '@/scripts/init-chart.js'; initChart(); diff --git a/packages/frontend/src/components/MkInstanceTicker.vue b/packages/frontend/src/components/MkInstanceTicker.vue index 646172fe8d..1a4f2bfbb9 100644 --- a/packages/frontend/src/components/MkInstanceTicker.vue +++ b/packages/frontend/src/components/MkInstanceTicker.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="$style.root" :style="bg"> <img v-if="faviconUrl" :class="$style.icon" :src="faviconUrl"/> @@ -7,9 +12,9 @@ <script lang="ts" setup> import { } from 'vue'; -import { instanceName } from '@/config'; -import { instance as Instance } from '@/instance'; -import { getProxiedImageUrlNullable } from '@/scripts/media-proxy'; +import { instanceName } from '@/config.js'; +import { instance as Instance } from '@/instance.js'; +import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js'; const props = defineProps<{ instance?: { diff --git a/packages/frontend/src/components/MkInviteCode.stories.impl.ts b/packages/frontend/src/components/MkInviteCode.stories.impl.ts index def0a96e6a..0127ce061b 100644 --- a/packages/frontend/src/components/MkInviteCode.stories.impl.ts +++ b/packages/frontend/src/components/MkInviteCode.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; import { rest } from 'msw'; @@ -48,13 +53,13 @@ export const Default = { export const Used = { ...Default, args: { - invite: inviteCode(true) as any + invite: inviteCode(true) as any, }, } satisfies StoryObj<typeof MkInviteCode>; export const Expired = { ...Default, args: { - invite: inviteCode(false, true, true) as any + invite: inviteCode(false, true, true) as any, }, } satisfies StoryObj<typeof MkInviteCode>; diff --git a/packages/frontend/src/components/MkInviteCode.vue b/packages/frontend/src/components/MkInviteCode.vue index 97bf732356..ff3794ad18 100644 --- a/packages/frontend/src/components/MkInviteCode.vue +++ b/packages/frontend/src/components/MkInviteCode.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkFolder> <template #label>{{ invite.code }}</template> @@ -54,15 +59,15 @@ <script lang="ts" setup> import { computed } from 'vue'; -import * as misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; import MkFolder from '@/components/MkFolder.vue'; import MkButton from '@/components/MkButton.vue'; -import copyToClipboard from '@/scripts/copy-to-clipboard'; -import { i18n } from '@/i18n'; -import * as os from '@/os'; +import copyToClipboard from '@/scripts/copy-to-clipboard.js'; +import { i18n } from '@/i18n.js'; +import * as os from '@/os.js'; const props = defineProps<{ - invite: misskey.entities.Invite; + invite: Misskey.entities.Invite; moderator?: boolean; }>(); diff --git a/packages/frontend/src/components/MkKeyValue.vue b/packages/frontend/src/components/MkKeyValue.vue index 4b6a775635..4f1e4df040 100644 --- a/packages/frontend/src/components/MkKeyValue.vue +++ b/packages/frontend/src/components/MkKeyValue.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="[$style.root, { [$style.oneline]: oneline }]"> <div :class="$style.key"> @@ -12,9 +17,9 @@ <script lang="ts" setup> import { } from 'vue'; -import copyToClipboard from '@/scripts/copy-to-clipboard'; -import * as os from '@/os'; -import { i18n } from '@/i18n'; +import copyToClipboard from '@/scripts/copy-to-clipboard.js'; +import * as os from '@/os.js'; +import { i18n } from '@/i18n.js'; const props = withDefaults(defineProps<{ copy?: string | null; diff --git a/packages/frontend/src/components/MkLaunchPad.vue b/packages/frontend/src/components/MkLaunchPad.vue index 9262778612..321acc0fbc 100644 --- a/packages/frontend/src/components/MkLaunchPad.vue +++ b/packages/frontend/src/components/MkLaunchPad.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkModal ref="modal" v-slot="{ type, maxHeight }" :preferType="preferedModalType" :anchor="anchor" :transparentBg="true" :src="src" @click="modal.close()" @closed="emit('closed')"> <div class="szkkfdyq _popup _shadow" :class="{ asDrawer: type === 'drawer' }" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : '' }"> @@ -23,8 +28,8 @@ import { } from 'vue'; import MkModal from '@/components/MkModal.vue'; import { navbarItemDef } from '@/navbar'; -import { defaultStore } from '@/store'; -import { deviceKind } from '@/scripts/device-kind'; +import { defaultStore } from '@/store.js'; +import { deviceKind } from '@/scripts/device-kind.js'; const props = withDefaults(defineProps<{ src?: HTMLElement; diff --git a/packages/frontend/src/components/MkLink.vue b/packages/frontend/src/components/MkLink.vue index 8e61c70484..0501d797f8 100644 --- a/packages/frontend/src/components/MkLink.vue +++ b/packages/frontend/src/components/MkLink.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <component :is="self ? 'MkA' : 'a'" ref="el" style="word-break: break-all;" class="_link" :[attr]="self ? url.substring(local.length) : url" :rel="rel" :target="target" @@ -10,9 +15,9 @@ <script lang="ts" setup> import { defineAsyncComponent } from 'vue'; -import { url as local } from '@/config'; -import { useTooltip } from '@/scripts/use-tooltip'; -import * as os from '@/os'; +import { url as local } from '@/config.js'; +import { useTooltip } from '@/scripts/use-tooltip.js'; +import * as os from '@/os.js'; const props = withDefaults(defineProps<{ url: string; diff --git a/packages/frontend/src/components/MkMarquee.vue b/packages/frontend/src/components/MkMarquee.vue index 5ca04b0b48..f9d0573227 100644 --- a/packages/frontend/src/components/MkMarquee.vue +++ b/packages/frontend/src/components/MkMarquee.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <script lang="ts"> import { h, onMounted, onUnmounted, ref, watch } from 'vue'; diff --git a/packages/frontend/src/components/MkMediaBanner.vue b/packages/frontend/src/components/MkMediaBanner.vue index 5902d6fd25..10b2ac9ece 100644 --- a/packages/frontend/src/components/MkMediaBanner.vue +++ b/packages/frontend/src/components/MkMediaBanner.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="$style.root"> <div v-if="media.isSensitive && hide" :class="$style.sensitive" @click="hide = false"> @@ -29,12 +34,12 @@ <script lang="ts" setup> import { onMounted } from 'vue'; -import * as misskey from 'misskey-js'; -import { soundConfigStore } from '@/scripts/sound'; -import { i18n } from '@/i18n'; +import * as Misskey from 'misskey-js'; +import { soundConfigStore } from '@/scripts/sound.js'; +import { i18n } from '@/i18n.js'; const props = withDefaults(defineProps<{ - media: misskey.entities.DriveFile; + media: Misskey.entities.DriveFile; }>(), { }); diff --git a/packages/frontend/src/components/MkMediaImage.vue b/packages/frontend/src/components/MkMediaImage.vue index 7e5c2c8dc3..cc1c28a9e1 100644 --- a/packages/frontend/src/components/MkMediaImage.vue +++ b/packages/frontend/src/components/MkMediaImage.vue @@ -1,32 +1,44 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> -<div :class="hide ? $style.hidden : $style.visible" :style="darkMode ? '--c: rgb(255 255 255 / 2%);' : '--c: rgb(0 0 0 / 2%);'" @click="onclick"> - <a - :class="$style.imageContainer" - :href="image.url" - :title="image.name" +<div :class="[hide ? $style.hidden : $style.visible, (image.isSensitive && defaultStore.state.highlightSensitiveMedia) && $style.sensitive]" :style="darkMode ? '--c: rgb(255 255 255 / 2%);' : '--c: rgb(0 0 0 / 2%);'" @click="onclick"> + <component + :is="disableImageLink ? 'div' : 'a'" + v-bind="disableImageLink ? { + title: image.name, + class: $style.imageContainer, + } : { + title: image.name, + class: $style.imageContainer, + href: image.url, + style: 'cursor: zoom-in;' + }" > <ImgWithBlurhash :hash="image.blurhash" :src="(defaultStore.state.enableDataSaverMode && hide) ? null : url" :forceBlurhash="hide" - :cover="hide" + :cover="hide || cover" :alt="image.comment || image.name" :title="image.comment || image.name" :width="image.properties.width" :height="image.properties.height" - :style="hide ? 'filter: brightness(0.5);' : null" + :style="hide ? 'filter: brightness(0.7);' : null" /> - </a> + </component> <template v-if="hide"> <div :class="$style.hiddenText"> <div :class="$style.hiddenTextWrapper"> <b v-if="image.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.enableDataSaverMode ? ` (${i18n.ts.image}${image.size ? ' ' + bytes(image.size) : ''})` : '' }}</b> <b v-else style="display: block;"><i class="ti ti-photo"></i> {{ defaultStore.state.enableDataSaverMode && image.size ? bytes(image.size) : i18n.ts.image }}</b> - <span style="display: block;">{{ i18n.ts.clickToShow }}</span> + <span v-if="controls" style="display: block;">{{ i18n.ts.clickToShow }}</span> </div> </div> </template> - <template v-else> + <template v-else-if="controls"> <div :class="$style.indicators"> <div v-if="['image/gif', 'image/apng'].includes(image.type)" :class="$style.indicator">GIF</div> <div v-if="image.comment" :class="$style.indicator">ALT</div> @@ -40,19 +52,26 @@ <script lang="ts" setup> import { watch } from 'vue'; -import * as misskey from 'misskey-js'; -import { getStaticImageUrl } from '@/scripts/media-proxy'; -import bytes from '@/filters/bytes'; +import * as Misskey from 'misskey-js'; +import { getStaticImageUrl } from '@/scripts/media-proxy.js'; +import bytes from '@/filters/bytes.js'; import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue'; -import { defaultStore } from '@/store'; -import { i18n } from '@/i18n'; -import * as os from '@/os'; -import { iAmModerator } from '@/account'; +import { defaultStore } from '@/store.js'; +import { i18n } from '@/i18n.js'; +import * as os from '@/os.js'; +import { iAmModerator } from '@/account.js'; -const props = defineProps<{ - image: misskey.entities.DriveFile; +const props = withDefaults(defineProps<{ + image: Misskey.entities.DriveFile; raw?: boolean; -}>(); + cover?: boolean; + disableImageLink?: boolean; + controls?: boolean; +}>(), { + cover: false, + disableImageLink: false, + controls: true, +}); let hide = $ref(true); let darkMode: boolean = $ref(defaultStore.state.darkMode); @@ -65,6 +84,9 @@ const url = $computed(() => (props.raw || defaultStore.state.loadRawImages) ); function onclick() { + if (!props.controls) { + return; + } if (hide) { hide = false; } @@ -102,6 +124,22 @@ function showMenu(ev: MouseEvent) { position: relative; } +.sensitive { + position: relative; + + &::after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + border-radius: inherit; + box-shadow: inset 0 0 0 4px var(--warn); + } +} + .hiddenText { position: absolute; left: 0; @@ -112,6 +150,7 @@ function showMenu(ev: MouseEvent) { display: flex; justify-content: center; align-items: center; + cursor: pointer; } .hide { @@ -162,7 +201,6 @@ function showMenu(ev: MouseEvent) { .imageContainer { display: block; - cursor: zoom-in; overflow: hidden; width: 100%; height: 100%; diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue index 0cdccfb169..f134f2021d 100644 --- a/packages/frontend/src/components/MkMediaList.vue +++ b/packages/frontend/src/components/MkMediaList.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div ref="root"> <XBanner v-for="media in mediaList.filter(media => !previewable(media))" :key="media.id" :media="media"/> @@ -59,20 +64,20 @@ async function getClientWidthWithCache(targetEl: HTMLElement, containerEl: HTMLE <script lang="ts" setup> import { onMounted, onUnmounted, shallowRef } from 'vue'; -import * as misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; import PhotoSwipeLightbox from 'photoswipe/lightbox'; import PhotoSwipe from 'photoswipe'; import 'photoswipe/style.css'; import XBanner from '@/components/MkMediaBanner.vue'; import XImage from '@/components/MkMediaImage.vue'; import XVideo from '@/components/MkMediaVideo.vue'; -import * as os from '@/os'; +import * as os from '@/os.js'; import { FILE_TYPE_BROWSERSAFE } from '@/const'; -import { defaultStore } from '@/store'; -import { getScrollContainer, getBodyScrollHeight } from '@/scripts/scroll'; +import { defaultStore } from '@/store.js'; +import { getScrollContainer, getBodyScrollHeight } from '@/scripts/scroll.js'; const props = defineProps<{ - mediaList: misskey.entities.DriveFile[]; + mediaList: Misskey.entities.DriveFile[]; raw?: boolean; }>(); @@ -247,7 +252,7 @@ onUnmounted(() => { lightbox = null; }); -const previewable = (file: misskey.entities.DriveFile): boolean => { +const previewable = (file: Misskey.entities.DriveFile): boolean => { if (file.type === 'image/svg+xml') return true; // svgのwebpublic/thumbnailはpngなのでtrue // FILE_TYPE_BROWSERSAFEに適合しないものはブラウザで表示するのに不適切 return (file.type.startsWith('video') || file.type.startsWith('image')) && FILE_TYPE_BROWSERSAFE.includes(file.type); diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue index dc5807b2dd..751b5f7570 100644 --- a/packages/frontend/src/components/MkMediaVideo.vue +++ b/packages/frontend/src/components/MkMediaVideo.vue @@ -1,5 +1,10 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> -<div v-if="hide" :class="$style.hidden" @click="hide = false"> +<div v-if="hide" :class="[$style.hidden, (video.isSensitive && defaultStore.state.highlightSensitiveMedia) && $style.sensitiveContainer]" @click="hide = false"> <!-- 【注意】dataSaverMode が有効になっている際には、hide が false になるまでサムネイルや動画を読み込まないようにすること --> <div :class="$style.sensitive"> <b v-if="video.isSensitive" style="display: block;"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.enableDataSaverMode ? ` (${i18n.ts.video}${video.size ? ' ' + bytes(video.size) : ''})` : '' }}</b> @@ -7,7 +12,7 @@ <span>{{ i18n.ts.clickToShow }}</span> </div> </div> -<div v-else :class="$style.visible"> +<div v-else :class="[$style.visible, (video.isSensitive && defaultStore.state.highlightSensitiveMedia) && $style.sensitiveContainer]"> <video :class="$style.video" :poster="video.thumbnailUrl" @@ -19,7 +24,6 @@ > <source :src="video.url" - :type="video.type" > </video> <i class="ti ti-eye-off" :class="$style.hide" @click="hide = true"></i> @@ -28,13 +32,13 @@ <script lang="ts" setup> import { ref } from 'vue'; -import * as misskey from 'misskey-js'; -import bytes from '@/filters/bytes'; -import { defaultStore } from '@/store'; -import { i18n } from '@/i18n'; +import * as Misskey from 'misskey-js'; +import bytes from '@/filters/bytes.js'; +import { defaultStore } from '@/store.js'; +import { i18n } from '@/i18n.js'; const props = defineProps<{ - video: misskey.entities.DriveFile; + video: Misskey.entities.DriveFile; }>(); const hide = ref((defaultStore.state.nsfw === 'force' || defaultStore.state.enableDataSaverMode) ? true : (props.video.isSensitive && defaultStore.state.nsfw !== 'ignore')); @@ -45,6 +49,22 @@ const hide = ref((defaultStore.state.nsfw === 'force' || defaultStore.state.enab position: relative; } +.sensitiveContainer { + position: relative; + + &::after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + border-radius: inherit; + box-shadow: inset 0 0 0 4px var(--warn); + } +} + .hide { display: block; position: absolute; diff --git a/packages/frontend/src/components/MkMention.vue b/packages/frontend/src/components/MkMention.vue index bb256c394b..ffc9ca3f4d 100644 --- a/packages/frontend/src/components/MkMention.vue +++ b/packages/frontend/src/components/MkMention.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkA v-user-preview="canonical" :class="[$style.root, { [$style.isMe]: isMe }]" :to="url" :style="{ background: bgCss }"> <img :class="$style.icon" :src="`/avatar/@${username}@${host}`" alt=""> @@ -12,9 +17,9 @@ import { toUnicode } from 'punycode'; import { } from 'vue'; import tinycolor from 'tinycolor2'; -import { host as localHost } from '@/config'; -import { $i } from '@/account'; -import { defaultStore } from '@/store'; +import { host as localHost } from '@/config.js'; +import { $i } from '@/account.js'; +import { defaultStore } from '@/store.js'; const props = defineProps<{ username: string; diff --git a/packages/frontend/src/components/MkMenu.child.vue b/packages/frontend/src/components/MkMenu.child.vue index 4fedfe7014..962dcd91eb 100644 --- a/packages/frontend/src/components/MkMenu.child.vue +++ b/packages/frontend/src/components/MkMenu.child.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div ref="el" :class="$style.root"> <MkMenu :items="items" :align="align" :width="width" :asDrawer="false" @close="onChildClosed"/> @@ -7,7 +12,7 @@ <script lang="ts" setup> import { nextTick, onMounted, onUnmounted, shallowRef, watch } from 'vue'; import MkMenu from './MkMenu.vue'; -import { MenuItem } from '@/types/menu'; +import { MenuItem } from '@/types/menu.js'; const props = defineProps<{ items: MenuItem[]; diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue index 7dd6a8c88f..079f07ee47 100644 --- a/packages/frontend/src/components/MkMenu.vue +++ b/packages/frontend/src/components/MkMenu.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div role="menu"> <div @@ -30,13 +35,14 @@ <MkAvatar :user="item.user" :class="$style.avatar"/><MkUserName :user="item.user"/> <span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span> </button> - <span v-else-if="item.type === 'switch'" role="menuitemcheckbox" :tabindex="i" :class="$style.item" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> - <MkSwitch v-model="item.ref" :disabled="item.disabled" class="form-switch">{{ item.text }}</MkSwitch> - </span> - <button v-else-if="item.type === 'parent'" role="menuitem" :tabindex="i" class="_button" :class="[$style.item, $style.parent, { [$style.childShowing]: childShowingItem === item }]" @mouseenter="showChildren(item, $event)"> - <i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i> - <span>{{ item.text }}</span> - <span :class="$style.caret"><i class="ti ti-chevron-right ti-fw"></i></span> + <button v-else-if="item.type === 'switch'" role="menuitemcheckbox" :tabindex="i" class="_button" :class="[$style.item, $style.switch, { [$style.switchDisabled]: item.disabled } ]" @click="switchItem(item)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> + <MkSwitchButton :class="$style.switchButton" :checked="item.ref" :disabled="item.disabled" @toggle="switchItem(item)"/> + <span :class="$style.switchText">{{ item.text }}</span> + </button> + <button v-else-if="item.type === 'parent'" class="_button" role="menuitem" :tabindex="i" :class="[$style.item, $style.parent, { [$style.childShowing]: childShowingItem === item }]" @mouseenter="preferClick ? null : showChildren(item, $event)" @click="!preferClick ? null : showChildren(item, $event)"> + <i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]" style="pointer-events: none;"></i> + <span style="pointer-events: none;">{{ item.text }}</span> + <span :class="$style.caret" style="pointer-events: none;"><i class="ti ti-chevron-right ti-fw"></i></span> </button> <button v-else :tabindex="i" class="_button" role="menuitem" :class="[$style.item, { [$style.danger]: item.danger, [$style.active]: item.active }]" :disabled="item.active" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)"> <i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i> @@ -50,19 +56,24 @@ </span> </div> <div v-if="childMenu"> - <XChild ref="child" :items="childMenu" :targetElement="childTarget" :rootElement="itemsEl" showing @actioned="childActioned"/> + <XChild ref="child" :items="childMenu" :targetElement="childTarget" :rootElement="itemsEl" showing @actioned="childActioned" @close="close(false)"/> </div> </div> </template> -<script lang="ts" setup> -import { defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'; -import { focusPrev, focusNext } from '@/scripts/focus'; -import MkSwitch from '@/components/MkSwitch.vue'; -import { MenuItem, InnerMenuItem, MenuPending, MenuAction } from '@/types/menu'; -import * as os from '@/os'; -import { i18n } from '@/i18n'; +<script lang="ts"> +import { Ref, defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'; +import { focusPrev, focusNext } from '@/scripts/focus.js'; +import MkSwitchButton from '@/components/MkSwitch.button.vue'; +import { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuParent } from '@/types/menu'; +import * as os from '@/os.js'; +import { i18n } from '@/i18n.js'; +import { isTouchUsing } from '@/scripts/touch.js'; + +const childrenCache = new WeakMap<MenuParent, MenuItem[]>(); +</script> +<script lang="ts" setup> const XChild = defineAsyncComponent(() => import('./MkMenu.child.vue')); const props = defineProps<{ @@ -76,6 +87,7 @@ const props = defineProps<{ const emit = defineEmits<{ (ev: 'close', actioned?: boolean): void; + (ev: 'hide'): void; }>(); let itemsEl = $shallowRef<HTMLDivElement>(); @@ -92,6 +104,8 @@ let keymap = $computed(() => ({ let childShowingItem = $ref<MenuItem | null>(); +let preferClick = isTouchUsing || props.asDrawer; + watch(() => props.items, () => { const items: (MenuItem | MenuPending)[] = [...props.items].filter(item => item !== undefined); @@ -111,7 +125,7 @@ watch(() => props.items, () => { immediate: true, }); -let childMenu = ref<MenuItem[] | null>(); +const childMenu = ref<MenuItem[] | null>(); let childTarget = $shallowRef<HTMLElement | null>(); function closeChild() { @@ -124,11 +138,11 @@ function childActioned() { close(true); } -function onGlobalMousedown(event: MouseEvent) { +const onGlobalMousedown = (event: MouseEvent) => { if (childTarget && (event.target === childTarget || childTarget.contains(event.target))) return; if (child && child.checkHit(event)) return; closeChild(); -} +}; let childCloseTimer: null | number = null; function onItemMouseEnter(item) { @@ -140,31 +154,30 @@ function onItemMouseLeave(item) { if (childCloseTimer) window.clearTimeout(childCloseTimer); } -let childrenCache = new WeakMap(); -async function showChildren(item: MenuItem, ev: MouseEvent) { - const children = ref([]); - if (childrenCache.has(item)) { - children.value = childrenCache.get(item); - } else { - if (typeof item.children === 'function') { - children.value = [{ - type: 'pending', - }]; - item.children().then(x => { - children.value = x; - childrenCache.set(item, x); - }); +async function showChildren(item: MenuParent, ev: MouseEvent) { + const children = await (async () => { + if (childrenCache.has(item)) { + return childrenCache.get(item)!; } else { - children.value = item.children; + if (typeof item.children === 'function') { + return Promise.resolve(item.children()); + } else { + return item.children; + } } - } + })(); + + childrenCache.set(item, children); if (props.asDrawer) { - os.popupMenu(children, ev.currentTarget ?? ev.target); - close(); + os.popupMenu(children, ev.currentTarget ?? ev.target).finally(() => { + emit('close'); + }); + emit('hide'); } else { childTarget = ev.currentTarget ?? ev.target; - childMenu = children; + // これでもリアクティビティは保たれる + childMenu.value = children; childShowingItem = item; } } @@ -186,10 +199,15 @@ function focusDown() { focusNext(document.activeElement); } +function switchItem(item: MenuSwitch & { ref: any }) { + if (item.disabled) return; + item.ref = !item.ref; +} + onMounted(() => { if (props.viaKeyboard) { nextTick(() => { - focusNext(itemsEl.children[0], true, false); + if (itemsEl) focusNext(itemsEl.children[0], true, false); }); } @@ -337,6 +355,7 @@ onBeforeUnmount(() => { } &.parent { + pointer-events: auto; display: flex; align-items: center; cursor: default; @@ -352,6 +371,37 @@ onBeforeUnmount(() => { } } +.switch { + position: relative; + display: flex; + transition: all 0.2s ease; + user-select: none; + cursor: pointer; +} + +.switchDisabled { + cursor: not-allowed; +} + +.switchButton { + margin-left: -2px; +} + +.switchText { + margin-left: 8px; + margin-top: 2px; + overflow: hidden; + text-overflow: ellipsis; +} + +.switchInput { + position: absolute; + width: 0; + height: 0; + opacity: 0; + margin: 0; +} + .icon { margin-right: 8px; } diff --git a/packages/frontend/src/components/MkMiniChart.vue b/packages/frontend/src/components/MkMiniChart.vue index e884455709..8d2a147306 100644 --- a/packages/frontend/src/components/MkMiniChart.vue +++ b/packages/frontend/src/components/MkMiniChart.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" style="overflow:visible"> <defs> @@ -29,7 +34,7 @@ import { watch } from 'vue'; import { v4 as uuid } from 'uuid'; import tinycolor from 'tinycolor2'; -import { useInterval } from '@/scripts/use-interval'; +import { useInterval } from '@/scripts/use-interval.js'; const props = defineProps<{ src: number[]; diff --git a/packages/frontend/src/components/MkModal.vue b/packages/frontend/src/components/MkModal.vue index bc6e3e4171..ec5039c504 100644 --- a/packages/frontend/src/components/MkModal.vue +++ b/packages/frontend/src/components/MkModal.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <Transition :name="transitionName" @@ -38,10 +43,10 @@ <script lang="ts" setup> import { nextTick, normalizeClass, onMounted, onUnmounted, provide, watch } from 'vue'; -import * as os from '@/os'; -import { isTouchUsing } from '@/scripts/touch'; -import { defaultStore } from '@/store'; -import { deviceKind } from '@/scripts/device-kind'; +import * as os from '@/os.js'; +import { isTouchUsing } from '@/scripts/touch.js'; +import { defaultStore } from '@/store.js'; +import { deviceKind } from '@/scripts/device-kind.js'; function getFixedContainer(el: Element | null): Element | null { if (el == null || el.tagName === 'BODY') return null; diff --git a/packages/frontend/src/components/MkModalWindow.vue b/packages/frontend/src/components/MkModalWindow.vue index 08569b4d6e..1fdf0beb26 100644 --- a/packages/frontend/src/components/MkModalWindow.vue +++ b/packages/frontend/src/components/MkModalWindow.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkModal ref="modal" :preferType="'dialog'" @click="onBgClick" @closed="$emit('closed')"> <div ref="rootEl" :class="$style.root" :style="{ width: `${width}px`, height: `min(${height}px, 100%)` }" @keydown="onKeydown"> diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 02431a4557..b397f3eee9 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div v-if="!muted" @@ -24,7 +29,7 @@ </I18n> <div :class="$style.renoteInfo"> <button ref="renoteTime" :class="$style.renoteTime" class="_button" @click="showRenoteMenu()"> - <i v-if="isMyRenote" class="ti ti-dots" :class="$style.renoteMenu"></i> + <i class="ti ti-dots" :class="$style.renoteMenu"></i> <MkTime :time="note.createdAt"/> </button> <span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]"> @@ -81,9 +86,7 @@ </div> <MkReactionsViewer :note="appearNote" :maxNumber="16"> <template #more> - <button class="_button" :class="$style.reactionDetailsButton" @click="showReactions"> - {{ i18n.ts.more }} - </button> + <div :class="$style.reactionOmitted">{{ i18n.ts.more }}</div> </template> </MkReactionsViewer> <footer :class="$style.footer"> @@ -135,7 +138,7 @@ <script lang="ts" setup> import { computed, inject, onMounted, ref, shallowRef, Ref, defineAsyncComponent } from 'vue'; import * as mfm from 'mfm-js'; -import * as misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; import MkNoteSub from '@/components/MkNoteSub.vue'; import MkNoteHeader from '@/components/MkNoteHeader.vue'; import MkNoteSimple from '@/components/MkNoteSimple.vue'; @@ -146,34 +149,34 @@ import MkPoll from '@/components/MkPoll.vue'; import MkUsersTooltip from '@/components/MkUsersTooltip.vue'; import MkUrlPreview from '@/components/MkUrlPreview.vue'; import MkInstanceTicker from '@/components/MkInstanceTicker.vue'; -import { pleaseLogin } from '@/scripts/please-login'; -import { focusPrev, focusNext } from '@/scripts/focus'; -import { checkWordMute } from '@/scripts/check-word-mute'; -import { userPage } from '@/filters/user'; -import * as os from '@/os'; -import { defaultStore, noteViewInterruptors } from '@/store'; -import { reactionPicker } from '@/scripts/reaction-picker'; -import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm'; -import { $i } from '@/account'; -import { i18n } from '@/i18n'; -import { getNoteClipMenu, getNoteMenu } from '@/scripts/get-note-menu'; -import { useNoteCapture } from '@/scripts/use-note-capture'; -import { deepClone } from '@/scripts/clone'; -import { useTooltip } from '@/scripts/use-tooltip'; -import { claimAchievement } from '@/scripts/achievements'; -import { getNoteSummary } from '@/scripts/get-note-summary'; +import { pleaseLogin } from '@/scripts/please-login.js'; +import { focusPrev, focusNext } from '@/scripts/focus.js'; +import { checkWordMute } from '@/scripts/check-word-mute.js'; +import { userPage } from '@/filters/user.js'; +import * as os from '@/os.js'; +import { defaultStore, noteViewInterruptors } from '@/store.js'; +import { reactionPicker } from '@/scripts/reaction-picker.js'; +import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js'; +import { $i } from '@/account.js'; +import { i18n } from '@/i18n.js'; +import { getAbuseNoteMenu, getCopyNoteLinkMenu, getNoteClipMenu, getNoteMenu } from '@/scripts/get-note-menu.js'; +import { useNoteCapture } from '@/scripts/use-note-capture.js'; +import { deepClone } from '@/scripts/clone.js'; +import { useTooltip } from '@/scripts/use-tooltip.js'; +import { claimAchievement } from '@/scripts/achievements.js'; +import { getNoteSummary } from '@/scripts/get-note-summary.js'; import { MenuItem } from '@/types/menu'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; -import { showMovedDialog } from '@/scripts/show-moved-dialog'; -import { shouldCollapsed } from '@/scripts/collapsed'; +import { showMovedDialog } from '@/scripts/show-moved-dialog.js'; +import { shouldCollapsed } from '@/scripts/collapsed.js'; const props = defineProps<{ - note: misskey.entities.Note; + note: Misskey.entities.Note; pinned?: boolean; }>(); const inChannel = inject('inChannel', null); -const currentClip = inject<Ref<misskey.entities.Clip> | null>('currentClip', null); +const currentClip = inject<Ref<Misskey.entities.Clip> | null>('currentClip', null); let note = $ref(deepClone(props.note)); @@ -201,7 +204,7 @@ const renoteButton = shallowRef<HTMLElement>(); const renoteTime = shallowRef<HTMLElement>(); const reactButton = shallowRef<HTMLElement>(); const clipButton = shallowRef<HTMLElement>(); -let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note : note); +let appearNote = $computed(() => isRenote ? note.renote as Misskey.entities.Note : note); const isMyRenote = $i && ($i.id === note.userId); const showContent = ref(false); const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : null; @@ -314,9 +317,15 @@ function renote(viaKeyboard = false) { const configuredVisibility = defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility; const localOnly = defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly; + let visibility = appearNote.visibility; + visibility = smallerVisibility(visibility, configuredVisibility); + if (appearNote.channel?.isSensitive) { + visibility = smallerVisibility(visibility, 'home'); + } + os.api('notes/create', { localOnly, - visibility: smallerVisibility(appearNote.visibility, configuredVisibility), + visibility, renoteId: appearNote.id, }).then(() => { os.toast(i18n.ts.renoted); @@ -403,14 +412,16 @@ function onContextmenu(ev: MouseEvent): void { ev.preventDefault(); react(); } else { - os.contextMenu(getNoteMenu({ note: note, translating, translation, menuButton, isDeleted, currentClip: currentClip?.value }), ev).then(focus); + const { menu, cleanup } = getNoteMenu({ note: note, translating, translation, menuButton, isDeleted, currentClip: currentClip?.value }); + os.contextMenu(menu, ev).then(focus).finally(cleanup); } } function menu(viaKeyboard = false): void { - os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton, isDeleted, currentClip: currentClip?.value }), menuButton.value, { + const { menu, cleanup } = getNoteMenu({ note: note, translating, translation, menuButton, isDeleted, currentClip: currentClip?.value }); + os.popupMenu(menu, menuButton.value, { viaKeyboard, - }).then(focus); + }).then(focus).finally(cleanup); } async function clip() { @@ -418,21 +429,39 @@ async function clip() { } function showRenoteMenu(viaKeyboard = false): void { - if (!isMyRenote) return; - pleaseLogin(); - os.popupMenu([{ - text: i18n.ts.unrenote, - icon: 'ti ti-trash', - danger: true, - action: () => { - os.api('notes/delete', { - noteId: note.id, - }); - isDeleted.value = true; - }, - }], renoteTime.value, { - viaKeyboard: viaKeyboard, - }); + function getUnrenote(): MenuItem { + return { + text: i18n.ts.unrenote, + icon: 'ti ti-trash', + danger: true, + action: () => { + os.api('notes/delete', { + noteId: note.id, + }); + isDeleted.value = true; + }, + }; + } + + if (isMyRenote) { + pleaseLogin(); + os.popupMenu([ + getCopyNoteLinkMenu(note, i18n.ts.copyLinkRenote), + null, + getUnrenote(), + ], renoteTime.value, { + viaKeyboard: viaKeyboard, + }); + } else { + os.popupMenu([ + getCopyNoteLinkMenu(note, i18n.ts.copyLinkRenote), + null, + getAbuseNoteMenu(note, i18n.ts.reportAbuseRenote), + $i.isModerator || $i.isAdmin ? getUnrenote() : undefined, + ], renoteTime.value, { + viaKeyboard: viaKeyboard, + }); + } } function focus() { @@ -457,12 +486,6 @@ function readPromo() { }); isDeleted.value = true; } - -function showReactions(): void { - os.popup(defineAsyncComponent(() => import('@/components/MkReactedUsersDialog.vue')), { - noteId: appearNote.id, - }, {}, 'closed'); -} </script> <style lang="scss" module> @@ -910,18 +933,11 @@ function showReactions(): void { opacity: 0.7; } -.reactionDetailsButton { +.reactionOmitted { display: inline-block; height: 32px; margin: 2px; padding: 0 6px; - border: dashed 1px var(--divider); - border-radius: 4px; - background: transparent; opacity: .8; - - &:hover { - background: var(--X5); - } } </style> diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index a40b9cd2bd..06663d0477 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div v-if="!muted" @@ -6,7 +11,12 @@ v-hotkey="keymap" :class="$style.root" > - <MkNoteSub v-for="note in conversation" :key="note.id" :class="$style.replyToMore" :note="note"/> + <div v-if="appearNote.reply && appearNote.reply.replyId"> + <div v-if="!conversationLoaded" style="padding: 16px"> + <MkButton style="margin: 0 auto;" primary rounded @click="loadConversation">{{ i18n.ts.loadConversation }}</MkButton> + </div> + <MkNoteSub v-for="note in conversation" :key="note.id" :class="$style.replyToMore" :note="note"/> + </div> <MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" :class="$style.replyTo"/> <div v-if="isRenote" :class="$style.renote"> <MkAvatar :class="$style.renoteAvatar" :user="note.user" link preview/> @@ -120,7 +130,47 @@ </button> </footer> </article> - <MkNoteSub v-for="note in replies" :key="note.id" :note="note" :class="$style.reply" :detail="true"/> + <div :class="$style.tabs"> + <button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'replies' }]" @click="tab = 'replies'"><i class="ti ti-arrow-back-up"></i> {{ i18n.ts.replies }}</button> + <button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'renotes' }]" @click="tab = 'renotes'"><i class="ti ti-repeat"></i> {{ i18n.ts.renotes }}</button> + <button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'reactions' }]" @click="tab = 'reactions'"><i class="ti ti-icons"></i> {{ i18n.ts.reactions }}</button> + </div> + <div> + <div v-if="tab === 'replies'" :class="$style.tab_replies"> + <div v-if="!repliesLoaded" style="padding: 16px"> + <MkButton style="margin: 0 auto;" primary rounded @click="loadReplies">{{ i18n.ts.loadReplies }}</MkButton> + </div> + <MkNoteSub v-for="note in replies" :key="note.id" :note="note" :class="$style.reply" :detail="true"/> + </div> + <div v-else-if="tab === 'renotes'" :class="$style.tab_renotes"> + <MkPagination :pagination="renotesPagination" :disableAutoLoad="true"> + <template #default="{ items }"> + <div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); grid-gap: 12px;"> + <MkA v-for="item in items" :key="item.id" :to="userPage(item.user)"> + <MkUserCardMini :user="item.user" :withChart="false"/> + </MkA> + </div> + </template> + </MkPagination> + </div> + <div v-else-if="tab === 'reactions'" :class="$style.tab_reactions"> + <div :class="$style.reactionTabs"> + <button v-for="reaction in Object.keys(appearNote.reactions)" :key="reaction" :class="[$style.reactionTab, { [$style.reactionTabActive]: reactionTabType === reaction }]" class="_button" @click="reactionTabType = reaction"> + <MkReactionIcon :reaction="reaction"/> + <span style="margin-left: 4px;">{{ appearNote.reactions[reaction] }}</span> + </button> + </div> + <MkPagination v-if="reactionTabType" :key="reactionTabType" :pagination="reactionsPagination" :disableAutoLoad="true"> + <template #default="{ items }"> + <div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); grid-gap: 12px;"> + <MkA v-for="item in items" :key="item.id" :to="userPage(item.user)"> + <MkUserCardMini :user="item.user" :withChart="false"/> + </MkA> + </div> + </template> + </MkPagination> + </div> + </div> </div> <div v-else class="_panel" :class="$style.muted" @click="muted = false"> <I18n :src="i18n.ts.userSaysSomething" tag="small"> @@ -136,7 +186,7 @@ <script lang="ts" setup> import { computed, inject, onMounted, ref, shallowRef } from 'vue'; import * as mfm from 'mfm-js'; -import * as misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; import MkNoteSub from '@/components/MkNoteSub.vue'; import MkNoteSimple from '@/components/MkNoteSimple.vue'; import MkReactionsViewer from '@/components/MkReactionsViewer.vue'; @@ -146,28 +196,31 @@ import MkPoll from '@/components/MkPoll.vue'; import MkUsersTooltip from '@/components/MkUsersTooltip.vue'; import MkUrlPreview from '@/components/MkUrlPreview.vue'; import MkInstanceTicker from '@/components/MkInstanceTicker.vue'; -import { pleaseLogin } from '@/scripts/please-login'; -import { checkWordMute } from '@/scripts/check-word-mute'; -import { userPage } from '@/filters/user'; -import { notePage } from '@/filters/note'; -import * as os from '@/os'; -import { defaultStore, noteViewInterruptors } from '@/store'; -import { reactionPicker } from '@/scripts/reaction-picker'; -import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm'; -import { $i } from '@/account'; -import { i18n } from '@/i18n'; -import { getNoteClipMenu, getNoteMenu } from '@/scripts/get-note-menu'; -import { useNoteCapture } from '@/scripts/use-note-capture'; -import { deepClone } from '@/scripts/clone'; -import { useTooltip } from '@/scripts/use-tooltip'; -import { claimAchievement } from '@/scripts/achievements'; +import { pleaseLogin } from '@/scripts/please-login.js'; +import { checkWordMute } from '@/scripts/check-word-mute.js'; +import { userPage } from '@/filters/user.js'; +import { notePage } from '@/filters/note.js'; +import * as os from '@/os.js'; +import { defaultStore, noteViewInterruptors } from '@/store.js'; +import { reactionPicker } from '@/scripts/reaction-picker.js'; +import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js'; +import { $i } from '@/account.js'; +import { i18n } from '@/i18n.js'; +import { getNoteClipMenu, getNoteMenu } from '@/scripts/get-note-menu.js'; +import { useNoteCapture } from '@/scripts/use-note-capture.js'; +import { deepClone } from '@/scripts/clone.js'; +import { useTooltip } from '@/scripts/use-tooltip.js'; +import { claimAchievement } from '@/scripts/achievements.js'; import { MenuItem } from '@/types/menu'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; -import { showMovedDialog } from '@/scripts/show-moved-dialog'; +import { showMovedDialog } from '@/scripts/show-moved-dialog.js'; +import MkUserCardMini from '@/components/MkUserCardMini.vue'; +import MkPagination, { Paging } from '@/components/MkPagination.vue'; +import MkReactionIcon from '@/components/MkReactionIcon.vue'; +import MkButton from '@/components/MkButton.vue'; const props = defineProps<{ - note: misskey.entities.Note; - pinned?: boolean; + note: Misskey.entities.Note; }>(); const inChannel = inject('inChannel', null); @@ -198,7 +251,7 @@ const renoteButton = shallowRef<HTMLElement>(); const renoteTime = shallowRef<HTMLElement>(); const reactButton = shallowRef<HTMLElement>(); const clipButton = shallowRef<HTMLElement>(); -let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note : note); +let appearNote = $computed(() => isRenote ? note.renote as Misskey.entities.Note : note); const isMyRenote = $i && ($i.id === note.userId); const showContent = ref(false); const isDeleted = ref(false); @@ -207,8 +260,8 @@ const translation = ref(null); const translating = ref(false); const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : null; const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance); -const conversation = ref<misskey.entities.Note[]>([]); -const replies = ref<misskey.entities.Note[]>([]); +const conversation = ref<Misskey.entities.Note[]>([]); +const replies = ref<Misskey.entities.Note[]>([]); const canRenote = computed(() => ['public', 'home'].includes(appearNote.visibility) || appearNote.userId === $i.id); const keymap = { @@ -220,6 +273,26 @@ const keymap = { 's': () => showContent.value !== showContent.value, }; +let tab = $ref('replies'); +let reactionTabType = $ref(null); + +const renotesPagination = $computed(() => ({ + endpoint: 'notes/renotes', + limit: 10, + params: { + noteId: appearNote.id, + }, +})); + +const reactionsPagination = $computed(() => ({ + endpoint: 'notes/reactions', + limit: 10, + params: { + noteId: appearNote.id, + type: reactionTabType, + }, +})); + useNoteCapture({ rootEl: el, note: $$(appearNote), @@ -380,14 +453,16 @@ function onContextmenu(ev: MouseEvent): void { ev.preventDefault(); react(); } else { - os.contextMenu(getNoteMenu({ note: note, translating, translation, menuButton, isDeleted }), ev).then(focus); + const { menu, cleanup } = getNoteMenu({ note: note, translating, translation, menuButton, isDeleted }); + os.contextMenu(menu, ev).then(focus).finally(cleanup); } } function menu(viaKeyboard = false): void { - os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton, isDeleted }), menuButton.value, { + const { menu, cleanup } = getNoteMenu({ note: note, translating, translation, menuButton, isDeleted }); + os.popupMenu(menu, menuButton.value, { viaKeyboard, - }).then(focus); + }).then(focus).finally(cleanup); } async function clip() { @@ -420,14 +495,20 @@ function blur() { el.value.blur(); } -os.api('notes/children', { - noteId: appearNote.id, - limit: 30, -}).then(res => { - replies.value = res; -}); +const repliesLoaded = ref(false); +function loadReplies() { + repliesLoaded.value = true; + os.api('notes/children', { + noteId: appearNote.id, + limit: 30, + }).then(res => { + replies.value = res; + }); +} -if (appearNote.replyId) { +const conversationLoaded = ref(false); +function loadConversation() { + conversationLoaded.value = true; os.api('notes/conversation', { noteId: appearNote.replyId, }).then(res => { @@ -634,10 +715,52 @@ if (appearNote.replyId) { } } -.reply { +.reply:not(:first-child) { border-top: solid 0.5px var(--divider); } +.tabs { + border-top: solid 0.5px var(--divider); + border-bottom: solid 0.5px var(--divider); + display: flex; +} + +.tab { + flex: 1; + padding: 12px 8px; + border-top: solid 2px transparent; + border-bottom: solid 2px transparent; +} + +.tabActive { + border-bottom: solid 2px var(--accent); +} + +.tab_renotes { + padding: 16px; +} + +.tab_reactions { + padding: 16px; +} + +.reactionTabs { + display: flex; + gap: 8px; + flex-wrap: wrap; + margin-bottom: 8px; +} + +.reactionTab { + padding: 4px 6px; + border: solid 1px var(--divider); + border-radius: 6px; +} + +.reactionTabActive { + border-color: var(--accent); +} + @container (max-width: 500px) { .root { font-size: 0.9em; diff --git a/packages/frontend/src/components/MkNoteHeader.vue b/packages/frontend/src/components/MkNoteHeader.vue index e468650430..dda7238d27 100644 --- a/packages/frontend/src/components/MkNoteHeader.vue +++ b/packages/frontend/src/components/MkNoteHeader.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <header :class="$style.root"> <MkA v-user-preview="note.user.id" :class="$style.name" :to="userPage(note.user)"> @@ -25,14 +30,13 @@ <script lang="ts" setup> import { } from 'vue'; -import * as misskey from 'misskey-js'; -import { i18n } from '@/i18n'; -import { notePage } from '@/filters/note'; -import { userPage } from '@/filters/user'; +import * as Misskey from 'misskey-js'; +import { i18n } from '@/i18n.js'; +import { notePage } from '@/filters/note.js'; +import { userPage } from '@/filters/user.js'; defineProps<{ - note: misskey.entities.Note; - pinned?: boolean; + note: Misskey.entities.Note; }>(); </script> diff --git a/packages/frontend/src/components/MkNotePreview.vue b/packages/frontend/src/components/MkNotePreview.vue index 6786f8b256..fc6ea89085 100644 --- a/packages/frontend/src/components/MkNotePreview.vue +++ b/packages/frontend/src/components/MkNotePreview.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="$style.root"> <MkAvatar :class="$style.avatar" :user="$i" link preview/> @@ -16,7 +21,7 @@ <script lang="ts" setup> import { } from 'vue'; -import { $i } from '@/account'; +import { $i } from '@/account.js'; const props = defineProps<{ text: string; diff --git a/packages/frontend/src/components/MkNoteSimple.vue b/packages/frontend/src/components/MkNoteSimple.vue index 98ea91d6be..f8ef3f3fa6 100644 --- a/packages/frontend/src/components/MkNoteSimple.vue +++ b/packages/frontend/src/components/MkNoteSimple.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="$style.root"> <MkAvatar :class="$style.avatar" :user="note.user" link preview/> @@ -18,15 +23,14 @@ <script lang="ts" setup> import { } from 'vue'; -import * as misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; import MkNoteHeader from '@/components/MkNoteHeader.vue'; import MkSubNoteContent from '@/components/MkSubNoteContent.vue'; import MkCwButton from '@/components/MkCwButton.vue'; -import { $i } from '@/account'; +import { $i } from '@/account.js'; const props = defineProps<{ - note: misskey.entities.Note; - pinned?: boolean; + note: Misskey.entities.Note; }>(); const showContent = $ref(false); diff --git a/packages/frontend/src/components/MkNoteSub.vue b/packages/frontend/src/components/MkNoteSub.vue index 9ac0b7858f..2a3cd9bf02 100644 --- a/packages/frontend/src/components/MkNoteSub.vue +++ b/packages/frontend/src/components/MkNoteSub.vue @@ -1,5 +1,10 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> -<div :class="[$style.root, { [$style.children]: depth > 1 }]"> +<div v-if="!muted" :class="[$style.root, { [$style.children]: depth > 1 }]"> <div :class="$style.main"> <div v-if="note.channel" :class="$style.colorBar" :style="{ background: note.channel.color }"></div> <MkAvatar :class="$style.avatar" :user="note.user" link preview/> @@ -23,21 +28,33 @@ <MkA class="_link" :to="notePage(note)">{{ i18n.ts.continueThread }} <i class="ti ti-chevron-double-right"></i></MkA> </div> </div> +<div v-else :class="$style.muted" @click="muted = false"> + <I18n :src="i18n.ts.userSaysSomething" tag="small"> + <template #name> + <MkA v-user-preview="note.userId" :to="userPage(note.user)"> + <MkUserName :user="note.user"/> + </MkA> + </template> + </I18n> +</div> </template> <script lang="ts" setup> -import { } from 'vue'; -import * as misskey from 'misskey-js'; +import { ref } from 'vue'; +import * as Misskey from 'misskey-js'; import MkNoteHeader from '@/components/MkNoteHeader.vue'; import MkSubNoteContent from '@/components/MkSubNoteContent.vue'; import MkCwButton from '@/components/MkCwButton.vue'; -import { notePage } from '@/filters/note'; -import * as os from '@/os'; -import { i18n } from '@/i18n'; -import { $i } from '@/account'; +import { notePage } from '@/filters/note.js'; +import * as os from '@/os.js'; +import { i18n } from '@/i18n.js'; +import { $i } from '@/account.js'; +import { userPage } from "@/filters/user"; +import { checkWordMute } from "@/scripts/check-word-mute"; +import { defaultStore } from "@/store"; const props = withDefaults(defineProps<{ - note: misskey.entities.Note; + note: Misskey.entities.Note; detail?: boolean; // how many notes are in between this one and the note being viewed in detail @@ -46,8 +63,10 @@ const props = withDefaults(defineProps<{ depth: 1, }); +const muted = ref(checkWordMute(props.note, $i, defaultStore.state.mutedWords)); + let showContent = $ref(false); -let replies: misskey.entities.Note[] = $ref([]); +let replies: Misskey.entities.Note[] = $ref([]); if (props.detail) { os.api('notes/children', { @@ -134,4 +153,12 @@ if (props.detail) { } } } + +.muted { + text-align: center; + padding: 8px !important; + border: 1px solid var(--divider); + margin: 8px 8px 0 8px; + border-radius: 8px; +} </style> diff --git a/packages/frontend/src/components/MkNotes.vue b/packages/frontend/src/components/MkNotes.vue index b49c8fa8b7..89fd504dcc 100644 --- a/packages/frontend/src/components/MkNotes.vue +++ b/packages/frontend/src/components/MkNotes.vue @@ -1,5 +1,10 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> -<MkPagination ref="pagingComponent" :pagination="pagination"> +<MkPagination ref="pagingComponent" :pagination="pagination" :disableAutoLoad="disableAutoLoad"> <template #empty> <div class="_fullinfo"> <img :src="infoImageUrl" class="_ghost"/> @@ -31,12 +36,13 @@ import { shallowRef } from 'vue'; import MkNote from '@/components/MkNote.vue'; import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue'; import MkPagination, { Paging } from '@/components/MkPagination.vue'; -import { i18n } from '@/i18n'; -import { infoImageUrl } from '@/instance'; +import { i18n } from '@/i18n.js'; +import { infoImageUrl } from '@/instance.js'; const props = defineProps<{ pagination: Paging; noGap?: boolean; + disableAutoLoad?: boolean; }>(); const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>(); diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue index d25332b10f..7ba102fd97 100644 --- a/packages/frontend/src/components/MkNotification.vue +++ b/packages/frontend/src/components/MkNotification.vue @@ -1,8 +1,15 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div ref="elRef" :class="$style.root"> <div :class="$style.head"> <MkAvatar v-if="notification.type === 'pollEnded'" :class="$style.icon" :user="notification.note.user" link preview/> + <MkAvatar v-else-if="notification.type === 'note'" :class="$style.icon" :user="notification.note.user" link preview/> <MkAvatar v-else-if="notification.type === 'achievementEarned'" :class="$style.icon" :user="$i" link preview/> + <img v-else-if="notification.type === 'test'" :class="$style.icon" :src="infoImageUrl"/> <MkAvatar v-else-if="notification.user" :class="$style.icon" :user="notification.user" link preview/> <img v-else-if="notification.icon" :class="$style.icon" :src="notification.icon" alt=""/> <div @@ -41,7 +48,9 @@ <div :class="$style.tail"> <header :class="$style.header"> <span v-if="notification.type === 'pollEnded'">{{ i18n.ts._notification.pollEnded }}</span> + <span v-else-if="notification.type === 'note'">{{ i18n.ts._notification.newNote }}: <MkUserName :user="notification.note.user"/></span> <span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span> + <span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span> <MkA v-else-if="notification.user" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA> <span v-else>{{ notification.header }}</span> <MkTime v-if="withTime" :time="notification.createdAt" :class="$style.headerTime"/> @@ -66,6 +75,9 @@ <MkA v-else-if="notification.type === 'quote'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> <Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :author="notification.note.user"/> </MkA> + <MkA v-else-if="notification.type === 'note'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> + <Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :author="notification.note.user"/> + </MkA> <MkA v-else-if="notification.type === 'pollEnded'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)"> <i class="ti ti-quote" :class="$style.quote"></i> <Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :author="notification.note.user"/> @@ -86,6 +98,7 @@ <MkButton :class="$style.followRequestCommandButton" rounded danger @click="rejectFollowRequest()"><i class="ti ti-x"/> {{ i18n.ts.reject }}</MkButton> </div> </template> + <span v-else-if="notification.type === 'test'" :class="$style.text">{{ i18n.ts._notification.notificationWillBeDisplayedLikeThis }}</span> <span v-else-if="notification.type === 'app'" :class="$style.text"> <Mfm :text="notification.body" :nowrap="false"/> </span> @@ -96,21 +109,22 @@ <script lang="ts" setup> import { ref, shallowRef } from 'vue'; -import * as misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; import MkReactionIcon from '@/components/MkReactionIcon.vue'; import MkFollowButton from '@/components/MkFollowButton.vue'; import XReactionTooltip from '@/components/MkReactionTooltip.vue'; import MkButton from '@/components/MkButton.vue'; -import { getNoteSummary } from '@/scripts/get-note-summary'; -import { notePage } from '@/filters/note'; -import { userPage } from '@/filters/user'; -import { i18n } from '@/i18n'; -import * as os from '@/os'; -import { useTooltip } from '@/scripts/use-tooltip'; -import { $i } from '@/account'; +import { getNoteSummary } from '@/scripts/get-note-summary.js'; +import { notePage } from '@/filters/note.js'; +import { userPage } from '@/filters/user.js'; +import { i18n } from '@/i18n.js'; +import * as os from '@/os.js'; +import { useTooltip } from '@/scripts/use-tooltip.js'; +import { $i } from '@/account.js'; +import { infoImageUrl } from '@/instance.js'; const props = withDefaults(defineProps<{ - notification: misskey.entities.Notification; + notification: Misskey.entities.Notification; withTime?: boolean; full?: boolean; }>(), { diff --git a/packages/frontend/src/components/MkNotificationSettingWindow.vue b/packages/frontend/src/components/MkNotificationSettingWindow.vue index 598d3a0551..a25914b214 100644 --- a/packages/frontend/src/components/MkNotificationSettingWindow.vue +++ b/packages/frontend/src/components/MkNotificationSettingWindow.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkModalWindow ref="dialog" @@ -39,7 +44,7 @@ import MkInfo from './MkInfo.vue'; import MkButton from './MkButton.vue'; import MkModalWindow from '@/components/MkModalWindow.vue'; import { notificationTypes } from '@/const'; -import { i18n } from '@/i18n'; +import { i18n } from '@/i18n.js'; type TypesMap = Record<typeof notificationTypes[number], Ref<boolean>> diff --git a/packages/frontend/src/components/MkNotifications.vue b/packages/frontend/src/components/MkNotifications.vue index d4a30d1916..ad1cb92ce1 100644 --- a/packages/frontend/src/components/MkNotifications.vue +++ b/packages/frontend/src/components/MkNotifications.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkPagination ref="pagingComponent" :pagination="pagination"> <template #empty> @@ -22,11 +27,11 @@ import MkPagination, { Paging } from '@/components/MkPagination.vue'; import XNotification from '@/components/MkNotification.vue'; import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue'; import MkNote from '@/components/MkNote.vue'; -import { useStream } from '@/stream'; -import { $i } from '@/account'; -import { i18n } from '@/i18n'; +import { useStream } from '@/stream.js'; +import { $i } from '@/account.js'; +import { i18n } from '@/i18n.js'; import { notificationTypes } from '@/const'; -import { infoImageUrl } from '@/instance'; +import { infoImageUrl } from '@/instance.js'; const props = defineProps<{ includeTypes?: typeof notificationTypes[number][]; diff --git a/packages/frontend/src/components/MkNumber.vue b/packages/frontend/src/components/MkNumber.vue index 51fc4d02bb..aa04ab253b 100644 --- a/packages/frontend/src/components/MkNumber.vue +++ b/packages/frontend/src/components/MkNumber.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <span>{{ number(Math.floor(tweened.number)) }}</span> </template> @@ -5,7 +10,7 @@ <script lang="ts" setup> import { reactive, watch } from 'vue'; import gsap from 'gsap'; -import number from '@/filters/number'; +import number from '@/filters/number.js'; const props = defineProps<{ value: number; diff --git a/packages/frontend/src/components/MkNumberDiff.vue b/packages/frontend/src/components/MkNumberDiff.vue index 303417dae8..a98b6c4713 100644 --- a/packages/frontend/src/components/MkNumberDiff.vue +++ b/packages/frontend/src/components/MkNumberDiff.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <span class="ceaaebcd" :class="{ [$style.isPlus]: isPlus, [$style.isMinus]: isMinus, [$style.isZero]: isZero }"> <slot name="before"></slot>{{ isPlus ? '+' : '' }}{{ number(value) }}<slot name="after"></slot> @@ -6,7 +11,7 @@ <script lang="ts" setup> import { computed } from 'vue'; -import number from '@/filters/number'; +import number from '@/filters/number.js'; const props = defineProps<{ value: number; diff --git a/packages/frontend/src/components/MkObjectView.value.vue b/packages/frontend/src/components/MkObjectView.value.vue index d48e7886eb..aa05c43c0b 100644 --- a/packages/frontend/src/components/MkObjectView.value.vue +++ b/packages/frontend/src/components/MkObjectView.value.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div class="igpposuu _monospace"> <div v-if="value === null" class="null">null</div> @@ -30,7 +35,7 @@ <script lang="ts" setup> import { reactive } from 'vue'; -import number from '@/filters/number'; +import number from '@/filters/number.js'; import XValue from '@/components/MkObjectView.value.vue'; const props = defineProps<{ diff --git a/packages/frontend/src/components/MkObjectView.vue b/packages/frontend/src/components/MkObjectView.vue index 8b1ed74142..30ec896ce4 100644 --- a/packages/frontend/src/components/MkObjectView.vue +++ b/packages/frontend/src/components/MkObjectView.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div> <XValue :value="value" :collapsed="false"/> diff --git a/packages/frontend/src/components/MkOmit.vue b/packages/frontend/src/components/MkOmit.vue index 668f9ff5af..8c113bd777 100644 --- a/packages/frontend/src/components/MkOmit.vue +++ b/packages/frontend/src/components/MkOmit.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div ref="content" :class="[$style.content, { [$style.omitted]: omitted }]"> <slot></slot> @@ -9,7 +14,7 @@ <script lang="ts" setup> import { onMounted, onUnmounted } from 'vue'; -import { i18n } from '@/i18n'; +import { i18n } from '@/i18n.js'; const props = withDefaults(defineProps<{ maxHeight?: number; diff --git a/packages/frontend/src/components/MkPagePreview.vue b/packages/frontend/src/components/MkPagePreview.vue index 4fda6df5e9..05b577c49c 100644 --- a/packages/frontend/src/components/MkPagePreview.vue +++ b/packages/frontend/src/components/MkPagePreview.vue @@ -1,6 +1,19 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkA :to="`/@${page.user.username}/pages/${page.name}`" class="vhpxefrj" tabindex="-1"> - <div v-if="page.eyeCatchingImage" class="thumbnail" :style="`background-image: url('${page.eyeCatchingImage.thumbnailUrl}')`"></div> + <div v-if="page.eyeCatchingImage" class="thumbnail"> + <MediaImage + :image="page.eyeCatchingImage" + :disableImageLink="true" + :controls="false" + :cover="true" + :class="$style.eyeCatchingImageRoot" + /> + </div> <article> <header> <h1 :title="page.title">{{ page.title }}</h1> @@ -16,14 +29,24 @@ <script lang="ts" setup> import { } from 'vue'; -import * as misskey from 'misskey-js'; -import { userName } from '@/filters/user'; +import * as Misskey from 'misskey-js'; +import { userName } from '@/filters/user.js'; +import MediaImage from '@/components/MkMediaImage.vue'; const props = defineProps<{ - page: misskey.entities.Page; + page: Misskey.entities.Page; }>(); </script> +<style module> +.eyeCatchingImageRoot { + width: 100%; + height: 200px; + border-radius: var(--radius) var(--radius) 0 0; + overflow: hidden; +} +</style> + <style lang="scss" scoped> .vhpxefrj { display: block; @@ -34,32 +57,15 @@ const props = defineProps<{ } > .thumbnail { - width: 100%; - height: 200px; - background-position: center; - background-size: cover; - display: flex; - justify-content: center; - align-items: center; - - > button { - font-size: 3.5em; - opacity: 0.7; - - &:hover { - font-size: 4em; - opacity: 0.9; - } - } - & + article { - left: 100px; - width: calc(100% - 100px); + border-radius: 0 0 var(--radius) var(--radius); } } > article { + background-color: var(--panel); padding: 16px; + border-radius: var(--radius); > header { margin-bottom: 8px; diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue index 6e35ad4241..3b273ac545 100644 --- a/packages/frontend/src/components/MkPageWindow.vue +++ b/packages/frontend/src/components/MkPageWindow.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkWindow ref="windowEl" @@ -27,17 +32,17 @@ import { ComputedRef, onMounted, onUnmounted, provide, shallowRef } from 'vue'; import RouterView from '@/components/global/RouterView.vue'; import MkWindow from '@/components/MkWindow.vue'; -import { popout as _popout } from '@/scripts/popout'; -import copyToClipboard from '@/scripts/copy-to-clipboard'; -import { url } from '@/config'; -import { mainRouter, routes, page } from '@/router'; -import { $i } from '@/account'; +import { popout as _popout } from '@/scripts/popout.js'; +import copyToClipboard from '@/scripts/copy-to-clipboard.js'; +import { url } from '@/config.js'; +import { mainRouter, routes, page } from '@/router.js'; +import { $i } from '@/account.js'; import { Router, useScrollPositionManager } from '@/nirax'; -import { i18n } from '@/i18n'; -import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata'; -import { openingWindowsCount } from '@/os'; -import { claimAchievement } from '@/scripts/achievements'; -import { getScrollContainer } from '@/scripts/scroll'; +import { i18n } from '@/i18n.js'; +import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js'; +import { openingWindowsCount } from '@/os.js'; +import { claimAchievement } from '@/scripts/achievements.js'; +import { getScrollContainer } from '@/scripts/scroll.js'; const props = defineProps<{ initialPath: string; diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue index b9a75f6002..5a87273386 100644 --- a/packages/frontend/src/components/MkPagination.vue +++ b/packages/frontend/src/components/MkPagination.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <Transition :enterActiveClass="defaultStore.state.animation ? $style.transition_fade_enterActive : ''" @@ -39,23 +44,23 @@ <script lang="ts"> import { computed, ComputedRef, isRef, nextTick, onActivated, onBeforeUnmount, onDeactivated, onMounted, ref, watch } from 'vue'; -import * as misskey from 'misskey-js'; -import * as os from '@/os'; -import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@/scripts/scroll'; -import { useDocumentVisibility } from '@/scripts/use-document-visibility'; +import * as Misskey from 'misskey-js'; +import * as os from '@/os.js'; +import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@/scripts/scroll.js'; +import { useDocumentVisibility } from '@/scripts/use-document-visibility.js'; import MkButton from '@/components/MkButton.vue'; -import { defaultStore } from '@/store'; +import { defaultStore } from '@/store.js'; import { MisskeyEntity } from '@/types/date-separated-list'; -import { i18n } from '@/i18n'; +import { i18n } from '@/i18n.js'; const SECOND_FETCH_LIMIT = 30; const TOLERANCE = 16; const APPEAR_MINIMUM_INTERVAL = 600; -export type Paging<E extends keyof misskey.Endpoints = keyof misskey.Endpoints> = { +export type Paging<E extends keyof Misskey.Endpoints = keyof Misskey.Endpoints> = { endpoint: E; limit: number; - params?: misskey.Endpoints[E]['req'] | ComputedRef<misskey.Endpoints[E]['req']>; + params?: Misskey.Endpoints[E]['req'] | ComputedRef<Misskey.Endpoints[E]['req']>; /** * 検索APIのような、ページング不可なエンドポイントを利用する場合 @@ -84,7 +89,7 @@ function concatMapWithArray(map: MisskeyEntityMap, entities: MisskeyEntity[]): M } </script> <script lang="ts" setup> -import { infoImageUrl } from '@/instance'; +import { infoImageUrl } from '@/instance.js'; const props = withDefaults(defineProps<{ pagination: Paging; diff --git a/packages/frontend/src/components/MkPasswordDialog.vue b/packages/frontend/src/components/MkPasswordDialog.vue new file mode 100644 index 0000000000..3abca7826f --- /dev/null +++ b/packages/frontend/src/components/MkPasswordDialog.vue @@ -0,0 +1,70 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<MkModalWindow + ref="dialog" + :width="370" + :height="400" + @close="onClose" + @closed="emit('closed')" +> + <template #header>{{ i18n.ts.authentication }}</template> + + <MkSpacer :marginMin="20" :marginMax="28"> + <div style="padding: 0 0 16px 0; text-align: center;"> + <img src="/fluent-emoji/1f510.png" alt="🔐" style="display: block; margin: 0 auto; width: 48px;"> + <div style="margin-top: 16px;">{{ i18n.ts.authenticationRequiredToContinue }}</div> + </div> + + <div class="_gaps"> + <MkInput ref="passwordInput" v-model="password" :placeholder="i18n.ts.password" type="password" autocomplete="current-password webauthn" :withPasswordToggle="true"> + <template #prefix><i class="ti ti-password"></i></template> + </MkInput> + + <MkInput v-if="$i.twoFactorEnabled" v-model="token" type="text" pattern="^([0-9]{6}|[A-Z0-9]{32})$" autocomplete="one-time-code" :spellcheck="false"> + <template #label>{{ i18n.ts.token }} ({{ i18n.ts['2fa'] }})</template> + <template #prefix><i class="ti ti-123"></i></template> + </MkInput> + + <MkButton :disabled="(password ?? '') == '' || ($i.twoFactorEnabled && (token ?? '') == '')" primary rounded style="margin: 0 auto;" @click="done"><i class="ti ti-lock-open"></i> {{ i18n.ts.continue }}</MkButton> + </div> + </MkSpacer> +</MkModalWindow> +</template> + +<script lang="ts" setup> +import { onMounted } from 'vue'; +import MkInput from '@/components/MkInput.vue'; +import MkButton from '@/components/MkButton.vue'; +import MkModalWindow from '@/components/MkModalWindow.vue'; +import { i18n } from '@/i18n.js'; +import { $i } from '@/account.js'; + +const emit = defineEmits<{ + (ev: 'done', v: { password: string; token: string | null; }): void; + (ev: 'closed'): void; + (ev: 'cancelled'): void; +}>(); + +const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>(); +const passwordInput = $shallowRef<InstanceType<typeof MkInput>>(); +const password = $ref(''); +const token = $ref(null); + +function onClose() { + emit('cancelled'); + if (dialog) dialog.close(); +} + +function done(res) { + emit('done', { password, token }); + if (dialog) dialog.close(); +} + +onMounted(() => { + if (passwordInput) passwordInput.focus(); +}); +</script> diff --git a/packages/frontend/src/components/MkPlusOneEffect.vue b/packages/frontend/src/components/MkPlusOneEffect.vue index 6a09669a68..0bc98f4334 100644 --- a/packages/frontend/src/components/MkPlusOneEffect.vue +++ b/packages/frontend/src/components/MkPlusOneEffect.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="$style.root" :style="{ zIndex, top: `${y - 64}px`, left: `${x - 64}px` }"> <span class="text" :class="{ up }">+1</span> @@ -6,7 +11,7 @@ <script lang="ts" setup> import { onMounted } from 'vue'; -import * as os from '@/os'; +import * as os from '@/os.js'; const props = withDefaults(defineProps<{ x: number; diff --git a/packages/frontend/src/components/MkPoll.vue b/packages/frontend/src/components/MkPoll.vue index 464e340116..682f8e3060 100644 --- a/packages/frontend/src/components/MkPoll.vue +++ b/packages/frontend/src/components/MkPoll.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="{ [$style.done]: closed || isVoted }"> <ul :class="$style.choices"> @@ -23,15 +28,15 @@ <script lang="ts" setup> import { computed, ref } from 'vue'; -import * as misskey from 'misskey-js'; -import { sum } from '@/scripts/array'; -import { pleaseLogin } from '@/scripts/please-login'; -import * as os from '@/os'; -import { i18n } from '@/i18n'; -import { useInterval } from '@/scripts/use-interval'; +import * as Misskey from 'misskey-js'; +import { sum } from '@/scripts/array.js'; +import { pleaseLogin } from '@/scripts/please-login.js'; +import * as os from '@/os.js'; +import { i18n } from '@/i18n.js'; +import { useInterval } from '@/scripts/use-interval.js'; const props = defineProps<{ - note: misskey.entities.Note; + note: Misskey.entities.Note; readOnly?: boolean; }>(); diff --git a/packages/frontend/src/components/MkPollEditor.vue b/packages/frontend/src/components/MkPollEditor.vue index 2da9339944..43e576d1ab 100644 --- a/packages/frontend/src/components/MkPollEditor.vue +++ b/packages/frontend/src/components/MkPollEditor.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div class="zmdxowus"> <p v-if="choices.length < 2" class="caution"> @@ -53,9 +58,9 @@ import MkInput from './MkInput.vue'; import MkSelect from './MkSelect.vue'; import MkSwitch from './MkSwitch.vue'; import MkButton from './MkButton.vue'; -import { formatDateTimeString } from '@/scripts/format-time-string'; -import { addTime } from '@/scripts/time'; -import { i18n } from '@/i18n'; +import { formatDateTimeString } from '@/scripts/format-time-string.js'; +import { addTime } from '@/scripts/time.js'; +import { i18n } from '@/i18n.js'; const props = defineProps<{ modelValue: { diff --git a/packages/frontend/src/components/MkPopupMenu.vue b/packages/frontend/src/components/MkPopupMenu.vue index 30af365669..ee7dbecf05 100644 --- a/packages/frontend/src/components/MkPopupMenu.vue +++ b/packages/frontend/src/components/MkPopupMenu.vue @@ -1,11 +1,16 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> -<MkModal ref="modal" v-slot="{ type, maxHeight }" :zPriority="'high'" :src="src" :transparentBg="true" @click="modal.close()" @close="emit('closing')" @closed="emit('closed')"> - <MkMenu :items="items" :align="align" :width="width" :max-height="maxHeight" :asDrawer="type === 'drawer'" :class="{ [$style.drawer]: type === 'drawer' }" @close="modal.close()"/> +<MkModal ref="modal" v-slot="{ type, maxHeight }" :manualShowing="manualShowing" :zPriority="'high'" :src="src" :transparentBg="true" @click="click" @close="onModalClose" @closed="onModalClosed"> + <MkMenu :items="items" :align="align" :width="width" :max-height="maxHeight" :asDrawer="type === 'drawer'" :class="{ [$style.drawer]: type === 'drawer' }" @close="onMenuClose" @hide="hide"/> </MkModal> </template> <script lang="ts" setup> -import { } from 'vue'; +import { ref } from 'vue'; import MkModal from './MkModal.vue'; import MkMenu from './MkMenu.vue'; import { MenuItem } from '@/types/menu'; @@ -24,6 +29,46 @@ const emit = defineEmits<{ }>(); let modal = $shallowRef<InstanceType<typeof MkModal>>(); +const manualShowing = ref(true); +const hiding = ref(false); + +function click() { + close(); +} + +function onModalClose() { + emit('closing'); +} + +function onMenuClose() { + close(); + if (hiding.value) { + // hidingであればclosedを発火 + emit('closed'); + } +} + +function onModalClosed() { + if (!hiding.value) { + // hidingでなければclosedを発火 + emit('closed'); + } +} + +function hide() { + manualShowing.value = false; + hiding.value = true; + + // closeは呼ぶ必要がある + modal?.close(); +} + +function close() { + manualShowing.value = false; + + // closeは呼ぶ必要がある + modal?.close(); +} </script> <style lang="scss" module> diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index 0527811ab0..2b4dcc8ed4 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="[$style.root, { [$style.modal]: modal, _popup: modal }]" @@ -95,46 +100,45 @@ <script lang="ts" setup> import { inject, watch, nextTick, onMounted, defineAsyncComponent } from 'vue'; import * as mfm from 'mfm-js'; -import * as misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; import insertTextAtCursor from 'insert-text-at-cursor'; import { toASCII } from 'punycode/'; -import * as Acct from 'misskey-js/built/acct'; import MkNoteSimple from '@/components/MkNoteSimple.vue'; import MkNotePreview from '@/components/MkNotePreview.vue'; import XPostFormAttaches from '@/components/MkPostFormAttaches.vue'; import MkPollEditor from '@/components/MkPollEditor.vue'; -import { host, url } from '@/config'; -import { erase, unique } from '@/scripts/array'; -import { extractMentions } from '@/scripts/extract-mentions'; -import { formatTimeString } from '@/scripts/format-time-string'; -import { Autocomplete } from '@/scripts/autocomplete'; -import * as os from '@/os'; -import { selectFiles } from '@/scripts/select-file'; -import { defaultStore, notePostInterruptors, postFormActions } from '@/store'; +import { host, url } from '@/config.js'; +import { erase, unique } from '@/scripts/array.js'; +import { extractMentions } from '@/scripts/extract-mentions.js'; +import { formatTimeString } from '@/scripts/format-time-string.js'; +import { Autocomplete } from '@/scripts/autocomplete.js'; +import * as os from '@/os.js'; +import { selectFiles } from '@/scripts/select-file.js'; +import { defaultStore, notePostInterruptors, postFormActions } from '@/store.js'; import MkInfo from '@/components/MkInfo.vue'; -import { i18n } from '@/i18n'; -import { instance } from '@/instance'; -import { $i, notesCount, incNotesCount, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account'; -import { uploadFile } from '@/scripts/upload'; -import { deepClone } from '@/scripts/clone'; +import { i18n } from '@/i18n.js'; +import { instance } from '@/instance.js'; +import { $i, notesCount, incNotesCount, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account.js'; +import { uploadFile } from '@/scripts/upload.js'; +import { deepClone } from '@/scripts/clone.js'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; -import { miLocalStorage } from '@/local-storage'; -import { claimAchievement } from '@/scripts/achievements'; +import { miLocalStorage } from '@/local-storage.js'; +import { claimAchievement } from '@/scripts/achievements.js'; const modal = inject('modal'); const props = withDefaults(defineProps<{ - reply?: misskey.entities.Note; - renote?: misskey.entities.Note; - channel?: misskey.entities.Channel; // TODO - mention?: misskey.entities.User; - specified?: misskey.entities.User; + reply?: Misskey.entities.Note; + renote?: Misskey.entities.Note; + channel?: Misskey.entities.Channel; // TODO + mention?: Misskey.entities.User; + specified?: Misskey.entities.User; initialText?: string; - initialVisibility?: (typeof misskey.noteVisibilities)[number]; - initialFiles?: misskey.entities.DriveFile[]; + initialVisibility?: (typeof Misskey.noteVisibilities)[number]; + initialFiles?: Misskey.entities.DriveFile[]; initialLocalOnly?: boolean; - initialVisibleUsers?: misskey.entities.User[]; - initialNote?: misskey.entities.Note; + initialVisibleUsers?: Misskey.entities.User[]; + initialNote?: Misskey.entities.Note; instant?: boolean; fixed?: boolean; autofocus?: boolean; @@ -166,10 +170,11 @@ let poll = $ref<{ expiredAfter: string | null; } | null>(null); let useCw = $ref(false); -let showPreview = $ref(false); +let showPreview = $ref(defaultStore.state.showPreview); +watch($$(showPreview), () => defaultStore.set('showPreview', showPreview)); let cw = $ref<string | null>(null); let localOnly = $ref<boolean>(props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly); -let visibility = $ref(props.initialVisibility ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility) as typeof misskey.noteVisibilities[number]); +let visibility = $ref(props.initialVisibility ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility) as typeof Misskey.noteVisibilities[number]); let visibleUsers = $ref([]); if (props.initialVisibleUsers) { props.initialVisibleUsers.forEach(pushVisibleUser); @@ -410,7 +415,7 @@ function updateFileName(file, name) { files[files.findIndex(x => x.id === file.id)].name = name; } -function replaceFile(file: misskey.entities.DriveFile, newFile: misskey.entities.DriveFile): void { +function replaceFile(file: Misskey.entities.DriveFile, newFile: Misskey.entities.DriveFile): void { files[files.findIndex(x => x.id === file.id)] = newFile; } @@ -510,7 +515,7 @@ function addVisibleUser() { pushVisibleUser(user); if (!text.toLowerCase().includes(`@${user.username.toLowerCase()}`)) { - text = `@${Acct.toString(user)} ${text}`; + text = `@${Misskey.acct.toString(user)} ${text}`; } }); } @@ -624,6 +629,8 @@ function onDrop(ev): void { } function saveDraft() { + if (props.instant) return; + const draftData = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}'); draftData[draftKey] = { @@ -746,18 +753,25 @@ async function post(ev?: MouseEvent) { claimAchievement('notes1'); } - const text = postData.text?.toLowerCase() ?? ''; - if ((text.includes('love') || text.includes('❤')) && text.includes('misskey')) { + const text = postData.text ?? ''; + const lowerCase = text.toLowerCase(); + if ((lowerCase.includes('love') || lowerCase.includes('❤')) && lowerCase.includes('misskey')) { claimAchievement('iLoveMisskey'); } - if ( - text.includes('https://youtu.be/Efrlqw8ytg4'.toLowerCase()) || - text.includes('https://www.youtube.com/watch?v=Efrlqw8ytg4'.toLowerCase()) || - text.includes('https://m.youtube.com/watch?v=Efrlqw8ytg4'.toLowerCase()) || - text.includes('https://youtu.be/XVCwzwxdHuA'.toLowerCase()) || - text.includes('https://www.youtube.com/watch?v=XVCwzwxdHuA'.toLowerCase()) || - text.includes('https://m.youtube.com/watch?v=XVCwzwxdHuA'.toLowerCase()) - ) { + if ([ + 'https://youtu.be/Efrlqw8ytg4', + 'https://www.youtube.com/watch?v=Efrlqw8ytg4', + 'https://m.youtube.com/watch?v=Efrlqw8ytg4', + + 'https://youtu.be/XVCwzwxdHuA', + 'https://www.youtube.com/watch?v=XVCwzwxdHuA', + 'https://m.youtube.com/watch?v=XVCwzwxdHuA', + + 'https://open.spotify.com/track/3Cuj0mZrlLoXx9nydNi7RB', + 'https://open.spotify.com/track/7anfcaNPQWlWCwyCHmZqNy', + 'https://open.spotify.com/track/5Odr16TvEN4my22K9nbH7l', + 'https://open.spotify.com/album/5bOlxyl4igOrp2DwVQxBco', + ].some(url => text.includes(url))) { claimAchievement('brainDiver'); } @@ -791,7 +805,7 @@ function cancel() { function insertMention() { os.selectUser().then(user => { - insertTextAtCursor(textareaEl, '@' + Acct.toString(user) + ' '); + insertTextAtCursor(textareaEl, '@' + Misskey.acct.toString(user) + ' '); }); } @@ -812,7 +826,7 @@ function showActions(ev) { })), ev.currentTarget ?? ev.target); } -let postAccount = $ref<misskey.entities.UserDetailed | null>(null); +let postAccount = $ref<Misskey.entities.UserDetailed | null>(null); function openAccountMenu(ev: MouseEvent) { openAccountMenu_({ diff --git a/packages/frontend/src/components/MkPostFormAttaches.vue b/packages/frontend/src/components/MkPostFormAttaches.vue index f419c75cad..624a44ecae 100644 --- a/packages/frontend/src/components/MkPostFormAttaches.vue +++ b/packages/frontend/src/components/MkPostFormAttaches.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div v-show="props.modelValue.length != 0" :class="$style.root"> <Sortable :modelValue="props.modelValue" :class="$style.files" itemKey="id" :animation="150" :delay="100" :delayOnTouchOnly="true" @update:modelValue="v => emit('update:modelValue', v)"> @@ -16,10 +21,10 @@ <script lang="ts" setup> import { defineAsyncComponent } from 'vue'; -import * as misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue'; -import * as os from '@/os'; -import { i18n } from '@/i18n'; +import * as os from '@/os.js'; +import { i18n } from '@/i18n.js'; const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); @@ -31,9 +36,9 @@ const props = defineProps<{ const emit = defineEmits<{ (ev: 'update:modelValue', value: any[]): void; (ev: 'detach', id: string): void; - (ev: 'changeSensitive', file: misskey.entities.DriveFile, isSensitive: boolean): void; - (ev: 'changeName', file: misskey.entities.DriveFile, newName: string): void; - (ev: 'replaceFile', file: misskey.entities.DriveFile, newFile: misskey.entities.DriveFile): void; + (ev: 'changeSensitive', file: Misskey.entities.DriveFile, isSensitive: boolean): void; + (ev: 'changeName', file: Misskey.entities.DriveFile, newName: string): void; + (ev: 'replaceFile', file: Misskey.entities.DriveFile, newFile: Misskey.entities.DriveFile): void; }>(); let menuShowing = false; @@ -87,12 +92,12 @@ async function describe(file) { }, 'closed'); } -async function crop(file: misskey.entities.DriveFile): Promise<void> { +async function crop(file: Misskey.entities.DriveFile): Promise<void> { const newFile = await os.cropImage(file, { aspectRatio: NaN }); emit('replaceFile', file, newFile); } -function showFileMenu(file: misskey.entities.DriveFile, ev: MouseEvent): void { +function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent): void { if (menuShowing) return; const isImage = file.type.startsWith('image/'); diff --git a/packages/frontend/src/components/MkPostFormDialog.vue b/packages/frontend/src/components/MkPostFormDialog.vue index 989c138e81..c07a166a83 100644 --- a/packages/frontend/src/components/MkPostFormDialog.vue +++ b/packages/frontend/src/components/MkPostFormDialog.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkModal ref="modal" :preferType="'dialog'" @click="modal.close()" @closed="onModalClosed()"> <MkPostForm ref="form" :class="$style.form" v-bind="props" autofocus freezeAfterPosted @posted="onPosted" @cancel="modal.close()" @esc="modal.close()"/> @@ -6,22 +11,22 @@ <script lang="ts" setup> import { } from 'vue'; -import * as misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; import MkModal from '@/components/MkModal.vue'; import MkPostForm from '@/components/MkPostForm.vue'; const props = defineProps<{ - reply?: misskey.entities.Note; - renote?: misskey.entities.Note; + reply?: Misskey.entities.Note; + renote?: Misskey.entities.Note; channel?: any; // TODO - mention?: misskey.entities.User; - specified?: misskey.entities.User; + mention?: Misskey.entities.User; + specified?: Misskey.entities.User; initialText?: string; - initialVisibility?: typeof misskey.noteVisibilities; - initialFiles?: misskey.entities.DriveFile[]; + initialVisibility?: typeof Misskey.noteVisibilities; + initialFiles?: Misskey.entities.DriveFile[]; initialLocalOnly?: boolean; - initialVisibleUsers?: misskey.entities.User[]; - initialNote?: misskey.entities.Note; + initialVisibleUsers?: Misskey.entities.User[]; + initialNote?: Misskey.entities.Note; instant?: boolean; fixed?: boolean; autofocus?: boolean; diff --git a/packages/frontend/src/components/MkPushNotificationAllowButton.vue b/packages/frontend/src/components/MkPushNotificationAllowButton.vue index 448084d9ba..ba64775298 100644 --- a/packages/frontend/src/components/MkPushNotificationAllowButton.vue +++ b/packages/frontend/src/components/MkPushNotificationAllowButton.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkButton v-if="supported && !pushRegistrationInServer" @@ -36,11 +41,11 @@ </template> <script setup lang="ts"> -import { $i, getAccounts } from '@/account'; +import { $i, getAccounts } from '@/account.js'; import MkButton from '@/components/MkButton.vue'; -import { instance } from '@/instance'; -import { api, apiWithDialog, promiseDialog } from '@/os'; -import { i18n } from '@/i18n'; +import { instance } from '@/instance.js'; +import { api, apiWithDialog, promiseDialog } from '@/os.js'; +import { i18n } from '@/i18n.js'; defineProps<{ primary?: boolean; diff --git a/packages/frontend/src/components/MkRadio.vue b/packages/frontend/src/components/MkRadio.vue index eea94d4692..c4df3e991b 100644 --- a/packages/frontend/src/components/MkRadio.vue +++ b/packages/frontend/src/components/MkRadio.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div v-adaptive-border diff --git a/packages/frontend/src/components/MkRadios.vue b/packages/frontend/src/components/MkRadios.vue index 84be10078a..d9178f3362 100644 --- a/packages/frontend/src/components/MkRadios.vue +++ b/packages/frontend/src/components/MkRadios.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <script lang="ts"> import { VNode, defineComponent, h, ref, watch } from 'vue'; import MkRadio from './MkRadio.vue'; diff --git a/packages/frontend/src/components/MkRange.vue b/packages/frontend/src/components/MkRange.vue index eaa134df25..2cfc27ceee 100644 --- a/packages/frontend/src/components/MkRange.vue +++ b/packages/frontend/src/components/MkRange.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div class="timctyfi" :class="{ disabled, easing }"> <div class="label"><slot name="label"></slot></div> @@ -18,7 +23,7 @@ <script lang="ts" setup> import { computed, defineAsyncComponent, onMounted, onUnmounted, ref, watch, shallowRef } from 'vue'; -import * as os from '@/os'; +import * as os from '@/os.js'; const props = withDefaults(defineProps<{ modelValue: number; diff --git a/packages/frontend/src/components/MkReactedUsersDialog.vue b/packages/frontend/src/components/MkReactedUsersDialog.vue deleted file mode 100644 index 0a858a8965..0000000000 --- a/packages/frontend/src/components/MkReactedUsersDialog.vue +++ /dev/null @@ -1,99 +0,0 @@ -<template> -<MkModalWindow - ref="dialog" - :width="400" - :height="450" - @close="dialog.close()" - @closed="emit('closed')" -> - <template #header>{{ i18n.ts.reactionsList }}</template> - - <MkSpacer :marginMin="20" :marginMax="28"> - <div v-if="note" class="_gaps"> - <div v-if="reactions.length === 0" class="_fullinfo"> - <img :src="infoImageUrl" class="_ghost"/> - <div>{{ i18n.ts.nothing }}</div> - </div> - <template v-else> - <div :class="$style.tabs"> - <button v-for="reaction in reactions" :key="reaction" :class="[$style.tab, { [$style.tabActive]: tab === reaction }]" class="_button" @click="tab = reaction"> - <MkReactionIcon :reaction="reaction"/> - <span style="margin-left: 4px;">{{ note.reactions[reaction] }}</span> - </button> - </div> - <MkA v-for="user in users" :key="user.id" :to="userPage(user)" @click="dialog.close()"> - <MkUserCardMini :user="user" :withChart="false"/> - </MkA> - </template> - </div> - <div v-else> - <MkLoading/> - </div> - </MkSpacer> -</MkModalWindow> -</template> - -<script lang="ts" setup> -import { onMounted, watch } from 'vue'; -import * as misskey from 'misskey-js'; -import MkModalWindow from '@/components/MkModalWindow.vue'; -import MkReactionIcon from '@/components/MkReactionIcon.vue'; -import MkUserCardMini from '@/components/MkUserCardMini.vue'; -import { userPage } from '@/filters/user'; -import { i18n } from '@/i18n'; -import * as os from '@/os'; -import { infoImageUrl } from '@/instance'; - -const emit = defineEmits<{ - (ev: 'closed'): void, -}>(); - -const props = defineProps<{ - noteId: misskey.entities.Note['id']; -}>(); - -const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>(); - -let note = $ref<misskey.entities.Note>(); -let tab = $ref<string>(); -let reactions = $ref<string[]>(); -let users = $ref(); - -watch($$(tab), async () => { - const res = await os.api('notes/reactions', { - noteId: props.noteId, - type: tab, - limit: 30, - }); - - users = res.map(x => x.user); -}); - -onMounted(() => { - os.api('notes/show', { - noteId: props.noteId, - }).then((res) => { - reactions = Object.keys(res.reactions); - tab = reactions[0]; - note = res; - }); -}); -</script> - -<style lang="scss" module> -.tabs { - display: flex; - gap: 8px; - flex-wrap: wrap; -} - -.tab { - padding: 4px 6px; - border: solid 1px var(--divider); - border-radius: 6px; -} - -.tabActive { - border-color: var(--accent); -} -</style> diff --git a/packages/frontend/src/components/MkReactionEffect.vue b/packages/frontend/src/components/MkReactionEffect.vue index d037ecced8..88e262d880 100644 --- a/packages/frontend/src/components/MkReactionEffect.vue +++ b/packages/frontend/src/components/MkReactionEffect.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="$style.root" :style="{ zIndex, top: `${y - 64}px`, left: `${x - 64}px` }"> <span :class="[$style.text, { [$style.up]: up }]"> @@ -8,7 +13,7 @@ <script lang="ts" setup> import { onMounted } from 'vue'; -import * as os from '@/os'; +import * as os from '@/os.js'; import MkReactionIcon from '@/components/MkReactionIcon.vue'; const props = withDefaults(defineProps<{ diff --git a/packages/frontend/src/components/MkReactionIcon.vue b/packages/frontend/src/components/MkReactionIcon.vue index dfb06f63c4..55c812cbc1 100644 --- a/packages/frontend/src/components/MkReactionIcon.vue +++ b/packages/frontend/src/components/MkReactionIcon.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkCustomEmoji v-if="reaction[0] === ':'" :name="reaction" :normal="true" :noStyle="noStyle" :url="emojiUrl"/> <MkEmoji v-else :emoji="reaction" :normal="true" :noStyle="noStyle"/> diff --git a/packages/frontend/src/components/MkReactionTooltip.vue b/packages/frontend/src/components/MkReactionTooltip.vue index 34afa72232..8527b45347 100644 --- a/packages/frontend/src/components/MkReactionTooltip.vue +++ b/packages/frontend/src/components/MkReactionTooltip.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkTooltip ref="tooltip" :showing="showing" :targetElement="targetElement" :maxWidth="340" @closed="emit('closed')"> <div :class="$style.root"> diff --git a/packages/frontend/src/components/MkReactionsViewer.details.vue b/packages/frontend/src/components/MkReactionsViewer.details.vue index 99960f5d25..fdd96d05ae 100644 --- a/packages/frontend/src/components/MkReactionsViewer.details.vue +++ b/packages/frontend/src/components/MkReactionsViewer.details.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkTooltip ref="tooltip" :showing="showing" :targetElement="targetElement" :maxWidth="340" @closed="emit('closed')"> <div :class="$style.root"> @@ -20,7 +25,7 @@ import { } from 'vue'; import MkTooltip from './MkTooltip.vue'; import MkReactionIcon from '@/components/MkReactionIcon.vue'; -import { getEmojiName } from '@/scripts/emojilist'; +import { getEmojiName } from '@/scripts/emojilist.js'; defineProps<{ showing: boolean; diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue index 69d495d86f..d0db515219 100644 --- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue +++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue @@ -1,9 +1,14 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <button ref="buttonEl" v-ripple="canToggle" class="_button" - :class="[$style.root, { [$style.reacted]: note.myReaction == reaction, [$style.canToggle]: canToggle, [$style.large]: defaultStore.state.largeNoteReactions }]" + :class="[$style.root, { [$style.reacted]: note.myReaction == reaction, [$style.canToggle]: canToggle, [$style.small]: defaultStore.state.reactionsDisplaySize === 'small', [$style.large]: defaultStore.state.reactionsDisplaySize === 'large' }]" @click="toggleReaction()" > <MkReactionIcon :class="$style.icon" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]"/> @@ -13,22 +18,22 @@ <script lang="ts" setup> import { computed, onMounted, shallowRef, watch } from 'vue'; -import * as misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; import XDetails from '@/components/MkReactionsViewer.details.vue'; import MkReactionIcon from '@/components/MkReactionIcon.vue'; -import * as os from '@/os'; -import { useTooltip } from '@/scripts/use-tooltip'; -import { $i } from '@/account'; +import * as os from '@/os.js'; +import { useTooltip } from '@/scripts/use-tooltip.js'; +import { $i } from '@/account.js'; import MkReactionEffect from '@/components/MkReactionEffect.vue'; -import { claimAchievement } from '@/scripts/achievements'; -import { defaultStore } from '@/store'; -import { i18n } from '@/i18n'; +import { claimAchievement } from '@/scripts/achievements.js'; +import { defaultStore } from '@/store.js'; +import { i18n } from '@/i18n.js'; const props = defineProps<{ reaction: string; count: number; isInitial: boolean; - note: misskey.entities.Note; + note: Misskey.entities.Note; }>(); const buttonEl = shallowRef<HTMLElement>(); @@ -110,10 +115,11 @@ useTooltip(buttonEl, async (showing) => { <style lang="scss" module> .root { display: inline-block; - height: 32px; + height: 42px; margin: 2px; padding: 0 6px; - border-radius: 4px; + font-size: 1.5em; + border-radius: 6px; &.canToggle { background: var(--buttonBg); @@ -127,26 +133,35 @@ useTooltip(buttonEl, async (showing) => { cursor: default; } - &.large { - height: 42px; - font-size: 1.5em; - border-radius: 6px; + &.small { + height: 32px; + font-size: 1em; + border-radius: 4px; > .count { - font-size: 0.7em; - line-height: 42px; + font-size: 0.9em; + line-height: 32px; } } - &.reacted { - background: var(--accent); + &.large { + height: 52px; + font-size: 2em; + border-radius: 8px; - &:hover { - background: var(--accent); + > .count { + font-size: 0.6em; + line-height: 52px; } + } + + &.reacted, &.reacted:hover { + background: var(--accentedBg); + color: var(--accent); + box-shadow: 0 0 0px 1px var(--accent) inset; > .count { - color: var(--fgOnAccent); + color: var(--accent); } > .icon { @@ -155,9 +170,13 @@ useTooltip(buttonEl, async (showing) => { } } +.icon { + max-width: 150px; +} + .count { - font-size: 0.9em; - line-height: 32px; + font-size: 0.7em; + line-height: 42px; margin: 0 0 0 4px; } </style> diff --git a/packages/frontend/src/components/MkReactionsViewer.vue b/packages/frontend/src/components/MkReactionsViewer.vue index ce146463ec..52ead19a4b 100644 --- a/packages/frontend/src/components/MkReactionsViewer.vue +++ b/packages/frontend/src/components/MkReactionsViewer.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <TransitionGroup :enterActiveClass="defaultStore.state.animation ? $style.transition_x_enterActive : ''" @@ -13,13 +18,13 @@ </template> <script lang="ts" setup> -import * as misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; import { watch } from 'vue'; import XReaction from '@/components/MkReactionsViewer.reaction.vue'; -import { defaultStore } from '@/store'; +import { defaultStore } from '@/store.js'; const props = withDefaults(defineProps<{ - note: misskey.entities.Note; + note: Misskey.entities.Note; maxNumber?: number; }>(), { maxNumber: Infinity, diff --git a/packages/frontend/src/components/MkRemoteCaution.vue b/packages/frontend/src/components/MkRemoteCaution.vue index f6081816ae..0ce67e872b 100644 --- a/packages/frontend/src/components/MkRemoteCaution.vue +++ b/packages/frontend/src/components/MkRemoteCaution.vue @@ -1,9 +1,14 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="$style.root"><i class="ti ti-alert-triangle" style="margin-right: 8px;"></i>{{ i18n.ts.remoteUserCaution }}<a :class="$style.link" :href="href" rel="nofollow noopener" target="_blank">{{ i18n.ts.showOnRemote }}</a></div> </template> <script lang="ts" setup> -import { i18n } from '@/i18n'; +import { i18n } from '@/i18n.js'; defineProps<{ href: string; diff --git a/packages/frontend/src/components/MkRenotedUsersDialog.vue b/packages/frontend/src/components/MkRenotedUsersDialog.vue deleted file mode 100644 index 484cb2f9a7..0000000000 --- a/packages/frontend/src/components/MkRenotedUsersDialog.vue +++ /dev/null @@ -1,66 +0,0 @@ -<template> -<MkModalWindow - ref="dialog" - :width="400" - :height="450" - @close="dialog.close()" - @closed="emit('closed')" -> - <template #header>{{ i18n.ts.renotesList }}</template> - - <MkSpacer :marginMin="20" :marginMax="28"> - <div v-if="renotes" class="_gaps"> - <div v-if="renotes.length === 0" class="_fullinfo"> - <img :src="infoImageUrl" class="_ghost"/> - <div>{{ i18n.ts.nothing }}</div> - </div> - <template v-else> - <MkA v-for="user in users" :key="user.id" :to="userPage(user)" @click="dialog.close()"> - <MkUserCardMini :user="user" :withChart="false"/> - </MkA> - </template> - </div> - <div v-else> - <MkLoading/> - </div> - </MkSpacer> -</MkModalWindow> -</template> - -<script lang="ts" setup> -import { onMounted } from 'vue'; -import * as misskey from 'misskey-js'; -import MkModalWindow from '@/components/MkModalWindow.vue'; -import MkUserCardMini from '@/components/MkUserCardMini.vue'; -import { userPage } from '@/filters/user'; -import { i18n } from '@/i18n'; -import * as os from '@/os'; -import { infoImageUrl } from '@/instance'; - -const emit = defineEmits<{ - (ev: 'closed'): void, -}>(); - -const props = defineProps<{ - noteId: misskey.entities.Note['id']; -}>(); - -const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>(); - -let note = $ref<misskey.entities.Note>(); -let renotes = $ref(); -let users = $ref(); - -onMounted(async () => { - const res = await os.api('notes/renotes', { - noteId: props.noteId, - limit: 30, - }); - - renotes = res; - users = res.map(x => x.user); -}); -</script> - -<style lang="scss" module> -</style> diff --git a/packages/frontend/src/components/MkRetentionHeatmap.vue b/packages/frontend/src/components/MkRetentionHeatmap.vue index 311d5c425c..3dc9a94ae2 100644 --- a/packages/frontend/src/components/MkRetentionHeatmap.vue +++ b/packages/frontend/src/components/MkRetentionHeatmap.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div ref="rootEl"> <MkLoading v-if="fetching"/> @@ -10,11 +15,11 @@ <script lang="ts" setup> import { onMounted, nextTick } from 'vue'; import { Chart } from 'chart.js'; -import * as os from '@/os'; -import { defaultStore } from '@/store'; -import { useChartTooltip } from '@/scripts/use-chart-tooltip'; -import { alpha } from '@/scripts/color'; -import { initChart } from '@/scripts/init-chart'; +import * as os from '@/os.js'; +import { defaultStore } from '@/store.js'; +import { useChartTooltip } from '@/scripts/use-chart-tooltip.js'; +import { alpha } from '@/scripts/color.js'; +import { initChart } from '@/scripts/init-chart.js'; initChart(); diff --git a/packages/frontend/src/components/MkRetentionLineChart.vue b/packages/frontend/src/components/MkRetentionLineChart.vue index 276bd6f984..e2682ec06b 100644 --- a/packages/frontend/src/components/MkRetentionLineChart.vue +++ b/packages/frontend/src/components/MkRetentionLineChart.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <canvas ref="chartEl"></canvas> </template> @@ -6,12 +11,12 @@ import { onMounted, shallowRef } from 'vue'; import { Chart } from 'chart.js'; import tinycolor from 'tinycolor2'; -import { defaultStore } from '@/store'; -import { useChartTooltip } from '@/scripts/use-chart-tooltip'; -import { chartVLine } from '@/scripts/chart-vline'; -import { alpha } from '@/scripts/color'; -import { initChart } from '@/scripts/init-chart'; -import * as os from '@/os'; +import { defaultStore } from '@/store.js'; +import { useChartTooltip } from '@/scripts/use-chart-tooltip.js'; +import { chartVLine } from '@/scripts/chart-vline.js'; +import { alpha } from '@/scripts/color.js'; +import { initChart } from '@/scripts/init-chart.js'; +import * as os from '@/os.js'; initChart(); diff --git a/packages/frontend/src/components/MkRippleEffect.vue b/packages/frontend/src/components/MkRippleEffect.vue index 60c3a47385..860b083327 100644 --- a/packages/frontend/src/components/MkRippleEffect.vue +++ b/packages/frontend/src/components/MkRippleEffect.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="$style.root" :style="{ zIndex, top: `${y - 64}px`, left: `${x - 64}px` }"> <svg width="128" height="128" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"> @@ -58,7 +63,7 @@ <script lang="ts" setup> import { onMounted } from 'vue'; -import * as os from '@/os'; +import * as os from '@/os.js'; const props = withDefaults(defineProps<{ x: number; diff --git a/packages/frontend/src/components/MkRolePreview.vue b/packages/frontend/src/components/MkRolePreview.vue index 9fbe1ec993..3e4586cee4 100644 --- a/packages/frontend/src/components/MkRolePreview.vue +++ b/packages/frontend/src/components/MkRolePreview.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkA v-adaptive-bg :to="forModeration ? `/admin/roles/${role.id}` : `/roles/${role.id}`" class="_panel" :class="$style.root" tabindex="-1" :style="{ '--color': role.color }"> <div :class="$style.title"> @@ -23,7 +28,7 @@ <script lang="ts" setup> import { } from 'vue'; -import { i18n } from '@/i18n'; +import { i18n } from '@/i18n.js'; const props = withDefaults(defineProps<{ role: any; diff --git a/packages/frontend/src/components/MkSelect.vue b/packages/frontend/src/components/MkSelect.vue index 4efb65c287..5a1c788005 100644 --- a/packages/frontend/src/components/MkSelect.vue +++ b/packages/frontend/src/components/MkSelect.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div> <div :class="$style.label" @click="focus"><slot name="label"></slot></div> @@ -29,9 +34,9 @@ <script lang="ts" setup> import { onMounted, nextTick, ref, watch, computed, toRefs, VNode, useSlots } from 'vue'; import MkButton from '@/components/MkButton.vue'; -import * as os from '@/os'; -import { useInterval } from '@/scripts/use-interval'; -import { i18n } from '@/i18n'; +import * as os from '@/os.js'; +import { useInterval } from '@/scripts/use-interval.js'; +import { i18n } from '@/i18n.js'; const props = defineProps<{ modelValue: string | null; diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue index b1a509b9e6..0fd67a0b46 100644 --- a/packages/frontend/src/components/MkSignin.vue +++ b/packages/frontend/src/components/MkSignin.vue @@ -1,16 +1,21 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <form :class="{ signing, totpLogin }" @submit.prevent="onSubmit"> <div class="_gaps_m"> - <div v-show="withAvatar" :class="$style.avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null, marginBottom: message ? '1.5em' : null }"></div> + <div v-show="withAvatar" :class="$style.avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : undefined, marginBottom: message ? '1.5em' : undefined }"></div> <MkInfo v-if="message"> {{ message }} </MkInfo> <div v-if="!totpLogin" class="normal-signin _gaps_m"> - <MkInput v-model="username" :placeholder="i18n.ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autocomplete="username" autofocus required data-cy-signin-username @update:modelValue="onUsernameChange"> + <MkInput v-model="username" :placeholder="i18n.ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autocomplete="username webauthn" autofocus required data-cy-signin-username @update:modelValue="onUsernameChange"> <template #prefix>@</template> <template #suffix>@{{ host }}</template> </MkInput> - <MkInput v-if="!user || user && !user.usePasswordLessLogin" v-model="password" :placeholder="i18n.ts.password" type="password" autocomplete="current-password" :withPasswordToggle="true" required data-cy-signin-password> + <MkInput v-if="!user || user && !user.usePasswordLessLogin" v-model="password" :placeholder="i18n.ts.password" type="password" autocomplete="current-password webauthn" :withPasswordToggle="true" required data-cy-signin-password> <template #prefix><i class="ti ti-lock"></i></template> <template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template> </MkInput> @@ -18,7 +23,7 @@ </div> <div v-if="totpLogin" class="2fa-signin" :class="{ securityKeys: user && user.securityKeys }"> <div v-if="user && user.securityKeys" class="twofa-group tap-group"> - <p>{{ i18n.ts.tapSecurityKey }}</p> + <p>{{ i18n.ts.useSecurityKey }}</p> <MkButton v-if="!queryingKey" @click="queryKey"> {{ i18n.ts.retry }} </MkButton> @@ -27,12 +32,12 @@ <p class="or-msg">{{ i18n.ts.or }}</p> </div> <div class="twofa-group totp-group"> - <p style="margin-bottom:0;">{{ i18n.ts.twoStepAuthentication }}</p> + <p style="margin-bottom:0;">{{ i18n.ts['2fa'] }}</p> <MkInput v-if="user && user.usePasswordLessLogin" v-model="password" type="password" autocomplete="current-password" :withPasswordToggle="true" required> <template #label>{{ i18n.ts.password }}</template> <template #prefix><i class="ti ti-lock"></i></template> </MkInput> - <MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="one-time-code" :spellcheck="false" required> + <MkInput v-model="token" type="text" pattern="^([0-9]{6}|[A-Z0-9]{32})$" autocomplete="one-time-code" :spellcheck="false" required> <template #label>{{ i18n.ts.token }}</template> <template #prefix><i class="ti ti-123"></i></template> </MkInput> @@ -46,32 +51,29 @@ <script lang="ts" setup> import { defineAsyncComponent } from 'vue'; import { toUnicode } from 'punycode/'; -import { showSuspendedDialog } from '../scripts/show-suspended-dialog'; +import * as Misskey from 'misskey-js'; +import { supported as webAuthnSupported, get as webAuthnRequest, parseRequestOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill'; +import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; import MkInfo from '@/components/MkInfo.vue'; -import { host as configHost } from '@/config'; -import { byteify, hexify } from '@/scripts/2fa'; -import * as os from '@/os'; -import { login } from '@/account'; -import { instance } from '@/instance'; -import { i18n } from '@/i18n'; +import { host as configHost } from '@/config.js'; +import * as os from '@/os.js'; +import { login } from '@/account.js'; +import { i18n } from '@/i18n.js'; let signing = $ref(false); -let user = $ref(null); +let user = $ref<Misskey.entities.UserDetailed | null>(null); let username = $ref(''); let password = $ref(''); let token = $ref(''); let host = $ref(toUnicode(configHost)); let totpLogin = $ref(false); -let credential = $ref(null); -let challengeData = $ref(null); let queryingKey = $ref(false); +let credentialRequest = $ref<CredentialRequestOptions | null>(null); let hCaptchaResponse = $ref(null); let reCaptchaResponse = $ref(null); -const meta = $computed(() => instance); - const emit = defineEmits<{ (ev: 'login', v: any): void; }>(); @@ -94,7 +96,7 @@ const props = defineProps({ }, }); -function onUsernameChange() { +function onUsernameChange(): void { os.api('users/show', { username: username, }).then(userResponse => { @@ -104,58 +106,46 @@ function onUsernameChange() { }); } -function onLogin(res) { +function onLogin(res: any): Promise<void> | void { if (props.autoSet) { return login(res.i); } } -function queryKey() { +async function queryKey(): Promise<void> { queryingKey = true; - return navigator.credentials.get({ - publicKey: { - challenge: byteify(challengeData.challenge, 'base64'), - allowCredentials: challengeData.securityKeys.map(key => ({ - id: byteify(key.id, 'hex'), - type: 'public-key', - transports: ['usb', 'nfc', 'ble', 'internal'], - })), - timeout: 60 * 1000, - }, - }).catch(() => { - queryingKey = false; - return Promise.reject(null); - }).then(credential => { - queryingKey = false; - signing = true; - return os.api('signin', { - username, - password, - signature: hexify(credential.response.signature), - authenticatorData: hexify(credential.response.authenticatorData), - clientDataJSON: hexify(credential.response.clientDataJSON), - credentialId: credential.id, - challengeId: challengeData.challengeId, - 'hcaptcha-response': hCaptchaResponse, - 'g-recaptcha-response': reCaptchaResponse, - }); - }).then(res => { - emit('login', res); - return onLogin(res); - }).catch(err => { - if (err === null) return; - os.alert({ - type: 'error', - text: i18n.ts.signinFailed, + await webAuthnRequest(credentialRequest) + .catch(() => { + queryingKey = false; + return Promise.reject(null); + }).then(credential => { + credentialRequest = null; + queryingKey = false; + signing = true; + return os.api('signin', { + username, + password, + credential: credential.toJSON(), + 'hcaptcha-response': hCaptchaResponse, + 'g-recaptcha-response': reCaptchaResponse, + }); + }).then(res => { + emit('login', res); + return onLogin(res); + }).catch(err => { + if (err === null) return; + os.alert({ + type: 'error', + text: i18n.ts.signinFailed, + }); + signing = false; }); - signing = false; - }); } -function onSubmit() { +function onSubmit(): void { signing = true; if (!totpLogin && user && user.twoFactorEnabled) { - if (window.PublicKeyCredential && user.securityKeys) { + if (webAuthnSupported() && user.securityKeys) { os.api('signin', { username, password, @@ -164,9 +154,12 @@ function onSubmit() { }).then(res => { totpLogin = true; signing = false; - challengeData = res; - return queryKey(); - }).catch(loginFailed); + credentialRequest = parseRequestOptionsFromJSON({ + publicKey: res, + }); + }) + .then(() => queryKey()) + .catch(loginFailed); } else { totpLogin = true; signing = false; @@ -177,7 +170,7 @@ function onSubmit() { password, 'hcaptcha-response': hCaptchaResponse, 'g-recaptcha-response': reCaptchaResponse, - token: user && user.twoFactorEnabled ? token : undefined, + token: user?.twoFactorEnabled ? token : undefined, }).then(res => { emit('login', res); onLogin(res); @@ -185,7 +178,7 @@ function onSubmit() { } } -function loginFailed(err) { +function loginFailed(err: any): void { switch (err.id) { case '6cc579cc-885d-43d8-95c2-b8c7fc963280': { os.alert({ @@ -216,7 +209,7 @@ function loginFailed(err) { break; } default: { - console.log(err); + console.error(err); os.alert({ type: 'error', title: i18n.ts.loginFailed, @@ -225,12 +218,11 @@ function loginFailed(err) { } } - challengeData = null; totpLogin = false; signing = false; } -function resetPassword() { +function resetPassword(): void { os.popup(defineAsyncComponent(() => import('@/components/MkForgotPassword.vue')), {}, { }, 'closed'); } diff --git a/packages/frontend/src/components/MkSigninDialog.vue b/packages/frontend/src/components/MkSigninDialog.vue index eb5876e584..05cef6ed3b 100644 --- a/packages/frontend/src/components/MkSigninDialog.vue +++ b/packages/frontend/src/components/MkSigninDialog.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkModalWindow ref="dialog" @@ -18,7 +23,7 @@ import { } from 'vue'; import MkSignin from '@/components/MkSignin.vue'; import MkModalWindow from '@/components/MkModalWindow.vue'; -import { i18n } from '@/i18n'; +import { i18n } from '@/i18n.js'; withDefaults(defineProps<{ autoSet?: boolean; diff --git a/packages/frontend/src/components/MkSignupDialog.form.vue b/packages/frontend/src/components/MkSignupDialog.form.vue index 472269abaf..a67251eda1 100644 --- a/packages/frontend/src/components/MkSignupDialog.form.vue +++ b/packages/frontend/src/components/MkSignupDialog.form.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div> <div :class="$style.banner"> @@ -72,17 +77,16 @@ <script lang="ts" setup> import { } from 'vue'; -import getPasswordStrength from 'syuilo-password-strength'; import { toUnicode } from 'punycode/'; import MkButton from './MkButton.vue'; import MkInput from './MkInput.vue'; import MkSwitch from './MkSwitch.vue'; import MkCaptcha, { type Captcha } from '@/components/MkCaptcha.vue'; -import * as config from '@/config'; -import * as os from '@/os'; -import { login } from '@/account'; -import { instance } from '@/instance'; -import { i18n } from '@/i18n'; +import * as config from '@/config.js'; +import * as os from '@/os.js'; +import { login } from '@/account.js'; +import { instance } from '@/instance.js'; +import { i18n } from '@/i18n.js'; const props = withDefaults(defineProps<{ autoSet?: boolean; @@ -127,6 +131,30 @@ const shouldDisableSubmitting = $computed((): boolean => { passwordRetypeState !== 'match'; }); +function getPasswordStrength(source: string): number { + let strength = 0; + let power = 0.018; + + // 英数字 + if (/[a-zA-Z]/.test(source) && /[0-9]/.test(source)) { + power += 0.020; + } + + // 大文字と小文字が混ざってたら + if (/[a-z]/.test(source) && /[A-Z]/.test(source)) { + power += 0.015; + } + + // 記号が混ざってたら + if (/[!\x22\#$%&@'()*+,-./_]/.test(source)) { + power += 0.02; + } + + strength = power * source.length; + + return Math.max(0, Math.min(1, strength)); +} + function onChangeUsername(): void { if (username === '') { usernameState = null; diff --git a/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts b/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts index 2d95455730..ab26df6342 100644 --- a/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts +++ b/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts @@ -1,11 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { expect } from '@storybook/jest'; import { userEvent, waitFor, within } from '@storybook/testing-library'; import { StoryObj } from '@storybook/vue3'; import { onBeforeUnmount } from 'vue'; import MkSignupServerRules from './MkSignupDialog.rules.vue'; -import { i18n } from '@/i18n'; -import { instance } from '@/instance'; +import { i18n } from '@/i18n.js'; +import { instance } from '@/instance.js'; export const Empty = { render(args) { return { diff --git a/packages/frontend/src/components/MkSignupDialog.rules.vue b/packages/frontend/src/components/MkSignupDialog.rules.vue index de5195ab4f..aa4a184d7b 100644 --- a/packages/frontend/src/components/MkSignupDialog.rules.vue +++ b/packages/frontend/src/components/MkSignupDialog.rules.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div> <div :class="$style.banner"> @@ -56,13 +61,13 @@ <script lang="ts" setup> import { computed, onMounted, ref, watch } from 'vue'; -import { instance } from '@/instance'; -import { i18n } from '@/i18n'; +import { instance } from '@/instance.js'; +import { i18n } from '@/i18n.js'; import MkButton from '@/components/MkButton.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkInfo from '@/components/MkInfo.vue'; -import * as os from '@/os'; +import * as os from '@/os.js'; const availableServerRules = instance.serverRules.length > 0; const availableTos = instance.tosUrl != null; diff --git a/packages/frontend/src/components/MkSignupDialog.vue b/packages/frontend/src/components/MkSignupDialog.vue index d8d002fdb6..d860ba5fe6 100644 --- a/packages/frontend/src/components/MkSignupDialog.vue +++ b/packages/frontend/src/components/MkSignupDialog.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkModalWindow ref="dialog" @@ -33,8 +38,8 @@ import { $ref } from 'vue/macros'; import XSignup from '@/components/MkSignupDialog.form.vue'; import XServerRules from '@/components/MkSignupDialog.rules.vue'; import MkModalWindow from '@/components/MkModalWindow.vue'; -import { i18n } from '@/i18n'; -import { instance } from '@/instance'; +import { i18n } from '@/i18n.js'; +import { instance } from '@/instance.js'; const props = withDefaults(defineProps<{ autoSet?: boolean; diff --git a/packages/frontend/src/components/MkSparkle.vue b/packages/frontend/src/components/MkSparkle.vue index 51d70822d3..a7cd1692bf 100644 --- a/packages/frontend/src/components/MkSparkle.vue +++ b/packages/frontend/src/components/MkSparkle.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <span :class="$style.root"> <span ref="el" style="display: inline-block;"> diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue index 3a032a1167..34fdd1cce4 100644 --- a/packages/frontend/src/components/MkSubNoteContent.vue +++ b/packages/frontend/src/components/MkSubNoteContent.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="[$style.root, { [$style.collapsed]: collapsed }]"> <div> @@ -26,15 +31,15 @@ <script lang="ts" setup> import { } from 'vue'; -import * as misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; import MkMediaList from '@/components/MkMediaList.vue'; import MkPoll from '@/components/MkPoll.vue'; -import { i18n } from '@/i18n'; -import { $i } from '@/account'; -import { shouldCollapsed } from '@/scripts/collapsed'; +import { i18n } from '@/i18n.js'; +import { $i } from '@/account.js'; +import { shouldCollapsed } from '@/scripts/collapsed.js'; const props = defineProps<{ - note: misskey.entities.Note; + note: Misskey.entities.Note; }>(); const isLong = shouldCollapsed(props.note); diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue index 0bc9b03160..efd5665396 100644 --- a/packages/frontend/src/components/MkSuperMenu.vue +++ b/packages/frontend/src/components/MkSuperMenu.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div class="rrevdjwu" :class="{ grid }"> <div v-for="group in def" class="group"> diff --git a/packages/frontend/src/components/MkSwitch.button.vue b/packages/frontend/src/components/MkSwitch.button.vue new file mode 100644 index 0000000000..a7e91acc39 --- /dev/null +++ b/packages/frontend/src/components/MkSwitch.button.vue @@ -0,0 +1,88 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<span + v-tooltip="checked ? i18n.ts.itsOn : i18n.ts.itsOff" + :class="{ + [$style.button]: true, + [$style.buttonChecked]: checked, + [$style.buttonDisabled]: props.disabled + }" + data-cy-switch-toggle + @click.prevent.stop="toggle" +> + <div :class="{ [$style.knob]: true, [$style.knobChecked]: checked }"></div> +</span> +</template> + +<script lang="ts" setup> +import { toRefs, Ref } from 'vue'; +import { i18n } from '@/i18n.js'; + +const props = withDefaults(defineProps<{ + checked: boolean | Ref<boolean>; + disabled?: boolean; +}>(), { + disabled: false, +}); + +const emit = defineEmits<{ + (ev: 'toggle'): void; +}>(); + +const checked = toRefs(props).checked; +const toggle = () => { + emit('toggle'); +}; +</script> + +<style lang="scss" module> +.button { + position: relative; + display: inline-flex; + flex-shrink: 0; + margin: 0; + box-sizing: border-box; + width: 32px; + height: 23px; + outline: none; + background: var(--switchOffBg); + background-clip: content-box; + border: solid 1px var(--switchOffBg); + border-radius: 999px; + cursor: pointer; + transition: inherit; + user-select: none; +} + +.buttonChecked { + background-color: var(--switchOnBg) !important; + border-color: var(--switchOnBg) !important; +} + +.buttonDisabled { + cursor: not-allowed; +} + +.knob { + position: absolute; + top: 3px; + width: 15px; + height: 15px; + border-radius: 999px; + transition: all 0.2s ease; + + &:not(.knobChecked) { + left: 3px; + background: var(--switchOffFg); + } +} + +.knobChecked { + left: 12px; + background: var(--switchOnFg); +} +</style> diff --git a/packages/frontend/src/components/MkSwitch.vue b/packages/frontend/src/components/MkSwitch.vue index 63738b6a44..8e946e7437 100644 --- a/packages/frontend/src/components/MkSwitch.vue +++ b/packages/frontend/src/components/MkSwitch.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="[$style.root, { [$style.disabled]: disabled, [$style.checked]: checked }]"> <input @@ -7,12 +12,15 @@ :class="$style.input" @keydown.enter="toggle" > - <span ref="button" v-tooltip="checked ? i18n.ts.itsOn : i18n.ts.itsOff" :class="$style.button" data-cy-switch-toggle @click.prevent="toggle"> - <div :class="$style.knob"></div> - </span> + <XButton :checked="checked" :disabled="disabled" @toggle="toggle"/> <span :class="$style.body"> <!-- TODO: 無名slotの方は廃止 --> - <span :class="$style.label" @click="toggle"><slot name="label"></slot><slot></slot></span> + <span :class="$style.label"> + <span @click="toggle"> + <slot name="label"></slot><slot></slot> + </span> + <span v-if="helpText" v-tooltip:dialog="helpText" class="_button _help" :class="$style.help"><i class="ti ti-help-circle"></i></span> + </span> <p :class="$style.caption"><slot name="caption"></slot></p> </span> </div> @@ -20,26 +28,22 @@ <script lang="ts" setup> import { toRefs, Ref } from 'vue'; -import { i18n } from '@/i18n'; +import XButton from '@/components/MkSwitch.button.vue'; const props = defineProps<{ modelValue: boolean | Ref<boolean>; disabled?: boolean; + helpText?: string; }>(); const emit = defineEmits<{ (ev: 'update:modelValue', v: boolean): void; }>(); -let button = $shallowRef<HTMLElement>(); const checked = toRefs(props).modelValue; const toggle = () => { if (props.disabled) return; emit('update:modelValue', !checked.value); - - if (!checked.value) { - - } }; </script> @@ -61,17 +65,8 @@ const toggle = () => { cursor: not-allowed; } - &.checked { - > .button { - background-color: var(--switchOnBg) !important; - border-color: var(--switchOnBg) !important; - - > .knob { - left: 12px; - background: var(--switchOnFg); - } - } - } + //&.checked { + //} } .input { @@ -81,36 +76,6 @@ const toggle = () => { opacity: 0; margin: 0; } - -.button { - position: relative; - display: inline-flex; - flex-shrink: 0; - margin: 0; - box-sizing: border-box; - width: 32px; - height: 23px; - outline: none; - background: var(--switchOffBg); - background-clip: content-box; - border: solid 1px var(--switchOffBg); - border-radius: 999px; - cursor: pointer; - transition: inherit; - user-select: none; -} - -.knob { - position: absolute; - top: 3px; - left: 3px; - width: 15px; - height: 15px; - background: var(--switchOffFg); - border-radius: 999px; - transition: all 0.2s ease; -} - .body { margin-left: 12px; margin-top: 2px; @@ -135,4 +100,10 @@ const toggle = () => { display: none; } } + +.help { + margin-left: 0.5em; + font-size: 85%; + vertical-align: top; +} </style> diff --git a/packages/frontend/src/components/MkTab.vue b/packages/frontend/src/components/MkTab.vue index 7274f9b310..9785d89403 100644 --- a/packages/frontend/src/components/MkTab.vue +++ b/packages/frontend/src/components/MkTab.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <script lang="ts"> import { defineComponent, h, resolveDirective, withDirectives } from 'vue'; diff --git a/packages/frontend/src/components/MkTagCloud.vue b/packages/frontend/src/components/MkTagCloud.vue index 21e76b766b..a3d82fee5e 100644 --- a/packages/frontend/src/components/MkTagCloud.vue +++ b/packages/frontend/src/components/MkTagCloud.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div ref="rootEl" :class="$style.root"> <canvas :id="idForCanvas" ref="canvasEl" style="display: block;" :width="width" height="300" @contextmenu.prevent="() => {}"></canvas> diff --git a/packages/frontend/src/components/MkTextarea.vue b/packages/frontend/src/components/MkTextarea.vue index 83b2ed2444..7c1ddcbbed 100644 --- a/packages/frontend/src/components/MkTextarea.vue +++ b/packages/frontend/src/components/MkTextarea.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div> <div :class="$style.label" @click="focus"><slot name="label"></slot></div> @@ -30,7 +35,7 @@ import { onMounted, nextTick, ref, watch, computed, toRefs, shallowRef } from 'vue'; import { debounce } from 'throttle-debounce'; import MkButton from '@/components/MkButton.vue'; -import { i18n } from '@/i18n'; +import { i18n } from '@/i18n.js'; const props = defineProps<{ modelValue: string | null; diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue index 062d0bd87a..d6712e7606 100644 --- a/packages/frontend/src/components/MkTimeline.vue +++ b/packages/frontend/src/components/MkTimeline.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkNotes ref="tlComponent" :noGap="!defaultStore.state.showGapBetweenNotesInTimeline" :pagination="pagination" @queue="emit('queue', $event)"/> </template> @@ -5,10 +10,10 @@ <script lang="ts" setup> import { computed, provide, onUnmounted } from 'vue'; import MkNotes from '@/components/MkNotes.vue'; -import { useStream } from '@/stream'; -import * as sound from '@/scripts/sound'; -import { $i } from '@/account'; -import { defaultStore } from '@/store'; +import { useStream } from '@/stream.js'; +import * as sound from '@/scripts/sound.js'; +import { $i } from '@/account.js'; +import { defaultStore } from '@/store.js'; const props = defineProps<{ src: string; diff --git a/packages/frontend/src/components/MkToast.vue b/packages/frontend/src/components/MkToast.vue index e135f56472..48908cf3e6 100644 --- a/packages/frontend/src/components/MkToast.vue +++ b/packages/frontend/src/components/MkToast.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div> <Transition @@ -18,8 +23,8 @@ <script lang="ts" setup> import { onMounted } from 'vue'; -import * as os from '@/os'; -import { defaultStore } from '@/store'; +import * as os from '@/os.js'; +import { defaultStore } from '@/store.js'; defineProps<{ message: string; diff --git a/packages/frontend/src/components/MkTokenGenerateWindow.vue b/packages/frontend/src/components/MkTokenGenerateWindow.vue index 3ddd81aaee..8958accc4a 100644 --- a/packages/frontend/src/components/MkTokenGenerateWindow.vue +++ b/packages/frontend/src/components/MkTokenGenerateWindow.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkModalWindow ref="dialog" @@ -27,7 +32,9 @@ <MkButton inline @click="disableAll">{{ i18n.ts.disableAll }}</MkButton> <MkButton inline @click="enableAll">{{ i18n.ts.enableAll }}</MkButton> </div> - <MkSwitch v-for="kind in (initialPermissions || kinds)" :key="kind" v-model="permissions[kind]">{{ i18n.t(`_permissions.${kind}`) }}</MkSwitch> + <div class="_gaps_s"> + <MkSwitch v-for="kind in (initialPermissions || Misskey.permissions)" :key="kind" v-model="permissions[kind]">{{ i18n.t(`_permissions.${kind}`) }}</MkSwitch> + </div> </div> </MkSpacer> </MkModalWindow> @@ -35,13 +42,13 @@ <script lang="ts" setup> import { } from 'vue'; -import { permissions as kinds } from 'misskey-js'; +import * as Misskey from 'misskey-js'; import MkInput from './MkInput.vue'; import MkSwitch from './MkSwitch.vue'; import MkButton from './MkButton.vue'; import MkInfo from './MkInfo.vue'; import MkModalWindow from '@/components/MkModalWindow.vue'; -import { i18n } from '@/i18n'; +import { i18n } from '@/i18n.js'; const props = withDefaults(defineProps<{ title?: string | null; @@ -69,7 +76,7 @@ if (props.initialPermissions) { permissions[kind] = true; } } else { - for (const kind of kinds) { + for (const kind of Misskey.permissions) { permissions[kind] = false; } } diff --git a/packages/frontend/src/components/MkTooltip.vue b/packages/frontend/src/components/MkTooltip.vue index 91c9b70a5a..d21c6317aa 100644 --- a/packages/frontend/src/components/MkTooltip.vue +++ b/packages/frontend/src/components/MkTooltip.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <Transition :enterActiveClass="defaultStore.state.animation ? $style.transition_tooltip_enterActive : ''" @@ -17,9 +22,9 @@ <script lang="ts" setup> import { nextTick, onMounted, onUnmounted, shallowRef } from 'vue'; -import * as os from '@/os'; -import { calcPopupPosition } from '@/scripts/popup-position'; -import { defaultStore } from '@/store'; +import * as os from '@/os.js'; +import { calcPopupPosition } from '@/scripts/popup-position.js'; +import { defaultStore } from '@/store.js'; const props = withDefaults(defineProps<{ showing: boolean; diff --git a/packages/frontend/src/components/MkUpdated.vue b/packages/frontend/src/components/MkUpdated.vue index 3a0b2abb4e..699d7af33e 100644 --- a/packages/frontend/src/components/MkUpdated.vue +++ b/packages/frontend/src/components/MkUpdated.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkModal ref="modal" :zPriority="'middle'" @click="$refs.modal.close()" @closed="$emit('closed')"> <div :class="$style.root"> @@ -14,9 +19,9 @@ import { onMounted, shallowRef } from 'vue'; import MkModal from '@/components/MkModal.vue'; import MkButton from '@/components/MkButton.vue'; import MkSparkle from '@/components/MkSparkle.vue'; -import { version } from '@/config'; -import { i18n } from '@/i18n'; -import { confetti } from '@/scripts/confetti'; +import { version } from '@/config.js'; +import { i18n } from '@/i18n.js'; +import { confetti } from '@/scripts/confetti.js'; const modal = shallowRef<InstanceType<typeof MkModal>>(); diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue index f7b1b7dfff..e2844f8fa1 100644 --- a/packages/frontend/src/components/MkUrlPreview.vue +++ b/packages/frontend/src/components/MkUrlPreview.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <template v-if="player.url && playerEnabled"> <div @@ -23,7 +28,14 @@ </template> <template v-else-if="tweetId && tweetExpanded"> <div ref="twitter"> - <iframe ref="tweet" scrolling="no" frameborder="no" :style="{ position: 'relative', width: '100%', height: `${tweetHeight}px` }" :src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&hideCard=false&hideThread=false&lang=en&theme=${defaultStore.state.darkMode ? 'dark' : 'light'}&id=${tweetId}`"></iframe> + <iframe + ref="tweet" + allow="fullscreen;web-share" + sandbox="allow-popups allow-scripts allow-same-origin" + scrolling="no" + :style="{ position: 'relative', width: '100%', height: `${tweetHeight}px`, border: 0 }" + :src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&hideCard=false&hideThread=false&lang=en&theme=${defaultStore.state.darkMode ? 'dark' : 'light'}&id=${tweetId}`" + ></iframe> </div> <div :class="$style.action"> <MkButton :small="true" inline @click="tweetExpanded = false"> @@ -33,7 +45,7 @@ </template> <div v-else> <component :is="self ? 'MkA' : 'a'" :class="[$style.link, { [$style.compact]: compact }]" :[attr]="self ? url.substring(local.length) : url" rel="nofollow noopener" :target="target" :title="url"> - <div v-if="thumbnail" :class="$style.thumbnail" :style="`background-image: url('${thumbnail}')`"> + <div v-if="thumbnail" :class="$style.thumbnail" :style="defaultStore.state.enableDataSaverMode ? '' : `background-image: url('${thumbnail}')`"> </div> <article :class="$style.body"> <header :class="$style.header"> @@ -55,7 +67,7 @@ <template v-if="showActions"> <div v-if="tweetId" :class="$style.action"> <MkButton :small="true" inline @click="tweetExpanded = true"> - <i class="ti ti-brand-twitter"></i> {{ i18n.ts.expandTweet }} + <i class="ti ti-brand-x"></i> {{ i18n.ts.expandTweet }} </MkButton> </div> <div v-if="!playerEnabled && player.url" :class="$style.action"> @@ -73,13 +85,13 @@ <script lang="ts" setup> import { defineAsyncComponent, onUnmounted } from 'vue'; import type { summaly } from 'summaly'; -import { url as local } from '@/config'; -import { i18n } from '@/i18n'; -import * as os from '@/os'; -import { deviceKind } from '@/scripts/device-kind'; +import { url as local } from '@/config.js'; +import { i18n } from '@/i18n.js'; +import * as os from '@/os.js'; +import { deviceKind } from '@/scripts/device-kind.js'; import MkButton from '@/components/MkButton.vue'; -import { versatileLang } from '@/scripts/intl-const'; -import { defaultStore } from '@/store'; +import { versatileLang } from '@/scripts/intl-const.js'; +import { defaultStore } from '@/store.js'; type SummalyResult = Awaited<ReturnType<typeof summaly>>; @@ -121,7 +133,7 @@ let unknownUrl = $ref(false); const requestUrl = new URL(props.url); if (!['http:', 'https:'].includes(requestUrl.protocol)) throw new Error('invalid url'); -if (requestUrl.hostname === 'twitter.com' || requestUrl.hostname === 'mobile.twitter.com') { +if (requestUrl.hostname === 'twitter.com' || requestUrl.hostname === 'mobile.twitter.com' || requestUrl.hostname === 'x.com' || requestUrl.hostname === 'mobile.x.com') { const m = requestUrl.pathname.match(/^\/.+\/status(?:es)?\/(\d+)/); if (m) tweetId = m[1]; } @@ -248,6 +260,7 @@ onUnmounted(() => { height: 100%; background-position: center; background-size: cover; + background-color: var(--bg); display: flex; justify-content: center; align-items: center; diff --git a/packages/frontend/src/components/MkUrlPreviewPopup.vue b/packages/frontend/src/components/MkUrlPreviewPopup.vue index d360169c82..0ab012dfb7 100644 --- a/packages/frontend/src/components/MkUrlPreviewPopup.vue +++ b/packages/frontend/src/components/MkUrlPreviewPopup.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="$style.root" :style="{ zIndex, top: top + 'px', left: left + 'px' }"> <Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" @afterLeave="emit('closed')"> @@ -9,8 +14,8 @@ <script lang="ts" setup> import { onMounted } from 'vue'; import MkUrlPreview from '@/components/MkUrlPreview.vue'; -import * as os from '@/os'; -import { defaultStore } from '@/store'; +import * as os from '@/os.js'; +import { defaultStore } from '@/store.js'; const props = defineProps<{ showing: boolean; diff --git a/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue b/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue new file mode 100644 index 0000000000..235df8822f --- /dev/null +++ b/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue @@ -0,0 +1,145 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<MkModalWindow + ref="dialog" + :width="400" + @close="dialog.close()" + @closed="$emit('closed')" +> + <template v-if="announcement" #header>:{{ announcement.title }}:</template> + <template v-else #header>New announcement</template> + + <div> + <MkSpacer :marginMin="20" :marginMax="28"> + <div class="_gaps_m"> + <MkInput v-model="title"> + <template #label>{{ i18n.ts.title }}</template> + </MkInput> + <MkTextarea v-model="text"> + <template #label>{{ i18n.ts.text }}</template> + </MkTextarea> + <MkRadios v-model="icon"> + <template #label>{{ i18n.ts.icon }}</template> + <option value="info"><i class="ti ti-info-circle"></i></option> + <option value="warning"><i class="ti ti-alert-triangle" style="color: var(--warn);"></i></option> + <option value="error"><i class="ti ti-circle-x" style="color: var(--error);"></i></option> + <option value="success"><i class="ti ti-check" style="color: var(--success);"></i></option> + </MkRadios> + <MkRadios v-model="display"> + <template #label>{{ i18n.ts.display }}</template> + <option value="normal">{{ i18n.ts.normal }}</option> + <option value="banner">{{ i18n.ts.banner }}</option> + <option value="dialog">{{ i18n.ts.dialog }}</option> + </MkRadios> + <MkSwitch v-model="needConfirmationToRead"> + {{ i18n.ts._announcement.needConfirmationToRead }} + <template #caption>{{ i18n.ts._announcement.needConfirmationToReadDescription }}</template> + </MkSwitch> + <MkButton v-if="announcement" danger @click="del()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton> + </div> + </MkSpacer> + <div :class="$style.footer"> + <MkButton primary rounded style="margin: 0 auto;" @click="done"><i class="ti ti-check"></i> {{ props.announcement ? i18n.ts.update : i18n.ts.create }}</MkButton> + </div> + </div> +</MkModalWindow> +</template> + +<script lang="ts" setup> +import { } from 'vue'; +import * as Misskey from 'misskey-js'; +import MkModalWindow from '@/components/MkModalWindow.vue'; +import MkButton from '@/components/MkButton.vue'; +import MkInput from '@/components/MkInput.vue'; +import * as os from '@/os.js'; +import { i18n } from '@/i18n.js'; +import MkTextarea from '@/components/MkTextarea.vue'; +import MkSwitch from '@/components/MkSwitch.vue'; +import MkRadios from '@/components/MkRadios.vue'; + +const props = defineProps<{ + user: Misskey.entities.User, + announcement?: any, +}>(); + +let dialog = $ref(null); +let title: string = $ref(props.announcement ? props.announcement.title : ''); +let text: string = $ref(props.announcement ? props.announcement.text : ''); +let icon: string = $ref(props.announcement ? props.announcement.icon : 'info'); +let display: string = $ref(props.announcement ? props.announcement.display : 'dialog'); +let needConfirmationToRead = $ref(props.announcement ? props.announcement.needConfirmationToRead : false); + +const emit = defineEmits<{ + (ev: 'done', v: { deleted?: boolean; updated?: any; created?: any }): void, + (ev: 'closed'): void +}>(); + +async function done() { + const params = { + title: title, + text: text, + icon: icon, + imageUrl: null, + display: display, + needConfirmationToRead: needConfirmationToRead, + userId: props.user.id, + }; + + if (props.announcement) { + await os.apiWithDialog('admin/announcements/update', { + id: props.announcement.id, + ...params, + }); + + emit('done', { + updated: { + id: props.announcement.id, + ...params, + }, + }); + + dialog.close(); + } else { + const created = await os.apiWithDialog('admin/announcements/create', params); + + emit('done', { + created: created, + }); + + dialog.close(); + } +} + +async function del() { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.t('removeAreYouSure', { x: title }), + }); + if (canceled) return; + + os.api('admin/announcements/delete', { + id: props.announcement.id, + }).then(() => { + emit('done', { + deleted: true, + }); + dialog.close(); + }); +} +</script> + +<style lang="scss" module> +.footer { + position: sticky; + bottom: 0; + left: 0; + padding: 12px; + border-top: solid 0.5px var(--divider); + -webkit-backdrop-filter: var(--blur, blur(15px)); + backdrop-filter: var(--blur, blur(15px)); +} +</style> diff --git a/packages/frontend/src/components/MkUserCardMini.vue b/packages/frontend/src/components/MkUserCardMini.vue index 457504e6ca..fbc2e09b0b 100644 --- a/packages/frontend/src/components/MkUserCardMini.vue +++ b/packages/frontend/src/components/MkUserCardMini.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div v-adaptive-bg :class="[$style.root, { yellow: user.isSilenced, red: user.isSuspended, gray: false }]"> <MkAvatar class="avatar" :user="user" indicator/> @@ -10,14 +15,14 @@ </template> <script lang="ts" setup> -import * as misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; import { onMounted } from 'vue'; import MkMiniChart from '@/components/MkMiniChart.vue'; -import * as os from '@/os'; -import { acct } from '@/filters/user'; +import * as os from '@/os.js'; +import { acct } from '@/filters/user.js'; const props = withDefaults(defineProps<{ - user: misskey.entities.User; + user: Misskey.entities.User; withChart: boolean; }>(), { withChart: true, diff --git a/packages/frontend/src/components/MkUserInfo.vue b/packages/frontend/src/components/MkUserInfo.vue index 5e538cc528..c13ef60f3b 100644 --- a/packages/frontend/src/components/MkUserInfo.vue +++ b/packages/frontend/src/components/MkUserInfo.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div class="_panel" :class="$style.root"> <div :class="$style.banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''"></div> @@ -29,16 +34,16 @@ </template> <script lang="ts" setup> -import * as misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; import MkFollowButton from '@/components/MkFollowButton.vue'; -import number from '@/filters/number'; -import { userPage } from '@/filters/user'; -import { i18n } from '@/i18n'; -import { $i } from '@/account'; -import { isFfVisibleForMe } from '@/scripts/isFfVisibleForMe'; +import number from '@/filters/number.js'; +import { userPage } from '@/filters/user.js'; +import { i18n } from '@/i18n.js'; +import { $i } from '@/account.js'; +import { isFfVisibleForMe } from '@/scripts/isFfVisibleForMe.js'; defineProps<{ - user: misskey.entities.UserDetailed; + user: Misskey.entities.UserDetailed; }>(); </script> diff --git a/packages/frontend/src/components/MkUserList.vue b/packages/frontend/src/components/MkUserList.vue index 2a23f3e70d..56a61dce23 100644 --- a/packages/frontend/src/components/MkUserList.vue +++ b/packages/frontend/src/components/MkUserList.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkPagination :pagination="pagination"> <template #empty> @@ -18,8 +23,8 @@ <script lang="ts" setup> import MkUserInfo from '@/components/MkUserInfo.vue'; import MkPagination, { Paging } from '@/components/MkPagination.vue'; -import { i18n } from '@/i18n'; -import { infoImageUrl } from '@/instance'; +import { i18n } from '@/i18n.js'; +import { infoImageUrl } from '@/instance.js'; const props = withDefaults(defineProps<{ pagination: Paging; diff --git a/packages/frontend/src/components/MkUserOnlineIndicator.vue b/packages/frontend/src/components/MkUserOnlineIndicator.vue index a2c2b53b08..8b792fe496 100644 --- a/packages/frontend/src/components/MkUserOnlineIndicator.vue +++ b/packages/frontend/src/components/MkUserOnlineIndicator.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div v-tooltip="text" @@ -12,11 +17,11 @@ <script lang="ts" setup> import { } from 'vue'; -import * as misskey from 'misskey-js'; -import { i18n } from '@/i18n'; +import * as Misskey from 'misskey-js'; +import { i18n } from '@/i18n.js'; const props = defineProps<{ - user: misskey.entities.User; + user: Misskey.entities.User; }>(); const text = $computed(() => { diff --git a/packages/frontend/src/components/MkUserPopup.vue b/packages/frontend/src/components/MkUserPopup.vue index 04331ceb50..33ef07d54b 100644 --- a/packages/frontend/src/components/MkUserPopup.vue +++ b/packages/frontend/src/components/MkUserPopup.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <Transition :enterActiveClass="defaultStore.state.animation ? $style.transition_popup_enterActive : ''" @@ -51,17 +56,16 @@ <script lang="ts" setup> import { onMounted } from 'vue'; -import * as Acct from 'misskey-js/built/acct'; -import * as misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; import MkFollowButton from '@/components/MkFollowButton.vue'; -import { userPage } from '@/filters/user'; -import * as os from '@/os'; -import { getUserMenu } from '@/scripts/get-user-menu'; -import number from '@/filters/number'; -import { i18n } from '@/i18n'; -import { defaultStore } from '@/store'; -import { $i } from '@/account'; -import { isFfVisibleForMe } from '@/scripts/isFfVisibleForMe'; +import { userPage } from '@/filters/user.js'; +import * as os from '@/os.js'; +import { getUserMenu } from '@/scripts/get-user-menu.js'; +import number from '@/filters/number.js'; +import { i18n } from '@/i18n.js'; +import { defaultStore } from '@/store.js'; +import { $i } from '@/account.js'; +import { isFfVisibleForMe } from '@/scripts/isFfVisibleForMe.js'; const props = defineProps<{ showing: boolean; @@ -76,12 +80,13 @@ const emit = defineEmits<{ }>(); const zIndex = os.claimZIndex('middle'); -let user = $ref<misskey.entities.UserDetailed | null>(null); +let user = $ref<Misskey.entities.UserDetailed | null>(null); let top = $ref(0); let left = $ref(0); function showMenu(ev: MouseEvent) { - os.popupMenu(getUserMenu(user), ev.currentTarget ?? ev.target); + const { menu, cleanup } = getUserMenu(user); + os.popupMenu(menu, ev.currentTarget ?? ev.target).finally(cleanup); } onMounted(() => { @@ -89,7 +94,7 @@ onMounted(() => { user = props.q; } else { const query = props.q.startsWith('@') ? - Acct.parse(props.q.substring(1)) : + Misskey.acct.parse(props.q.substring(1)) : { userId: props.q }; os.api('users/show', query).then(res => { diff --git a/packages/frontend/src/components/MkUserSelectDialog.vue b/packages/frontend/src/components/MkUserSelectDialog.vue index 792ff7afd7..ac38c4b62f 100644 --- a/packages/frontend/src/components/MkUserSelectDialog.vue +++ b/packages/frontend/src/components/MkUserSelectDialog.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkModalWindow ref="dialogEl" @@ -53,18 +58,18 @@ <script lang="ts" setup> import { onMounted } from 'vue'; -import * as misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; import MkInput from '@/components/MkInput.vue'; import FormSplit from '@/components/form/split.vue'; import MkModalWindow from '@/components/MkModalWindow.vue'; -import * as os from '@/os'; -import { defaultStore } from '@/store'; -import { i18n } from '@/i18n'; -import { $i } from '@/account'; -import { hostname } from '@/config'; +import * as os from '@/os.js'; +import { defaultStore } from '@/store.js'; +import { i18n } from '@/i18n.js'; +import { $i } from '@/account.js'; +import { hostname } from '@/config.js'; const emit = defineEmits<{ - (ev: 'ok', selected: misskey.entities.UserDetailed): void; + (ev: 'ok', selected: Misskey.entities.UserDetailed): void; (ev: 'cancel'): void; (ev: 'closed'): void; }>(); @@ -75,9 +80,9 @@ const props = defineProps<{ let username = $ref(''); let host = $ref(''); -let users: misskey.entities.UserDetailed[] = $ref([]); -let recentUsers: misskey.entities.UserDetailed[] = $ref([]); -let selected: misskey.entities.UserDetailed | null = $ref(null); +let users: Misskey.entities.UserDetailed[] = $ref([]); +let recentUsers: Misskey.entities.UserDetailed[] = $ref([]); +let selected: Misskey.entities.UserDetailed | null = $ref(null); let dialogEl = $ref(); const search = () => { diff --git a/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts index 67243b78f3..9122bb8983 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts +++ b/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; import { rest } from 'msw'; diff --git a/packages/frontend/src/components/MkUserSetupDialog.Follow.vue b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue index 789f88a8fe..4ecca7334c 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.Follow.vue +++ b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div class="_gaps"> <div style="text-align: center;">{{ i18n.ts._initialAccountSetting.followUsers }}</div> @@ -30,14 +35,14 @@ <script lang="ts" setup> import { computed, ref, watch } from 'vue'; -import { instance } from '@/instance'; -import { i18n } from '@/i18n'; +import { instance } from '@/instance.js'; +import { i18n } from '@/i18n.js'; import MkButton from '@/components/MkButton.vue'; import MkFolder from '@/components/MkFolder.vue'; import XUser from '@/components/MkUserSetupDialog.User.vue'; import MkInfo from '@/components/MkInfo.vue'; -import * as os from '@/os'; -import { $i } from '@/account'; +import * as os from '@/os.js'; +import { $i } from '@/account.js'; import MkPagination from '@/components/MkPagination.vue'; const pinnedUsers = { endpoint: 'pinned-users', noPaging: true }; diff --git a/packages/frontend/src/components/MkUserSetupDialog.Privacy.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.Privacy.stories.impl.ts index 0726289722..0f81c0817d 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.Privacy.stories.impl.ts +++ b/packages/frontend/src/components/MkUserSetupDialog.Privacy.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; import MkUserSetupDialog_Privacy from './MkUserSetupDialog.Privacy.vue'; diff --git a/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue b/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue index 5cea67ccf5..841ab5ba0c 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue +++ b/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div class="_gaps"> <MkInfo>{{ i18n.ts._initialAccountSetting.theseSettingsCanEditLater }}</MkInfo> @@ -40,13 +45,13 @@ <script lang="ts" setup> import { computed, ref, watch } from 'vue'; -import { instance } from '@/instance'; -import { i18n } from '@/i18n'; +import { instance } from '@/instance.js'; +import { i18n } from '@/i18n.js'; import MkSwitch from '@/components/MkSwitch.vue'; import MkInfo from '@/components/MkInfo.vue'; import MkFolder from '@/components/MkFolder.vue'; -import * as os from '@/os'; -import { $i } from '@/account'; +import * as os from '@/os.js'; +import { $i } from '@/account.js'; let isLocked = ref(false); let hideOnlineStatus = ref(false); diff --git a/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts index 3444605e97..d2c6f7d479 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts +++ b/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; import MkUserSetupDialog_Profile from './MkUserSetupDialog.Profile.vue'; diff --git a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue index 3107209b97..8de9bbdbb1 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue +++ b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div class="_gaps"> <MkInfo>{{ i18n.ts._initialAccountSetting.theseSettingsCanEditLater }}</MkInfo> @@ -26,16 +31,16 @@ <script lang="ts" setup> import { computed, ref, watch } from 'vue'; -import { instance } from '@/instance'; -import { i18n } from '@/i18n'; +import { instance } from '@/instance.js'; +import { i18n } from '@/i18n.js'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; import MkTextarea from '@/components/MkTextarea.vue'; import FormSlot from '@/components/form/slot.vue'; import MkInfo from '@/components/MkInfo.vue'; -import { chooseFileFromPc } from '@/scripts/select-file'; -import * as os from '@/os'; -import { $i } from '@/account'; +import { chooseFileFromPc } from '@/scripts/select-file.js'; +import * as os from '@/os.js'; +import { $i } from '@/account.js'; const name = ref($i.name ?? ''); const description = ref($i.description ?? ''); diff --git a/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts index 7413f4884b..3324c0186c 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts +++ b/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; import { userDetailed } from '../../.storybook/fakes'; diff --git a/packages/frontend/src/components/MkUserSetupDialog.User.vue b/packages/frontend/src/components/MkUserSetupDialog.User.vue index b35f27c5b0..746781d71f 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.User.vue +++ b/packages/frontend/src/components/MkUserSetupDialog.User.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div v-adaptive-bg class="_panel" style="position: relative;"> <div :class="$style.banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''"></div> @@ -20,15 +25,15 @@ </template> <script lang="ts" setup> -import * as misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; import { ref } from 'vue'; import MkButton from '@/components/MkButton.vue'; -import { i18n } from '@/i18n'; -import { $i } from '@/account'; -import * as os from '@/os'; +import { i18n } from '@/i18n.js'; +import { $i } from '@/account.js'; +import * as os from '@/os.js'; const props = defineProps<{ - user: misskey.entities.UserDetailed; + user: Misskey.entities.UserDetailed; }>(); const isFollowing = ref(false); diff --git a/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts index f47f4c13d5..2795bcb2fa 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts +++ b/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; import { rest } from 'msw'; diff --git a/packages/frontend/src/components/MkUserSetupDialog.vue b/packages/frontend/src/components/MkUserSetupDialog.vue index 566441213e..d60e01c44d 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.vue +++ b/packages/frontend/src/components/MkUserSetupDialog.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkModalWindow ref="dialog" @@ -125,12 +130,12 @@ import XProfile from '@/components/MkUserSetupDialog.Profile.vue'; import XFollow from '@/components/MkUserSetupDialog.Follow.vue'; import XPrivacy from '@/components/MkUserSetupDialog.Privacy.vue'; import MkAnimBg from '@/components/MkAnimBg.vue'; -import { i18n } from '@/i18n'; -import { instance } from '@/instance'; -import { host } from '@/config'; +import { i18n } from '@/i18n.js'; +import { instance } from '@/instance.js'; +import { host } from '@/config.js'; import MkPushNotificationAllowButton from '@/components/MkPushNotificationAllowButton.vue'; -import { defaultStore } from '@/store'; -import * as os from '@/os'; +import { defaultStore } from '@/store.js'; +import * as os from '@/os.js'; const emit = defineEmits<{ (ev: 'closed'): void; diff --git a/packages/frontend/src/components/MkUsersTooltip.vue b/packages/frontend/src/components/MkUsersTooltip.vue index 0b80c2edc7..37548952b6 100644 --- a/packages/frontend/src/components/MkUsersTooltip.vue +++ b/packages/frontend/src/components/MkUsersTooltip.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkTooltip ref="tooltip" :showing="showing" :targetElement="targetElement" :maxWidth="250" @closed="emit('closed')"> <div :class="$style.root"> diff --git a/packages/frontend/src/components/MkVisibilityPicker.vue b/packages/frontend/src/components/MkVisibilityPicker.vue index c8dbe90944..982a69925b 100644 --- a/packages/frontend/src/components/MkVisibilityPicker.vue +++ b/packages/frontend/src/components/MkVisibilityPicker.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkModal ref="modal" v-slot="{ type }" :zPriority="'high'" :src="src" @click="modal.close()" @closed="emit('closed')"> <div class="_popup" :class="{ [$style.root]: true, [$style.asDrawer]: type === 'drawer' }"> @@ -38,27 +43,27 @@ <script lang="ts" setup> import { nextTick } from 'vue'; -import * as misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; import MkModal from '@/components/MkModal.vue'; -import { i18n } from '@/i18n'; +import { i18n } from '@/i18n.js'; const modal = $shallowRef<InstanceType<typeof MkModal>>(); const props = withDefaults(defineProps<{ - currentVisibility: typeof misskey.noteVisibilities[number]; + currentVisibility: typeof Misskey.noteVisibilities[number]; localOnly: boolean; src?: HTMLElement; }>(), { }); const emit = defineEmits<{ - (ev: 'changeVisibility', v: typeof misskey.noteVisibilities[number]): void; + (ev: 'changeVisibility', v: typeof Misskey.noteVisibilities[number]): void; (ev: 'closed'): void; }>(); let v = $ref(props.currentVisibility); -function choose(visibility: typeof misskey.noteVisibilities[number]): void { +function choose(visibility: typeof Misskey.noteVisibilities[number]): void { v = visibility; emit('changeVisibility', visibility); nextTick(() => { diff --git a/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue index fb705786cf..26de7dee52 100644 --- a/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue +++ b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div> <MkLoading v-if="fetching"/> @@ -12,11 +17,11 @@ import { onMounted } from 'vue'; import { Chart } from 'chart.js'; import gradient from 'chartjs-plugin-gradient'; import tinycolor from 'tinycolor2'; -import * as os from '@/os'; -import { defaultStore } from '@/store'; -import { useChartTooltip } from '@/scripts/use-chart-tooltip'; -import { chartVLine } from '@/scripts/chart-vline'; -import { initChart } from '@/scripts/init-chart'; +import * as os from '@/os.js'; +import { defaultStore } from '@/store.js'; +import { useChartTooltip } from '@/scripts/use-chart-tooltip.js'; +import { chartVLine } from '@/scripts/chart-vline.js'; +import { initChart } from '@/scripts/init-chart.js'; initChart(); diff --git a/packages/frontend/src/components/MkVisitorDashboard.vue b/packages/frontend/src/components/MkVisitorDashboard.vue index 9566cc651f..7a8d7e6109 100644 --- a/packages/frontend/src/components/MkVisitorDashboard.vue +++ b/packages/frontend/src/components/MkVisitorDashboard.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div v-if="meta" :class="$style.root"> <div :class="[$style.main, $style.panel]"> @@ -47,22 +52,22 @@ <script lang="ts" setup> import { } from 'vue'; -import { Instance } from 'misskey-js/built/entities'; +import * as Misskey from 'misskey-js'; import XTimeline from './welcome.timeline.vue'; import XSigninDialog from '@/components/MkSigninDialog.vue'; import XSignupDialog from '@/components/MkSignupDialog.vue'; import MkButton from '@/components/MkButton.vue'; import MkTimeline from '@/components/MkTimeline.vue'; import MkInfo from '@/components/MkInfo.vue'; -import { instanceName } from '@/config'; -import * as os from '@/os'; -import { i18n } from '@/i18n'; -import { instance } from '@/instance'; -import number from '@/filters/number'; +import { instanceName } from '@/config.js'; +import * as os from '@/os.js'; +import { i18n } from '@/i18n.js'; +import { instance } from '@/instance.js'; +import number from '@/filters/number.js'; import MkNumber from '@/components/MkNumber.vue'; import XActiveUsersChart from '@/components/MkVisitorDashboard.ActiveUsersChart.vue'; -let meta = $ref<Instance>(); +let meta = $ref<Misskey.entities.Instance>(); let stats = $ref(null); os.api('meta', { detail: true }).then(_meta => { diff --git a/packages/frontend/src/components/MkWaitingDialog.vue b/packages/frontend/src/components/MkWaitingDialog.vue index 1b6ab1f13a..1326ca2693 100644 --- a/packages/frontend/src/components/MkWaitingDialog.vue +++ b/packages/frontend/src/components/MkWaitingDialog.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkModal ref="modal" :preferType="'dialog'" :zPriority="'high'" @click="success ? done() : () => {}" @closed="emit('closed')"> <div :class="[$style.root, { [$style.iconOnly]: (text == null) || success }]"> diff --git a/packages/frontend/src/components/MkWidgets.vue b/packages/frontend/src/components/MkWidgets.vue index 30547c7444..e2d7ceab7b 100644 --- a/packages/frontend/src/components/MkWidgets.vue +++ b/packages/frontend/src/components/MkWidgets.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="$style.root"> <template v-if="edit"> @@ -50,8 +55,8 @@ import { v4 as uuid } from 'uuid'; import MkSelect from '@/components/MkSelect.vue'; import MkButton from '@/components/MkButton.vue'; import { widgets as widgetDefs } from '@/widgets'; -import * as os from '@/os'; -import { i18n } from '@/i18n'; +import * as os from '@/os.js'; +import { i18n } from '@/i18n.js'; const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); diff --git a/packages/frontend/src/components/MkWindow.vue b/packages/frontend/src/components/MkWindow.vue index dafabf2ba8..ccb8b09b6c 100644 --- a/packages/frontend/src/components/MkWindow.vue +++ b/packages/frontend/src/components/MkWindow.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <Transition :enterActiveClass="defaultStore.state.animation ? $style.transition_window_enterActive : ''" @@ -49,11 +54,11 @@ <script lang="ts" setup> import { onBeforeUnmount, onMounted, provide } from 'vue'; -import contains from '@/scripts/contains'; -import * as os from '@/os'; +import contains from '@/scripts/contains.js'; +import * as os from '@/os.js'; import { MenuItem } from '@/types/menu'; -import { i18n } from '@/i18n'; -import { defaultStore } from '@/store'; +import { i18n } from '@/i18n.js'; +import { defaultStore } from '@/store.js'; const minHeight = 50; const minWidth = 250; diff --git a/packages/frontend/src/components/MkYouTubePlayer.vue b/packages/frontend/src/components/MkYouTubePlayer.vue index 0edfd98efc..d74ad0eda4 100644 --- a/packages/frontend/src/components/MkYouTubePlayer.vue +++ b/packages/frontend/src/components/MkYouTubePlayer.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkWindow :initialWidth="640" :initialHeight="402" :canResize="true" :closeButton="true"> <template #header> @@ -20,8 +25,8 @@ <script lang="ts" setup> import MkWindow from '@/components/MkWindow.vue'; -import { versatileLang } from '@/scripts/intl-const'; -import { defaultStore } from '@/store'; +import { versatileLang } from '@/scripts/intl-const.js'; +import { defaultStore } from '@/store.js'; const props = defineProps<{ url: string; diff --git a/packages/frontend/src/components/form/link.vue b/packages/frontend/src/components/form/link.vue index 22b5edc3c9..4d711814b5 100644 --- a/packages/frontend/src/components/form/link.vue +++ b/packages/frontend/src/components/form/link.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="[$style.root, { [$style.inline]: inline }]"> <a v-if="external" :class="$style.main" class="_button" :href="to" target="_blank"> diff --git a/packages/frontend/src/components/form/section.vue b/packages/frontend/src/components/form/section.vue index 55308b9c80..095b24604a 100644 --- a/packages/frontend/src/components/form/section.vue +++ b/packages/frontend/src/components/form/section.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="[$style.root, { [$style.rootFirst]: first }]"> <div :class="[$style.label, { [$style.labelFirst]: first }]"><slot name="label"></slot></div> diff --git a/packages/frontend/src/components/form/slot.vue b/packages/frontend/src/components/form/slot.vue index 809d80620f..dc4d197507 100644 --- a/packages/frontend/src/components/form/slot.vue +++ b/packages/frontend/src/components/form/slot.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div> <div :class="$style.label" @click="focus"><slot name="label"></slot></div> diff --git a/packages/frontend/src/components/form/split.vue b/packages/frontend/src/components/form/split.vue index 1b61f0534e..8cb24b479e 100644 --- a/packages/frontend/src/components/form/split.vue +++ b/packages/frontend/src/components/form/split.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="$style.root"> <slot></slot> diff --git a/packages/frontend/src/components/form/suspense.vue b/packages/frontend/src/components/form/suspense.vue index b3d8c22b27..e3db639ff0 100644 --- a/packages/frontend/src/components/form/suspense.vue +++ b/packages/frontend/src/components/form/suspense.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div v-if="pending"> <MkLoading/> @@ -16,8 +21,8 @@ <script lang="ts" setup> import { ref, watch } from 'vue'; import MkButton from '@/components/MkButton.vue'; -import { defaultStore } from '@/store'; -import { i18n } from '@/i18n'; +import { defaultStore } from '@/store.js'; +import { i18n } from '@/i18n.js'; const props = defineProps<{ p: () => Promise<any>; diff --git a/packages/frontend/src/components/global/MkA.stories.impl.ts b/packages/frontend/src/components/global/MkA.stories.impl.ts index 6e3ff573cb..62f4805a11 100644 --- a/packages/frontend/src/components/global/MkA.stories.impl.ts +++ b/packages/frontend/src/components/global/MkA.stories.impl.ts @@ -1,9 +1,14 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { expect } from '@storybook/jest'; import { userEvent, within } from '@storybook/testing-library'; import { StoryObj } from '@storybook/vue3'; import MkA from './MkA.vue'; -import { tick } from '@/scripts/test-utils'; +import { tick } from '@/scripts/test-utils.js'; export const Default = { render(args) { return { diff --git a/packages/frontend/src/components/global/MkA.vue b/packages/frontend/src/components/global/MkA.vue index 4e608c6efe..2c50511b8b 100644 --- a/packages/frontend/src/components/global/MkA.vue +++ b/packages/frontend/src/components/global/MkA.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <a :href="to" :class="active ? activeClass : null" @click.prevent="nav" @contextmenu.prevent.stop="onContextmenu"> <slot></slot> @@ -5,12 +10,12 @@ </template> <script lang="ts" setup> -import * as os from '@/os'; -import copyToClipboard from '@/scripts/copy-to-clipboard'; -import { url } from '@/config'; -import { popout as popout_ } from '@/scripts/popout'; -import { i18n } from '@/i18n'; -import { useRouter } from '@/router'; +import * as os from '@/os.js'; +import copyToClipboard from '@/scripts/copy-to-clipboard.js'; +import { url } from '@/config.js'; +import { popout as popout_ } from '@/scripts/popout.js'; +import { i18n } from '@/i18n.js'; +import { useRouter } from '@/router.js'; const props = withDefaults(defineProps<{ to: string; diff --git a/packages/frontend/src/components/global/MkAcct.stories.impl.ts b/packages/frontend/src/components/global/MkAcct.stories.impl.ts index 9d5fd3947d..00c1d94330 100644 --- a/packages/frontend/src/components/global/MkAcct.stories.impl.ts +++ b/packages/frontend/src/components/global/MkAcct.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; import { userDetailed } from '../../../.storybook/fakes'; diff --git a/packages/frontend/src/components/global/MkAcct.vue b/packages/frontend/src/components/global/MkAcct.vue index f93659f5ed..42d29db488 100644 --- a/packages/frontend/src/components/global/MkAcct.vue +++ b/packages/frontend/src/components/global/MkAcct.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <MkCondensedLine v-if="defaultStore.state.enableCondensedLineForAcct" :minScale="2 / 3"> <span>@{{ user.username }}</span> @@ -10,14 +15,14 @@ </template> <script lang="ts" setup> -import * as misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; import { toUnicode } from 'punycode/'; import MkCondensedLine from './MkCondensedLine.vue'; -import { host as hostRaw } from '@/config'; -import { defaultStore } from '@/store'; +import { host as hostRaw } from '@/config.js'; +import { defaultStore } from '@/store.js'; defineProps<{ - user: misskey.entities.UserDetailed; + user: Misskey.entities.UserDetailed; detail?: boolean; }>(); diff --git a/packages/frontend/src/components/global/MkAd.stories.impl.ts b/packages/frontend/src/components/global/MkAd.stories.impl.ts index 8d15e1f65b..360bc88b4a 100644 --- a/packages/frontend/src/components/global/MkAd.stories.impl.ts +++ b/packages/frontend/src/components/global/MkAd.stories.impl.ts @@ -1,9 +1,14 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { expect } from '@storybook/jest'; import { userEvent, waitFor, within } from '@storybook/testing-library'; import { StoryObj } from '@storybook/vue3'; import MkAd from './MkAd.vue'; -import { i18n } from '@/i18n'; +import { i18n } from '@/i18n.js'; let lock: Promise<undefined> | undefined; diff --git a/packages/frontend/src/components/global/MkAd.vue b/packages/frontend/src/components/global/MkAd.vue index 8b25ab1b6a..421fe99127 100644 --- a/packages/frontend/src/components/global/MkAd.vue +++ b/packages/frontend/src/components/global/MkAd.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div v-if="chosen && !shouldHide" :class="$style.root"> <div @@ -28,13 +33,13 @@ <script lang="ts" setup> import { ref } from 'vue'; -import { i18n } from '@/i18n'; -import { instance } from '@/instance'; -import { host } from '@/config'; +import { i18n } from '@/i18n.js'; +import { instance } from '@/instance.js'; +import { host } from '@/config.js'; import MkButton from '@/components/MkButton.vue'; -import { defaultStore } from '@/store'; -import * as os from '@/os'; -import { $i } from '@/account'; +import { defaultStore } from '@/store.js'; +import * as os from '@/os.js'; +import { $i } from '@/account.js'; type Ad = (typeof instance)['ads'][number]; diff --git a/packages/frontend/src/components/global/MkAvatar.stories.impl.ts b/packages/frontend/src/components/global/MkAvatar.stories.impl.ts index 3c69c80825..877511f8fc 100644 --- a/packages/frontend/src/components/global/MkAvatar.stories.impl.ts +++ b/packages/frontend/src/components/global/MkAvatar.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; import { userDetailed } from '../../../.storybook/fakes'; diff --git a/packages/frontend/src/components/global/MkAvatar.vue b/packages/frontend/src/components/global/MkAvatar.vue index 1952ba9811..27c25b9490 100644 --- a/packages/frontend/src/components/global/MkAvatar.vue +++ b/packages/frontend/src/components/global/MkAvatar.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <component :is="link ? MkA : 'span'" v-user-preview="preview ? user.id : undefined" v-bind="bound" class="_noSelect" :class="[$style.root, { [$style.animation]: animation, [$style.cat]: user.isCat, [$style.square]: squareAvatars }]" :style="{ color }" :title="acct(user)" @click="onClick"> <MkImgWithBlurhash :class="$style.inner" :src="url" :hash="user?.avatarBlurhash" :cover="true" :onlyAvgColor="true"/> @@ -23,21 +28,21 @@ <script lang="ts" setup> import { watch } from 'vue'; -import * as misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; import MkImgWithBlurhash from '../MkImgWithBlurhash.vue'; import MkA from './MkA.vue'; -import { getStaticImageUrl } from '@/scripts/media-proxy'; -import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash'; -import { acct, userPage } from '@/filters/user'; +import { getStaticImageUrl } from '@/scripts/media-proxy.js'; +import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash.js'; +import { acct, userPage } from '@/filters/user.js'; import MkUserOnlineIndicator from '@/components/MkUserOnlineIndicator.vue'; -import { defaultStore } from '@/store'; +import { defaultStore } from '@/store.js'; const animation = $ref(defaultStore.state.animation); const squareAvatars = $ref(defaultStore.state.squareAvatars); const useBlurEffect = $ref(defaultStore.state.useBlurEffect); const props = withDefaults(defineProps<{ - user: misskey.entities.User; + user: Misskey.entities.User; target?: string | null; link?: boolean; preview?: boolean; diff --git a/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts b/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts index ce985bc59f..7df49a2066 100644 --- a/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts +++ b/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; import MkCondensedLine from './MkCondensedLine.vue'; diff --git a/packages/frontend/src/components/global/MkCondensedLine.vue b/packages/frontend/src/components/global/MkCondensedLine.vue index 4b2e8e4750..ef1c931bc3 100644 --- a/packages/frontend/src/components/global/MkCondensedLine.vue +++ b/packages/frontend/src/components/global/MkCondensedLine.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <span :class="$style.container"> <span ref="content" :class="$style.content"> diff --git a/packages/frontend/src/components/global/MkCustomEmoji.stories.impl.ts b/packages/frontend/src/components/global/MkCustomEmoji.stories.impl.ts index 36ab85b579..f50217b70d 100644 --- a/packages/frontend/src/components/global/MkCustomEmoji.stories.impl.ts +++ b/packages/frontend/src/components/global/MkCustomEmoji.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; import MkCustomEmoji from './MkCustomEmoji.vue'; diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue index e7af472682..063b122f8b 100644 --- a/packages/frontend/src/components/global/MkCustomEmoji.vue +++ b/packages/frontend/src/components/global/MkCustomEmoji.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <span v-if="errored">:{{ customEmojiName }}:</span> <img v-else :class="[$style.root, { [$style.normal]: normal, [$style.noStyle]: noStyle }]" :src="url" :alt="alt" :title="alt" decoding="async" @error="errored = true" @load="errored = false"/> @@ -5,9 +10,9 @@ <script lang="ts" setup> import { computed } from 'vue'; -import { getProxiedImageUrl, getStaticImageUrl } from '@/scripts/media-proxy'; -import { defaultStore } from '@/store'; -import { customEmojisMap } from '@/custom-emojis'; +import { getProxiedImageUrl, getStaticImageUrl } from '@/scripts/media-proxy.js'; +import { defaultStore } from '@/store.js'; +import { customEmojisMap } from '@/custom-emojis.js'; const props = defineProps<{ name: string; diff --git a/packages/frontend/src/components/global/MkEllipsis.stories.impl.ts b/packages/frontend/src/components/global/MkEllipsis.stories.impl.ts index 65405a9bc8..32deaae8e2 100644 --- a/packages/frontend/src/components/global/MkEllipsis.stories.impl.ts +++ b/packages/frontend/src/components/global/MkEllipsis.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; import isChromatic from 'chromatic/isChromatic'; diff --git a/packages/frontend/src/components/global/MkEllipsis.vue b/packages/frontend/src/components/global/MkEllipsis.vue index c8f6cd3394..5cc07f7040 100644 --- a/packages/frontend/src/components/global/MkEllipsis.vue +++ b/packages/frontend/src/components/global/MkEllipsis.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <span :class="[$style.root, { [$style.static]: static }]"> <span :class="$style.dot">.</span><span :class="$style.dot">.</span><span :class="$style.dot">.</span> diff --git a/packages/frontend/src/components/global/MkEmoji.stories.impl.ts b/packages/frontend/src/components/global/MkEmoji.stories.impl.ts index f9900375f7..c8beec7e8f 100644 --- a/packages/frontend/src/components/global/MkEmoji.stories.impl.ts +++ b/packages/frontend/src/components/global/MkEmoji.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; import MkEmoji from './MkEmoji.vue'; diff --git a/packages/frontend/src/components/global/MkEmoji.vue b/packages/frontend/src/components/global/MkEmoji.vue index 1b0d34a445..e06549a891 100644 --- a/packages/frontend/src/components/global/MkEmoji.vue +++ b/packages/frontend/src/components/global/MkEmoji.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <img v-if="!useOsNativeEmojis" :class="$style.root" :src="url" :alt="props.emoji" decoding="async" @pointerenter="computeTitle"/> <span v-else-if="useOsNativeEmojis" :alt="props.emoji" @pointerenter="computeTitle">{{ props.emoji }}</span> @@ -6,9 +11,9 @@ <script lang="ts" setup> import { computed } from 'vue'; -import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@/scripts/emoji-base'; -import { defaultStore } from '@/store'; -import { getEmojiName } from '@/scripts/emojilist'; +import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@/scripts/emoji-base.js'; +import { defaultStore } from '@/store.js'; +import { getEmojiName } from '@/scripts/emojilist.js'; const props = defineProps<{ emoji: string; diff --git a/packages/frontend/src/components/global/MkError.stories.impl.ts b/packages/frontend/src/components/global/MkError.stories.impl.ts index 8252a4d76e..cf0a1dbb5f 100644 --- a/packages/frontend/src/components/global/MkError.stories.impl.ts +++ b/packages/frontend/src/components/global/MkError.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { action } from '@storybook/addon-actions'; import { expect } from '@storybook/jest'; diff --git a/packages/frontend/src/components/global/MkError.stories.meta.ts b/packages/frontend/src/components/global/MkError.stories.meta.ts index 51d763ada7..a3955c5786 100644 --- a/packages/frontend/src/components/global/MkError.stories.meta.ts +++ b/packages/frontend/src/components/global/MkError.stories.meta.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + export const argTypes = { retry: { action: 'retry', diff --git a/packages/frontend/src/components/global/MkError.vue b/packages/frontend/src/components/global/MkError.vue index 503e00387c..7181ae61a1 100644 --- a/packages/frontend/src/components/global/MkError.vue +++ b/packages/frontend/src/components/global/MkError.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" appear> <div :class="$style.root"> @@ -10,9 +15,9 @@ <script lang="ts" setup> import MkButton from '@/components/MkButton.vue'; -import { i18n } from '@/i18n'; -import { defaultStore } from '@/store'; -import { serverErrorImageUrl } from '@/instance'; +import { i18n } from '@/i18n.js'; +import { defaultStore } from '@/store.js'; +import { serverErrorImageUrl } from '@/instance.js'; const emit = defineEmits<{ (ev: 'retry'): void; diff --git a/packages/frontend/src/components/global/MkLoading.stories.impl.ts b/packages/frontend/src/components/global/MkLoading.stories.impl.ts index 9dcc0cdea1..9cedd68fd8 100644 --- a/packages/frontend/src/components/global/MkLoading.stories.impl.ts +++ b/packages/frontend/src/components/global/MkLoading.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; import isChromatic from 'chromatic/isChromatic'; diff --git a/packages/frontend/src/components/global/MkLoading.vue b/packages/frontend/src/components/global/MkLoading.vue index 4311f9fe8a..3f34e83f58 100644 --- a/packages/frontend/src/components/global/MkLoading.vue +++ b/packages/frontend/src/components/global/MkLoading.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="[$style.root, { [$style.inline]: inline, [$style.colored]: colored, [$style.mini]: mini, [$style.em]: em }]"> <div :class="$style.container"> diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.stories.impl.ts b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.stories.impl.ts index 685b3b8b8e..9d9febf693 100644 --- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.stories.impl.ts +++ b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; import { within } from '@storybook/testing-library'; diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts index 1c417991e0..2ae3fc89c8 100644 --- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts +++ b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + import { VNode, h } from 'vue'; import * as mfm from 'mfm-js'; import * as Misskey from 'misskey-js'; @@ -10,8 +15,8 @@ import MkCode from '@/components/MkCode.vue'; import MkGoogle from '@/components/MkGoogle.vue'; import MkSparkle from '@/components/MkSparkle.vue'; import MkA from '@/components/global/MkA.vue'; -import { host } from '@/config'; -import { defaultStore } from '@/store'; +import { host } from '@/config.js'; +import { defaultStore } from '@/store.js'; const QUOTE_STYLE = ` display: block; @@ -170,8 +175,13 @@ export default function(props: { }, genEl(token.children, scale)); } case 'rainbow': { + if (!useAnim) { + return h('span', { + class: '_mfm_rainbow_fallback_', + }, genEl(token.children, scale)); + } const speed = validTime(token.props.args.speed) ?? '1s'; - style = useAnim ? `animation: mfm-rainbow ${speed} linear infinite;` : ''; + style = `animation: mfm-rainbow ${speed} linear infinite;`; break; } case 'sparkle': { diff --git a/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts b/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts index 7485f3b82f..d3fd1bdc08 100644 --- a/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts +++ b/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { waitFor } from '@storybook/testing-library'; import { StoryObj } from '@storybook/vue3'; diff --git a/packages/frontend/src/components/global/MkPageHeader.tabs.stories.impl.ts b/packages/frontend/src/components/global/MkPageHeader.tabs.stories.impl.ts index 6d4460d593..130dde63af 100644 --- a/packages/frontend/src/components/global/MkPageHeader.tabs.stories.impl.ts +++ b/packages/frontend/src/components/global/MkPageHeader.tabs.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import MkPageHeader_tabs from './MkPageHeader.tabs.vue'; void MkPageHeader_tabs; diff --git a/packages/frontend/src/components/global/MkPageHeader.tabs.vue b/packages/frontend/src/components/global/MkPageHeader.tabs.vue index d71343baf9..e62967963f 100644 --- a/packages/frontend/src/components/global/MkPageHeader.tabs.vue +++ b/packages/frontend/src/components/global/MkPageHeader.tabs.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div ref="el" :class="$style.tabs" @wheel="onTabWheel"> <div :class="$style.tabsInner"> @@ -49,7 +54,7 @@ export type Tab = { <script lang="ts" setup> import { onMounted, onUnmounted, watch, nextTick, shallowRef } from 'vue'; -import { defaultStore } from '@/store'; +import { defaultStore } from '@/store.js'; const props = withDefaults(defineProps<{ tabs?: Tab[]; diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue index 0a21d39bca..ef8bfbbbfc 100644 --- a/packages/frontend/src/components/global/MkPageHeader.vue +++ b/packages/frontend/src/components/global/MkPageHeader.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div v-if="show" ref="el" :class="[$style.root]" :style="{ background: bg }"> <div :class="[$style.upper, { [$style.slim]: narrow, [$style.thin]: thin_ }]"> @@ -39,10 +44,10 @@ import { onMounted, onUnmounted, ref, inject } from 'vue'; import tinycolor from 'tinycolor2'; import XTabs, { Tab } from './MkPageHeader.tabs.vue'; -import { scrollToTop } from '@/scripts/scroll'; +import { scrollToTop } from '@/scripts/scroll.js'; import { globalEvents } from '@/events'; -import { injectPageMetadata } from '@/scripts/page-metadata'; -import { $i, openAccountMenu as openAccountMenu_ } from '@/account'; +import { injectPageMetadata } from '@/scripts/page-metadata.js'; +import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js'; const props = withDefaults(defineProps<{ tabs?: Tab[]; diff --git a/packages/frontend/src/components/global/MkSpacer.vue b/packages/frontend/src/components/global/MkSpacer.vue index ba7c0400c7..a384e06f77 100644 --- a/packages/frontend/src/components/global/MkSpacer.vue +++ b/packages/frontend/src/components/global/MkSpacer.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div :class="[$style.root, { [$style.rootMin]: forceSpacerMin }]"> <div :class="$style.content"> @@ -8,7 +13,7 @@ <script lang="ts" setup> import { inject } from 'vue'; -import { deviceKind } from '@/scripts/device-kind'; +import { deviceKind } from '@/scripts/device-kind.js'; const props = withDefaults(defineProps<{ contentMax?: number | null; diff --git a/packages/frontend/src/components/global/MkStickyContainer.stories.impl.ts b/packages/frontend/src/components/global/MkStickyContainer.stories.impl.ts index 97b8cc0c5b..16c62ce03d 100644 --- a/packages/frontend/src/components/global/MkStickyContainer.stories.impl.ts +++ b/packages/frontend/src/components/global/MkStickyContainer.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import MkStickyContainer from './MkStickyContainer.vue'; void MkStickyContainer; diff --git a/packages/frontend/src/components/global/MkStickyContainer.vue b/packages/frontend/src/components/global/MkStickyContainer.vue index e5dba54b4e..8e9bff11d1 100644 --- a/packages/frontend/src/components/global/MkStickyContainer.vue +++ b/packages/frontend/src/components/global/MkStickyContainer.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div ref="rootEl"> <div ref="headerEl"> diff --git a/packages/frontend/src/components/global/MkTime.stories.impl.ts b/packages/frontend/src/components/global/MkTime.stories.impl.ts index b72601b1ff..0eeefa4859 100644 --- a/packages/frontend/src/components/global/MkTime.stories.impl.ts +++ b/packages/frontend/src/components/global/MkTime.stories.impl.ts @@ -1,9 +1,14 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { expect } from '@storybook/jest'; import { StoryObj } from '@storybook/vue3'; import MkTime from './MkTime.vue'; -import { i18n } from '@/i18n'; -import { dateTimeFormat } from '@/scripts/intl-const'; +import { i18n } from '@/i18n.js'; +import { dateTimeFormat } from '@/scripts/intl-const.js'; const now = new Date('2023-04-01T00:00:00.000Z'); const future = new Date(8640000000000000); const oneHourAgo = new Date(now.getTime() - 3600000); diff --git a/packages/frontend/src/components/global/MkTime.vue b/packages/frontend/src/components/global/MkTime.vue index 9b02f989b4..d06aa036e7 100644 --- a/packages/frontend/src/components/global/MkTime.vue +++ b/packages/frontend/src/components/global/MkTime.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <time :title="absolute"> <template v-if="invalid">{{ i18n.ts._ago.invalid }}</template> @@ -10,8 +15,8 @@ <script lang="ts" setup> import isChromatic from 'chromatic/isChromatic'; import { onMounted, onUnmounted } from 'vue'; -import { i18n } from '@/i18n'; -import { dateTimeFormat } from '@/scripts/intl-const'; +import { i18n } from '@/i18n.js'; +import { dateTimeFormat } from '@/scripts/intl-const.js'; const props = withDefaults(defineProps<{ time: Date | string | number | null; diff --git a/packages/frontend/src/components/global/MkUrl.stories.impl.ts b/packages/frontend/src/components/global/MkUrl.stories.impl.ts index c5875d4779..84ac13f95a 100644 --- a/packages/frontend/src/components/global/MkUrl.stories.impl.ts +++ b/packages/frontend/src/components/global/MkUrl.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { expect } from '@storybook/jest'; import { userEvent, waitFor, within } from '@storybook/testing-library'; diff --git a/packages/frontend/src/components/global/MkUrl.vue b/packages/frontend/src/components/global/MkUrl.vue index c1efd9a06b..a8832cde01 100644 --- a/packages/frontend/src/components/global/MkUrl.vue +++ b/packages/frontend/src/components/global/MkUrl.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +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" :target="target" @@ -21,10 +26,10 @@ <script lang="ts" setup> import { defineAsyncComponent, ref } from 'vue'; import { toUnicode as decodePunycode } from 'punycode/'; -import { url as local } from '@/config'; -import * as os from '@/os'; -import { useTooltip } from '@/scripts/use-tooltip'; -import { safeURIDecode } from '@/scripts/safe-uri-decode'; +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'; const props = defineProps<{ url: string; diff --git a/packages/frontend/src/components/global/MkUserName.stories.impl.ts b/packages/frontend/src/components/global/MkUserName.stories.impl.ts index fa4f0f3b72..8c24a4819f 100644 --- a/packages/frontend/src/components/global/MkUserName.stories.impl.ts +++ b/packages/frontend/src/components/global/MkUserName.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { expect } from '@storybook/jest'; import { userEvent, within } from '@storybook/testing-library'; diff --git a/packages/frontend/src/components/global/MkUserName.vue b/packages/frontend/src/components/global/MkUserName.vue index c9e85c5460..be283ea922 100644 --- a/packages/frontend/src/components/global/MkUserName.vue +++ b/packages/frontend/src/components/global/MkUserName.vue @@ -1,13 +1,18 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <Mfm :text="user.name ?? user.username" :author="user" :plain="true" :nowrap="nowrap" :emojiUrls="user.emojis"/> </template> <script lang="ts" setup> import { } from 'vue'; -import * as misskey from 'misskey-js'; +import * as Misskey from 'misskey-js'; const props = withDefaults(defineProps<{ - user: misskey.entities.User; + user: Misskey.entities.User; nowrap?: boolean; }>(), { nowrap: true, diff --git a/packages/frontend/src/components/global/RouterView.stories.impl.ts b/packages/frontend/src/components/global/RouterView.stories.impl.ts index 7910b8b3cb..2fe4c53e78 100644 --- a/packages/frontend/src/components/global/RouterView.stories.impl.ts +++ b/packages/frontend/src/components/global/RouterView.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import RouterView from './RouterView.vue'; void RouterView; diff --git a/packages/frontend/src/components/global/RouterView.vue b/packages/frontend/src/components/global/RouterView.vue index 5763c84e81..99f42f4fcb 100644 --- a/packages/frontend/src/components/global/RouterView.vue +++ b/packages/frontend/src/components/global/RouterView.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <KeepAlive :max="defaultStore.state.numberOfPageCache"> <Suspense :timeout="0"> @@ -13,7 +18,7 @@ <script lang="ts" setup> import { inject, onBeforeUnmount, provide } from 'vue'; import { Resolved, Router } from '@/nirax'; -import { defaultStore } from '@/store'; +import { defaultStore } from '@/store.js'; const props = defineProps<{ router?: Router; diff --git a/packages/frontend/src/components/global/i18n.ts b/packages/frontend/src/components/global/i18n.ts index 6706d08f2f..2f4d7edabd 100644 --- a/packages/frontend/src/components/global/i18n.ts +++ b/packages/frontend/src/components/global/i18n.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + import { h } from 'vue'; export default function(props: { src: string; tag?: string; textTag?: string; }, { slots }) { diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts index ee2a2bc7bd..48af4754d7 100644 --- a/packages/frontend/src/components/index.ts +++ b/packages/frontend/src/components/index.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + import { App } from 'vue'; import Mfm from './global/MkMisskeyFlavoredMarkdown.ts'; diff --git a/packages/frontend/src/components/page/block.type.ts b/packages/frontend/src/components/page/block.type.ts index 71249a8aff..cdd39339e6 100644 --- a/packages/frontend/src/components/page/block.type.ts +++ b/packages/frontend/src/components/page/block.type.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + export type BlockBase = { id: string; type: string; diff --git a/packages/frontend/src/components/page/page.block.vue b/packages/frontend/src/components/page/page.block.vue index 2bf3d12daa..c039a1c7b7 100644 --- a/packages/frontend/src/components/page/page.block.vue +++ b/packages/frontend/src/components/page/page.block.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <component :is="getComponent(block.type)" :key="block.id" :page="page" :block="block" :h="h"/> </template> diff --git a/packages/frontend/src/components/page/page.image.vue b/packages/frontend/src/components/page/page.image.vue index 2edcfb8b1a..80a75f02a5 100644 --- a/packages/frontend/src/components/page/page.image.vue +++ b/packages/frontend/src/components/page/page.image.vue @@ -1,19 +1,28 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div> - <ImgWithBlurhash v-if="image" style="max-width: 100%;" :hash="image.blurhash" :src="image.url" :alt="image.comment" :title="image.comment" :width="image.properties.width" :height="image.properties.height" :cover="false"/> + <MediaImage + v-if="image" + :image="image" + :disableImageLink="true" + /> </div> </template> <script lang="ts" setup> -import { } from 'vue'; +import { ref } from 'vue'; import * as Misskey from 'misskey-js'; import { ImageBlock } from './block.type'; -import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue'; +import MediaImage from '@/components/MkMediaImage.vue'; const props = defineProps<{ block: ImageBlock, page: Misskey.entities.Page, }>(); -const image = props.page.attachedFiles.find(x => x.id === props.block.fileId); +const image = ref<Misskey.entities.DriveFile>(props.page.attachedFiles.find(x => x.id === props.block.fileId)); </script> diff --git a/packages/frontend/src/components/page/page.note.vue b/packages/frontend/src/components/page/page.note.vue index 7133a7f5a1..d052ce2c1f 100644 --- a/packages/frontend/src/components/page/page.note.vue +++ b/packages/frontend/src/components/page/page.note.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div style="margin: 1em 0;"> <MkNote v-if="note && !block.detailed" :key="note.id + ':normal'" v-model:note="note"/> @@ -11,7 +16,7 @@ import * as Misskey from 'misskey-js'; import { NoteBlock } from './block.type'; import MkNote from '@/components/MkNote.vue'; import MkNoteDetailed from '@/components/MkNoteDetailed.vue'; -import * as os from '@/os'; +import * as os from '@/os.js'; const props = defineProps<{ block: NoteBlock, diff --git a/packages/frontend/src/components/page/page.section.vue b/packages/frontend/src/components/page/page.section.vue index 83a16ae0a5..84d0399cf8 100644 --- a/packages/frontend/src/components/page/page.section.vue +++ b/packages/frontend/src/components/page/page.section.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <section> <component diff --git a/packages/frontend/src/components/page/page.text.vue b/packages/frontend/src/components/page/page.text.vue index 48ce4b0e1e..35021be95e 100644 --- a/packages/frontend/src/components/page/page.text.vue +++ b/packages/frontend/src/components/page/page.text.vue @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> <div class="_gaps"> <Mfm :text="block.text" :isNote="false" :i="$i"/> @@ -10,8 +15,8 @@ import { defineAsyncComponent } from 'vue'; import * as mfm from 'mfm-js'; import * as Misskey from 'misskey-js'; import { TextBlock } from './block.type'; -import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm'; -import { $i } from '@/account'; +import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js'; +import { $i } from '@/account.js'; const MkUrlPreview = defineAsyncComponent(() => import('@/components/MkUrlPreview.vue')); diff --git a/packages/frontend/src/components/page/page.vue b/packages/frontend/src/components/page/page.vue index c2c2693224..ab37ca69ad 100644 --- a/packages/frontend/src/components/page/page.vue +++ b/packages/frontend/src/components/page/page.vue @@ -1,5 +1,10 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + <template> -<div :class="{ [$style.center]: page.alignCenter, [$style.serif]: page.font === 'serif' }"> +<div :class="{ [$style.center]: page.alignCenter, [$style.serif]: page.font === 'serif' }" class="_gaps_s"> <XBlock v-for="child in page.content" :key="child.id" :page="page" :block="child" :h="2"/> </div> </template> |