feat(bookmarks): shared useBookmarks panel + wired into all four readers

This commit is contained in:
2026-09-08 01:06:39 +08:00
parent b2c5c4a445
commit d989668650
5 changed files with 206 additions and 14 deletions
+16 -6
View File
@@ -2,6 +2,7 @@ import DOMPurify from "dompurify";
import { marked } from "marked";
import { useEffect, useMemo, useRef, useState, type MouseEvent, type ReactNode } from "react";
import { apiRaw } from "../api/client";
import { useBookmarks } from "../components/Bookmarks";
import { btn } from "../components/ui";
import { chapterText, seekChapter, splitChapters } from "../lib/chapters";
import { decodeText } from "../lib/encoding";
@@ -130,9 +131,10 @@ export default function TextReader(props: ReaderProps) {
return props.book.format === "md" ? <MdView {...props} /> : <TxtView {...props} />;
}
function MdView({ book, initialLocator, chrome }: ReaderProps) {
function MdView({ book, initialLocator, initialPercent, chrome }: ReaderProps) {
const boxRef = useRef<HTMLDivElement>(null);
const saver = useProgressSaver(book.id);
const saver = useProgressSaver(book.id, { locator: initialLocator, percent: initialPercent });
const bm = useBookmarks({ book, capture: saver.capture, seek: (l) => seek(Number(l.scrollFraction) || 0) });
const restored = useRef(false);
const pr = useReaderPrefs();
const [frac, setFrac] = useState(0);
@@ -168,7 +170,7 @@ function MdView({ book, initialLocator, chrome }: ReaderProps) {
if (err) return <LoadState text={err} onRetry={retry} />;
if (text == null) return <LoadState text="正文加载中…" />;
return (
<div className="rd-surface flex h-full flex-col" data-rd={pr.prefs.theme}>
<div className="rd-surface relative flex h-full flex-col" data-rd={pr.prefs.theme}>
<div
ref={boxRef}
onScroll={onScroll}
@@ -181,15 +183,16 @@ function MdView({ book, initialLocator, chrome }: ReaderProps) {
dangerouslySetInnerHTML={{ __html: html ?? "" }}
/>
</div>
{chrome.on && <ReaderSheet pr={pr} frac={frac} onSeek={seek} />}
{chrome.on && <ReaderSheet pr={pr} frac={frac} onSeek={seek}>{bm.btn}</ReaderSheet>}
{bm.panel}
</div>
);
}
// 整本塞一个 <pre> 会冻死浏览器:按章渲染,一章一个滚动块。
function TxtView({ book, initialLocator, chrome }: ReaderProps) {
function TxtView({ book, initialLocator, initialPercent, chrome }: ReaderProps) {
const boxRef = useRef<HTMLDivElement>(null);
const saver = useProgressSaver(book.id);
const saver = useProgressSaver(book.id, { locator: initialLocator, percent: initialPercent });
const inited = useRef(false);
const restoreFrac = useRef<number | null>(null);
const targetCi = useRef(0);
@@ -199,6 +202,11 @@ function TxtView({ book, initialLocator, chrome }: ReaderProps) {
const [toc, setToc] = useState(false);
const pr = useReaderPrefs();
const { text, err, retry } = useFileText(book.file_url ?? "");
const bm = useBookmarks({
book,
capture: saver.capture,
seek: (l) => goChapter(Number(l.ch) || 0, Number(l.scrollFraction) || 0),
});
const chapters = useMemo(() => (text == null ? null : splitChapters(text)), [text]);
@@ -279,6 +287,7 @@ function TxtView({ book, initialLocator, chrome }: ReaderProps) {
</div>
{chrome.on && (
<ReaderSheet pr={pr} frac={total} onSeek={seek}>
{bm.btn}
{chapters.length > 1 && (
<>
<button
@@ -330,6 +339,7 @@ function TxtView({ book, initialLocator, chrome }: ReaderProps) {
</nav>
</>
)}
{bm.panel}
</div>
);
}