@kesike/dsh-exit 0.1.2 → 0.2.0

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/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  [![dsh web plugin](https://img.shields.io/badge/dsh-web%20plugin-4f46e5?style=for-the-badge)](https://npmjs.com/package/@deepseek-ai/dsh)
8
8
  [![npm](https://img.shields.io/npm/v/%40kesike%2Fdsh-exit?style=for-the-badge&label=npm)](https://www.npmjs.com/package/@kesike/dsh-exit)
9
9
  [![Platform](https://img.shields.io/badge/platform-web-0ea5e9?style=for-the-badge)](https://github.com/KeS1Ke/dsh-exit)
10
- [![Version](https://img.shields.io/badge/version-0.1.2-64748b?style=for-the-badge)](package.json)
10
+ [![Version](https://img.shields.io/badge/version-0.2.0-64748b?style=for-the-badge)](package.json)
11
11
  [![Dependencies](https://img.shields.io/badge/runtime%20dependencies-none-16a34a?style=for-the-badge)](package.json)
12
12
 
13
13
  [简体中文](https://github.com/KeS1Ke/dsh-exit/blob/main/README.zh-CN.md)
@@ -19,39 +19,56 @@
19
19
 
20
20
  ## ✨ What it does
21
21
 
22
- `dsh-exit` adds a compact, floating **Exit** button to the lower-right corner of the entire dsh web interface.
22
+ `dsh-exit` adds a dedicated **Exit** section to the dsh settings panel. It uses the highest settings order so newly installed settings plugins appear above it.
23
23
 
24
24
  - Opens a confirmation modal before doing anything destructive.
25
+ - Keeps the **Exit** section at the bottom of the settings navigation.
25
26
  - Calls the typed `dshExit/exit` Remote method only after confirmation.
26
27
  - Gives the gateway a short acknowledgement window, then ends the host process.
27
28
  - Releases every port held by that process, including the usual `127.0.0.1:3080`.
28
29
  - Keeps the terminal window and browser tab available for the next action.
29
30
  - Uses the existing dsh design tokens and the Lucide `power` icon.
30
31
 
31
- ## 🎬 Animated exit flow
32
+ ## 🎬 GitHub showcase
33
+
34
+ The animation, video, and screenshot below are repository showcase assets. They stay in GitHub for documentation and are intentionally excluded from the npm package.
35
+
36
+ <p align="center">
37
+ <img src="docs/dsh-exit-landscape.webp" alt="Landscape animated dsh-exit flow generated by Archify" width="720">
38
+ </p>
32
39
 
33
40
  <p align="center">
34
- <img src="https://raw.githubusercontent.com/KeS1Ke/dsh-exit/main/docs/dsh-exit-landscape.webp" alt="Landscape animated dsh-exit flow generated by Archify" width="720">
41
+ <video controls muted loop playsinline width="720" poster="docs/dsh-exit-landscape.webp">
42
+ <source src="docs/dsh-exit-landscape.mp4" type="video/mp4">
43
+ <source src="docs/dsh-exit-landscape.webm" type="video/webm">
44
+ <a href="https://github.com/KeS1Ke/dsh-exit/blob/main/docs/dsh-exit-landscape.mp4">Download the exit-flow video</a>
45
+ </video>
35
46
  </p>
36
47
 
37
48
  ## 📸 Real dsh UI
38
49
 
39
- The capture below comes from a running dsh Web profile with `dsh-exit` installed. The red power button is the actual plugin control rendered at the lower-right corner.
50
+ The capture below comes from a running dsh Web profile with `dsh-exit` installed. The Exit control is now provided by the final section in the settings navigation.
40
51
 
41
52
  <p align="center">
42
- <img src="https://raw.githubusercontent.com/KeS1Ke/dsh-exit/main/docs/dsh-exit-real.png" alt="Running dsh Web UI with the dsh-exit power button in the lower-right corner" width="720">
53
+ <img src="docs/dsh-exit-real.png" alt="Running dsh Web UI with the dsh-exit power button in the lower-right corner" width="720">
43
54
  </p>
44
55
 
45
- ## 🧱 What ships
56
+ ## 🧱 npm package contents
46
57
 
47
58
  | Layer | File | Responsibility |
48
59
  | --- | --- | --- |
49
60
  | Host | [`lib/index.js`](lib/index.js) | Registers the `dshExit` Cordis service and the typed `exit()` Remote method. |
50
- | Client | [`lib/client.js`](lib/client.js) | Self-contained browser bundle: button, CSS, modal, keyboard handling, and Remote mounting. |
61
+ | Client | [`lib/client.js`](lib/client.js) | Self-contained browser bundle: settings section, exit button, CSS, modal, keyboard handling, and Remote mounting. |
51
62
  | Bundle patch | [`cordis.patch.yml`](cordis.patch.yml) | Inserts the plugin row into the dsh profile. |
52
63
  | Package metadata | [`package.json`](package.json) | Declares the host entry, web client, and bundle patch. |
53
- | Animated flow | [`docs/dsh-exit-landscape.webp`](https://github.com/KeS1Ke/dsh-exit/blob/main/docs/dsh-exit-landscape.webp) | Landscape Archify trace animation rendered inline as animated WebP. |
54
- | Real UI capture | [`docs/dsh-exit-real.png`](https://github.com/KeS1Ke/dsh-exit/blob/main/docs/dsh-exit-real.png) | Running dsh Web profile with the actual button visible. |
64
+
65
+ The npm package contains only the four core runtime files above plus package metadata and README files. GitHub-only media is kept separately:
66
+
67
+ | Showcase asset | Location |
68
+ | --- | --- |
69
+ | Animated WebP | [`docs/dsh-exit-landscape.webp`](https://github.com/KeS1Ke/dsh-exit/blob/main/docs/dsh-exit-landscape.webp) |
70
+ | MP4 / WebM video | [`docs/dsh-exit-landscape.mp4`](https://github.com/KeS1Ke/dsh-exit/blob/main/docs/dsh-exit-landscape.mp4) · [`docs/dsh-exit-landscape.webm`](https://github.com/KeS1Ke/dsh-exit/blob/main/docs/dsh-exit-landscape.webm) |
71
+ | Real UI capture | [`docs/dsh-exit-real.png`](https://github.com/KeS1Ke/dsh-exit/blob/main/docs/dsh-exit-real.png) |
55
72
 
56
73
 
57
74
  ## 🚀 Install from npm
package/README.zh-CN.md CHANGED
@@ -7,7 +7,7 @@
7
7
  [![dsh Web 插件](https://img.shields.io/badge/dsh-Web%20%E6%8F%92%E4%BB%B6-4f46e5?style=for-the-badge)](https://npmjs.com/package/@deepseek-ai/dsh)
8
8
  [![npm](https://img.shields.io/npm/v/%40kesike%2Fdsh-exit?style=for-the-badge&label=npm)](https://www.npmjs.com/package/@kesike/dsh-exit)
9
9
  [![平台](https://img.shields.io/badge/%E5%B9%B3%E5%8F%B0-Web-0ea5e9?style=for-the-badge)](https://github.com/KeS1Ke/dsh-exit)
10
- [![版本](https://img.shields.io/badge/%E7%89%88%E6%9C%AC-0.1.2-64748b?style=for-the-badge)](package.json)
10
+ [![版本](https://img.shields.io/badge/%E7%89%88%E6%9C%AC-0.2.0-64748b?style=for-the-badge)](package.json)
11
11
  [![依赖](https://img.shields.io/badge/%E8%BF%90%E8%A1%8C%E6%97%B6%E4%BE%9D%E8%B5%96-%E6%97%A0-16a34a?style=for-the-badge)](package.json)
12
12
 
13
13
  **简体中文** | [English](https://github.com/KeS1Ke/dsh-exit/blob/main/README.md)
@@ -19,39 +19,56 @@
19
19
 
20
20
  ## ✨ 功能概览
21
21
 
22
- `dsh-exit` 在 dsh Web 界面右下角增加一个紧凑的悬浮**退出**按钮。
22
+ `dsh-exit` 在 dsh 设置面板中增加一个独立的**退出**栏目,并使用最高排序值,确保新安装的设置类插件仍排在它前面。
23
23
 
24
24
  - 先弹出确认弹窗,确认前不会执行退出动作。
25
+ - **退出**栏目始终位于设置左侧导航的最底部。
25
26
  - 确认后调用类型化的 `dshExit/exit` Remote 方法。
26
27
  - 先返回调用应答,再等待短暂窗口结束宿主进程。
27
28
  - 释放该进程占用的全部端口,包括常见的 `127.0.0.1:3080`。
28
29
  - 保留宿主终端窗口和浏览器标签页,方便后续操作。
29
30
  - 使用 dsh 现有设计令牌与 Lucide `power` 电源图标。
30
31
 
31
- ## 🎬 动态退出流程
32
+ ## 🎬 GitHub 展示区
33
+
34
+ 下面的动画、视频和截图是仓库专用展示素材,保留在 GitHub 用于文档展示,并明确排除在 npm 包之外。
35
+
36
+ <p align="center">
37
+ <img src="docs/dsh-exit-landscape.webp" alt="由 Archify 生成的横版 dsh-exit 动态流程图" width="720">
38
+ </p>
32
39
 
33
40
  <p align="center">
34
- <img src="https://raw.githubusercontent.com/KeS1Ke/dsh-exit/main/docs/dsh-exit-landscape.webp" alt="由 Archify 生成的横版 dsh-exit 动态流程图" width="720">
41
+ <video controls muted loop playsinline width="720" poster="docs/dsh-exit-landscape.webp">
42
+ <source src="docs/dsh-exit-landscape.mp4" type="video/mp4">
43
+ <source src="docs/dsh-exit-landscape.webm" type="video/webm">
44
+ <a href="https://github.com/KeS1Ke/dsh-exit/blob/main/docs/dsh-exit-landscape.mp4">下载退出流程视频</a>
45
+ </video>
35
46
  </p>
36
47
 
37
48
  ## 📸 真实 dsh 界面
38
49
 
39
- 下图来自已经安装 `dsh-exit` 的真实 dsh Web profile;右下角的红色电源按钮就是插件实际渲染的控件。
50
+ 下图来自已经安装 `dsh-exit` 的真实 dsh Web profile;退出操作现在位于设置左侧导航的最后一栏。
40
51
 
41
52
  <p align="center">
42
- <img src="https://raw.githubusercontent.com/KeS1Ke/dsh-exit/main/docs/dsh-exit-real.png" alt="运行中的 dsh Web 界面与右下角 dsh-exit 电源按钮" width="720">
53
+ <img src="docs/dsh-exit-real.png" alt="运行中的 dsh Web 界面与右下角 dsh-exit 电源按钮" width="720">
43
54
  </p>
44
55
 
45
- ## 🧱 项目组成
56
+ ## 🧱 npm 包内容
46
57
 
47
58
  | 层次 | 文件 | 职责 |
48
59
  | --- | --- | --- |
49
60
  | 宿主侧 | [`lib/index.js`](lib/index.js) | 注册 `dshExit` Cordis 服务和类型化 `exit()` Remote 方法。 |
50
- | 客户端 | [`lib/client.js`](lib/client.js) | 自包含浏览器 bundle:按钮、样式、弹窗、键盘交互和 Remote 挂载。 |
61
+ | 客户端 | [`lib/client.js`](lib/client.js) | 自包含浏览器 bundle:设置栏目、退出按钮、样式、弹窗、键盘交互和 Remote 挂载。 |
51
62
  | Bundle 补丁 | [`cordis.patch.yml`](cordis.patch.yml) | 向 dsh profile 插入插件行。 |
52
63
  | 包元数据 | [`package.json`](package.json) | 声明宿主入口、Web 客户端和 Bundle 补丁。 |
53
- | 动态流程 | [`docs/dsh-exit-landscape.webp`](https://github.com/KeS1Ke/dsh-exit/blob/main/docs/dsh-exit-landscape.webp) | 横版 Archify trace 动画,以动画 WebP 直接内嵌。 |
54
- | 真实界面截图 | [`docs/dsh-exit-real.png`](https://github.com/KeS1Ke/dsh-exit/blob/main/docs/dsh-exit-real.png) | 已运行 dsh Web profile 中的实际按钮。 |
64
+
65
+ npm 包只包含上面四个核心运行文件、包元数据和 README;GitHub 展示素材单独保留在仓库中:
66
+
67
+ | 展示素材 | 位置 |
68
+ | --- | --- |
69
+ | 动画 WebP | [`docs/dsh-exit-landscape.webp`](https://github.com/KeS1Ke/dsh-exit/blob/main/docs/dsh-exit-landscape.webp) |
70
+ | MP4 / WebM 视频 | [`docs/dsh-exit-landscape.mp4`](https://github.com/KeS1Ke/dsh-exit/blob/main/docs/dsh-exit-landscape.mp4) · [`docs/dsh-exit-landscape.webm`](https://github.com/KeS1Ke/dsh-exit/blob/main/docs/dsh-exit-landscape.webm) |
71
+ | 真实界面截图 | [`docs/dsh-exit-real.png`](https://github.com/KeS1Ke/dsh-exit/blob/main/docs/dsh-exit-real.png) |
55
72
 
56
73
 
57
74
  ## 🚀 从 npm 安装
package/lib/client.js CHANGED
@@ -1,19 +1,21 @@
1
1
  //#region dsh-exit 浏览器客户端 bundle
2
2
  /**
3
3
  * dsh-exit 客户端:
4
- * - 在整个界面右下角挂载紧凑的退出按钮
5
- * - 退出按钮首次点击弹确认弹窗;确认后调用 remote.dshExit.exit(),
4
+ * - 在 dsh 设置菜单中注册一个始终排在最后的“退出”栏目
5
+ * - 栏目中的退出按钮首次点击弹确认弹窗;确认后调用 remote.dshExit.exit(),
6
6
  * 服务端结束宿主进程并释放端口,不操作终端窗口或浏览器标签页
7
7
  *
8
- * 无外部依赖:全部逻辑在本工厂内完成,不 require 任何模块。
8
+ * 页面使用 dsh 已提供的 React、slots 和 locale 运行时,不引入新的运行时依赖。
9
9
  */
10
10
  window.__ModuleLoader__.load({
11
- // The bundle row is addressed by the stable dsh patch id, not the npm scope.
12
- id: "dsh-exit",
13
- factory: () => {
11
+ // The loader key follows the resolved npm package name, including its scope.
12
+ id: "@kesike/dsh-exit",
13
+ factory: (require) => {
14
14
  var module = { exports: {} };
15
15
  var exports = module.exports;
16
16
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
17
+ const React = require("react");
18
+ const h = React.createElement;
17
19
 
18
20
  /** 与 archive-manager「删除会话」相同的错误红(设计令牌)。 */
19
21
  const ERROR_RED = "var(--dsw-alias-state-error-primary)";
@@ -22,11 +24,19 @@ window.__ModuleLoader__.load({
22
24
  const ICON_SVG = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 2v10"/><path d="M18.4 6.6a9 9 0 1 1-12.77.04"/></svg>';
23
25
 
24
26
  const CSS_TEXT = [
25
- ".dshExitBtn{position:fixed;right:12px;bottom:12px;left:auto;z-index:2147482000;box-sizing:border-box;width:32px;height:32px;padding:0;display:grid;place-items:center;cursor:pointer;border:1px solid " + ERROR_RED + ";border-radius:50%;background:var(--dsw-alias-button-elevated-fill);color:" + ERROR_RED + ";box-shadow:0 2px 8px rgba(0,0,0,.24);transition:background .12s ease,transform .12s ease}",
26
- ".dshExitBtn svg{width:16px;height:16px;flex:none}",
27
- ".dshExitBtn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);transform:translateY(-1px)}",
28
- ".dshExitBtn:focus-visible{outline:2px solid " + ERROR_RED + ";outline-offset:2px}",
29
- ".dshExitBtn:disabled{cursor:default;opacity:.7}",
27
+ ".dshExitSettings{box-sizing:border-box;max-width:720px;padding:28px 32px 40px;color:var(--dsw-alias-label-primary)}",
28
+ ".dshExitSettingsHeader{display:flex;align-items:flex-start;gap:14px;padding-bottom:22px;border-bottom:1px solid var(--dsw-alias-border-l2)}",
29
+ ".dshExitSettingsIcon{box-sizing:border-box;width:38px;height:38px;display:grid;place-items:center;flex:none;border:1px solid " + ERROR_RED + ";border-radius:12px;color:" + ERROR_RED + ";background:var(--dsw-alias-button-elevated-fill)}",
30
+ ".dshExitSettingsIcon svg{width:20px;height:20px}",
31
+ ".dshExitSettingsTitle{margin:0;font-size:20px;line-height:28px;font-weight:650;color:var(--dsw-alias-label-primary)}",
32
+ ".dshExitSettingsDescription{margin:6px 0 0;max-width:560px;font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}",
33
+ ".dshExitSettingsCard{display:flex;align-items:center;justify-content:space-between;gap:24px;margin-top:24px;padding:20px;border:1px solid var(--dsw-alias-border-l2);border-radius:14px;background:var(--dsw-alias-bg-layer-2)}",
34
+ ".dshExitSettingsWarning{margin:0;max-width:430px;font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}",
35
+ ".dshExitSettingsButton{display:inline-flex;align-items:center;justify-content:center;gap:8px;flex:none;min-width:112px;height:36px;padding:0 16px;cursor:pointer;border:0;border-radius:10px;background:" + ERROR_RED + ";color:#fff;font-size:13px;font-weight:650;transition:filter .12s ease,opacity .12s ease}",
36
+ ".dshExitSettingsButton svg{width:16px;height:16px}",
37
+ ".dshExitSettingsButton:hover:not(:disabled){filter:brightness(1.08)}",
38
+ ".dshExitSettingsButton:focus-visible{outline:2px solid " + ERROR_RED + ";outline-offset:2px}",
39
+ ".dshExitSettingsButton:disabled{cursor:default;opacity:.6}",
30
40
  ".dshExitOverlay{position:fixed;inset:0;z-index:2147483000;display:flex;align-items:center;justify-content:center;background:rgba(15,20,25,.45);animation:dshExitFade .12s ease-out}",
31
41
  "@keyframes dshExitFade{from{opacity:0}}",
32
42
  ".dshExitModal{box-sizing:border-box;width:min(380px,calc(100vw - 48px));border-radius:16px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-button-elevated-fill);box-shadow:0 24px 64px rgba(0,0,0,.35);padding:24px;display:flex;flex-direction:column;gap:12px;animation:dshExitPop .14s ease-out}",
@@ -71,8 +81,37 @@ window.__ModuleLoader__.load({
71
81
  ]
72
82
  };
73
83
 
84
+ const NS = "dsh-exit";
85
+ const DICT = {
86
+ zh: {
87
+ nav: "退出",
88
+ title: "退出 dsh",
89
+ description: "从这里安全结束 dsh 宿主进程,并释放它占用的端口。",
90
+ warning: "确认后将结束所有运行中的会话并停止后台服务,3080 端口会随之释放。",
91
+ button: "确认退出",
92
+ dialogTitle: "退出 DeepSeek Harness?",
93
+ dialogBody: "退出后将结束所有运行中的会话并停止后台服务,3080 端口会随之释放。",
94
+ cancel: "取消",
95
+ confirm: "确认退出",
96
+ exiting: "正在退出…",
97
+ errorPrefix: "退出失败:"
98
+ },
99
+ en: {
100
+ nav: "Exit",
101
+ title: "Exit dsh",
102
+ description: "Safely terminate the dsh host process and release the ports it owns.",
103
+ warning: "Confirming ends all running sessions and stops the background service, releasing port 3080.",
104
+ button: "Confirm exit",
105
+ dialogTitle: "Exit DeepSeek Harness?",
106
+ dialogBody: "This ends all running sessions and stops the background service, releasing port 3080.",
107
+ cancel: "Cancel",
108
+ confirm: "Confirm exit",
109
+ exiting: "Exiting…",
110
+ errorPrefix: "Exit failed: "
111
+ }
112
+ };
113
+
74
114
  let exiting = false;
75
- let exitBtnEl = null;
76
115
  let overlayEl = null;
77
116
  let escHandler = null;
78
117
 
@@ -84,32 +123,40 @@ window.__ModuleLoader__.load({
84
123
  document.head.appendChild(tag);
85
124
  }
86
125
 
87
- function createExitButton() {
88
- const exitBtn = document.createElement("button");
89
- exitBtn.className = "dshExitBtn";
90
- exitBtn.type = "button";
91
- exitBtn.disabled = false;
92
- exitBtn.setAttribute("aria-label", "退出");
93
- exitBtn.title = "退出 DeepSeek Harness";
94
- exitBtn.dataset.dshExitButton = "true";
95
- exitBtn.innerHTML = ICON_SVG;
96
- return exitBtn;
126
+ function textOf(t, key) {
127
+ return typeof t === "function" ? t(key) : DICT.zh[key] ?? key;
97
128
  }
98
129
 
99
- function mount(ctx) {
100
- const existing = document.querySelector("[data-dsh-exit-button='true']");
101
- if (existing) {
102
- exitBtnEl = existing;
103
- return;
104
- }
105
- if (!document.body) return;
106
- const exitBtn = createExitButton();
107
- exitBtn.addEventListener("click", (event) => {
108
- event.stopPropagation();
109
- openModal(ctx, exitBtn);
110
- });
111
- document.body.appendChild(exitBtn);
112
- exitBtnEl = exitBtn;
130
+ function ExitSettingsSection({ ctx, t }) {
131
+ return h("section", {
132
+ className: "dshExitSettings",
133
+ "aria-labelledby": "dsh-exit-settings-title"
134
+ }, [
135
+ h("div", { className: "dshExitSettingsHeader", key: "header" }, [
136
+ h("span", {
137
+ className: "dshExitSettingsIcon",
138
+ dangerouslySetInnerHTML: { __html: ICON_SVG },
139
+ key: "icon"
140
+ }),
141
+ h("div", { key: "copy" }, [
142
+ h("h2", { className: "dshExitSettingsTitle", id: "dsh-exit-settings-title", key: "title" }, textOf(t, "title")),
143
+ h("p", { className: "dshExitSettingsDescription", key: "description" }, textOf(t, "description"))
144
+ ])
145
+ ]),
146
+ h("div", { className: "dshExitSettingsCard", key: "card" }, [
147
+ h("p", { className: "dshExitSettingsWarning", key: "warning" }, textOf(t, "warning")),
148
+ h("button", {
149
+ type: "button",
150
+ className: "dshExitSettingsButton",
151
+ "aria-label": textOf(t, "button"),
152
+ onClick: (event) => openModal(ctx, event.currentTarget, t),
153
+ key: "button"
154
+ }, [
155
+ h("span", { dangerouslySetInnerHTML: { __html: ICON_SVG }, key: "icon" }),
156
+ textOf(t, "button")
157
+ ])
158
+ ])
159
+ ]);
113
160
  }
114
161
 
115
162
  function closeModal() {
@@ -123,7 +170,8 @@ window.__ModuleLoader__.load({
123
170
  }
124
171
  }
125
172
 
126
- function openModal(ctx, exitBtn) {
173
+ function openModal(ctx, exitBtn, t) {
174
+ const text = (key) => textOf(t, key);
127
175
  closeModal();
128
176
  overlayEl = document.createElement("div");
129
177
  overlayEl.className = "dshExitOverlay";
@@ -131,13 +179,13 @@ window.__ModuleLoader__.load({
131
179
  panel.className = "dshExitModal";
132
180
  panel.setAttribute("role", "dialog");
133
181
  panel.setAttribute("aria-modal", "true");
134
- panel.setAttribute("aria-label", "退出 DeepSeek Harness");
182
+ panel.setAttribute("aria-label", text("dialogTitle"));
135
183
  const title = document.createElement("h3");
136
184
  title.className = "dshExitTitle";
137
- title.textContent = "退出 DeepSeek Harness?";
185
+ title.textContent = text("dialogTitle");
138
186
  const body = document.createElement("p");
139
187
  body.className = "dshExitBody";
140
- body.textContent = "退出后将结束所有运行中的会话并停止后台服务,3080 端口会随之释放。";
188
+ body.textContent = text("dialogBody");
141
189
  const errorEl = document.createElement("p");
142
190
  errorEl.className = "dshExitError";
143
191
  const actions = document.createElement("div");
@@ -145,11 +193,11 @@ window.__ModuleLoader__.load({
145
193
  const cancel = document.createElement("button");
146
194
  cancel.type = "button";
147
195
  cancel.className = "dshExitCancel";
148
- cancel.textContent = "取消";
196
+ cancel.textContent = text("cancel");
149
197
  const confirm = document.createElement("button");
150
198
  confirm.type = "button";
151
199
  confirm.className = "dshExitConfirm";
152
- confirm.textContent = "确认退出";
200
+ confirm.textContent = text("confirm");
153
201
  actions.append(cancel, confirm);
154
202
  panel.append(title, body, errorEl, actions);
155
203
  overlayEl.appendChild(panel);
@@ -170,7 +218,7 @@ window.__ModuleLoader__.load({
170
218
  cancel.disabled = true;
171
219
  errorEl.textContent = "";
172
220
  exitBtn.disabled = true;
173
- exitBtn.setAttribute("aria-label", "正在退出…");
221
+ exitBtn.setAttribute("aria-label", text("exiting"));
174
222
  Promise.resolve()
175
223
  .then(() => {
176
224
  const service = ctx.get("remote.dshExit");
@@ -189,7 +237,7 @@ window.__ModuleLoader__.load({
189
237
  cancel.disabled = false;
190
238
  exitBtn.disabled = false;
191
239
  exitBtn.setAttribute("aria-label", "退出");
192
- errorEl.textContent = "退出失败:" + (reason instanceof Error ? reason.message : String(reason));
240
+ errorEl.textContent = text("errorPrefix") + (reason instanceof Error ? reason.message : String(reason));
193
241
  });
194
242
  });
195
243
  document.body.appendChild(overlayEl);
@@ -197,25 +245,26 @@ window.__ModuleLoader__.load({
197
245
 
198
246
  async function apply(ctx) {
199
247
  injectCss();
200
- mount(ctx);
201
- const observer = new MutationObserver(() => {
202
- if (!exiting) mount(ctx);
203
- });
204
- observer.observe(document.documentElement, { childList: true, subtree: true });
248
+ ctx.effect(() => ctx.locale.register(NS, DICT), "dsh-exit: settings dictionaries");
249
+ const t = ctx.locale.bind(NS);
250
+ const SettingsSection = (props) => h(ExitSettingsSection, { ...props, ctx });
251
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
252
+ name: "settings.section",
253
+ id: "dsh-exit",
254
+ order: Number.MAX_SAFE_INTEGER,
255
+ label: () => t("nav"),
256
+ locale: NS
257
+ }, SettingsSection));
205
258
  let disposeRemote = () => {};
206
259
  const remote = ctx.get("remote");
207
260
  if (remote !== undefined) disposeRemote = await remote.$mount(EXIT_REMOTE);
208
261
  return async () => {
209
- observer.disconnect();
210
262
  closeModal();
211
- if (exitBtnEl) {
212
- exitBtnEl.remove();
213
- exitBtnEl = null;
214
- }
215
263
  disposeRemote();
216
264
  };
217
265
  }
218
266
 
267
+ exports.inject = ["slots", "locale"];
219
268
  exports.apply = apply;
220
269
  return module.exports;
221
270
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kesike/dsh-exit",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "在 dsh web 界面提供退出按钮:确认后结束宿主进程并释放端口",
6
6
  "keywords": [
@@ -43,6 +43,11 @@
43
43
  "patch": "./cordis.patch.yml"
44
44
  },
45
45
  "client": {
46
+ "inject": [
47
+ "@deepseek-ai/dsh-client-runtime",
48
+ "@deepseek-ai/dsh-client-ui-slots",
49
+ "@deepseek-ai/dsh-client-locale"
50
+ ],
46
51
  "platform": "web"
47
52
  }
48
53
  }