diff options
| author | syuilo <4439005+syuilo@users.noreply.github.com> | 2025-05-09 17:40:08 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-05-09 17:40:08 +0900 |
| commit | 8c2ab25e5f2040fcbc81bc2a02a279fed40e1c11 (patch) | |
| tree | ae0d3573bd5a3175bc6174d33129dc64205a1436 /packages/frontend/src/ui | |
| parent | refactor (diff) | |
| download | misskey-8c2ab25e5f2040fcbc81bc2a02a279fed40e1c11.tar.gz misskey-8c2ab25e5f2040fcbc81bc2a02a279fed40e1c11.tar.bz2 misskey-8c2ab25e5f2040fcbc81bc2a02a279fed40e1c11.zip | |
Feat: No websocket mode (#15851)
* wip
* wip
* wip
* wip
* Update MkTimeline.vue
* wip
* wip
* wip
* Update MkTimeline.vue
* Update use-pagination.ts
* wip
* wip
* Update MkTimeline.vue
* Update MkTimeline.vue
* wip
* wip
* Update MkTimeline.vue
* Update MkTimeline.vue
* Update MkTimeline.vue
* wip
* Update use-pagination.ts
* wip
* Update use-pagination.ts
* Update MkNotifications.vue
* Update MkNotifications.vue
* wip
* wip
* wip
* Update use-note-capture.ts
* Update use-note-capture.ts
* Update use-note-capture.ts
* wip
* wip
* wip
* wip
* Update MkNoteDetailed.vue
* wip
* wip
* Update MkTimeline.vue
* wip
* fix
* Update MkTimeline.vue
* wip
* test
* Revert "test"
This reverts commit 3375619396c54dcda5e564eb1da444c2391208c9.
* Update use-pagination.ts
* test
* Revert "test"
This reverts commit 42c53c830e28485d2fb49061fa7cdeee31bc6a22.
* test
* Revert "test"
This reverts commit c4f8cda4aa1cec9d1eb97557145f3ad3d2d0e469.
* Update style.scss
* Update MkTimeline.vue
* Update MkTimeline.vue
* Update MkTimeline.vue
* ✌️
* Update MkTimeline.vue
* wip
* wip
* test
* Update MkPullToRefresh.vue
* Update MkPullToRefresh.vue
* Update MkPullToRefresh.vue
* Update MkPullToRefresh.vue
* Update MkTimeline.vue
* wip
* tweak navbar
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* Update home.vue
* wip
* refactor
* wip
* wip
* Update note.vue
* Update navbar.vue
* Update MkPullToRefresh.vue
* Update MkPullToRefresh.vue
* Update MkPullToRefresh.vue
* wip
* Update MkStreamingNotificationsTimeline.vue
* Update use-pagination.ts
* wip
* improve perf
* wip
* Update MkNotesTimeline.vue
* wip
* megre
* Update use-pagination.ts
* Update use-pagination.ts
* Update MkStreamingNotesTimeline.vue
* Update use-pagination.ts
* Update CHANGELOG.md
* Update CHANGELOG.md
* Update CHANGELOG.md
Diffstat (limited to 'packages/frontend/src/ui')
| -rw-r--r-- | packages/frontend/src/ui/_common_/common.vue | 22 | ||||
| -rw-r--r-- | packages/frontend/src/ui/_common_/navbar-for-mobile.vue | 273 | ||||
| -rw-r--r-- | packages/frontend/src/ui/_common_/navbar.vue | 198 | ||||
| -rw-r--r-- | packages/frontend/src/ui/_common_/stream-indicator.vue | 11 | ||||
| -rw-r--r-- | packages/frontend/src/ui/deck/antenna-column.vue | 9 | ||||
| -rw-r--r-- | packages/frontend/src/ui/deck/channel-column.vue | 9 | ||||
| -rw-r--r-- | packages/frontend/src/ui/deck/direct-column.vue | 10 | ||||
| -rw-r--r-- | packages/frontend/src/ui/deck/list-column.vue | 9 | ||||
| -rw-r--r-- | packages/frontend/src/ui/deck/mentions-column.vue | 12 | ||||
| -rw-r--r-- | packages/frontend/src/ui/deck/notifications-column.vue | 4 | ||||
| -rw-r--r-- | packages/frontend/src/ui/deck/role-timeline-column.vue | 9 | ||||
| -rw-r--r-- | packages/frontend/src/ui/deck/tl-column.vue | 10 |
12 files changed, 191 insertions, 385 deletions
diff --git a/packages/frontend/src/ui/_common_/common.vue b/packages/frontend/src/ui/_common_/common.vue index 5fe99e0d14..fcf9fb234d 100644 --- a/packages/frontend/src/ui/_common_/common.vue +++ b/packages/frontend/src/ui/_common_/common.vue @@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only :leaveToClass="prefer.s.animation ? $style.transition_menuDrawer_leaveTo : ''" > <div v-if="drawerMenuShowing" :class="$style.menuDrawer"> - <XDrawerMenu/> + <XNavbar style="height: 100%;" :asDrawer="true" :showWidgetButton="false"/> </div> </Transition> @@ -112,7 +112,8 @@ import { useStream } from '@/stream.js'; import { i18n } from '@/i18n.js'; import { prefer } from '@/preferences.js'; import { globalEvents } from '@/events.js'; -import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue'; +import { store } from '@/store.js'; +import XNavbar from '@/ui/_common_/navbar.vue'; const XStreamIndicator = defineAsyncComponent(() => import('./stream-indicator.vue')); const XUpload = defineAsyncComponent(() => import('./upload.vue')); @@ -129,7 +130,9 @@ function onNotification(notification: Misskey.entities.Notification, isClient = if (window.document.visibilityState === 'visible') { if (!isClient && notification.type !== 'test') { // サーバーサイドのテスト通知の際は自動で既読をつけない(テストできないので) - useStream().send('readNotification'); + if (store.s.realtimeMode) { + useStream().send('readNotification'); + } } notifications.value.unshift(notification); @@ -146,11 +149,12 @@ function onNotification(notification: Misskey.entities.Notification, isClient = } if ($i) { - const connection = useStream().useChannel('main', null, 'UI'); - connection.on('notification', onNotification); + if (store.s.realtimeMode) { + const connection = useStream().useChannel('main'); + connection.on('notification', onNotification); + } globalEvents.on('clientNotification', notification => onNotification(notification, true)); - //#region Listen message from SW if ('serviceWorker' in navigator) { swInject(); } @@ -226,12 +230,6 @@ if ($i) { left: 0; z-index: 1001; height: 100dvh; - width: 240px; - box-sizing: border-box; - contain: strict; - overflow: auto; - overscroll-behavior: contain; - background: var(--MI_THEME-navBg); } .widgetsDrawerBg { diff --git a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue b/packages/frontend/src/ui/_common_/navbar-for-mobile.vue deleted file mode 100644 index 826e03751a..0000000000 --- a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue +++ /dev/null @@ -1,273 +0,0 @@ -<!-- -SPDX-FileCopyrightText: syuilo and misskey-project -SPDX-License-Identifier: AGPL-3.0-only ---> - -<template> -<div :class="$style.root"> - <div :class="$style.top"> - <div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }"></div> - <button class="_button" :class="$style.instance" @click="openInstanceMenu"> - <img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" :class="$style.instanceIcon"/> - </button> - </div> - <div :class="$style.middle"> - <MkA :class="$style.item" :activeClass="$style.active" to="/" exact> - <i :class="$style.itemIcon" class="ti ti-home ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.timeline }}</span> - </MkA> - <template v-for="item in prefer.r.menu.value"> - <div v-if="item === '-'" :class="$style.divider"></div> - <component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" class="_button" :class="[$style.item, { [$style.active]: navbarItemDef[item].active }]" :activeClass="$style.active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}"> - <i class="ti-fw" :class="[$style.itemIcon, navbarItemDef[item].icon]"></i><span :class="$style.itemText">{{ navbarItemDef[item].title }}</span> - <span v-if="navbarItemDef[item].indicated" :class="$style.itemIndicator" class="_blink"> - <span v-if="navbarItemDef[item].indicateValue" class="_indicateCounter" :class="$style.itemIndicateValueIcon">{{ navbarItemDef[item].indicateValue }}</span> - <i v-else class="_indicatorCircle"></i> - </span> - </component> - </template> - <div :class="$style.divider"></div> - <MkA v-if="$i.isAdmin || $i.isModerator" :class="$style.item" :activeClass="$style.active" to="/admin"> - <i :class="$style.itemIcon" class="ti ti-dashboard ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.controlPanel }}</span> - </MkA> - <button :class="$style.item" class="_button" @click="more"> - <i :class="$style.itemIcon" class="ti ti-grid-dots ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.more }}</span> - <span v-if="otherMenuItemIndicated" :class="$style.itemIndicator" class="_blink"><i class="_indicatorCircle"></i></span> - </button> - <MkA :class="$style.item" :activeClass="$style.active" to="/settings"> - <i :class="$style.itemIcon" class="ti ti-settings ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.settings }}</span> - </MkA> - </div> - <div :class="$style.bottom"> - <button class="_button" :class="$style.post" data-cy-open-post-form @click="os.post"> - <i :class="$style.postIcon" class="ti ti-pencil ti-fw"></i><span style="position: relative;">{{ i18n.ts.note }}</span> - </button> - <button class="_button" :class="$style.account" @click="openAccountMenu"> - <MkAvatar :user="$i" :class="$style.avatar"/><MkAcct :class="$style.acct" class="_nowrap" :user="$i"/> - </button> - </div> -</div> -</template> - -<script lang="ts" setup> -import { computed, defineAsyncComponent } from 'vue'; -import { openInstanceMenu } from './common.js'; -import * as os from '@/os.js'; -import { navbarItemDef } from '@/navbar.js'; -import { prefer } from '@/preferences.js'; -import { i18n } from '@/i18n.js'; -import { instance } from '@/instance.js'; -import { openAccountMenu as openAccountMenu_ } from '@/accounts.js'; -import { $i } from '@/i.js'; - -const otherMenuItemIndicated = computed(() => { - for (const def in navbarItemDef) { - if (prefer.r.menu.value.includes(def)) continue; - if (navbarItemDef[def].indicated) return true; - } - return false; -}); - -function openAccountMenu(ev: MouseEvent) { - openAccountMenu_({ - withExtraOperation: true, - }, ev); -} - -function more() { - const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkLaunchPad.vue')), {}, { - closed: () => dispose(), - }); -} -</script> - -<style lang="scss" module> -.root { - --nav-bg-transparent: color(from var(--MI_THEME-navBg) srgb r g b / 0.5); - - display: flex; - flex-direction: column; -} - -.top { - position: sticky; - top: 0; - z-index: 1; - padding: 20px 0; - background: var(--nav-bg-transparent); - -webkit-backdrop-filter: var(--MI-blur, blur(8px)); - backdrop-filter: var(--MI-blur, blur(8px)); -} - -.banner { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-size: cover; - background-position: center center; - -webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%); - mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%); -} - -.instance { - position: relative; - display: block; - text-align: center; - width: 100%; -} - -.instanceIcon { - display: inline-block; - width: 38px; - aspect-ratio: 1; - border-radius: 8px; -} - -.bottom { - position: sticky; - bottom: 0; - padding: 20px 0; - background: var(--nav-bg-transparent); - -webkit-backdrop-filter: var(--MI-blur, blur(8px)); - backdrop-filter: var(--MI-blur, blur(8px)); -} - -.post { - position: relative; - display: block; - width: 100%; - height: 40px; - color: var(--MI_THEME-fgOnAccent); - font-weight: bold; - text-align: left; - - &::before { - content: ""; - display: block; - width: calc(100% - 38px); - height: 100%; - margin: auto; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - border-radius: 999px; - background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB)); - } - - &:hover, &.active { - &::before { - background: hsl(from var(--MI_THEME-accent) h s calc(l + 10)); - } - } -} - -.postIcon { - position: relative; - margin-left: 30px; - margin-right: 8px; - width: 32px; -} - -.account { - position: relative; - display: flex; - align-items: center; - padding-left: 30px; - width: 100%; - text-align: left; - box-sizing: border-box; - margin-top: 16px; -} - -.avatar { - display: block; - flex-shrink: 0; - position: relative; - width: 32px; - aspect-ratio: 1; - margin-right: 8px; -} - -.acct { - display: block; - flex-shrink: 1; - padding-right: 8px; -} - -.middle { - flex: 1; -} - -.divider { - margin: 16px 16px; - border-top: solid 0.5px var(--MI_THEME-divider); -} - -.item { - position: relative; - display: block; - padding-left: 24px; - line-height: 2.85rem; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - width: 100%; - text-align: left; - box-sizing: border-box; - color: var(--MI_THEME-navFg); - - &:hover { - text-decoration: none; - color: light-dark(hsl(from var(--MI_THEME-navFg) h s calc(l - 17)), hsl(from var(--MI_THEME-navFg) h s calc(l + 17))); - } - - &.active { - color: var(--MI_THEME-navActive); - } - - &:hover, &.active { - &::before { - content: ""; - display: block; - width: calc(100% - 24px); - height: 100%; - margin: auto; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - border-radius: 999px; - background: var(--MI_THEME-accentedBg); - } - } -} - -.itemIcon { - position: relative; - width: 32px; - margin-right: 8px; -} - -.itemIndicator { - position: absolute; - top: 0; - left: 20px; - color: var(--MI_THEME-navIndicator); - font-size: 8px; - - &:has(.itemIndicateValueIcon) { - animation: none; - left: auto; - right: 20px; - } -} - -.itemText { - position: relative; - font-size: 0.9em; -} -</style> diff --git a/packages/frontend/src/ui/_common_/navbar.vue b/packages/frontend/src/ui/_common_/navbar.vue index ce8efa3324..7cfedc939f 100644 --- a/packages/frontend/src/ui/_common_/navbar.vue +++ b/packages/frontend/src/ui/_common_/navbar.vue @@ -10,6 +10,9 @@ SPDX-License-Identifier: AGPL-3.0-only <button v-tooltip.noDelay.right="instance.name ?? i18n.ts.instance" class="_button" :class="$style.instance" @click="openInstanceMenu"> <img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" :class="$style.instanceIcon" style="viewTransitionName: navbar-serverIcon;"/> </button> + <button v-if="!iconOnly" v-tooltip.noDelay.right="i18n.ts.realtimeMode" class="_button" :class="[$style.realtimeMode, store.r.realtimeMode.value ? $style.on : null]" @click="toggleRealtimeMode"> + <i class="ti ti-bolt ti-fw"></i> + </button> </div> <div :class="$style.middle"> <MkA v-tooltip.noDelay.right="i18n.ts.timeline" :class="$style.item" :activeClass="$style.active" to="/" exact> @@ -50,6 +53,9 @@ SPDX-License-Identifier: AGPL-3.0-only <button v-if="showWidgetButton" class="_button" :class="[$style.widget]" @click="() => emit('widgetButtonClick')"> <i class="ti ti-apps ti-fw"></i> </button> + <button v-if="iconOnly" v-tooltip.noDelay.right="i18n.ts.realtimeMode" class="_button" :class="[$style.realtimeMode, store.r.realtimeMode.value ? $style.on : null]" @click="toggleRealtimeMode"> + <i class="ti ti-bolt ti-fw"></i> + </button> <button v-tooltip.noDelay.right="i18n.ts.note" class="_button" :class="[$style.post]" data-cy-open-post-form @click="() => { os.post(); }"> <i class="ti ti-pencil ti-fw" :class="$style.postIcon"></i><span :class="$style.postText">{{ i18n.ts.note }}</span> </button> @@ -76,16 +82,18 @@ SPDX-License-Identifier: AGPL-3.0-only </svg> <button class="_button" :class="$style.subButtonClickable" @click="menuEdit"><i :class="$style.subButtonIcon" class="ti ti-settings-2"></i></button> </div> - <div :class="$style.subButtonGapFill"></div> - <div :class="$style.subButtonGapFillDivider"></div> - <div :class="[$style.subButton, $style.toggleButton]"> - <svg viewBox="0 0 16 64" :class="$style.subButtonShape"> - <g transform="matrix(0.333333,0,0,0.222222,0.000895785,21.3333)"> - <path d="M47.488,7.995C47.79,10.11 47.943,12.266 47.943,14.429C47.997,26.989 47.997,84 47.997,84C47.997,84 44.018,118.246 23.997,133.5C-0.374,152.07 -0.003,192 -0.003,192L-0.003,-96C-0.003,-96 0.151,-56.216 23.997,-37.5C40.861,-24.265 46.043,-1.243 47.488,7.995Z" style="fill:var(--MI_THEME-navBg);"/> - </g> - </svg> - <button class="_button" :class="$style.subButtonClickable" @click="toggleIconOnly"><i v-if="iconOnly" class="ti ti-chevron-right" :class="$style.subButtonIcon"></i><i v-else class="ti ti-chevron-left" :class="$style.subButtonIcon"></i></button> - </div> + <template v-if="!props.asDrawer"> + <div :class="$style.subButtonGapFill"></div> + <div :class="$style.subButtonGapFillDivider"></div> + <div :class="[$style.subButton, $style.toggleButton]"> + <svg viewBox="0 0 16 64" :class="$style.subButtonShape"> + <g transform="matrix(0.333333,0,0,0.222222,0.000895785,21.3333)"> + <path d="M47.488,7.995C47.79,10.11 47.943,12.266 47.943,14.429C47.997,26.989 47.997,84 47.997,84C47.997,84 44.018,118.246 23.997,133.5C-0.374,152.07 -0.003,192 -0.003,192L-0.003,-96C-0.003,-96 0.151,-56.216 23.997,-37.5C40.861,-24.265 46.043,-1.243 47.488,7.995Z" style="fill:var(--MI_THEME-navBg);"/> + </g> + </svg> + <button class="_button" :class="$style.subButtonClickable" @click="toggleIconOnly"><i v-if="iconOnly" class="ti ti-chevron-right" :class="$style.subButtonIcon"></i><i v-else class="ti ti-chevron-left" :class="$style.subButtonIcon"></i></button> + </div> + </template> </div> </div> </template> @@ -108,15 +116,16 @@ const router = useRouter(); const props = defineProps<{ showWidgetButton?: boolean; + asDrawer?: boolean; }>(); const emit = defineEmits<{ (ev: 'widgetButtonClick'): void; }>(); -const forceIconOnly = ref(window.innerWidth <= 1279); +const forceIconOnly = ref(!props.asDrawer && window.innerWidth <= 1279); const iconOnly = computed(() => { - return forceIconOnly.value || (store.r.menuDisplay.value === 'sideIcon'); + return !props.asDrawer && (forceIconOnly.value || (store.r.menuDisplay.value === 'sideIcon')); }); const otherMenuItemIndicated = computed(() => { @@ -147,6 +156,20 @@ function toggleIconOnly() { } } +function toggleRealtimeMode(ev: MouseEvent) { + os.popupMenu([{ + type: 'label', + text: i18n.ts.realtimeMode, + }, { + text: store.s.realtimeMode ? i18n.ts.turnItOff : i18n.ts.turnItOn, + icon: store.s.realtimeMode ? 'ti ti-bolt-off' : 'ti ti-bolt', + action: () => { + store.set('realtimeMode', !store.s.realtimeMode); + window.location.reload(); + }, + }], ev.currentTarget ?? ev.target); +} + function openAccountMenu(ev: MouseEvent) { openAccountMenu_({ withExtraOperation: true, @@ -191,21 +214,108 @@ function menuEdit() { overscroll-behavior: contain; background: var(--MI_THEME-navBg); contain: strict; + + /* 画面が縦に長い、設置している項目数が少ないなどの環境においても確実にbottomを最下部に表示するため */ display: flex; flex-direction: column; - direction: rtl; // スクロールバーを左に表示したいため + + direction: rtl; /* スクロールバーを左に表示したいため */ } .top { + flex-shrink: 0; direction: ltr; + + /* 疑似progressive blur */ + &::before { + position: absolute; + z-index: -1; + inset: 0; + content: ""; + backdrop-filter: blur(8px); + mask-image: linear-gradient( + to top, + rgb(0 0 0 / 0%) 0%, + rgb(0 0 0 / 4.9%) 7.75%, + rgb(0 0 0 / 10.4%) 11.25%, + rgb(0 0 0 / 45%) 23.55%, + rgb(0 0 0 / 55%) 26.45%, + rgb(0 0 0 / 89.6%) 38.75%, + rgb(0 0 0 / 95.1%) 42.25%, + rgb(0 0 0 / 100%) 50% + ); + } + + &::after { + position: absolute; + z-index: -1; + inset: 0; + bottom: 25%; + content: ""; + backdrop-filter: blur(16px); + mask-image: linear-gradient( + to top, + rgb(0 0 0 / 0%) 0%, + rgb(0 0 0 / 4.9%) 15.5%, + rgb(0 0 0 / 10.4%) 22.5%, + rgb(0 0 0 / 45%) 47.1%, + rgb(0 0 0 / 55%) 52.9%, + rgb(0 0 0 / 89.6%) 77.5%, + rgb(0 0 0 / 95.1%) 91.9%, + rgb(0 0 0 / 100%) 100% + ); + } } .middle { + flex: 1; direction: ltr; } .bottom { + flex-shrink: 0; direction: ltr; + + /* 疑似progressive blur */ + &::before { + position: absolute; + z-index: -1; + inset: -30px 0 0 0; + content: ""; + backdrop-filter: blur(8px); + mask-image: linear-gradient( + to bottom, + rgb(0 0 0 / 0%) 0%, + rgb(0 0 0 / 4.9%) 7.75%, + rgb(0 0 0 / 10.4%) 11.25%, + rgb(0 0 0 / 45%) 23.55%, + rgb(0 0 0 / 55%) 26.45%, + rgb(0 0 0 / 89.6%) 38.75%, + rgb(0 0 0 / 95.1%) 42.25%, + rgb(0 0 0 / 100%) 50% + ); + pointer-events: none; + } + + &::after { + position: absolute; + z-index: -1; + inset: 0; + top: 25%; + content: ""; + backdrop-filter: blur(16px); + mask-image: linear-gradient( + to bottom, + rgb(0 0 0 / 0%) 0%, + rgb(0 0 0 / 4.9%) 15.5%, + rgb(0 0 0 / 10.4%) 22.5%, + rgb(0 0 0 / 45%) 47.1%, + rgb(0 0 0 / 55%) 52.9%, + rgb(0 0 0 / 89.6%) 77.5%, + rgb(0 0 0 / 95.1%) 91.9%, + rgb(0 0 0 / 100%) 100% + ); + } } .subButtons { @@ -290,29 +400,18 @@ function menuEdit() { } .top { + --top-height: 80px; + position: sticky; top: 0; z-index: 1; - padding: 20px 0; - background: var(--nav-bg-transparent); - -webkit-backdrop-filter: var(--MI-blur, blur(8px)); - backdrop-filter: var(--MI-blur, blur(8px)); + display: flex; + height: var(--top-height); } .instance { position: relative; - display: block; - text-align: center; - width: 100%; - - &:focus-visible { - outline: none; - - > .instanceIcon { - outline: 2px solid var(--MI_THEME-focus); - outline-offset: 2px; - } - } + width: var(--top-height); } .instanceIcon { @@ -322,13 +421,20 @@ function menuEdit() { border-radius: 8px; } + .realtimeMode { + display: inline-block; + width: var(--top-height); + margin-left: auto; + + &.on { + color: var(--MI_THEME-accent); + } + } + .bottom { position: sticky; bottom: 0; padding-top: 20px; - background: var(--nav-bg-transparent); - -webkit-backdrop-filter: var(--MI-blur, blur(8px)); - backdrop-filter: var(--MI-blur, blur(8px)); } .post { @@ -416,10 +522,6 @@ function menuEdit() { padding-right: 8px; } - .middle { - flex: 1; - } - .divider { margin: 16px 16px; border-top: solid 0.5px var(--MI_THEME-divider); @@ -520,9 +622,6 @@ function menuEdit() { top: 0; z-index: 1; padding: 20px 0; - background: var(--nav-bg-transparent); - -webkit-backdrop-filter: var(--MI-blur, blur(8px)); - backdrop-filter: var(--MI-blur, blur(8px)); } .instance { @@ -551,9 +650,6 @@ function menuEdit() { position: sticky; bottom: 0; padding-top: 20px; - background: var(--nav-bg-transparent); - -webkit-backdrop-filter: var(--MI-blur, blur(8px)); - backdrop-filter: var(--MI-blur, blur(8px)); } .widget { @@ -564,6 +660,18 @@ function menuEdit() { text-align: center; } + .realtimeMode { + display: block; + position: relative; + width: 100%; + height: 52px; + text-align: center; + + &.on { + color: var(--MI_THEME-accent); + } + } + .post { display: block; position: relative; @@ -637,10 +745,6 @@ function menuEdit() { display: none; } - .middle { - flex: 1; - } - .divider { margin: 8px auto; width: calc(100% - 32px); @@ -650,7 +754,7 @@ function menuEdit() { .item { display: block; position: relative; - padding: 18px 0; + padding: 16px 0; width: 100%; text-align: center; diff --git a/packages/frontend/src/ui/_common_/stream-indicator.vue b/packages/frontend/src/ui/_common_/stream-indicator.vue index 5f7600881f..35508b7ce6 100644 --- a/packages/frontend/src/ui/_common_/stream-indicator.vue +++ b/packages/frontend/src/ui/_common_/stream-indicator.vue @@ -20,6 +20,7 @@ import { i18n } from '@/i18n.js'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; import { prefer } from '@/preferences.js'; +import { store } from '@/store.js'; const zIndex = os.claimZIndex('high'); @@ -37,11 +38,13 @@ function reload() { window.location.reload(); } -useStream().on('_disconnected_', onDisconnected); +if (store.s.realtimeMode) { + useStream().on('_disconnected_', onDisconnected); -onUnmounted(() => { - useStream().off('_disconnected_', onDisconnected); -}); + onUnmounted(() => { + useStream().off('_disconnected_', onDisconnected); + }); +} </script> <style lang="scss" module> diff --git a/packages/frontend/src/ui/deck/antenna-column.vue b/packages/frontend/src/ui/deck/antenna-column.vue index 194b56c842..716f0ba995 100644 --- a/packages/frontend/src/ui/deck/antenna-column.vue +++ b/packages/frontend/src/ui/deck/antenna-column.vue @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only <i class="ti ti-antenna"></i><span style="margin-left: 8px;">{{ column.name || antennaName || i18n.ts._deck._columns.antenna }}</span> </template> - <MkTimeline v-if="column.antennaId" ref="timeline" src="antenna" :antenna="column.antennaId" @note="onNote"/> + <MkStreamingNotesTimeline v-if="column.antennaId" ref="timeline" src="antenna" :antenna="column.antennaId"/> </XColumn> </template> @@ -21,13 +21,12 @@ import type { Column } from '@/deck.js'; import type { MenuItem } from '@/types/menu.js'; import type { SoundStore } from '@/preferences/def.js'; import { updateColumn } from '@/deck.js'; -import MkTimeline from '@/components/MkTimeline.vue'; +import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; import { antennasCache } from '@/cache.js'; import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js'; -import * as sound from '@/utility/sound.js'; const props = defineProps<{ column: Column; @@ -96,10 +95,6 @@ function editAntenna() { os.pageWindow('my/antennas/' + props.column.antennaId); } -function onNote() { - sound.playMisskeySfxFile(soundSetting.value); -} - const menu: MenuItem[] = [ { icon: 'ti ti-pencil', diff --git a/packages/frontend/src/ui/deck/channel-column.vue b/packages/frontend/src/ui/deck/channel-column.vue index c2644da707..3439a2a56e 100644 --- a/packages/frontend/src/ui/deck/channel-column.vue +++ b/packages/frontend/src/ui/deck/channel-column.vue @@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div style="padding: 8px; text-align: center;"> <MkButton primary gradate rounded inline small @click="post"><i class="ti ti-pencil"></i></MkButton> </div> - <MkTimeline ref="timeline" src="channel" :channel="column.channelId" @note="onNote"/> + <MkStreamingNotesTimeline ref="timeline" src="channel" :channel="column.channelId"/> </template> </XColumn> </template> @@ -26,14 +26,13 @@ import type { Column } from '@/deck.js'; import type { MenuItem } from '@/types/menu.js'; import type { SoundStore } from '@/preferences/def.js'; import { updateColumn } from '@/deck.js'; -import MkTimeline from '@/components/MkTimeline.vue'; +import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; import { favoritedChannelsCache } from '@/cache.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js'; -import * as sound from '@/utility/sound.js'; const props = defineProps<{ column: Column; @@ -90,10 +89,6 @@ async function post() { }); } -function onNote() { - sound.playMisskeySfxFile(soundSetting.value); -} - const menu: MenuItem[] = [{ icon: 'ti ti-pencil', text: i18n.ts.selectChannel, diff --git a/packages/frontend/src/ui/deck/direct-column.vue b/packages/frontend/src/ui/deck/direct-column.vue index 772188d773..c8b174da09 100644 --- a/packages/frontend/src/ui/deck/direct-column.vue +++ b/packages/frontend/src/ui/deck/direct-column.vue @@ -7,15 +7,15 @@ SPDX-License-Identifier: AGPL-3.0-only <XColumn :column="column" :isStacked="isStacked" :refresher="() => reloadTimeline()"> <template #header><i class="ti ti-mail" style="margin-right: 8px;"></i>{{ column.name || i18n.ts._deck._columns.direct }}</template> - <MkNotes ref="tlComponent" :pagination="pagination"/> + <MkNotesTimeline ref="tlComponent" :pagination="pagination"/> </XColumn> </template> <script lang="ts" setup> -import { ref } from 'vue'; +import { ref, useTemplateRef } from 'vue'; import XColumn from './column.vue'; import type { Column } from '@/deck.js'; -import MkNotes from '@/components/MkNotes.vue'; +import MkNotesTimeline from '@/components/MkNotesTimeline.vue'; import { i18n } from '@/i18n.js'; defineProps<{ @@ -31,11 +31,11 @@ const pagination = { }, }; -const tlComponent = ref<InstanceType<typeof MkNotes>>(); +const tlComponent = useTemplateRef('tlComponent'); function reloadTimeline() { return new Promise<void>((res) => { - tlComponent.value?.pagingComponent?.reload().then(() => { + tlComponent.value?.reload().then(() => { res(); }); }); diff --git a/packages/frontend/src/ui/deck/list-column.vue b/packages/frontend/src/ui/deck/list-column.vue index a8f17feb23..5b7390b1b2 100644 --- a/packages/frontend/src/ui/deck/list-column.vue +++ b/packages/frontend/src/ui/deck/list-column.vue @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only <i class="ti ti-list"></i><span style="margin-left: 8px;">{{ (column.name || listName) ?? i18n.ts._deck._columns.list }}</span> </template> - <MkTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId" :withRenotes="withRenotes" @note="onNote"/> + <MkStreamingNotesTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId" :withRenotes="withRenotes"/> </XColumn> </template> @@ -21,13 +21,12 @@ import type { Column } from '@/deck.js'; import type { MenuItem } from '@/types/menu.js'; import type { SoundStore } from '@/preferences/def.js'; import { updateColumn } from '@/deck.js'; -import MkTimeline from '@/components/MkTimeline.vue'; +import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; import { userListsCache } from '@/cache.js'; import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js'; -import * as sound from '@/utility/sound.js'; const props = defineProps<{ column: Column; @@ -102,10 +101,6 @@ function editList() { os.pageWindow('my/lists/' + props.column.listId); } -function onNote() { - sound.playMisskeySfxFile(soundSetting.value); -} - const menu: MenuItem[] = [ { icon: 'ti ti-pencil', diff --git a/packages/frontend/src/ui/deck/mentions-column.vue b/packages/frontend/src/ui/deck/mentions-column.vue index ffd0307940..640e933f23 100644 --- a/packages/frontend/src/ui/deck/mentions-column.vue +++ b/packages/frontend/src/ui/deck/mentions-column.vue @@ -7,27 +7,27 @@ SPDX-License-Identifier: AGPL-3.0-only <XColumn :column="column" :isStacked="isStacked" :refresher="() => reloadTimeline()"> <template #header><i class="ti ti-at" style="margin-right: 8px;"></i>{{ column.name || i18n.ts._deck._columns.mentions }}</template> - <MkNotes ref="tlComponent" :pagination="pagination"/> + <MkNotesTimeline ref="tlComponent" :pagination="pagination"/> </XColumn> </template> <script lang="ts" setup> -import { ref } from 'vue'; +import { ref, useTemplateRef } from 'vue'; import XColumn from './column.vue'; import type { Column } from '@/deck.js'; -import MkNotes from '@/components/MkNotes.vue'; -import { i18n } from '../../i18n.js'; +import { i18n } from '@/i18n.js'; +import MkNotesTimeline from '@/components/MkNotesTimeline.vue'; defineProps<{ column: Column; isStacked: boolean; }>(); -const tlComponent = ref<InstanceType<typeof MkNotes>>(); +const tlComponent = useTemplateRef('tlComponent'); function reloadTimeline() { return new Promise<void>((res) => { - tlComponent.value?.pagingComponent?.reload().then(() => { + tlComponent.value?.reload().then(() => { res(); }); }); diff --git a/packages/frontend/src/ui/deck/notifications-column.vue b/packages/frontend/src/ui/deck/notifications-column.vue index 8378dddfef..0e84ed3572 100644 --- a/packages/frontend/src/ui/deck/notifications-column.vue +++ b/packages/frontend/src/ui/deck/notifications-column.vue @@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only <XColumn :column="column" :isStacked="isStacked" :menu="menu" :refresher="async () => { await notificationsComponent?.reload() }"> <template #header><i class="ti ti-bell" style="margin-right: 8px;"></i>{{ column.name || i18n.ts._deck._columns.notifications }}</template> - <XNotifications ref="notificationsComponent" :excludeTypes="props.column.excludeTypes"/> + <MkStreamingNotificationsTimeline ref="notificationsComponent" :excludeTypes="props.column.excludeTypes"/> </XColumn> </template> @@ -16,7 +16,7 @@ import { defineAsyncComponent, useTemplateRef } from 'vue'; import XColumn from './column.vue'; import type { Column } from '@/deck.js'; import { updateColumn } from '@/deck.js'; -import XNotifications from '@/components/MkNotifications.vue'; +import MkStreamingNotificationsTimeline from '@/components/MkStreamingNotificationsTimeline.vue'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; diff --git a/packages/frontend/src/ui/deck/role-timeline-column.vue b/packages/frontend/src/ui/deck/role-timeline-column.vue index 468b3e49e0..ff00dfa6e0 100644 --- a/packages/frontend/src/ui/deck/role-timeline-column.vue +++ b/packages/frontend/src/ui/deck/role-timeline-column.vue @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only <i class="ti ti-badge"></i><span style="margin-left: 8px;">{{ column.name || roleName || i18n.ts._deck._columns.roleTimeline }}</span> </template> - <MkTimeline v-if="column.roleId" ref="timeline" src="role" :role="column.roleId" @note="onNote"/> + <MkStreamingNotesTimeline v-if="column.roleId" ref="timeline" src="role" :role="column.roleId"/> </XColumn> </template> @@ -20,12 +20,11 @@ import type { Column } from '@/deck.js'; import type { MenuItem } from '@/types/menu.js'; import type { SoundStore } from '@/preferences/def.js'; import { updateColumn } from '@/deck.js'; -import MkTimeline from '@/components/MkTimeline.vue'; +import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js'; -import * as sound from '@/utility/sound.js'; const props = defineProps<{ column: Column; @@ -68,10 +67,6 @@ async function setRole() { }); } -function onNote() { - sound.playMisskeySfxFile(soundSetting.value); -} - const menu: MenuItem[] = [{ icon: 'ti ti-pencil', text: i18n.ts.role, diff --git a/packages/frontend/src/ui/deck/tl-column.vue b/packages/frontend/src/ui/deck/tl-column.vue index 6759135654..97208f1c6a 100644 --- a/packages/frontend/src/ui/deck/tl-column.vue +++ b/packages/frontend/src/ui/deck/tl-column.vue @@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only </p> <p :class="$style.disabledDescription">{{ i18n.ts._disabledTimeline.description }}</p> </div> - <MkTimeline + <MkStreamingNotesTimeline v-else-if="column.tl" ref="timeline" :key="column.tl + withRenotes + withReplies + onlyFiles" @@ -26,7 +26,6 @@ SPDX-License-Identifier: AGPL-3.0-only :withReplies="withReplies" :withSensitive="withSensitive" :onlyFiles="onlyFiles" - @note="onNote" /> </XColumn> </template> @@ -38,12 +37,11 @@ import type { Column } from '@/deck.js'; import type { MenuItem } from '@/types/menu.js'; import type { SoundStore } from '@/preferences/def.js'; import { removeColumn, updateColumn } from '@/deck.js'; -import MkTimeline from '@/components/MkTimeline.vue'; +import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; import { hasWithReplies, isAvailableBasicTimeline, basicTimelineIconClass } from '@/timelines.js'; import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js'; -import * as sound from '@/utility/sound.js'; const props = defineProps<{ column: Column; @@ -117,10 +115,6 @@ async function setType() { }); } -function onNote() { - sound.playMisskeySfxFile(soundSetting.value); -} - const menu = computed<MenuItem[]>(() => { const menuItems: MenuItem[] = []; |