feat(reader): immersive chrome + WeChat-Read/Mihon-style bottom sheets — tap-center toggles header/toolbar, progress & page sliders seek anywhere, WYSIWYG theme swatches, A± font size; CBZ gains 连读/整页/适高 modes, edge-tap & arrow-key paging, persisted mode (seekChapter helper + test)

This commit is contained in:
2026-09-07 23:03:26 +08:00
parent 7e63c21902
commit 065fde5b56
9 changed files with 330 additions and 102 deletions
+13 -3
View File
@@ -1,5 +1,5 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Suspense, lazy, type ComponentType, type LazyExoticComponent } from "react";
import { Suspense, lazy, useCallback, useMemo, useState, type ComponentType, type LazyExoticComponent } from "react";
import { Link, useNavigate, useParams } from "react-router-dom";
import { api } from "../api/client";
import type { Format } from "../api/types";
@@ -22,6 +22,11 @@ export default function Reader() {
const qc = useQueryClient();
const nav = useNavigate();
const { isAdmin } = useAuth();
const [chromeOn, setChromeOn] = useState(true);
const toggle = useCallback(() => setChromeOn((v) => !v), []);
const show = useCallback(() => setChromeOn(true), []);
const chrome = useMemo(() => ({ on: chromeOn, toggle, show }), [chromeOn, toggle, show]);
const bookQ = useQuery({ queryKey: ["book", id], queryFn: () => api.getBook(id), enabled: Number.isFinite(id) });
const progQ = useQuery({ queryKey: ["progress"], queryFn: api.listProgress });
@@ -52,12 +57,17 @@ export default function Reader() {
const R = READERS[book.format];
if (!R) return <Msg text={`暂不支持的格式:${book.format}`} />;
const row = (progQ.data ?? []).find((r) => r.library_id === book.library_id && r.path === book.path);
return <R book={book} initialLocator={row?.locator} />;
return <R book={book} initialLocator={row?.locator} chrome={chrome} />;
})();
return (
<div className="flex h-full flex-col">
<header className="flex shrink-0 items-center gap-3 border-b border-stone-800/80 bg-stone-950/80 px-4 py-2 text-sm backdrop-blur">
<header
className={
"flex shrink-0 items-center gap-3 overflow-hidden border-b border-stone-800/80 bg-stone-950/80 px-4 text-sm backdrop-blur transition-all duration-300 " +
(chromeOn ? "h-11 py-2 opacity-100" : "h-0 border-b-0 py-0 opacity-0")
}
>
<Link
to="/"
className="shrink-0 rounded-md px-2 py-1 text-stone-400 transition-colors hover:bg-stone-800 hover:text-stone-100"