fix(frontend): タブが不可視なあいだのpaginationのアップデートを停止するように (#16243)

* fix(frontend): タブが不可視なあいだのpaginationのアップデートを停止するように

* fix lint

* 待たない
This commit is contained in:
かっこかり 2025-07-03 18:52:16 +09:00 committed by GitHub
parent 7c44881ca8
commit 179d990c39
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 69 additions and 5 deletions

View File

@ -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 { computed, watch, onUnmounted, provide, useTemplateRef, TransitionGroup, onMounted, shallowRef, ref, markRaw } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { useInterval } from '@@/js/use-interval.js'; import { useInterval } from '@@/js/use-interval.js';
import { useDocumentVisibility } from '@@/js/use-document-visibility.js';
import { getScrollContainer, scrollToTop } from '@@/js/scroll.js'; import { getScrollContainer, scrollToTop } from '@@/js/scroll.js';
import type { BasicTimelineType } from '@/timelines.js'; import type { BasicTimelineType } from '@/timelines.js';
import type { SoundStore } from '@/preferences/def.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; let adInsertionCounter = 0;
const MIN_POLLING_INTERVAL = 1000 * 10; const MIN_POLLING_INTERVAL = 1000 * 10;
@ -237,7 +252,7 @@ if (!store.s.realtimeMode) {
// TODO: 1TL // TODO: 1TL
useInterval(async () => { useInterval(async () => {
paginator.fetchNewer({ paginator.fetchNewer({
toQueue: !isTop(), toQueue: !isTop() || isPausingUpdate,
}); });
}, POLLING_INTERVAL, { }, POLLING_INTERVAL, {
immediate: false, immediate: false,
@ -246,7 +261,7 @@ if (!store.s.realtimeMode) {
useGlobalEvent('notePosted', (note) => { useGlobalEvent('notePosted', (note) => {
paginator.fetchNewer({ paginator.fetchNewer({
toQueue: !isTop(), toQueue: !isTop() || isPausingUpdate,
}); });
}); });
} }
@ -257,7 +272,7 @@ useGlobalEvent('noteDeleted', (noteId) => {
function releaseQueue() { function releaseQueue() {
paginator.releaseQueue(); paginator.releaseQueue();
scrollToTop(rootEl.value); scrollToTop(rootEl.value!);
} }
function prepend(note: Misskey.entities.Note & MisskeyEntity) { function prepend(note: Misskey.entities.Note & MisskeyEntity) {
@ -267,7 +282,7 @@ function prepend(note: Misskey.entities.Note & MisskeyEntity) {
note._shouldInsertAd_ = true; note._shouldInsertAd_ = true;
} }
if (isTop()) { if (isTop() && !isPausingUpdate) {
paginator.prepend(note); paginator.prepend(note);
} else { } else {
paginator.enqueue(note); paginator.enqueue(note);

View File

@ -45,7 +45,9 @@ SPDX-License-Identifier: AGPL-3.0-only
import { onUnmounted, onMounted, computed, useTemplateRef, TransitionGroup, markRaw, watch } from 'vue'; import { onUnmounted, onMounted, computed, useTemplateRef, TransitionGroup, markRaw, watch } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { useInterval } from '@@/js/use-interval.js'; import { useInterval } from '@@/js/use-interval.js';
import { useDocumentVisibility } from '@@/js/use-document-visibility.js';
import type { notificationTypes } from '@@/js/const.js'; import type { notificationTypes } from '@@/js/const.js';
import { getScrollContainer, scrollToTop } from '@@/js/scroll.js';
import XNotification from '@/components/MkNotification.vue'; import XNotification from '@/components/MkNotification.vue';
import MkNote from '@/components/MkNote.vue'; import MkNote from '@/components/MkNote.vue';
import { useStream } from '@/stream.js'; 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 }); // scrollendios
}
}, { 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) { function onNotification(notification) {
const isMuted = props.excludeTypes ? props.excludeTypes.includes(notification.type) : false; const isMuted = props.excludeTypes ? props.excludeTypes.includes(notification.type) : false;
if (isMuted || window.document.visibilityState === 'visible') { if (isMuted || window.document.visibilityState === 'visible') {
@ -101,7 +146,11 @@ function onNotification(notification) {
} }
if (!isMuted) { if (!isMuted) {
paginator.prepend(notification); if (isTop() && !isPausingUpdate) {
paginator.prepend(notification);
} else {
paginator.enqueue(notification);
}
} }
} }