dsh-pocket 2.1.4 → 2.3.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.en.md +1 -0
- package/README.md +1 -0
- package/client/client.js +36 -6
- package/client/mobile/layout-mode.mjs +46 -0
- package/client/mobile/mobile-apply.tsx +17 -6
- package/lib/index.js +4 -2
- package/lib/settings.mjs +20 -0
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -132,6 +132,7 @@ Note: in named-tunnel mode the public PIN is **not auto-rotated** (the address i
|
|
|
132
132
|
| `ERR_PNPM_ADDING_TO_ROOT` | pnpm 9 workspace-root restriction: append `-w` (`--workspace-root`) to install/update commands |
|
|
133
133
|
| Nothing changed after install/update | **You must restart `dsh web`**; the running process still loads the old code |
|
|
134
134
|
| `listen EADDRINUSE ... :3081` | A stale dsh-pocket process holds the port: macOS/Linux `lsof -ti :3081 \| xargs kill -9`; Windows `netstat -ano \| findstr :3081` (find the LISTENING PID) → `taskkill /PID <PID> /F`, then retry |
|
|
135
|
+
| Want a different port (issue #70) | Plugin mode: write `"proxyPort": 3082` into `$DSH_HOME/dsh-pocket/settings.json` and restart `dsh web`. CLI mode: `dsh-pocket --port 3082`. If the port is taken you'll get `EADDRINUSE` — kill the old process or pick another one |
|
|
135
136
|
| Version stuck below 1.x | `^0.x` ranges never jump to 1.x: update with `--latest` (`dsh plugin --profile web update dsh-pocket --latest -w`) |
|
|
136
137
|
| Public `error 1033` | See "Public tunnel troubleshooting" below — usually a local proxy/VPN (Clash etc. TUN mode) killing the tunnel |
|
|
137
138
|
| After "Restart dsh web", the page says the process is running in the background | The new process from in-page self-restart is a detached background process (not attached to your terminal) — that's the standard way to apply updates in-page; stop it: macOS/Linux `lsof -ti :3080 \| xargs kill -9`; Windows `netstat -ano \| findstr :3080` → `taskkill /PID <PID> /F` (logs under `$DSH_HOME` as `dsh-pocket-restart-*.log`) |
|
package/README.md
CHANGED
|
@@ -132,6 +132,7 @@ npx @deepseek-ai/dsh web
|
|
|
132
132
|
| `ERR_PNPM_ADDING_TO_ROOT` | pnpm 9 对 workspace 根的限制:安装/更新命令**末尾加 `-w`**(`--workspace-root`) |
|
|
133
133
|
| 装完/更新了但界面没变化 | **必须重启 `dsh web`** 才生效;运行中的进程仍加载旧代码 |
|
|
134
134
|
| `listen EADDRINUSE ... :3081` | 旧 dsh-pocket 进程还占着端口:macOS/Linux `lsof -ti :3081 \| xargs kill -9`;Windows `netstat -ano \| findstr :3081`(找 LISTENING 的 PID)→ `taskkill /PID <PID> /F`,后重试 |
|
|
135
|
+
| 想换端口(issue #70) | 插件模式:在 `$DSH_HOME/dsh-pocket/settings.json` 写 `"proxyPort": 3082` 后重启 `dsh web`。CLI 模式:`dsh-pocket --port 3082`。端口被占会报 `EADDRINUSE`,杀掉旧进程或换一个端口 |
|
|
135
136
|
| 版本停在 0.x 升不上去 | `^0.x` 范围不允许升到 1.x:更新用 `--latest`(`dsh plugin --profile web update dsh-pocket --latest -w`) |
|
|
136
137
|
| 公网 `error 1033` | 见下方「公网隧道常见问题」——多半是本机代理/VPN(Clash 等 TUN 模式)掐断了隧道 |
|
|
137
138
|
| 点「重启 dsh web」后页面提示进程在后台运行 | 自重启的新进程是 detached 后台进程(不挂终端),是页内更新的标准做法;停止它:macOS/Linux `lsof -ti :3080 \| xargs kill -9`;Windows `netstat -ano \| findstr :3080` → `taskkill /PID <PID> /F`(日志在 `$DSH_HOME` 下 `dsh-pocket-restart-*.log`) |
|
package/client/client.js
CHANGED
|
@@ -1231,8 +1231,44 @@ var en = {
|
|
|
1231
1231
|
"files": "Files"
|
|
1232
1232
|
};
|
|
1233
1233
|
|
|
1234
|
+
// client/mobile/layout-mode.mjs
|
|
1235
|
+
function resolveLayout({ urlValue, stored, narrowMatch }) {
|
|
1236
|
+
const url = String(urlValue ?? "").trim();
|
|
1237
|
+
if (url === "desktop") return "desktop";
|
|
1238
|
+
if (url === "mobile") return "mobile";
|
|
1239
|
+
if (stored === "desktop" || stored === "mobile") return stored;
|
|
1240
|
+
return narrowMatch ? "mobile" : "desktop";
|
|
1241
|
+
}
|
|
1242
|
+
function persistLayoutFromUrl(urlValue) {
|
|
1243
|
+
if (typeof localStorage === "undefined") return "";
|
|
1244
|
+
const v = String(urlValue ?? "").trim();
|
|
1245
|
+
try {
|
|
1246
|
+
if (v === "desktop" || v === "mobile") localStorage.setItem("dsh-pocket.layout", v);
|
|
1247
|
+
else if (v === "auto" || v === "") localStorage.removeItem("dsh-pocket.layout");
|
|
1248
|
+
} catch {
|
|
1249
|
+
}
|
|
1250
|
+
try {
|
|
1251
|
+
const s = localStorage.getItem("dsh-pocket.layout");
|
|
1252
|
+
return s === "desktop" || s === "mobile" ? s : "";
|
|
1253
|
+
} catch {
|
|
1254
|
+
return "";
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1234
1258
|
// client/mobile/mobile-apply.tsx
|
|
1235
1259
|
function mobileApply(ctx) {
|
|
1260
|
+
const urlValue = new URL(window.location.href).searchParams.get("dsh-layout") ?? "";
|
|
1261
|
+
const narrowMQ = window.matchMedia("(max-width: 1023px)");
|
|
1262
|
+
const stored = persistLayoutFromUrl(urlValue);
|
|
1263
|
+
const layout = resolveLayout({ urlValue, stored, narrowMatch: narrowMQ.matches });
|
|
1264
|
+
document.body?.setAttribute("data-dsh-pocket-layout", layout);
|
|
1265
|
+
if (layout === "desktop") return;
|
|
1266
|
+
let narrow = narrowMQ;
|
|
1267
|
+
if (layout === "mobile") {
|
|
1268
|
+
narrow = { matches: true, addEventListener: () => {
|
|
1269
|
+
}, removeEventListener: () => {
|
|
1270
|
+
} };
|
|
1271
|
+
}
|
|
1236
1272
|
ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-mobile-nav: dictionaries");
|
|
1237
1273
|
ctx.effect(() => {
|
|
1238
1274
|
const tag = document.createElement("style");
|
|
@@ -1245,7 +1281,6 @@ function mobileApply(ctx) {
|
|
|
1245
1281
|
};
|
|
1246
1282
|
}, "dsh-mobile-nav: styles");
|
|
1247
1283
|
ctx.effect(() => {
|
|
1248
|
-
const narrow = window.matchMedia("(max-width: 1023px)");
|
|
1249
1284
|
const viewport = document.querySelector('meta[name="viewport"]');
|
|
1250
1285
|
const originalViewport = viewport?.content ?? "";
|
|
1251
1286
|
const themeMeta = document.createElement("meta");
|
|
@@ -1277,7 +1312,6 @@ function mobileApply(ctx) {
|
|
|
1277
1312
|
};
|
|
1278
1313
|
}, "dsh-mobile-nav: status bar theme + viewport + zoom guard");
|
|
1279
1314
|
ctx.effect(() => {
|
|
1280
|
-
const narrow = window.matchMedia("(max-width: 1023px)");
|
|
1281
1315
|
if (!narrow.matches) return () => {
|
|
1282
1316
|
};
|
|
1283
1317
|
const onChevronClick = (event) => {
|
|
@@ -1289,7 +1323,6 @@ function mobileApply(ctx) {
|
|
|
1289
1323
|
return () => document.removeEventListener("click", onChevronClick, true);
|
|
1290
1324
|
}, "dsh-mobile-nav: aionui explorer close marker");
|
|
1291
1325
|
ctx.effect(() => {
|
|
1292
|
-
const narrow = window.matchMedia("(max-width: 1023px)");
|
|
1293
1326
|
if (!narrow.matches) return () => {
|
|
1294
1327
|
};
|
|
1295
1328
|
const frame = () => document.querySelector('[data-mobile-nav="frame"]');
|
|
@@ -1307,7 +1340,6 @@ function mobileApply(ctx) {
|
|
|
1307
1340
|
};
|
|
1308
1341
|
}, "dsh-mobile-nav: explorer availability (issue #48)");
|
|
1309
1342
|
ctx.effect(() => {
|
|
1310
|
-
const narrow = window.matchMedia("(max-width: 1023px)");
|
|
1311
1343
|
if (!narrow.matches) return () => {
|
|
1312
1344
|
};
|
|
1313
1345
|
const frame = () => document.querySelector('[data-mobile-nav="frame"]');
|
|
@@ -1332,7 +1364,6 @@ function mobileApply(ctx) {
|
|
|
1332
1364
|
};
|
|
1333
1365
|
}, "dsh-mobile-nav: preview sheet open marker");
|
|
1334
1366
|
ctx.effect(() => {
|
|
1335
|
-
const narrow = window.matchMedia("(max-width: 1023px)");
|
|
1336
1367
|
if (!narrow.matches) return () => {
|
|
1337
1368
|
};
|
|
1338
1369
|
const moveTps = (stats) => {
|
|
@@ -1366,7 +1397,6 @@ function mobileApply(ctx) {
|
|
|
1366
1397
|
};
|
|
1367
1398
|
}, "dsh-mobile-nav: stats line marker");
|
|
1368
1399
|
ctx.effect(() => {
|
|
1369
|
-
const narrow = window.matchMedia("(max-width: 1023px)");
|
|
1370
1400
|
if (!narrow.matches) return () => {
|
|
1371
1401
|
};
|
|
1372
1402
|
const cols = ["[data-aionui-explorer-col]", "[data-aionui-preview-col]"];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// 布局模式判定(issue #74:宽屏手机/Pad 可选电脑布局)
|
|
2
|
+
//
|
|
3
|
+
// 优先级:URL 参数 (?dsh-layout=desktop|mobile|auto) > localStorage > 默认 auto。
|
|
4
|
+
// - 'auto' 不写 localStorage,仅按 matchMedia 走;
|
|
5
|
+
// - 显式 'desktop' / 'mobile' 同步到 localStorage,下次无 URL 参数也走同样布局。
|
|
6
|
+
//
|
|
7
|
+
// 纯函数,便于单测;mobile-apply.tsx 读 DOM/localStorage 喂进来。
|
|
8
|
+
|
|
9
|
+
/** @typedef {'mobile' | 'desktop'} ForcedLayout */
|
|
10
|
+
|
|
11
|
+
/** 解析输入。 */
|
|
12
|
+
export function resolveLayout({ urlValue, stored, narrowMatch }) {
|
|
13
|
+
const url = String(urlValue ?? '').trim();
|
|
14
|
+
if (url === 'desktop') return 'desktop';
|
|
15
|
+
if (url === 'mobile') return 'mobile';
|
|
16
|
+
// 'auto' / 空 / 非法值 → 用 localStorage / matchMedia
|
|
17
|
+
if (stored === 'desktop' || stored === 'mobile') return stored;
|
|
18
|
+
return narrowMatch ? 'mobile' : 'desktop';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** 同步 localStorage 的副作用(在 mobileApply 入口跑一次)。返回最终存储值。 */
|
|
22
|
+
export function persistLayoutFromUrl(urlValue) {
|
|
23
|
+
if (typeof localStorage === 'undefined') return '';
|
|
24
|
+
const v = String(urlValue ?? '').trim();
|
|
25
|
+
try {
|
|
26
|
+
if (v === 'desktop' || v === 'mobile') localStorage.setItem('dsh-pocket.layout', v);
|
|
27
|
+
else if (v === 'auto' || v === '') localStorage.removeItem('dsh-pocket.layout');
|
|
28
|
+
} catch { /* 隐私模式/无 storage → 静默 */ }
|
|
29
|
+
try {
|
|
30
|
+
const s = localStorage.getItem('dsh-pocket.layout');
|
|
31
|
+
return s === 'desktop' || s === 'mobile' ? s : '';
|
|
32
|
+
} catch {
|
|
33
|
+
return '';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 读 localStorage 当前的 layout 值('desktop' | 'mobile' | '')。 */
|
|
38
|
+
export function readStoredLayout() {
|
|
39
|
+
if (typeof localStorage === 'undefined') return '';
|
|
40
|
+
try {
|
|
41
|
+
const v = localStorage.getItem('dsh-pocket.layout');
|
|
42
|
+
return v === 'desktop' || v === 'mobile' ? v : '';
|
|
43
|
+
} catch {
|
|
44
|
+
return '';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -6,6 +6,7 @@ import { MobileDrawerFooter } from './MobileDrawerFooter.tsx'
|
|
|
6
6
|
import { MOBILE_CSS } from './mobile.css.ts'
|
|
7
7
|
import { NS, en, zh } from './locales.ts'
|
|
8
8
|
import type { MobileNavKey } from './locales.ts'
|
|
9
|
+
import { resolveLayout, persistLayoutFromUrl } from './layout-mode.mjs'
|
|
9
10
|
|
|
10
11
|
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
11
12
|
interface LocaleNamespaceMap {
|
|
@@ -23,6 +24,22 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
|
23
24
|
* @param ctx - client root context.
|
|
24
25
|
*/
|
|
25
26
|
export function mobileApply(ctx): void {
|
|
27
|
+
// 布局模式(issue #74):URL 参数 > localStorage > auto(=matchMedia)。
|
|
28
|
+
// desktop 模式(宽屏手机/平板强制电脑布局)下整段 mobile 效果都不挂——
|
|
29
|
+
// 不加 styles、不挂 slots、不跑 effects,直接走 DSH 原生桌面 UI。
|
|
30
|
+
const urlValue = new URL(window.location.href).searchParams.get('dsh-layout') ?? '';
|
|
31
|
+
const narrowMQ = window.matchMedia('(max-width: 1023px)');
|
|
32
|
+
const stored = persistLayoutFromUrl(urlValue);
|
|
33
|
+
const layout = resolveLayout({ urlValue, stored, narrowMatch: narrowMQ.matches });
|
|
34
|
+
document.body?.setAttribute('data-dsh-pocket-layout', layout);
|
|
35
|
+
if (layout === 'desktop') return;
|
|
36
|
+
// 强制 mobile:narrow 永远 true;宽度变化不再切换(用户已显式选 mobile)
|
|
37
|
+
// auto 模式:narrow 是真实的 matchMedia,宽度变化会触发 effect 挂载/卸载
|
|
38
|
+
let narrow: MediaQueryList = narrowMQ;
|
|
39
|
+
if (layout === 'mobile') {
|
|
40
|
+
narrow = { matches: true, addEventListener: () => {}, removeEventListener: () => {} } as MediaQueryList;
|
|
41
|
+
}
|
|
42
|
+
|
|
26
43
|
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-mobile-nav: dictionaries')
|
|
27
44
|
|
|
28
45
|
ctx.effect(() => {
|
|
@@ -52,7 +69,6 @@ export function mobileApply(ctx): void {
|
|
|
52
69
|
// zoom; modern browsers are covered by the stylesheet's
|
|
53
70
|
// touch-action: manipulation (which keeps pan and pinch zoom).
|
|
54
71
|
ctx.effect(() => {
|
|
55
|
-
const narrow = window.matchMedia('(max-width: 1023px)')
|
|
56
72
|
const viewport = document.querySelector<HTMLMetaElement>('meta[name="viewport"]')
|
|
57
73
|
const originalViewport = viewport?.content ?? ''
|
|
58
74
|
const themeMeta = document.createElement('meta')
|
|
@@ -96,7 +112,6 @@ export function mobileApply(ctx): void {
|
|
|
96
112
|
// sheet's own collapse chevron is tapped, so closing is symmetric with
|
|
97
113
|
// opening.
|
|
98
114
|
ctx.effect(() => {
|
|
99
|
-
const narrow = window.matchMedia('(max-width: 1023px)')
|
|
100
115
|
if (!narrow.matches) return () => {}
|
|
101
116
|
const onChevronClick = (event: MouseEvent) => {
|
|
102
117
|
const target = event.target as HTMLElement | null
|
|
@@ -115,7 +130,6 @@ export function mobileApply(ctx): void {
|
|
|
115
130
|
// `data-mobile-nav-explorer="1|0"` so the stylesheet can hide the entries
|
|
116
131
|
// on hosts without it (dsh-web-ui installs keep the feature).
|
|
117
132
|
ctx.effect(() => {
|
|
118
|
-
const narrow = window.matchMedia('(max-width: 1023px)')
|
|
119
133
|
if (!narrow.matches) return () => {}
|
|
120
134
|
const frame = (): HTMLElement | null => document.querySelector('[data-mobile-nav="frame"]')
|
|
121
135
|
const check = () => {
|
|
@@ -141,7 +155,6 @@ export function mobileApply(ctx): void {
|
|
|
141
155
|
// whenever the suite hides the column again (collapse chevron / tab
|
|
142
156
|
// close), so a restored-but-unwanted sheet never appears.
|
|
143
157
|
ctx.effect(() => {
|
|
144
|
-
const narrow = window.matchMedia('(max-width: 1023px)')
|
|
145
158
|
if (!narrow.matches) return () => {}
|
|
146
159
|
const frame = (): HTMLElement | null => document.querySelector('[data-mobile-nav="frame"]')
|
|
147
160
|
const onTap = (event: MouseEvent) => {
|
|
@@ -173,7 +186,6 @@ export function mobileApply(ctx): void {
|
|
|
173
186
|
// marked row out as ONE horizontally scrolling line with every metric
|
|
174
187
|
// reachable.
|
|
175
188
|
ctx.effect(() => {
|
|
176
|
-
const narrow = window.matchMedia('(max-width: 1023px)')
|
|
177
189
|
if (!narrow.matches) return () => {}
|
|
178
190
|
// The composer root renders the TPS readout ("TPS 89.4 tok/s") as its
|
|
179
191
|
// own row BELOW the status strip; fold it into the strip so every
|
|
@@ -218,7 +230,6 @@ export function mobileApply(ctx): void {
|
|
|
218
230
|
// with the Web Animations API each time a column turns visible, then
|
|
219
231
|
// leave the resting state to the stylesheet.
|
|
220
232
|
ctx.effect(() => {
|
|
221
|
-
const narrow = window.matchMedia('(max-width: 1023px)')
|
|
222
233
|
if (!narrow.matches) return () => {}
|
|
223
234
|
const cols = ['[data-aionui-explorer-col]', '[data-aionui-preview-col]']
|
|
224
235
|
const seen = new Map<string, boolean>()
|
package/lib/index.js
CHANGED
|
@@ -21,7 +21,7 @@ import { createPocketService } from './service.mjs';
|
|
|
21
21
|
import { installPocketRpc } from './web-rpc.js';
|
|
22
22
|
import { restartHost } from './restart.js';
|
|
23
23
|
import { advancedNoticeScript, DEFAULT_INJECT, classifyHost } from './proxy.mjs';
|
|
24
|
-
import { lanEnabled, setLanEnabled, lanAuthEnabled, setLanAuthEnabled, lanIpOverride, setLanIpOverride, pinCustom, setPinCustom, tunnelMode, setTunnelMode, tunnelToken, setTunnelToken, tunnelHostname, setTunnelHostname, resetSettings } from './settings.mjs';
|
|
24
|
+
import { lanEnabled, setLanEnabled, lanAuthEnabled, setLanAuthEnabled, lanIpOverride, setLanIpOverride, pinCustom, setPinCustom, tunnelMode, setTunnelMode, tunnelToken, setTunnelToken, tunnelHostname, setTunnelHostname, resetSettings, proxyPort } from './settings.mjs';
|
|
25
25
|
|
|
26
26
|
const name = 'dsh-pocket';
|
|
27
27
|
const inject = ['connection', 'webServer'];
|
|
@@ -246,7 +246,9 @@ export function apply(ctx, config = {}, internals = {}) {
|
|
|
246
246
|
|
|
247
247
|
const service = internals.service ?? createPocketService({
|
|
248
248
|
dshPort,
|
|
249
|
-
|
|
249
|
+
// 端口(issue #70):插件模式优先级 settings.proxyPort > cordis patch config.port > 3081。
|
|
250
|
+
// 3081 被占时 createPocketService 内部 EADDRINUSE 抛错,按 DSH 插件启动失败处理(用户改端口重启即可)。
|
|
251
|
+
port: internals.port ?? config.port ?? (proxyPort() || 3081),
|
|
250
252
|
home: internals.home,
|
|
251
253
|
internals,
|
|
252
254
|
getLanIpOverride: () => lanIpOverride(),
|
package/lib/settings.mjs
CHANGED
|
@@ -192,3 +192,23 @@ export function setTunnelHostname(value) {
|
|
|
192
192
|
writeSettings(s);
|
|
193
193
|
return v;
|
|
194
194
|
}
|
|
195
|
+
|
|
196
|
+
// ---------- 代理端口(issue #70) ----------
|
|
197
|
+
// 局域网代理的监听端口(默认 3081)。插件模式下唯一改法就是写 settings.json 的
|
|
198
|
+
// proxyPort 字段(CLI 模式可用 dsh-pocket --port)。允许范围 1-65535;
|
|
199
|
+
// 端口已被占用时 dsh web 启动会直接抛 EADDRINUSE(保持原行为),不必在 setter 校验。
|
|
200
|
+
/** 当前代理端口(0 = 用默认 3081)。 */
|
|
201
|
+
export function proxyPort() {
|
|
202
|
+
const v = Number(readSettings().proxyPort);
|
|
203
|
+
return Number.isInteger(v) && v >= 1 && v <= 65535 ? v : 0;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** 设置代理端口(持久化)。空/0/非法值清除,回退默认 3081。 */
|
|
207
|
+
export function setProxyPort(value) {
|
|
208
|
+
const n = Number(value);
|
|
209
|
+
const s = readSettings();
|
|
210
|
+
if (Number.isInteger(n) && n >= 1 && n <= 65535) s.proxyPort = n;
|
|
211
|
+
else delete s.proxyPort;
|
|
212
|
+
writeSettings(s);
|
|
213
|
+
return proxyPort();
|
|
214
|
+
}
|
package/package.json
CHANGED