From ef82d103ff552a6e8ffa9fceb870156a2db7d49b Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Fri, 2 May 2025 15:42:28 +0900
Subject: [PATCH] Update MkTimeline.vue

---
 .../frontend/src/components/MkTimeline.vue    | 39 +++++++++++++++----
 1 file changed, 32 insertions(+), 7 deletions(-)

diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue
index 5fc619e169..7b083831b1 100644
--- a/packages/frontend/src/components/MkTimeline.vue
+++ b/packages/frontend/src/components/MkTimeline.vue
@@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, watch, onUnmounted, provide, useTemplateRef, TransitionGroup, onMounted, shallowRef, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import { useInterval } from '@@/js/use-interval.js';
-import { isHeadVisible, scrollToTop } from '@@/js/scroll.js';
+import { getScrollContainer, scrollToTop } from '@@/js/scroll.js';
 import type { BasicTimelineType } from '@/timelines.js';
 import type { PagingCtx } from '@/use/use-pagination.js';
 import { usePagination } from '@/use/use-pagination.js';
@@ -98,7 +98,35 @@ provide('inTimeline', true);
 provide('tl_withSensitive', computed(() => props.withSensitive));
 provide('inChannel', computed(() => props.src === 'channel'));
 
+function isTop() {
+	if (scrollContainer == null) return false;
+	if (rootEl.value == null) return false;
+	const scrollTop = scrollContainer.scrollTop;
+	const tlTop = rootEl.value.offsetTop - scrollContainer.offsetTop;
+	return scrollTop <= tlTop;
+}
+
+let scrollContainer: HTMLElement | null = null;
+
+function onScrollContainerScroll() {
+	if (isTop()) {
+		paginator.releaseQueue();
+	}
+}
+
 const rootEl = useTemplateRef('rootEl');
+watch(rootEl, (el) => {
+	if (el && scrollContainer == null) {
+		scrollContainer = getScrollContainer(el)!;
+		scrollContainer.addEventListener('scroll', onScrollContainerScroll, { passive: true }); // ほんとはscrollendにしたいけどiosが非対応
+	}
+}, { immediate: true });
+
+onUnmounted(() => {
+	if (scrollContainer) {
+		scrollContainer.removeEventListener('scroll', onScrollContainerScroll);
+	}
+});
 
 type TimelineQueryType = {
 	antennaId?: string,
@@ -122,9 +150,8 @@ const POLLING_INTERVAL =
 
 if (!store.s.realtimeMode) {
 	useInterval(async () => {
-		const isTop = rootEl.value == null ? false : isHeadVisible(rootEl.value, 16);
 		paginator.fetchNewer({
-			toQueue: !isTop,
+			toQueue: !isTop(),
 		});
 	}, POLLING_INTERVAL, {
 		immediate: false,
@@ -133,9 +160,8 @@ if (!store.s.realtimeMode) {
 }
 
 globalEvents.on('notePosted', (note: Misskey.entities.Note) => {
-	const isTop = rootEl.value == null ? false : isHeadVisible(rootEl.value, 16);
 	paginator.fetchNewer({
-		toQueue: !isTop,
+		toQueue: !isTop(),
 	});
 });
 
@@ -151,8 +177,7 @@ function prepend(note: Misskey.entities.Note) {
 		note._shouldInsertAd_ = true;
 	}
 
-	const isTop = isHeadVisible(rootEl.value, 16);
-	if (isTop) {
+	if (isTop()) {
 		paginator.prepend(note);
 	} else {
 		paginator.enqueue(note);