dsh-mobile 0.6.2 → 0.6.4
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 +36 -0
- package/CONTRIBUTORS.md +19 -4
- package/README.en.md +16 -8
- package/README.md +16 -8
- package/SECURITY.md +3 -2
- package/THIRD_PARTY_NOTICES.md +29 -1
- package/apps/mobile/README.md +25 -5
- package/apps/mobile/release.json +3 -3
- package/cordis.patch.yml +7 -3
- package/docs/ATTACH_EXISTING_FRPS.en.md +1 -1
- package/docs/ATTACH_EXISTING_FRPS.md +2 -2
- package/docs/CADDY_MANAGED.en.md +15 -1
- package/docs/CADDY_MANAGED.md +15 -1
- package/docs/CLOUDFLARE_TUNNEL.en.md +2 -2
- package/docs/CLOUDFLARE_TUNNEL.md +3 -3
- package/docs/EXTENSION_WORKERS.en.md +49 -0
- package/docs/EXTENSION_WORKERS.md +49 -0
- package/docs/MOBILE_COMPOSER_TOOLBAR.en.md +27 -0
- package/docs/MOBILE_COMPOSER_TOOLBAR.md +27 -0
- package/docs/README.md +2 -0
- package/docs/SELF_HOSTED_FRP.en.md +4 -2
- package/docs/SELF_HOSTED_FRP.md +4 -2
- package/docs/SLOW_CONNECTIONS.en.md +6 -0
- package/docs/SLOW_CONNECTIONS.md +6 -0
- package/docs/TRUSTED_NETWORKS.en.md +1 -1
- package/docs/TRUSTED_NETWORKS.md +1 -1
- package/lib/cli.js +140 -19
- package/lib/cli.js.map +1 -1
- package/lib/client.js +1264 -106
- package/lib/client.js.map +1 -1
- package/lib/extension-worker-runtime.mjs +1646 -0
- package/lib/extension-worker-runtime.mjs.map +1 -0
- package/lib/index.d.mts +209 -12
- package/lib/index.mjs +2152 -811
- package/lib/index.mjs.map +1 -1
- package/lib/mobile-layout.js.map +1 -1
- package/package.json +17 -6
- package/packages/question-fixes/LICENSE +201 -0
- package/packages/question-fixes/package.json +9 -2
- package/node_modules/dsh-mobile-question-fixes/lib/client.js +0 -78
- package/node_modules/dsh-mobile-question-fixes/lib/client.js.map +0 -1
- package/node_modules/dsh-mobile-question-fixes/lib/index.mjs +0 -14
- package/node_modules/dsh-mobile-question-fixes/lib/index.mjs.map +0 -1
- package/node_modules/dsh-mobile-question-fixes/package.json +0 -25
package/lib/client.js
CHANGED
|
@@ -6,6 +6,59 @@ window.__ModuleLoader__.load({
|
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
8
|
//#region src/client-messages.ts
|
|
9
|
+
const storageMessages = {
|
|
10
|
+
en: {
|
|
11
|
+
funnelStateInvalid: "The private Tailscale state folder cannot be accessed safely. Check its permissions or directory links; reinstalling the component is not required.",
|
|
12
|
+
componentPathUnsafe: "The component folder contains an unsafe link or cannot be accessed. Check the directory and permissions, then retry."
|
|
13
|
+
},
|
|
14
|
+
zh: {
|
|
15
|
+
funnelStateInvalid: "Tailscale 私有登录目录无法安全访问。请检查目录权限或链接,不必重新安装组件。",
|
|
16
|
+
componentPathUnsafe: "组件目录包含不安全的链接或无法访问。请检查目录和权限后重试。"
|
|
17
|
+
},
|
|
18
|
+
it: {
|
|
19
|
+
funnelStateInvalid: "La cartella privata Tailscale non è accessibile in sicurezza. Controlla permessi o collegamenti; non occorre reinstallare il componente.",
|
|
20
|
+
componentPathUnsafe: "La cartella del componente contiene un collegamento non sicuro o non è accessibile. Controlla directory e permessi, poi riprova."
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
/** Desktop-only stopped-worker recovery, independent of remote provider state. */
|
|
24
|
+
const EXTENSION_RECOVERY_MESSAGES = {
|
|
25
|
+
en: {
|
|
26
|
+
extensionRecoveryTitle: "An extension needs a restart",
|
|
27
|
+
extensionRecoveryHelp: "Only the stopped extension restarts. Unfinished actions are not retried.",
|
|
28
|
+
extensionRecoveryRestart: "Restart",
|
|
29
|
+
extensionRecoveryRestarting: "Restarting…",
|
|
30
|
+
extensionRecoveryRestartAria: "Restart extension {name}",
|
|
31
|
+
extensionRecoveryConfirm: "Restart extension “{name}”? Unfinished actions will not be retried. Previously completed changes will not be undone.",
|
|
32
|
+
extensionRecoveryRefresh: "Check again",
|
|
33
|
+
extensionRecoveryLoadFailed: "Could not check the extension status. Check again before restarting.",
|
|
34
|
+
extensionRecoveryFailed: "The extension did not restart. Check its configuration, then retry.",
|
|
35
|
+
extensionRecoveryChanged: "The extension files changed. Review the changes and reload the extension before trying again."
|
|
36
|
+
},
|
|
37
|
+
it: {
|
|
38
|
+
extensionRecoveryTitle: "Un’estensione richiede un riavvio",
|
|
39
|
+
extensionRecoveryHelp: "Si riavvia solo l’estensione arrestata. Le azioni incompiute non vengono ripetute.",
|
|
40
|
+
extensionRecoveryRestart: "Riavvia",
|
|
41
|
+
extensionRecoveryRestarting: "Riavvio…",
|
|
42
|
+
extensionRecoveryRestartAria: "Riavvia l’estensione {name}",
|
|
43
|
+
extensionRecoveryConfirm: "Riavviare l’estensione “{name}”? Le azioni incompiute non verranno ripetute. Le modifiche già completate non saranno annullate.",
|
|
44
|
+
extensionRecoveryRefresh: "Controlla di nuovo",
|
|
45
|
+
extensionRecoveryLoadFailed: "Impossibile verificare lo stato. Controlla di nuovo prima di riavviare.",
|
|
46
|
+
extensionRecoveryFailed: "L’estensione non si è riavviata. Controlla la configurazione e riprova.",
|
|
47
|
+
extensionRecoveryChanged: "I file dell’estensione sono cambiati. Verifica le modifiche e ricarica l’estensione prima di riprovare."
|
|
48
|
+
},
|
|
49
|
+
zh: {
|
|
50
|
+
extensionRecoveryTitle: "扩展需要重启",
|
|
51
|
+
extensionRecoveryHelp: "仅重启已停止的扩展,不会重新执行未完成的操作。",
|
|
52
|
+
extensionRecoveryRestart: "重启扩展",
|
|
53
|
+
extensionRecoveryRestarting: "正在重启…",
|
|
54
|
+
extensionRecoveryRestartAria: "重启扩展 {name}",
|
|
55
|
+
extensionRecoveryConfirm: "要重启扩展“{name}”吗?未完成的操作不会重新执行,已经完成的修改也不会撤销。",
|
|
56
|
+
extensionRecoveryRefresh: "重新检测",
|
|
57
|
+
extensionRecoveryLoadFailed: "暂时无法检查扩展状态,请重新检测后再操作。",
|
|
58
|
+
extensionRecoveryFailed: "扩展没有重启成功,请检查配置后重试。",
|
|
59
|
+
extensionRecoveryChanged: "扩展文件已经变化,请核对修改并重新加载扩展后再操作。"
|
|
60
|
+
}
|
|
61
|
+
};
|
|
9
62
|
const MOBILE_CONTROL_MESSAGES = {
|
|
10
63
|
en: {
|
|
11
64
|
mobileAccess: "Mobile access",
|
|
@@ -345,6 +398,16 @@ window.__ModuleLoader__.load({
|
|
|
345
398
|
},
|
|
346
399
|
zh: {}
|
|
347
400
|
};
|
|
401
|
+
for (const locale of [
|
|
402
|
+
"en",
|
|
403
|
+
"it",
|
|
404
|
+
"zh"
|
|
405
|
+
]) Object.assign(MOBILE_CONTROL_MESSAGES[locale], EXTENSION_RECOVERY_MESSAGES[locale]);
|
|
406
|
+
for (const locale of [
|
|
407
|
+
"en",
|
|
408
|
+
"it",
|
|
409
|
+
"zh"
|
|
410
|
+
]) Object.assign(MOBILE_CONTROL_MESSAGES[locale], storageMessages[locale]);
|
|
348
411
|
Object.assign(MOBILE_CONTROL_MESSAGES.zh, {
|
|
349
412
|
mobileAccess: "移动访问",
|
|
350
413
|
collapseMobileAccess: "收起移动访问",
|
|
@@ -1136,7 +1199,9 @@ window.__ModuleLoader__.load({
|
|
|
1136
1199
|
Object.assign(MOBILE_CONTROL_MESSAGES.en, {
|
|
1137
1200
|
selfHostedDescription: "Use your own VPS or HTTPS reverse proxy without a public tunnel service.",
|
|
1138
1201
|
originName: "Own reverse proxy",
|
|
1139
|
-
|
|
1202
|
+
caddyName: "Managed Caddy",
|
|
1203
|
+
caddyModePreviewNote: "HTTPS form preview differs from the current connection.",
|
|
1204
|
+
originDescription: "Use managed Caddy or your existing HTTPS reverse proxy with DSH Mobile’s private, authenticated backend.",
|
|
1140
1205
|
prepareOrigin: "Connect your HTTPS reverse proxy",
|
|
1141
1206
|
originPublicOrigin: "Public HTTPS address",
|
|
1142
1207
|
originListenHost: "Private listen IPv4",
|
|
@@ -1193,7 +1258,9 @@ window.__ModuleLoader__.load({
|
|
|
1193
1258
|
Object.assign(MOBILE_CONTROL_MESSAGES.it, {
|
|
1194
1259
|
selfHostedDescription: "Usa il tuo VPS o reverse proxy HTTPS senza un servizio di tunnel pubblico.",
|
|
1195
1260
|
originName: "Reverse proxy proprio",
|
|
1196
|
-
|
|
1261
|
+
caddyName: "Caddy gestito",
|
|
1262
|
+
caddyModePreviewNote: "L’anteprima del modulo HTTPS differisce dalla connessione attuale.",
|
|
1263
|
+
originDescription: "Usa Caddy gestito o il tuo reverse proxy HTTPS con il backend privato autenticato di DSH Mobile.",
|
|
1197
1264
|
prepareOrigin: "Collega il tuo reverse proxy HTTPS",
|
|
1198
1265
|
originPublicOrigin: "Indirizzo HTTPS pubblico",
|
|
1199
1266
|
originListenHost: "IPv4 privato di ascolto",
|
|
@@ -1250,7 +1317,9 @@ window.__ModuleLoader__.load({
|
|
|
1250
1317
|
Object.assign(MOBILE_CONTROL_MESSAGES.zh, {
|
|
1251
1318
|
selfHostedDescription: "使用自己的 VPS 或 HTTPS 反向代理,不依赖公共隧道服务。",
|
|
1252
1319
|
originName: "自有反向代理",
|
|
1253
|
-
|
|
1320
|
+
caddyName: "托管 Caddy",
|
|
1321
|
+
caddyModePreviewNote: "HTTPS 表单预览与当前连接不同。",
|
|
1322
|
+
originDescription: "使用托管 Caddy 或已有 HTTPS 反向代理,接入 DSH Mobile 带认证的私有后端。",
|
|
1254
1323
|
prepareOrigin: "接入自有 HTTPS 反向代理",
|
|
1255
1324
|
originPublicOrigin: "公网 HTTPS 地址",
|
|
1256
1325
|
originListenHost: "私有监听 IPv4",
|
|
@@ -2409,19 +2478,396 @@ window.__ModuleLoader__.load({
|
|
|
2409
2478
|
function createFrpAttachTemplate(settings, options = {}) {
|
|
2410
2479
|
return createFrpAttachTemplateParts(settings, options).text;
|
|
2411
2480
|
}
|
|
2412
|
-
|
|
2413
|
-
|
|
2481
|
+
//#endregion
|
|
2482
|
+
//#region node_modules/@noble/hashes/esm/utils.js
|
|
2483
|
+
/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
2484
|
+
/** Checks if something is Uint8Array. Be careful: nodejs Buffer will return true. */
|
|
2485
|
+
function isBytes(a) {
|
|
2486
|
+
return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
|
|
2487
|
+
}
|
|
2488
|
+
/** Asserts something is Uint8Array. */
|
|
2489
|
+
function abytes(b, ...lengths) {
|
|
2490
|
+
if (!isBytes(b)) throw new Error("Uint8Array expected");
|
|
2491
|
+
if (lengths.length > 0 && !lengths.includes(b.length)) throw new Error("Uint8Array expected of length " + lengths + ", got length=" + b.length);
|
|
2492
|
+
}
|
|
2493
|
+
/** Asserts a hash instance has not been destroyed / finished */
|
|
2494
|
+
function aexists(instance, checkFinished = true) {
|
|
2495
|
+
if (instance.destroyed) throw new Error("Hash instance has been destroyed");
|
|
2496
|
+
if (checkFinished && instance.finished) throw new Error("Hash#digest() has already been called");
|
|
2497
|
+
}
|
|
2498
|
+
/** Asserts output is properly-sized byte array */
|
|
2499
|
+
function aoutput(out, instance) {
|
|
2500
|
+
abytes(out);
|
|
2501
|
+
const min = instance.outputLen;
|
|
2502
|
+
if (out.length < min) throw new Error("digestInto() expects output buffer of length at least " + min);
|
|
2503
|
+
}
|
|
2504
|
+
/** Zeroize a byte array. Warning: JS provides no guarantees. */
|
|
2505
|
+
function clean(...arrays) {
|
|
2506
|
+
for (let i = 0; i < arrays.length; i++) arrays[i].fill(0);
|
|
2507
|
+
}
|
|
2508
|
+
/** Create DataView of an array for easy byte-level manipulation. */
|
|
2509
|
+
function createView(arr) {
|
|
2510
|
+
return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
2511
|
+
}
|
|
2512
|
+
/** The rotate right (circular right shift) operation for uint32 */
|
|
2513
|
+
function rotr(word, shift) {
|
|
2514
|
+
return word << 32 - shift | word >>> shift;
|
|
2515
|
+
}
|
|
2516
|
+
const hasHexBuiltin = /* @__PURE__ */ (() => typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex === "function")();
|
|
2517
|
+
const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
|
|
2518
|
+
/**
|
|
2519
|
+
* Convert byte array to hex string. Uses built-in function, when available.
|
|
2520
|
+
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
|
|
2521
|
+
*/
|
|
2522
|
+
function bytesToHex(bytes) {
|
|
2523
|
+
abytes(bytes);
|
|
2524
|
+
if (hasHexBuiltin) return bytes.toHex();
|
|
2525
|
+
let hex = "";
|
|
2526
|
+
for (let i = 0; i < bytes.length; i++) hex += hexes[bytes[i]];
|
|
2527
|
+
return hex;
|
|
2528
|
+
}
|
|
2529
|
+
/**
|
|
2530
|
+
* Converts string to bytes using UTF8 encoding.
|
|
2531
|
+
* @example utf8ToBytes('abc') // Uint8Array.from([97, 98, 99])
|
|
2532
|
+
*/
|
|
2533
|
+
function utf8ToBytes(str) {
|
|
2534
|
+
if (typeof str !== "string") throw new Error("string expected");
|
|
2535
|
+
return new Uint8Array(new TextEncoder().encode(str));
|
|
2536
|
+
}
|
|
2537
|
+
/**
|
|
2538
|
+
* Normalizes (non-hex) string or Uint8Array to Uint8Array.
|
|
2539
|
+
* Warning: when Uint8Array is passed, it would NOT get copied.
|
|
2540
|
+
* Keep in mind for future mutable operations.
|
|
2541
|
+
*/
|
|
2542
|
+
function toBytes(data) {
|
|
2543
|
+
if (typeof data === "string") data = utf8ToBytes(data);
|
|
2544
|
+
abytes(data);
|
|
2545
|
+
return data;
|
|
2546
|
+
}
|
|
2547
|
+
/** For runtime check if class implements interface */
|
|
2548
|
+
var Hash = class {};
|
|
2549
|
+
/** Wraps hash function, creating an interface on top of it */
|
|
2550
|
+
function createHasher(hashCons) {
|
|
2551
|
+
const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
|
|
2552
|
+
const tmp = hashCons();
|
|
2553
|
+
hashC.outputLen = tmp.outputLen;
|
|
2554
|
+
hashC.blockLen = tmp.blockLen;
|
|
2555
|
+
hashC.create = () => hashCons();
|
|
2556
|
+
return hashC;
|
|
2557
|
+
}
|
|
2558
|
+
//#endregion
|
|
2559
|
+
//#region node_modules/@noble/hashes/esm/_md.js
|
|
2560
|
+
/**
|
|
2561
|
+
* Internal Merkle-Damgard hash utils.
|
|
2562
|
+
* @module
|
|
2563
|
+
*/
|
|
2564
|
+
/** Polyfill for Safari 14. https://caniuse.com/mdn-javascript_builtins_dataview_setbiguint64 */
|
|
2565
|
+
function setBigUint64(view, byteOffset, value, isLE) {
|
|
2566
|
+
if (typeof view.setBigUint64 === "function") return view.setBigUint64(byteOffset, value, isLE);
|
|
2567
|
+
const _32n = BigInt(32);
|
|
2568
|
+
const _u32_max = BigInt(4294967295);
|
|
2569
|
+
const wh = Number(value >> _32n & _u32_max);
|
|
2570
|
+
const wl = Number(value & _u32_max);
|
|
2571
|
+
const h = isLE ? 4 : 0;
|
|
2572
|
+
const l = isLE ? 0 : 4;
|
|
2573
|
+
view.setUint32(byteOffset + h, wh, isLE);
|
|
2574
|
+
view.setUint32(byteOffset + l, wl, isLE);
|
|
2575
|
+
}
|
|
2576
|
+
/** Choice: a ? b : c */
|
|
2577
|
+
function Chi(a, b, c) {
|
|
2578
|
+
return a & b ^ ~a & c;
|
|
2579
|
+
}
|
|
2580
|
+
/** Majority function, true if any two inputs is true. */
|
|
2581
|
+
function Maj(a, b, c) {
|
|
2582
|
+
return a & b ^ a & c ^ b & c;
|
|
2583
|
+
}
|
|
2584
|
+
/**
|
|
2585
|
+
* Merkle-Damgard hash construction base class.
|
|
2586
|
+
* Could be used to create MD5, RIPEMD, SHA1, SHA2.
|
|
2587
|
+
*/
|
|
2588
|
+
var HashMD = class extends Hash {
|
|
2589
|
+
constructor(blockLen, outputLen, padOffset, isLE) {
|
|
2590
|
+
super();
|
|
2591
|
+
this.finished = false;
|
|
2592
|
+
this.length = 0;
|
|
2593
|
+
this.pos = 0;
|
|
2594
|
+
this.destroyed = false;
|
|
2595
|
+
this.blockLen = blockLen;
|
|
2596
|
+
this.outputLen = outputLen;
|
|
2597
|
+
this.padOffset = padOffset;
|
|
2598
|
+
this.isLE = isLE;
|
|
2599
|
+
this.buffer = new Uint8Array(blockLen);
|
|
2600
|
+
this.view = createView(this.buffer);
|
|
2601
|
+
}
|
|
2602
|
+
update(data) {
|
|
2603
|
+
aexists(this);
|
|
2604
|
+
data = toBytes(data);
|
|
2605
|
+
abytes(data);
|
|
2606
|
+
const { view, buffer, blockLen } = this;
|
|
2607
|
+
const len = data.length;
|
|
2608
|
+
for (let pos = 0; pos < len;) {
|
|
2609
|
+
const take = Math.min(blockLen - this.pos, len - pos);
|
|
2610
|
+
if (take === blockLen) {
|
|
2611
|
+
const dataView = createView(data);
|
|
2612
|
+
for (; blockLen <= len - pos; pos += blockLen) this.process(dataView, pos);
|
|
2613
|
+
continue;
|
|
2614
|
+
}
|
|
2615
|
+
buffer.set(data.subarray(pos, pos + take), this.pos);
|
|
2616
|
+
this.pos += take;
|
|
2617
|
+
pos += take;
|
|
2618
|
+
if (this.pos === blockLen) {
|
|
2619
|
+
this.process(view, 0);
|
|
2620
|
+
this.pos = 0;
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
this.length += data.length;
|
|
2624
|
+
this.roundClean();
|
|
2625
|
+
return this;
|
|
2626
|
+
}
|
|
2627
|
+
digestInto(out) {
|
|
2628
|
+
aexists(this);
|
|
2629
|
+
aoutput(out, this);
|
|
2630
|
+
this.finished = true;
|
|
2631
|
+
const { buffer, view, blockLen, isLE } = this;
|
|
2632
|
+
let { pos } = this;
|
|
2633
|
+
buffer[pos++] = 128;
|
|
2634
|
+
clean(this.buffer.subarray(pos));
|
|
2635
|
+
if (this.padOffset > blockLen - pos) {
|
|
2636
|
+
this.process(view, 0);
|
|
2637
|
+
pos = 0;
|
|
2638
|
+
}
|
|
2639
|
+
for (let i = pos; i < blockLen; i++) buffer[i] = 0;
|
|
2640
|
+
setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE);
|
|
2641
|
+
this.process(view, 0);
|
|
2642
|
+
const oview = createView(out);
|
|
2643
|
+
const len = this.outputLen;
|
|
2644
|
+
if (len % 4) throw new Error("_sha2: outputLen should be aligned to 32bit");
|
|
2645
|
+
const outLen = len / 4;
|
|
2646
|
+
const state = this.get();
|
|
2647
|
+
if (outLen > state.length) throw new Error("_sha2: outputLen bigger than state");
|
|
2648
|
+
for (let i = 0; i < outLen; i++) oview.setUint32(4 * i, state[i], isLE);
|
|
2649
|
+
}
|
|
2650
|
+
digest() {
|
|
2651
|
+
const { buffer, outputLen } = this;
|
|
2652
|
+
this.digestInto(buffer);
|
|
2653
|
+
const res = buffer.slice(0, outputLen);
|
|
2654
|
+
this.destroy();
|
|
2655
|
+
return res;
|
|
2656
|
+
}
|
|
2657
|
+
_cloneInto(to) {
|
|
2658
|
+
to || (to = new this.constructor());
|
|
2659
|
+
to.set(...this.get());
|
|
2660
|
+
const { blockLen, buffer, length, finished, destroyed, pos } = this;
|
|
2661
|
+
to.destroyed = destroyed;
|
|
2662
|
+
to.finished = finished;
|
|
2663
|
+
to.length = length;
|
|
2664
|
+
to.pos = pos;
|
|
2665
|
+
if (length % blockLen) to.buffer.set(buffer);
|
|
2666
|
+
return to;
|
|
2667
|
+
}
|
|
2668
|
+
clone() {
|
|
2669
|
+
return this._cloneInto();
|
|
2670
|
+
}
|
|
2671
|
+
};
|
|
2672
|
+
/**
|
|
2673
|
+
* Initial SHA-2 state: fractional parts of square roots of first 16 primes 2..53.
|
|
2674
|
+
* Check out `test/misc/sha2-gen-iv.js` for recomputation guide.
|
|
2675
|
+
*/
|
|
2676
|
+
/** Initial SHA256 state. Bits 0..32 of frac part of sqrt of primes 2..19 */
|
|
2677
|
+
const SHA256_IV = /* @__PURE__ */ Uint32Array.from([
|
|
2678
|
+
1779033703,
|
|
2679
|
+
3144134277,
|
|
2680
|
+
1013904242,
|
|
2681
|
+
2773480762,
|
|
2682
|
+
1359893119,
|
|
2683
|
+
2600822924,
|
|
2684
|
+
528734635,
|
|
2685
|
+
1541459225
|
|
2686
|
+
]);
|
|
2687
|
+
//#endregion
|
|
2688
|
+
//#region node_modules/@noble/hashes/esm/sha2.js
|
|
2689
|
+
/**
|
|
2690
|
+
* SHA2 hash function. A.k.a. sha256, sha384, sha512, sha512_224, sha512_256.
|
|
2691
|
+
* SHA256 is the fastest hash implementable in JS, even faster than Blake3.
|
|
2692
|
+
* Check out [RFC 4634](https://datatracker.ietf.org/doc/html/rfc4634) and
|
|
2693
|
+
* [FIPS 180-4](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf).
|
|
2694
|
+
* @module
|
|
2695
|
+
*/
|
|
2696
|
+
/**
|
|
2697
|
+
* Round constants:
|
|
2698
|
+
* First 32 bits of fractional parts of the cube roots of the first 64 primes 2..311)
|
|
2699
|
+
*/
|
|
2700
|
+
const SHA256_K = /* @__PURE__ */ Uint32Array.from([
|
|
2701
|
+
1116352408,
|
|
2702
|
+
1899447441,
|
|
2703
|
+
3049323471,
|
|
2704
|
+
3921009573,
|
|
2705
|
+
961987163,
|
|
2706
|
+
1508970993,
|
|
2707
|
+
2453635748,
|
|
2708
|
+
2870763221,
|
|
2709
|
+
3624381080,
|
|
2710
|
+
310598401,
|
|
2711
|
+
607225278,
|
|
2712
|
+
1426881987,
|
|
2713
|
+
1925078388,
|
|
2714
|
+
2162078206,
|
|
2715
|
+
2614888103,
|
|
2716
|
+
3248222580,
|
|
2717
|
+
3835390401,
|
|
2718
|
+
4022224774,
|
|
2719
|
+
264347078,
|
|
2720
|
+
604807628,
|
|
2721
|
+
770255983,
|
|
2722
|
+
1249150122,
|
|
2723
|
+
1555081692,
|
|
2724
|
+
1996064986,
|
|
2725
|
+
2554220882,
|
|
2726
|
+
2821834349,
|
|
2727
|
+
2952996808,
|
|
2728
|
+
3210313671,
|
|
2729
|
+
3336571891,
|
|
2730
|
+
3584528711,
|
|
2731
|
+
113926993,
|
|
2732
|
+
338241895,
|
|
2733
|
+
666307205,
|
|
2734
|
+
773529912,
|
|
2735
|
+
1294757372,
|
|
2736
|
+
1396182291,
|
|
2737
|
+
1695183700,
|
|
2738
|
+
1986661051,
|
|
2739
|
+
2177026350,
|
|
2740
|
+
2456956037,
|
|
2741
|
+
2730485921,
|
|
2742
|
+
2820302411,
|
|
2743
|
+
3259730800,
|
|
2744
|
+
3345764771,
|
|
2745
|
+
3516065817,
|
|
2746
|
+
3600352804,
|
|
2747
|
+
4094571909,
|
|
2748
|
+
275423344,
|
|
2749
|
+
430227734,
|
|
2750
|
+
506948616,
|
|
2751
|
+
659060556,
|
|
2752
|
+
883997877,
|
|
2753
|
+
958139571,
|
|
2754
|
+
1322822218,
|
|
2755
|
+
1537002063,
|
|
2756
|
+
1747873779,
|
|
2757
|
+
1955562222,
|
|
2758
|
+
2024104815,
|
|
2759
|
+
2227730452,
|
|
2760
|
+
2361852424,
|
|
2761
|
+
2428436474,
|
|
2762
|
+
2756734187,
|
|
2763
|
+
3204031479,
|
|
2764
|
+
3329325298
|
|
2765
|
+
]);
|
|
2766
|
+
/** Reusable temporary buffer. "W" comes straight from spec. */
|
|
2767
|
+
const SHA256_W = /* @__PURE__ */ new Uint32Array(64);
|
|
2768
|
+
var SHA256 = class extends HashMD {
|
|
2769
|
+
constructor(outputLen = 32) {
|
|
2770
|
+
super(64, outputLen, 8, false);
|
|
2771
|
+
this.A = SHA256_IV[0] | 0;
|
|
2772
|
+
this.B = SHA256_IV[1] | 0;
|
|
2773
|
+
this.C = SHA256_IV[2] | 0;
|
|
2774
|
+
this.D = SHA256_IV[3] | 0;
|
|
2775
|
+
this.E = SHA256_IV[4] | 0;
|
|
2776
|
+
this.F = SHA256_IV[5] | 0;
|
|
2777
|
+
this.G = SHA256_IV[6] | 0;
|
|
2778
|
+
this.H = SHA256_IV[7] | 0;
|
|
2779
|
+
}
|
|
2780
|
+
get() {
|
|
2781
|
+
const { A, B, C, D, E, F, G, H } = this;
|
|
2782
|
+
return [
|
|
2783
|
+
A,
|
|
2784
|
+
B,
|
|
2785
|
+
C,
|
|
2786
|
+
D,
|
|
2787
|
+
E,
|
|
2788
|
+
F,
|
|
2789
|
+
G,
|
|
2790
|
+
H
|
|
2791
|
+
];
|
|
2792
|
+
}
|
|
2793
|
+
set(A, B, C, D, E, F, G, H) {
|
|
2794
|
+
this.A = A | 0;
|
|
2795
|
+
this.B = B | 0;
|
|
2796
|
+
this.C = C | 0;
|
|
2797
|
+
this.D = D | 0;
|
|
2798
|
+
this.E = E | 0;
|
|
2799
|
+
this.F = F | 0;
|
|
2800
|
+
this.G = G | 0;
|
|
2801
|
+
this.H = H | 0;
|
|
2802
|
+
}
|
|
2803
|
+
process(view, offset) {
|
|
2804
|
+
for (let i = 0; i < 16; i++, offset += 4) SHA256_W[i] = view.getUint32(offset, false);
|
|
2805
|
+
for (let i = 16; i < 64; i++) {
|
|
2806
|
+
const W15 = SHA256_W[i - 15];
|
|
2807
|
+
const W2 = SHA256_W[i - 2];
|
|
2808
|
+
const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ W15 >>> 3;
|
|
2809
|
+
const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ W2 >>> 10;
|
|
2810
|
+
SHA256_W[i] = s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16] | 0;
|
|
2811
|
+
}
|
|
2812
|
+
let { A, B, C, D, E, F, G, H } = this;
|
|
2813
|
+
for (let i = 0; i < 64; i++) {
|
|
2814
|
+
const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);
|
|
2815
|
+
const T1 = H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i] | 0;
|
|
2816
|
+
const T2 = (rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22)) + Maj(A, B, C) | 0;
|
|
2817
|
+
H = G;
|
|
2818
|
+
G = F;
|
|
2819
|
+
F = E;
|
|
2820
|
+
E = D + T1 | 0;
|
|
2821
|
+
D = C;
|
|
2822
|
+
C = B;
|
|
2823
|
+
B = A;
|
|
2824
|
+
A = T1 + T2 | 0;
|
|
2825
|
+
}
|
|
2826
|
+
A = A + this.A | 0;
|
|
2827
|
+
B = B + this.B | 0;
|
|
2828
|
+
C = C + this.C | 0;
|
|
2829
|
+
D = D + this.D | 0;
|
|
2830
|
+
E = E + this.E | 0;
|
|
2831
|
+
F = F + this.F | 0;
|
|
2832
|
+
G = G + this.G | 0;
|
|
2833
|
+
H = H + this.H | 0;
|
|
2834
|
+
this.set(A, B, C, D, E, F, G, H);
|
|
2835
|
+
}
|
|
2836
|
+
roundClean() {
|
|
2837
|
+
clean(SHA256_W);
|
|
2838
|
+
}
|
|
2839
|
+
destroy() {
|
|
2840
|
+
this.set(0, 0, 0, 0, 0, 0, 0, 0);
|
|
2841
|
+
clean(this.buffer);
|
|
2842
|
+
}
|
|
2843
|
+
};
|
|
2844
|
+
//#endregion
|
|
2845
|
+
//#region node_modules/@noble/hashes/esm/sha256.js
|
|
2846
|
+
/**
|
|
2847
|
+
* SHA2-256 a.k.a. sha256. In JS, it is the fastest hash, even faster than Blake3.
|
|
2848
|
+
*
|
|
2849
|
+
* To break sha256 using birthday attack, attackers need to try 2^128 hashes.
|
|
2850
|
+
* BTC network is doing 2^70 hashes/sec (2^95 hashes/year) as per 2025.
|
|
2851
|
+
*
|
|
2852
|
+
* Check out [FIPS 180-4](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf).
|
|
2853
|
+
* @module
|
|
2854
|
+
* @deprecated
|
|
2855
|
+
*/
|
|
2856
|
+
/** @deprecated Use import from `noble/hashes/sha2` module */
|
|
2857
|
+
const sha256 = /* @__PURE__ */ createHasher(() => new SHA256());
|
|
2858
|
+
Object.freeze({
|
|
2414
2859
|
title: 80,
|
|
2415
2860
|
body: 200,
|
|
2416
2861
|
tag: 64
|
|
2417
2862
|
});
|
|
2418
2863
|
const MAX_SEEN_QUESTION_KEYS = 20;
|
|
2419
|
-
|
|
2420
|
-
|
|
2864
|
+
/** A bounded native identifier derived from the complete, case-sensitive wire ID. */
|
|
2865
|
+
function notificationIdentity(value) {
|
|
2866
|
+
return bytesToHex(sha256(JSON.stringify(value)).subarray(0, 16));
|
|
2421
2867
|
}
|
|
2422
2868
|
/** Stable per-turn tag so concurrent sessions keep separate notifications. */
|
|
2423
2869
|
function taskCompletionTag(sessionId, turn) {
|
|
2424
|
-
return `dsh-task-done-${
|
|
2870
|
+
return `dsh-task-done-${notificationIdentity(sessionId)}-${String(turn)}`;
|
|
2425
2871
|
}
|
|
2426
2872
|
/** Decide whether the current snapshot deserves a system notification. */
|
|
2427
2873
|
var TaskNotifyTracker = class {
|
|
@@ -2512,7 +2958,7 @@ window.__ModuleLoader__.load({
|
|
|
2512
2958
|
}
|
|
2513
2959
|
event(kind, sessionLabel, questionKey) {
|
|
2514
2960
|
const text = this.format(kind, sessionLabel);
|
|
2515
|
-
const tag = kind === "question" && questionKey !== void 0 ? `dsh-task-question-${
|
|
2961
|
+
const tag = kind === "question" && questionKey !== void 0 ? `dsh-task-question-${notificationIdentity(questionKey)}` : "dsh-task-done";
|
|
2516
2962
|
return Object.freeze({
|
|
2517
2963
|
kind,
|
|
2518
2964
|
title: text.title,
|
|
@@ -2597,7 +3043,7 @@ window.__ModuleLoader__.load({
|
|
|
2597
3043
|
if (typeof record.sessionId !== "string" || record.sessionId.trim() === "") return void 0;
|
|
2598
3044
|
const turn = typeof record.turn === "number" && Number.isSafeInteger(record.turn) && record.turn >= 0 ? record.turn : 0;
|
|
2599
3045
|
return Object.freeze({
|
|
2600
|
-
sessionId: record.sessionId
|
|
3046
|
+
sessionId: record.sessionId,
|
|
2601
3047
|
turn
|
|
2602
3048
|
});
|
|
2603
3049
|
}
|
|
@@ -2707,6 +3153,42 @@ window.__ModuleLoader__.load({
|
|
|
2707
3153
|
if (anchorTimer !== 0) window.clearTimeout(anchorTimer);
|
|
2708
3154
|
};
|
|
2709
3155
|
}
|
|
3156
|
+
//#endregion
|
|
3157
|
+
//#region src/mobile-composer-toolbar.ts
|
|
3158
|
+
/**
|
|
3159
|
+
* Compact the stock InputBar without changing its React ownership, handlers,
|
|
3160
|
+
* activity tree or the two groups observed by DSH's model-width controller.
|
|
3161
|
+
* Extra controls wrap inside their own group; menus remain overflow-visible.
|
|
3162
|
+
*/
|
|
3163
|
+
const MOBILE_COMPOSER_TOOLBAR_STYLES = `
|
|
3164
|
+
/* These markers are attached only to the stock composer on the mobile page.
|
|
3165
|
+
Body portals and inline dialogs are not part of the compact toolbar. */
|
|
3166
|
+
[data-dsh-mobile-composer-row] { min-width:0 !important; max-width:100% !important; }
|
|
3167
|
+
[data-dsh-mobile-composer-tools]:not([hidden]),[data-dsh-mobile-composer-controls]:not([hidden]) { flex-wrap:wrap !important; min-width:0 !important; max-width:100% !important; }
|
|
3168
|
+
[data-dsh-mobile-composer-tools] > :not(:where(dialog,[popover],[role="dialog"],[role="menu"],[role="listbox"],[data-trigger-menu])),
|
|
3169
|
+
[data-dsh-mobile-composer-tools] > [data-slot] > :not(:where(dialog,[popover],[role="dialog"],[role="menu"],[role="listbox"],[data-trigger-menu])),
|
|
3170
|
+
[data-dsh-mobile-composer-controls] > :not(:where(dialog,[popover],[role="dialog"],[role="menu"],[role="listbox"],[data-trigger-menu])),
|
|
3171
|
+
[data-dsh-mobile-composer-controls] > [data-slot] > :not(:where(dialog,[popover],[role="dialog"],[role="menu"],[role="listbox"],[data-trigger-menu])) { min-width:0 !important; max-width:100% !important; }
|
|
3172
|
+
[data-dsh-mobile-composer-tools] button:not(:where(dialog *,[popover] *,[role="dialog"] *,[role="menu"] *,[role="listbox"] *,[data-trigger-menu] *)),
|
|
3173
|
+
[data-dsh-mobile-composer-controls] button:not(:where(dialog *,[popover] *,[role="dialog"] *,[role="menu"] *,[role="listbox"] *,[data-trigger-menu] *)) { white-space:normal !important; overflow-wrap:anywhere !important; }
|
|
3174
|
+
@media (max-width:720px) {
|
|
3175
|
+
[data-dsh-mobile-composer-row] button:not([role="switch"]):not(:where(dialog *,[popover] *,[role="dialog"] *,[role="menu"] *,[role="listbox"] *,[data-trigger-menu] *)) { min-width:44px !important; min-height:44px !important; touch-action:manipulation; }
|
|
3176
|
+
/* Public slot icon wrappers must reserve the same space as their enlarged
|
|
3177
|
+
child targets. Keep wrapper and tooltip ownership intact; pure switches,
|
|
3178
|
+
dialogs and wrappers without direct non-switch buttons are not selected. */
|
|
3179
|
+
:is([data-dsh-mobile-composer-tools],[data-dsh-mobile-composer-controls]) > :is([data-slot="conversation.input.left"],[data-slot="conversation.input.right"]) > :where(span,div):not(:where(dialog,[popover],[role="dialog"],[role="menu"],[role="listbox"],[data-trigger-menu])):has(> button:not([role="switch"])) { min-width:44px !important; min-height:44px !important; flex-shrink:0 !important; }
|
|
3180
|
+
[data-dsh-mobile-composer-trailing] > button[class*="_primary"] { min-width:44px !important; flex-shrink:0 !important; }
|
|
3181
|
+
[data-dsh-mobile-composer-row] { display:flex !important; flex-wrap:wrap !important; align-items:center !important; gap:4px 8px !important; min-width:0 !important; max-width:100% !important; }
|
|
3182
|
+
[data-dsh-mobile-composer-tools]:not([hidden]) { display:flex !important; flex:0 1 auto !important; flex-wrap:wrap !important; width:auto !important; min-width:0 !important; max-width:100% !important; gap:6px !important; }
|
|
3183
|
+
/* The activity seat can be wider than a single icon. Intrinsic demand, not
|
|
3184
|
+
a fixed 100px reserve, decides whether this intact group needs to wrap. */
|
|
3185
|
+
[data-dsh-mobile-composer-trailing] { display:flex !important; flex:1 0 auto !important; flex-wrap:nowrap !important; width:auto !important; min-width:0 !important; max-width:100% !important; gap:6px !important; margin-left:0 !important; justify-content:flex-end !important; }
|
|
3186
|
+
[data-dsh-mobile-composer-controls]:not([hidden]) { display:flex !important; flex:1 1 0 !important; flex-wrap:wrap !important; justify-content:flex-end !important; min-width:44px !important; max-width:100% !important; gap:6px !important; }
|
|
3187
|
+
[data-dsh-mobile-composer-model] { flex:0 1 auto !important; width:auto !important; min-width:44px !important; max-width:100% !important; }
|
|
3188
|
+
[data-dsh-mobile-composer-model-trigger] { box-sizing:border-box !important; width:100% !important; max-width:100% !important; min-width:0 !important; padding-left:6px !important; padding-right:4px !important; }
|
|
3189
|
+
[data-dsh-mobile-composer-model-label] { flex:1 1 auto !important; max-width:none !important; min-width:0 !important; overflow:hidden !important; text-overflow:ellipsis !important; white-space:nowrap !important; }
|
|
3190
|
+
}
|
|
3191
|
+
`;
|
|
2710
3192
|
//#endregion
|
|
2711
3193
|
//#region src/native-mobile.ts
|
|
2712
3194
|
/**
|
|
@@ -2747,13 +3229,15 @@ window.__ModuleLoader__.load({
|
|
|
2747
3229
|
.dsh-mobile-settings_fontControls { display:flex; align-items:center; gap:8px; flex-shrink:0; }
|
|
2748
3230
|
.dsh-mobile-settings_fontControls button { min-width:48px; padding:0 12px; }
|
|
2749
3231
|
.dsh-mobile-settings_fontControls output { min-width:42px; text-align:center; font-variant-numeric:tabular-nums; }
|
|
3232
|
+
.dsh-mobile-settings_fontDetails { min-width:0; padding:0 0 16px; border-bottom:0.5px solid var(--dsw-alias-border-l2); }
|
|
3233
|
+
.dsh-mobile-settings_fontDetails > summary { display:list-item; min-height:48px; padding:14px 0; box-sizing:border-box; color:var(--dsw-alias-label-primary); font-size:14px; cursor:pointer; }
|
|
3234
|
+
.dsh-mobile-settings_fontDetails .dsh-mobile-settings_rowText { padding-right:8px; }
|
|
3235
|
+
.dsh-mobile-settings_fontFamily { display:flex; flex-wrap:wrap; align-items:center; gap:8px; min-width:0; padding:12px 0; }
|
|
3236
|
+
.dsh-mobile-settings_fontFamily > span { flex:1 1 100px; }
|
|
3237
|
+
.dsh-mobile-settings_fontFamily input { box-sizing:border-box; flex:1 1 180px; width:100%; min-width:0; min-height:48px; padding:8px 12px; border:0.5px solid var(--dsw-alias-border-l2); border-radius:10px; background:var(--dsw-alias-bg-module-platform); color:var(--dsw-alias-label-primary); font:inherit; }
|
|
3238
|
+
.dsh-mobile-settings_fontFamily input:focus-visible,.dsh-mobile-settings_fontDetails > summary:focus-visible { outline:2px solid var(--dsw-alias-label-primary-bluish,#2563eb); outline-offset:2px; }
|
|
2750
3239
|
/* The mobile row replaces the Host-backed font control, not the theme selector. */
|
|
2751
3240
|
html.dsh-native-mobile-active [data-slot="settings.general.item"]:has([data-mobile-font-setting]) > :not([data-mobile-font-setting])[class*="_row"]:has([class*="_control"] > [class*="_stepper"] > [class*="_arrows"]) { display:none !important; }
|
|
2752
|
-
/* Landscape and wide App windows also constrain standard extension controls. */
|
|
2753
|
-
[data-dsh-mobile-composer-row] { min-width:0 !important; max-width:100% !important; }
|
|
2754
|
-
[data-dsh-mobile-composer-tools]:not([hidden]),[data-dsh-mobile-composer-controls]:not([hidden]) { flex-wrap:wrap !important; min-width:0 !important; max-width:100% !important; }
|
|
2755
|
-
[data-dsh-mobile-composer-tools] > *,[data-dsh-mobile-composer-tools] > [data-slot] > *,[data-dsh-mobile-composer-controls] > *,[data-dsh-mobile-composer-controls] > [data-slot] > * { min-width:0 !important; max-width:100% !important; }
|
|
2756
|
-
[data-dsh-mobile-composer-tools] button,[data-dsh-mobile-composer-controls] button { white-space:normal !important; overflow-wrap:anywhere !important; }
|
|
2757
3241
|
@media ${NATIVE_MOBILE_OVERLAY_QUERY} {
|
|
2758
3242
|
html.dsh-native-mobile-active,html.dsh-native-mobile-active body { width:100%; height:100%; overflow:hidden; }
|
|
2759
3243
|
html.dsh-native-mobile-active { --dsh-mobile-motion-duration:200ms; --dsh-mobile-motion-ease:cubic-bezier(.22,1,.36,1); }
|
|
@@ -2770,6 +3254,9 @@ window.__ModuleLoader__.load({
|
|
|
2770
3254
|
the ellipsis action visible and give it a reliable hit target. */
|
|
2771
3255
|
html.dsh-native-mobile-active [data-dsh-mobile-sidebar] [class*="_rowActions"] { display:inline-flex !important; align-items:center !important; gap:8px !important; }
|
|
2772
3256
|
html.dsh-native-mobile-active [data-dsh-mobile-sidebar] [class*="_sessionRow"] [class*="_time"] { display:none !important; }
|
|
3257
|
+
/* The always-visible pin action already conveys pinned state. Desktop only
|
|
3258
|
+
hides this passive marker on hover; touch rows must hide it throughout. */
|
|
3259
|
+
html.dsh-native-mobile-active [data-dsh-mobile-sidebar] [class*="_sessionRow"] [class*="_pinIndicator"] { display:none !important; }
|
|
2773
3260
|
html.dsh-native-mobile-active [data-dsh-mobile-sidebar] [class*="_rowActions"] button { box-sizing:border-box !important; width:32px !important; min-width:32px !important; height:32px !important; min-height:32px !important; }
|
|
2774
3261
|
[data-dsh-mobile-frame] { grid-template-columns:0 minmax(0,1fr) 0 !important; width:100% !important; height:100dvh !important; overflow:hidden !important; }
|
|
2775
3262
|
[data-dsh-mobile-center] { grid-column:2 !important; width:100vw !important; min-width:0 !important; }
|
|
@@ -2827,6 +3314,11 @@ window.__ModuleLoader__.load({
|
|
|
2827
3314
|
[data-dsh-mobile-settings-options] [data-slot="settings.general.item"] [class*="_selector"] { box-sizing:border-box !important; align-self:flex-start !important; justify-content:space-between !important; min-width:0 !important; min-height:48px !important; max-width:100% !important; }
|
|
2828
3315
|
[data-dsh-mobile-settings-options] :is(input,select,textarea,button) { max-width:100%; }
|
|
2829
3316
|
[data-dsh-mobile-settings-options] :is(input,select,textarea) { box-sizing:border-box; width:100%; min-width:0; }
|
|
3317
|
+
/* Local steppers and native page-scale controls keep their own touch targets. */
|
|
3318
|
+
[data-dsh-mobile-settings-options] [data-slot="settings.general.item"] .dsh-mobile-settings_fontControls button { flex:0 0 auto !important; width:auto !important; min-width:48px !important; align-self:center !important; justify-content:center !important; }
|
|
3319
|
+
[data-dsh-mobile-settings-options] [data-slot="settings.general.item"] .dsh-mobile-settings_fontControls select { flex:0 0 auto !important; width:auto !important; min-width:76px !important; align-self:center !important; }
|
|
3320
|
+
[data-dsh-mobile-settings-options] [data-mobile-font-setting] .dsh-mobile-settings_row { flex-wrap:wrap !important; }
|
|
3321
|
+
[data-dsh-mobile-settings-options] [data-mobile-font-setting] .dsh-mobile-settings_rowText { flex:1 1 140px !important; width:auto !important; padding-right:8px !important; }
|
|
2830
3322
|
[data-dsh-mobile-settings-options] [class*="_head"] { min-width:0; flex-wrap:wrap; }
|
|
2831
3323
|
/* Provider names may shrink, but their edit/delete actions remain horizontal
|
|
2832
3324
|
and retain a full touch target on narrow screens. */
|
|
@@ -2931,23 +3423,6 @@ window.__ModuleLoader__.load({
|
|
|
2931
3423
|
/* Collapse the scroll viewport only; the editor, draft and attachments stay
|
|
2932
3424
|
owned by DSH. Toolbar focus keeps the card expanded during menu taps. */
|
|
2933
3425
|
[data-dsh-mobile-center] [data-composer-card]:not(:focus-within) > [data-input-scroll] { max-height:72px !important; overflow-y:auto !important; }
|
|
2934
|
-
/* Stock Send/Queue/Steer and Stop keep their handlers and disabled state. */
|
|
2935
|
-
[data-dsh-mobile-composer-row] button { min-width:44px !important; min-height:44px !important; touch-action:manipulation; }
|
|
2936
|
-
[data-dsh-mobile-composer-row] button[class*="_primary"] { min-width:44px !important; flex-shrink:0 !important; }
|
|
2937
|
-
/* Keep ordinary controls on one row. The trailing group reserves room for
|
|
2938
|
-
its model and primary buttons; extra tools wrap only when space is short. */
|
|
2939
|
-
[data-dsh-mobile-composer-row] { display:flex !important; flex-wrap:wrap !important; align-items:center !important; gap:4px 8px !important; min-width:0 !important; max-width:100% !important; }
|
|
2940
|
-
[data-dsh-mobile-composer-tools]:not([hidden]) { display:flex !important; flex:0 1 auto !important; flex-wrap:wrap !important; width:auto !important; min-width:0 !important; max-width:100% !important; gap:6px !important; }
|
|
2941
|
-
[data-dsh-mobile-composer-tools] > *,[data-dsh-mobile-composer-tools] > [data-slot] > * { min-width:0 !important; max-width:100% !important; }
|
|
2942
|
-
[data-dsh-mobile-composer-tools] button { white-space:normal !important; overflow-wrap:anywhere !important; }
|
|
2943
|
-
[data-dsh-mobile-composer-trailing] { display:flex !important; flex:1 0 100px !important; flex-wrap:nowrap !important; width:auto !important; min-width:min(100%,100px) !important; max-width:100% !important; gap:6px !important; margin-left:0 !important; justify-content:flex-end !important; }
|
|
2944
|
-
[data-dsh-mobile-composer-trailing]:has(> button[class*="_primary"] ~ button[class*="_primary"]) { flex-basis:144px !important; min-width:min(100%,144px) !important; }
|
|
2945
|
-
[data-dsh-mobile-composer-controls]:not([hidden]) { display:flex !important; flex:1 1 0 !important; flex-wrap:wrap !important; justify-content:flex-end !important; min-width:0 !important; max-width:100% !important; gap:6px !important; }
|
|
2946
|
-
[data-dsh-mobile-composer-controls] > *,[data-dsh-mobile-composer-controls] > [data-slot] > * { min-width:0 !important; max-width:100% !important; }
|
|
2947
|
-
[data-dsh-mobile-composer-controls] button { white-space:normal !important; overflow-wrap:anywhere !important; }
|
|
2948
|
-
[data-dsh-mobile-composer-model] { flex:0 1 auto !important; width:auto !important; min-width:44px !important; max-width:100% !important; }
|
|
2949
|
-
[data-dsh-mobile-composer-model-trigger] { box-sizing:border-box !important; width:100% !important; max-width:100% !important; min-width:0 !important; padding-left:6px !important; padding-right:4px !important; }
|
|
2950
|
-
[data-dsh-mobile-composer-model-label] { flex:1 1 auto !important; max-width:none !important; min-width:0 !important; overflow:hidden !important; text-overflow:ellipsis !important; white-space:nowrap !important; }
|
|
2951
3426
|
[data-dsh-mobile-center] [class*="_root"]:has(> [class*="_card"] textarea) { box-sizing:border-box !important; width:100% !important; padding:0 0 8px !important; }
|
|
2952
3427
|
[data-dsh-mobile-center] [class*="_root"]:has(> [class*="_card"] textarea) > [class=""]:last-child { display:none !important; }
|
|
2953
3428
|
}
|
|
@@ -2972,7 +3447,7 @@ window.__ModuleLoader__.load({
|
|
|
2972
3447
|
[data-dsh-mobile-settings-content][data-dsh-mobile-view-transition="true"],
|
|
2973
3448
|
[data-dsh-mobile-view][data-dsh-mobile-view-transition="true"] { animation:none !important; }
|
|
2974
3449
|
}
|
|
2975
|
-
|
|
3450
|
+
` + MOBILE_COMPOSER_TOOLBAR_STYLES;
|
|
2976
3451
|
function classToken(element, suffix) {
|
|
2977
3452
|
return Array.from(element.classList).some((value) => value.endsWith(suffix));
|
|
2978
3453
|
}
|
|
@@ -4306,6 +4781,92 @@ window.__ModuleLoader__.load({
|
|
|
4306
4781
|
//#region src/mobile-font.ts
|
|
4307
4782
|
/** Device-local typography for authenticated mobile pages, without Host settings writes. */
|
|
4308
4783
|
const MOBILE_FONT_STORAGE_KEY = "dsh-mobile.content-font-size.v1";
|
|
4784
|
+
const MOBILE_TYPOGRAPHY_STORAGE_KEY = "dsh-mobile.typography.v1";
|
|
4785
|
+
const MOBILE_FONT_ROLES = ["text", "code"];
|
|
4786
|
+
const roleSpecs = {
|
|
4787
|
+
text: {
|
|
4788
|
+
default: 16,
|
|
4789
|
+
min: 12,
|
|
4790
|
+
variable: "--dsh-content-font-size"
|
|
4791
|
+
},
|
|
4792
|
+
code: {
|
|
4793
|
+
default: 11,
|
|
4794
|
+
min: 10,
|
|
4795
|
+
variable: "--dsh-code-font-size"
|
|
4796
|
+
}
|
|
4797
|
+
};
|
|
4798
|
+
const genericFamilies = /* @__PURE__ */ new Set([
|
|
4799
|
+
"serif",
|
|
4800
|
+
"sans-serif",
|
|
4801
|
+
"monospace",
|
|
4802
|
+
"cursive",
|
|
4803
|
+
"fantasy",
|
|
4804
|
+
"system-ui",
|
|
4805
|
+
"ui-serif",
|
|
4806
|
+
"ui-sans-serif",
|
|
4807
|
+
"ui-monospace",
|
|
4808
|
+
"ui-rounded",
|
|
4809
|
+
"emoji",
|
|
4810
|
+
"math",
|
|
4811
|
+
"fangsong",
|
|
4812
|
+
"-apple-system",
|
|
4813
|
+
"blinkmacsystemfont"
|
|
4814
|
+
]);
|
|
4815
|
+
const MAX_FONT_FAMILY_LENGTH = 1024;
|
|
4816
|
+
/** Normalize a comma-separated local font list without loading or discovering fonts. */
|
|
4817
|
+
function normalizeMobileFontFamily(value) {
|
|
4818
|
+
let result = "";
|
|
4819
|
+
for (const part of value.slice(0, MAX_FONT_FAMILY_LENGTH * 2).split(",")) {
|
|
4820
|
+
const name = part.replace(/["'\\<>\u0000-\u001f\u007f]/gu, "").replace(/\s+/gu, " ").trim();
|
|
4821
|
+
if (name === "") continue;
|
|
4822
|
+
const generic = genericFamilies.has(name.toLowerCase());
|
|
4823
|
+
const separator = result === "" ? "" : ", ";
|
|
4824
|
+
const remaining = MAX_FONT_FAMILY_LENGTH - result.length - separator.length - (generic ? 0 : 2);
|
|
4825
|
+
if (remaining < 1 || generic && name.length > remaining) break;
|
|
4826
|
+
result += separator + (generic ? name : `"${name.slice(0, remaining)}"`);
|
|
4827
|
+
}
|
|
4828
|
+
return result;
|
|
4829
|
+
}
|
|
4830
|
+
function defaultTypography(textSize = 16) {
|
|
4831
|
+
return Object.freeze({
|
|
4832
|
+
sizes: Object.freeze({
|
|
4833
|
+
text: textSize,
|
|
4834
|
+
code: roleSpecs.code.default
|
|
4835
|
+
}),
|
|
4836
|
+
families: Object.freeze({
|
|
4837
|
+
text: "",
|
|
4838
|
+
code: ""
|
|
4839
|
+
})
|
|
4840
|
+
});
|
|
4841
|
+
}
|
|
4842
|
+
/** Durable local JSON is validated independently for each typography role. */
|
|
4843
|
+
function readTypography(value, legacySize) {
|
|
4844
|
+
const fallback = defaultTypography(legacySize);
|
|
4845
|
+
if (value === null) return fallback;
|
|
4846
|
+
let parsed;
|
|
4847
|
+
try {
|
|
4848
|
+
parsed = JSON.parse(value);
|
|
4849
|
+
} catch (error) {
|
|
4850
|
+
return fallback;
|
|
4851
|
+
}
|
|
4852
|
+
if (typeof parsed !== "object" || parsed === null || !("sizes" in parsed) || !("families" in parsed)) return fallback;
|
|
4853
|
+
const sizes = { ...fallback.sizes };
|
|
4854
|
+
const families = { ...fallback.families };
|
|
4855
|
+
for (const role of MOBILE_FONT_ROLES) {
|
|
4856
|
+
if (typeof parsed.sizes === "object" && parsed.sizes !== null && role in parsed.sizes) {
|
|
4857
|
+
const size = Reflect.get(parsed.sizes, role);
|
|
4858
|
+
if (typeof size === "number" && Number.isInteger(size) && size >= roleSpecs[role].min && size <= 32) sizes[role] = size;
|
|
4859
|
+
}
|
|
4860
|
+
if (typeof parsed.families === "object" && parsed.families !== null && role in parsed.families) {
|
|
4861
|
+
const family = Reflect.get(parsed.families, role);
|
|
4862
|
+
if (typeof family === "string") families[role] = normalizeMobileFontFamily(family);
|
|
4863
|
+
}
|
|
4864
|
+
}
|
|
4865
|
+
return Object.freeze({
|
|
4866
|
+
sizes: Object.freeze(sizes),
|
|
4867
|
+
families: Object.freeze(families)
|
|
4868
|
+
});
|
|
4869
|
+
}
|
|
4309
4870
|
/** Read one local storage value; malformed or unavailable preferences use the mobile default. */
|
|
4310
4871
|
function parseMobileFontSize(value) {
|
|
4311
4872
|
if (value === null || !/^\d+$/.test(value)) return 16;
|
|
@@ -4315,15 +4876,21 @@ window.__ModuleLoader__.load({
|
|
|
4315
4876
|
/** Local preference storage; unavailable browser storage still permits in-page adjustment. */
|
|
4316
4877
|
var MobileFontPreference = class {
|
|
4317
4878
|
storage;
|
|
4318
|
-
|
|
4879
|
+
typography = defaultTypography();
|
|
4319
4880
|
listeners = /* @__PURE__ */ new Set();
|
|
4320
4881
|
constructor(storage) {
|
|
4321
4882
|
this.storage = storage;
|
|
4883
|
+
let legacySize = 16;
|
|
4322
4884
|
try {
|
|
4323
|
-
|
|
4885
|
+
legacySize = parseMobileFontSize(storage?.getItem("dsh-mobile.content-font-size.v1") ?? null);
|
|
4886
|
+
} catch (error) {}
|
|
4887
|
+
this.typography = defaultTypography(legacySize);
|
|
4888
|
+
try {
|
|
4889
|
+
this.typography = readTypography(storage?.getItem("dsh-mobile.typography.v1") ?? null, legacySize);
|
|
4324
4890
|
} catch (error) {}
|
|
4325
4891
|
}
|
|
4326
|
-
getSnapshot = () => this.
|
|
4892
|
+
getSnapshot = () => this.typography.sizes.text;
|
|
4893
|
+
getTypographySnapshot = () => this.typography;
|
|
4327
4894
|
subscribe = (listener) => {
|
|
4328
4895
|
this.listeners.add(listener);
|
|
4329
4896
|
return () => {
|
|
@@ -4332,11 +4899,45 @@ window.__ModuleLoader__.load({
|
|
|
4332
4899
|
};
|
|
4333
4900
|
/** Apply a user-selected size locally; never send it to DSH's settings RPC. */
|
|
4334
4901
|
set(size) {
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4902
|
+
this.setRoleSize("text", size);
|
|
4903
|
+
}
|
|
4904
|
+
/** Change one mobile font role independently; all values remain address-local. */
|
|
4905
|
+
setRoleSize(role, size) {
|
|
4906
|
+
if (!Number.isInteger(size) || size < roleSpecs[role].min || size > 32) throw new RangeError(`Mobile ${role} font size must be an integer from ${roleSpecs[role].min} to 32`);
|
|
4907
|
+
if (this.typography.sizes[role] === size) return;
|
|
4908
|
+
this.persist({
|
|
4909
|
+
sizes: Object.freeze({
|
|
4910
|
+
...this.typography.sizes,
|
|
4911
|
+
[role]: size
|
|
4912
|
+
}),
|
|
4913
|
+
families: this.typography.families
|
|
4914
|
+
});
|
|
4915
|
+
}
|
|
4916
|
+
/** Select existing local fonts; an empty list uses DSH's built-in font stack. */
|
|
4917
|
+
setRoleFamily(role, family) {
|
|
4918
|
+
const normalized = normalizeMobileFontFamily(family);
|
|
4919
|
+
if (this.typography.families[role] === normalized) return;
|
|
4920
|
+
this.persist({
|
|
4921
|
+
sizes: this.typography.sizes,
|
|
4922
|
+
families: Object.freeze({
|
|
4923
|
+
...this.typography.families,
|
|
4924
|
+
[role]: normalized
|
|
4925
|
+
})
|
|
4926
|
+
});
|
|
4927
|
+
}
|
|
4928
|
+
/** Restore mobile sizes and built-in font stacks without modifying Host settings. */
|
|
4929
|
+
reset() {
|
|
4930
|
+
const next = defaultTypography();
|
|
4931
|
+
if (MOBILE_FONT_ROLES.every((role) => this.typography.sizes[role] === next.sizes[role] && this.typography.families[role] === "")) return;
|
|
4932
|
+
this.persist(next);
|
|
4933
|
+
}
|
|
4934
|
+
persist(typography) {
|
|
4935
|
+
this.typography = Object.freeze(typography);
|
|
4338
4936
|
try {
|
|
4339
|
-
this.storage?.setItem(
|
|
4937
|
+
this.storage?.setItem(MOBILE_TYPOGRAPHY_STORAGE_KEY, JSON.stringify(typography));
|
|
4938
|
+
} catch (error) {}
|
|
4939
|
+
try {
|
|
4940
|
+
this.storage?.setItem(MOBILE_FONT_STORAGE_KEY, String(typography.sizes.text));
|
|
4340
4941
|
} catch (error) {}
|
|
4341
4942
|
for (const listener of this.listeners) listener();
|
|
4342
4943
|
}
|
|
@@ -4348,19 +4949,31 @@ window.__ModuleLoader__.load({
|
|
|
4348
4949
|
storage = window.localStorage;
|
|
4349
4950
|
} catch (error) {}
|
|
4350
4951
|
const preference = new MobileFontPreference(storage);
|
|
4351
|
-
const
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4952
|
+
const style = document.body.style;
|
|
4953
|
+
const properties = MOBILE_FONT_ROLES.flatMap((role) => [roleSpecs[role].variable, `--dsh-font-family-${role}`]);
|
|
4954
|
+
const previous = new Map(properties.map((property) => [property, {
|
|
4955
|
+
value: style.getPropertyValue(property),
|
|
4956
|
+
priority: style.getPropertyPriority(property)
|
|
4957
|
+
}]));
|
|
4958
|
+
const owned = /* @__PURE__ */ new Map();
|
|
4355
4959
|
const apply = () => {
|
|
4356
|
-
const
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4960
|
+
const typography = preference.getTypographySnapshot();
|
|
4961
|
+
for (const role of MOBILE_FONT_ROLES) for (const [property, value] of [[roleSpecs[role].variable, `${typography.sizes[role]}px`], [`--dsh-font-family-${role}`, typography.families[role]]]) {
|
|
4962
|
+
const current = style.getPropertyValue(property);
|
|
4963
|
+
const priority = style.getPropertyPriority(property);
|
|
4964
|
+
const last = owned.get(property);
|
|
4965
|
+
if (last !== void 0 && (current !== last.value || priority !== last.priority)) previous.set(property, {
|
|
4966
|
+
value: current,
|
|
4967
|
+
priority
|
|
4968
|
+
});
|
|
4969
|
+
owned.set(property, {
|
|
4970
|
+
value,
|
|
4971
|
+
priority: ""
|
|
4972
|
+
});
|
|
4973
|
+
if (current === value && priority === "") continue;
|
|
4974
|
+
if (value === "") style.removeProperty(property);
|
|
4975
|
+
else style.setProperty(property, value);
|
|
4360
4976
|
}
|
|
4361
|
-
const value = `${preference.getSnapshot()}px`;
|
|
4362
|
-
ownedValue = value;
|
|
4363
|
-
if (current !== value) document.body.style.setProperty(property, value);
|
|
4364
4977
|
};
|
|
4365
4978
|
apply();
|
|
4366
4979
|
const unsubscribe = preference.subscribe(apply);
|
|
@@ -4374,59 +4987,343 @@ window.__ModuleLoader__.load({
|
|
|
4374
4987
|
dispose: () => {
|
|
4375
4988
|
observer.disconnect();
|
|
4376
4989
|
unsubscribe();
|
|
4377
|
-
|
|
4378
|
-
|
|
4990
|
+
for (const [property, value] of previous) {
|
|
4991
|
+
const last = owned.get(property);
|
|
4992
|
+
if (last === void 0 || style.getPropertyValue(property) !== last.value || style.getPropertyPriority(property) !== last.priority) continue;
|
|
4993
|
+
if (value.value === "") style.removeProperty(property);
|
|
4994
|
+
else style.setProperty(property, value.value, value.priority);
|
|
4995
|
+
}
|
|
4379
4996
|
}
|
|
4380
4997
|
};
|
|
4381
4998
|
}
|
|
4382
|
-
const messages = {
|
|
4999
|
+
const messages$1 = {
|
|
4383
5000
|
zh: {
|
|
4384
5001
|
title: "移动端字号",
|
|
4385
5002
|
description: "仅保存在当前访问地址的本地数据中,不影响电脑或其他设备。",
|
|
4386
5003
|
decrease: "减小移动端字号",
|
|
4387
|
-
increase: "增大移动端字号"
|
|
5004
|
+
increase: "增大移动端字号",
|
|
5005
|
+
more: "更多字体设置",
|
|
5006
|
+
reset: "恢复默认",
|
|
5007
|
+
defaultFamily: "默认字体",
|
|
5008
|
+
familyHint: "填写设备已有的字体,多个名称用逗号分隔;留空使用默认字体。",
|
|
5009
|
+
codeSize: "代码字号",
|
|
5010
|
+
textFamily: "正文字体",
|
|
5011
|
+
codeFamily: "代码字体"
|
|
4388
5012
|
},
|
|
4389
5013
|
en: {
|
|
4390
5014
|
title: "Mobile font size",
|
|
4391
5015
|
description: "Saved locally for this address, without changing your computer or other devices.",
|
|
4392
5016
|
decrease: "Decrease mobile font size",
|
|
4393
|
-
increase: "Increase mobile font size"
|
|
5017
|
+
increase: "Increase mobile font size",
|
|
5018
|
+
more: "More font settings",
|
|
5019
|
+
reset: "Restore defaults",
|
|
5020
|
+
defaultFamily: "Default font",
|
|
5021
|
+
familyHint: "Use fonts already on your device, separated by commas. Leave empty for the default.",
|
|
5022
|
+
codeSize: "Code font size",
|
|
5023
|
+
textFamily: "Text font",
|
|
5024
|
+
codeFamily: "Code font"
|
|
4394
5025
|
},
|
|
4395
5026
|
it: {
|
|
4396
5027
|
title: "Dimensione testo mobile",
|
|
4397
5028
|
description: "Salvata localmente per questo indirizzo, senza modificare il computer o altri dispositivi.",
|
|
4398
5029
|
decrease: "Riduci il testo mobile",
|
|
4399
|
-
increase: "Aumenta il testo mobile"
|
|
5030
|
+
increase: "Aumenta il testo mobile",
|
|
5031
|
+
more: "Altre impostazioni dei caratteri",
|
|
5032
|
+
reset: "Ripristina predefiniti",
|
|
5033
|
+
defaultFamily: "Carattere predefinito",
|
|
5034
|
+
familyHint: "Usa caratteri presenti sul dispositivo, separati da virgole. Lascia vuoto per il predefinito.",
|
|
5035
|
+
codeSize: "Dimensione codice",
|
|
5036
|
+
textFamily: "Carattere testo",
|
|
5037
|
+
codeFamily: "Carattere codice"
|
|
4400
5038
|
}
|
|
4401
5039
|
};
|
|
5040
|
+
function FontFamilyInput({ preference, role, value, label, placeholder }) {
|
|
5041
|
+
const [draft, setDraft] = (0, react.useState)(value);
|
|
5042
|
+
(0, react.useEffect)(() => {
|
|
5043
|
+
setDraft(value);
|
|
5044
|
+
}, [value]);
|
|
5045
|
+
return (0, react.createElement)("label", { className: "dsh-mobile-settings_fontFamily" }, (0, react.createElement)("span", { className: "dsh-mobile-settings_title" }, label), (0, react.createElement)("input", {
|
|
5046
|
+
type: "text",
|
|
5047
|
+
value: draft,
|
|
5048
|
+
maxLength: MAX_FONT_FAMILY_LENGTH,
|
|
5049
|
+
placeholder,
|
|
5050
|
+
autoComplete: "off",
|
|
5051
|
+
spellCheck: false,
|
|
5052
|
+
"data-mobile-font-family": role,
|
|
5053
|
+
onChange: (event) => {
|
|
5054
|
+
setDraft(event.currentTarget.value);
|
|
5055
|
+
},
|
|
5056
|
+
onBlur: () => {
|
|
5057
|
+
preference.setRoleFamily(role, draft);
|
|
5058
|
+
setDraft(preference.getTypographySnapshot().families[role]);
|
|
5059
|
+
}
|
|
5060
|
+
}));
|
|
5061
|
+
}
|
|
4402
5062
|
/** General settings row matching the existing Mobile controls and DSH theme tokens. */
|
|
4403
5063
|
function MobileFontSizeRow({ preference, locale }) {
|
|
4404
|
-
const
|
|
4405
|
-
const copy = messages[locale];
|
|
5064
|
+
const typography = (0, react.useSyncExternalStore)(preference.subscribe, preference.getTypographySnapshot);
|
|
5065
|
+
const copy = messages$1[locale];
|
|
5066
|
+
const controls = (role, title) => {
|
|
5067
|
+
const size = typography.sizes[role];
|
|
5068
|
+
return (0, react.createElement)("div", {
|
|
5069
|
+
className: "dsh-mobile-settings_fontControls",
|
|
5070
|
+
"data-mobile-font-role": role
|
|
5071
|
+
}, (0, react.createElement)("button", {
|
|
5072
|
+
type: "button",
|
|
5073
|
+
className: "dsh-mobile-settings_selector",
|
|
5074
|
+
"aria-label": `${copy.decrease}: ${title}`,
|
|
5075
|
+
disabled: size === roleSpecs[role].min,
|
|
5076
|
+
onClick: () => {
|
|
5077
|
+
preference.setRoleSize(role, size - 1);
|
|
5078
|
+
}
|
|
5079
|
+
}, "−"), (0, react.createElement)("output", {
|
|
5080
|
+
"aria-live": "polite",
|
|
5081
|
+
"aria-label": title
|
|
5082
|
+
}, `${size}px`), (0, react.createElement)("button", {
|
|
5083
|
+
type: "button",
|
|
5084
|
+
className: "dsh-mobile-settings_selector",
|
|
5085
|
+
"aria-label": `${copy.increase}: ${title}`,
|
|
5086
|
+
disabled: size === 32,
|
|
5087
|
+
onClick: () => {
|
|
5088
|
+
preference.setRoleSize(role, size + 1);
|
|
5089
|
+
}
|
|
5090
|
+
}, "+"));
|
|
5091
|
+
};
|
|
4406
5092
|
return (0, react.createElement)("div", {
|
|
4407
|
-
className: "dsh-mobile-settings_row",
|
|
4408
5093
|
lang: locale,
|
|
4409
5094
|
"data-mobile-font-setting": true
|
|
4410
|
-
}, (0, react.createElement)("div", { className: "dsh-mobile-settings_rowText" }, (0, react.createElement)("div", { className: "dsh-mobile-settings_title" }, copy.title), (0, react.createElement)("div", { className: "dsh-mobile-settings_desc" }, copy.description)), (0, react.createElement)("div", { className: "dsh-mobile-
|
|
5095
|
+
}, (0, react.createElement)("div", { className: "dsh-mobile-settings_row" }, (0, react.createElement)("div", { className: "dsh-mobile-settings_rowText" }, (0, react.createElement)("div", { className: "dsh-mobile-settings_title" }, copy.title), (0, react.createElement)("div", { className: "dsh-mobile-settings_desc" }, copy.description)), controls("text", copy.title)), (0, react.createElement)("details", { className: "dsh-mobile-settings_fontDetails" }, (0, react.createElement)("summary", null, copy.more), (0, react.createElement)("p", { className: "dsh-mobile-settings_desc" }, copy.familyHint), (0, react.createElement)("div", { className: "dsh-mobile-settings_row" }, (0, react.createElement)("span", { className: "dsh-mobile-settings_rowText dsh-mobile-settings_title" }, copy.codeSize), controls("code", copy.codeSize)), ...MOBILE_FONT_ROLES.map((role) => (0, react.createElement)(FontFamilyInput, {
|
|
5096
|
+
key: role,
|
|
5097
|
+
preference,
|
|
5098
|
+
role,
|
|
5099
|
+
value: typography.families[role],
|
|
5100
|
+
label: role === "text" ? copy.textFamily : copy.codeFamily,
|
|
5101
|
+
placeholder: copy.defaultFamily
|
|
5102
|
+
})), (0, react.createElement)("button", {
|
|
4411
5103
|
type: "button",
|
|
4412
5104
|
className: "dsh-mobile-settings_selector",
|
|
4413
|
-
"aria-label": copy.decrease,
|
|
4414
|
-
disabled: size === 12,
|
|
4415
5105
|
onClick: () => {
|
|
4416
|
-
preference.
|
|
5106
|
+
preference.reset();
|
|
5107
|
+
}
|
|
5108
|
+
}, copy.reset)));
|
|
5109
|
+
}
|
|
5110
|
+
//#endregion
|
|
5111
|
+
//#region src/mobile-display-scale.ts
|
|
5112
|
+
/** Native App page scaling in DSH General settings, independent from local typography. */
|
|
5113
|
+
/** Validate the native reply before displaying or reusing its values in a request. */
|
|
5114
|
+
function parseMobileDisplayScale(value) {
|
|
5115
|
+
if (typeof value !== "object" || value === null || !("percent" in value) || !("minPercent" in value) || !("maxPercent" in value)) throw new TypeError("Native display scale reply is invalid");
|
|
5116
|
+
const { percent, minPercent, maxPercent } = value;
|
|
5117
|
+
if (typeof percent !== "number" || !Number.isInteger(percent) || typeof minPercent !== "number" || !Number.isInteger(minPercent) || typeof maxPercent !== "number" || !Number.isInteger(maxPercent) || minPercent < 80 || maxPercent > 125 || minPercent > 100 || maxPercent < 100 || percent < minPercent || percent > maxPercent) throw new TypeError("Native display scale reply is invalid");
|
|
5118
|
+
return Object.freeze({
|
|
5119
|
+
percent,
|
|
5120
|
+
minPercent,
|
|
5121
|
+
maxPercent
|
|
5122
|
+
});
|
|
5123
|
+
}
|
|
5124
|
+
/** Serial native requests; a replaced bridge or disposed row cannot publish stale results. */
|
|
5125
|
+
var MobileDisplayScaleController = class {
|
|
5126
|
+
getBridge;
|
|
5127
|
+
snapshot = {
|
|
5128
|
+
state: "hidden",
|
|
5129
|
+
value: null
|
|
5130
|
+
};
|
|
5131
|
+
listeners = /* @__PURE__ */ new Set();
|
|
5132
|
+
bridge;
|
|
5133
|
+
generation = 0;
|
|
5134
|
+
active = false;
|
|
5135
|
+
constructor(getBridge) {
|
|
5136
|
+
this.getBridge = getBridge;
|
|
5137
|
+
}
|
|
5138
|
+
getSnapshot = () => this.snapshot;
|
|
5139
|
+
subscribe = (listener) => {
|
|
5140
|
+
this.listeners.add(listener);
|
|
5141
|
+
return () => {
|
|
5142
|
+
this.listeners.delete(listener);
|
|
5143
|
+
};
|
|
5144
|
+
};
|
|
5145
|
+
start() {
|
|
5146
|
+
this.active = true;
|
|
5147
|
+
this.refresh();
|
|
5148
|
+
}
|
|
5149
|
+
stop() {
|
|
5150
|
+
this.active = false;
|
|
5151
|
+
this.generation += 1;
|
|
5152
|
+
this.bridge = void 0;
|
|
5153
|
+
}
|
|
5154
|
+
/** Discover both capabilities before showing a control or querying its current native value. */
|
|
5155
|
+
refresh = () => {
|
|
5156
|
+
if (!this.active) return;
|
|
5157
|
+
const generation = ++this.generation;
|
|
5158
|
+
const bridge = this.getBridge();
|
|
5159
|
+
this.bridge = bridge;
|
|
5160
|
+
this.publish({
|
|
5161
|
+
state: "hidden",
|
|
5162
|
+
value: null
|
|
5163
|
+
});
|
|
5164
|
+
if (bridge === void 0) return;
|
|
5165
|
+
Promise.resolve().then(() => bridge.capabilities()).then(async (capabilities) => {
|
|
5166
|
+
if (!this.current(bridge, generation)) return;
|
|
5167
|
+
if (!capabilities.includes("mobile.display-scale.get") || !capabilities.includes("mobile.display-scale.set")) return;
|
|
5168
|
+
this.publish({
|
|
5169
|
+
state: "loading",
|
|
5170
|
+
value: null
|
|
5171
|
+
});
|
|
5172
|
+
try {
|
|
5173
|
+
const value = parseMobileDisplayScale(await bridge.invoke("mobile.display-scale.get", {}));
|
|
5174
|
+
if (this.current(bridge, generation)) this.publish({
|
|
5175
|
+
state: "ready",
|
|
5176
|
+
value
|
|
5177
|
+
});
|
|
5178
|
+
} catch (error) {
|
|
5179
|
+
if (this.current(bridge, generation)) this.publish({
|
|
5180
|
+
state: "error",
|
|
5181
|
+
value: null
|
|
5182
|
+
});
|
|
5183
|
+
}
|
|
5184
|
+
}, () => {});
|
|
5185
|
+
};
|
|
5186
|
+
/** Apply a device-local value once, preserving the last confirmed value when the native request fails. */
|
|
5187
|
+
setPercent(percent) {
|
|
5188
|
+
const value = this.snapshot.value;
|
|
5189
|
+
const bridge = this.bridge;
|
|
5190
|
+
if (!this.active || bridge === void 0 || bridge !== this.getBridge() || value === null || this.snapshot.state === "busy" || this.snapshot.state === "loading") return;
|
|
5191
|
+
if (!Number.isInteger(percent) || percent < value.minPercent || percent > value.maxPercent) return;
|
|
5192
|
+
if (percent === value.percent && this.snapshot.state !== "error") return;
|
|
5193
|
+
const generation = this.generation;
|
|
5194
|
+
this.publish({
|
|
5195
|
+
state: "busy",
|
|
5196
|
+
value
|
|
5197
|
+
});
|
|
5198
|
+
Promise.resolve().then(() => this.current(bridge, generation) ? bridge.invoke("mobile.display-scale.set", { percent }) : void 0).then((reply) => {
|
|
5199
|
+
if (!this.current(bridge, generation)) return;
|
|
5200
|
+
try {
|
|
5201
|
+
this.publish({
|
|
5202
|
+
state: "ready",
|
|
5203
|
+
value: parseMobileDisplayScale(reply)
|
|
5204
|
+
});
|
|
5205
|
+
} catch (error) {
|
|
5206
|
+
this.publish({
|
|
5207
|
+
state: "error",
|
|
5208
|
+
value
|
|
5209
|
+
});
|
|
5210
|
+
}
|
|
5211
|
+
}, () => {
|
|
5212
|
+
if (this.current(bridge, generation)) this.publish({
|
|
5213
|
+
state: "error",
|
|
5214
|
+
value
|
|
5215
|
+
});
|
|
5216
|
+
});
|
|
5217
|
+
}
|
|
5218
|
+
current(bridge, generation) {
|
|
5219
|
+
return this.active && generation === this.generation && this.bridge === bridge && this.getBridge() === bridge;
|
|
5220
|
+
}
|
|
5221
|
+
publish(snapshot) {
|
|
5222
|
+
this.snapshot = Object.freeze(snapshot);
|
|
5223
|
+
for (const listener of this.listeners) listener();
|
|
5224
|
+
}
|
|
5225
|
+
};
|
|
5226
|
+
const messages = {
|
|
5227
|
+
zh: {
|
|
5228
|
+
title: "页面缩放",
|
|
5229
|
+
description: "调整整个页面的显示大小,仅保存在当前 App 中,与字号设置独立。",
|
|
5230
|
+
reset: "恢复 100%",
|
|
5231
|
+
loading: "读取中…",
|
|
5232
|
+
busy: "正在调整…",
|
|
5233
|
+
error: "无法调整页面缩放,请重试。",
|
|
5234
|
+
retry: "重试"
|
|
5235
|
+
},
|
|
5236
|
+
en: {
|
|
5237
|
+
title: "Page zoom",
|
|
5238
|
+
description: "Resize the whole page. Saved in this App, separately from font size.",
|
|
5239
|
+
reset: "Reset to 100%",
|
|
5240
|
+
loading: "Loading…",
|
|
5241
|
+
busy: "Applying…",
|
|
5242
|
+
error: "Could not change page zoom. Try again.",
|
|
5243
|
+
retry: "Retry"
|
|
5244
|
+
},
|
|
5245
|
+
it: {
|
|
5246
|
+
title: "Zoom pagina",
|
|
5247
|
+
description: "Ridimensiona tutta la pagina. Salvato in questa app, separatamente dalla dimensione del testo.",
|
|
5248
|
+
reset: "Ripristina 100%",
|
|
5249
|
+
loading: "Caricamento…",
|
|
5250
|
+
busy: "Applicazione…",
|
|
5251
|
+
error: "Impossibile modificare lo zoom. Riprova.",
|
|
5252
|
+
retry: "Riprova"
|
|
5253
|
+
}
|
|
5254
|
+
};
|
|
5255
|
+
/** Native-only General settings row using the existing DSH Mobile controls and locale. */
|
|
5256
|
+
function MobileDisplayScaleRow({ locale }) {
|
|
5257
|
+
const [controller] = (0, react.useState)(() => new MobileDisplayScaleController(() => window.__DSH_MOBILE_NATIVE__));
|
|
5258
|
+
const snapshot = (0, react.useSyncExternalStore)(controller.subscribe, controller.getSnapshot);
|
|
5259
|
+
(0, react.useEffect)(() => {
|
|
5260
|
+
controller.start();
|
|
5261
|
+
window.addEventListener("dsh-mobile-native-ready", controller.refresh);
|
|
5262
|
+
return () => {
|
|
5263
|
+
window.removeEventListener("dsh-mobile-native-ready", controller.refresh);
|
|
5264
|
+
controller.stop();
|
|
5265
|
+
};
|
|
5266
|
+
}, [controller]);
|
|
5267
|
+
if (snapshot.state === "hidden") return null;
|
|
5268
|
+
const copy = messages[locale];
|
|
5269
|
+
const value = snapshot.value;
|
|
5270
|
+
const busy = snapshot.state === "busy" || snapshot.state === "loading";
|
|
5271
|
+
const description = snapshot.state === "error" ? copy.error : snapshot.state === "loading" ? copy.loading : snapshot.state === "busy" ? copy.busy : copy.description;
|
|
5272
|
+
const options = value === null ? [] : [.../* @__PURE__ */ new Set([
|
|
5273
|
+
value.minPercent,
|
|
5274
|
+
value.maxPercent,
|
|
5275
|
+
value.percent,
|
|
5276
|
+
...[
|
|
5277
|
+
80,
|
|
5278
|
+
85,
|
|
5279
|
+
90,
|
|
5280
|
+
95,
|
|
5281
|
+
100,
|
|
5282
|
+
105,
|
|
5283
|
+
110,
|
|
5284
|
+
115,
|
|
5285
|
+
120,
|
|
5286
|
+
125
|
|
5287
|
+
].filter((percent) => percent >= value.minPercent && percent <= value.maxPercent)
|
|
5288
|
+
])].sort((a, b) => a - b);
|
|
5289
|
+
return (0, react.createElement)("div", {
|
|
5290
|
+
className: "dsh-mobile-settings_row",
|
|
5291
|
+
style: { flexWrap: "wrap" },
|
|
5292
|
+
lang: locale,
|
|
5293
|
+
"data-mobile-display-scale-setting": true,
|
|
5294
|
+
"aria-busy": busy
|
|
5295
|
+
}, (0, react.createElement)("div", {
|
|
5296
|
+
className: "dsh-mobile-settings_rowText",
|
|
5297
|
+
style: {
|
|
5298
|
+
flexBasis: 160,
|
|
5299
|
+
paddingRight: 8
|
|
5300
|
+
}
|
|
5301
|
+
}, (0, react.createElement)("div", { className: "dsh-mobile-settings_title" }, copy.title), (0, react.createElement)("div", {
|
|
5302
|
+
className: "dsh-mobile-settings_desc",
|
|
5303
|
+
"aria-live": "polite"
|
|
5304
|
+
}, description)), (0, react.createElement)("div", { className: "dsh-mobile-settings_fontControls" }, value === null ? null : (0, react.createElement)("select", {
|
|
5305
|
+
className: "dsh-mobile-settings_selector",
|
|
5306
|
+
"aria-label": copy.title,
|
|
5307
|
+
disabled: busy,
|
|
5308
|
+
value: value.percent,
|
|
5309
|
+
onChange: (event) => {
|
|
5310
|
+
controller.setPercent(Number(event.currentTarget.value));
|
|
4417
5311
|
}
|
|
4418
|
-
},
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
}, `${
|
|
5312
|
+
}, ...options.map((percent) => (0, react.createElement)("option", {
|
|
5313
|
+
key: percent,
|
|
5314
|
+
value: percent
|
|
5315
|
+
}, `${percent}%`))), snapshot.state === "error" ? (0, react.createElement)("button", {
|
|
5316
|
+
type: "button",
|
|
5317
|
+
className: "dsh-mobile-settings_selector",
|
|
5318
|
+
onClick: controller.refresh
|
|
5319
|
+
}, copy.retry) : (0, react.createElement)("button", {
|
|
4422
5320
|
type: "button",
|
|
4423
5321
|
className: "dsh-mobile-settings_selector",
|
|
4424
|
-
|
|
4425
|
-
disabled: size === 32,
|
|
5322
|
+
disabled: busy || value === null || value.percent === 100,
|
|
4426
5323
|
onClick: () => {
|
|
4427
|
-
|
|
5324
|
+
controller.setPercent(100);
|
|
4428
5325
|
}
|
|
4429
|
-
},
|
|
5326
|
+
}, copy.reset)));
|
|
4430
5327
|
}
|
|
4431
5328
|
//#endregion
|
|
4432
5329
|
//#region src/local-admin-host.ts
|
|
@@ -4778,6 +5675,244 @@ window.__ModuleLoader__.load({
|
|
|
4778
5675
|
}, copy.open));
|
|
4779
5676
|
}
|
|
4780
5677
|
//#endregion
|
|
5678
|
+
//#region src/extension-recovery-ui.ts
|
|
5679
|
+
/** Validate the Host response and select only stopped workers, never in-process hosts. */
|
|
5680
|
+
function parseUnavailableExtensionHosts(payload) {
|
|
5681
|
+
if (typeof payload !== "object" || payload === null || !("hosts" in payload) || !Array.isArray(payload.hosts)) return void 0;
|
|
5682
|
+
const ids = /* @__PURE__ */ new Set();
|
|
5683
|
+
const hosts = [];
|
|
5684
|
+
for (const value of payload.hosts) {
|
|
5685
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
5686
|
+
const entry = value;
|
|
5687
|
+
if (typeof entry.id !== "string" || !/^[a-z][a-z0-9-]{0,63}$/u.test(entry.id) || ids.has(entry.id) || typeof entry.name !== "string" || entry.name.trim() === "" || entry.mode !== "worker" && entry.mode !== "in-process" || entry.state !== "ready" && entry.state !== "unavailable" || typeof entry.generation !== "string" || !/^[a-f\d]{64}$/u.test(entry.generation)) return void 0;
|
|
5688
|
+
ids.add(entry.id);
|
|
5689
|
+
if (entry.mode === "worker" && entry.state === "unavailable") hosts.push({
|
|
5690
|
+
id: entry.id,
|
|
5691
|
+
name: entry.name,
|
|
5692
|
+
generation: entry.generation
|
|
5693
|
+
});
|
|
5694
|
+
}
|
|
5695
|
+
return hosts;
|
|
5696
|
+
}
|
|
5697
|
+
/** One section shared across desktop access views; recovery is never automatic. */
|
|
5698
|
+
function installExtensionRecoveryControl(options) {
|
|
5699
|
+
const { document, signal, request, confirm, translate: t } = options;
|
|
5700
|
+
const section = document.createElement("section");
|
|
5701
|
+
section.className = "dsh-mobile-control__extension-recovery";
|
|
5702
|
+
section.hidden = true;
|
|
5703
|
+
section.setAttribute("aria-label", t("extensionRecoveryTitle"));
|
|
5704
|
+
const title = document.createElement("h3");
|
|
5705
|
+
title.textContent = t("extensionRecoveryTitle");
|
|
5706
|
+
const help = document.createElement("p");
|
|
5707
|
+
help.textContent = t("extensionRecoveryHelp");
|
|
5708
|
+
const list = document.createElement("ul");
|
|
5709
|
+
const feedback = document.createElement("p");
|
|
5710
|
+
feedback.className = "dsh-mobile-control__extension-recovery-error";
|
|
5711
|
+
feedback.hidden = true;
|
|
5712
|
+
feedback.setAttribute("role", "status");
|
|
5713
|
+
feedback.setAttribute("aria-live", "polite");
|
|
5714
|
+
const retry = document.createElement("button");
|
|
5715
|
+
retry.type = "button";
|
|
5716
|
+
retry.className = "dsh-mobile-control__secondary";
|
|
5717
|
+
retry.textContent = t("extensionRecoveryRefresh");
|
|
5718
|
+
retry.hidden = true;
|
|
5719
|
+
section.append(title, help, list, feedback, retry);
|
|
5720
|
+
const rows = /* @__PURE__ */ new Map();
|
|
5721
|
+
let hosts = [];
|
|
5722
|
+
let busyId;
|
|
5723
|
+
let errorText = "";
|
|
5724
|
+
let errorSource;
|
|
5725
|
+
let revision = 0;
|
|
5726
|
+
let reading = false;
|
|
5727
|
+
let refreshPending = false;
|
|
5728
|
+
let disposed = false;
|
|
5729
|
+
const live = () => !disposed && !signal.aborted;
|
|
5730
|
+
const render = () => {
|
|
5731
|
+
if (!live()) return;
|
|
5732
|
+
section.hidden = hosts.length === 0;
|
|
5733
|
+
section.setAttribute("aria-busy", String(busyId !== void 0));
|
|
5734
|
+
for (const [id, entry] of rows) {
|
|
5735
|
+
if (hosts.some((host) => host.id === id)) continue;
|
|
5736
|
+
entry.row.remove();
|
|
5737
|
+
rows.delete(id);
|
|
5738
|
+
}
|
|
5739
|
+
for (const host of hosts) {
|
|
5740
|
+
let entry = rows.get(host.id);
|
|
5741
|
+
if (entry === void 0) {
|
|
5742
|
+
const row = document.createElement("li");
|
|
5743
|
+
const text = document.createElement("div");
|
|
5744
|
+
const name = document.createElement("strong");
|
|
5745
|
+
const id = document.createElement("code");
|
|
5746
|
+
const button = document.createElement("button");
|
|
5747
|
+
button.type = "button";
|
|
5748
|
+
button.className = "dsh-mobile-control__secondary";
|
|
5749
|
+
button.dataset.extensionRecoveryId = host.id;
|
|
5750
|
+
button.addEventListener("click", () => {
|
|
5751
|
+
recover(host.id);
|
|
5752
|
+
});
|
|
5753
|
+
text.append(name, id);
|
|
5754
|
+
row.append(text, button);
|
|
5755
|
+
list.append(row);
|
|
5756
|
+
entry = {
|
|
5757
|
+
row,
|
|
5758
|
+
name,
|
|
5759
|
+
id,
|
|
5760
|
+
button
|
|
5761
|
+
};
|
|
5762
|
+
rows.set(host.id, entry);
|
|
5763
|
+
}
|
|
5764
|
+
entry.name.textContent = host.name;
|
|
5765
|
+
entry.id.textContent = host.id;
|
|
5766
|
+
entry.button.disabled = busyId !== void 0;
|
|
5767
|
+
entry.button.textContent = t(busyId === host.id ? "extensionRecoveryRestarting" : "extensionRecoveryRestart");
|
|
5768
|
+
entry.button.setAttribute("aria-label", t("extensionRecoveryRestartAria", { name: host.name }));
|
|
5769
|
+
}
|
|
5770
|
+
feedback.hidden = errorText === "";
|
|
5771
|
+
feedback.textContent = errorText;
|
|
5772
|
+
retry.hidden = errorText === "";
|
|
5773
|
+
retry.disabled = reading || busyId !== void 0;
|
|
5774
|
+
};
|
|
5775
|
+
const accept = (payload, recovered = false) => {
|
|
5776
|
+
const parsed = parseUnavailableExtensionHosts(payload);
|
|
5777
|
+
if (parsed === void 0) throw new Error("extension_recovery_invalid_response");
|
|
5778
|
+
hosts = parsed;
|
|
5779
|
+
if (recovered || hosts.length === 0 || errorSource === "load") {
|
|
5780
|
+
errorText = "";
|
|
5781
|
+
errorSource = void 0;
|
|
5782
|
+
}
|
|
5783
|
+
render();
|
|
5784
|
+
};
|
|
5785
|
+
const refresh = async () => {
|
|
5786
|
+
if (!live()) return;
|
|
5787
|
+
if (reading || busyId !== void 0) {
|
|
5788
|
+
refreshPending = true;
|
|
5789
|
+
return;
|
|
5790
|
+
}
|
|
5791
|
+
reading = true;
|
|
5792
|
+
const current = ++revision;
|
|
5793
|
+
render();
|
|
5794
|
+
try {
|
|
5795
|
+
const payload = await request("/api/mobile-access/extensions/hosts");
|
|
5796
|
+
if (live() && current === revision) accept(payload);
|
|
5797
|
+
} catch (_error) {
|
|
5798
|
+
if (live() && current === revision && hosts.length > 0 && errorSource !== "recover") {
|
|
5799
|
+
errorText = t("extensionRecoveryLoadFailed");
|
|
5800
|
+
errorSource = "load";
|
|
5801
|
+
}
|
|
5802
|
+
} finally {
|
|
5803
|
+
reading = false;
|
|
5804
|
+
if (live()) {
|
|
5805
|
+
render();
|
|
5806
|
+
if (refreshPending && busyId === void 0) {
|
|
5807
|
+
refreshPending = false;
|
|
5808
|
+
await refresh();
|
|
5809
|
+
}
|
|
5810
|
+
}
|
|
5811
|
+
}
|
|
5812
|
+
};
|
|
5813
|
+
const recover = async (id) => {
|
|
5814
|
+
const host = hosts.find((entry) => entry.id === id);
|
|
5815
|
+
if (!live() || busyId !== void 0 || host === void 0 || !confirm(t("extensionRecoveryConfirm", { name: host.name }))) return;
|
|
5816
|
+
if (!live()) return;
|
|
5817
|
+
busyId = id;
|
|
5818
|
+
errorText = "";
|
|
5819
|
+
errorSource = void 0;
|
|
5820
|
+
const current = ++revision;
|
|
5821
|
+
render();
|
|
5822
|
+
try {
|
|
5823
|
+
const payload = await request("/api/mobile-access/extensions/recover", {
|
|
5824
|
+
method: "POST",
|
|
5825
|
+
body: JSON.stringify({
|
|
5826
|
+
id,
|
|
5827
|
+
confirm: true
|
|
5828
|
+
})
|
|
5829
|
+
});
|
|
5830
|
+
if (live() && current === revision) accept(payload, true);
|
|
5831
|
+
} catch (error) {
|
|
5832
|
+
if (live() && current === revision) {
|
|
5833
|
+
errorText = t(error instanceof Error && error.message === "extension_changed_during_activation" ? "extensionRecoveryChanged" : "extensionRecoveryFailed");
|
|
5834
|
+
errorSource = "recover";
|
|
5835
|
+
}
|
|
5836
|
+
} finally {
|
|
5837
|
+
busyId = void 0;
|
|
5838
|
+
if (live()) {
|
|
5839
|
+
render();
|
|
5840
|
+
if (refreshPending && !reading) {
|
|
5841
|
+
refreshPending = false;
|
|
5842
|
+
await refresh();
|
|
5843
|
+
}
|
|
5844
|
+
}
|
|
5845
|
+
}
|
|
5846
|
+
};
|
|
5847
|
+
retry.addEventListener("click", () => {
|
|
5848
|
+
errorText = "";
|
|
5849
|
+
errorSource = void 0;
|
|
5850
|
+
refresh();
|
|
5851
|
+
});
|
|
5852
|
+
return {
|
|
5853
|
+
section,
|
|
5854
|
+
refresh,
|
|
5855
|
+
dispose: () => {
|
|
5856
|
+
disposed = true;
|
|
5857
|
+
revision++;
|
|
5858
|
+
section.remove();
|
|
5859
|
+
rows.clear();
|
|
5860
|
+
}
|
|
5861
|
+
};
|
|
5862
|
+
}
|
|
5863
|
+
/** Theme-owned compact recovery rows; normal connection views keep their layout. */
|
|
5864
|
+
const EXTENSION_RECOVERY_STYLES = `
|
|
5865
|
+
.dsh-mobile-control__extension-recovery{margin:0 0 14px;padding:12px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary)}
|
|
5866
|
+
.dsh-mobile-control__extension-recovery[hidden]{display:none}
|
|
5867
|
+
.dsh-mobile-control__extension-recovery h3{margin:0 0 6px;font-size:13px;line-height:1.5;font-weight:600}
|
|
5868
|
+
.dsh-mobile-control__extension-recovery p{margin:0 0 8px;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-secondary)}
|
|
5869
|
+
.dsh-mobile-control__extension-recovery ul{list-style:none;margin:0;padding:0}
|
|
5870
|
+
.dsh-mobile-control__extension-recovery li{display:flex;align-items:center;gap:12px;padding:6px 0}
|
|
5871
|
+
.dsh-mobile-control__extension-recovery li>div{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;overflow-wrap:anywhere}
|
|
5872
|
+
.dsh-mobile-control__extension-recovery strong{font-size:12px;line-height:1.5}
|
|
5873
|
+
.dsh-mobile-control__extension-recovery code{font-size:11px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}
|
|
5874
|
+
.dsh-mobile-control__extension-recovery button{flex:none;box-sizing:border-box;min-height:48px;min-width:48px;white-space:normal}
|
|
5875
|
+
.dsh-mobile-control__extension-recovery button:disabled{cursor:wait;opacity:.55}
|
|
5876
|
+
.dsh-mobile-control__extension-recovery button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
|
|
5877
|
+
.dsh-mobile-control__extension-recovery button:active:not(:disabled){background:var(--dsw-alias-interactive-bg-active,var(--dsw-alias-interactive-bg-hover))}
|
|
5878
|
+
.dsh-mobile-control__extension-recovery .dsh-mobile-control__extension-recovery-error{color:var(--dsw-alias-label-primary)}
|
|
5879
|
+
`;
|
|
5880
|
+
//#endregion
|
|
5881
|
+
//#region src/native-capability.ts
|
|
5882
|
+
/** Cancel queued calls before native effects start; started effects are not rolled back. */
|
|
5883
|
+
function invokeBoundNativeCapability(bridge, currentBridge, action, input, signal) {
|
|
5884
|
+
const abortReason = () => signal.reason ?? new DOMException("mobile extension disposed", "AbortError");
|
|
5885
|
+
if (signal.aborted) return Promise.reject(abortReason());
|
|
5886
|
+
return new Promise((resolve, reject) => {
|
|
5887
|
+
let settled = false;
|
|
5888
|
+
const finish = (callback) => {
|
|
5889
|
+
if (settled) return;
|
|
5890
|
+
settled = true;
|
|
5891
|
+
signal.removeEventListener("abort", onAbort);
|
|
5892
|
+
callback();
|
|
5893
|
+
};
|
|
5894
|
+
const onAbort = () => {
|
|
5895
|
+
finish(() => {
|
|
5896
|
+
reject(abortReason());
|
|
5897
|
+
});
|
|
5898
|
+
};
|
|
5899
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
5900
|
+
Promise.resolve().then(() => {
|
|
5901
|
+
if (settled || signal.aborted) throw abortReason();
|
|
5902
|
+
if (currentBridge() !== bridge) throw new DOMException("native bridge changed", "AbortError");
|
|
5903
|
+
return bridge.invoke(action, input);
|
|
5904
|
+
}).then((value) => {
|
|
5905
|
+
finish(() => {
|
|
5906
|
+
resolve(value);
|
|
5907
|
+
});
|
|
5908
|
+
}, (error) => {
|
|
5909
|
+
finish(() => {
|
|
5910
|
+
reject(error);
|
|
5911
|
+
});
|
|
5912
|
+
});
|
|
5913
|
+
});
|
|
5914
|
+
}
|
|
5915
|
+
//#endregion
|
|
4781
5916
|
//#region src/client.ts
|
|
4782
5917
|
/** Map a host-reported provider onto the UI union, defaulting to the built-in one. */
|
|
4783
5918
|
function parseMobileRemoteProvider(value) {
|
|
@@ -5291,6 +6426,14 @@ window.__ModuleLoader__.load({
|
|
|
5291
6426
|
funnel_https_required: "funnelHttps",
|
|
5292
6427
|
funnel_start_failed: "funnelStart",
|
|
5293
6428
|
funnel_start_timeout: "funnelTimeout",
|
|
6429
|
+
funnel_state_invalid: "funnelStateInvalid",
|
|
6430
|
+
cpolar_path_invalid: "componentPathUnsafe",
|
|
6431
|
+
cloudflared_path_invalid: "componentPathUnsafe",
|
|
6432
|
+
cloudflared_tunnel_path_invalid: "componentPathUnsafe",
|
|
6433
|
+
frp_path_invalid: "componentPathUnsafe",
|
|
6434
|
+
frp_config_path_invalid: "componentPathUnsafe",
|
|
6435
|
+
frp_ingress_path_invalid: "componentPathUnsafe",
|
|
6436
|
+
caddy_path_invalid: "componentPathUnsafe",
|
|
5294
6437
|
tailscale_dns_missing: "tailscaleDnsMissing",
|
|
5295
6438
|
gateway_start_failed: "gatewayStartFailed",
|
|
5296
6439
|
control_channel_failed: "controlChannelFailed",
|
|
@@ -5388,6 +6531,7 @@ window.__ModuleLoader__.load({
|
|
|
5388
6531
|
"cpolar_component_invalid",
|
|
5389
6532
|
"cpolar_config_missing",
|
|
5390
6533
|
"cpolar_config_invalid",
|
|
6534
|
+
"funnel_state_invalid",
|
|
5391
6535
|
"cpolar_start_timeout",
|
|
5392
6536
|
"cpolar_stopped",
|
|
5393
6537
|
"cpolar_exited",
|
|
@@ -5462,6 +6606,13 @@ window.__ModuleLoader__.load({
|
|
|
5462
6606
|
...init,
|
|
5463
6607
|
signal: lifecycle.signal
|
|
5464
6608
|
}, timeoutMs);
|
|
6609
|
+
const extensionRecovery = installExtensionRecoveryControl({
|
|
6610
|
+
document,
|
|
6611
|
+
signal: lifecycle.signal,
|
|
6612
|
+
request: controlRequestJson,
|
|
6613
|
+
confirm: (message) => window.confirm(message),
|
|
6614
|
+
translate: t
|
|
6615
|
+
});
|
|
5465
6616
|
const root = element("div", "dsh-mobile-control");
|
|
5466
6617
|
root.lang = locale;
|
|
5467
6618
|
const panel = element("section", "dsh-mobile-control__panel");
|
|
@@ -6274,6 +7425,9 @@ window.__ModuleLoader__.load({
|
|
|
6274
7425
|
caddyModeManagedLabel.append(caddyModeManaged, caddyManagedText);
|
|
6275
7426
|
caddyModeExternalLabel.append(caddyModeExternal, caddyExternalText);
|
|
6276
7427
|
caddyModeRow.append(caddyModeLegend, caddyModeExternalLabel, caddyModeManagedLabel);
|
|
7428
|
+
const caddyModePreviewNote = element("p", "dsh-mobile-control__component-note");
|
|
7429
|
+
caddyModePreviewNote.textContent = t("caddyModePreviewNote");
|
|
7430
|
+
caddyModePreviewNote.hidden = true;
|
|
6277
7431
|
const caddyForm = element("div", "dsh-mobile-control__caddy-form");
|
|
6278
7432
|
caddyForm.hidden = true;
|
|
6279
7433
|
const caddyFields = element("div", "dsh-mobile-control__origin-fields");
|
|
@@ -6341,7 +7495,7 @@ window.__ModuleLoader__.load({
|
|
|
6341
7495
|
const caddyActions = element("div", "dsh-mobile-control__actions dsh-mobile-control__caddy-actions");
|
|
6342
7496
|
caddyActions.append(caddyInstall, caddySave, caddyPurgeButton);
|
|
6343
7497
|
caddyForm.append(caddyFields, caddyInstallNote, caddyStatus, caddyActions, caddyFeedback);
|
|
6344
|
-
originSetup.append(originSetupTitle, caddyModeRow, originHelp, originWarning, originFields, originFeedback, originFormActions, caddyForm, originBackend);
|
|
7498
|
+
originSetup.append(originSetupTitle, caddyModeRow, caddyModePreviewNote, originHelp, originWarning, originFields, originFeedback, originFormActions, caddyForm, originBackend);
|
|
6345
7499
|
const tailscaleInfo = element("details", "dsh-mobile-control__details");
|
|
6346
7500
|
const tailscaleInfoSummary = element("summary");
|
|
6347
7501
|
tailscaleInfoSummary.textContent = t("tailscaleHelp");
|
|
@@ -6670,7 +7824,7 @@ window.__ModuleLoader__.load({
|
|
|
6670
7824
|
lanView.append(lanSetup, access, qrBox, status, extensionStatus, actions, manageRow, devicePanel, trustedNetworks);
|
|
6671
7825
|
remoteView.append(remoteIntro, httpFrameWarning, providerSection, remoteWorkspace);
|
|
6672
7826
|
diagnosticsView.append(diagnosticsIntro, diagnosticsSummary, diagnosticsToolbar, diagnosticsFeedback, diagnosticsChecks, wsPathsSection, diagnosticsDetails);
|
|
6673
|
-
panel.append(header, releaseNotice, updateCard, appDownload, switcher, lanView, remoteView, diagnosticsView);
|
|
7827
|
+
panel.append(header, releaseNotice, updateCard, appDownload, extensionRecovery.section, switcher, lanView, remoteView, diagnosticsView);
|
|
6674
7828
|
root.append(panel);
|
|
6675
7829
|
document.body.append(root);
|
|
6676
7830
|
let running = false;
|
|
@@ -6790,6 +7944,7 @@ window.__ModuleLoader__.load({
|
|
|
6790
7944
|
providerInfoButton.setAttribute("aria-expanded", "false");
|
|
6791
7945
|
});
|
|
6792
7946
|
const selectView = (view) => {
|
|
7947
|
+
if (!panel.hidden) extensionRecovery.refresh();
|
|
6793
7948
|
if (view !== "diagnostics") previousAccessView = view;
|
|
6794
7949
|
lanView.hidden = view !== "lan";
|
|
6795
7950
|
remoteView.hidden = view !== "remote";
|
|
@@ -6820,6 +7975,7 @@ window.__ModuleLoader__.load({
|
|
|
6820
7975
|
});
|
|
6821
7976
|
const setOpen = (open) => {
|
|
6822
7977
|
panel.hidden = !open;
|
|
7978
|
+
if (open) extensionRecovery.refresh();
|
|
6823
7979
|
for (const trigger of document.querySelectorAll(".dsh-mobile-control__trigger")) trigger.setAttribute("aria-expanded", String(open));
|
|
6824
7980
|
};
|
|
6825
7981
|
const render = (data) => {
|
|
@@ -7042,6 +8198,7 @@ window.__ModuleLoader__.load({
|
|
|
7042
8198
|
const cloudflared = remoteProvider === "cloudflared";
|
|
7043
8199
|
const frp = remoteProvider === "frp";
|
|
7044
8200
|
const origin = remoteProvider === "origin";
|
|
8201
|
+
const managedOrigin = origin && data.originMode === "managed";
|
|
7045
8202
|
const tailscale = remoteProvider === "tailscale";
|
|
7046
8203
|
tailscaleChoice.classList.toggle("is-selected", tailscale);
|
|
7047
8204
|
cpolarChoice.classList.toggle("is-selected", cpolar);
|
|
@@ -7056,7 +8213,7 @@ window.__ModuleLoader__.load({
|
|
|
7056
8213
|
cloudflaredChoice.tabIndex = cloudflared ? 0 : -1;
|
|
7057
8214
|
frpChoice.setAttribute("aria-pressed", String(frp));
|
|
7058
8215
|
originChoice.setAttribute("aria-pressed", String(origin));
|
|
7059
|
-
providerSetupName.textContent = cpolar ? "cpolar" : cloudflared ? "cloudflared" : frp ? t("frpName") : origin ? t("originName") : "Tailscale Funnel";
|
|
8216
|
+
providerSetupName.textContent = cpolar ? "cpolar" : cloudflared ? "cloudflared" : frp ? t("frpName") : origin ? t(managedOrigin ? "caddyName" : "originName") : "Tailscale Funnel";
|
|
7060
8217
|
tailscaleChoice.disabled = remoteProviderBusy;
|
|
7061
8218
|
cpolarChoice.disabled = remoteProviderBusy;
|
|
7062
8219
|
cloudflaredChoice.disabled = remoteProviderBusy;
|
|
@@ -7251,6 +8408,7 @@ window.__ModuleLoader__.load({
|
|
|
7251
8408
|
const caddySupported = caddyComponentInfo !== void 0 && caddyComponentInfo.supported === true;
|
|
7252
8409
|
if (!caddyModePreviewDirty) caddyModeManagedSelected = data.originMode === "managed";
|
|
7253
8410
|
const showManaged = origin && caddyModeManagedSelected;
|
|
8411
|
+
caddyModePreviewNote.hidden = !origin || !caddyModePreviewDirty || caddyModeManagedSelected === managedOrigin;
|
|
7254
8412
|
const caddyConfiguration = typeof data.caddyConfiguration === "object" && data.caddyConfiguration !== null ? data.caddyConfiguration : {};
|
|
7255
8413
|
const caddyState = typeof data.caddyState === "object" && data.caddyState !== null ? data.caddyState : {};
|
|
7256
8414
|
caddyModeRow.hidden = !origin;
|
|
@@ -7284,7 +8442,6 @@ window.__ModuleLoader__.load({
|
|
|
7284
8442
|
caddySecretKey,
|
|
7285
8443
|
caddyPort
|
|
7286
8444
|
]) input.disabled = remoteProviderBusy || !caddySupported;
|
|
7287
|
-
selfHostedBadge.textContent = origin ? originListening ? t("originBackendReady") : t("advanced") : frpConfigured && frpInstalled ? t("ready") : t("advanced");
|
|
7288
8445
|
const state = typeof data.state === "string" ? data.state : "error";
|
|
7289
8446
|
const errorCode = typeof data.errorCode === "string" ? data.errorCode : "";
|
|
7290
8447
|
const remoteOrigin = typeof data.origin === "string" ? data.origin : "";
|
|
@@ -7297,10 +8454,11 @@ window.__ModuleLoader__.load({
|
|
|
7297
8454
|
}[errorCode] ?? "";
|
|
7298
8455
|
const needsFunnelSetup = state === "error" && remoteSetupUrl !== "";
|
|
7299
8456
|
remoteReady = remoteRunning && state === "ready" && remoteOrigin !== "";
|
|
8457
|
+
selfHostedBadge.textContent = origin ? managedOrigin ? t(remoteReady ? "ready" : "advanced") : originListening ? t("originBackendReady") : t("advanced") : frpConfigured && frpInstalled ? t("ready") : t("advanced");
|
|
7300
8458
|
remoteStateBadge.classList.toggle("is-ready", remoteReady);
|
|
7301
8459
|
remoteStateBadge.classList.toggle("is-busy", state === "starting" || state === "connecting" || state === "needs-login");
|
|
7302
8460
|
remoteStateBadge.classList.toggle("is-attention", state === "error" || state === "unavailable");
|
|
7303
|
-
remoteStateBadge.textContent = remoteReady ? origin ? t("originBackendReady") : t("ready") : state === "starting" || state === "connecting" || state === "needs-login" ? t("remoteStateConnecting") : state === "error" || state === "unavailable" ? t("remoteStateAttention") : t("remoteStateOff");
|
|
8461
|
+
remoteStateBadge.textContent = remoteReady ? origin && !managedOrigin ? t("originBackendReady") : t("ready") : state === "starting" || state === "connecting" || state === "needs-login" ? t("remoteStateConnecting") : state === "error" || state === "unavailable" ? t("remoteStateAttention") : t("remoteStateOff");
|
|
7304
8462
|
remoteAccess.hidden = !remoteReady;
|
|
7305
8463
|
remoteAccessLink.href = remoteOrigin;
|
|
7306
8464
|
remoteAccessLink.textContent = remoteOrigin;
|
|
@@ -7312,7 +8470,7 @@ window.__ModuleLoader__.load({
|
|
|
7312
8470
|
starting: cpolar ? t("remoteStartingCpolar") : cloudflared ? t("remoteStartingCloudflared") : frp ? t("remoteStartingFrp") : origin ? t("originStarting") : t("remoteStartingTailscale"),
|
|
7313
8471
|
"needs-login": t("remoteNeedsLogin"),
|
|
7314
8472
|
connecting: cpolar ? t("remoteConnectingCpolar") : cloudflared ? t("remoteConnectingCloudflared") : frp ? t("remoteConnectingFrp") : t("remoteConnectingTailscale"),
|
|
7315
|
-
ready: origin ? t("originReady") : t("remoteReady"),
|
|
8473
|
+
ready: origin ? t(managedOrigin ? "caddyVerified" : "originReady") : t("remoteReady"),
|
|
7316
8474
|
error: t("remoteError")
|
|
7317
8475
|
};
|
|
7318
8476
|
const errorLabels = translatedRemoteErrors();
|
|
@@ -8711,6 +9869,9 @@ window.__ModuleLoader__.load({
|
|
|
8711
9869
|
loadDiagnostics();
|
|
8712
9870
|
});
|
|
8713
9871
|
diagnosticsRun.addEventListener("click", loadDiagnostics);
|
|
9872
|
+
diagnosticsRun.addEventListener("click", () => {
|
|
9873
|
+
extensionRecovery.refresh();
|
|
9874
|
+
});
|
|
8714
9875
|
const closeUpdateCard = () => {
|
|
8715
9876
|
updateCard.hidden = true;
|
|
8716
9877
|
if (pluginUpdateAvailable && pluginLatestVersion !== "") updatePlugin.hidden = !diagnosticsView.hidden;
|
|
@@ -8802,10 +9963,14 @@ window.__ModuleLoader__.load({
|
|
|
8802
9963
|
if (!diagnosticsView.hidden) renderWsBlocked(wsPathsBlocked);
|
|
8803
9964
|
}, () => {});
|
|
8804
9965
|
};
|
|
8805
|
-
const wsBlockedPoll = window.setInterval(
|
|
9966
|
+
const wsBlockedPoll = window.setInterval(() => {
|
|
9967
|
+
pollWsBlocked();
|
|
9968
|
+
if (!panel.hidden) extensionRecovery.refresh();
|
|
9969
|
+
}, 2e4);
|
|
8806
9970
|
return {
|
|
8807
9971
|
remove: () => {
|
|
8808
9972
|
lifecycle.abort();
|
|
9973
|
+
extensionRecovery.dispose();
|
|
8809
9974
|
window.clearInterval(remotePoll);
|
|
8810
9975
|
window.clearInterval(wsBlockedPoll);
|
|
8811
9976
|
window.removeEventListener("focus", retryAfterSetup);
|
|
@@ -9352,6 +10517,7 @@ window.__ModuleLoader__.load({
|
|
|
9352
10517
|
const activations = new PerIdActivationLifecycle();
|
|
9353
10518
|
const styleNodes = /* @__PURE__ */ new Map();
|
|
9354
10519
|
const styleEtags = /* @__PURE__ */ new Map();
|
|
10520
|
+
const scriptEtags = /* @__PURE__ */ new Map();
|
|
9355
10521
|
const scriptDigests = /* @__PURE__ */ new Map();
|
|
9356
10522
|
const activeHostGenerations = /* @__PURE__ */ new Map();
|
|
9357
10523
|
const activationKeys = /* @__PURE__ */ new Map();
|
|
@@ -9412,30 +10578,7 @@ window.__ModuleLoader__.load({
|
|
|
9412
10578
|
const abortReason = () => signal.reason ?? new DOMException("mobile extension disposed", "AbortError");
|
|
9413
10579
|
if (signal.aborted) throw abortReason();
|
|
9414
10580
|
const bridge = window.__DSH_MOBILE_NATIVE__;
|
|
9415
|
-
if (bridge !== void 0) return
|
|
9416
|
-
let settled = false;
|
|
9417
|
-
const finish = (callback) => {
|
|
9418
|
-
if (settled) return;
|
|
9419
|
-
settled = true;
|
|
9420
|
-
signal.removeEventListener("abort", onAbort);
|
|
9421
|
-
callback();
|
|
9422
|
-
};
|
|
9423
|
-
const onAbort = () => {
|
|
9424
|
-
finish(() => {
|
|
9425
|
-
reject(abortReason());
|
|
9426
|
-
});
|
|
9427
|
-
};
|
|
9428
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
9429
|
-
Promise.resolve().then(() => bridge.invoke(action, input)).then((value) => {
|
|
9430
|
-
finish(() => {
|
|
9431
|
-
resolve(materializeNativeFile(value));
|
|
9432
|
-
});
|
|
9433
|
-
}, (error) => {
|
|
9434
|
-
finish(() => {
|
|
9435
|
-
reject(error);
|
|
9436
|
-
});
|
|
9437
|
-
});
|
|
9438
|
-
});
|
|
10581
|
+
if (bridge !== void 0) return invokeBoundNativeCapability(bridge, () => window.__DSH_MOBILE_NATIVE__, action, input, signal).then(materializeNativeFile);
|
|
9439
10582
|
if (action === "share" && typeof navigator.share === "function") {
|
|
9440
10583
|
await navigator.share(input ?? {});
|
|
9441
10584
|
return { ok: true };
|
|
@@ -9755,6 +10898,7 @@ window.__ModuleLoader__.load({
|
|
|
9755
10898
|
styleNodes.get(id)?.remove();
|
|
9756
10899
|
styleNodes.delete(id);
|
|
9757
10900
|
styleEtags.delete(id);
|
|
10901
|
+
scriptEtags.delete(id);
|
|
9758
10902
|
scriptDigests.delete(id);
|
|
9759
10903
|
activeHostGenerations.delete(id);
|
|
9760
10904
|
activationKeys.delete(id);
|
|
@@ -9763,6 +10907,7 @@ window.__ModuleLoader__.load({
|
|
|
9763
10907
|
const managedManifestIdSources = () => [
|
|
9764
10908
|
styleNodes.keys(),
|
|
9765
10909
|
styleEtags.keys(),
|
|
10910
|
+
scriptEtags.keys(),
|
|
9766
10911
|
scriptDigests.keys(),
|
|
9767
10912
|
managedDefinitionIds
|
|
9768
10913
|
];
|
|
@@ -9856,12 +11001,13 @@ window.__ModuleLoader__.load({
|
|
|
9856
11001
|
commitStyle(entry.id, styleChange, pendingCss, pendingStyleEtag);
|
|
9857
11002
|
activations.remove(entry.id);
|
|
9858
11003
|
activeHostGenerations.delete(entry.id);
|
|
11004
|
+
scriptEtags.delete(entry.id);
|
|
9859
11005
|
scriptDigests.delete(entry.id);
|
|
9860
11006
|
if (managedDefinitionIds.delete(entry.id)) definitions.delete(entry.id);
|
|
9861
11007
|
} else {
|
|
9862
11008
|
const scriptHeaders = {};
|
|
9863
|
-
const
|
|
9864
|
-
if (
|
|
11009
|
+
const storedEtag = scriptEtags.get(entry.id);
|
|
11010
|
+
if (storedEtag !== void 0) scriptHeaders["if-none-match"] = storedEtag;
|
|
9865
11011
|
const scriptResponse = await fetch(scriptUrl, {
|
|
9866
11012
|
credentials: "same-origin",
|
|
9867
11013
|
cache: "no-store",
|
|
@@ -9869,8 +11015,10 @@ window.__ModuleLoader__.load({
|
|
|
9869
11015
|
signal
|
|
9870
11016
|
});
|
|
9871
11017
|
let nextDigest;
|
|
11018
|
+
let nextEtag;
|
|
9872
11019
|
if (scriptResponse.status !== 304) {
|
|
9873
11020
|
if (!scriptResponse.ok) throw new Error("mobile extension script failed to load");
|
|
11021
|
+
nextEtag = scriptResponse.headers.get("etag") ?? void 0;
|
|
9874
11022
|
const source = await scriptResponse.text();
|
|
9875
11023
|
if (refreshAborted(signal)) return false;
|
|
9876
11024
|
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(source));
|
|
@@ -9912,6 +11060,10 @@ window.__ModuleLoader__.load({
|
|
|
9912
11060
|
}
|
|
9913
11061
|
managedDefinitionIds.add(entry.id);
|
|
9914
11062
|
if (nextDigest !== void 0) scriptDigests.set(entry.id, nextDigest);
|
|
11063
|
+
if (scriptResponse.status !== 304) {
|
|
11064
|
+
if (nextEtag === void 0 || nextEtag === "") scriptEtags.delete(entry.id);
|
|
11065
|
+
else scriptEtags.set(entry.id, nextEtag);
|
|
11066
|
+
}
|
|
9915
11067
|
}
|
|
9916
11068
|
} catch {
|
|
9917
11069
|
if (evaluatedDefinition) {
|
|
@@ -10051,7 +11203,7 @@ window.__ModuleLoader__.load({
|
|
|
10051
11203
|
.dsh-mobile-control__origin-modes{min-width:0;margin:0 0 14px;padding:0;border:0}.dsh-mobile-control__origin-modes legend{margin:0 0 8px;padding:0;color:var(--dsw-alias-label-secondary,#606873);font:600 12px/1.4 system-ui}.dsh-mobile-control__origin-mode{display:flex;box-sizing:border-box;min-height:44px;align-items:center;gap:10px;padding:10px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:10px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);font:12px/1.5 system-ui;cursor:pointer}.dsh-mobile-control__origin-mode+.dsh-mobile-control__origin-mode{margin-top:8px}.dsh-mobile-control__origin-mode:has(input:checked){border-color:var(--dsw-alias-label-primary-bluish,#2563eb)}.dsh-mobile-control__origin-mode:focus-within{outline:2px solid var(--dsw-alias-state-business-primary,#2563eb);outline-offset:2px}.dsh-mobile-control__origin-mode:has(input:disabled){cursor:wait;opacity:.55}.dsh-mobile-control__origin-mode input[type=radio]{flex:0 0 18px;box-sizing:border-box;width:18px;height:18px;min-height:18px;margin:0;padding:0;accent-color:var(--dsw-alias-label-primary-bluish,#2563eb)}.dsh-mobile-control__origin-mode span{min-width:0;overflow-wrap:anywhere}
|
|
10052
11204
|
/* Base the columns on the card width, not the desktop viewport behind the popup. */
|
|
10053
11205
|
.dsh-mobile-control__origin-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,140px),1fr));gap:12px}.dsh-mobile-control__origin-fields>.dsh-mobile-control__field{min-width:0;grid-column:auto;font-size:12px}.dsh-mobile-control__origin-fields>.dsh-mobile-control__field--full{grid-column:1/-1}.dsh-mobile-control__origin-fields select{box-sizing:border-box;width:100%;min-height:44px;padding:9px 10px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);font:16px/1.4 system-ui}.dsh-mobile-control__origin-fields select:disabled{cursor:not-allowed;opacity:.55}.dsh-mobile-control__caddy-form{display:grid;gap:12px}.dsh-mobile-control__caddy-form>.dsh-mobile-control__component-note,.dsh-mobile-control__caddy-form>.dsh-mobile-control__component-status,.dsh-mobile-control__caddy-form>.dsh-mobile-control__origin-feedback{margin:0;overflow-wrap:anywhere}.dsh-mobile-control__origin-setup .dsh-mobile-control__actions.dsh-mobile-control__caddy-actions{grid-template-columns:minmax(0,1fr);margin:0}.dsh-mobile-control__caddy-actions button{box-sizing:border-box;min-width:0;min-height:44px;padding:10px 12px;border-radius:10px;font:600 12px/1.4 system-ui;cursor:pointer}.dsh-mobile-control__origin-warning{margin:12px 0;padding:10px 12px;border-left:3px solid var(--dsw-alias-state-warn-primary,#a56810);border-radius:4px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-primary,#384152);font-size:12px;line-height:1.6}.dsh-mobile-control__origin-feedback{font-size:12px;line-height:1.6;overflow-wrap:anywhere}.dsh-mobile-control__origin-feedback.is-error{color:var(--dsw-alias-state-error-primary,#bc3030)}.dsh-mobile-control__origin-fields input[aria-invalid=true]{border-color:var(--dsw-alias-state-error-primary,#bc3030)}.dsh-mobile-control__origin-backend{display:flex;min-width:0;flex-direction:column;align-items:stretch;gap:8px;margin-top:12px;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l2,#dbe1e8)}.dsh-mobile-control__origin-backend button{align-self:flex-start;min-height:44px}.dsh-mobile-control__self-hosted-body .is-origin{margin-top:8px}
|
|
10054
|
-
|
|
11206
|
+
` + EXTENSION_RECOVERY_STYLES;
|
|
10055
11207
|
/** Mount the desktop control or mobile feature enhancements. */
|
|
10056
11208
|
function apply(ctx) {
|
|
10057
11209
|
ctx.effect(() => {
|
|
@@ -10086,6 +11238,11 @@ window.__ModuleLoader__.load({
|
|
|
10086
11238
|
preference: localFont.preference,
|
|
10087
11239
|
locale: selectedMobileControlLocale()
|
|
10088
11240
|
}));
|
|
11241
|
+
const removeDisplayScale = ctx.slots.register({
|
|
11242
|
+
name: "settings.general.item",
|
|
11243
|
+
id: "dsh-mobile-display-scale",
|
|
11244
|
+
order: 12
|
|
11245
|
+
}, () => (0, react.createElement)(MobileDisplayScaleRow, { locale: selectedMobileControlLocale() }));
|
|
10089
11246
|
const removeSwitchComputer = ctx.slots.register({
|
|
10090
11247
|
name: "settings.general.item",
|
|
10091
11248
|
id: "dsh-mobile-switch-computer",
|
|
@@ -10099,6 +11256,7 @@ window.__ModuleLoader__.load({
|
|
|
10099
11256
|
return () => {
|
|
10100
11257
|
removeTaskNotifications();
|
|
10101
11258
|
removeSwitchComputer();
|
|
11259
|
+
removeDisplayScale();
|
|
10102
11260
|
removeFontSize();
|
|
10103
11261
|
};
|
|
10104
11262
|
});
|