fix(preview): enable wheel and touch scrolling

This commit is contained in:
caoqianming 2026-08-12 13:37:09 +08:00
parent ac2a632aa8
commit 531ad51015
6 changed files with 18 additions and 4 deletions

View File

@ -5,6 +5,10 @@
> 所以不是每个版本号都有条目。条目格式 `## <版本> — <日期>`,新条目加在最上面。
> 工程口径的完整记录见 `PROGRESS.md` / git log。
## 0.64.1 — 2026-08-12
- PDF 和 PPT 连续预览现在支持鼠标滚轮与手机手势滑动,不再只能拖动右侧滚动条。
## 0.64.0 — 2026-08-12
- 已发布产物现在拥有稳定身份:重命名和移动后,历史对话中的产物仍可正常预览和下载;复制产物会创建可独立管理的副本。

View File

@ -2,7 +2,7 @@
> 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9`
最后更新:2026-08-12(artifact 稳定身份与隐藏回收,bump 0.64.0)
最后更新:2026-08-12(PDF/PPT 连续预览滚轮与触摸修复,bump 0.64.1)
---
@ -23,6 +23,8 @@
### 2026-08-12
- **08-12 / 0.64.1 / PDF/PPT 连续预览滚轮与触摸修复**:外层预览的防滚动穿透逻辑不再拦截来自 PDF 内层 viewport 的普通 wheel 事件,保留图片 Ctrl+滚轮缩放和其他预览的边界隔离PDF viewport 同步声明触摸平移与 WebView/iOS 惯性滚动使鼠标滚轮、触控板和手机上下滑动都直接驱动连续页列表。Node 前端预览 11 项、JavaScript 语法及 diff 检查通过;无 schema、migration、HTTP API、依赖或运行方式变化未连接生产 DB。
- **08-12 / 0.64.0 / artifact 稳定身份 + 隐藏回收**:新增 0028 `artifacts` 生命周期表并回填存量结构化引用;新发布消息写带 `artifact_id` 的 v2 引用,移动/重命名保持身份,复制创建独立身份并记录来源,历史卡片按身份解析最新路径。删除已发布产物时移动到用户隐藏目录 `.zcbot_artifact_trash/` 并软删记录普通文件仍物理删除Python 35 项(测试库门控 1 skip、Node 前端 11 项、mypy、Alembic 单 head、编译、Ruff 致命规则及 diff 检查通过,未连接或写入生产 DB。
- **08-12 / 0.63.11 / PDF/PPT 连续预览滚动布局修复**:补齐 PDF 预览根容器的纵向 Flex 布局与可收缩高度约束,使内部连续页 viewport 获得实际可用高度和独立滚动区域,不再被外层 `overflow:hidden` 裁断回归测试同步锁定根容器、viewport 与页列表三层布局契约。Node 前端预览 11 项、JavaScript 语法及 diff 检查通过;无 schema、migration、HTTP API、依赖或运行方式变化未连接生产 DB。

View File

@ -1,3 +1,3 @@
# zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。
# 改版本只动这一行。
__version__ = "0.64.0"
__version__ = "0.64.1"

View File

@ -88,8 +88,9 @@ test("PDF previews render a continuously scrollable lazy page list", () => {
assert.match(previewJs, /root: viewportHost/);
assert.match(previewJs, /viewportHost\.addEventListener\("scroll"/);
assert.doesNotMatch(previewJs, /class="small pdf-(?:prev|next)"/);
assert.match(previewJs, /e\.target\.closest\("\.pdf-viewport"\)\) return/);
assert.match(pageHtml, /\.pdf-preview\s*\{[^}]*display:\s*flex[^}]*flex-direction:\s*column[^}]*min-height:\s*0/s);
assert.match(pageHtml, /\.pdf-viewport\s*\{[^}]*flex:\s*1[^}]*min-height:\s*0[^}]*overflow:\s*auto/s);
assert.match(pageHtml, /\.pdf-viewport\s*\{[^}]*flex:\s*1[^}]*min-height:\s*0[^}]*overflow-y:\s*auto[^}]*touch-action:\s*pan-x pan-y/s);
assert.match(pageHtml, /\.pdf-pages-list\s*\{[^}]*flex-direction:\s*column/s);
});

View File

@ -1385,7 +1385,10 @@
.pdf-toolbar .pdf-page { width: 48px; padding: 5px 4px; text-align: center; }
.pdf-toolbar-spacer { flex: 1; }
.pdf-zoom { min-width: 38px; text-align: center; font-size: 12px; color: var(--muted); }
.pdf-viewport { flex: 1; min-height: 0; overflow: auto; padding: 12px; overscroll-behavior: contain; }
.pdf-viewport {
flex: 1; min-height: 0; overflow-x: auto; overflow-y: auto; padding: 12px;
overscroll-behavior: contain; touch-action: pan-x pan-y; -webkit-overflow-scrolling: touch;
}
.pdf-pages-list { display: flex; flex-direction: column; align-items: center; gap: 14px; min-width: max-content; }
.pdf-page-shell {
position: relative; flex: 0 0 auto; display: flex; align-items: center; justify-content: center;

View File

@ -240,6 +240,10 @@ function _bindBodyWheel(bodyEl) {
_applyZoom(z);
return;
}
// PDF 使用 body 内部的独立 viewport 连续滚动。wheel 会继续冒泡到 body
// 若仍按 body 自身是否可滚判断并 preventDefault会把内层的默认滚动一并取消。
// viewport 已用 overscroll-behavior 隔离边界穿透,因此这里直接交还给它。
if (e.target instanceof Element && e.target.closest(".pdf-viewport")) return;
// 非 ctrl:容器不可滚 / 已到边界时阻断,避免冒泡到背景滚动对话列表
const canScroll = bodyEl.scrollHeight > bodyEl.clientHeight + 1;
if (!canScroll) { e.preventDefault(); return; }