dsh-pocket 2.1.4 → 2.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/client/client.js +36 -6
- package/client/mobile/layout-mode.mjs +46 -0
- package/client/mobile/mobile-apply.tsx +17 -6
- package/package.json +1 -1
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/package.json
CHANGED