diff options
| author | Hazelnoot <acomputerdog@gmail.com> | 2025-03-30 01:58:17 -0400 |
|---|---|---|
| committer | Hazelnoot <acomputerdog@gmail.com> | 2025-03-30 01:58:17 -0400 |
| commit | 4ae26e6e185e52ac186ac10ccd4eda7718bf6e26 (patch) | |
| tree | 8d556072f3876f0bfbab9d24e7cd209ca6bd091a /packages/frontend/src/components | |
| parent | restore following feed deck UI (diff) | |
| parent | New Crowdin updates (#15721) (diff) | |
| download | sharkey-4ae26e6e185e52ac186ac10ccd4eda7718bf6e26.tar.gz sharkey-4ae26e6e185e52ac186ac10ccd4eda7718bf6e26.tar.bz2 sharkey-4ae26e6e185e52ac186ac10ccd4eda7718bf6e26.zip | |
Merge branch 'misskey-develop' into merge/2025-03-24
Diffstat (limited to 'packages/frontend/src/components')
23 files changed, 217 insertions, 200 deletions
diff --git a/packages/frontend/src/components/MkChannelFollowButton.stories.impl.ts b/packages/frontend/src/components/MkChannelFollowButton.stories.impl.ts index a42e80c27a..4304c2e2b7 100644 --- a/packages/frontend/src/components/MkChannelFollowButton.stories.impl.ts +++ b/packages/frontend/src/components/MkChannelFollowButton.stories.impl.ts @@ -2,20 +2,18 @@ * SPDX-FileCopyrightText: syuilo and misskey-project * SPDX-License-Identifier: AGPL-3.0-only */ - -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable import/no-default-export */ -import type { StoryObj } from '@storybook/vue3'; + import { HttpResponse, http } from 'msw'; import { action } from '@storybook/addon-actions'; import { expect, userEvent, within } from '@storybook/test'; import { channel } from '../../.storybook/fakes.js'; import { commonHandlers } from '../../.storybook/mocks.js'; import MkChannelFollowButton from './MkChannelFollowButton.vue'; +import type { StoryObj } from '@storybook/vue3'; import { i18n } from '@/i18n.js'; function sleep(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise(resolve => window.setTimeout(resolve, ms)); } export const Default = { diff --git a/packages/frontend/src/components/MkClickerGame.stories.impl.ts b/packages/frontend/src/components/MkClickerGame.stories.impl.ts index eb7e61f294..6e1eb13d61 100644 --- a/packages/frontend/src/components/MkClickerGame.stories.impl.ts +++ b/packages/frontend/src/components/MkClickerGame.stories.impl.ts @@ -2,18 +2,16 @@ * SPDX-FileCopyrightText: syuilo and misskey-project * SPDX-License-Identifier: AGPL-3.0-only */ - -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable import/no-default-export */ -import type { StoryObj } from '@storybook/vue3'; + import { HttpResponse, http } from 'msw'; import { action } from '@storybook/addon-actions'; import { expect, userEvent, within } from '@storybook/test'; import { commonHandlers } from '../../.storybook/mocks.js'; import MkClickerGame from './MkClickerGame.vue'; +import type { StoryObj } from '@storybook/vue3'; function sleep(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise(resolve => window.setTimeout(resolve, ms)); } export const Default = { diff --git a/packages/frontend/src/components/MkDrive.vue b/packages/frontend/src/components/MkDrive.vue index 730dd0d55e..0aeb65d69c 100644 --- a/packages/frontend/src/components/MkDrive.vue +++ b/packages/frontend/src/components/MkDrive.vue @@ -4,41 +4,45 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<div :class="$style.root"> - <nav :class="$style.nav"> - <div :class="$style.navPath" @contextmenu.prevent.stop="() => {}"> - <XNavFolder - :class="[$style.navPathItem, { [$style.navCurrent]: folder == null }]" - :parentFolder="folder" - @move="move" - @upload="upload" - @removeFile="removeFile" - @removeFolder="removeFolder" - /> - <template v-for="f in hierarchyFolders"> - <span :class="[$style.navPathItem, $style.navSeparator]"><i class="ti ti-chevron-right"></i></span> +<MkStickyContainer> + <template #header> + <nav :class="$style.nav"> + <div :class="$style.navPath" @contextmenu.prevent.stop="() => {}"> <XNavFolder - :folder="f" + :class="[$style.navPathItem, { [$style.navCurrent]: folder == null }]" :parentFolder="folder" - :class="[$style.navPathItem]" @move="move" @upload="upload" @removeFile="removeFile" @removeFolder="removeFolder" /> - </template> - <span v-if="folder != null" :class="[$style.navPathItem, $style.navSeparator]"><i class="ti ti-chevron-right"></i></span> - <span v-if="folder != null" :class="[$style.navPathItem, $style.navCurrent]">{{ folder.name }}</span> - </div> - <div :class="$style.navMenu"> - <!-- "Search drive via alt text or file names" --> - <MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" :placeholder="i18n.ts.driveSearchbarPlaceholder" @enter="fetch"> - <template #prefix><i class="ph-magnifying-glass ph-bold ph-lg"></i></template> - </MkInput> - + <template v-for="f in hierarchyFolders"> + <span :class="[$style.navPathItem, $style.navSeparator]"><i class="ti ti-chevron-right"></i></span> + <XNavFolder + :folder="f" + :parentFolder="folder" + :class="[$style.navPathItem]" + @move="move" + @upload="upload" + @removeFile="removeFile" + @removeFolder="removeFolder" + /> + </template> + <span v-if="folder != null" :class="[$style.navPathItem, $style.navSeparator]"><i class="ti ti-chevron-right"></i></span> + <span v-if="folder != null" :class="[$style.navPathItem, $style.navCurrent]">{{ folder.name }}</span> + </div> <button class="_button" :class="$style.navMenu" @click="showMenu"><i class="ti ti-dots"></i></button> - </div> - </nav> + <div :class="$style.navMenu"> + <!-- "Search drive via alt text or file names" --> + <MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" :placeholder="i18n.ts.driveSearchbarPlaceholder" @enter="fetch"> + <template #prefix><i class="ph-magnifying-glass ph-bold ph-lg"></i></template> + </MkInput> + + <button class="_button" :class="$style.navMenu" @click="showMenu"><i class="ti ti-dots"></i></button> + </div> + </nav> + </template> + <div ref="main" :class="[$style.main, { [$style.uploading]: uploadings.length > 0, [$style.fetching]: fetching }]" @@ -98,8 +102,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkLoading v-if="fetching"/> </div> <div v-if="draghover" :class="$style.dropzone"></div> - <input ref="fileInput" style="display: none;" type="file" accept="*/*" multiple tabindex="-1" @change="onChangeFileInput"/> -</div> +</MkStickyContainer> </template> <script lang="ts" setup> @@ -118,6 +121,7 @@ import { i18n } from '@/i18n.js'; import { uploadFile, uploads } from '@/utility/upload.js'; import { claimAchievement } from '@/utility/achievements.js'; import { prefer } from '@/preferences.js'; +import { chooseFileFromPc } from '@/utility/select-file.js'; const searchQuery = ref(''); @@ -140,7 +144,6 @@ const emit = defineEmits<{ }>(); const loadMoreFiles = useTemplateRef('loadMoreFiles'); -const fileInput = useTemplateRef('fileInput'); const folder = ref<Misskey.entities.DriveFolder | null>(null); const files = ref<Misskey.entities.DriveFile[]>([]); @@ -152,7 +155,6 @@ const selectedFiles = ref<Misskey.entities.DriveFile[]>([]); const selectedFolders = ref<Misskey.entities.DriveFolder[]>([]); const uploadings = uploads; const connection = useStream().useChannel('drive'); -const keepOriginal = ref<boolean>(prefer.s.keepOriginalUploading); // 外部渡しが多いので$refは使わないほうがよい // ドロップされようとしているか const draghover = ref(false); @@ -314,10 +316,6 @@ function onDrop(ev: DragEvent) { //#endregion } -function selectLocalFile() { - fileInput.value?.click(); -} - function urlUpload() { os.inputText({ title: i18n.ts.uploadFromUrl, @@ -393,15 +391,8 @@ function deleteFolder(folderToDelete: Misskey.entities.DriveFolder) { }); } -function onChangeFileInput() { - if (!fileInput.value?.files) return; - for (const file of Array.from(fileInput.value.files)) { - upload(file, folder.value); - } -} - -function upload(file: File, folderToUpload?: Misskey.entities.DriveFolder | null) { - uploadFile(file, (folderToUpload && typeof folderToUpload === 'object') ? folderToUpload.id : null, undefined, keepOriginal.value).then(res => { +function upload(file: File, folderToUpload?: Misskey.entities.DriveFolder | null, keepOriginal?: boolean) { + uploadFile(file, (folderToUpload && typeof folderToUpload === 'object') ? folderToUpload.id : null, undefined, keepOriginal).then(res => { addFile(res, true); }); } @@ -644,16 +635,20 @@ function getMenu() { const menu: MenuItem[] = []; menu.push({ - type: 'switch', - text: i18n.ts.keepOriginalUploading, - ref: keepOriginal, - }, { type: 'divider' }, { text: i18n.ts.addFile, type: 'label', }, { + text: i18n.ts.upload + ' (' + i18n.ts.compress + ')', + icon: 'ti ti-upload', + action: () => { + chooseFileFromPc(true, { keepOriginal: false }); + }, + }, { text: i18n.ts.upload, icon: 'ti ti-upload', - action: () => { selectLocalFile(); }, + action: () => { + chooseFileFromPc(true, { keepOriginal: true }); + }, }, { text: i18n.ts.fromUrl, icon: 'ti ti-link', @@ -765,22 +760,17 @@ onBeforeUnmount(() => { </script> <style lang="scss" module> -.root { - display: flex; - flex-direction: column; - height: 100%; -} - .nav { display: flex; - z-index: 2; width: 100%; padding: 0 8px; box-sizing: border-box; overflow: auto; font-size: 0.9em; - box-shadow: 0 1px 0 var(--MI_THEME-divider); - user-select: none; + background: color(from var(--MI_THEME-bg) srgb r g b / 0.75); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); + border-bottom: solid 0.5px var(--MI_THEME-divider); } .navPath { diff --git a/packages/frontend/src/components/MkEmojiPickerDialog.vue b/packages/frontend/src/components/MkEmojiPickerDialog.vue index 5c4400eaf8..118eb8ecc0 100644 --- a/packages/frontend/src/components/MkEmojiPickerDialog.vue +++ b/packages/frontend/src/components/MkEmojiPickerDialog.vue @@ -79,7 +79,7 @@ function opening() { picker.value?.focus(); // 何故かちょっと待たないとフォーカスされない - setTimeout(() => { + window.setTimeout(() => { picker.value?.focus(); }, 10); } diff --git a/packages/frontend/src/components/MkHorizontalSwipe.vue b/packages/frontend/src/components/MkHorizontalSwipe.vue index bc63bef0b6..1d0ffaea11 100644 --- a/packages/frontend/src/components/MkHorizontalSwipe.vue +++ b/packages/frontend/src/components/MkHorizontalSwipe.vue @@ -100,7 +100,7 @@ function touchMove(event: TouchEvent) { pullDistance.value = 0; isSwiping.value = false; - setTimeout(() => { + window.setTimeout(() => { isSwipingForClass.value = false; }, 400); diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue index a60e100969..8db3924fbd 100644 --- a/packages/frontend/src/components/MkMediaVideo.vue +++ b/packages/frontend/src/components/MkMediaVideo.vue @@ -342,7 +342,7 @@ const bufferedDataRatio = computed(() => { // MediaControl Events function onMouseOver() { if (controlStateTimer) { - clearTimeout(controlStateTimer); + window.clearTimeout(controlStateTimer); } isHoverring.value = true; } diff --git a/packages/frontend/src/components/MkModal.vue b/packages/frontend/src/components/MkModal.vue index b5c93df4ed..3bcf835ec9 100644 --- a/packages/frontend/src/components/MkModal.vue +++ b/packages/frontend/src/components/MkModal.vue @@ -50,6 +50,7 @@ import { deviceKind } from '@/utility/device-kind.js'; import { focusTrap } from '@/utility/focus-trap.js'; import { focusParent } from '@/utility/focus.js'; import { prefer } from '@/preferences.js'; +import { DI } from '@/di.js'; function getFixedContainer(el: Element | null): Element | null { if (el == null || el.tagName === 'BODY') return null; @@ -94,7 +95,7 @@ const emit = defineEmits<{ (ev: 'closed'): void; }>(); -provide('modal', true); +provide(DI.inModal, true); const maxHeight = ref<number>(); const fixed = ref(false); diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 2e2693d319..5fcb91b338 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -19,8 +19,6 @@ SPDX-License-Identifier: AGPL-3.0-only </div> <MkNoteSub v-if="appearNote.reply" v-show="!renoteCollapsed && !inReplyToCollapsed" :note="appearNote.reply" :class="$style.replyTo"/> <div v-if="pinned" :class="$style.tip"><i class="ti ti-pin"></i> {{ i18n.ts.pinnedNote }}</div> - <!--<div v-if="appearNote._prId_" class="tip"><i class="ti ti-speakerphone"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="ti ti-x"></i></button></div>--> - <!--<div v-if="appearNote._featuredId_" class="tip"><i class="ti ti-bolt"></i> {{ i18n.ts.featured }}</div>--> <div v-if="isRenote" :class="$style.renote"> <div v-if="note.channel" :class="$style.colorBar" :style="{ background: note.channel.color }"></div> <MkAvatar :class="$style.renoteAvatar" :user="note.user" link preview/> @@ -53,7 +51,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> <article v-else :class="$style.article" @contextmenu.stop="onContextmenu"> <div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div> - <MkAvatar :class="$style.avatar" :user="appearNote.user" :link="!mock" :preview="!mock"/> + <MkAvatar :class="[$style.avatar, prefer.s.useStickyIcons ? $style.useSticky : null]" :user="appearNote.user" :link="!mock" :preview="!mock"/> <div :class="[$style.main, { [$style.clickToOpen]: defaultStore.state.clickToOpen }]" @click.stop="defaultStore.state.clickToOpen ? noteclick(appearNote.id) : undefined"> <MkNoteHeader :note="appearNote" :mini="true" @click.stop/> <MkInstanceTicker v-if="showTicker" :host="appearNote.user.host" :instance="appearNote.user.instance"/> @@ -922,7 +920,6 @@ function emitUpdReaction(emoji: string, delta: number) { <style lang="scss" module> .root { position: relative; - transition: box-shadow 0.1s ease; font-size: 1.05em; overflow: clip; contain: content; @@ -993,6 +990,8 @@ function emitUpdReaction(emoji: string, delta: number) { } .skipRender { + // TODO: これが有効だとTransitionGroupでnoteを追加するときに一瞬がくっとなってしまうのをどうにかしたい + // Transitionが完了するのを待ってからskipRenderを付与すれば解決しそうだけどパフォーマンス的な影響が不明 content-visibility: auto; contain-intrinsic-size: 0 150px; } @@ -1127,9 +1126,12 @@ function emitUpdReaction(emoji: string, delta: number) { margin: 0 14px 0 0; width: var(--MI-avatar); height: var(--MI-avatar); - position: sticky !important; - top: calc(22px + var(--MI-stickyTop, 0px)); - left: 0; + + &.useSticky { + position: sticky !important; + top: calc(22px + var(--MI-stickyTop, 0px)); + left: 0; + } } .main { @@ -1324,7 +1326,10 @@ function emitUpdReaction(emoji: string, delta: number) { margin: 0 10px 0 0; width: 46px; height: 46px; - top: calc(14px + var(--MI-stickyTop, 0px)); + + &.useSticky { + top: calc(14px + var(--MI-stickyTop, 0px)); + } } } diff --git a/packages/frontend/src/components/MkNotes.vue b/packages/frontend/src/components/MkNotes.vue index 928a143a72..bc72351552 100644 --- a/packages/frontend/src/components/MkNotes.vue +++ b/packages/frontend/src/components/MkNotes.vue @@ -13,38 +13,34 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <template #default="{ items: notes }"> - <div :class="[$style.root, { [$style.noGap]: noGap }]"> - <MkDateSeparatedList - ref="notes" - v-slot="{ item: note }" - :items="notes" - :direction="pagination.reversed ? 'up' : 'down'" - :reversed="pagination.reversed" - :noGap="noGap" - :ad="true" - :class="$style.notes" - > - <MkNote :key="note._featuredId_ || note._prId_ || note.id" :class="$style.note" :note="note" :withHardMute="true"/> - </MkDateSeparatedList> - </div> + <component + :is="prefer.s.animation ? TransitionGroup : 'div'" :class="[$style.root, { [$style.noGap]: noGap, '_gaps': !noGap }]" + :enterActiveClass="$style.transition_x_enterActive" + :leaveActiveClass="$style.transition_x_leaveActive" + :enterFromClass="$style.transition_x_enterFrom" + :leaveToClass="$style.transition_x_leaveTo" + :moveClass=" $style.transition_x_move" + tag="div" + > + <template v-for="(note, i) in notes" :key="note.id"> + <DynamicNote :class="$style.note" :note="note" :withHardMute="true"/> + <div v-if="note._shouldInsertAd_" :class="$style.ad"> + <MkAd :preferForms="['horizontal', 'horizontal-big']"/> + </div> + </template> + </component> </template> </MkPagination> </template> <script lang="ts" setup> -import { defineAsyncComponent, useTemplateRef } from 'vue'; +import { useTemplateRef, TransitionGroup } from 'vue'; import type { Paging } from '@/components/MkPagination.vue'; -import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue'; +import DynamicNote from '@/components/DynamicNote.vue'; import MkPagination from '@/components/MkPagination.vue'; import { i18n } from '@/i18n.js'; import { infoImageUrl } from '@/instance.js'; -import { defaultStore } from '@/store.js'; - -const MkNote = defineAsyncComponent(() => - (defaultStore.state.noteDesign === 'misskey') ? import('@/components/MkNote.vue') : - (defaultStore.state.noteDesign === 'sharkey') ? import('@/components/SkNote.vue') : - null -); +import { prefer } from '@/preferences.js'; const props = defineProps<{ pagination: Paging; @@ -60,24 +56,49 @@ defineExpose({ </script> <style lang="scss" module> +.transition_x_move, +.transition_x_enterActive, +.transition_x_leaveActive { + transition: opacity 0.3s cubic-bezier(0,.5,.5,1), transform 0.3s cubic-bezier(0,.5,.5,1) !important; +} +.transition_x_enterFrom, +.transition_x_leaveTo { + opacity: 0; + transform: translateY(-50%); +} +.transition_x_leaveActive { + position: absolute; +} + .root { + container-type: inline-size; + &.noGap { - border-radius: var(--MI-radius); + background: var(--MI_THEME-panel); + + .note { + border-bottom: solid 0.5px var(--MI_THEME-divider); + } - > .notes { - background: color-mix(in srgb, var(--MI_THEME-panel) 65%, transparent); + .ad { + padding: 8px; + background-size: auto auto; + background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, var(--MI_THEME-bg) 8px, var(--MI_THEME-bg) 14px); + border-bottom: solid 0.5px var(--MI_THEME-divider); } } &:not(.noGap) { - > .notes { - background: var(--MI_THEME-bg); + background: var(--MI_THEME-bg); - .note { - background: color-mix(in srgb, var(--MI_THEME-panel) 65%, transparent); - border-radius: var(--MI-radius); - } + .note { + background: var(--MI_THEME-panel); + border-radius: var(--MI-radius); } } } + +.ad:empty { + display: none; +} </style> diff --git a/packages/frontend/src/components/MkNotifications.vue b/packages/frontend/src/components/MkNotifications.vue index 4a1377655f..453a6c83b3 100644 --- a/packages/frontend/src/components/MkNotifications.vue +++ b/packages/frontend/src/components/MkNotifications.vue @@ -14,34 +14,38 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <template #default="{ items: notifications }"> - <MkDateSeparatedList v-slot="{ item: notification }" :class="$style.list" :items="notifications" :noGap="true"> - <MkNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id + ':note'" :note="notification.note" :withHardMute="true"/> - <XNotification v-else :key="notification.id" :notification="notification" :withTime="true" :full="true" class="_panel"/> - </MkDateSeparatedList> + <component + :is="prefer.s.animation ? TransitionGroup : 'div'" :class="[$style.notifications]" + :enterActiveClass="$style.transition_x_enterActive" + :leaveActiveClass="$style.transition_x_leaveActive" + :enterFromClass="$style.transition_x_enterFrom" + :leaveToClass="$style.transition_x_leaveTo" + :moveClass=" $style.transition_x_move" + tag="div" + > + <template v-for="(notification, i) in notifications" :key="notification.id"> + <DynamicNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :class="$style.item" :note="notification.note" :withHardMute="true"/> + <XNotification v-else :class="$style.item" :notification="notification" :withTime="true" :full="true"/> + </template> + </component> </template> </MkPagination> </MkPullToRefresh> </template> <script lang="ts" setup> -import { defineAsyncComponent, onUnmounted, onDeactivated, onMounted, computed, useTemplateRef, onActivated } from 'vue'; +import { onUnmounted, onMounted, computed, useTemplateRef, TransitionGroup } from 'vue'; import * as Misskey from 'misskey-js'; import type { notificationTypes } from '@@/js/const.js'; import MkPagination from '@/components/MkPagination.vue'; import XNotification from '@/components/MkNotification.vue'; -import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue'; +import DynamicNote from '@/components/DynamicNote.vue'; import { useStream } from '@/stream.js'; import { i18n } from '@/i18n.js'; import { infoImageUrl } from '@/instance.js'; import MkPullToRefresh from '@/components/MkPullToRefresh.vue'; import { prefer } from '@/preferences.js'; -const MkNote = defineAsyncComponent(() => - (defaultStore.state.noteDesign === 'misskey') ? import('@/components/MkNote.vue') : - (defaultStore.state.noteDesign === 'sharkey') ? import('@/components/SkNote.vue') : - null -); - const props = defineProps<{ excludeTypes?: typeof notificationTypes[number][]; }>(); @@ -89,28 +93,36 @@ onMounted(() => { connection.on('notificationFlushed', reload); }); -onActivated(() => { - pagingComponent.value?.reload(); - connection = useStream().useChannel('main'); - connection.on('notification', onNotification); - connection.on('notificationFlushed', reload); -}); - onUnmounted(() => { if (connection) connection.dispose(); }); -onDeactivated(() => { - if (connection) connection.dispose(); -}); - defineExpose({ reload, }); </script> <style lang="scss" module> -.list { +.transition_x_move, +.transition_x_enterActive, +.transition_x_leaveActive { + transition: opacity 0.3s cubic-bezier(0,.5,.5,1), transform 0.3s cubic-bezier(0,.5,.5,1) !important; +} +.transition_x_enterFrom, +.transition_x_leaveTo { + opacity: 0; + transform: translateY(-50%); +} +.transition_x_leaveActive { + position: absolute; +} + +.notifications { + container-type: inline-size; background: var(--MI_THEME-panel); } + +.item { + border-bottom: solid 0.5px var(--MI_THEME-divider); +} </style> diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue index 71f7fc513b..ee128e5991 100644 --- a/packages/frontend/src/components/MkPagination.vue +++ b/packages/frontend/src/components/MkPagination.vue @@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkError v-else-if="error" @retry="init()"/> - <div v-else-if="empty" key="_empty_" class="empty"> + <div v-else-if="empty" key="_empty_"> <slot name="empty"> <div class="_fullinfo"> <img :src="infoImageUrl" draggable="false"/> @@ -29,14 +29,14 @@ SPDX-License-Identifier: AGPL-3.0-only <MkButton v-if="!moreFetching" v-appear="(enableInfiniteScroll && !props.disableAutoLoad) ? appearFetchMoreAhead : null" :class="$style.more" :wait="moreFetching" primary rounded @click="fetchMoreAhead"> {{ i18n.ts.loadMore }} </MkButton> - <MkLoading v-else class="loading"/> + <MkLoading v-else/> </div> <slot :items="Array.from(items.values())" :fetching="fetching || moreFetching"></slot> <div v-show="!pagination.reversed && more" key="_more_"> <MkButton v-if="!moreFetching" v-appear="(enableInfiniteScroll && !props.disableAutoLoad) ? appearFetchMore : null" :class="$style.more" :wait="moreFetching" primary rounded @click="fetchMore"> {{ i18n.ts.loadMore }} </MkButton> - <MkLoading v-else class="loading"/> + <MkLoading v-else/> </div> </div> </Transition> @@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, useTemplateRef, watch, type Ref } from 'vue'; import * as Misskey from 'misskey-js'; import { useDocumentVisibility } from '@@/js/use-document-visibility.js'; -import { onScrollTop, isHeadVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isTailVisible } from '@@/js/scroll.js'; +import { onScrollTop, isHeadVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scrollInContainer, isTailVisible } from '@@/js/scroll.js'; import type { ComputedRef } from 'vue'; import type { MisskeyEntity } from '@/types/date-separated-list.js'; import { misskeyApi } from '@/utility/misskey-api.js'; @@ -268,7 +268,7 @@ const fetchMore = async (): Promise<void> => { return nextTick(() => { if (scrollableElement.value) { - scroll(scrollableElement.value, { top: oldScroll + (scrollableElement.value.scrollHeight - oldHeight), behavior: 'instant' }); + scrollInContainer(scrollableElement.value, { top: oldScroll + (scrollableElement.value.scrollHeight - oldHeight), behavior: 'instant' }); } else { window.scroll({ top: oldScroll + (getBodyScrollHeight() - oldHeight), behavior: 'instant' }); } @@ -368,7 +368,7 @@ watch(visibility, () => { BACKGROUND_PAUSE_WAIT_SEC * 1000); } else { // 'visible' if (timerForSetPause) { - clearTimeout(timerForSetPause); + window.clearTimeout(timerForSetPause); timerForSetPause = null; } else { isPausingUpdate = false; @@ -464,11 +464,11 @@ onBeforeMount(() => { init().then(() => { if (props.pagination.reversed) { nextTick(() => { - setTimeout(toBottom, 800); + window.setTimeout(toBottom, 800); // scrollToBottomでmoreFetchingボタンが画面外まで出るまで // more = trueを遅らせる - setTimeout(() => { + window.setTimeout(() => { moreFetching.value = false; }, 2000); }); @@ -478,11 +478,11 @@ onBeforeMount(() => { onBeforeUnmount(() => { if (timerForSetPause) { - clearTimeout(timerForSetPause); + window.clearTimeout(timerForSetPause); timerForSetPause = null; } if (preventAppearFetchMoreTimer.value) { - clearTimeout(preventAppearFetchMoreTimer.value); + window.clearTimeout(preventAppearFetchMoreTimer.value); preventAppearFetchMoreTimer.value = null; } scrollObserver.value?.disconnect(); diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index 921829c41e..c70d0d5581 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -150,7 +150,7 @@ import { DI } from '@/di.js'; const $i = ensureSignin(); -const modal = inject('modal'); +const modal = inject(DI.inModal, false); const props = withDefaults(defineProps<PostFormProps & { fixed?: boolean; @@ -1431,7 +1431,7 @@ defineExpose({ padding: 0 24px; margin: 0; width: 100%; - font-size: 16px; + font-size: 110%; border: none; border-radius: 0; background: transparent; diff --git a/packages/frontend/src/components/MkPullToRefresh.vue b/packages/frontend/src/components/MkPullToRefresh.vue index 1fbf00d212..22ae563d13 100644 --- a/packages/frontend/src/components/MkPullToRefresh.vue +++ b/packages/frontend/src/components/MkPullToRefresh.vue @@ -16,9 +16,8 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </div> </div> - <div :class="{ [$style.slotClip]: isPullStart }"> - <slot/> - </div> + + <slot/> </div> </template> @@ -82,11 +81,11 @@ function moveBySystem(to: number): Promise<void> { return; } const startTime = Date.now(); - let intervalId = setInterval(() => { + let intervalId = window.setInterval(() => { const time = Date.now() - startTime; if (time > RELEASE_TRANSITION_DURATION) { pullDistance.value = to; - clearInterval(intervalId); + window.clearInterval(intervalId); r(); return; } @@ -261,8 +260,4 @@ defineExpose({ margin: 5px 0; } } - -.slotClip { - overflow-y: clip; -} </style> diff --git a/packages/frontend/src/components/MkReactionsViewer.vue b/packages/frontend/src/components/MkReactionsViewer.vue index cf0bb7b1f2..6b19f4a55c 100644 --- a/packages/frontend/src/components/MkReactionsViewer.vue +++ b/packages/frontend/src/components/MkReactionsViewer.vue @@ -4,22 +4,24 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<TransitionGroup - :enterActiveClass="prefer.s.animation ? $style.transition_x_enterActive : ''" - :leaveActiveClass="prefer.s.animation ? $style.transition_x_leaveActive : ''" - :enterFromClass="prefer.s.animation ? $style.transition_x_enterFrom : ''" - :leaveToClass="prefer.s.animation ? $style.transition_x_leaveTo : ''" - :moveClass="prefer.s.animation ? $style.transition_x_move : ''" +<component + :is="prefer.s.animation ? TransitionGroup : 'div'" + :enterActiveClass="$style.transition_x_enterActive" + :leaveActiveClass="$style.transition_x_leaveActive" + :enterFromClass="$style.transition_x_enterFrom" + :leaveToClass="$style.transition_x_leaveTo" + :moveClass="$style.transition_x_move" tag="div" :class="$style.root" > <XReaction v-for="[reaction, count] in reactions" :key="reaction" :reaction="reaction" :count="count" :isInitial="initialReactions.has(reaction)" :note="note" @reactionToggled="onMockToggleReaction"/> <slot v-if="hasMoreReactions" name="more"/> -</TransitionGroup> +</component> </template> <script lang="ts" setup> import * as Misskey from 'misskey-js'; import { inject, watch, ref } from 'vue'; +import { TransitionGroup } from 'vue'; import XReaction from '@/components/MkReactionsViewer.reaction.vue'; import { prefer } from '@/preferences.js'; import { DI } from '@/di.js'; diff --git a/packages/frontend/src/components/MkTooltip.vue b/packages/frontend/src/components/MkTooltip.vue index 955c24b6ef..16913386c1 100644 --- a/packages/frontend/src/components/MkTooltip.vue +++ b/packages/frontend/src/components/MkTooltip.vue @@ -9,7 +9,8 @@ SPDX-License-Identifier: AGPL-3.0-only :leaveActiveClass="prefer.s.animation ? $style.transition_tooltip_leaveActive : ''" :enterFromClass="prefer.s.animation ? $style.transition_tooltip_enterFrom : ''" :leaveToClass="prefer.s.animation ? $style.transition_tooltip_leaveTo : ''" - appear @afterLeave="emit('closed')" + appear :css="prefer.s.animation" + @afterLeave="emit('closed')" > <div v-show="showing" ref="el" :class="$style.root" class="_acrylic _shadow" :style="{ zIndex, maxWidth: maxWidth + 'px' }"> <slot> diff --git a/packages/frontend/src/components/global/MkAd.vue b/packages/frontend/src/components/global/MkAd.vue index b29bbb6392..525c47da45 100644 --- a/packages/frontend/src/components/global/MkAd.vue +++ b/packages/frontend/src/components/global/MkAd.vue @@ -36,7 +36,6 @@ SPDX-License-Identifier: AGPL-3.0-only <button class="_textButton" @click="toggleMenu">{{ i18n.ts._ad.back }}</button> </div> </div> -<div v-else></div> </template> <script lang="ts" setup> @@ -53,7 +52,7 @@ import { prefer } from '@/preferences.js'; type Ad = (typeof instance)['ads'][number]; const props = defineProps<{ - prefer: string[]; + preferForms: string[]; specify?: Ad; }>(); @@ -72,7 +71,7 @@ const choseAd = (): Ad | null => { ratio: 0, } : ad); - let ads = allAds.filter(ad => props.prefer.includes(ad.place)); + let ads = allAds.filter(ad => props.preferForms.includes(ad.place)); if (ads.length === 0) { ads = allAds.filter(ad => ad.place === 'square'); diff --git a/packages/frontend/src/components/global/MkEmoji.vue b/packages/frontend/src/components/global/MkEmoji.vue index f5323690d0..6abe1f30b6 100644 --- a/packages/frontend/src/components/global/MkEmoji.vue +++ b/packages/frontend/src/components/global/MkEmoji.vue @@ -25,7 +25,7 @@ const props = defineProps<{ menuReaction?: boolean; }>(); -const react = inject(DI.mfmEmojiReactCallback); +const react = inject(DI.mfmEmojiReactCallback, null); const char2path = prefer.s.emojiStyle === 'twemoji' ? char2twemojiFilePath : prefer.s.emojiStyle === 'tossface' ? char2tossfaceFilePath : char2fluentEmojiFilePath; diff --git a/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts b/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts index c9af5f4ea4..15938d0495 100644 --- a/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts +++ b/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts @@ -2,11 +2,10 @@ * SPDX-FileCopyrightText: syuilo and misskey-project * SPDX-License-Identifier: AGPL-3.0-only */ - -/* eslint-disable @typescript-eslint/explicit-function-return-type */ + import { waitFor } from '@storybook/test'; -import type { StoryObj } from '@storybook/vue3'; import MkPageHeader from './MkPageHeader.vue'; +import type { StoryObj } from '@storybook/vue3'; export const Empty = { render(args) { return { @@ -29,7 +28,7 @@ export const Empty = { }; }, async play() { - const wait = new Promise((resolve) => setTimeout(resolve, 800)); + const wait = new Promise((resolve) => window.setTimeout(resolve, 800)); await waitFor(async () => await wait); }, args: { diff --git a/packages/frontend/src/components/global/MkPageHeader.tabs.vue b/packages/frontend/src/components/global/MkPageHeader.tabs.vue index 832c7887cb..b54e683179 100644 --- a/packages/frontend/src/components/global/MkPageHeader.tabs.vue +++ b/packages/frontend/src/components/global/MkPageHeader.tabs.vue @@ -133,7 +133,7 @@ async function enter(el: Element) { entering = false; }); - setTimeout(renderTab, 170); + window.setTimeout(renderTab, 170); } function afterEnter(el: Element) { diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue index 54ce0f85eb..1dda723fd2 100644 --- a/packages/frontend/src/components/global/MkPageHeader.vue +++ b/packages/frontend/src/components/global/MkPageHeader.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<div v-if="show" ref="el" :class="[$style.root]" :style="{ background: bg }"> +<div v-if="show" ref="el" :class="[$style.root]"> <div :class="[$style.upper, { [$style.slim]: narrow, [$style.thin]: thin_ }]"> <div v-if="!thin_ && narrow && props.displayMyAvatar && $i" class="_button" :class="$style.buttonsLeft" @click="openAccountMenu"> <MkAvatar :class="$style.avatar" :user="$i"/> @@ -51,7 +51,6 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { onMounted, onUnmounted, ref, inject, useTemplateRef, computed } from 'vue'; -import tinycolor from 'tinycolor2'; import { scrollToTop } from '@@/js/scroll.js'; import XTabs from './MkPageHeader.tabs.vue'; import type { Tab } from './MkPageHeader.tabs.vue'; @@ -82,7 +81,6 @@ const emit = defineEmits<{ const displayBackButton = props.displayBackButton && history.state.key !== 'index' && history.length > 1 && inject('shouldBackButton', true); const viewId = inject(DI.viewId); -const viewTransitionName = computed(() => `${viewId}---pageHeader`); const injectedPageMetadata = inject(DI.pageMetadata); const pageMetadata = computed(() => props.overridePageMetadata ?? injectedPageMetadata.value); @@ -90,7 +88,6 @@ const hideTitle = computed(() => inject('shouldOmitHeaderTitle', false) || props const thin_ = props.thin || inject('shouldHeaderThin', false); const el = useTemplateRef('el'); -const bg = ref<string | undefined>(undefined); const narrow = ref(false); const hasTabs = computed(() => props.tabs.length > 0); const hasActions = computed(() => props.actions && props.actions.length > 0); @@ -122,19 +119,9 @@ function goBack(): void { window.history.back(); } -const calcBg = () => { - const rawBg = 'var(--MI_THEME-bg)'; - const tinyBg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(window.document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg); - tinyBg.setAlpha(0.85); - bg.value = tinyBg.toRgbString(); -}; - let ro: ResizeObserver | null; onMounted(() => { - calcBg(); - globalEvents.on('themeChanging', calcBg); - if (el.value && el.value.parentElement) { narrow.value = el.value.parentElement.offsetWidth < 500; ro = new ResizeObserver((entries, observer) => { @@ -147,18 +134,17 @@ onMounted(() => { }); onUnmounted(() => { - globalEvents.off('themeChanging', calcBg); if (ro) ro.disconnect(); }); </script> <style lang="scss" module> .root { + background: color(from var(--MI_THEME-bg) srgb r g b / 0.75); -webkit-backdrop-filter: var(--MI-blur, blur(15px)); backdrop-filter: var(--MI-blur, blur(15px)); border-bottom: solid 0.5px var(--MI_THEME-divider); width: 100%; - view-transition-name: v-bind(viewTransitionName); } .upper, diff --git a/packages/frontend/src/components/global/PageWithHeader.vue b/packages/frontend/src/components/global/PageWithHeader.vue index fb813689ba..7ea0b5c97f 100644 --- a/packages/frontend/src/components/global/PageWithHeader.vue +++ b/packages/frontend/src/components/global/PageWithHeader.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<div :class="[$style.root, reversed ? '_pageScrollableReversed' : '_pageScrollable']"> +<div ref="rootEl" :class="[$style.root, reversed ? '_pageScrollableReversed' : '_pageScrollable']"> <MkStickyContainer> <template #header><MkPageHeader v-model:tab="tab" :actions="actions" :tabs="tabs"/></template> <div :class="$style.body"> @@ -16,6 +16,8 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> +import { useTemplateRef } from 'vue'; +import { scrollInContainer } from '@@/js/scroll.js'; import type { PageHeaderItem } from '@/types/page-header.js'; import type { Tab } from './MkPageHeader.tabs.vue'; @@ -31,6 +33,13 @@ const props = withDefaults(defineProps<{ }); const tab = defineModel<string>('tab'); +const rootEl = useTemplateRef('rootEl'); + +defineExpose({ + scrollToTop: () => { + if (rootEl.value) scrollInContainer(rootEl.value, { top: 0, behavior: 'smooth' }); + }, +}); </script> <style lang="scss" module> diff --git a/packages/frontend/src/components/global/RouterView.vue b/packages/frontend/src/components/global/RouterView.vue index 1c0c35f34e..78ac6900a3 100644 --- a/packages/frontend/src/components/global/RouterView.vue +++ b/packages/frontend/src/components/global/RouterView.vue @@ -44,7 +44,9 @@ provide(DI.routerCurrentDepth, currentDepth + 1); const rootEl = useTemplateRef('rootEl'); onMounted(() => { - rootEl.value.style.viewTransitionName = viewId; // view-transition-nameにcss varが使えないっぽいため直接代入 + if (prefer.s.animation) { + rootEl.value.style.viewTransitionName = viewId; // view-transition-nameにcss varが使えないっぽいため直接代入 + } }); // view-transition-newなどの<pt-name-selector>にはcss varが使えず、v-bindできないため直接スタイルを生成 diff --git a/packages/frontend/src/components/grid/MkGrid.vue b/packages/frontend/src/components/grid/MkGrid.vue index 94f4f3dab1..c37f3df0d3 100644 --- a/packages/frontend/src/components/grid/MkGrid.vue +++ b/packages/frontend/src/components/grid/MkGrid.vue @@ -50,6 +50,12 @@ SPDX-License-Identifier: AGPL-3.0-only <script setup lang="ts"> import { computed, onMounted, ref, toRefs, watch } from 'vue'; +import type { DataSource, GridSetting, GridState, Size } from '@/components/grid/grid.js'; +import type { CellAddress, CellValue, GridCell } from '@/components/grid/cell.js'; +import type { GridContext, GridEvent } from '@/components/grid/grid-event.js'; +import type { GridColumn } from '@/components/grid/column.js'; +import type { GridRow, GridRowSetting } from '@/components/grid/row.js'; +import type { MenuItem } from '@/types/menu.js'; import { GridEventEmitter } from '@/components/grid/grid.js'; import MkDataRow from '@/components/grid/MkDataRow.vue'; import MkHeaderRow from '@/components/grid/MkHeaderRow.vue'; @@ -68,13 +74,6 @@ import { createColumn } from '@/components/grid/column.js'; import { createRow, defaultGridRowSetting, resetRow } from '@/components/grid/row.js'; import { handleKeyEvent } from '@/utility/key-event.js'; -import type { DataSource, GridSetting, GridState, Size } from '@/components/grid/grid.js'; -import type { CellAddress, CellValue, GridCell } from '@/components/grid/cell.js'; -import type { GridContext, GridEvent } from '@/components/grid/grid-event.js'; -import type { GridColumn } from '@/components/grid/column.js'; -import type { GridRow, GridRowSetting } from '@/components/grid/row.js'; -import type { MenuItem } from '@/types/menu.js'; - type RowHolder = { row: GridRow, cells: GridCell[], @@ -130,7 +129,7 @@ const bus = new GridEventEmitter(); * * @see {@link onResize} */ -const resizeObserver = new ResizeObserver((entries) => setTimeout(() => onResize(entries))); +const resizeObserver = new ResizeObserver((entries) => window.setTimeout(() => onResize(entries))); const rootEl = ref<InstanceType<typeof HTMLTableElement>>(); /** |