sulfide2085
dsh-custom-context-menu
DSH Web custom context menu: extensible provider API + open workspace folder in Explorer
- Stars
- 0
- Language
- JavaScript
- Created
- Aug 16, 2026
- Updated
- Aug 16, 2026
Introduction
dsh-custom-context-menu
English · 中文
DeepSeek Harness(DSH)Web UI 的自定义右键菜单插件:全局接管浏览器右键,提供零依赖的扩展菜单框架,并内置“右键工作区 → 在资源管理器中打开文件夹”。
特性
- 全局 contextmenu 接管:捕获阶段拦截浏览器右键,输入框/文本域/可编辑元素与菜单自身除外
- 可扩展 provider:一行
register()注册菜单项,支持分隔线、分组标题、禁用项、危险项、hint - 键盘导航:↑/↓ 循环选择,Enter/Space 触发
- 智能定位:菜单超右/下边缘自动翻转,并夹紧在视口内
- 无命中不打扰:没有 provider 命中时放行浏览器原生菜单,不再显示“暂无菜单项”占位
- 打开工作区文件夹:右键侧边栏工作区行 → 「打开文件夹」→ host 半直接拉起系统文件管理器
- Windows:
explorer.exe <path>,路径自动规范化为反斜杠,并通过前台激活 helper 把窗口带到最前 - macOS:
open <path> - Linux:
xdg-open <path>
- Windows:
- 失败可见:打开前校验目录存在;路径失效时明确报错,不会假报“已打开文件夹”
安装
本地安装
cd dsh-custom-context-menu
pnpm install
cd ..
dsh plugin --profile web add ./dsh-custom-context-menu
重启 DSH Web 生效。
从 GitHub 安装
dsh plugin --profile web add "https://github.com/sulfide2085/dsh-custom-context-menu.git"
dsh plugin add会把包以link:/Git 依赖方式装进 profile,并把本包cordis.patch.yml声明的 bundle 层追加到dsh.profile.bundles。
快速使用:注册菜单项
在浏览器控制台(F12)或其他插件中:
window.dshContextMenu.register((target, event) => {
const row = target.closest("[data-session-row]");
if (row === null) return null; // 不处理,交给下一个 provider
return [
{ type: "label", text: row.dataset.title },
{ label: "复制标题", onClick: () => navigator.clipboard.writeText(row.dataset.title) },
{ type: "separator" },
{ label: "删除会话", danger: true, onClick: () => { /* ... */ } }
];
});
菜单项模型
| 形式 | 说明 |
|---|---|
"字符串" | 简写 item,label 即字符串 |
{ label, onClick?, disabled?, danger?, hint? } | 常规项;onClick(item, ctx) 的 ctx 为 { target, x, y } |
{ type: "separator" } | 分隔线(连续分隔线自动压缩,首尾丢弃) |
{ type: "label", text } | 只读分组标题 |
完整 API(window.dshContextMenu)
| 成员 | 说明 |
|---|---|
register(provider) | 注册 (target, event) => items[] | null,返回注销函数;多个 provider 依次询问,第一个非空结果胜出 |
setEnabled(bool) / enabled | 全局开关;关闭后完全放行浏览器原生右键菜单 |
open(x, y, items) | 命令式在视口坐标弹菜单(不经过拦截器) |
close() / isOpen / itemCount | 关闭 / 状态查询 |
debug() | 诊断各部件状态,便于排障 |
工作区“打开文件夹”的匹配规则
右键目标向上查找 div[role="treeitem"],三路匹配 ctx.workspaces.list 条目,任一唯一命中即提供菜单项:
- 行尾省略号按钮的
aria-label匹配官方 workspace 字典 - 行内首个文本 span 匹配 workspace
title - 行内首个文本 span 匹配目录
path的 basename(title 被重命名时)
重名工作区不提供菜单项,避免打开错误目录。
行为约定
- 输入框 / 文本域 / 可编辑元素右键 → 放行原生菜单(保留复制/粘贴)
- 自定义菜单自身右键 → 放行(避免关掉又弹)
- 其余区域且 provider 命中 →
preventDefault()+ 弹出自定义菜单 - 无 provider 命中 → 关闭已打开的自定义菜单并放行原生菜单
- 关闭方式:点击菜单项 / 点击菜单外 / Esc / Tab / 锚点滚出视口 / 窗口缩放或失焦
- 菜单尾部固定附“打开浏览器菜单”,点击后下一次右键显示浏览器原生菜单(仅一次)
文件结构
| 文件 | 作用 |
|---|---|
lib/client.js | 浏览器半:全局拦截 + 菜单框架 + 工作区 provider(点击调用 folderOpener/open 远程) |
lib/index.js | host 半:folderOpener Typert 远程服务(路径校验、平台命令分派、Windows 前台激活) |
cordis.patch.yml | bundle patch,安装时自动挂载 |
test/ | node:test 单元测试 |
开发
pnpm install
pnpm test
依赖
zod:host 侧 wire schema@deepseek-ai/dsh-typert-protocol:Typert Remote 服务基类@deepseek-ai/cordis:Service 基类(Typert 协议 peer)
浏览器半(lib/client.js)为手写 bundle,零依赖、全部原生 DOM。
限制
- 菜单项为单层(无子菜单)
- 工作区匹配依赖 DSH Web 官方 workspace 行结构与
ctx.workspaces服务 - 依赖浏览器
contextmenu事件;Electron/浏览器外壳同样适用
License
MIT