Reader: treat browser refresh as implicit resume

Refreshing the chapter page was landing the user back at the top, because
only ?resume=1 triggered the saved-position restore. Added isPageReload()
helper (checks performance navigation entry type === 'reload') and OR'd
it with the resume flag. Refresh now restores to the last scroll
position; drawer/list clicks still go to top as intended.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
yiekheng 2026-04-15 22:29:14 +08:00
parent fb2b032d73
commit 95135942a2
2 changed files with 18 additions and 4 deletions

View File

@ -15,6 +15,7 @@ import {
readProgress,
writeProgress,
} from "@/components/ReadingProgressButton";
import { isPageReload } from "@/lib/progress";
import { LoadingLogo } from "@/components/LoadingLogo";
import {
calcScrollRatio,
@ -70,7 +71,8 @@ export function PageReader({
const [currentChapterNum, setCurrentChapterNum] =
useState(startChapterNumber);
const [currentPageNum, setCurrentPageNum] = useState(() => {
if (typeof window === "undefined" || !resume) return 1;
if (typeof window === "undefined") return 1;
if (!resume && !isPageReload()) return 1;
const p = readProgress(mangaSlug);
if (p && p.chapter === startChapterNumber && p.page > 1) return p.page;
return 1;
@ -322,15 +324,17 @@ export function PageReader({
// All reader Links use scroll={false} to preserve scroll during in-reader
// nav (natural scroll between chapters updates URL without remount). On
// a fresh mount we must actively position the scroll: resume-to-saved
// if ?resume=1 AND the saved chapter matches; otherwise top.
// a fresh mount we position scroll: resume-to-saved if ?resume=1 (from
// 继续阅读) OR a page reload (so browser refresh preserves position).
// Plain chapter-link clicks from drawer / list go to top.
const resumeDoneRef = useRef(false);
useLayoutEffect(() => {
if (resumeDoneRef.current) return;
resumeDoneRef.current = true;
const instantTop = (top: number) =>
window.scrollTo({ top, behavior: "instant" as ScrollBehavior });
if (!resume) {
const shouldResume = resume || isPageReload();
if (!shouldResume) {
instantTop(0);
return;
}

View File

@ -66,3 +66,13 @@ export function writeProgress(
function defaultStorage(): StorageLike | null {
return typeof window === "undefined" ? null : window.localStorage;
}
export function isPageReload(): boolean {
if (typeof window === "undefined") return false;
if (typeof performance === "undefined") return false;
const entries = performance.getEntriesByType("navigation");
if (entries.length === 0) return false;
return (
(entries[0] as PerformanceNavigationTiming).type === "reload"
);
}