Commit Graph
17 Commits
Author SHA1 Message Date
hectorqin 2d7e858ecc refactor(web): drop Mantine; one ActionMenu for every ⋯ menu
- Replace every Mantine component with the toolkit (Button, Callout, IconButton) or plain markup; remove MantineProvider, its stylesheet and the --mantine variable bridge; keep its form-font reset in reader.css. Uninstall @mantine/core and @mantine/hooks.
- ActionMenu/ActionSheet (ui/action-menu.tsx): the file manager's Mantine Menu, the shelf pick dialog and the media <details> menus (catalog, narrators, detail, libraries, library rows, folders, chapters, queue, resource panel, favorites filter) are one compact Sheet menu.
- Toolkit Callout for page-level messages; operation failures go to FloatingNotice; .page-state for whole-page loading/error.
- Shared SourcesHeader and SourceBookDetailSheet; source editor and credentials pages rebuilt without Mantine, deleting a source now asks first.
- Remove dead code: SettingsPage (unrouted), MiniPlayer, ui/dom.ts (formatters moved to lib/format.ts), the unused metadata form dialog, useAppThemeDark.
- ui:review react-app passes again: fixed stale steps (/settings, mini player, login race, missing-edition detail).
2026-10-21 11:38:12 +08:00
hectorqin fbb6dc65cf feat(ui): one Sheet for every overlay; drop 来源与刮削
- ui/sheet.tsx Sheet/ConfirmSheet replace the centred Modal, SweetAlert confirms, DialogView overlays, the reader Panel, the shelf settings floating card, the two media dialogs and Mantine Modal.
- Phones: bottom sheet. Wide windows: right-edge drawer; compact sheets (confirm, prompt, pick) open next to the tap or focused control.
- One set of exits (close, Escape, backdrop), 关闭 label, focus starts on 取消, danger confirms.
- Remove sweetalert2 and the unused @mantine/notifications.
- Remove the 来源与刮削 media settings panel and its entries in 我的 and 影音设置; TMDB and MusicBrainz stay configurable in 系统设置 > 服务配置.
- docs/ui.md 3.7 describes the rule; tests and review scripts follow the new markup.
2026-10-21 07:30:27 +08:00
hectorqin 2aaa4280c7 refactor(web): migrate client to React architecture 2026-10-03 12:12:30 +08:00
hectorqin 80124d8c4e chore: commit current workspace changes 2026-10-02 11:24:25 +08:00
hectorqin ad17211950 feat(media): use DPlayer and searchable source filters 2026-10-01 09:20:00 +08:00
hectorqin 73f9eb6e20 feat(media): add isolated media libraries and playback
Support local and OpenList libraries, metadata, independent media navigation, themes, and audio/video playback. Preserve approved v2 prototypes and current documentation; archive superseded review artifacts and keep generated validation output outside version control.
2026-09-27 18:22:51 +08:00
hectorqin 07f9a73698 fix(ui): use floating confirmations and notifications 2026-09-24 17:48:50 +08:00
hectorqin 7bff77eaf8 feat(reader): deliver P0-P2 reading tools and ecosystem integrations
Add reliable reading progress, verified backup and restore, reading tools, offline downloads, source quality checks, personal credential status, OPDS serving and verified WebDAV backup uploads.
2026-09-24 07:47:32 +08:00
hectorqin 47baee014e feat(ui): unify page feedback with global Notyf notifications
Route login, shelf, library, file operations, reading, sources and extension feedback through one compact themed notification host. Keep native modal feedback in the top layer, escape remote text, update progress in place and let reading controls receive clicks beneath message text. Cover lifecycle, error handling and layout with unit and Chromium checks.
2026-09-23 10:13:03 +08:00
hectorqin 72c4405435 chore: keep host repository plugin agnostic 2026-09-22 17:47:21 +08:00
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