Skip to content
dsh.fish
Bundle

dsh-custom-context-menu

DSH Web 自定义右键菜单:全局拦截 contextmenu,提供可扩展菜单框架,并内置“打开工作区文件夹”功能

Source
sulfide2085
License
MIT
Updated
Updated 24 days ago

Readme

# dsh-custom-context-menu

[English](README.en.md) · 中文

> 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>`
- **失败可见**:打开前校验目录存在;路径失效时明确报错,不会假报“已打开文件夹”

## 安装

### 本地安装

```powershell
cd dsh-custom-context-menu
pnpm install
cd ..
dsh plugin --profile web add ./dsh-custom-context-menu
```

重启 DSH Web 生效。

### 从 GitHub 安装

```powershell
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)或其他插件中:

```js
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` 条目,任一唯一命中即提供菜单项:

1. 行尾省略号按钮的 `aria-label` 匹配官方 workspace 字典
2. 行内首个文本 span 匹配 workspace `title`
3. 行内首个文本 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` 单元测试 |

## 开发

```powershell
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

Install

dsh plugin --profile web add github:sulfide2085/dsh-custom-context-menu

Profile: web

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source