From 179d990c39ed5c7f79e7481ca86b07ec9f70d474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Thu, 3 Jul 2025 18:52:16 +0900 Subject: [PATCH] =?UTF-8?q?fix(frontend):=20=E3=82=BF=E3=83=96=E3=81=8C?= =?UTF-8?q?=E4=B8=8D=E5=8F=AF=E8=A6=96=E3=81=AA=E3=81=82=E3=81=84=E3=81=A0?= =?UTF-8?q?=E3=81=AEpagination=E3=81=AE=E3=82=A2=E3=83=83=E3=83=97?= =?UTF-8?q?=E3=83=87=E3=83=BC=E3=83=88=E3=82=92=E5=81=9C=E6=AD=A2=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=20(#16243)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(frontend): タブが不可視なあいだのpaginationのアップデートを停止するように * fix lint * 待たない --- .../components/MkStreamingNotesTimeline.vue | 23 +++++++-- .../MkStreamingNotificationsTimeline.vue | 51 ++++++++++++++++++- 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/packages/frontend/src/components/MkStreamingNotesTimeline.vue b/packages/frontend/src/components/MkStreamingNotesTimeline.vue index b697e18f79..44f873b6e3 100644 --- a/packages/frontend/src/components/MkStreamingNotesTimeline.vue +++ b/packages/frontend/src/components/MkStreamingNotesTimeline.vue @@ -59,6 +59,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, watch, onUnmounted, provide, useTemplateRef, TransitionGroup, onMounted, shallowRef, ref, markRaw } from 'vue'; import * as Misskey from 'misskey-js'; import { useInterval } from '@@/js/use-interval.js'; +import { useDocumentVisibility } from '@@/js/use-document-visibility.js'; import { getScrollContainer, scrollToTop } from '@@/js/scroll.js'; import type { BasicTimelineType } from '@/timelines.js'; import type { SoundStore } from '@/preferences/def.js'; @@ -224,6 +225,20 @@ onUnmounted(() => { } }); +const visibility = useDocumentVisibility(); +let isPausingUpdate = false; + +watch(visibility, () => { + if (visibility.value === 'hidden') { + isPausingUpdate = true; + } else { // 'visible' + isPausingUpdate = false; + if (isTop()) { + releaseQueue(); + } + } +}); + let adInsertionCounter = 0; const MIN_POLLING_INTERVAL = 1000 * 10; @@ -237,7 +252,7 @@ if (!store.s.realtimeMode) { // TODO: 先頭のノートの作成日時が1日以上前であれば流速が遅いTLと見做してインターバルを通常より延ばす useInterval(async () => { paginator.fetchNewer({ - toQueue: !isTop(), + toQueue: !isTop() || isPausingUpdate, }); }, POLLING_INTERVAL, { immediate: false, @@ -246,7 +261,7 @@ if (!store.s.realtimeMode) { useGlobalEvent('notePosted', (note) => { paginator.fetchNewer({ - toQueue: !isTop(), + toQueue: !isTop() || isPausingUpdate, }); }); } @@ -257,7 +272,7 @@ useGlobalEvent('noteDeleted', (noteId) => { function releaseQueue() { paginator.releaseQueue(); - scrollToTop(rootEl.value); + scrollToTop(rootEl.value!); } function prepend(note: Misskey.entities.Note & MisskeyEntity) { @@ -267,7 +282,7 @@ function prepend(note: Misskey.entities.Note & MisskeyEntity) { note._shouldInsertAd_ = true; } - if (isTop()) { + if (isTop() && !isPausingUpdate) { paginator.prepend(note); } else { paginator.enqueue(note); diff --git a/packages/frontend/src/components/MkStreamingNotificationsTimeline.vue b/packages/frontend/src/components/MkStreamingNotificationsTimeline.vue index 869d848d90..e21adab36c 100644 --- a/packages/frontend/src/components/MkStreamingNotificationsTimeline.vue +++ b/packages/frontend/src/components/MkStreamingNotificationsTimeline.vue @@ -45,7 +45,9 @@ SPDX-License-Identifier: AGPL-3.0-only import { onUnmounted, onMounted, computed, useTemplateRef, TransitionGroup, markRaw, watch } from 'vue'; import * as Misskey from 'misskey-js'; import { useInterval } from '@@/js/use-interval.js'; +import { useDocumentVisibility } from '@@/js/use-document-visibility.js'; import type { notificationTypes } from '@@/js/const.js'; +import { getScrollContainer, scrollToTop } from '@@/js/scroll.js'; import XNotification from '@/components/MkNotification.vue'; import MkNote from '@/components/MkNote.vue'; import { useStream } from '@/stream.js'; @@ -92,6 +94,49 @@ if (!store.s.realtimeMode) { }); } +function isTop() { + if (scrollContainer == null) return true; + if (rootEl.value == null) return true; + const scrollTop = scrollContainer.scrollTop; + const tlTop = rootEl.value.offsetTop - scrollContainer.offsetTop; + return scrollTop <= tlTop; +} + +function releaseQueue() { + paginator.releaseQueue(); + scrollToTop(rootEl.value!); +} + +let scrollContainer: HTMLElement | null = null; + +function onScrollContainerScroll() { + if (isTop()) { + paginator.releaseQueue(); + } +} + +watch(rootEl, (el) => { + if (el && scrollContainer == null) { + scrollContainer = getScrollContainer(el); + if (scrollContainer == null) return; + scrollContainer.addEventListener('scroll', onScrollContainerScroll, { passive: true }); // ほんとはscrollendにしたいけどiosが非対応 + } +}, { immediate: true }); + +const visibility = useDocumentVisibility(); +let isPausingUpdate = false; + +watch(visibility, () => { + if (visibility.value === 'hidden') { + isPausingUpdate = true; + } else { // 'visible' + isPausingUpdate = false; + if (isTop()) { + releaseQueue(); + } + } +}); + function onNotification(notification) { const isMuted = props.excludeTypes ? props.excludeTypes.includes(notification.type) : false; if (isMuted || window.document.visibilityState === 'visible') { @@ -101,7 +146,11 @@ function onNotification(notification) { } if (!isMuted) { - paginator.prepend(notification); + if (isTop() && !isPausingUpdate) { + paginator.prepend(notification); + } else { + paginator.enqueue(notification); + } } }