@winking-r/dsh-uniterminal 0.2.16 → 0.2.17

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 ADDED
@@ -0,0 +1,57 @@
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.17] - 2026-10-29
10
+
11
+ ### 修复
12
+
13
+ - **行尾「更多操作」按钮的菜单被面板右边缘裁掉**:锚点靠右时菜单仍向右伸展,伸出面板后被宿主的
14
+ `overflow` 裁掉,「移入分组…」「测试连接」只显示半截。现在锚点过半宽时改为**右对齐、向左展开**;
15
+ 鼠标右键、`Shift+F10`/菜单键、「⋯」按钮三条入口共用同一套判定(`src/client/menu-anchor.ts`)。
16
+
17
+ ### 文档
18
+
19
+ - 新增 `CONTRIBUTING.md`:源码断言的约定(改 CSS/文案要同步更新断言、不要删断言)、
20
+ DOM 测试模板(`// @vitest-environment jsdom` + `afterEach(cleanup)`)、CSS 契约、
21
+ 以及**禁止用固定字符数切源码**这条踩过两次的坑。
22
+ - README 补「右栏承载」一节:装了 `DSH-better-sidebar` 优先注册到 `ctx.betterSidebar`,
23
+ 否则回落官方 `sidebarRightTabs`。
24
+
25
+ ## [0.2.16] - 2026-10-29
26
+
27
+ 首个公开发布版本(MIT)。
28
+
29
+ ### 新增
30
+
31
+ - SSH 终端(xterm.js)、SFTP 文件管理、端口转发(`-L` / `-R` / `-D`)、跳板链、分组管理、外观设置。
32
+ - 一个侧边栏标签装多条会话,顶部会话栏切换;同一主机连两次显示 `#2`;刷新页面自动接回会话。
33
+ - **凭据交给操作系统密码箱**(Windows 凭据管理器 / macOS 钥匙串 / Linux Secret Service),
34
+ 插件自身不实现任何加密;只有机器没有系统密码箱时才回落明文文件(0600 + 原子写),并在界面如实说明。
35
+ - 失败提示区分 7 类原因(超时 / 拒绝 / DNS / 不可达 / 主机指纹 / 认证 / 未知)并给出下一步动作;
36
+ 内部字段名不再出现在界面上(细节只进 diag)。
37
+
38
+ ### 无障碍
39
+
40
+ - 右键菜单与确认弹框:打开即聚焦、方向键/Home/End、`Tab` 焦点陷阱、关闭后**把焦点还给触发元素**。
41
+ - 提示条改为常驻 live region(成功 `status/polite`、失败 `alert/assertive`),读屏能听见错误。
42
+ - SFTP 文件行支持键盘选中(`aria-pressed`);主机行「连接 / 测试」的可访问名带主机名。
43
+
44
+ ### 设计
45
+
46
+ - 引入 token 层(字号 13/14/16 三级阶梯、圆角 4/6/10/999、灰阶与遮罩、语义色单一来源)。
47
+ - 内联样式收敛 66%(541 → 182 条声明);重复选择器 4 组 → 0 组。
48
+
49
+ ### 质量
50
+
51
+ - 严格 TypeScript(`strict` + `noUncheckedIndexedAccess`):`:any` 与 `@ts-ignore` 均为 0。
52
+ - 64 个测试文件 / 915 个用例(含 jsdom 真实交互用例);`pnpm audit --prod` 零已知漏洞。
53
+ - `prepublishOnly` 强制跑 typecheck + test + build,过不了发不出去。
54
+
55
+ [Unreleased]: https://github.com/WinKing-R/dsh-uniterminal/compare/v0.2.17...HEAD
56
+ [0.2.17]: https://github.com/WinKing-R/dsh-uniterminal/compare/v0.2.16...v0.2.17
57
+ [0.2.16]: https://github.com/WinKing-R/dsh-uniterminal/releases/tag/v0.2.16
package/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # UniTerminal · DSH 远程终端插件
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/@winking-r/dsh-uniterminal.svg)](https://www.npmjs.com/package/@winking-r/dsh-uniterminal)
4
+ [![license](https://img.shields.io/npm/l/@winking-r/dsh-uniterminal.svg)](LICENSE)
5
+ [![GitHub](https://img.shields.io/badge/GitHub-WinKing--R%2Fdsh--uniterminal-blue)](https://github.com/WinKing-R/dsh-uniterminal)
6
+
3
7
  SSH / SFTP / 端口转发,挂在 DSH 右侧栏里。**一个侧边栏标签里装多条会话**(顶部会话栏切换;同一台主机连两次显示 `#2`),
4
8
  每条会话的终端、文件、隧道三视图并列。
5
9
 
@@ -7,7 +11,7 @@ SSH / SFTP / 端口转发,挂在 DSH 右侧栏里。**一个侧边栏标签里
7
11
 
8
12
  | 项 | 值 |
9
13
  |---|---|
10
- | 版本 | `0.2.16`(`0.x`:API 与配置项仍可能变,按语义化版本升号) |
14
+ | 版本 | `0.2.17`(`0.x`:API 与配置项仍可能变,按语义化版本升号;变更见 [CHANGELOG](CHANGELOG.md)) |
11
15
  | 许可证 | **MIT**(见 [LICENSE](LICENSE))——可自由使用、修改、商用,保留版权声明即可 |
12
16
  | 环境 | DSH `>=0.2.1-rc.2`、Node `>=20` |
13
17
 
@@ -25,14 +29,30 @@ SSH / SFTP / 端口转发,挂在 DSH 右侧栏里。**一个侧边栏标签里
25
29
  前提:DSH `>=0.2.1-rc.2`(见本包 `package.json` 的 `dsh.engines`)、Node `>=20`。
26
30
  从源码装还需要 pnpm;用打好的 `tgz` 安装则**不需要**自己构建。
27
31
 
32
+ ### 右栏承载:官方 `sidebarRight` 或第三方 `DSH-better-sidebar`
33
+
34
+ 本插件**双注册 + 运行时探测**(`src/client/right-pane.ts`):
35
+
36
+ | 你的 DSH 里装了 | 插件注册到 | 结果 |
37
+ |---|---|---|
38
+ | [`DSH-better-sidebar`](https://github.com/omdsh-dev/DSH-better-sidebar) | `ctx.betterSidebar`(**优先**) | 右栏像 IDE 那样多标签并存,本插件占一个标签 |
39
+ | 只有官方 `@deepseek-ai/dsh-client-ui-sidebar-right` | `ctx.sidebarRightTabs` | 回落到官方右栏标签 |
40
+
41
+ 两种都能用;**没有 better-sidebar 也能跑**(会自动回落)。
42
+ 若右栏始终不出现本插件,先确认这两者至少有一个已启用——诊断见
43
+ [`docs/acceptance-m0.md`](docs/acceptance-m0.md)(`target=none` 表示两套注册表都不可用)。
44
+
28
45
  ```bash
29
46
  # 在 DSH 的 desktop profile 目录下执行
30
47
  cd ~/.dsh/profiles/desktop
31
48
 
32
- # 方式一:从源码链接(改代码即生效,开发用)
49
+ # 方式一:从 npm 安装(推荐,不需要 Node 工具链)
50
+ pnpm add @winking-r/dsh-uniterminal
51
+
52
+ # 方式二:从源码链接(改代码即生效,开发用)
33
53
  pnpm add "link:<你的本仓库绝对路径>"
34
54
 
35
- # 方式二:从目录安装(用打包好的 tgz,无需 Node 工具链)
55
+ # 方式三:从目录安装(用打包好的 tgz)
36
56
  pnpm add "file:<tgz 的绝对路径>"
37
57
  ```
38
58
 
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?.getBoundingClientRect();
2274
- return {
2275
- left: rect.left - (pane?.left ?? 0),
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 rect = paneRef.current?.getBoundingClientRect();
2335
+ const pane = paneRef.current;
2336
+ const rect = pane?.getBoundingClientRect();
2320
2337
  setMenu({
2321
- left: event.clientX - (rect?.left ?? 0),
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 rect = paneRef.current?.getBoundingClientRect();
2773
+ const pane = paneRef.current;
2774
+ const rect = pane?.getBoundingClientRect();
2758
2775
  setGroupMenu({
2759
- left: event.clientX - (rect?.left ?? 0),
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.16",
3
+ "version": "0.2.17",
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": {