diff --git a/CHANGELOG.md b/CHANGELOG.md index 08223eb..058b812 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ > 所以不是每个版本号都有条目。条目格式 `## <版本> — <日期>`,新条目加在最上面。 > 工程口径的完整记录见 `PROGRESS.md` / git log。 +## 0.64.1 — 2026-08-12 + +- PDF 和 PPT 连续预览现在支持鼠标滚轮与手机手势滑动,不再只能拖动右侧滚动条。 + ## 0.64.0 — 2026-08-12 - 已发布产物现在拥有稳定身份:重命名和移动后,历史对话中的产物仍可正常预览和下载;复制产物会创建可独立管理的副本。 diff --git a/PROGRESS.md b/PROGRESS.md index bb5cf03..f14ce10 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -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。 diff --git a/core/__init__.py b/core/__init__.py index e3e3972..cd0184d 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -1,3 +1,3 @@ # zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。 # 改版本只动这一行。 -__version__ = "0.64.0" +__version__ = "0.64.1" diff --git a/tests/frontend_preview.test.mjs b/tests/frontend_preview.test.mjs index 70e3b32..bdab1fc 100644 --- a/tests/frontend_preview.test.mjs +++ b/tests/frontend_preview.test.mjs @@ -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); }); diff --git a/web/static/dev.html b/web/static/dev.html index 5aa6812..d491e6c 100644 --- a/web/static/dev.html +++ b/web/static/dev.html @@ -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; diff --git a/web/static/js/preview.js b/web/static/js/preview.js index 4dcfdab..8f4a21b 100644 --- a/web/static/js/preview.js +++ b/web/static/js/preview.js @@ -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; }