秉烛夜读。
在线体验:https://talebook.github.io/candle-reader/
不用安装,打开即可试读《西游记》,体验划线、评论、回复和赞踩:
| 入口 | 说明 |
|---|---|
| 介绍页 | 功能介绍,以及下面各个入口 |
| 从第一回开始 | 以读者「悟空迷」的身份从第一回读起 |
| 继续上次阅读 | 回到上次读到的位置 |
| 以游客身份 | 未登录状态,互动时引导登录 |
| 诊断模式 | 记录选中文字相关事件,用于真机排查 |
评论数据只存在浏览器内存里,不连任何后端,刷新即重置。每次合并进 main 自动重新发布。
make dev # 打开 http://localhost:5001/demo.html
make dist # 打包,并生成可直接部署的 dist/demo.html
# 合并进 main 后由 .github/workflows/pages.yml 发布到上面的在线体验地址演示页用《西游记》和一个纯内存版宿主(demo/memory-host.js,完整实现下面的回调契约),可以体验划线、评论、回复、赞踩、「我的」和游客登录引导;数据只在内存里,刷新即重置。
打包产物以 npm 包 @talebook/candle-reader 发布,包内只有 dist/,宿主(talebook)锁定版本后把 dist/ 拷到自己的静态目录使用。
git tag v26.10.09 && git push origin v26.10.09版本号用发版日期 vYY.MM.DD 。推送 v* tag 后 .github/workflows/release.yml 会打包,把 dist/ 以 tag 对应的版本号发布到 npm(semver 不允许前导零,v26.10.09 发布为 26.10.9 ;同一天只能发一版),同时把 dist.zip 上传到 GitHub Release。发布走 npm Trusted Publishing(GitHub OIDC),仓库里不保存 npm token;包在 npmjs.com 的 Trusted Publisher 需指向本仓库的 release.yml 。package.json 里的 version 只作参考,以 tag 为准。
本地联调时不必发版:make dist install 把产物直接拷进本地 talebook 的静态目录。
划线、文字评论和整书评论是同一种记录,统称「评论」。阅读器负责全部界面:选区工具栏、评论抽屉、完整评论页、评论详情页、编辑框、赞踩与回复。阅读器自身不请求任何评论或账号服务,数据与登录态全部通过初始化时注入的 annotation_callbacks 交给宿主:
new Reader('#app', {
book_url: '/books/1/',
book_id: 1,
annotation_callbacks: {
async user() {}, // 当前读者 { id, nickname, avatar };游客返回 null
async login() {}, // 游客触发互动时调用,由宿主引导登录
async load({ chapter }) {}, // 当前读者在本章的记录(用于正文标记),返回 Annotation[]
async list({ scope, chapter, paragraph_cfi, cursor, limit }) {}, // 顶层记录分页
async summary({ chapter }) {}, // [{ paragraph_cfi, count }],段尾气泡的公开评论数
async get({ id }) {}, // 单条记录
async replies({ root_id, cursor, limit }) {}, // 某条评论的回复分页
async save(annotation, { book_id, book_url }) {}, // 新建或修改,返回保存后的记录
async remove({ id }) {}, // 真删除
async vote({ id, value }) {}, // value: 1 赞 / -1 踩 / 0 取消;返回 { like_count, dislike_count, user_vote }
},
})除 save 外,每个回调的第一个参数都会并入 { book_id, book_url }。load 与 save 必须同时提供;其余回调缺失时,对应功能会显示明确错误,不会静默改写到本地。
分页回调返回 { items, next_cursor, has_more }(也接受纯数组,视为没有下一页)。list 的 scope:
| scope | 返回 |
|---|---|
paragraph |
归属 paragraph_cfi 这一段的公开文字评论 |
chapter |
本章全部公开文字评论,不含整书评论 |
book |
全书公开评论,含整书评论 |
mine |
当前读者在本书的全部记录,含私密划线与私密文字 |
公开范围的列表由宿主排序:当前读者自己的在前,再按时间倒序,排序覆盖整个结果集后再分页。回复按时间正序。
记录字段:
id/client_id:稳定标识;save按它们判断新建还是修改。annotation_type:highlight(划线)、note(文字评论)、book_comment(整书评论)。is_private:划线固定true。回复没有自己的公开范围,跟随主评论;主评论为私密时,其回复只对主评论作者可见。chapter、cfi、range_cfi、quote_text:cfi是评论归属的位置——划线为精确选区,文字评论为所在段落(跨段选区归属最后一段),整书评论为全书开头;range_cfi保留真实选区。content、created_at、author_name、is_mine。like_count、dislike_count、user_vote(1/-1/0)、reply_count。- 回复:
root_id(所属主评论)、reply_to_id(回复对象,空表示回复主评论)、reply_to_name、thread_id(所属第一层回复,由宿主根据reply_to_id推出;第一层回复为空)。
宿主需要保证:非作者访问私密记录及其回复时按不存在处理;remove 主评论时连同全部回复和投票删除,删除第一层回复时连同其下回复删除;私密记录不接受投票和回复。
不传 annotation_callbacks 时,阅读器按 book_id(缺省时按 book_url)隔离并保存到浏览器 localStorage:只有当前浏览器这一位读者,公开范围不会真的对他人生效。回调已经注入但执行失败时不会静默写入本地,避免宿主数据和本地数据产生不可见分叉。
“设置”里有两个相互独立的开关,都不删除数据,评论入口始终可用:
- 显示全部划线和评论:控制正文里的划线标记和段尾的评论数量气泡(有评论的段落只显示气泡,不铺底色)。
- 选中文字后显示工具栏:控制选区工具栏,关闭后选中文字只出现系统自带的菜单。
底部固定为目录、白天/夜晚、评论、设置,听书在顶部。完整的交互约定见 AGENTS.md。
传入 audiobook_callbacks 后显示「听书」入口。宿主提供章节清单(manifest)和时间轴(timeline),可选提供收听会话与进度上报(start_session / report_progress / end_session)。阅读器不直接请求有声书接口,音频文件按清单里的 audio_url 由浏览器加载。完整契约见 DESIGN.md 第 6.3 节。
旧 readerSettings 自动迁移:早期的总开关 notes_enabled 已去掉,曾经关闭它的读者迁移后两个开关都按关闭处理;show_comments 保留,show_selection_toolbar 缺失时取旧 show_annotations(缺失按开启)。