@winking-r/dsh-uniterminal 0.2.16 → 0.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +73 -0
- package/README.md +59 -15
- package/docs/SECURITY.md +5 -5
- package/lib/client.js +29 -11
- package/package.json +2 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# 更新日志
|
|
2
|
+
|
|
3
|
+
遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
|
4
|
+
|
|
5
|
+
`0.x` 阶段:**次要版本可能包含不兼容变更**(配置项与插件 API 仍可能调整),补丁版本只修缺陷。
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.2.18] - 2026-10-29
|
|
10
|
+
|
|
11
|
+
纯文档版本,**代码与 0.2.17 完全相同**。
|
|
12
|
+
|
|
13
|
+
### 文档
|
|
14
|
+
|
|
15
|
+
- README 新增**截图**(主机列表 / 终端 / 连接表单 / SFTP / 外观):演示数据为 `demo01`–`demo03` 与 RFC 5737 文档段 `192.0.2.0/24`;
|
|
16
|
+
图片经绝对 URL 引用,npm 页面同样能显示。
|
|
17
|
+
- 安装说明改为**一条命令**:`dsh plugin --profile <profile 名> add @winking-r/dsh-uniterminal@latest`;
|
|
18
|
+
手工方式(`pnpm add` + 编辑 `dsh.profile.bundles`)作为备选保留。
|
|
19
|
+
- **已知限制去掉开发日志式条目**:划掉的旧问题、「已解决(M4-x)」、里程碑编号、"已在真实服务器验证"这类记录都不再出现在用户文档里;
|
|
20
|
+
只保留用户实际会遇到的行为,凭据细节统一链到 [docs/SECURITY.md](docs/SECURITY.md)。
|
|
21
|
+
- [SECURITY.md](docs/SECURITY.md) 同步去掉里程碑编号与历史叙述,改为陈述当前行为。
|
|
22
|
+
- 新增 [CONTRIBUTING.md](CONTRIBUTING.md):源码断言的约定、DOM 测试模板、CSS 契约。
|
|
23
|
+
|
|
24
|
+
## [0.2.17] - 2026-10-29
|
|
25
|
+
|
|
26
|
+
### 修复
|
|
27
|
+
|
|
28
|
+
- **行尾「更多操作」按钮的菜单被面板右边缘裁掉**:锚点靠右时菜单仍向右伸展,伸出面板后被宿主的
|
|
29
|
+
`overflow` 裁掉,「移入分组…」「测试连接」只显示半截。现在锚点过半宽时改为**右对齐、向左展开**;
|
|
30
|
+
鼠标右键、`Shift+F10`/菜单键、「⋯」按钮三条入口共用同一套判定(`src/client/menu-anchor.ts`)。
|
|
31
|
+
|
|
32
|
+
### 文档
|
|
33
|
+
|
|
34
|
+
- 新增 `CONTRIBUTING.md`:源码断言的约定(改 CSS/文案要同步更新断言、不要删断言)、
|
|
35
|
+
DOM 测试模板(`// @vitest-environment jsdom` + `afterEach(cleanup)`)、CSS 契约、
|
|
36
|
+
以及**禁止用固定字符数切源码**这条踩过两次的坑。
|
|
37
|
+
- README 补「右栏承载」一节:装了 `DSH-better-sidebar` 优先注册到 `ctx.betterSidebar`,
|
|
38
|
+
否则回落官方 `sidebarRightTabs`。
|
|
39
|
+
|
|
40
|
+
## [0.2.16] - 2026-10-29
|
|
41
|
+
|
|
42
|
+
首个公开发布版本(MIT)。
|
|
43
|
+
|
|
44
|
+
### 新增
|
|
45
|
+
|
|
46
|
+
- SSH 终端(xterm.js)、SFTP 文件管理、端口转发(`-L` / `-R` / `-D`)、跳板链、分组管理、外观设置。
|
|
47
|
+
- 一个侧边栏标签装多条会话,顶部会话栏切换;同一主机连两次显示 `#2`;刷新页面自动接回会话。
|
|
48
|
+
- **凭据交给操作系统密码箱**(Windows 凭据管理器 / macOS 钥匙串 / Linux Secret Service),
|
|
49
|
+
插件自身不实现任何加密;只有机器没有系统密码箱时才回落明文文件(0600 + 原子写),并在界面如实说明。
|
|
50
|
+
- 失败提示区分 7 类原因(超时 / 拒绝 / DNS / 不可达 / 主机指纹 / 认证 / 未知)并给出下一步动作;
|
|
51
|
+
内部字段名不再出现在界面上(细节只进 diag)。
|
|
52
|
+
|
|
53
|
+
### 无障碍
|
|
54
|
+
|
|
55
|
+
- 右键菜单与确认弹框:打开即聚焦、方向键/Home/End、`Tab` 焦点陷阱、关闭后**把焦点还给触发元素**。
|
|
56
|
+
- 提示条改为常驻 live region(成功 `status/polite`、失败 `alert/assertive`),读屏能听见错误。
|
|
57
|
+
- SFTP 文件行支持键盘选中(`aria-pressed`);主机行「连接 / 测试」的可访问名带主机名。
|
|
58
|
+
|
|
59
|
+
### 设计
|
|
60
|
+
|
|
61
|
+
- 引入 token 层(字号 13/14/16 三级阶梯、圆角 4/6/10/999、灰阶与遮罩、语义色单一来源)。
|
|
62
|
+
- 内联样式收敛 66%(541 → 182 条声明);重复选择器 4 组 → 0 组。
|
|
63
|
+
|
|
64
|
+
### 质量
|
|
65
|
+
|
|
66
|
+
- 严格 TypeScript(`strict` + `noUncheckedIndexedAccess`):`:any` 与 `@ts-ignore` 均为 0。
|
|
67
|
+
- 64 个测试文件 / 915 个用例(含 jsdom 真实交互用例);`pnpm audit --prod` 零已知漏洞。
|
|
68
|
+
- `prepublishOnly` 强制跑 typecheck + test + build,过不了发不出去。
|
|
69
|
+
|
|
70
|
+
[Unreleased]: https://github.com/WinKing-R/dsh-uniterminal/compare/v0.2.18...HEAD
|
|
71
|
+
[0.2.18]: https://github.com/WinKing-R/dsh-uniterminal/compare/v0.2.17...v0.2.18
|
|
72
|
+
[0.2.17]: https://github.com/WinKing-R/dsh-uniterminal/compare/v0.2.16...v0.2.17
|
|
73
|
+
[0.2.16]: https://github.com/WinKing-R/dsh-uniterminal/releases/tag/v0.2.16
|
package/README.md
CHANGED
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
# UniTerminal · DSH 远程终端插件
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@winking-r/dsh-uniterminal)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://github.com/WinKing-R/dsh-uniterminal)
|
|
6
|
+
|
|
3
7
|
SSH / SFTP / 端口转发,挂在 DSH 右侧栏里。**一个侧边栏标签里装多条会话**(顶部会话栏切换;同一台主机连两次显示 `#2`),
|
|
4
8
|
每条会话的终端、文件、隧道三视图并列。
|
|
5
9
|
|
|
10
|
+
## 截图
|
|
11
|
+
|
|
12
|
+
| 主机列表:分组 · 搜索 · 行内「测试 / 连接」,行尾 `⋯` 是其余操作 | 终端:一个侧边栏标签装多条会话,顶部会话栏切换(同一主机连两次显示 `#2`) |
|
|
13
|
+
|---|---|
|
|
14
|
+
| <img src="https://raw.githubusercontent.com/WinKing-R/dsh-uniterminal/main/docs/images/hosts.png" width="400"> | <img src="https://raw.githubusercontent.com/WinKing-R/dsh-uniterminal/main/docs/images/terminal.png" width="400"> |
|
|
15
|
+
|
|
16
|
+
| 连接表单:记住的密码交给**操作系统密码箱** | SFTP:目录浏览、上传下载、进度 | 外观:字体、字号、行高、主题模式 |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| <img src="https://raw.githubusercontent.com/WinKing-R/dsh-uniterminal/main/docs/images/connect-form.png" width="260"> | <img src="https://raw.githubusercontent.com/WinKing-R/dsh-uniterminal/main/docs/images/sftp.png" width="260"> | <img src="https://raw.githubusercontent.com/WinKing-R/dsh-uniterminal/main/docs/images/appearance.png" width="260"> |
|
|
19
|
+
|
|
20
|
+
> 截图使用演示数据(主机名 `demo01`–`demo03`,地址为 [RFC 5737](https://www.rfc-editor.org/rfc/rfc5737) 文档保留段 `192.0.2.0/24`)。
|
|
21
|
+
> 图片用**绝对 URL** 引用:npm 页面不解析相对路径,相对写法在 npmjs.com 上会显示破图。
|
|
22
|
+
|
|
6
23
|
## 状态
|
|
7
24
|
|
|
8
25
|
| 项 | 值 |
|
|
9
26
|
|---|---|
|
|
10
|
-
| 版本 | `0.2.
|
|
27
|
+
| 版本 | `0.2.18`(`0.x`:API 与配置项仍可能变,按语义化版本升号;变更见 [CHANGELOG](CHANGELOG.md)) |
|
|
11
28
|
| 许可证 | **MIT**(见 [LICENSE](LICENSE))——可自由使用、修改、商用,保留版权声明即可 |
|
|
12
29
|
| 环境 | DSH `>=0.2.1-rc.2`、Node `>=20` |
|
|
13
30
|
|
|
@@ -23,17 +40,33 @@ SSH / SFTP / 端口转发,挂在 DSH 右侧栏里。**一个侧边栏标签里
|
|
|
23
40
|
## 安装
|
|
24
41
|
|
|
25
42
|
前提:DSH `>=0.2.1-rc.2`(见本包 `package.json` 的 `dsh.engines`)、Node `>=20`。
|
|
26
|
-
|
|
43
|
+
|
|
44
|
+
### 一条命令(推荐)
|
|
45
|
+
|
|
46
|
+
DSH 自带 CLI 会把「装包 + 登记到 profile」一步做完:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
dsh plugin --profile desktop add @winking-r/dsh-uniterminal@latest
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`--profile` 填你的 profile 名 —— `~/.dsh/profiles/` 下有哪些目录就用哪个(常见 `desktop` 或 `web`)。
|
|
53
|
+
装完重启 DSH,右栏会出现 `UniTerminal` 标签。
|
|
54
|
+
|
|
55
|
+
> 没装 `dsh` 命令,或者要装本地构建(`link:` / `tgz`)时,用下面的手动方式,效果等价。
|
|
56
|
+
|
|
57
|
+
### 手动方式(等价,两步)
|
|
27
58
|
|
|
28
59
|
```bash
|
|
29
|
-
# 在 DSH 的 desktop profile 目录下执行
|
|
30
60
|
cd ~/.dsh/profiles/desktop
|
|
31
61
|
|
|
32
|
-
#
|
|
33
|
-
pnpm add
|
|
62
|
+
# npm 上的正式包
|
|
63
|
+
pnpm add @winking-r/dsh-uniterminal
|
|
64
|
+
|
|
65
|
+
# 或者:改代码即生效(开发用)
|
|
66
|
+
pnpm add "link:<本仓库绝对路径>"
|
|
34
67
|
|
|
35
|
-
#
|
|
36
|
-
pnpm add "file:<tgz
|
|
68
|
+
# 或者:装打包好的 tgz(不需要自己构建)
|
|
69
|
+
pnpm add "file:<tgz 绝对路径>"
|
|
37
70
|
```
|
|
38
71
|
|
|
39
72
|
然后在 `~/.dsh/profiles/desktop/package.json` 的 `dsh.profile.bundles` 数组里追加一行本包名:
|
|
@@ -44,6 +77,19 @@ pnpm add "file:<tgz 的绝对路径>"
|
|
|
44
77
|
|
|
45
78
|
重启 DSH。若右栏没有自动出现本插件,用右栏的 `+` 手动添加一个 `UniTerminal` 标签。
|
|
46
79
|
|
|
80
|
+
### 右栏承载:官方 `sidebarRight` 或第三方 `DSH-better-sidebar`
|
|
81
|
+
|
|
82
|
+
本插件**双注册 + 运行时探测**(`src/client/right-pane.ts`):
|
|
83
|
+
|
|
84
|
+
| 你的 DSH 里装了 | 插件注册到 | 结果 |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| [`DSH-better-sidebar`](https://github.com/omdsh-dev/DSH-better-sidebar) | `ctx.betterSidebar`(**优先**) | 右栏像 IDE 那样多标签并存,本插件占一个标签 |
|
|
87
|
+
| 只有官方 `@deepseek-ai/dsh-client-ui-sidebar-right` | `ctx.sidebarRightTabs` | 回落到官方右栏标签 |
|
|
88
|
+
|
|
89
|
+
两种都能用;**没有 better-sidebar 也能跑**(会自动回落)。
|
|
90
|
+
若右栏始终不出现本插件,先确认这两者至少有一个已启用 —— 诊断见
|
|
91
|
+
[`docs/acceptance-m0.md`](docs/acceptance-m0.md)(`target=none` 表示两套注册表都不可用)。
|
|
92
|
+
|
|
47
93
|
> **改动代码后必须重启 DSH**:宿主半边(`lib/index.js`)在启动时加载,硬刷新页面不会生效。
|
|
48
94
|
|
|
49
95
|
### 卸载
|
|
@@ -60,7 +106,7 @@ pnpm remove @winking-r/dsh-uniterminal
|
|
|
60
106
|
|
|
61
107
|
| 域 | 能力 |
|
|
62
108
|
|---|---|
|
|
63
|
-
| 主机 | 添加 / 编辑 / 删除(二次确认)/ 分组;从 `~/.ssh/config`
|
|
109
|
+
| 主机 | 添加 / 编辑 / 删除(二次确认)/ 分组;从 `~/.ssh/config` 导入;连接前「测试」连通性;**孤儿会话可接管或关闭**(接管不需要重新输入密码) |
|
|
64
110
|
| 认证 | 密码、私钥(读本地路径或粘贴内容)、**两者可同时启用**;按主机「记住密码」;跳板链 `ProxyJump` |
|
|
65
111
|
| 终端 | xterm.js,自适应尺寸,断线重连;面板重挂载与**页面刷新**后会话与输出不丢(存档落 `sessionStorage`) |
|
|
66
112
|
| 文件 | SFTP 浏览、多选上传(带进度、可取消)、下载、新建目录、删除 |
|
|
@@ -104,16 +150,14 @@ pnpm remove @winking-r/dsh-uniterminal
|
|
|
104
150
|
- **没有** → 回落明文文件 `credentials.json`,靠 `chmod 0600` 与 Windows 账户目录权限保护;Windows 上 `chmod` 实际无效,真实防护来自 NTFS ACL。**不要在多用户共用的机器上勾选「记住密码」。**
|
|
105
151
|
- **密码箱这一刻用不了**(被锁 / 服务没起)→ 临时改用本机文件,界面会明说;此前存在密码箱里的条目暂时取不到。
|
|
106
152
|
界面表单页常驻小字会说明当前用的是哪一种(详见 [docs/SECURITY.md](docs/SECURITY.md))。
|
|
107
|
-
|
|
108
|
-
本机若还留着旧版凭据文件,界面会提示「清理旧版凭据」。**清理只会丢弃旧格式文件里那份读不出来的数据——明文库里已记住的密码不受影响**;旧档里的那份密码需要重新输入一次。
|
|
153
|
+
早期试用版若在本机留下旧格式凭据文件,界面会提示「清理旧版凭据」——**清理只删掉那份读不出来的旧数据,明文库里已记住的密码不受影响**。
|
|
109
154
|
2. **不提供终端锁屏**(明确不做的决策,不是遗漏)。离开座位时已连接的会话没有保护——需要时请自行锁屏或断开连接。
|
|
110
|
-
3. **`-R`
|
|
155
|
+
3. **`-R` 远程转发**可用,但**停止隧道不会立即释放服务端的远端端口**,要等会话关闭(ssh2 不提供 cancel-tcpip-forward);在此期间到达的连接会被明确拒绝,不会穿透。
|
|
111
156
|
4. **跳板链每跳使用同一套凭据**(同一口令或同一私钥)。「跳板机用 A 钥、目标机用 B 钥」尚未支持。
|
|
112
157
|
5. **覆盖式上传**优先用 OpenSSH 的 `posix-rename@openssh.com` 做原子替换;服务器不支持时会退化为「先删目标再改名」,存在极短窗口(目标暂时不存在)。
|
|
113
|
-
6.
|
|
114
|
-
7. **
|
|
115
|
-
8. **
|
|
116
|
-
9. **AI 命令执行的安全边界**:工具默认开启,但只能操作**你已打开的会话**——AI 无法自行建立连接或提交凭据。每次执行都会写入 `diag.log`(命令预览截断 120 字符并 scrub 口令/私钥)。如不需要,把 `agentExec` 设为 `off`。
|
|
158
|
+
6. **SFTP 路径记忆、`-R` 端口即时释放**在待办中。
|
|
159
|
+
7. **shell 模式拒绝交互式/全屏程序**(vi/vim/nano/less/top/htop/watch/man/tmux/screen/ssh/su):它们会占满用户终端、提示符回不来。按分段首词判定,sudo vim、xargs vim 之类仍可绕过——真正的防线是审计日志。
|
|
160
|
+
8. **AI 命令执行的安全边界**:工具默认开启,但只能操作**你已打开的会话**——AI 无法自行建立连接或提交凭据。每次执行都会写入 `diag.log`(命令预览截断 120 字符并 scrub 口令/私钥)。如不需要,把 `agentExec` 设为 `off`。
|
|
117
161
|
|
|
118
162
|
## 结构
|
|
119
163
|
|
package/docs/SECURITY.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# 安全说明(UniTerminal 凭据存储)
|
|
2
2
|
|
|
3
3
|
> 这一份是**凭据存储**的对外说明:威胁模型、边界、密码存在哪、怎么清、已知限制。
|
|
4
|
-
>
|
|
4
|
+
> 设计与取舍的完整记录见 `docs/superpowers/plans/ledger-m0.md`。
|
|
5
5
|
|
|
6
6
|
## 一、威胁模型
|
|
7
7
|
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
`secret` 的字段一律被路由层拒绝(`CREDENTIAL_FIELDS` 白名单)。
|
|
43
43
|
3. **密码永远不进日志与诊断。**
|
|
44
44
|
`diag.log` 与 `reportClient()` 上报里只有 id、状态、类别;写入失败时错误消息里的密码会被 `scrub` 抹掉。
|
|
45
|
-
4.
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
4. **插件不自己实现加密。**
|
|
46
|
+
密码只有两个去处:**操作系统密码箱**,或**明文文件**(并在界面如实说明当前是哪种)。
|
|
47
|
+
不存在"插件自建加密、由插件保管密钥"这条路。
|
|
48
48
|
|
|
49
49
|
## 三、密码存在哪
|
|
50
50
|
|
|
@@ -109,6 +109,6 @@ Linux 上不显式指定后端时,底层库会"Secret Service → 内核 keyri
|
|
|
109
109
|
5. **Linux 上没有 Secret Service**(无 GNOME Keyring / KWallet 的精简桌面)时,
|
|
110
110
|
插件会走文件后端并明说——**不会**退到内核 keyring(不跨重启)。
|
|
111
111
|
6. **没有终端锁屏**:离开座位时已建立的会话没有任何保护,需要时请自行锁屏或断开连接。
|
|
112
|
-
7.
|
|
112
|
+
7. **插件不提供自研加密**:没有"插件保管密钥"这条路。
|
|
113
113
|
需要更强的保护时,请用操作系统的全盘加密与账户隔离。
|
|
114
114
|
|
package/lib/client.js
CHANGED
|
@@ -1404,6 +1404,7 @@ window.__ModuleLoader__.load({
|
|
|
1404
1404
|
"aria-label": props.label,
|
|
1405
1405
|
style: {
|
|
1406
1406
|
left: props.left,
|
|
1407
|
+
right: props.right,
|
|
1407
1408
|
top: props.top
|
|
1408
1409
|
},
|
|
1409
1410
|
onMouseDown: (event) => event.stopPropagation(),
|
|
@@ -1426,6 +1427,23 @@ window.__ModuleLoader__.load({
|
|
|
1426
1427
|
}, item.label))
|
|
1427
1428
|
});
|
|
1428
1429
|
}
|
|
1430
|
+
// src/client/menu-anchor.ts
|
|
1431
|
+
/**
|
|
1432
|
+
* @param anchorX 锚点的横向位置(相对面板左边缘,像素)
|
|
1433
|
+
* @param anchorY 锚点的纵向位置(相对面板上边缘,像素)
|
|
1434
|
+
* @param paneWidth 面板宽度;0 或未知时退回左对齐(总比跑到面板外好)
|
|
1435
|
+
*/
|
|
1436
|
+
function menuAnchor(anchorX, anchorY, paneWidth) {
|
|
1437
|
+
const top = Math.max(0, anchorY);
|
|
1438
|
+
if (paneWidth > 0 && anchorX > paneWidth / 2) return {
|
|
1439
|
+
right: Math.max(0, paneWidth - anchorX),
|
|
1440
|
+
top
|
|
1441
|
+
};
|
|
1442
|
+
return {
|
|
1443
|
+
left: Math.max(0, anchorX),
|
|
1444
|
+
top
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1429
1447
|
// src/client/host-filter.ts
|
|
1430
1448
|
/**
|
|
1431
1449
|
* 主机/分组搜索。
|
|
@@ -2270,11 +2288,9 @@ window.__ModuleLoader__.load({
|
|
|
2270
2288
|
*/
|
|
2271
2289
|
function anchorOf(element) {
|
|
2272
2290
|
const rect = element.getBoundingClientRect();
|
|
2273
|
-
const pane = paneRef.current
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
top: rect.bottom - (pane?.top ?? 0)
|
|
2277
|
-
};
|
|
2291
|
+
const pane = paneRef.current;
|
|
2292
|
+
const paneRect = pane?.getBoundingClientRect();
|
|
2293
|
+
return menuAnchor(rect.right - (paneRect?.left ?? 0), rect.bottom - (paneRect?.top ?? 0), pane?.clientWidth ?? 0);
|
|
2278
2294
|
}
|
|
2279
2295
|
/** 打开主机菜单:trigger 既当锚点,也是关闭后焦点要回的元素。 */
|
|
2280
2296
|
function openRowMenu(trigger, profileId) {
|
|
@@ -2316,10 +2332,10 @@ window.__ModuleLoader__.load({
|
|
|
2316
2332
|
},
|
|
2317
2333
|
onContextMenu: (event) => {
|
|
2318
2334
|
event.preventDefault();
|
|
2319
|
-
const
|
|
2335
|
+
const pane = paneRef.current;
|
|
2336
|
+
const rect = pane?.getBoundingClientRect();
|
|
2320
2337
|
setMenu({
|
|
2321
|
-
|
|
2322
|
-
top: event.clientY - (rect?.top ?? 0),
|
|
2338
|
+
...menuAnchor(event.clientX - (rect?.left ?? 0), event.clientY - (rect?.top ?? 0), pane?.clientWidth ?? 0),
|
|
2323
2339
|
profileId: profile.id,
|
|
2324
2340
|
trigger: event.currentTarget
|
|
2325
2341
|
});
|
|
@@ -2754,10 +2770,10 @@ window.__ModuleLoader__.load({
|
|
|
2754
2770
|
},
|
|
2755
2771
|
onContextMenu: (event) => {
|
|
2756
2772
|
event.preventDefault();
|
|
2757
|
-
const
|
|
2773
|
+
const pane = paneRef.current;
|
|
2774
|
+
const rect = pane?.getBoundingClientRect();
|
|
2758
2775
|
setGroupMenu({
|
|
2759
|
-
|
|
2760
|
-
top: event.clientY - (rect?.top ?? 0),
|
|
2776
|
+
...menuAnchor(event.clientX - (rect?.left ?? 0), event.clientY - (rect?.top ?? 0), pane?.clientWidth ?? 0),
|
|
2761
2777
|
groupId: node.id,
|
|
2762
2778
|
trigger: event.currentTarget
|
|
2763
2779
|
});
|
|
@@ -2904,6 +2920,7 @@ window.__ModuleLoader__.load({
|
|
|
2904
2920
|
if (target === void 0) return null;
|
|
2905
2921
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContextMenu, {
|
|
2906
2922
|
left: menu.left,
|
|
2923
|
+
right: menu.right,
|
|
2907
2924
|
top: menu.top,
|
|
2908
2925
|
label: "主机 " + target.name + " 的操作",
|
|
2909
2926
|
returnFocusTo: menu.trigger,
|
|
@@ -2937,6 +2954,7 @@ window.__ModuleLoader__.load({
|
|
|
2937
2954
|
if (node === void 0) return null;
|
|
2938
2955
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContextMenu, {
|
|
2939
2956
|
left: groupMenu.left,
|
|
2957
|
+
right: groupMenu.right,
|
|
2940
2958
|
top: groupMenu.top,
|
|
2941
2959
|
label: "分组 " + node.path.join(" / ") + " 的操作",
|
|
2942
2960
|
returnFocusTo: groupMenu.trigger,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@winking-r/dsh-uniterminal",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.18",
|
|
4
4
|
"description": "在 DSH 右侧栏里提供 SSH 终端、SFTP 文件管理与端口转发的插件;密码交给操作系统密码箱保管,插件自身不实现加密。",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"lib",
|
|
37
37
|
"cordis.patch.yml",
|
|
38
38
|
"README.md",
|
|
39
|
+
"CHANGELOG.md",
|
|
39
40
|
"docs/SECURITY.md"
|
|
40
41
|
],
|
|
41
42
|
"publishConfig": {
|