hectorqin
|
da1aa15174
|
fix(reader): 稳定阅读位置并重整手机阅读界面
固定工具栏显隐时的正文区域,保存最新阅读偏移并抑制亚像素量测抖动。重组底部四入口、半屏面板和章内滑杆,采用 Lucide SVG 图标并新增浅绿主题;补齐 PDF 内容传递。
验证:构建通过,498 项单元测试、29 项接口及分页测试、30 项 UI 检查通过。
|
2026-09-20 17:02:40 +08:00 |
|
hectorqin
|
3c3e461a49
|
feat(web): 界面图标换成自带字体,补 UI/UX 规范并统一设计令牌
按钮里放的是 Unicode 字符——`☰` `⚙` `✕` `⟨` `⟩` `+` `🗂` `📁` `⮝`——
和 emoji。八个字形来自八个字族,光学重量各不相同;`🗂` `📁` `⏮` 在部分平台
被渲染成彩色的,而一个彩色 emoji 出现在单色工具栏里,是「没有设计过」最直接的
信号。不同平台的字体回退还不同,同一个界面在 Android 和桌面浏览器上长得不一样。
此外样式表里所有按钮都没有 `:focus-visible` 规则,键盘走一遍看不到焦点在哪。
所以这次做三件事:图标改成自带字体、把缺失的交互状态补齐、把散落的字面量收成
设计令牌,并把规范写成文档。
图标用字体而不是内联 SVG,理由是**一致性可以被强制**:字体把字形变成字符,它
继承 `color` 和 `font-size`,没有自己的几何,因此没有可以漂移的地方;而三十个
内联 SVG 组件里,颜色、尺寸、线宽每一项都是单个字形可以漂移的位置。字体是
bundle 里的一个 16KB 相对路径文件,`file://` 下能加载,与「一份产物两个宿主」
的既有约束一致。
字形由 `web/tools/icons/` 下的脚本从描边路径编译:路径 → 折线 → 圆头矩形 →
精确布尔并(Clipper)→ TTF。先做过一版「栅格化 + 边界追踪」,失败了:笔画宽度
只有一个栅格单元,而逐单元的边界规则无法表达「边界在一格宽的图形上左右交替」,
结果是一个圆被追踪成 58 段碎片。精确布尔没有这种情形。
放弃的方案:内联 SVG(前面说的漂移问题)、把图标做成 React 组件并逐个手绘
(每个都是独立的几何,光学重量必然不一致)、从 CDN 引用图标字体(本产品必须
在无外网环境下工作)。
界面改动:
- `Icon` 组件 + 37 个字形,码位表由生成器写出,测试直接解析字体 `cmap` 核对
- `IconButton` 的 `icon` 是字形名而非任意 `children`,传错名字是编译错误;
`label` 必填,字形始终 `aria-hidden`
- 补上全部按钮的 `:focus-visible`(本次之前完全缺失)
- 间距/字号/动效收成令牌;`prefers-reduced-motion` 通过折叠时长令牌实现,
而不是给每条动画各加一个媒体查询
- 悬停包进 `@media (hover: hover) and (pointer: fine)`,避免触屏上按钮停在悬停态
验证:`web` 336 个测试通过(新增 9 个图标契约测试);`typecheck`、`build`、
`check-shell.sh`、`verify-web-assets.sh` 全过;字体已确认被打进 Android assets。
真浏览器(Chromium + 真实服务端)实测了书架、书库管理、阅读器三个屏幕的浅色与
深色主题,以及键盘 Tab 下的焦点环。产物 259.5KB → 259.5KB(gzip 81.4KB),
新增 16KB 字体、CSS 减到 24.4KB。
Ref: #29
|
2026-09-18 07:08:02 +08:00 |
|
CodeBuddy
|
586bec25b1
|
refactor(web): 界面层迁移到 Preact,排版层保持命令式
书架、登录、书库管理、以及阅读器的铬层(顶栏/页脚/目录/设置面板/朗读条)
从「构造函数建节点 + 实例字段 + 若干 render*()」改成组件树 + 单一状态。
阅读舞台——分页、注入文档、shadow root、量测列宽——仍然零框架。
为什么换:
- 同一份状态有多个副本。分段控件的选中态既在 settings 又在 aria-pressed 上,
靠点击处理器手工同步;LoginScreen 的通知/错误/注册开关/按钮文案是四个节点
被五个地方改,漏一处不报错。
- 重建与状态互相矛盾。朗读引擎行要在 HTTP 能力探测回来时重新生成,而整块
面板重建会丢掉面板滚动位置,于是长出一套「只替换 [data-speech=group]」
的定点重建;改成组件后探测只是改状态,diff 天然保住滚动位置。
边界(写在 ReaderScreen 里,不靠自觉):
- stage 由类持有并交给 ReaderView,只通过 ref 回调挂进组件树;组件决定它
放在哪,内容一概不碰。mountUI() 是组件树与类式屏幕之间唯一的接缝,
路由仍不认识框架,测试仍打在真实 DOM 上。
配套:
- toolkit.tsx:IconButton / Button / Segmented / SwitchRow / Scrim 这些原语
各把 a11y 与状态细节收在一处,消掉两处手工 aria-pressed 同步。
- vendor/preact.ts:框架唯一入口,只 re-export preact 与 preact/hooks,
无 preact/compat、无 react-dom、无额外 signals 依赖。
- tsconfig: jsx=react-jsx, jsxImportSource=preact。
- 修一处回归:书库管理的状态行改由 listing 推导,写入结果跨重载保留。
验证:web 全量 260 项测试通过(manager.test.ts 断言逻辑未改),
typecheck、build、android check-shell.sh、build-web-assets.sh 均通过。
产物 235KB → 245KB(gzip 71.2KB → 77.2KB)。
|
2026-09-17 17:52:18 +08:00 |
|
cnb.dQC5J6ohAGA
|
be606c0184
|
fix
|
2026-09-17 13:42:22 +08:00 |
|
cnb.dQC5J6ohAGA
|
ca68a03e5d
|
merge: 解决 auto/init-server-scaffold-81b3 与 main 的冲突
两个分支各自演进:一侧引入格式注册表、H5 客户端与 Android 外壳,另一侧
引入漫画目录书与逐页取图。冲突集中在扫描器、metadata、library 路由和 web
客户端,逐项按「两边能力都要保留」的原则合并。
合并后修复了 4 个被冲突掩盖的真实缺陷:
- scanner 同时保留了两套目录书实现,类型检查与测试均无法通过;改为统一
走格式注册表,目录书在文件遍历之前发现,其内容不再重复入库。
- /manifest 的 files 对漫画目录书返回的是文件夹路径,客户端无法逐页取图;
改为由 manifest 派生真实页路径。
- 单个文件端点只认 book_files 行,漫画目录书的每一页都 404;改为目录书
额外认领其目录下的路径,并修正路径归一化使穿越尝试仍被拒绝。
- 目录书判定只看图片数量,会把夹着一本书的目录整体吞掉;改为注册表驱动
的判定,含书籍文件的目录始终是书架。
同时修复了 web 端两套测试运行器互相无法执行对方用例的问题(vitest 与
node:test 混跑会静默跳过),以及 web 侧 5 处类型错误。
Ref: #9
|
2026-09-16 18:04:11 +08:00 |
|
CodeBuddy
|
5e8d14b460
|
fix(web): 用 iframe 承载章节文档,浏览器实测逼出来的四处修正
真机(Chromium,390x844)跑通整条链路后,之前"看起来对"的地方全都露馅了。
这一轮把渲染层按实测重做,四个都是必须修的:
1. **章节必须是真文档,不能是 shadow DOM 里的片段。**
章节里的 <img src> 和 <link rel=stylesheet> 是浏览器自己去请求的,
没法带 Authorization 头。把它们注入宿主文档的结果是每个插图、每个
样式表都 401 —— 书渲染成无样式无图。iframe 是真浏览上下文,它的
子资源请求是普通导航,URL 里带什么就发什么。所以:外壳用 shadow DOM
(挡的是客户端样式往书里漏,这是它真正能挡的方向),书本身用 iframe。
2. **子资源的 token 只能走 query。**
服务端新增 `queryTokenAllowed`,白名单式放行:仅 GET、仅
/api/v1/books/*/assets|cover|content。触发扫描、写元数据这类端点一律
不接受 URL 里的 token —— 那才是真正会泄到日志和 Referer 的地方。
新增 5 个测试固定这条边界。
3. **`column-fill: auto` 需要确定高度,否则根本没有分页。**
body 不设高度时它会撑到内容高度,整章排成一列,scrollWidth 等于
clientWidth —— 翻页手势完全无效,一章只显示一页。修完 390px 宽的一章
是 3732px / 9 页,翻页步长精确 422px(390 + 32 gutter)。
同时去掉 scroll-snap:吸附目标是列边缘而不是我做出来的页边界,
会导致 scrollLeft 落在两页之间、甚至一次跳两页。
4. **`pageWidth` 是上限不是实际宽度。**
手机上 720px 的列比屏幕还宽,同样退化成"一章一页"。取
min(读者设置, 实际宽度)。
另外发现并按格式分流修好的:PDF(kind=document)之前落进了图片翻页器,
读者看到的是一个破图;现在走 PdfReader。
封面 <img> 也漏了 token,一并改成 api.coverUrl()。
顺带把一个设计缺陷拆开了:目录不再从 manifest 的 groups 取。
groups 是传输分窗,它把目录显示成「第 1 章 – 第 40 章」——服务端的分页
边界漏进了 UI。新增 /books/:id/toc,按格式返回真正的目录
(epub 的 spine 标题、txt 的章节、漫画的卷),与分窗互不干扰。
|
2026-09-16 15:28:34 +08:00 |
|
CodeBuddy
|
d0a475919d
|
feat(web): H5 客户端,精排 epub 渲染 + 多格式阅读
用户的三个问题在这里落地:
1. 「H5 是一个完整的客户端」——同一份构建产物既由服务端托管(`/` 直接
就是完整阅读器),也可被 Android 外壳打进 assets 复用,两种宿主只差
一个 serverUrl。
2. 「纯 H5 时 epub 等资源是否需要 iframe 或 shadow DOM」——用 shadow DOM,
但不是为了隔离出版方样式(那做不到也不该做:书必须能给自己排版)。
它挡的是反方向:客户端自身的 CSS 不会漏进书里。iframe 只在 PDF 那一处
用,因为 PDF 需要自己的浏览上下文,理由写在 render/container.ts 里。
3. 「Android 端 epub 用 H5 渲染,其他类型用原生」——按 kind 分流,
capabilities() 明确声明:reflowable 走 WebView,paged/document 在
Android 交回原生。理由写进 reader-view.ts:WebView 每页一个合成层,
300MB 的画册在一台中端机上就是 OOM。
服务端同步配合的两处:
- 资源流式下发 + HTTP Range
- epub 按包内路径寻址、items/manifest 分窗
前端无框架:渲染热路径是命令式 DOM 操作,引框架只会把"最小覆盖 CSS"
的策略搞复杂;最终产物 35KB(gzip 10.7KB)。
|
2026-09-16 14:45:04 +08:00 |
|