3.8 KiB
3.8 KiB
书签系统(备注 + 跳转)设计 / Bookmarks (notes + jump-to) design
日期 2026-09-08。分支 feat/bookmarks。状态:已获用户批准(会话内确认)。
目标 / Goal
阅读任意格式的书时可「加书签」,书签携带备注,可从书签列表一键跳转(seek)到原位。 Bookmarks with editable notes and one-click jump-to-position for all reader formats.
决策记录 / Decisions
- 个人可见(与 progress 同款 user 隔离),不做共享。用户选定。
- 阅读器内闭环(工具条按钮 + 侧栏),不做全局书签页。用户选定。
- 全格式(cbz/txt/md/pdf/epub):locator 存取/恢复机制四类 reader 均已存在,书签复用。用户选定。
- 否决方案:书签数组塞 progress JSONB(整包重写、并发互踩);纯 localStorage(不跨设备、与服务端进度先例分裂)。
- 删书不级联清书签:与
reading_progress既有行为一致(孤儿行 JOIN 不中即无害)。
数据模型 / Schema
backend/internal/db/schema.sql 追加(新表,无迁移痛):
CREATE TABLE IF NOT EXISTS bookmarks (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
library_id BIGINT NOT NULL,
book_path TEXT NOT NULL,
locator JSONB NOT NULL,
percent DOUBLE PRECISION NOT NULL DEFAULT 0,
note TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT now());
CREATE INDEX IF NOT EXISTS bookmarks_user_book_idx
ON bookmarks (user_id, library_id, book_path);
定位键与 progress 相同 (user_id, library_id, book_path);locator 各格式形状不变
(cbz/pdf {page},txt/md {ch,scrollFraction},epub {cfi})。
API(source of truth: cmd/webui/api/router.go)
| 方法/路径 | 语义 | 错误 |
|---|---|---|
GET /api/books/:id/bookmarks |
本书书签,percent 升序 | book 不存在 404 |
POST /api/books/:id/bookmarks {locator,percent,note?} |
建,201 返回全字段 | locator 空/percent∉[0,1]/note>500 字符 → 400 |
PATCH /api/bookmarks/:id {note} |
仅改备注 | 非本人/不存在 → 404 |
DELETE /api/bookmarks/:id |
删,204 | 非本人/不存在 → 404 |
- (library_id, book_path) 由服务端从 :id 解析,客户端不传路径。
- 全部登录可见(admin 不特殊);「非本人」统一 404,不泄露存在性。
- store 层新增
ListBookmarks/InsertBookmark/UpdateBookmarkNote/DeleteBookmark,均带 user_id 条件。
前端 / Frontend
useProgressSaver暴露capture(): {locator, percent} | null——每次report()记下最新值, 打开时以该书已有进度(initialLocator/percent)播种。书签 = 当前进度快照 + 备注。components/Bookmarks.tsx共享组件(目录侧栏同款交互/样式 rd-*):- 工具条「书签」按钮 → 弹出备注输入(可空,Enter 保存) → POST → 刷新列表。
- 侧栏行:
percent%+ 备注(空则省略) + 时间;点行 =seek(locator)并收起;✎ 行内改备注;✕ 删除。
- 各 reader 提供
seek(locator):cbzjump(page)已有;txtgoChapter+scrollFraction已有; epub 复用初始恢复路径(display(cfi));pdf 补一个滚动到页的函数(~5 行)。
错误处理 / Errors
- 网络失败:toast 报错,列表不半更新(react-query invalidate 全书)。
- 竞态(两端同时加):无幂等要求,各自成条。
- percent 越界/locator 非法:400,前端表单先行禁用空提交。
测试 / Tests
- 后端
handlers/bookmarks_test.go:CRUD 全链路、他人 404、note 超长 400、book 不存在 404、 percent 越界 400、列表排序。 - 前端:
npm run check绿;真实浏览器点验(加/跳/改/删)。不为组件引入新测试框架。 - 回归:
go vet+go test -p 1 ./...。
不做 / Non-goals
全局书签页、书详情页书签区、去重、标签/分组、跨用户共享、书签导出。有真实需求再做。