@wenbin_wb/dsh-bridge 2.11.1 → 2.11.3
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 +39 -0
- package/client/client.js +226 -24
- package/client/index.js +91 -17
- package/client/locales.mjs +174 -0
- package/client/mobile-styles.js +28 -8
- package/docs/release-process.md +1 -1
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,45 @@
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [v2.11.3] - 2026-10-02
|
|
8
|
+
|
|
9
|
+
> 本版为移动端修复 + 国际化版:修复顶栏会话标题恒为「新会话」,并让插件 UI 与显示名称跟随 DSH 主体语言(中/英)。
|
|
10
|
+
|
|
11
|
+
### 🐞 修复
|
|
12
|
+
|
|
13
|
+
- **移动端顶栏会话标题恒为「新会话」**:DSH 0.1.7 起 `sessions.list` 快照不再有 `current` 字段,旧逻辑取不到当前会话。现改为读宿主「会话层级」面包屑(`Session hierarchy`)的最后一级——与 PC 顶栏同源、宿主实时维护,空会话回退「新会话」;并用 MutationObserver 守护切换同步(写前比对避免自循环)。
|
|
14
|
+
|
|
15
|
+
### ✨ 新功能
|
|
16
|
+
|
|
17
|
+
- **移动端自绘 UI 文案国际化**:顶栏按钮(打开菜单/新建会话)、标题回退、输入区提示、返回对话、收起/展开输入框等文案跟随 DSH 主体语言(`<html lang>` 同步,中/英词典见 `client/locales.mjs`)。
|
|
18
|
+
- **插件显示名称本地化**:新增 `locale/en.json` 与 `locale/zh.json`(`meta.title`/`meta.description`),插件管理页与市场按当前语言显示本插件名称(如 "DSH Bridge — Remote Access & Security" / "DSH Bridge — 远程访问与安全守护")。
|
|
19
|
+
|
|
20
|
+
### 🧪 测试
|
|
21
|
+
|
|
22
|
+
- 新增「移动端顶栏标题读取宿主会话层级面包屑(中英文锚点)」结构断言、「客户端国际化(locales.mjs 中英键一致)」与「插件管理页本地化(locale/*.json 存在且 exports 暴露)」断言;`test/browser/mobile-sidebar-drawer.mjs` 新增「顶栏标题跟随当前会话」浏览器断言。全量 343 项测试 100% 通过。
|
|
23
|
+
|
|
24
|
+
> ⚠️ 生效说明:客户端文案刷新页面即生效;插件管理页显示名称在 dsh 重启后完全生效(元数据由宿主进程读取)。
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## [v2.11.2] - 2026-10-02
|
|
29
|
+
|
|
30
|
+
> 本版为移动端修复版:解决英文界面下侧边栏(抽屉)点击后空白,以及 DSH 0.1.7 升级后右侧栏点击后不显示的问题。
|
|
31
|
+
|
|
32
|
+
### 🐞 修复
|
|
33
|
+
|
|
34
|
+
- **移动端侧边栏(抽屉)点击后空白**:宿主按钮 aria-label 随界面语言切换(中文「打开侧边栏」/ 英文 "Open sidebar"),插件此前只按中文匹配宿主按钮,英文界面下漏取 → 抽屉滑出但会话列表不挂载。现将抽屉展开/收起/会话操作/新建会话等工作区按钮一律中英双语匹配(英文界面与中文界面行为一致)。
|
|
35
|
+
- **移动端右侧栏点击后不显示**:DSH 0.1.7 把宿主布局外壳改为 grid 三列布局(sidebar | center | rightbar),并把右侧栏全屏面板从 `fixed` 改为 `absolute` 相对右侧栏列定位;插件旧规则把框架强制成 flex 单列、且面板 top 让位二次叠加,导致面板被定位到视口外(点开按钮后按钮消失、右侧栏不出来)。现恢复宿主 grid 布局并显式归位三列,面板统一强制 fixed + 顶栏下方让位(兼容 0.1.5 与 0.1.7 宿主)。
|
|
36
|
+
|
|
37
|
+
### 🧪 测试
|
|
38
|
+
|
|
39
|
+
- 新增 `test/browser/mobile-sidebar-drawer.mjs`(抽屉展开/收起,中英文按钮匹配)与 `test/browser/mobile-sidebar-right.mjs`(右侧栏 grid 布局)两个真实浏览器验收套件(共 8 套件);`test/mobile-fixed-panel-offset.test.mjs` 新增面板 fixed 与 frame grid 归位结构断言;`test/client-provide.test.mjs` 新增宿主按钮中英双语匹配断言。全量 340 项测试 100% 通过。
|
|
40
|
+
- 清理 CodeQL note 级告警 `js/unused-local-variable`(测试文件未使用导入)。
|
|
41
|
+
|
|
42
|
+
> ⚠️ 生效说明:纯前端改动,刷新页面即生效(无需重启 dsh)。
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
7
46
|
## [v2.11.1] - 2026-10-02
|
|
8
47
|
|
|
9
48
|
> 本版为修复版:解决 DSH 0.1.7 升级后暴露的移动端「看不到最新消息」、设置弹窗层级误抬,以及 linux 下托管误判导致的 systemd 掉线问题。
|
package/client/client.js
CHANGED
|
@@ -120,29 +120,49 @@ var MOBILE_STYLES_CSS = `
|
|
|
120
120
|
\u8868\u73B0\u4E3A\u300C\u770B\u4E0D\u5230\u6700\u65B0\u6D88\u606F\u300D\u3002\u5B98\u65B9\u7ED9\u8BE5\u5BB9\u5668\u7684\u89C4\u5219\u662F flex:none;height:auto
|
|
121
121
|
\uFF08\u8BA9\u8BB0\u5F55\u968F\u5185\u5BB9\u81EA\u7136\u589E\u9AD8\u3001\u7531\u5916\u5C42\u7EDF\u4E00\u6EDA\u52A8\uFF09\uFF0C\u4E0E\u8FD9\u91CC\u51B2\u7A81\uFF0C\u6545\u5FC5\u987B\u6392\u9664\u3002 */
|
|
122
122
|
[data-slot="root"] > div[class*="_frame"] {
|
|
123
|
-
display:
|
|
124
|
-
|
|
123
|
+
display: grid !important;
|
|
124
|
+
grid-template-columns: 0px minmax(0px, 1fr) minmax(0px, 0px) !important;
|
|
125
|
+
grid-template-rows: minmax(0, 100%) !important;
|
|
125
126
|
width: 100vw !important;
|
|
126
127
|
height: 100dvh !important;
|
|
127
128
|
margin: 0 !important;
|
|
128
129
|
padding-top: var(--dsh-mobile-header-h) !important;
|
|
129
130
|
position: relative !important;
|
|
130
|
-
grid-template-columns: 1fr !important;
|
|
131
131
|
overflow: hidden !important;
|
|
132
132
|
box-sizing: border-box !important;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
/* \u5BBF\u4E3B 0.1.7 \u7684 frame \u662F grid \u4E09\u5217\u5E03\u5C40\uFF08sidebar | center | rightbar\uFF09\uFF0CJS \u6309\u72B6\u6001\u6CE8\u5165
|
|
136
|
+
grid-template-columns\uFF1B\u63D2\u4EF6\u628A\u5DE6\u4FA7\u680F\u62BD\u5C49\u5316\u540E sidebarCol \u53D8\u6210 fixed\uFF08\u8131\u79BB grid \u6D41\uFF09\uFF0C
|
|
137
|
+
\u82E5\u4E0D\u663E\u5F0F\u6307\u5B9A\u5217\u4F4D\uFF0CcenterCol \u4F1A\u88AB\u81EA\u52A8\u6392\u8FDB\u7B2C 1 \u5217\uFF0856px \u8F68\u9053\uFF09\u3001rightbarCol \u584C\u9677\u5230
|
|
138
|
+
\u89C6\u53E3\u5E95\u90E8 \u2192 \u53F3\u4FA7\u680F\u70B9\u5F00\u540E\u9762\u677F\u88AB\u5B9A\u4F4D\u5230\u89C6\u53E3\u5916\uFF08\u7528\u6237\u770B\u5230\u7684\u300C\u6309\u94AE\u6D88\u5931\u3001\u53F3\u4FA7\u680F\u6CA1\u51FA\u6765\u300D\uFF09\u3002
|
|
139
|
+
\u8FD9\u91CC\u663E\u5F0F\u5F52\u4F4D\uFF1Asidebar \u62BD\u5C49\u4E0D\u5360\u5217\uFF08\u7B2C 1 \u5217 0px\uFF09\u3001center \u5360\u6EE1\uFF08\u7B2C 2 \u5217 1fr\uFF09\u3001
|
|
140
|
+
rightbar \u4FDD\u7559\u5BBF\u4E3B\u53F3\u4FA7\u680F\u5217\uFF08\u7B2C 3 \u5217 0px \u6298\u53E0\uFF0C\u5168\u5C4F\u9762\u677F fixed \u76F8\u5BF9\u89C6\u53E3\u3001\u4E0D\u4F9D\u8D56\u6B64\u5217\uFF09\u3002 */
|
|
141
|
+
div[class*="_centerCol"] { grid-column: 2 !important; }
|
|
142
|
+
div[class*="_rightbarCol"] { grid-column: 3 !important; }
|
|
143
|
+
div[class*="_overlayLayer"] { grid-column: 1 / -1 !important; }
|
|
144
|
+
|
|
135
145
|
/* 1.1 fixed \u5168\u5C4F\u9762\u677F\u5355\u72EC\u8BA9\u4F4D\uFF1Aposition:fixed \u7684\u5305\u542B\u5757\u662F viewport\uFF08CSS 2.1 \xA710.1\uFF09\uFF0C
|
|
136
146
|
\u4E0D\u8DDF\u968F\u4E0A\u9762 frame \u7684 padding-top\uFF0C\u56E0\u6B64\u9876\u90E8 52px \u4F1A\u843D\u8FDB\u9876\u680F\u8986\u76D6\u533A\uFF08#41\uFF09\u3002
|
|
137
|
-
\u5B98\u65B9\u53F3\u4FA7\u680F\u5728 <768px \u81EA\u52A8\u5168\u5C4F\
|
|
138
|
-
|
|
139
|
-
\
|
|
140
|
-
\
|
|
141
|
-
\
|
|
147
|
+
\u5B98\u65B9\u53F3\u4FA7\u680F\u5728 <768px \u81EA\u52A8\u5168\u5C4F\uFF0C\u8FD9\u91CC\u6309 data \u5C5E\u6027\u76F4\u63A5\u4F4D\u79FB\u5BB9\u5668\u672C\u8EAB\uFF0C\u4E0D\u4F9D\u8D56\u5BBF\u4E3B
|
|
148
|
+
CSS-module \u54C8\u5E0C\u7C7B\u540D\u3002
|
|
149
|
+
\u5BBF\u4E3B\u7248\u672C\u5DEE\u5F02\uFF080.1.5 vs 0.1.7\uFF09\uFF1A0.1.5 \u7684\u9762\u677F\u81EA\u8EAB\u5C31\u662F position:fixed; inset:0
|
|
150
|
+
\uFF08\u76F8\u5BF9\u89C6\u53E3\uFF09\uFF0Ctop \u8BA9\u4F4D\u76F4\u63A5\u751F\u6548\uFF1B0.1.7 \u628A\u9762\u677F\u6539\u6210 position:absolute \u76F8\u5BF9
|
|
151
|
+
rightbarCol \u5217\u5B9A\u4F4D\uFF0C\u4E14\u5BBF\u4E3B\u5DF2\u628A\u5B83\u653E\u5728\u5185\u5BB9\u533A\u8D77\u70B9\u2014\u2014\u82E5\u63D2\u4EF6\u4ECD\u53EA\u6539 top\uFF0C\u4F1A\u5728\u5BBF\u4E3B
|
|
152
|
+
\u5B9A\u4F4D\u4E4B\u4E0A\u518D\u53E0\u52A0 52px\uFF08\u9762\u677F\u88AB\u63A8\u5230\u89C6\u53E3\u5916\uFF0C\u8868\u73B0\u4E3A\u300C\u70B9\u5F00\u53F3\u4FA7\u680F\u6309\u94AE\u6D88\u5931\u3001\u9762\u677F\u4E0D
|
|
153
|
+
\u51FA\u6765\u300D\uFF09\u3002\u4FEE\u6CD5\uFF1A\u628A\u9762\u677F\u7EDF\u4E00\u5F3A\u5236\u4E3A fixed \u76F8\u5BF9\u89C6\u53E3 + top:52px \u8BA9\u4F4D\uFF0C\u4E24\u7248\u5BBF\u4E3B
|
|
154
|
+
\u884C\u4E3A\u4E00\u81F4\u3002 */
|
|
142
155
|
[data-sidebar-right-panel="fullscreen"] {
|
|
156
|
+
position: fixed !important;
|
|
143
157
|
top: var(--dsh-mobile-header-h, 52px) !important;
|
|
158
|
+
left: 0 !important;
|
|
159
|
+
right: 0 !important;
|
|
160
|
+
bottom: 0 !important;
|
|
161
|
+
width: 100vw !important;
|
|
144
162
|
height: calc(100dvh - var(--dsh-mobile-header-h, 52px)) !important;
|
|
145
163
|
max-height: calc(100dvh - var(--dsh-mobile-header-h, 52px)) !important;
|
|
164
|
+
z-index: 40 !important;
|
|
165
|
+
box-sizing: border-box !important;
|
|
146
166
|
}
|
|
147
167
|
|
|
148
168
|
/* 2. \u9876\u90E8\u539F\u751F\u5BFC\u822A\u6761\uFF1A100% \u8FD8\u539F DeepSeek App (\u5DE6\u4FA7\u53CC\u6A2A\u7EBF\uFF0C\u53F3\u4FA7(+)\uFF0C\u4E2D\u95F4\u7559\u767D\uFF0C\u65E0\u591A\u4F59\u8BBE\u7F6E\u6309\u94AE) */
|
|
@@ -1548,6 +1568,148 @@ function registerSettingsNavIcon(resolveLabel = () => "\u8FDC\u7A0B\u8BBF\u95EE"
|
|
|
1548
1568
|
};
|
|
1549
1569
|
}
|
|
1550
1570
|
|
|
1571
|
+
// client/locales.mjs
|
|
1572
|
+
var LOCALE_NAMESPACE = "@wenbin_wb/dsh-bridge";
|
|
1573
|
+
var zh = {
|
|
1574
|
+
// 移动端顶栏
|
|
1575
|
+
"menu.open": "\u6253\u5F00\u83DC\u5355",
|
|
1576
|
+
"menu.newSession": "\u65B0\u5EFA\u4F1A\u8BDD",
|
|
1577
|
+
"title.fallback": "\u65B0\u4F1A\u8BDD",
|
|
1578
|
+
// 移动端输入区
|
|
1579
|
+
"composer.placeholder": "\u270D\uFE0F \u70B9\u51FB\u8F93\u5165\u6D88\u606F\u2026",
|
|
1580
|
+
"composer.expand": "\u5C55\u5F00\u8F93\u5165\u6846",
|
|
1581
|
+
"composer.collapse": "\u6536\u8D77\u8F93\u5165\u6846\uFF0C\u6700\u5927\u5316\u5BF9\u8BDD\u9605\u8BFB\u533A",
|
|
1582
|
+
"panel.backToChat": "\u8FD4\u56DE\u5BF9\u8BDD",
|
|
1583
|
+
// 移动端设置面板 / 抽屉(dsh-bridge 自绘部分)
|
|
1584
|
+
"workspace.choose": "\u9009\u62E9\u7535\u8111\u5DE5\u4F5C\u533A",
|
|
1585
|
+
"workspace.reading": "\u6B63\u5728\u8BFB\u53D6\u76EE\u5F55\u5185\u5BB9\u2026",
|
|
1586
|
+
"workspace.currentDir": "\u5F53\u524D\u76EE\u5F55:",
|
|
1587
|
+
"workspace.registeredHint": "\u5DF2\u6CE8\u518C\u5DE5\u4F5C\u533A (${count} \u4E2A\uFF0C\u70B9\u51FB\u76F4\u63A5\u5207\u6362)\uFF1A",
|
|
1588
|
+
"workspace.enterHint": "\u70B9\u51FB\u8FDB\u5165\u6587\u4EF6\u5939\uFF0C\u6216\u70B9\u51FB\u300C+ \u9009\u4E3A\u5DE5\u4F5C\u533A\u300D\u76F4\u63A5\u6DFB\u52A0\u5E76\u5207\u6362",
|
|
1589
|
+
"workspace.unlockHint": "\u8FDC\u7A0B\u8BBF\u95EE\u65F6\u6D4F\u89C8/\u6DFB\u52A0\u5DE5\u4F5C\u533A\u9700\u8F93\u5165\u540E\u53F0\u7BA1\u7406\u5BC6\u7801\u89E3\u9501\uFF08\u4E0E\u8BBF\u95EE\u5BC6\u7801\u4E0D\u540C\uFF09\u3002",
|
|
1590
|
+
"workspace.chooseFolder": "\u9009\u62E9\u5DE5\u4F5C\u533A",
|
|
1591
|
+
"workspace.addFolder": "\u6DFB\u52A0",
|
|
1592
|
+
"workspace.refresh": "\u5237\u65B0\u76EE\u5F55",
|
|
1593
|
+
"workspace.back": "\u8FD4\u56DE\u4E0A\u4E00\u7EA7",
|
|
1594
|
+
"workspace.filter": "\u8FC7\u6EE4\u5B50\u6587\u4EF6\u5939\u2026",
|
|
1595
|
+
"workspace.more": "\u8FD8\u6709\u66F4\u591A",
|
|
1596
|
+
"workspace.addSubfolder": "\u76F4\u63A5\u6DFB\u52A0\u6B64\u5B50\u6587\u4EF6\u5939\u4E3A\u5DE5\u4F5C\u533A\u5E76\u8FDB\u5165",
|
|
1597
|
+
"workspace.onlyAdmin": "\u4EC5\u9650\u672C\u673A\u7BA1\u7406",
|
|
1598
|
+
"workspace.isCurrent": "\u5F53\u524D\u4E3B\u5165\u53E3",
|
|
1599
|
+
"workspace.isFallback": "\u4E3B\u5165\u53E3",
|
|
1600
|
+
// 远程访问面板
|
|
1601
|
+
"access.copyLink": "\u590D\u5236\u94FE\u63A5",
|
|
1602
|
+
"access.copied": "\u2713 \u5DF2\u590D\u5236",
|
|
1603
|
+
"access.hideQr": "\u9690\u85CF\u4E8C\u7EF4\u7801",
|
|
1604
|
+
"access.showQr": "\u663E\u793A\u4E8C\u7EF4\u7801",
|
|
1605
|
+
"access.privateEnv": "\u8BF7\u5728\u79C1\u5BC6\u73AF\u5883\u4E0B\u4F7F\u7528",
|
|
1606
|
+
"access.switching": "\u5207\u6362\u4E2D\u2026",
|
|
1607
|
+
"access.publicEntry": "\u516C\u7F51\u8BBF\u95EE\u5165\u53E3",
|
|
1608
|
+
"access.copy": "\u590D\u5236",
|
|
1609
|
+
"access.stopReconnect": "\u505C\u6B62\u91CD\u8FDE",
|
|
1610
|
+
"access.close": "\u5173\u95ED",
|
|
1611
|
+
"access.connecting": "\u8FDE\u63A5\u4E2D\u2026",
|
|
1612
|
+
"access.downloading": "\u4E0B\u8F7D\u4E2D\u2026",
|
|
1613
|
+
"access.enable": "\u5F00\u542F\u516C\u7F51\u96A7\u9053",
|
|
1614
|
+
"access.scanHint": "\u8BF7\u5728\u79C1\u5BC6\u73AF\u5883\u4E0B\u626B\u7801\u4F7F\u7528",
|
|
1615
|
+
"access.autoStart": "\u968F DSH \u542F\u52A8\u81EA\u52A8\u5F00\u542F",
|
|
1616
|
+
"access.tunnelConfig": "\u96A7\u9053\u914D\u7F6E",
|
|
1617
|
+
"access.expand": "\u5C55\u5F00 \u25BE",
|
|
1618
|
+
"access.collapse": "\u6536\u8D77 \u25B4",
|
|
1619
|
+
"access.saveError": "\u4FDD\u5B58\u914D\u7F6E\u5931\u8D25",
|
|
1620
|
+
"access.serverConfig": "\u96A7\u9053\u670D\u52A1\u5668\u914D\u7F6E",
|
|
1621
|
+
"access.saving": "\u4FDD\u5B58\u4E2D\u2026",
|
|
1622
|
+
"access.saved": "\u2713 \u5DF2\u4FDD\u5B58",
|
|
1623
|
+
"access.saveConfig": "\u4FDD\u5B58\u914D\u7F6E",
|
|
1624
|
+
"access.clear": "\u6E05\u9664",
|
|
1625
|
+
"access.externalTunnel": "\u5916\u90E8\u5DF2\u90E8\u7F72\u96A7\u9053",
|
|
1626
|
+
"access.saveFixed": "\u4FDD\u5B58\u56FA\u5B9A\u57DF\u540D\u914D\u7F6E",
|
|
1627
|
+
"access.updateFailed": "\u66F4\u65B0\u5931\u8D25",
|
|
1628
|
+
"access.securityHint": "\u8BBE\u7F6E\u5916\u90E8\u8BBF\u5BA2\u8BBF\u95EE\u5BC6\u7801",
|
|
1629
|
+
"access.goSetup": "\u53BB\u5F00\u542F \u2794",
|
|
1630
|
+
"access.gotoSettings": "\u8BBE\u7F6E \u2794",
|
|
1631
|
+
"access.tutorial": "\u67E5\u770B\u81EA\u5EFA\u96A7\u9053\u670D\u52A1\u5668\u642D\u5EFA\u6559\u7A0B"
|
|
1632
|
+
};
|
|
1633
|
+
var en = {
|
|
1634
|
+
"menu.open": "Open menu",
|
|
1635
|
+
"menu.newSession": "New session",
|
|
1636
|
+
"title.fallback": "New Session",
|
|
1637
|
+
"composer.placeholder": "\u270D\uFE0F Tap to type a message\u2026",
|
|
1638
|
+
"composer.expand": "Expand input",
|
|
1639
|
+
"composer.collapse": "Collapse input to maximize chat view",
|
|
1640
|
+
"panel.backToChat": "Back to chat",
|
|
1641
|
+
"workspace.choose": "Choose a workspace",
|
|
1642
|
+
"workspace.reading": "Reading directory\u2026",
|
|
1643
|
+
"workspace.currentDir": "Current directory:",
|
|
1644
|
+
"workspace.registeredHint": "Registered workspaces (${count}, click to switch):",
|
|
1645
|
+
"workspace.enterHint": "Enter a folder, or tap \u201C+ Select as workspace\u201D to add & switch",
|
|
1646
|
+
"workspace.unlockHint": "Browsing/adding workspaces remotely requires the admin password to unlock (different from the access password).",
|
|
1647
|
+
"workspace.chooseFolder": "Choose workspace",
|
|
1648
|
+
"workspace.addFolder": "Add",
|
|
1649
|
+
"workspace.refresh": "Refresh",
|
|
1650
|
+
"workspace.back": "Back",
|
|
1651
|
+
"workspace.filter": "Filter subfolders\u2026",
|
|
1652
|
+
"workspace.more": "More",
|
|
1653
|
+
"workspace.addSubfolder": "Add this subfolder as a workspace and enter",
|
|
1654
|
+
"workspace.onlyAdmin": "Local admin only",
|
|
1655
|
+
"workspace.isCurrent": "Current entry",
|
|
1656
|
+
"workspace.isFallback": "Main entry",
|
|
1657
|
+
"access.copyLink": "Copy link",
|
|
1658
|
+
"access.copied": "\u2713 Copied",
|
|
1659
|
+
"access.hideQr": "Hide QR",
|
|
1660
|
+
"access.showQr": "Show QR",
|
|
1661
|
+
"access.privateEnv": "Use in a private environment",
|
|
1662
|
+
"access.switching": "Switching\u2026",
|
|
1663
|
+
"access.publicEntry": "Public access entry",
|
|
1664
|
+
"access.copy": "Copy",
|
|
1665
|
+
"access.stopReconnect": "Stop reconnecting",
|
|
1666
|
+
"access.close": "Close",
|
|
1667
|
+
"access.connecting": "Connecting\u2026",
|
|
1668
|
+
"access.downloading": "Downloading\u2026",
|
|
1669
|
+
"access.enable": "Enable public tunnel",
|
|
1670
|
+
"access.scanHint": "Scan in a private environment",
|
|
1671
|
+
"access.autoStart": "Start automatically with DSH",
|
|
1672
|
+
"access.tunnelConfig": "Tunnel configuration",
|
|
1673
|
+
"access.expand": "Expand \u25BE",
|
|
1674
|
+
"access.collapse": "Collapse \u25B4",
|
|
1675
|
+
"access.saveError": "Failed to save configuration",
|
|
1676
|
+
"access.serverConfig": "Tunnel server configuration",
|
|
1677
|
+
"access.saving": "Saving\u2026",
|
|
1678
|
+
"access.saved": "\u2713 Saved",
|
|
1679
|
+
"access.saveConfig": "Save configuration",
|
|
1680
|
+
"access.clear": "Clear",
|
|
1681
|
+
"access.externalTunnel": "External tunnel",
|
|
1682
|
+
"access.saveFixed": "Save fixed-domain configuration",
|
|
1683
|
+
"access.updateFailed": "Update failed",
|
|
1684
|
+
"access.securityHint": "Set an external visitor access password",
|
|
1685
|
+
"access.goSetup": "Set up \u2794",
|
|
1686
|
+
"access.gotoSettings": "Settings \u2794",
|
|
1687
|
+
"access.tutorial": "View the self-hosted tunnel server tutorial"
|
|
1688
|
+
};
|
|
1689
|
+
function createTranslator(ctx) {
|
|
1690
|
+
const active = () => {
|
|
1691
|
+
const lang = (typeof document !== "undefined" ? document.documentElement.getAttribute("lang") : "") || "";
|
|
1692
|
+
return lang.toLowerCase().startsWith("zh") ? "zh" : "en";
|
|
1693
|
+
};
|
|
1694
|
+
const entryOf = (key) => ({ zh: zh[key], en: en[key] });
|
|
1695
|
+
const t = (key, vars) => {
|
|
1696
|
+
const e = entryOf(key);
|
|
1697
|
+
let text = e[active()] ?? e.en ?? e.zh ?? "";
|
|
1698
|
+
if (vars && text) {
|
|
1699
|
+
for (const [k, v] of Object.entries(vars)) text = text.replaceAll(`\${${k}}`, String(v));
|
|
1700
|
+
}
|
|
1701
|
+
return text;
|
|
1702
|
+
};
|
|
1703
|
+
try {
|
|
1704
|
+
const locale = ctx && (typeof ctx.get === "function" ? ctx.get("locale") : ctx.locale);
|
|
1705
|
+
if (locale && typeof locale.register === "function") {
|
|
1706
|
+
locale.register(LOCALE_NAMESPACE, { zh, en });
|
|
1707
|
+
}
|
|
1708
|
+
} catch {
|
|
1709
|
+
}
|
|
1710
|
+
return { t, active };
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1551
1713
|
// lib/bridge-rpc-constants.js
|
|
1552
1714
|
var BRIDGE_RPC_CHANNEL = "/dsh-bridge";
|
|
1553
1715
|
var BRIDGE_ENDPOINTS = {
|
|
@@ -1738,6 +1900,7 @@ var RELEASES_URL = "https://github.com/wenbin-wb/dsh-bridge/releases";
|
|
|
1738
1900
|
var ISSUES_URL = "https://github.com/wenbin-wb/dsh-bridge/issues/new";
|
|
1739
1901
|
var TUNNEL_DOCS_URL = "https://github.com/wenbin-wb/dsh-bridge/blob/main/docs/custom-tunnel.md";
|
|
1740
1902
|
var CLOUDFLARE_TUTORIAL_URL = "https://github.com/wenbin-wb/dsh-bridge/blob/main/docs/cloudflare-fixed-domain.md";
|
|
1903
|
+
var localeT = (key) => key;
|
|
1741
1904
|
function upgradeCommands(latest) {
|
|
1742
1905
|
const spec = `@wenbin_wb/dsh-bridge@${latest}`;
|
|
1743
1906
|
return [
|
|
@@ -5889,7 +6052,7 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
5889
6052
|
header.className = "dsh-mobile-app-header";
|
|
5890
6053
|
const leftBtn = document.createElement("button");
|
|
5891
6054
|
leftBtn.className = "dsh-header-menu-btn";
|
|
5892
|
-
leftBtn.title = "
|
|
6055
|
+
leftBtn.title = localeT("menu.open");
|
|
5893
6056
|
leftBtn.innerHTML = `
|
|
5894
6057
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round">
|
|
5895
6058
|
<line x1="3" y1="8" x2="21" y2="8"></line>
|
|
@@ -5900,7 +6063,9 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
5900
6063
|
e.stopPropagation();
|
|
5901
6064
|
const isOpen = document.body.classList.toggle("dsh-drawer-open");
|
|
5902
6065
|
if (isOpen) {
|
|
5903
|
-
const expand = document.querySelector(
|
|
6066
|
+
const expand = document.querySelector(
|
|
6067
|
+
'button[aria-label*="\u6253\u5F00\u4FA7\u8FB9\u680F"], button[title*="\u6253\u5F00\u4FA7\u8FB9\u680F"], button[aria-label*="Open sidebar"], button[title*="Open sidebar"]'
|
|
6068
|
+
);
|
|
5904
6069
|
if (expand) expand.click();
|
|
5905
6070
|
}
|
|
5906
6071
|
};
|
|
@@ -5913,7 +6078,7 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
5913
6078
|
};
|
|
5914
6079
|
const rightBtn = document.createElement("button");
|
|
5915
6080
|
rightBtn.className = "dsh-header-new-btn";
|
|
5916
|
-
rightBtn.title = "
|
|
6081
|
+
rightBtn.title = localeT("menu.newSession");
|
|
5917
6082
|
rightBtn.innerHTML = `
|
|
5918
6083
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
5919
6084
|
<circle cx="12" cy="12" r="9.5"></circle>
|
|
@@ -5924,7 +6089,7 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
5924
6089
|
rightBtn.onclick = () => {
|
|
5925
6090
|
const openPanels = document.querySelectorAll('div[class*="nArs4W_panel"]:not([class*="panelHidden"]), div[class*="workbench_panel"]:not([class*="panelHidden"])');
|
|
5926
6091
|
openPanels.forEach((p) => p.classList.add("nArs4W_panelHidden"));
|
|
5927
|
-
const dshNewBtn = document.querySelector('button[aria-label="\u65B0\u5EFA\u4F1A\u8BDD"]');
|
|
6092
|
+
const dshNewBtn = document.querySelector('button[aria-label="\u65B0\u5EFA\u4F1A\u8BDD"], button[aria-label="New session"]');
|
|
5928
6093
|
if (dshNewBtn) dshNewBtn.click();
|
|
5929
6094
|
};
|
|
5930
6095
|
header.appendChild(leftBtn);
|
|
@@ -5932,22 +6097,55 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
5932
6097
|
header.appendChild(rightBtn);
|
|
5933
6098
|
document.body.appendChild(header);
|
|
5934
6099
|
}
|
|
6100
|
+
const MOBILE_TITLE_FALLBACK_TEXT = () => localeT("title.fallback");
|
|
6101
|
+
const readHostSessionTitle = () => {
|
|
6102
|
+
if (typeof document === "undefined") return "";
|
|
6103
|
+
const nav = document.querySelector('nav[aria-label="Session hierarchy"], nav[aria-label="\u4F1A\u8BDD\u5C42\u7EA7"]');
|
|
6104
|
+
if (!nav) return "";
|
|
6105
|
+
const segs = [...nav.querySelectorAll('[class*="crumbSeg"]')];
|
|
6106
|
+
const lastSeg = segs[segs.length - 1];
|
|
6107
|
+
const lastCrumb = lastSeg ? lastSeg.querySelector('[class*="crumbCurrent"]') || lastSeg.querySelector("span, button") : null;
|
|
6108
|
+
if (!lastCrumb) return "";
|
|
6109
|
+
const text = (lastCrumb.getAttribute?.("aria-label") || lastCrumb.textContent || "").trim();
|
|
6110
|
+
if (!text || /^\d+\s+(item|items|项|个)/i.test(text)) return "";
|
|
6111
|
+
return text;
|
|
6112
|
+
};
|
|
6113
|
+
const setTitleText = (next) => {
|
|
6114
|
+
if (titleEl.textContent === next) return;
|
|
6115
|
+
titleEl.innerText = next;
|
|
6116
|
+
};
|
|
5935
6117
|
const syncMobileTitle = () => {
|
|
5936
6118
|
if (!titleEl) titleEl = document.querySelector(".dsh-mobile-header-title");
|
|
5937
6119
|
if (!titleEl) return;
|
|
6120
|
+
const hostTitle = readHostSessionTitle();
|
|
6121
|
+
if (hostTitle) {
|
|
6122
|
+
setTitleText(hostTitle);
|
|
6123
|
+
return;
|
|
6124
|
+
}
|
|
5938
6125
|
const snap = ctx?.sessions?.list?.getSnapshot?.();
|
|
5939
6126
|
if (!snap) return;
|
|
5940
6127
|
const cur = snap.current ? snap.byId?.[snap.current] : null;
|
|
5941
6128
|
if (!cur || cur.blank) {
|
|
5942
|
-
|
|
6129
|
+
setTitleText(MOBILE_TITLE_FALLBACK_TEXT());
|
|
5943
6130
|
} else {
|
|
5944
|
-
|
|
6131
|
+
setTitleText(cur.displayTitle || cur.title || MOBILE_TITLE_FALLBACK_TEXT());
|
|
5945
6132
|
}
|
|
5946
6133
|
};
|
|
5947
6134
|
syncMobileTitle();
|
|
5948
6135
|
if (typeof ctx?.sessions?.list?.subscribe === "function") {
|
|
5949
6136
|
ctx.sessions.list.subscribe(syncMobileTitle);
|
|
5950
6137
|
}
|
|
6138
|
+
if (typeof MutationObserver !== "undefined") {
|
|
6139
|
+
let mobileTitleRaf = 0;
|
|
6140
|
+
const mobileTitleObserver = new MutationObserver(() => {
|
|
6141
|
+
if (mobileTitleRaf) return;
|
|
6142
|
+
mobileTitleRaf = requestAnimationFrame(() => {
|
|
6143
|
+
mobileTitleRaf = 0;
|
|
6144
|
+
syncMobileTitle();
|
|
6145
|
+
});
|
|
6146
|
+
});
|
|
6147
|
+
mobileTitleObserver.observe(document.body, { childList: true, subtree: true, characterData: true });
|
|
6148
|
+
}
|
|
5951
6149
|
if (typeof ctx?.sessions?.active?.subscribe === "function") {
|
|
5952
6150
|
ctx.sessions.active.subscribe(() => {
|
|
5953
6151
|
if (document.body.classList.contains("dsh-drawer-open")) {
|
|
@@ -5972,12 +6170,12 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
5972
6170
|
if (bar && !bar.querySelector(".dsh-mobile-panel-close-btn")) {
|
|
5973
6171
|
const btn = document.createElement("button");
|
|
5974
6172
|
btn.className = "dsh-mobile-panel-close-btn";
|
|
5975
|
-
btn.innerHTML = `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg><span
|
|
6173
|
+
btn.innerHTML = `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg><span>${localeT("panel.backToChat")}</span>`;
|
|
5976
6174
|
btn.onclick = (e) => {
|
|
5977
6175
|
e.stopPropagation();
|
|
5978
6176
|
document.body.classList.remove("dsh-workbench-open");
|
|
5979
6177
|
p.classList.add("nArs4W_panelHidden");
|
|
5980
|
-
const collapseBtn = document.querySelector('button[class*="toggleButton"][aria-label*="\u6536\u8D77"]');
|
|
6178
|
+
const collapseBtn = document.querySelector('button[class*="toggleButton"][aria-label*="\u6536\u8D77"], button[class*="toggleButton"][aria-label*="Collapse"]');
|
|
5981
6179
|
if (collapseBtn) collapseBtn.click();
|
|
5982
6180
|
};
|
|
5983
6181
|
bar.appendChild(btn);
|
|
@@ -5989,14 +6187,14 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
5989
6187
|
window.addEventListener("resize", ensurePanelCloseButton);
|
|
5990
6188
|
document.addEventListener("click", (e) => {
|
|
5991
6189
|
if (typeof window === "undefined" || window.innerWidth > MOBILE_MAX_WIDTH) return;
|
|
5992
|
-
const trigger = e.target.closest('button[aria-label*="\u9762\u677F"], button[aria-label*="\u5DE5\u4F5C\u533A"], div[class*="toggleCluster"] button, button[class*="subagent"], div[class*="headerActions"] button, div[class*="titleRow"] button');
|
|
6190
|
+
const trigger = e.target.closest('button[aria-label*="\u9762\u677F"], button[aria-label*="\u5DE5\u4F5C\u533A"], button[aria-label*="workspace"], button[aria-label*="Workspace"], div[class*="toggleCluster"] button, button[class*="subagent"], div[class*="headerActions"] button, div[class*="titleRow"] button');
|
|
5993
6191
|
if (trigger && !trigger.classList.contains("dsh-mobile-panel-close-btn") && !trigger.classList.contains("dsh-header-menu-btn") && !trigger.classList.contains("dsh-header-new-btn")) {
|
|
5994
6192
|
document.body.classList.add("dsh-workbench-open");
|
|
5995
6193
|
}
|
|
5996
6194
|
}, true);
|
|
5997
6195
|
document.addEventListener("click", (e) => {
|
|
5998
6196
|
if (typeof window === "undefined" || window.innerWidth > MOBILE_MAX_WIDTH) return;
|
|
5999
|
-
const toggle = e.target.closest('button[aria-label*="\u6536\u8D77\u4FA7\u8FB9\u680F"], button[title*="\u6536\u8D77\u4FA7\u8FB9\u680F"]');
|
|
6197
|
+
const toggle = e.target.closest('button[aria-label*="\u6536\u8D77\u4FA7\u8FB9\u680F"], button[title*="\u6536\u8D77\u4FA7\u8FB9\u680F"], button[aria-label*="Collapse sidebar"], button[title*="Collapse sidebar"]');
|
|
6000
6198
|
if (toggle) {
|
|
6001
6199
|
document.body.classList.remove("dsh-drawer-open");
|
|
6002
6200
|
}
|
|
@@ -6068,7 +6266,7 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
6068
6266
|
if (navigator.vibrate) navigator.vibrate(40);
|
|
6069
6267
|
} catch (_) {
|
|
6070
6268
|
}
|
|
6071
|
-
const actionBtn = sessionRow.querySelector('button[aria-label*="\u64CD\u4F5C"], button[class*="iconButton"], button');
|
|
6269
|
+
const actionBtn = sessionRow.querySelector('button[aria-label*="\u64CD\u4F5C"], button[aria-label*="Session actions"], button[class*="iconButton"], button');
|
|
6072
6270
|
if (actionBtn) {
|
|
6073
6271
|
actionBtn.click();
|
|
6074
6272
|
}
|
|
@@ -6095,7 +6293,9 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
6095
6293
|
if (Math.abs(deltaX) > 60 && Math.abs(deltaX) > Math.abs(deltaY) * 1.5) {
|
|
6096
6294
|
if (deltaX > 0 && touchStartX <= 35) {
|
|
6097
6295
|
document.body.classList.add("dsh-drawer-open");
|
|
6098
|
-
const collapsedToggle = document.querySelector(
|
|
6296
|
+
const collapsedToggle = document.querySelector(
|
|
6297
|
+
'div[class*="hHd-Xa_collapsed"] button[class*="hHd-Xa_toggle"], button[aria-label*="\u6253\u5F00\u4FA7\u8FB9\u680F"], button[title*="\u6253\u5F00\u4FA7\u8FB9\u680F"], button[aria-label*="Open sidebar"], button[title*="Open sidebar"]'
|
|
6298
|
+
);
|
|
6099
6299
|
if (collapsedToggle) collapsedToggle.click();
|
|
6100
6300
|
} else if (deltaX < 0 && document.body.classList.contains("dsh-drawer-open")) {
|
|
6101
6301
|
document.body.classList.remove("dsh-drawer-open");
|
|
@@ -6108,7 +6308,7 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
6108
6308
|
if (!btn) return;
|
|
6109
6309
|
if (btn.closest("#dsh-remote-workspace-modal")) return;
|
|
6110
6310
|
const label = (btn.getAttribute("aria-label") || btn.innerText || btn.title || "").trim();
|
|
6111
|
-
const isAddWorkspace = label === "\u6DFB\u52A0\u5DE5\u4F5C\u533A" || label === "\u65B0\u5EFA\u5DE5\u4F5C\u533A" || label === "\u6253\u5F00\u5DE5\u4F5C\u533A" || label === "\u6253\u5F00\u6587\u4EF6\u5939" || label === "Add Workspace" || label === "Open Folder" || label.includes("\u6DFB\u52A0\u5DE5\u4F5C\u533A") || label.includes("\u6253\u5F00\u5DE5\u4F5C\u533A") || label.includes("\u6253\u5F00\u6587\u4EF6\u5939") || btn.matches('button[aria-label*="\u5DE5\u4F5C\u533A"][aria-label*="\u6DFB\u52A0"], button[aria-label*="\u5DE5\u4F5C\u533A"][aria-label*="\u6253\u5F00"]');
|
|
6311
|
+
const isAddWorkspace = label === "\u6DFB\u52A0\u5DE5\u4F5C\u533A" || label === "\u65B0\u5EFA\u5DE5\u4F5C\u533A" || label === "\u6253\u5F00\u5DE5\u4F5C\u533A" || label === "\u6253\u5F00\u6587\u4EF6\u5939" || label === "Add Workspace" || label === "Open Folder" || label.includes("\u6DFB\u52A0\u5DE5\u4F5C\u533A") || label.includes("\u6253\u5F00\u5DE5\u4F5C\u533A") || label.includes("\u6253\u5F00\u6587\u4EF6\u5939") || btn.matches('button[aria-label*="\u5DE5\u4F5C\u533A"][aria-label*="\u6DFB\u52A0"], button[aria-label*="\u5DE5\u4F5C\u533A"][aria-label*="\u6253\u5F00"], button[aria-label*="Workspace"][aria-label*="add"], button[aria-label*="Workspace"][aria-label*="open"], button[aria-label*="Add workspace"], button[aria-label*="Open workspace"]');
|
|
6112
6312
|
if (isAddWorkspace) {
|
|
6113
6313
|
e.preventDefault();
|
|
6114
6314
|
e.stopPropagation();
|
|
@@ -6853,7 +7053,7 @@ function setupComposerCollapse() {
|
|
|
6853
7053
|
if (!utils) return null;
|
|
6854
7054
|
const btn = document.createElement("button");
|
|
6855
7055
|
btn.className = "dsh-header-fold-btn";
|
|
6856
|
-
btn.setAttribute("aria-label", "
|
|
7056
|
+
btn.setAttribute("aria-label", `${localeT("composer.expand")} / ${localeT("composer.collapse")}`);
|
|
6857
7057
|
btn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="3" y1="18" x2="21" y2="18"></line><polyline points="6 9 12 15 18 9"></polyline></svg>';
|
|
6858
7058
|
if (logBtn && logBtn.parentElement === utils) utils.insertBefore(btn, logBtn);
|
|
6859
7059
|
else utils.appendChild(btn);
|
|
@@ -6874,7 +7074,7 @@ function setupComposerCollapse() {
|
|
|
6874
7074
|
if (!scrollBody) return null;
|
|
6875
7075
|
bar = document.createElement("div");
|
|
6876
7076
|
bar.className = "dsh-composer-collapsed-bar";
|
|
6877
|
-
bar.textContent = "
|
|
7077
|
+
bar.textContent = localeT("composer.placeholder");
|
|
6878
7078
|
scrollBody.insertBefore(bar, seat);
|
|
6879
7079
|
bar.addEventListener("click", () => setCollapsed(false));
|
|
6880
7080
|
return bar;
|
|
@@ -6909,7 +7109,7 @@ function setupComposerCollapse() {
|
|
|
6909
7109
|
const updateButton = (collapsed) => {
|
|
6910
7110
|
const btn = getFoldBtn();
|
|
6911
7111
|
if (!btn) return;
|
|
6912
|
-
btn.title = collapsed ? "
|
|
7112
|
+
btn.title = collapsed ? localeT("composer.expand") : localeT("composer.collapse");
|
|
6913
7113
|
const icon = collapsed ? '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="6" width="20" height="12" rx="2"></rect><line x1="6" y1="10" x2="6.01" y2="10"></line><line x1="10" y1="10" x2="10.01" y2="10"></line><line x1="14" y1="10" x2="14.01" y2="10"></line><line x1="6" y1="14" x2="10" y2="14"></line><line x1="14" y1="14" x2="18" y2="14"></line></svg>' : '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="3" y1="18" x2="21" y2="18"></line><polyline points="6 9 12 15 18 9"></polyline></svg>';
|
|
6914
7114
|
const existing = btn.querySelector("svg");
|
|
6915
7115
|
if (!existing || existing.outerHTML !== icon) btn.innerHTML = icon;
|
|
@@ -6972,6 +7172,8 @@ function setupComposerCollapse() {
|
|
|
6972
7172
|
}
|
|
6973
7173
|
function apply(ctx) {
|
|
6974
7174
|
window.__dshClientCtx = ctx;
|
|
7175
|
+
const intl = createTranslator(ctx);
|
|
7176
|
+
localeT = (key, vars) => intl.t(key, vars);
|
|
6975
7177
|
setupSettingsDrilldown();
|
|
6976
7178
|
if (typeof ctx.effect === "function") {
|
|
6977
7179
|
ctx.effect(() => registerSettingsNavIcon(() => "\u8FDC\u7A0B\u8BBF\u95EE"), "dsh-bridge: settings nav icon");
|
package/client/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
getRuntimeFeatureOverrides,
|
|
14
14
|
} from './mobile-yield.js'
|
|
15
15
|
import { registerSettingsNavIcon } from './settings-nav-icon.js'
|
|
16
|
+
import { createTranslator } from './locales.mjs'
|
|
16
17
|
// dsh-bridge 客户端插件:设置页「远程访问」面板
|
|
17
18
|
|
|
18
19
|
// 兼容非 HTTPS 环境(如手机局域网 HTTP 访问):为非安全上下文补齐 crypto.randomUUID
|
|
@@ -121,6 +122,10 @@ const ISSUES_URL = 'https://github.com/wenbin-wb/dsh-bridge/issues/new';
|
|
|
121
122
|
const TUNNEL_DOCS_URL = 'https://github.com/wenbin-wb/dsh-bridge/blob/main/docs/custom-tunnel.md';
|
|
122
123
|
const CLOUDFLARE_TUTORIAL_URL = 'https://github.com/wenbin-wb/dsh-bridge/blob/main/docs/cloudflare-fixed-domain.md';
|
|
123
124
|
|
|
125
|
+
// 模块级翻译函数:apply() 里随 ctx.locale 初始化(跟随 DSH 主体语言),
|
|
126
|
+
// 供 setupMobileExperience / setupComposerCollapse 等各函数读取(不通过参数传递)。
|
|
127
|
+
let localeT = (key) => key;
|
|
128
|
+
|
|
124
129
|
// 生成升级命令(拼接具体版本号;用 add 而非 update,update --latest 受已安装依赖版本约束可能无法升级到最新版)
|
|
125
130
|
function upgradeCommands(latest) {
|
|
126
131
|
const spec = `@wenbin_wb/dsh-bridge@${latest}`;
|
|
@@ -131,6 +136,9 @@ function upgradeCommands(latest) {
|
|
|
131
136
|
}
|
|
132
137
|
|
|
133
138
|
const name = 'dsh-bridge';
|
|
139
|
+
// 注:locale 服务不能通过 inject 声明(动态插件激活时该服务不在运行时上下文,
|
|
140
|
+
// 声明会导致「entry did not activate」);改用 apply 内 ctx.get('locale') 可选获取,
|
|
141
|
+
// 无则退化为本地 en/zh 词典(见 locales.mjs)。
|
|
134
142
|
const inject = ['slots', 'connection', 'workspaces', 'sessions'];
|
|
135
143
|
|
|
136
144
|
// semver 比较:a > b
|
|
@@ -3939,7 +3947,7 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
3939
3947
|
// 左侧双横线菜单按钮 (DeepSeek App 原生图标):联动展开 DSH 原生侧边栏会话列表
|
|
3940
3948
|
const leftBtn = document.createElement('button');
|
|
3941
3949
|
leftBtn.className = 'dsh-header-menu-btn';
|
|
3942
|
-
leftBtn.title = '
|
|
3950
|
+
leftBtn.title = localeT('menu.open');
|
|
3943
3951
|
leftBtn.innerHTML = `
|
|
3944
3952
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round">
|
|
3945
3953
|
<line x1="3" y1="8" x2="21" y2="8"></line>
|
|
@@ -3950,7 +3958,12 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
3950
3958
|
e.stopPropagation();
|
|
3951
3959
|
const isOpen = document.body.classList.toggle('dsh-drawer-open');
|
|
3952
3960
|
if (isOpen) {
|
|
3953
|
-
|
|
3961
|
+
// 匹配宿主「展开侧边栏」按钮。宿主文案随界面语言切换:中文「打开侧边栏」、
|
|
3962
|
+
// 英文 "Open sidebar"(0.1.7 起宿主按浏览器语言渲染 en,只匹配中文会漏取,
|
|
3963
|
+
// 导致抽屉滑出但会话列表未挂载 → 侧边栏空白)。
|
|
3964
|
+
const expand = document.querySelector(
|
|
3965
|
+
'button[aria-label*="打开侧边栏"], button[title*="打开侧边栏"], button[aria-label*="Open sidebar"], button[title*="Open sidebar"]',
|
|
3966
|
+
);
|
|
3954
3967
|
if (expand) expand.click();
|
|
3955
3968
|
}
|
|
3956
3969
|
};
|
|
@@ -3967,7 +3980,7 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
3967
3980
|
// 右侧 (+) 新建会话按钮 (DeepSeek App 圆形加号风格)
|
|
3968
3981
|
const rightBtn = document.createElement('button');
|
|
3969
3982
|
rightBtn.className = 'dsh-header-new-btn';
|
|
3970
|
-
rightBtn.title = '
|
|
3983
|
+
rightBtn.title = localeT('menu.newSession');
|
|
3971
3984
|
rightBtn.innerHTML = `
|
|
3972
3985
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
3973
3986
|
<circle cx="12" cy="12" r="9.5"></circle>
|
|
@@ -3978,7 +3991,8 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
3978
3991
|
rightBtn.onclick = () => {
|
|
3979
3992
|
const openPanels = document.querySelectorAll('div[class*="nArs4W_panel"]:not([class*="panelHidden"]), div[class*="workbench_panel"]:not([class*="panelHidden"])');
|
|
3980
3993
|
openPanels.forEach((p) => p.classList.add('nArs4W_panelHidden'));
|
|
3981
|
-
|
|
3994
|
+
// 宿主新建会话按钮文案随语言切换:中文「新建会话」/ 英文 "New session"(两者都要匹配,否则英文界面下 (+) 失效)
|
|
3995
|
+
const dshNewBtn = document.querySelector('button[aria-label="新建会话"], button[aria-label="New session"]');
|
|
3982
3996
|
if (dshNewBtn) dshNewBtn.click();
|
|
3983
3997
|
};
|
|
3984
3998
|
|
|
@@ -3989,16 +4003,52 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
3989
4003
|
}
|
|
3990
4004
|
|
|
3991
4005
|
// 绑定会话标题实时同步 (切换会话或收到首条回复自动更新)
|
|
4006
|
+
//
|
|
4007
|
+
// 标题来源优先取宿主「会话层级」面包屑(PC 顶栏同源):宿主自己维护当前会话标题,
|
|
4008
|
+
// 随会话切换实时更新,0.1.7 的 sessions.list 快照没有 current 字段(旧方案取不到 →
|
|
4009
|
+
// 顶栏永远显示「新会话」)。crumbs 的 nav 带稳定 aria-label(中英双语),最后一级
|
|
4010
|
+
// crumb 就是当前会话标题;空会话时无 crumbs,回退「新会话」。
|
|
4011
|
+
const MOBILE_TITLE_FALLBACK_TEXT = () => localeT('title.fallback');
|
|
4012
|
+
// 取宿主会话层级面包屑的「当前会话标题」。稳定锚点是 nav 的 aria-label(中英双语);
|
|
4013
|
+
// 标题是最后一个 crumbSeg 里的「crumb(当前段)」,不是 nav 里任意最后一个 span——
|
|
4014
|
+
// 子 agent 会话会把末级 crumb 换成 dropdown(内含计数 span),取最后一个 span 会拿到
|
|
4015
|
+
// 计数文本。crumb 本身是 span/button 且带 crumbCurrent 类;取不到则回退空串。
|
|
4016
|
+
const readHostSessionTitle = () => {
|
|
4017
|
+
if (typeof document === 'undefined') return '';
|
|
4018
|
+
const nav = document.querySelector('nav[aria-label="Session hierarchy"], nav[aria-label="会话层级"]');
|
|
4019
|
+
if (!nav) return '';
|
|
4020
|
+
const segs = [...nav.querySelectorAll('[class*="crumbSeg"]')];
|
|
4021
|
+
const lastSeg = segs[segs.length - 1];
|
|
4022
|
+
const lastCrumb = lastSeg
|
|
4023
|
+
? lastSeg.querySelector('[class*="crumbCurrent"]') || lastSeg.querySelector('span, button')
|
|
4024
|
+
: null;
|
|
4025
|
+
if (!lastCrumb) return '';
|
|
4026
|
+
const text = ((lastCrumb.getAttribute?.('aria-label') || lastCrumb.textContent) || '').trim();
|
|
4027
|
+
// 非标题节点(如子 agent 计数 "2 items"/"2 项")不应被当作标题
|
|
4028
|
+
if (!text || /^\d+\s+(item|items|项|个)/i.test(text)) return '';
|
|
4029
|
+
return text;
|
|
4030
|
+
};
|
|
4031
|
+
const setTitleText = (next) => {
|
|
4032
|
+
if (titleEl.textContent === next) return; // 写前比对:避免每帧重写触发 observer 自循环
|
|
4033
|
+
titleEl.innerText = next;
|
|
4034
|
+
};
|
|
3992
4035
|
const syncMobileTitle = () => {
|
|
3993
4036
|
if (!titleEl) titleEl = document.querySelector('.dsh-mobile-header-title');
|
|
3994
4037
|
if (!titleEl) return;
|
|
4038
|
+
// 1) 宿主当前会话标题(最准确,随切换实时更新)
|
|
4039
|
+
const hostTitle = readHostSessionTitle();
|
|
4040
|
+
if (hostTitle) {
|
|
4041
|
+
setTitleText(hostTitle);
|
|
4042
|
+
return;
|
|
4043
|
+
}
|
|
4044
|
+
// 2) 兜底:从 sessions.list 快照尝试(旧宿主可能有 current;新宿主空会话时回退)
|
|
3995
4045
|
const snap = ctx?.sessions?.list?.getSnapshot?.();
|
|
3996
4046
|
if (!snap) return;
|
|
3997
4047
|
const cur = snap.current ? snap.byId?.[snap.current] : null;
|
|
3998
4048
|
if (!cur || cur.blank) {
|
|
3999
|
-
|
|
4049
|
+
setTitleText(MOBILE_TITLE_FALLBACK_TEXT());
|
|
4000
4050
|
} else {
|
|
4001
|
-
|
|
4051
|
+
setTitleText(cur.displayTitle || cur.title || MOBILE_TITLE_FALLBACK_TEXT());
|
|
4002
4052
|
}
|
|
4003
4053
|
};
|
|
4004
4054
|
|
|
@@ -4006,6 +4056,19 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
4006
4056
|
if (typeof ctx?.sessions?.list?.subscribe === 'function') {
|
|
4007
4057
|
ctx.sessions.list.subscribe(syncMobileTitle);
|
|
4008
4058
|
}
|
|
4059
|
+
// 宿主会话层级面包屑是 React 渲染,切换会话/标题变化未必走 ctx.sessions.list 通知;
|
|
4060
|
+
// 补一个 MutationObserver 守护顶栏标题始终与宿主当前会话一致。
|
|
4061
|
+
if (typeof MutationObserver !== 'undefined') {
|
|
4062
|
+
let mobileTitleRaf = 0;
|
|
4063
|
+
const mobileTitleObserver = new MutationObserver(() => {
|
|
4064
|
+
if (mobileTitleRaf) return;
|
|
4065
|
+
mobileTitleRaf = requestAnimationFrame(() => {
|
|
4066
|
+
mobileTitleRaf = 0;
|
|
4067
|
+
syncMobileTitle();
|
|
4068
|
+
});
|
|
4069
|
+
});
|
|
4070
|
+
mobileTitleObserver.observe(document.body, { childList: true, subtree: true, characterData: true });
|
|
4071
|
+
}
|
|
4009
4072
|
if (typeof ctx?.sessions?.active?.subscribe === 'function') {
|
|
4010
4073
|
ctx.sessions.active.subscribe(() => {
|
|
4011
4074
|
if (document.body.classList.contains('dsh-drawer-open')) {
|
|
@@ -4033,12 +4096,12 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
4033
4096
|
if (bar && !bar.querySelector('.dsh-mobile-panel-close-btn')) {
|
|
4034
4097
|
const btn = document.createElement('button');
|
|
4035
4098
|
btn.className = 'dsh-mobile-panel-close-btn';
|
|
4036
|
-
btn.innerHTML = `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg><span
|
|
4099
|
+
btn.innerHTML = `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg><span>${localeT('panel.backToChat')}</span>`;
|
|
4037
4100
|
btn.onclick = (e) => {
|
|
4038
4101
|
e.stopPropagation();
|
|
4039
4102
|
document.body.classList.remove('dsh-workbench-open');
|
|
4040
4103
|
p.classList.add('nArs4W_panelHidden');
|
|
4041
|
-
const collapseBtn = document.querySelector('button[class*="toggleButton"][aria-label*="收起"]');
|
|
4104
|
+
const collapseBtn = document.querySelector('button[class*="toggleButton"][aria-label*="收起"], button[class*="toggleButton"][aria-label*="Collapse"]');
|
|
4042
4105
|
if (collapseBtn) collapseBtn.click();
|
|
4043
4106
|
};
|
|
4044
4107
|
bar.appendChild(btn);
|
|
@@ -4051,18 +4114,22 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
4051
4114
|
window.addEventListener('resize', ensurePanelCloseButton);
|
|
4052
4115
|
|
|
4053
4116
|
// 移动端点击面板/工作区触发按钮时,自动激活 dsh-workbench-open
|
|
4117
|
+
// (宿主文案随语言切换:中文「面板/工作区」/ 英文 "Panel/workspace" —— 注意英文实际是
|
|
4118
|
+
// 小写 "Add workspace"、"Choose workspace" 与大写 "Workspace actions for …",两者都要覆盖;
|
|
4119
|
+
// CSS 属性子串匹配区分大小写,不能只写一种)
|
|
4054
4120
|
document.addEventListener('click', (e) => {
|
|
4055
4121
|
if (typeof window === 'undefined' || window.innerWidth > MOBILE_MAX_WIDTH) return;
|
|
4056
|
-
const trigger = e.target.closest('button[aria-label*="面板"], button[aria-label*="工作区"], div[class*="toggleCluster"] button, button[class*="subagent"], div[class*="headerActions"] button, div[class*="titleRow"] button');
|
|
4122
|
+
const trigger = e.target.closest('button[aria-label*="面板"], button[aria-label*="工作区"], button[aria-label*="workspace"], button[aria-label*="Workspace"], div[class*="toggleCluster"] button, button[class*="subagent"], div[class*="headerActions"] button, div[class*="titleRow"] button');
|
|
4057
4123
|
if (trigger && !trigger.classList.contains('dsh-mobile-panel-close-btn') && !trigger.classList.contains('dsh-header-menu-btn') && !trigger.classList.contains('dsh-header-new-btn')) {
|
|
4058
4124
|
document.body.classList.add('dsh-workbench-open');
|
|
4059
4125
|
}
|
|
4060
4126
|
}, true);
|
|
4061
4127
|
|
|
4062
4128
|
// 移动端点击 DSH 自带的收起侧边栏图标时,自动收起抽屉
|
|
4129
|
+
// (文案随宿主界面语言切换:中文「收起侧边栏」/ 英文 "Collapse sidebar",两者都要匹配)
|
|
4063
4130
|
document.addEventListener('click', (e) => {
|
|
4064
4131
|
if (typeof window === 'undefined' || window.innerWidth > MOBILE_MAX_WIDTH) return;
|
|
4065
|
-
const toggle = e.target.closest('button[aria-label*="收起侧边栏"], button[title*="收起侧边栏"]');
|
|
4132
|
+
const toggle = e.target.closest('button[aria-label*="收起侧边栏"], button[title*="收起侧边栏"], button[aria-label*="Collapse sidebar"], button[title*="Collapse sidebar"]');
|
|
4066
4133
|
if (toggle) {
|
|
4067
4134
|
document.body.classList.remove('dsh-drawer-open');
|
|
4068
4135
|
}
|
|
@@ -4170,8 +4237,8 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
4170
4237
|
if (navigator.vibrate) navigator.vibrate(40);
|
|
4171
4238
|
} catch (_) {}
|
|
4172
4239
|
|
|
4173
|
-
//
|
|
4174
|
-
const actionBtn = sessionRow.querySelector('button[aria-label*="操作"], button[class*="iconButton"], button');
|
|
4240
|
+
// 寻找该会话项内的三点操作按钮并触发点击(宿主文案:中文「操作」/ 英文 "Session actions")
|
|
4241
|
+
const actionBtn = sessionRow.querySelector('button[aria-label*="操作"], button[aria-label*="Session actions"], button[class*="iconButton"], button');
|
|
4175
4242
|
if (actionBtn) {
|
|
4176
4243
|
actionBtn.click();
|
|
4177
4244
|
}
|
|
@@ -4202,7 +4269,9 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
4202
4269
|
if (Math.abs(deltaX) > 60 && Math.abs(deltaX) > Math.abs(deltaY) * 1.5) {
|
|
4203
4270
|
if (deltaX > 0 && touchStartX <= 35) {
|
|
4204
4271
|
document.body.classList.add('dsh-drawer-open');
|
|
4205
|
-
const collapsedToggle = document.querySelector(
|
|
4272
|
+
const collapsedToggle = document.querySelector(
|
|
4273
|
+
'div[class*="hHd-Xa_collapsed"] button[class*="hHd-Xa_toggle"], button[aria-label*="打开侧边栏"], button[title*="打开侧边栏"], button[aria-label*="Open sidebar"], button[title*="Open sidebar"]',
|
|
4274
|
+
);
|
|
4206
4275
|
if (collapsedToggle) collapsedToggle.click();
|
|
4207
4276
|
} else if (deltaX < 0 && document.body.classList.contains('dsh-drawer-open')) {
|
|
4208
4277
|
document.body.classList.remove('dsh-drawer-open');
|
|
@@ -4234,7 +4303,7 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
4234
4303
|
label.includes('添加工作区') ||
|
|
4235
4304
|
label.includes('打开工作区') ||
|
|
4236
4305
|
label.includes('打开文件夹') ||
|
|
4237
|
-
btn.matches('button[aria-label*="工作区"][aria-label*="添加"], button[aria-label*="工作区"][aria-label*="打开"]')
|
|
4306
|
+
btn.matches('button[aria-label*="工作区"][aria-label*="添加"], button[aria-label*="工作区"][aria-label*="打开"], button[aria-label*="Workspace"][aria-label*="add"], button[aria-label*="Workspace"][aria-label*="open"], button[aria-label*="Add workspace"], button[aria-label*="Open workspace"]')
|
|
4238
4307
|
);
|
|
4239
4308
|
|
|
4240
4309
|
if (isAddWorkspace) {
|
|
@@ -5099,7 +5168,7 @@ function setupComposerCollapse() {
|
|
|
5099
5168
|
if (!utils) return null;
|
|
5100
5169
|
const btn = document.createElement('button');
|
|
5101
5170
|
btn.className = 'dsh-header-fold-btn';
|
|
5102
|
-
btn.setAttribute('aria-label', '
|
|
5171
|
+
btn.setAttribute('aria-label', `${localeT('composer.expand')} / ${localeT('composer.collapse')}`);
|
|
5103
5172
|
btn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="3" y1="18" x2="21" y2="18"></line><polyline points="6 9 12 15 18 9"></polyline></svg>';
|
|
5104
5173
|
// 插到 session 下载按钮左侧(紧邻原生工具钮)
|
|
5105
5174
|
if (logBtn && logBtn.parentElement === utils) utils.insertBefore(btn, logBtn);
|
|
@@ -5116,7 +5185,7 @@ function setupComposerCollapse() {
|
|
|
5116
5185
|
if (!scrollBody) return null;
|
|
5117
5186
|
bar = document.createElement('div');
|
|
5118
5187
|
bar.className = 'dsh-composer-collapsed-bar';
|
|
5119
|
-
bar.textContent = '
|
|
5188
|
+
bar.textContent = localeT('composer.placeholder');
|
|
5120
5189
|
scrollBody.insertBefore(bar, seat);
|
|
5121
5190
|
bar.addEventListener('click', () => setCollapsed(false));
|
|
5122
5191
|
return bar;
|
|
@@ -5141,7 +5210,7 @@ function setupComposerCollapse() {
|
|
|
5141
5210
|
const updateButton = (collapsed) => {
|
|
5142
5211
|
const btn = getFoldBtn();
|
|
5143
5212
|
if (!btn) return;
|
|
5144
|
-
btn.title = collapsed ? '
|
|
5213
|
+
btn.title = collapsed ? localeT('composer.expand') : localeT('composer.collapse');
|
|
5145
5214
|
const icon = collapsed
|
|
5146
5215
|
? '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="6" width="20" height="12" rx="2"></rect><line x1="6" y1="10" x2="6.01" y2="10"></line><line x1="10" y1="10" x2="10.01" y2="10"></line><line x1="14" y1="10" x2="14.01" y2="10"></line><line x1="6" y1="14" x2="10" y2="14"></line><line x1="14" y1="14" x2="18" y2="14"></line></svg>'
|
|
5147
5216
|
: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="3" y1="18" x2="21" y2="18"></line><polyline points="6 9 12 15 18 9"></polyline></svg>';
|
|
@@ -5208,6 +5277,11 @@ function setupComposerCollapse() {
|
|
|
5208
5277
|
|
|
5209
5278
|
function apply(ctx) {
|
|
5210
5279
|
window.__dshClientCtx = ctx;
|
|
5280
|
+
// 国际化:创建翻译器(跟随 DSH 主体语言,读 <html lang>;有 locale 服务则顺带注册
|
|
5281
|
+
// 词典,拿不到/被门控也不抛错)。注:locale 服务不能声明进 inject——动态插件声明
|
|
5282
|
+
// 会导致激活等待(宿主 env 无该服务,entry did not activate)。
|
|
5283
|
+
const intl = createTranslator(ctx);
|
|
5284
|
+
localeT = (key, vars) => intl.t(key, vars);
|
|
5211
5285
|
// 放在最前:交互层先就绪,后续任何初始化抛异常都不会留下「CSS 生效但监听器缺失」
|
|
5212
5286
|
// 的状态(CSS 6.2 块另有 <html> 就绪开关双重兜底)。
|
|
5213
5287
|
setupSettingsDrilldown();
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// dsh-bridge 客户端国际化:跟随 DSH 主体语言(LocaleFace)。
|
|
2
|
+
//
|
|
3
|
+
// DSH 0.1.7 的 client 提供 locale 服务(`dsh-client-locale` 的 LocaleFace):
|
|
4
|
+
// - ctx.locale.register(namespace, { zh: {...}, en: {...} }) 注册词典;
|
|
5
|
+
// - ctx.locale.translate(map) 按当前语言取译文(纯字符串原样返回、map 走词典,
|
|
6
|
+
// 未声明语言回退到 fallback 链,最终到 en);
|
|
7
|
+
// - ctx.locale.subscribe(fn) 在语言切换时通知。
|
|
8
|
+
// 插件把 "locale" 加进 inject 后即可拿到 ctx.locale,从而自动跟随 DSH 主体语言
|
|
9
|
+
// (含用户手动切换、浏览器语言回退、<html lang> 同步),无需自建语言开关。
|
|
10
|
+
//
|
|
11
|
+
// 本文件只收「移动端自绘 UI 的用户可见文案」(A 类):顶栏按钮、工作区选择器、
|
|
12
|
+
// 远程访问面板、浏览器输入区提示等。内部字符串(DOM/CSS 类名匹配、注释、日志)
|
|
13
|
+
// 保持中文原样——它们不是 UI 文案,翻译会破坏匹配(见 readHostSessionTitle 教训)。
|
|
14
|
+
|
|
15
|
+
export const LOCALE_NAMESPACE = '@wenbin_wb/dsh-bridge';
|
|
16
|
+
|
|
17
|
+
/** 词典:每个命名空间至少要有 en(最终 fallback);zh 为默认语言。 */
|
|
18
|
+
export const zh = {
|
|
19
|
+
// 移动端顶栏
|
|
20
|
+
'menu.open': '打开菜单',
|
|
21
|
+
'menu.newSession': '新建会话',
|
|
22
|
+
'title.fallback': '新会话',
|
|
23
|
+
// 移动端输入区
|
|
24
|
+
'composer.placeholder': '✍️ 点击输入消息…',
|
|
25
|
+
'composer.expand': '展开输入框',
|
|
26
|
+
'composer.collapse': '收起输入框,最大化对话阅读区',
|
|
27
|
+
'panel.backToChat': '返回对话',
|
|
28
|
+
// 移动端设置面板 / 抽屉(dsh-bridge 自绘部分)
|
|
29
|
+
'workspace.choose': '选择电脑工作区',
|
|
30
|
+
'workspace.reading': '正在读取目录内容…',
|
|
31
|
+
'workspace.currentDir': '当前目录:',
|
|
32
|
+
'workspace.registeredHint': '已注册工作区 (${count} 个,点击直接切换):',
|
|
33
|
+
'workspace.enterHint': '点击进入文件夹,或点击「+ 选为工作区」直接添加并切换',
|
|
34
|
+
'workspace.unlockHint': '远程访问时浏览/添加工作区需输入后台管理密码解锁(与访问密码不同)。',
|
|
35
|
+
'workspace.chooseFolder': '选择工作区',
|
|
36
|
+
'workspace.addFolder': '添加',
|
|
37
|
+
'workspace.refresh': '刷新目录',
|
|
38
|
+
'workspace.back': '返回上一级',
|
|
39
|
+
'workspace.filter': '过滤子文件夹…',
|
|
40
|
+
'workspace.more': '还有更多',
|
|
41
|
+
'workspace.addSubfolder': '直接添加此子文件夹为工作区并进入',
|
|
42
|
+
'workspace.onlyAdmin': '仅限本机管理',
|
|
43
|
+
'workspace.isCurrent': '当前主入口',
|
|
44
|
+
'workspace.isFallback': '主入口',
|
|
45
|
+
// 远程访问面板
|
|
46
|
+
'access.copyLink': '复制链接',
|
|
47
|
+
'access.copied': '✓ 已复制',
|
|
48
|
+
'access.hideQr': '隐藏二维码',
|
|
49
|
+
'access.showQr': '显示二维码',
|
|
50
|
+
'access.privateEnv': '请在私密环境下使用',
|
|
51
|
+
'access.switching': '切换中…',
|
|
52
|
+
'access.publicEntry': '公网访问入口',
|
|
53
|
+
'access.copy': '复制',
|
|
54
|
+
'access.stopReconnect': '停止重连',
|
|
55
|
+
'access.close': '关闭',
|
|
56
|
+
'access.connecting': '连接中…',
|
|
57
|
+
'access.downloading': '下载中…',
|
|
58
|
+
'access.enable': '开启公网隧道',
|
|
59
|
+
'access.scanHint': '请在私密环境下扫码使用',
|
|
60
|
+
'access.autoStart': '随 DSH 启动自动开启',
|
|
61
|
+
'access.tunnelConfig': '隧道配置',
|
|
62
|
+
'access.expand': '展开 ▾',
|
|
63
|
+
'access.collapse': '收起 ▴',
|
|
64
|
+
'access.saveError': '保存配置失败',
|
|
65
|
+
'access.serverConfig': '隧道服务器配置',
|
|
66
|
+
'access.saving': '保存中…',
|
|
67
|
+
'access.saved': '✓ 已保存',
|
|
68
|
+
'access.saveConfig': '保存配置',
|
|
69
|
+
'access.clear': '清除',
|
|
70
|
+
'access.externalTunnel': '外部已部署隧道',
|
|
71
|
+
'access.saveFixed': '保存固定域名配置',
|
|
72
|
+
'access.updateFailed': '更新失败',
|
|
73
|
+
'access.securityHint': '设置外部访客访问密码',
|
|
74
|
+
'access.goSetup': '去开启 ➔',
|
|
75
|
+
'access.gotoSettings': '设置 ➔',
|
|
76
|
+
'access.tutorial': '查看自建隧道服务器搭建教程',
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const en = {
|
|
80
|
+
'menu.open': 'Open menu',
|
|
81
|
+
'menu.newSession': 'New session',
|
|
82
|
+
'title.fallback': 'New Session',
|
|
83
|
+
'composer.placeholder': '✍️ Tap to type a message…',
|
|
84
|
+
'composer.expand': 'Expand input',
|
|
85
|
+
'composer.collapse': 'Collapse input to maximize chat view',
|
|
86
|
+
'panel.backToChat': 'Back to chat',
|
|
87
|
+
'workspace.choose': 'Choose a workspace',
|
|
88
|
+
'workspace.reading': 'Reading directory…',
|
|
89
|
+
'workspace.currentDir': 'Current directory:',
|
|
90
|
+
'workspace.registeredHint': 'Registered workspaces (${count}, click to switch):',
|
|
91
|
+
'workspace.enterHint': 'Enter a folder, or tap “+ Select as workspace” to add & switch',
|
|
92
|
+
'workspace.unlockHint': 'Browsing/adding workspaces remotely requires the admin password to unlock (different from the access password).',
|
|
93
|
+
'workspace.chooseFolder': 'Choose workspace',
|
|
94
|
+
'workspace.addFolder': 'Add',
|
|
95
|
+
'workspace.refresh': 'Refresh',
|
|
96
|
+
'workspace.back': 'Back',
|
|
97
|
+
'workspace.filter': 'Filter subfolders…',
|
|
98
|
+
'workspace.more': 'More',
|
|
99
|
+
'workspace.addSubfolder': 'Add this subfolder as a workspace and enter',
|
|
100
|
+
'workspace.onlyAdmin': 'Local admin only',
|
|
101
|
+
'workspace.isCurrent': 'Current entry',
|
|
102
|
+
'workspace.isFallback': 'Main entry',
|
|
103
|
+
'access.copyLink': 'Copy link',
|
|
104
|
+
'access.copied': '✓ Copied',
|
|
105
|
+
'access.hideQr': 'Hide QR',
|
|
106
|
+
'access.showQr': 'Show QR',
|
|
107
|
+
'access.privateEnv': 'Use in a private environment',
|
|
108
|
+
'access.switching': 'Switching…',
|
|
109
|
+
'access.publicEntry': 'Public access entry',
|
|
110
|
+
'access.copy': 'Copy',
|
|
111
|
+
'access.stopReconnect': 'Stop reconnecting',
|
|
112
|
+
'access.close': 'Close',
|
|
113
|
+
'access.connecting': 'Connecting…',
|
|
114
|
+
'access.downloading': 'Downloading…',
|
|
115
|
+
'access.enable': 'Enable public tunnel',
|
|
116
|
+
'access.scanHint': 'Scan in a private environment',
|
|
117
|
+
'access.autoStart': 'Start automatically with DSH',
|
|
118
|
+
'access.tunnelConfig': 'Tunnel configuration',
|
|
119
|
+
'access.expand': 'Expand ▾',
|
|
120
|
+
'access.collapse': 'Collapse ▴',
|
|
121
|
+
'access.saveError': 'Failed to save configuration',
|
|
122
|
+
'access.serverConfig': 'Tunnel server configuration',
|
|
123
|
+
'access.saving': 'Saving…',
|
|
124
|
+
'access.saved': '✓ Saved',
|
|
125
|
+
'access.saveConfig': 'Save configuration',
|
|
126
|
+
'access.clear': 'Clear',
|
|
127
|
+
'access.externalTunnel': 'External tunnel',
|
|
128
|
+
'access.saveFixed': 'Save fixed-domain configuration',
|
|
129
|
+
'access.updateFailed': 'Update failed',
|
|
130
|
+
'access.securityHint': 'Set an external visitor access password',
|
|
131
|
+
'access.goSetup': 'Set up ➔',
|
|
132
|
+
'access.gotoSettings': 'Settings ➔',
|
|
133
|
+
'access.tutorial': 'View the self-hosted tunnel server tutorial',
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 构造一个翻译函数:优先用宿主的 ctx.locale.translate(跟随主体语言),
|
|
138
|
+
* 宿主不支持时退化为「取当前语言词典(en/zh)+ 互为兜底」。
|
|
139
|
+
* @param {object} ctx - Cordis 客户端上下文(含可选 locale 服务)
|
|
140
|
+
* @returns {{ t: Function, active: () => string, subscribe: Function }}
|
|
141
|
+
*/
|
|
142
|
+
/**
|
|
143
|
+
* 构造翻译器:读 DSH 主体语言(`<html lang>`,宿主 LocaleFace 实时同步),
|
|
144
|
+
* 中英词典按当前语言选词。`ctx.locale` 只作可选的词典注册(拿到则注册,
|
|
145
|
+
* 拿不到或未声明 inject 也不抛错、不影响翻译——动态插件无法把 locale 加进
|
|
146
|
+
* inject,宿主会对未声明服务抛 denyRead,详见 index.js 注释)。
|
|
147
|
+
* @param {object} ctx - Cordis 客户端上下文(可选)
|
|
148
|
+
* @returns {{ t: Function, active: () => string }}
|
|
149
|
+
*/
|
|
150
|
+
export function createTranslator(ctx) {
|
|
151
|
+
// 当前语言:宿主把 active 语言同步到 <html lang>(zh→zh-CN,其余原样)。
|
|
152
|
+
// 读它即可跟随 DSH 主体语言且无需声明 locale 服务。
|
|
153
|
+
const active = () => {
|
|
154
|
+
const lang = (typeof document !== 'undefined' ? document.documentElement.getAttribute('lang') : '') || '';
|
|
155
|
+
return lang.toLowerCase().startsWith('zh') ? 'zh' : 'en';
|
|
156
|
+
};
|
|
157
|
+
const entryOf = (key) => ({ zh: zh[key], en: en[key] });
|
|
158
|
+
const t = (key, vars) => {
|
|
159
|
+
const e = entryOf(key);
|
|
160
|
+
let text = e[active()] ?? e.en ?? e.zh ?? '';
|
|
161
|
+
if (vars && text) {
|
|
162
|
+
for (const [k, v] of Object.entries(vars)) text = text.replaceAll(`\${${k}}`, String(v));
|
|
163
|
+
}
|
|
164
|
+
return text;
|
|
165
|
+
};
|
|
166
|
+
// 可选:向宿主注册词典(仅当其有 register 且可访问时;失败无妨)
|
|
167
|
+
try {
|
|
168
|
+
const locale = ctx && (typeof ctx.get === 'function' ? ctx.get('locale') : ctx.locale);
|
|
169
|
+
if (locale && typeof locale.register === 'function') {
|
|
170
|
+
locale.register(LOCALE_NAMESPACE, { zh, en });
|
|
171
|
+
}
|
|
172
|
+
} catch { /* 动态插件的 locale 服务被宿主门控时忽略 */ }
|
|
173
|
+
return { t, active };
|
|
174
|
+
}
|
package/client/mobile-styles.js
CHANGED
|
@@ -89,29 +89,49 @@ export const MOBILE_STYLES_CSS = `
|
|
|
89
89
|
表现为「看不到最新消息」。官方给该容器的规则是 flex:none;height:auto
|
|
90
90
|
(让记录随内容自然增高、由外层统一滚动),与这里冲突,故必须排除。 */
|
|
91
91
|
[data-slot="root"] > div[class*="_frame"] {
|
|
92
|
-
display:
|
|
93
|
-
|
|
92
|
+
display: grid !important;
|
|
93
|
+
grid-template-columns: 0px minmax(0px, 1fr) minmax(0px, 0px) !important;
|
|
94
|
+
grid-template-rows: minmax(0, 100%) !important;
|
|
94
95
|
width: 100vw !important;
|
|
95
96
|
height: 100dvh !important;
|
|
96
97
|
margin: 0 !important;
|
|
97
98
|
padding-top: var(--dsh-mobile-header-h) !important;
|
|
98
99
|
position: relative !important;
|
|
99
|
-
grid-template-columns: 1fr !important;
|
|
100
100
|
overflow: hidden !important;
|
|
101
101
|
box-sizing: border-box !important;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/* 宿主 0.1.7 的 frame 是 grid 三列布局(sidebar | center | rightbar),JS 按状态注入
|
|
105
|
+
grid-template-columns;插件把左侧栏抽屉化后 sidebarCol 变成 fixed(脱离 grid 流),
|
|
106
|
+
若不显式指定列位,centerCol 会被自动排进第 1 列(56px 轨道)、rightbarCol 塌陷到
|
|
107
|
+
视口底部 → 右侧栏点开后面板被定位到视口外(用户看到的「按钮消失、右侧栏没出来」)。
|
|
108
|
+
这里显式归位:sidebar 抽屉不占列(第 1 列 0px)、center 占满(第 2 列 1fr)、
|
|
109
|
+
rightbar 保留宿主右侧栏列(第 3 列 0px 折叠,全屏面板 fixed 相对视口、不依赖此列)。 */
|
|
110
|
+
div[class*="_centerCol"] { grid-column: 2 !important; }
|
|
111
|
+
div[class*="_rightbarCol"] { grid-column: 3 !important; }
|
|
112
|
+
div[class*="_overlayLayer"] { grid-column: 1 / -1 !important; }
|
|
113
|
+
|
|
104
114
|
/* 1.1 fixed 全屏面板单独让位:position:fixed 的包含块是 viewport(CSS 2.1 §10.1),
|
|
105
115
|
不跟随上面 frame 的 padding-top,因此顶部 52px 会落进顶栏覆盖区(#41)。
|
|
106
|
-
官方右侧栏在 <768px
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
116
|
+
官方右侧栏在 <768px 自动全屏,这里按 data 属性直接位移容器本身,不依赖宿主
|
|
117
|
+
CSS-module 哈希类名。
|
|
118
|
+
宿主版本差异(0.1.5 vs 0.1.7):0.1.5 的面板自身就是 position:fixed; inset:0
|
|
119
|
+
(相对视口),top 让位直接生效;0.1.7 把面板改成 position:absolute 相对
|
|
120
|
+
rightbarCol 列定位,且宿主已把它放在内容区起点——若插件仍只改 top,会在宿主
|
|
121
|
+
定位之上再叠加 52px(面板被推到视口外,表现为「点开右侧栏按钮消失、面板不
|
|
122
|
+
出来」)。修法:把面板统一强制为 fixed 相对视口 + top:52px 让位,两版宿主
|
|
123
|
+
行为一致。 */
|
|
111
124
|
[data-sidebar-right-panel="fullscreen"] {
|
|
125
|
+
position: fixed !important;
|
|
112
126
|
top: var(--dsh-mobile-header-h, 52px) !important;
|
|
127
|
+
left: 0 !important;
|
|
128
|
+
right: 0 !important;
|
|
129
|
+
bottom: 0 !important;
|
|
130
|
+
width: 100vw !important;
|
|
113
131
|
height: calc(100dvh - var(--dsh-mobile-header-h, 52px)) !important;
|
|
114
132
|
max-height: calc(100dvh - var(--dsh-mobile-header-h, 52px)) !important;
|
|
133
|
+
z-index: 40 !important;
|
|
134
|
+
box-sizing: border-box !important;
|
|
115
135
|
}
|
|
116
136
|
|
|
117
137
|
/* 2. 顶部原生导航条:100% 还原 DeepSeek App (左侧双横线,右侧(+),中间留白,无多余设置按钮) */
|
package/docs/release-process.md
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
3. **构建与测试**:
|
|
24
24
|
- `npm run build:client`(**必须**:客户端产物要与源码同步,`npm test` 有一项断言会校验产物内嵌的 CSS 与源码逐字一致)
|
|
25
25
|
- `npm test` + `npm run lint`
|
|
26
|
-
- `npm run verify:mobile-ui`(**发布前必做**:跑真实 GUI 的移动端行为验收,
|
|
26
|
+
- `npm run verify:mobile-ui`(**发布前必做**:跑真实 GUI 的移动端行为验收,8 个套件。需要 dsh web 正在运行;Chrome 路径可用 `PUPPETEER_EXECUTABLE_PATH` 指定。它不在 CI 里跑,因为 CI 没有宿主)
|
|
27
27
|
- `npm run build:banner`(**仅当 `docs/screenshots/` 素材有变、需要重出 README banner 时执行**;输入没变时应跳过,否则只会产生 JPEG 重编码噪声。注意该脚本默认 Chrome 路径是写死的 Windows 路径,需用 `PUPPETEER_EXECUTABLE_PATH` 覆盖)
|
|
28
28
|
- `npm run build:lark`(**仅在需要重建飞书 vendor bundle 时执行**。它用当前 esbuild 重新打包 `lib/feishu/lark-bundled.mjs`,产物会随 esbuild 版本漂移;**不要放进 `prepack`**,否则每次 `npm publish` 都会让线上 tarball 里的 vendor 文件与 tag 里的不一致)
|
|
29
29
|
4. **提交**:合并到 `main`(fast-forward),提交信息 `release: vX.Y.Z — ...`
|
package/locale/en.json
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"meta": {
|
|
3
|
+
"title": "DSH Bridge — Remote Access & Security",
|
|
4
|
+
"description": "Scan the QR code to keep using DeepSeek Harness on mobile or public networks when you're away from your computer. One-tap LAN QR code, Cloudflare public tunnel, self-hosted tunnel, and WeChat / QQ / Lark / Telegram bots (multi-workspace / session persistence / media / card approval / streaming output), no public server required."
|
|
5
|
+
}
|
|
6
|
+
}
|
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wenbin_wb/dsh-bridge",
|
|
3
|
-
"version": "2.11.
|
|
3
|
+
"version": "2.11.3",
|
|
4
4
|
"description": "手机扫码即可在移动端/公网继续用 DeepSeek Harness,人不在电脑前也能接着干。一键局域网二维码、Cloudflare 公网隧道、自建隧道与微信 / QQ / 飞书 / Telegram Bot(多工作区/会话持久化/媒体/卡片审批/流式输出),无需自己搭公网服务器。",
|
|
5
|
-
"releaseNotes": "【v2.11.
|
|
5
|
+
"releaseNotes": "【v2.11.3】移动端顶栏标题修复 + 国际化(跟随 DSH 主体语言)\n• 修复:移动端顶栏会话标题恒为「新会话」——0.1.7 起会话快照无 current 字段,改为读宿主会话层级面包屑(PC 顶栏同源)\n• 新功能:移动端自绘 UI 文案国际化,随 DSH 主体语言切换(中/英)\n• 新功能:插件管理页/市场显示本插件的本地化名称(locale/en.json + zh.json)\n• 说明:纯前端改动 + 包元数据,刷新页面即生效",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "lib/index.js",
|
|
8
8
|
"exports": {
|
|
@@ -10,11 +10,13 @@
|
|
|
10
10
|
"default": "./lib/index.js"
|
|
11
11
|
},
|
|
12
12
|
"./client": "./client/client.js",
|
|
13
|
+
"./locale/*": "./locale/*",
|
|
13
14
|
"./package.json": "./package.json"
|
|
14
15
|
},
|
|
15
16
|
"files": [
|
|
16
17
|
"lib",
|
|
17
18
|
"client",
|
|
19
|
+
"locale",
|
|
18
20
|
"cordis.patch.yml",
|
|
19
21
|
"README.md",
|
|
20
22
|
"README.en.md",
|