diff --git a/components/PageReader.tsx b/components/PageReader.tsx index 6484d1b..dea9b6a 100644 --- a/components/PageReader.tsx +++ b/components/PageReader.tsx @@ -3,7 +3,10 @@ import { useState, useEffect, useRef, useCallback } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; -import { writeLastReadChapter } from "@/components/ReadingProgressButton"; +import { + readProgress, + writeProgress, +} from "@/components/ReadingProgressButton"; type ChapterMeta = { id: number; @@ -48,12 +51,30 @@ export function PageReader({ const [pages, setPages] = useState([]); const [currentChapterNum, setCurrentChapterNum] = useState(startChapterNumber); + const [currentPageNum, setCurrentPageNum] = useState(() => { + if (typeof window === "undefined") return 1; + const p = readProgress(mangaSlug); + if (p && p.chapter === startChapterNumber && p.page > 1) return p.page; + return 1; + }); const hiddenByScrollRef = useRef(false); const fetchChapterIdxRef = useRef( chapters.findIndex((c) => c.number === startChapterNumber) ); + // Initialize offset from saved progress so the first fetch starts AT the + // user's last-read page — previous pages are skipped entirely const offsetRef = useRef(0); + const initialPageRef = useRef(1); + const offsetInitedRef = useRef(false); + if (!offsetInitedRef.current && typeof window !== "undefined") { + offsetInitedRef.current = true; + const p = readProgress(mangaSlug); + if (p && p.chapter === startChapterNumber && p.page > 1) { + offsetRef.current = p.page - 1; + initialPageRef.current = p.page; + } + } const loadingRef = useRef(false); const doneRef = useRef(false); // Count of pages already loaded — tracked via ref so fetchBatch stays stable @@ -232,14 +253,20 @@ export function PageReader({ hiddenByScrollRef.current = true; setShowUI(false); } - let current = startChapterNumber; + // Nothing loaded yet — don't overwrite the resumed-page state + if (pages.length === 0) return; + let chapter = pages[0].chapterNumber; + let page = pages[0].pageNumber; for (let i = 0; i < pages.length; i++) { const el = pageRefsRef.current.get(i); if (!el) continue; - if (el.offsetTop <= y + 80) current = pages[i].chapterNumber; - else break; + if (el.offsetTop <= y + 80) { + chapter = pages[i].chapterNumber; + page = pages[i].pageNumber; + } else break; } - setCurrentChapterNum(current); + setCurrentChapterNum(chapter); + setCurrentPageNum(page); }; const onScroll = () => { if (rafId) return; @@ -252,10 +279,15 @@ export function PageReader({ }; }, [pages, startChapterNumber]); - // Persist reading progress whenever the visible chapter changes + // Persist progress as user scrolls. offsetRef has been pre-seeded above + // so the first fetched page IS the resumed page — no scroll restoration + // needed. useEffect(() => { - writeLastReadChapter(mangaSlug, currentChapterNum); - }, [mangaSlug, currentChapterNum]); + writeProgress(mangaSlug, { + chapter: currentChapterNum, + page: currentPageNum, + }); + }, [mangaSlug, currentChapterNum, currentPageNum]); const currentChapter = chapters.find((c) => c.number === currentChapterNum) ?? diff --git a/components/ReadingProgressButton.tsx b/components/ReadingProgressButton.tsx index eb7abf3..d7ebf16 100644 --- a/components/ReadingProgressButton.tsx +++ b/components/ReadingProgressButton.tsx @@ -13,34 +13,59 @@ type Props = { chapters: ChapterLite[]; }; +export type ReadingProgress = { + chapter: number; + page: number; +}; + function storageKey(slug: string) { return `sunnymh:last-read:${slug}`; } -export function readLastReadChapter(slug: string): number | null { +export function readProgress(slug: string): ReadingProgress | null { if (typeof window === "undefined") return null; const raw = window.localStorage.getItem(storageKey(slug)); if (!raw) return null; + // New format: JSON { chapter, page } + if (raw.startsWith("{")) { + try { + const parsed = JSON.parse(raw) as ReadingProgress; + if ( + typeof parsed.chapter === "number" && + typeof parsed.page === "number" && + parsed.chapter > 0 && + parsed.page > 0 + ) { + return parsed; + } + } catch { + return null; + } + return null; + } + // Legacy format: bare chapter number const n = Number(raw); - return Number.isFinite(n) && n > 0 ? n : null; + return Number.isFinite(n) && n > 0 ? { chapter: n, page: 1 } : null; } -export function writeLastReadChapter(slug: string, chapter: number) { +export function writeProgress(slug: string, progress: ReadingProgress) { if (typeof window === "undefined") return; - window.localStorage.setItem(storageKey(slug), String(chapter)); + window.localStorage.setItem(storageKey(slug), JSON.stringify(progress)); } export function ReadingProgressButton({ mangaSlug, chapters }: Props) { - const [lastRead, setLastRead] = useState(null); + const [progress, setProgress] = useState(null); useEffect(() => { - setLastRead(readLastReadChapter(mangaSlug)); + setProgress(readProgress(mangaSlug)); }, [mangaSlug]); if (chapters.length === 0) return null; const first = chapters[0]; const resumeChapter = - lastRead !== null ? chapters.find((c) => c.number === lastRead) : null; + progress !== null + ? chapters.find((c) => c.number === progress.chapter) + : null; const target = resumeChapter ?? first; return (