From 61648f1114d8c77ea6306cbd6beb516a2c194bbf Mon Sep 17 00:00:00 2001 From: caoqianming Date: Wed, 29 Jul 2026 16:35:20 +0800 Subject: [PATCH] Add context menus to task and file lists --- CHANGELOG.md | 4 ++++ PROGRESS.md | 3 ++- core/__init__.py | 2 +- web/static/js/chat.js | 8 +++++++- web/static/js/dom.js | 27 ++++++++++++++++++++++++++- web/static/js/files.js | 10 +++++++++- 6 files changed, 49 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08da960..c63bcbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ > 所以不是每个版本号都有条目。条目格式 `## <版本> — <日期>`,新条目加在最上面。 > 工程口径的完整记录见 `PROGRESS.md` / git log。 +## 0.60.12 — 2026-07-29 + +- 任务列表以及文件、文件夹列表现在支持右键操作,可在鼠标位置直接打开与「⋯」按钮一致的菜单;菜单靠近屏幕边缘时会自动调整位置。 + ## 0.60.11 — 2026-07-29 - 网页工作台进一步回归清爽白色界面:助手回复改为开放式文档流,工具调用与结果合并成可展开的紧凑活动项;同时修复语音输入停止后按钮仍显示“停止”的问题。 diff --git a/PROGRESS.md b/PROGRESS.md index 5a1a949..6d19853 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -2,7 +2,7 @@ > 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9`。 -最后更新:2026-07-29(Web 对话流去卡片化,bump 0.60.11) +最后更新:2026-07-29(任务与文件列表右键菜单,bump 0.60.12) --- @@ -23,6 +23,7 @@ ### 2026-07-29 +- **07-29 / 0.60.12 / 任务与文件列表右键菜单**:任务行、文件行和目录行增加原生 `contextmenu` 入口,直接复用既有 `taskMenuItems` / `fileMenuItems`,因此与「⋯」按钮保持相同权限、状态禁用、确认弹框和后端调用;单例浮层菜单增加按鼠标坐标定位及视口双向钳制,右键任务不切换当前会话、右键文件不触发打开。纯前端加法,无 API、schema、migration 或依赖变化;3 个 JS 文件语法及 diff 格式检查通过,浏览器实例不可用故真实鼠标回放留页面复核。 - **07-29 / 0.60.11 / Web 对话流去卡片化 + 工具活动合并 + 语音状态修复**:工作台背景统一以纯白为主,浅灰仅保留在 hover、代码块等局部状态;助手历史/实时回复移除重复角色标签、大卡片边框、左色条与运行态红框,改为左锚开放文档流。历史态按 `tool_call_id` 将 assistant 调用与 tool result 合并,实时态复用同一 details 原位更新结果,统一为默认折叠单行活动项(执行中/成功/失败状态明确),产物 chip、媒体 banner 与后台进程活化继续保留;顶部 task progress dock 的位置和逻辑未改。另修短按语音转连续录音时直接覆写按钮 `textContent`、销毁 SVG/label 导致停止后无法恢复“语音”的问题,收口为 class/label/ARIA 状态函数。相关 Python 24 项 + Node 5 项及 JS 语法、diff 格式检查通过;无 API、schema、migration 或依赖变化。 - **07-29 / 0.60.10 / Web 工作台移动端空间优化**:根据第二轮手机实图反馈把暖白/浅米灰改回中性白色,桌面助手回答、工具卡与任务进度恢复左侧阅读轴;手机端压缩顶栏、Tab、任务操作区、模型区、消息间距和 composer,高频操作键由 44px 收至 40px、输入框最低高度由 64px 收至 52px,并隐藏空闲态“请选择文件保存位置/就绪”占位行,运行、上传与错误等有效状态提示继续按需显示。相关 Python 21 项 + Node 5 项及 JS 语法、diff 格式检查通过;无 API、schema、migration 或依赖变化。 - **07-29 / 0.60.9 / Web 工作台视觉复核修正(桌面 + 手机实图)**:据上线截图把冷蓝灰改为暖白/浅米灰;任务与文件行恢复明确分隔,助手消息收进居中 52rem 文档型回答块,工具与进度对齐同一阅读轨道;同时修掉 `task_progress`-only assistant 被过滤工具后仍留下空“助手”壳。手机端 composer 改为独立状态行 + 3 个 44px SVG 图标键 + 发送键,缩短 placeholder、加入安全区并压缩操作/模型区;录音与润色状态改只更新 label/ARIA,不再覆盖 SVG DOM。相关 Python 21 项 + Node 5 项及 JS 语法、HTML/SVG 结构、CSS 括号检查通过;无 API、schema、migration 或依赖变化。 diff --git a/core/__init__.py b/core/__init__.py index f061a6d..4a8aa3d 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -1,3 +1,3 @@ # zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。 # 改版本只动这一行。 -__version__ = "0.60.11" +__version__ = "0.60.12" diff --git a/web/static/js/chat.js b/web/static/js/chat.js index 71a079e..a112d4e 100644 --- a/web/static/js/chat.js +++ b/web/static/js/chat.js @@ -5,7 +5,7 @@ // 各入口模块顶层自绑;对外导出 loadTaskList / loadModels / selectTask // (供 main enterApp、embed、files、newtask)。 import { state, LS_TASK_FILTERS_COLLAPSED } from "./state.js"; -import { $, showMenu } from "./dom.js"; +import { $, showMenu, showMenuAtPoint } from "./dom.js"; import { api } from "./api.js"; import { escapeHtml, fmtTime, fmtTokens, fmtTimeAgo, taskUsageTooltip, formatTaskUsage, formatContextStats, formatUsageStats, ctxRingHtml } from "./format.js"; import { renderMd, highlightIn } from "./markdown.js"; @@ -238,6 +238,12 @@ function renderTaskList(tasks, append = false) { if (e.target.closest(".dd-toggle")) return; // 菜单按钮点击不触发选中 selectTask(el.dataset.tid); }; + el.oncontextmenu = (e) => { + e.preventDefault(); + const t = state.tasksById[el.dataset.tid]; + if (!t) return; + showMenuAtPoint(e.clientX, e.clientY, taskMenuItems(t)); + }; const btn = el.querySelector(".task-menu"); if (btn) { btn.onclick = (e) => { diff --git a/web/static/js/dom.js b/web/static/js/dom.js index ba2def2..5ebe5e4 100644 --- a/web/static/js/dom.js +++ b/web/static/js/dom.js @@ -6,7 +6,7 @@ export const $ = (id) => document.getElementById(id); // ───── floating dropdown menu (single instance) ───── // 用 position: fixed 单例避免被 pane overflow 裁剪;按位置算出右上角对齐 let _menuItems = null; -export function showMenu(triggerEl, items) { +function renderMenu(items) { _menuItems = items; const menu = $("floating-menu"); menu.innerHTML = items.map((it) => { @@ -23,6 +23,20 @@ export function showMenu(triggerEl, items) { if (item && item.onclick) item.onclick(); }; }); + return menu; +} + +function clampMenuPosition(menu, left, top) { + const mh = menu.offsetHeight || 120; + const mw = menu.offsetWidth || 132; + menu.style.left = Math.max(4, Math.min(left, window.innerWidth - mw - 4)) + "px"; + menu.style.right = "auto"; + menu.style.top = Math.max(4, Math.min(top, window.innerHeight - mh - 4)) + "px"; + menu.style.visibility = ""; +} + +export function showMenu(triggerEl, items) { + const menu = renderMenu(items); // 默认右下展开;若空间不足则改向上 const rect = triggerEl.getBoundingClientRect(); menu.style.visibility = "hidden"; @@ -45,6 +59,15 @@ export function showMenu(triggerEl, items) { } menu.style.visibility = ""; } + +// 右键菜单按指针位置展开;靠近视口边缘时向内收,避免操作项被裁掉。 +export function showMenuAtPoint(clientX, clientY, items) { + const menu = renderMenu(items); + menu.style.visibility = "hidden"; + menu.classList.add("show"); + clampMenuPosition(menu, clientX, clientY); +} + export function hideMenu() { _menuItems = null; $("floating-menu").classList.remove("show"); @@ -54,6 +77,8 @@ document.addEventListener("click", (e) => { if (e.target.closest("#floating-menu")) return; hideMenu(); }, true); +// 在非自定义菜单区域右键时,先收掉可能残留的浮层;列表行的冒泡处理随后会重新打开。 +document.addEventListener("contextmenu", hideMenu, true); window.addEventListener("resize", hideMenu); // 滚动 pane 时菜单位置失效,直接关 document.addEventListener("scroll", hideMenu, true); diff --git a/web/static/js/files.js b/web/static/js/files.js index 0e8daae..dbc79ae 100644 --- a/web/static/js/files.js +++ b/web/static/js/files.js @@ -5,7 +5,7 @@ // 反向依赖:openFilePreview(preview)、logout(auth)、以及 main 的 glue // downloadFile / selectTask / loadTaskList / loadFolderSuggestions(后续随 media/tasks/newtask 模块化再迁)。 import { state } from "./state.js"; -import { $, showMenu } from "./dom.js"; +import { $, showMenu, showMenuAtPoint } from "./dom.js"; import { api } from "./api.js"; import { escapeHtml, humanSize } from "./format.js"; import { openFilePreview } from "./preview.js"; @@ -267,6 +267,14 @@ function renderFiles(data) { showMenu(btn, fileMenuItems(e)); }; }); + $("file-list").querySelectorAll(".file-row").forEach((row) => { + row.oncontextmenu = (ev) => { + ev.preventDefault(); + const e = state.entriesByRel[row.dataset.rel]; + if (!e) return; + showMenuAtPoint(ev.clientX, ev.clientY, fileMenuItems(e)); + }; + }); } function fileMenuItems(e) {