sunnymh-manga-site/components/ChapterList.tsx
yiekheng 43a2a6d3f8 Rewrite reader around known-dimension page placeholders
Replaces the prepend/flushSync/scrollBy gymnastics with placeholder divs
sized by each page's width/height. Document height is correct from the
first paint, so resume + backward scroll just work — no scroll
compensation, no gesture fights, no forced aspect ratio distorting images.

- New /api/chapters/[id]/meta returns the dim skeleton for any chapter.
- Chapter page pre-fetches the starting chapter's meta server-side and
  parallelizes the two Prisma queries via Promise.all.
- Reader renders placeholders with aspectRatio: w/h, lazy-loads image
  URLs in batches via IntersectionObserver, and prefetches the next
  chapter's meta ~3 pages from the end.
- Scroll tracker walks only the intersecting-pages set (~3–5 elements)
  instead of every loaded page per rAF.
- scroll={false} on all Links into the reader + { scroll: false } on
  double-tap router.push, plus a belt-and-suspenders rAF re-scroll, so
  resume survives soft navigation and browser scroll-restoration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 13:01:41 +08:00

51 lines
1.2 KiB
TypeScript

import Link from "next/link";
type Chapter = {
id: number;
number: number;
title: string;
};
export function ChapterList({
chapters,
mangaSlug,
}: {
chapters: Chapter[];
mangaSlug: string;
}) {
if (chapters.length === 0) {
return (
<p className="text-muted text-center py-8">No chapters available yet</p>
);
}
return (
<div className="space-y-2">
{chapters.map((ch) => (
<Link
key={ch.id}
href={`/manga/${mangaSlug}/${ch.number}`}
scroll={false}
className="flex items-center justify-between px-4 py-3 bg-surface rounded-xl hover:bg-surface-hover active:scale-[0.98] transition-all"
>
<div className="flex items-center gap-3 min-w-0">
<span className="text-accent font-bold text-sm tabular-nums shrink-0">
#{ch.number}
</span>
<span className="text-sm font-medium truncate">{ch.title}</span>
</div>
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
className="w-4 h-4 text-muted shrink-0"
>
<polyline points="9 18 15 12 9 6" />
</svg>
</Link>
))}
</div>
);
}