dsh-mobile 0.6.3 → 0.6.5
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 +33 -0
- package/CONTRIBUTORS.md +13 -3
- package/README.en.md +14 -7
- package/README.md +14 -7
- package/THIRD_PARTY_NOTICES.md +29 -1
- package/apps/mobile/README.md +14 -6
- package/apps/mobile/release.json +3 -3
- package/cordis.patch.yml +5 -2
- package/docs/ATTACH_EXISTING_FRPS.en.md +1 -1
- package/docs/ATTACH_EXISTING_FRPS.md +1 -1
- package/docs/CLIENT_MODULES.en.md +14 -4
- package/docs/CLIENT_MODULES.md +14 -4
- package/docs/MOBILE_COMPOSER_TOOLBAR.en.md +8 -0
- package/docs/MOBILE_COMPOSER_TOOLBAR.md +8 -0
- package/docs/README.md +1 -0
- package/docs/SELF_HOSTED_FRP.en.md +3 -1
- package/docs/SELF_HOSTED_FRP.md +3 -1
- package/docs/SKIN_COMPATIBILITY.en.md +25 -0
- package/docs/SKIN_COMPATIBILITY.md +25 -0
- package/docs/SLOW_CONNECTIONS.en.md +6 -0
- package/docs/SLOW_CONNECTIONS.md +6 -0
- package/lib/cli.js +139 -19
- package/lib/cli.js.map +1 -1
- package/lib/client.js +1369 -240
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +89 -5
- package/lib/index.mjs +6944 -6203
- package/lib/index.mjs.map +1 -1
- package/lib/mobile-layout.js +7 -1
- package/lib/mobile-layout.js.map +1 -1
- package/package.json +8 -1
- package/packages/question-fixes/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -598,7 +598,12 @@ window.__ModuleLoader__.load({
|
|
|
598
598
|
cpolarDownloadTimeout: "The cpolar download timed out. Check this computer’s network or proxy, then retry.",
|
|
599
599
|
cpolarDownloadHttp: "The official cpolar download returned HTTP {status}. Retry later or check the official download page.",
|
|
600
600
|
cpolarIntegrityFailed: "The cpolar download did not match the pinned build, so it was not installed. Retry later or update DSH Mobile; do not bypass verification.",
|
|
601
|
-
cpolarExtractFailed: "The cpolar download
|
|
601
|
+
cpolarExtractFailed: "The cpolar download was verified, but extraction failed. Check the extraction stage in the DSH log; the message alone does not establish a security-software block.",
|
|
602
|
+
cpolarExtractToolsFailed: "A required Windows extraction tool is unavailable. Check system PowerShell, tar and expand; no administrator startup or service installation is required.",
|
|
603
|
+
cpolarExtractArchiveFailed: "Could not extract the verified cpolar ZIP or locate its MSI. Check the ZIP extraction stage in the DSH log.",
|
|
604
|
+
cpolarExtractDatabaseFailed: "Could not read the cpolar MSI database or its embedded cabinet. Check the MSI read stage in the DSH log; do not install the MSI as a workaround.",
|
|
605
|
+
cpolarExtractCabinetFailed: "Could not extract the cpolar executable from its cabinet. Check the cabinet extraction stage and private-directory permissions.",
|
|
606
|
+
cpolarExtractFileFailed: "Could not read or copy the extracted cpolar executable. Check private-directory permissions and disk space; your existing component is retained.",
|
|
602
607
|
cpolarStorageFailed: "Could not write the cpolar component in DSH Mobile’s private directory. Check disk space, permissions, and security software, then retry."
|
|
603
608
|
});
|
|
604
609
|
Object.assign(MOBILE_CONTROL_MESSAGES.it, {
|
|
@@ -623,7 +628,12 @@ window.__ModuleLoader__.load({
|
|
|
623
628
|
cpolarDownloadTimeout: "Download di cpolar scaduto. Controlla rete o proxy del computer e riprova.",
|
|
624
629
|
cpolarDownloadHttp: "Il download ufficiale di cpolar ha restituito HTTP {status}. Riprova più tardi o controlla la pagina ufficiale.",
|
|
625
630
|
cpolarIntegrityFailed: "Il pacchetto cpolar non corrisponde alla versione verificata e non è stato installato. Riprova più tardi o aggiorna DSH Mobile; non aggirare la verifica.",
|
|
626
|
-
cpolarExtractFailed: "Download di cpolar
|
|
631
|
+
cpolarExtractFailed: "Download di cpolar verificato, ma estrazione non riuscita. Controlla la fase nel log DSH; il messaggio non dimostra un blocco del software di sicurezza.",
|
|
632
|
+
cpolarExtractToolsFailed: "Uno strumento di estrazione Windows richiesto non è disponibile. Controlla PowerShell di sistema, tar ed expand; non serve avviare come amministratore o installare un servizio.",
|
|
633
|
+
cpolarExtractArchiveFailed: "Impossibile estrarre lo ZIP cpolar verificato o trovare il suo MSI. Controlla la fase ZIP nel log DSH.",
|
|
634
|
+
cpolarExtractDatabaseFailed: "Impossibile leggere il database MSI cpolar o il cabinet incorporato. Controlla la fase di lettura MSI nel log DSH; non installare il MSI per aggirare il problema.",
|
|
635
|
+
cpolarExtractCabinetFailed: "Impossibile estrarre il programma cpolar dal cabinet. Controlla la fase cabinet e i permessi della directory privata.",
|
|
636
|
+
cpolarExtractFileFailed: "Impossibile leggere o copiare il programma cpolar estratto. Controlla permessi e spazio nella directory privata; il componente esistente è conservato.",
|
|
627
637
|
cpolarStorageFailed: "Impossibile scrivere cpolar nella directory privata di DSH Mobile. Controlla spazio, permessi e software di sicurezza, poi riprova."
|
|
628
638
|
});
|
|
629
639
|
Object.assign(MOBILE_CONTROL_MESSAGES.zh, {
|
|
@@ -648,7 +658,12 @@ window.__ModuleLoader__.load({
|
|
|
648
658
|
cpolarDownloadTimeout: "下载 cpolar 组件超时。请检查这台电脑的网络或代理,然后重试。",
|
|
649
659
|
cpolarDownloadHttp: "cpolar 官网下载返回 HTTP {status}。请稍后重试,或检查官网下载页。",
|
|
650
660
|
cpolarIntegrityFailed: "下载的 cpolar 组件未通过固定版本校验,因此没有安装。请稍后重试或更新 DSH Mobile,不要跳过校验。",
|
|
651
|
-
cpolarExtractFailed: "cpolar
|
|
661
|
+
cpolarExtractFailed: "cpolar 下载已校验,但提取失败。请查看 DSH 日志中的具体阶段,不能仅凭此提示认定安全软件拦截。",
|
|
662
|
+
cpolarExtractToolsFailed: "所需的 Windows 提取工具不可用。请检查系统 PowerShell、tar 和 expand;无需以管理员启动 DSH 或安装系统服务。",
|
|
663
|
+
cpolarExtractArchiveFailed: "无法展开已校验的 cpolar ZIP 或找到其中的 MSI。请查看 DSH 日志中的 ZIP 提取阶段。",
|
|
664
|
+
cpolarExtractDatabaseFailed: "无法读取 cpolar MSI 数据库或内嵌 CAB。请查看 DSH 日志中的 MSI 读取阶段,不要用运行 MSI 安装程序绕过此问题。",
|
|
665
|
+
cpolarExtractCabinetFailed: "无法从 CAB 中提取 cpolar 程序。请查看 CAB 提取阶段及私有目录权限。",
|
|
666
|
+
cpolarExtractFileFailed: "无法读取或复制已提取的 cpolar 程序。请检查私有目录权限和磁盘空间;已有组件不会被替换。",
|
|
652
667
|
cpolarStorageFailed: "无法把 cpolar 写入 DSH Mobile 私有目录。请检查磁盘空间、目录权限和安全软件,然后重试。"
|
|
653
668
|
});
|
|
654
669
|
Object.assign(MOBILE_CONTROL_MESSAGES.en, {
|
|
@@ -2478,19 +2493,396 @@ window.__ModuleLoader__.load({
|
|
|
2478
2493
|
function createFrpAttachTemplate(settings, options = {}) {
|
|
2479
2494
|
return createFrpAttachTemplateParts(settings, options).text;
|
|
2480
2495
|
}
|
|
2481
|
-
|
|
2482
|
-
|
|
2496
|
+
//#endregion
|
|
2497
|
+
//#region node_modules/@noble/hashes/esm/utils.js
|
|
2498
|
+
/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
2499
|
+
/** Checks if something is Uint8Array. Be careful: nodejs Buffer will return true. */
|
|
2500
|
+
function isBytes(a) {
|
|
2501
|
+
return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
|
|
2502
|
+
}
|
|
2503
|
+
/** Asserts something is Uint8Array. */
|
|
2504
|
+
function abytes(b, ...lengths) {
|
|
2505
|
+
if (!isBytes(b)) throw new Error("Uint8Array expected");
|
|
2506
|
+
if (lengths.length > 0 && !lengths.includes(b.length)) throw new Error("Uint8Array expected of length " + lengths + ", got length=" + b.length);
|
|
2507
|
+
}
|
|
2508
|
+
/** Asserts a hash instance has not been destroyed / finished */
|
|
2509
|
+
function aexists(instance, checkFinished = true) {
|
|
2510
|
+
if (instance.destroyed) throw new Error("Hash instance has been destroyed");
|
|
2511
|
+
if (checkFinished && instance.finished) throw new Error("Hash#digest() has already been called");
|
|
2512
|
+
}
|
|
2513
|
+
/** Asserts output is properly-sized byte array */
|
|
2514
|
+
function aoutput(out, instance) {
|
|
2515
|
+
abytes(out);
|
|
2516
|
+
const min = instance.outputLen;
|
|
2517
|
+
if (out.length < min) throw new Error("digestInto() expects output buffer of length at least " + min);
|
|
2518
|
+
}
|
|
2519
|
+
/** Zeroize a byte array. Warning: JS provides no guarantees. */
|
|
2520
|
+
function clean(...arrays) {
|
|
2521
|
+
for (let i = 0; i < arrays.length; i++) arrays[i].fill(0);
|
|
2522
|
+
}
|
|
2523
|
+
/** Create DataView of an array for easy byte-level manipulation. */
|
|
2524
|
+
function createView(arr) {
|
|
2525
|
+
return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
2526
|
+
}
|
|
2527
|
+
/** The rotate right (circular right shift) operation for uint32 */
|
|
2528
|
+
function rotr(word, shift) {
|
|
2529
|
+
return word << 32 - shift | word >>> shift;
|
|
2530
|
+
}
|
|
2531
|
+
const hasHexBuiltin = /* @__PURE__ */ (() => typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex === "function")();
|
|
2532
|
+
const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
|
|
2533
|
+
/**
|
|
2534
|
+
* Convert byte array to hex string. Uses built-in function, when available.
|
|
2535
|
+
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
|
|
2536
|
+
*/
|
|
2537
|
+
function bytesToHex(bytes) {
|
|
2538
|
+
abytes(bytes);
|
|
2539
|
+
if (hasHexBuiltin) return bytes.toHex();
|
|
2540
|
+
let hex = "";
|
|
2541
|
+
for (let i = 0; i < bytes.length; i++) hex += hexes[bytes[i]];
|
|
2542
|
+
return hex;
|
|
2543
|
+
}
|
|
2544
|
+
/**
|
|
2545
|
+
* Converts string to bytes using UTF8 encoding.
|
|
2546
|
+
* @example utf8ToBytes('abc') // Uint8Array.from([97, 98, 99])
|
|
2547
|
+
*/
|
|
2548
|
+
function utf8ToBytes(str) {
|
|
2549
|
+
if (typeof str !== "string") throw new Error("string expected");
|
|
2550
|
+
return new Uint8Array(new TextEncoder().encode(str));
|
|
2551
|
+
}
|
|
2552
|
+
/**
|
|
2553
|
+
* Normalizes (non-hex) string or Uint8Array to Uint8Array.
|
|
2554
|
+
* Warning: when Uint8Array is passed, it would NOT get copied.
|
|
2555
|
+
* Keep in mind for future mutable operations.
|
|
2556
|
+
*/
|
|
2557
|
+
function toBytes(data) {
|
|
2558
|
+
if (typeof data === "string") data = utf8ToBytes(data);
|
|
2559
|
+
abytes(data);
|
|
2560
|
+
return data;
|
|
2561
|
+
}
|
|
2562
|
+
/** For runtime check if class implements interface */
|
|
2563
|
+
var Hash = class {};
|
|
2564
|
+
/** Wraps hash function, creating an interface on top of it */
|
|
2565
|
+
function createHasher(hashCons) {
|
|
2566
|
+
const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
|
|
2567
|
+
const tmp = hashCons();
|
|
2568
|
+
hashC.outputLen = tmp.outputLen;
|
|
2569
|
+
hashC.blockLen = tmp.blockLen;
|
|
2570
|
+
hashC.create = () => hashCons();
|
|
2571
|
+
return hashC;
|
|
2572
|
+
}
|
|
2573
|
+
//#endregion
|
|
2574
|
+
//#region node_modules/@noble/hashes/esm/_md.js
|
|
2575
|
+
/**
|
|
2576
|
+
* Internal Merkle-Damgard hash utils.
|
|
2577
|
+
* @module
|
|
2578
|
+
*/
|
|
2579
|
+
/** Polyfill for Safari 14. https://caniuse.com/mdn-javascript_builtins_dataview_setbiguint64 */
|
|
2580
|
+
function setBigUint64(view, byteOffset, value, isLE) {
|
|
2581
|
+
if (typeof view.setBigUint64 === "function") return view.setBigUint64(byteOffset, value, isLE);
|
|
2582
|
+
const _32n = BigInt(32);
|
|
2583
|
+
const _u32_max = BigInt(4294967295);
|
|
2584
|
+
const wh = Number(value >> _32n & _u32_max);
|
|
2585
|
+
const wl = Number(value & _u32_max);
|
|
2586
|
+
const h = isLE ? 4 : 0;
|
|
2587
|
+
const l = isLE ? 0 : 4;
|
|
2588
|
+
view.setUint32(byteOffset + h, wh, isLE);
|
|
2589
|
+
view.setUint32(byteOffset + l, wl, isLE);
|
|
2590
|
+
}
|
|
2591
|
+
/** Choice: a ? b : c */
|
|
2592
|
+
function Chi(a, b, c) {
|
|
2593
|
+
return a & b ^ ~a & c;
|
|
2594
|
+
}
|
|
2595
|
+
/** Majority function, true if any two inputs is true. */
|
|
2596
|
+
function Maj(a, b, c) {
|
|
2597
|
+
return a & b ^ a & c ^ b & c;
|
|
2598
|
+
}
|
|
2599
|
+
/**
|
|
2600
|
+
* Merkle-Damgard hash construction base class.
|
|
2601
|
+
* Could be used to create MD5, RIPEMD, SHA1, SHA2.
|
|
2602
|
+
*/
|
|
2603
|
+
var HashMD = class extends Hash {
|
|
2604
|
+
constructor(blockLen, outputLen, padOffset, isLE) {
|
|
2605
|
+
super();
|
|
2606
|
+
this.finished = false;
|
|
2607
|
+
this.length = 0;
|
|
2608
|
+
this.pos = 0;
|
|
2609
|
+
this.destroyed = false;
|
|
2610
|
+
this.blockLen = blockLen;
|
|
2611
|
+
this.outputLen = outputLen;
|
|
2612
|
+
this.padOffset = padOffset;
|
|
2613
|
+
this.isLE = isLE;
|
|
2614
|
+
this.buffer = new Uint8Array(blockLen);
|
|
2615
|
+
this.view = createView(this.buffer);
|
|
2616
|
+
}
|
|
2617
|
+
update(data) {
|
|
2618
|
+
aexists(this);
|
|
2619
|
+
data = toBytes(data);
|
|
2620
|
+
abytes(data);
|
|
2621
|
+
const { view, buffer, blockLen } = this;
|
|
2622
|
+
const len = data.length;
|
|
2623
|
+
for (let pos = 0; pos < len;) {
|
|
2624
|
+
const take = Math.min(blockLen - this.pos, len - pos);
|
|
2625
|
+
if (take === blockLen) {
|
|
2626
|
+
const dataView = createView(data);
|
|
2627
|
+
for (; blockLen <= len - pos; pos += blockLen) this.process(dataView, pos);
|
|
2628
|
+
continue;
|
|
2629
|
+
}
|
|
2630
|
+
buffer.set(data.subarray(pos, pos + take), this.pos);
|
|
2631
|
+
this.pos += take;
|
|
2632
|
+
pos += take;
|
|
2633
|
+
if (this.pos === blockLen) {
|
|
2634
|
+
this.process(view, 0);
|
|
2635
|
+
this.pos = 0;
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
this.length += data.length;
|
|
2639
|
+
this.roundClean();
|
|
2640
|
+
return this;
|
|
2641
|
+
}
|
|
2642
|
+
digestInto(out) {
|
|
2643
|
+
aexists(this);
|
|
2644
|
+
aoutput(out, this);
|
|
2645
|
+
this.finished = true;
|
|
2646
|
+
const { buffer, view, blockLen, isLE } = this;
|
|
2647
|
+
let { pos } = this;
|
|
2648
|
+
buffer[pos++] = 128;
|
|
2649
|
+
clean(this.buffer.subarray(pos));
|
|
2650
|
+
if (this.padOffset > blockLen - pos) {
|
|
2651
|
+
this.process(view, 0);
|
|
2652
|
+
pos = 0;
|
|
2653
|
+
}
|
|
2654
|
+
for (let i = pos; i < blockLen; i++) buffer[i] = 0;
|
|
2655
|
+
setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE);
|
|
2656
|
+
this.process(view, 0);
|
|
2657
|
+
const oview = createView(out);
|
|
2658
|
+
const len = this.outputLen;
|
|
2659
|
+
if (len % 4) throw new Error("_sha2: outputLen should be aligned to 32bit");
|
|
2660
|
+
const outLen = len / 4;
|
|
2661
|
+
const state = this.get();
|
|
2662
|
+
if (outLen > state.length) throw new Error("_sha2: outputLen bigger than state");
|
|
2663
|
+
for (let i = 0; i < outLen; i++) oview.setUint32(4 * i, state[i], isLE);
|
|
2664
|
+
}
|
|
2665
|
+
digest() {
|
|
2666
|
+
const { buffer, outputLen } = this;
|
|
2667
|
+
this.digestInto(buffer);
|
|
2668
|
+
const res = buffer.slice(0, outputLen);
|
|
2669
|
+
this.destroy();
|
|
2670
|
+
return res;
|
|
2671
|
+
}
|
|
2672
|
+
_cloneInto(to) {
|
|
2673
|
+
to || (to = new this.constructor());
|
|
2674
|
+
to.set(...this.get());
|
|
2675
|
+
const { blockLen, buffer, length, finished, destroyed, pos } = this;
|
|
2676
|
+
to.destroyed = destroyed;
|
|
2677
|
+
to.finished = finished;
|
|
2678
|
+
to.length = length;
|
|
2679
|
+
to.pos = pos;
|
|
2680
|
+
if (length % blockLen) to.buffer.set(buffer);
|
|
2681
|
+
return to;
|
|
2682
|
+
}
|
|
2683
|
+
clone() {
|
|
2684
|
+
return this._cloneInto();
|
|
2685
|
+
}
|
|
2686
|
+
};
|
|
2687
|
+
/**
|
|
2688
|
+
* Initial SHA-2 state: fractional parts of square roots of first 16 primes 2..53.
|
|
2689
|
+
* Check out `test/misc/sha2-gen-iv.js` for recomputation guide.
|
|
2690
|
+
*/
|
|
2691
|
+
/** Initial SHA256 state. Bits 0..32 of frac part of sqrt of primes 2..19 */
|
|
2692
|
+
const SHA256_IV = /* @__PURE__ */ Uint32Array.from([
|
|
2693
|
+
1779033703,
|
|
2694
|
+
3144134277,
|
|
2695
|
+
1013904242,
|
|
2696
|
+
2773480762,
|
|
2697
|
+
1359893119,
|
|
2698
|
+
2600822924,
|
|
2699
|
+
528734635,
|
|
2700
|
+
1541459225
|
|
2701
|
+
]);
|
|
2702
|
+
//#endregion
|
|
2703
|
+
//#region node_modules/@noble/hashes/esm/sha2.js
|
|
2704
|
+
/**
|
|
2705
|
+
* SHA2 hash function. A.k.a. sha256, sha384, sha512, sha512_224, sha512_256.
|
|
2706
|
+
* SHA256 is the fastest hash implementable in JS, even faster than Blake3.
|
|
2707
|
+
* Check out [RFC 4634](https://datatracker.ietf.org/doc/html/rfc4634) and
|
|
2708
|
+
* [FIPS 180-4](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf).
|
|
2709
|
+
* @module
|
|
2710
|
+
*/
|
|
2711
|
+
/**
|
|
2712
|
+
* Round constants:
|
|
2713
|
+
* First 32 bits of fractional parts of the cube roots of the first 64 primes 2..311)
|
|
2714
|
+
*/
|
|
2715
|
+
const SHA256_K = /* @__PURE__ */ Uint32Array.from([
|
|
2716
|
+
1116352408,
|
|
2717
|
+
1899447441,
|
|
2718
|
+
3049323471,
|
|
2719
|
+
3921009573,
|
|
2720
|
+
961987163,
|
|
2721
|
+
1508970993,
|
|
2722
|
+
2453635748,
|
|
2723
|
+
2870763221,
|
|
2724
|
+
3624381080,
|
|
2725
|
+
310598401,
|
|
2726
|
+
607225278,
|
|
2727
|
+
1426881987,
|
|
2728
|
+
1925078388,
|
|
2729
|
+
2162078206,
|
|
2730
|
+
2614888103,
|
|
2731
|
+
3248222580,
|
|
2732
|
+
3835390401,
|
|
2733
|
+
4022224774,
|
|
2734
|
+
264347078,
|
|
2735
|
+
604807628,
|
|
2736
|
+
770255983,
|
|
2737
|
+
1249150122,
|
|
2738
|
+
1555081692,
|
|
2739
|
+
1996064986,
|
|
2740
|
+
2554220882,
|
|
2741
|
+
2821834349,
|
|
2742
|
+
2952996808,
|
|
2743
|
+
3210313671,
|
|
2744
|
+
3336571891,
|
|
2745
|
+
3584528711,
|
|
2746
|
+
113926993,
|
|
2747
|
+
338241895,
|
|
2748
|
+
666307205,
|
|
2749
|
+
773529912,
|
|
2750
|
+
1294757372,
|
|
2751
|
+
1396182291,
|
|
2752
|
+
1695183700,
|
|
2753
|
+
1986661051,
|
|
2754
|
+
2177026350,
|
|
2755
|
+
2456956037,
|
|
2756
|
+
2730485921,
|
|
2757
|
+
2820302411,
|
|
2758
|
+
3259730800,
|
|
2759
|
+
3345764771,
|
|
2760
|
+
3516065817,
|
|
2761
|
+
3600352804,
|
|
2762
|
+
4094571909,
|
|
2763
|
+
275423344,
|
|
2764
|
+
430227734,
|
|
2765
|
+
506948616,
|
|
2766
|
+
659060556,
|
|
2767
|
+
883997877,
|
|
2768
|
+
958139571,
|
|
2769
|
+
1322822218,
|
|
2770
|
+
1537002063,
|
|
2771
|
+
1747873779,
|
|
2772
|
+
1955562222,
|
|
2773
|
+
2024104815,
|
|
2774
|
+
2227730452,
|
|
2775
|
+
2361852424,
|
|
2776
|
+
2428436474,
|
|
2777
|
+
2756734187,
|
|
2778
|
+
3204031479,
|
|
2779
|
+
3329325298
|
|
2780
|
+
]);
|
|
2781
|
+
/** Reusable temporary buffer. "W" comes straight from spec. */
|
|
2782
|
+
const SHA256_W = /* @__PURE__ */ new Uint32Array(64);
|
|
2783
|
+
var SHA256 = class extends HashMD {
|
|
2784
|
+
constructor(outputLen = 32) {
|
|
2785
|
+
super(64, outputLen, 8, false);
|
|
2786
|
+
this.A = SHA256_IV[0] | 0;
|
|
2787
|
+
this.B = SHA256_IV[1] | 0;
|
|
2788
|
+
this.C = SHA256_IV[2] | 0;
|
|
2789
|
+
this.D = SHA256_IV[3] | 0;
|
|
2790
|
+
this.E = SHA256_IV[4] | 0;
|
|
2791
|
+
this.F = SHA256_IV[5] | 0;
|
|
2792
|
+
this.G = SHA256_IV[6] | 0;
|
|
2793
|
+
this.H = SHA256_IV[7] | 0;
|
|
2794
|
+
}
|
|
2795
|
+
get() {
|
|
2796
|
+
const { A, B, C, D, E, F, G, H } = this;
|
|
2797
|
+
return [
|
|
2798
|
+
A,
|
|
2799
|
+
B,
|
|
2800
|
+
C,
|
|
2801
|
+
D,
|
|
2802
|
+
E,
|
|
2803
|
+
F,
|
|
2804
|
+
G,
|
|
2805
|
+
H
|
|
2806
|
+
];
|
|
2807
|
+
}
|
|
2808
|
+
set(A, B, C, D, E, F, G, H) {
|
|
2809
|
+
this.A = A | 0;
|
|
2810
|
+
this.B = B | 0;
|
|
2811
|
+
this.C = C | 0;
|
|
2812
|
+
this.D = D | 0;
|
|
2813
|
+
this.E = E | 0;
|
|
2814
|
+
this.F = F | 0;
|
|
2815
|
+
this.G = G | 0;
|
|
2816
|
+
this.H = H | 0;
|
|
2817
|
+
}
|
|
2818
|
+
process(view, offset) {
|
|
2819
|
+
for (let i = 0; i < 16; i++, offset += 4) SHA256_W[i] = view.getUint32(offset, false);
|
|
2820
|
+
for (let i = 16; i < 64; i++) {
|
|
2821
|
+
const W15 = SHA256_W[i - 15];
|
|
2822
|
+
const W2 = SHA256_W[i - 2];
|
|
2823
|
+
const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ W15 >>> 3;
|
|
2824
|
+
const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ W2 >>> 10;
|
|
2825
|
+
SHA256_W[i] = s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16] | 0;
|
|
2826
|
+
}
|
|
2827
|
+
let { A, B, C, D, E, F, G, H } = this;
|
|
2828
|
+
for (let i = 0; i < 64; i++) {
|
|
2829
|
+
const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);
|
|
2830
|
+
const T1 = H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i] | 0;
|
|
2831
|
+
const T2 = (rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22)) + Maj(A, B, C) | 0;
|
|
2832
|
+
H = G;
|
|
2833
|
+
G = F;
|
|
2834
|
+
F = E;
|
|
2835
|
+
E = D + T1 | 0;
|
|
2836
|
+
D = C;
|
|
2837
|
+
C = B;
|
|
2838
|
+
B = A;
|
|
2839
|
+
A = T1 + T2 | 0;
|
|
2840
|
+
}
|
|
2841
|
+
A = A + this.A | 0;
|
|
2842
|
+
B = B + this.B | 0;
|
|
2843
|
+
C = C + this.C | 0;
|
|
2844
|
+
D = D + this.D | 0;
|
|
2845
|
+
E = E + this.E | 0;
|
|
2846
|
+
F = F + this.F | 0;
|
|
2847
|
+
G = G + this.G | 0;
|
|
2848
|
+
H = H + this.H | 0;
|
|
2849
|
+
this.set(A, B, C, D, E, F, G, H);
|
|
2850
|
+
}
|
|
2851
|
+
roundClean() {
|
|
2852
|
+
clean(SHA256_W);
|
|
2853
|
+
}
|
|
2854
|
+
destroy() {
|
|
2855
|
+
this.set(0, 0, 0, 0, 0, 0, 0, 0);
|
|
2856
|
+
clean(this.buffer);
|
|
2857
|
+
}
|
|
2858
|
+
};
|
|
2859
|
+
//#endregion
|
|
2860
|
+
//#region node_modules/@noble/hashes/esm/sha256.js
|
|
2861
|
+
/**
|
|
2862
|
+
* SHA2-256 a.k.a. sha256. In JS, it is the fastest hash, even faster than Blake3.
|
|
2863
|
+
*
|
|
2864
|
+
* To break sha256 using birthday attack, attackers need to try 2^128 hashes.
|
|
2865
|
+
* BTC network is doing 2^70 hashes/sec (2^95 hashes/year) as per 2025.
|
|
2866
|
+
*
|
|
2867
|
+
* Check out [FIPS 180-4](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf).
|
|
2868
|
+
* @module
|
|
2869
|
+
* @deprecated
|
|
2870
|
+
*/
|
|
2871
|
+
/** @deprecated Use import from `noble/hashes/sha2` module */
|
|
2872
|
+
const sha256 = /* @__PURE__ */ createHasher(() => new SHA256());
|
|
2873
|
+
Object.freeze({
|
|
2483
2874
|
title: 80,
|
|
2484
2875
|
body: 200,
|
|
2485
2876
|
tag: 64
|
|
2486
2877
|
});
|
|
2487
2878
|
const MAX_SEEN_QUESTION_KEYS = 20;
|
|
2488
|
-
|
|
2489
|
-
|
|
2879
|
+
/** A bounded native identifier derived from the complete, case-sensitive wire ID. */
|
|
2880
|
+
function notificationIdentity(value) {
|
|
2881
|
+
return bytesToHex(sha256(JSON.stringify(value)).subarray(0, 16));
|
|
2490
2882
|
}
|
|
2491
2883
|
/** Stable per-turn tag so concurrent sessions keep separate notifications. */
|
|
2492
2884
|
function taskCompletionTag(sessionId, turn) {
|
|
2493
|
-
return `dsh-task-done-${
|
|
2885
|
+
return `dsh-task-done-${notificationIdentity(sessionId)}-${String(turn)}`;
|
|
2494
2886
|
}
|
|
2495
2887
|
/** Decide whether the current snapshot deserves a system notification. */
|
|
2496
2888
|
var TaskNotifyTracker = class {
|
|
@@ -2581,7 +2973,7 @@ window.__ModuleLoader__.load({
|
|
|
2581
2973
|
}
|
|
2582
2974
|
event(kind, sessionLabel, questionKey) {
|
|
2583
2975
|
const text = this.format(kind, sessionLabel);
|
|
2584
|
-
const tag = kind === "question" && questionKey !== void 0 ? `dsh-task-question-${
|
|
2976
|
+
const tag = kind === "question" && questionKey !== void 0 ? `dsh-task-question-${notificationIdentity(questionKey)}` : "dsh-task-done";
|
|
2585
2977
|
return Object.freeze({
|
|
2586
2978
|
kind,
|
|
2587
2979
|
title: text.title,
|
|
@@ -2666,7 +3058,7 @@ window.__ModuleLoader__.load({
|
|
|
2666
3058
|
if (typeof record.sessionId !== "string" || record.sessionId.trim() === "") return void 0;
|
|
2667
3059
|
const turn = typeof record.turn === "number" && Number.isSafeInteger(record.turn) && record.turn >= 0 ? record.turn : 0;
|
|
2668
3060
|
return Object.freeze({
|
|
2669
|
-
sessionId: record.sessionId
|
|
3061
|
+
sessionId: record.sessionId,
|
|
2670
3062
|
turn
|
|
2671
3063
|
});
|
|
2672
3064
|
}
|
|
@@ -2796,6 +3188,10 @@ window.__ModuleLoader__.load({
|
|
|
2796
3188
|
[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; }
|
|
2797
3189
|
@media (max-width:720px) {
|
|
2798
3190
|
[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; }
|
|
3191
|
+
/* Public slot icon wrappers must reserve the same space as their enlarged
|
|
3192
|
+
child targets. Keep wrapper and tooltip ownership intact; pure switches,
|
|
3193
|
+
dialogs and wrappers without direct non-switch buttons are not selected. */
|
|
3194
|
+
: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; }
|
|
2799
3195
|
[data-dsh-mobile-composer-trailing] > button[class*="_primary"] { min-width:44px !important; flex-shrink:0 !important; }
|
|
2800
3196
|
[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; }
|
|
2801
3197
|
[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; }
|
|
@@ -2855,6 +3251,8 @@ window.__ModuleLoader__.load({
|
|
|
2855
3251
|
.dsh-mobile-settings_fontFamily > span { flex:1 1 100px; }
|
|
2856
3252
|
.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; }
|
|
2857
3253
|
.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; }
|
|
3254
|
+
html.dsh-native-mobile-active [data-dsh-mobile-settings-header] { box-sizing:border-box !important; height:56px !important; min-height:56px !important; padding:4px 12px !important; }
|
|
3255
|
+
html.dsh-native-mobile-active [data-dsh-mobile-settings-header] [class*="_close"] { box-sizing:border-box !important; flex:none !important; width:48px !important; min-width:48px !important; height:48px !important; min-height:48px !important; }
|
|
2858
3256
|
/* The mobile row replaces the Host-backed font control, not the theme selector. */
|
|
2859
3257
|
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; }
|
|
2860
3258
|
@media ${NATIVE_MOBILE_OVERLAY_QUERY} {
|
|
@@ -2924,8 +3322,6 @@ window.__ModuleLoader__.load({
|
|
|
2924
3322
|
[data-dsh-mobile-settings-list] [class*="_navCell"] { flex:0 0 auto !important; min-width:max-content !important; height:44px !important; padding:10px 12px !important; }
|
|
2925
3323
|
[data-dsh-mobile-settings-list] [aria-current="true"] { border-color:transparent !important; outline:0 !important; box-shadow:none !important; }
|
|
2926
3324
|
[data-dsh-mobile-settings-content] { flex:1 1 auto !important; width:100% !important; min-height:0 !important; }
|
|
2927
|
-
[data-dsh-mobile-settings-header] { height:48px !important; min-height:48px !important; padding:10px 12px 6px !important; }
|
|
2928
|
-
[data-dsh-mobile-settings-header] [class*="_close"] { width:36px !important; height:36px !important; }
|
|
2929
3325
|
[data-dsh-mobile-settings-options] { box-sizing:border-box !important; width:100% !important; padding:4px 16px max(24px,env(safe-area-inset-bottom)) !important; overflow-x:hidden !important; }
|
|
2930
3326
|
[data-dsh-mobile-settings-options] > * { width:100% !important; min-width:0 !important; }
|
|
2931
3327
|
[data-dsh-mobile-settings-options] [data-slot="settings.general.item"] > [class*="_row"] { flex-direction:column !important; align-items:stretch !important; gap:12px !important; }
|
|
@@ -4397,6 +4793,63 @@ window.__ModuleLoader__.load({
|
|
|
4397
4793
|
};
|
|
4398
4794
|
}
|
|
4399
4795
|
//#endregion
|
|
4796
|
+
//#region src/mobile-font-capabilities.ts
|
|
4797
|
+
const NO_ADVANCED_MOBILE_TYPOGRAPHY = Object.freeze({
|
|
4798
|
+
codeSize: false,
|
|
4799
|
+
textFamily: false,
|
|
4800
|
+
codeFamily: false
|
|
4801
|
+
});
|
|
4802
|
+
/**
|
|
4803
|
+
* Probe computed fonts derived from DSH's public Markdown tokens. Input values are
|
|
4804
|
+
* restored synchronously before painting; merely defining the input variable is not support.
|
|
4805
|
+
* @param document - Live page document in the current browsing context.
|
|
4806
|
+
* @returns Independently observed advanced typography support.
|
|
4807
|
+
*/
|
|
4808
|
+
function readMobileTypographyCapabilities(document) {
|
|
4809
|
+
const style = document.body.style;
|
|
4810
|
+
const text = document.createElement("span");
|
|
4811
|
+
const code = document.createElement("span");
|
|
4812
|
+
for (const probe of [text, code]) {
|
|
4813
|
+
probe.setAttribute("aria-hidden", "true");
|
|
4814
|
+
probe.style.cssText = "position:fixed;visibility:hidden;pointer-events:none;contain:strict;width:0;height:0;overflow:hidden";
|
|
4815
|
+
}
|
|
4816
|
+
text.style.font = "var(--dsw-font-markdown-base, 14px sans-serif)";
|
|
4817
|
+
code.style.font = "var(--dsw-font-markdown-code-block, 11px monospace)";
|
|
4818
|
+
document.body.append(text, code);
|
|
4819
|
+
const previous = /* @__PURE__ */ new Map();
|
|
4820
|
+
const sample = (property, value) => {
|
|
4821
|
+
if (!previous.has(property)) previous.set(property, {
|
|
4822
|
+
value: style.getPropertyValue(property),
|
|
4823
|
+
priority: style.getPropertyPriority(property)
|
|
4824
|
+
});
|
|
4825
|
+
style.setProperty(property, value);
|
|
4826
|
+
};
|
|
4827
|
+
try {
|
|
4828
|
+
sample("--dsh-code-font-size", "23px");
|
|
4829
|
+
const firstSize = getComputedStyle(code).fontSize;
|
|
4830
|
+
sample("--dsh-code-font-size", "24px");
|
|
4831
|
+
const secondSize = getComputedStyle(code).fontSize;
|
|
4832
|
+
sample("--dsh-font-family-text", "\"dsh-mobile-text-capability-a\"");
|
|
4833
|
+
const firstTextFamily = getComputedStyle(text).fontFamily;
|
|
4834
|
+
sample("--dsh-font-family-text", "\"dsh-mobile-text-capability-b\"");
|
|
4835
|
+
const secondTextFamily = getComputedStyle(text).fontFamily;
|
|
4836
|
+
sample("--dsh-font-family-code", "\"dsh-mobile-code-capability-a\"");
|
|
4837
|
+
const firstCodeFamily = getComputedStyle(code).fontFamily;
|
|
4838
|
+
sample("--dsh-font-family-code", "\"dsh-mobile-code-capability-b\"");
|
|
4839
|
+
const secondCodeFamily = getComputedStyle(code).fontFamily;
|
|
4840
|
+
return Object.freeze({
|
|
4841
|
+
codeSize: firstSize === "23px" && secondSize === "24px",
|
|
4842
|
+
textFamily: firstTextFamily.includes("dsh-mobile-text-capability-a") && secondTextFamily.includes("dsh-mobile-text-capability-b"),
|
|
4843
|
+
codeFamily: firstCodeFamily.includes("dsh-mobile-code-capability-a") && secondCodeFamily.includes("dsh-mobile-code-capability-b")
|
|
4844
|
+
});
|
|
4845
|
+
} finally {
|
|
4846
|
+
for (const [property, value] of previous) if (value.value === "") style.removeProperty(property);
|
|
4847
|
+
else style.setProperty(property, value.value, value.priority);
|
|
4848
|
+
text.remove();
|
|
4849
|
+
code.remove();
|
|
4850
|
+
}
|
|
4851
|
+
}
|
|
4852
|
+
//#endregion
|
|
4400
4853
|
//#region src/mobile-font.ts
|
|
4401
4854
|
/** Device-local typography for authenticated mobile pages, without Host settings writes. */
|
|
4402
4855
|
const MOBILE_FONT_STORAGE_KEY = "dsh-mobile.content-font-size.v1";
|
|
@@ -4497,6 +4950,8 @@ window.__ModuleLoader__.load({
|
|
|
4497
4950
|
storage;
|
|
4498
4951
|
typography = defaultTypography();
|
|
4499
4952
|
listeners = /* @__PURE__ */ new Set();
|
|
4953
|
+
capabilities = NO_ADVANCED_MOBILE_TYPOGRAPHY;
|
|
4954
|
+
capabilityListeners = /* @__PURE__ */ new Set();
|
|
4500
4955
|
constructor(storage) {
|
|
4501
4956
|
this.storage = storage;
|
|
4502
4957
|
let legacySize = 16;
|
|
@@ -4516,6 +4971,19 @@ window.__ModuleLoader__.load({
|
|
|
4516
4971
|
this.listeners.delete(listener);
|
|
4517
4972
|
};
|
|
4518
4973
|
};
|
|
4974
|
+
getCapabilitiesSnapshot = () => this.capabilities;
|
|
4975
|
+
subscribeCapabilities = (listener) => {
|
|
4976
|
+
this.capabilityListeners.add(listener);
|
|
4977
|
+
return () => {
|
|
4978
|
+
this.capabilityListeners.delete(listener);
|
|
4979
|
+
};
|
|
4980
|
+
};
|
|
4981
|
+
/** Update observed theme support without changing stored values or Host settings. */
|
|
4982
|
+
updateCapabilities(capabilities) {
|
|
4983
|
+
if (this.capabilities.codeSize === capabilities.codeSize && this.capabilities.textFamily === capabilities.textFamily && this.capabilities.codeFamily === capabilities.codeFamily) return;
|
|
4984
|
+
this.capabilities = Object.freeze({ ...capabilities });
|
|
4985
|
+
for (const listener of this.capabilityListeners) listener();
|
|
4986
|
+
}
|
|
4519
4987
|
/** Apply a user-selected size locally; never send it to DSH's settings RPC. */
|
|
4520
4988
|
set(size) {
|
|
4521
4989
|
this.setRoleSize("text", size);
|
|
@@ -4550,6 +5018,23 @@ window.__ModuleLoader__.load({
|
|
|
4550
5018
|
if (MOBILE_FONT_ROLES.every((role) => this.typography.sizes[role] === next.sizes[role] && this.typography.families[role] === "")) return;
|
|
4551
5019
|
this.persist(next);
|
|
4552
5020
|
}
|
|
5021
|
+
/** Reset visible supported controls while retaining preferences an older theme cannot consume. */
|
|
5022
|
+
resetSupported() {
|
|
5023
|
+
const defaults = defaultTypography();
|
|
5024
|
+
const sizes = {
|
|
5025
|
+
text: defaults.sizes.text,
|
|
5026
|
+
code: this.capabilities.codeSize ? defaults.sizes.code : this.typography.sizes.code
|
|
5027
|
+
};
|
|
5028
|
+
const families = {
|
|
5029
|
+
text: this.capabilities.textFamily ? "" : this.typography.families.text,
|
|
5030
|
+
code: this.capabilities.codeFamily ? "" : this.typography.families.code
|
|
5031
|
+
};
|
|
5032
|
+
if (MOBILE_FONT_ROLES.every((role) => this.typography.sizes[role] === sizes[role] && this.typography.families[role] === families[role])) return;
|
|
5033
|
+
this.persist({
|
|
5034
|
+
sizes: Object.freeze(sizes),
|
|
5035
|
+
families: Object.freeze(families)
|
|
5036
|
+
});
|
|
5037
|
+
}
|
|
4553
5038
|
persist(typography) {
|
|
4554
5039
|
this.typography = Object.freeze(typography);
|
|
4555
5040
|
try {
|
|
@@ -4561,7 +5046,11 @@ window.__ModuleLoader__.load({
|
|
|
4561
5046
|
for (const listener of this.listeners) listener();
|
|
4562
5047
|
}
|
|
4563
5048
|
};
|
|
4564
|
-
/**
|
|
5049
|
+
/**
|
|
5050
|
+
* Keep mobile typography independent from Host theme changes, including body-level portals.
|
|
5051
|
+
* Recheck head-owned stylesheets and body presentation attributes, not conversation DOM updates.
|
|
5052
|
+
* Arbitrary stylesheets inserted into body are not observed.
|
|
5053
|
+
*/
|
|
4565
5054
|
function installMobileFontPreference() {
|
|
4566
5055
|
let storage = null;
|
|
4567
5056
|
try {
|
|
@@ -4577,11 +5066,26 @@ window.__ModuleLoader__.load({
|
|
|
4577
5066
|
const owned = /* @__PURE__ */ new Map();
|
|
4578
5067
|
const apply = () => {
|
|
4579
5068
|
const typography = preference.getTypographySnapshot();
|
|
5069
|
+
const capabilities = preference.getCapabilitiesSnapshot();
|
|
4580
5070
|
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]]]) {
|
|
4581
5071
|
const current = style.getPropertyValue(property);
|
|
4582
5072
|
const priority = style.getPropertyPriority(property);
|
|
4583
5073
|
const last = owned.get(property);
|
|
4584
|
-
if (
|
|
5074
|
+
if (!(property === roleSpecs[role].variable ? role === "text" || capabilities.codeSize : role === "text" ? capabilities.textFamily : capabilities.codeFamily)) {
|
|
5075
|
+
if (last !== void 0) {
|
|
5076
|
+
if (current === last.value && priority === last.priority) {
|
|
5077
|
+
const foreign = previous.get(property);
|
|
5078
|
+
if (foreign.value === "") style.removeProperty(property);
|
|
5079
|
+
else style.setProperty(property, foreign.value, foreign.priority);
|
|
5080
|
+
} else previous.set(property, {
|
|
5081
|
+
value: current,
|
|
5082
|
+
priority
|
|
5083
|
+
});
|
|
5084
|
+
owned.delete(property);
|
|
5085
|
+
}
|
|
5086
|
+
continue;
|
|
5087
|
+
}
|
|
5088
|
+
if (last === void 0 || current !== last.value || priority !== last.priority) previous.set(property, {
|
|
4585
5089
|
value: current,
|
|
4586
5090
|
priority
|
|
4587
5091
|
});
|
|
@@ -4594,17 +5098,55 @@ window.__ModuleLoader__.load({
|
|
|
4594
5098
|
else style.setProperty(property, value);
|
|
4595
5099
|
}
|
|
4596
5100
|
};
|
|
4597
|
-
|
|
4598
|
-
const
|
|
4599
|
-
const
|
|
5101
|
+
let lastBodyState = "";
|
|
5102
|
+
const bodyState = () => `${style.cssText}|${document.body.className}|${document.body.getAttribute("data-ds-dark-theme")}`;
|
|
5103
|
+
const applyAndRemember = () => {
|
|
5104
|
+
apply();
|
|
5105
|
+
lastBodyState = bodyState();
|
|
5106
|
+
};
|
|
5107
|
+
const refresh = () => {
|
|
5108
|
+
preference.updateCapabilities(readMobileTypographyCapabilities(document));
|
|
5109
|
+
applyAndRemember();
|
|
5110
|
+
};
|
|
5111
|
+
refresh();
|
|
5112
|
+
const unsubscribe = preference.subscribe(applyAndRemember);
|
|
5113
|
+
const observer = new MutationObserver(() => {
|
|
5114
|
+
if (bodyState() !== lastBodyState) refresh();
|
|
5115
|
+
});
|
|
4600
5116
|
observer.observe(document.body, {
|
|
4601
5117
|
attributes: true,
|
|
4602
|
-
attributeFilter: [
|
|
5118
|
+
attributeFilter: [
|
|
5119
|
+
"style",
|
|
5120
|
+
"class",
|
|
5121
|
+
"data-ds-dark-theme"
|
|
5122
|
+
]
|
|
5123
|
+
});
|
|
5124
|
+
const themeObserver = new MutationObserver((records) => {
|
|
5125
|
+
const isSheet = (node) => node instanceof Element && (node.matches("style,link[rel=\"stylesheet\"]") || node.querySelector("style,link[rel=\"stylesheet\"]") !== null);
|
|
5126
|
+
if (records.some((record) => record.target instanceof Element && record.target.closest("style,link[rel=\"stylesheet\"]") !== null || [...record.addedNodes, ...record.removedNodes].some(isSheet))) refresh();
|
|
4603
5127
|
});
|
|
5128
|
+
themeObserver.observe(document.head, {
|
|
5129
|
+
subtree: true,
|
|
5130
|
+
childList: true,
|
|
5131
|
+
characterData: true,
|
|
5132
|
+
attributes: true,
|
|
5133
|
+
attributeFilter: [
|
|
5134
|
+
"href",
|
|
5135
|
+
"media",
|
|
5136
|
+
"disabled",
|
|
5137
|
+
"rel"
|
|
5138
|
+
]
|
|
5139
|
+
});
|
|
5140
|
+
const sheetLoaded = (event) => {
|
|
5141
|
+
if (event.target instanceof Element && event.target.matches("link[rel=\"stylesheet\"]")) refresh();
|
|
5142
|
+
};
|
|
5143
|
+
document.head.addEventListener("load", sheetLoaded, true);
|
|
4604
5144
|
return {
|
|
4605
5145
|
preference,
|
|
4606
5146
|
dispose: () => {
|
|
4607
5147
|
observer.disconnect();
|
|
5148
|
+
themeObserver.disconnect();
|
|
5149
|
+
document.head.removeEventListener("load", sheetLoaded, true);
|
|
4608
5150
|
unsubscribe();
|
|
4609
5151
|
for (const [property, value] of previous) {
|
|
4610
5152
|
const last = owned.get(property);
|
|
@@ -4627,7 +5169,8 @@ window.__ModuleLoader__.load({
|
|
|
4627
5169
|
familyHint: "填写设备已有的字体,多个名称用逗号分隔;留空使用默认字体。",
|
|
4628
5170
|
codeSize: "代码字号",
|
|
4629
5171
|
textFamily: "正文字体",
|
|
4630
|
-
codeFamily: "代码字体"
|
|
5172
|
+
codeFamily: "代码字体",
|
|
5173
|
+
limited: "只显示当前 DSH 主题支持的字体选项;其他已保存的设置会保留。"
|
|
4631
5174
|
},
|
|
4632
5175
|
en: {
|
|
4633
5176
|
title: "Mobile font size",
|
|
@@ -4640,7 +5183,8 @@ window.__ModuleLoader__.load({
|
|
|
4640
5183
|
familyHint: "Use fonts already on your device, separated by commas. Leave empty for the default.",
|
|
4641
5184
|
codeSize: "Code font size",
|
|
4642
5185
|
textFamily: "Text font",
|
|
4643
|
-
codeFamily: "Code font"
|
|
5186
|
+
codeFamily: "Code font",
|
|
5187
|
+
limited: "Only font options supported by the current DSH theme are shown. Other saved preferences are retained."
|
|
4644
5188
|
},
|
|
4645
5189
|
it: {
|
|
4646
5190
|
title: "Dimensione testo mobile",
|
|
@@ -4653,7 +5197,8 @@ window.__ModuleLoader__.load({
|
|
|
4653
5197
|
familyHint: "Usa caratteri presenti sul dispositivo, separati da virgole. Lascia vuoto per il predefinito.",
|
|
4654
5198
|
codeSize: "Dimensione codice",
|
|
4655
5199
|
textFamily: "Carattere testo",
|
|
4656
|
-
codeFamily: "Carattere codice"
|
|
5200
|
+
codeFamily: "Carattere codice",
|
|
5201
|
+
limited: "Sono mostrate solo le opzioni supportate dal tema DSH corrente. Le altre preferenze salvate vengono mantenute."
|
|
4657
5202
|
}
|
|
4658
5203
|
};
|
|
4659
5204
|
function FontFamilyInput({ preference, role, value, label, placeholder }) {
|
|
@@ -4681,6 +5226,7 @@ window.__ModuleLoader__.load({
|
|
|
4681
5226
|
/** General settings row matching the existing Mobile controls and DSH theme tokens. */
|
|
4682
5227
|
function MobileFontSizeRow({ preference, locale }) {
|
|
4683
5228
|
const typography = (0, react.useSyncExternalStore)(preference.subscribe, preference.getTypographySnapshot);
|
|
5229
|
+
const capabilities = (0, react.useSyncExternalStore)(preference.subscribeCapabilities, preference.getCapabilitiesSnapshot);
|
|
4684
5230
|
const copy = messages$1[locale];
|
|
4685
5231
|
const controls = (role, title) => {
|
|
4686
5232
|
const size = typography.sizes[role];
|
|
@@ -4711,18 +5257,22 @@ window.__ModuleLoader__.load({
|
|
|
4711
5257
|
return (0, react.createElement)("div", {
|
|
4712
5258
|
lang: locale,
|
|
4713
5259
|
"data-mobile-font-setting": true
|
|
4714
|
-
}, (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)),
|
|
5260
|
+
}, (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)), !Object.values(capabilities).every(Boolean) && (0, react.createElement)("p", {
|
|
5261
|
+
className: "dsh-mobile-settings_desc",
|
|
5262
|
+
"data-mobile-font-limited": true
|
|
5263
|
+
}, copy.limited), Object.values(capabilities).some(Boolean) && (0, react.createElement)("details", { className: "dsh-mobile-settings_fontDetails" }, (0, react.createElement)("summary", null, copy.more), (capabilities.textFamily || capabilities.codeFamily) && (0, react.createElement)("p", { className: "dsh-mobile-settings_desc" }, copy.familyHint), capabilities.codeSize && (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.filter((role) => role === "text" ? capabilities.textFamily : capabilities.codeFamily).map((role) => (0, react.createElement)(FontFamilyInput, {
|
|
4715
5264
|
key: role,
|
|
4716
5265
|
preference,
|
|
4717
5266
|
role,
|
|
4718
5267
|
value: typography.families[role],
|
|
4719
5268
|
label: role === "text" ? copy.textFamily : copy.codeFamily,
|
|
4720
5269
|
placeholder: copy.defaultFamily
|
|
4721
|
-
})), (0, react.createElement)("button", {
|
|
5270
|
+
}))), (0, react.createElement)("div", { className: "dsh-mobile-settings_row" }, (0, react.createElement)("button", {
|
|
4722
5271
|
type: "button",
|
|
4723
5272
|
className: "dsh-mobile-settings_selector",
|
|
5273
|
+
"data-mobile-font-reset": true,
|
|
4724
5274
|
onClick: () => {
|
|
4725
|
-
preference.
|
|
5275
|
+
preference.resetSupported();
|
|
4726
5276
|
}
|
|
4727
5277
|
}, copy.reset)));
|
|
4728
5278
|
}
|
|
@@ -4945,6 +5495,116 @@ window.__ModuleLoader__.load({
|
|
|
4945
5495
|
}, copy.reset)));
|
|
4946
5496
|
}
|
|
4947
5497
|
//#endregion
|
|
5498
|
+
//#region src/mobile-native-settings-action.ts
|
|
5499
|
+
/** Own capability discovery and explicit calls; native actions retain the App's own timeout. */
|
|
5500
|
+
var MobileNativeSettingsActionController = class {
|
|
5501
|
+
action;
|
|
5502
|
+
getBridge;
|
|
5503
|
+
snapshot = Object.freeze({
|
|
5504
|
+
supported: false,
|
|
5505
|
+
state: "idle"
|
|
5506
|
+
});
|
|
5507
|
+
listeners = /* @__PURE__ */ new Set();
|
|
5508
|
+
bridge;
|
|
5509
|
+
active = false;
|
|
5510
|
+
generation = 0;
|
|
5511
|
+
probe = 0;
|
|
5512
|
+
attempt = 0;
|
|
5513
|
+
constructor(action, getBridge) {
|
|
5514
|
+
this.action = action;
|
|
5515
|
+
this.getBridge = getBridge;
|
|
5516
|
+
}
|
|
5517
|
+
getSnapshot = () => this.snapshot;
|
|
5518
|
+
subscribe = (listener) => {
|
|
5519
|
+
this.listeners.add(listener);
|
|
5520
|
+
return () => {
|
|
5521
|
+
this.listeners.delete(listener);
|
|
5522
|
+
};
|
|
5523
|
+
};
|
|
5524
|
+
start() {
|
|
5525
|
+
this.active = true;
|
|
5526
|
+
this.publish({
|
|
5527
|
+
supported: false,
|
|
5528
|
+
state: "idle"
|
|
5529
|
+
});
|
|
5530
|
+
this.refresh();
|
|
5531
|
+
}
|
|
5532
|
+
stop() {
|
|
5533
|
+
this.active = false;
|
|
5534
|
+
this.generation += 1;
|
|
5535
|
+
this.probe += 1;
|
|
5536
|
+
this.attempt += 1;
|
|
5537
|
+
this.bridge = void 0;
|
|
5538
|
+
}
|
|
5539
|
+
/** Recheck support without replaying or invalidating an in-flight call to the same adapter. */
|
|
5540
|
+
refresh = () => {
|
|
5541
|
+
if (!this.active) return;
|
|
5542
|
+
const bridge = this.getBridge();
|
|
5543
|
+
const probe = ++this.probe;
|
|
5544
|
+
if (bridge !== this.bridge) {
|
|
5545
|
+
this.bridge = bridge;
|
|
5546
|
+
this.generation += 1;
|
|
5547
|
+
this.attempt += 1;
|
|
5548
|
+
this.publish({
|
|
5549
|
+
supported: false,
|
|
5550
|
+
state: "idle"
|
|
5551
|
+
});
|
|
5552
|
+
}
|
|
5553
|
+
if (bridge === void 0) return;
|
|
5554
|
+
const generation = this.generation;
|
|
5555
|
+
const settle = (supported) => {
|
|
5556
|
+
if (!this.current(bridge, generation) || probe !== this.probe) return;
|
|
5557
|
+
if (!supported) this.attempt += 1;
|
|
5558
|
+
this.publish({
|
|
5559
|
+
supported,
|
|
5560
|
+
state: supported ? this.snapshot.state : "idle"
|
|
5561
|
+
});
|
|
5562
|
+
};
|
|
5563
|
+
Promise.resolve().then(() => {
|
|
5564
|
+
if (!this.current(bridge, generation) || probe !== this.probe) return void 0;
|
|
5565
|
+
return bridge.capabilities();
|
|
5566
|
+
}).then((capabilities) => {
|
|
5567
|
+
settle(capabilities?.includes(this.action) === true);
|
|
5568
|
+
}, () => {
|
|
5569
|
+
settle(false);
|
|
5570
|
+
});
|
|
5571
|
+
};
|
|
5572
|
+
/** Send at most one explicit action; late replies cannot update a replaced or unmounted row. */
|
|
5573
|
+
invoke = () => {
|
|
5574
|
+
const bridge = this.bridge;
|
|
5575
|
+
if (bridge === void 0 || !this.snapshot.supported || !this.current(bridge, this.generation) || this.snapshot.state === "busy" || this.action === "mobile.switch-computer" && this.snapshot.state === "success") return;
|
|
5576
|
+
const generation = this.generation;
|
|
5577
|
+
const attempt = ++this.attempt;
|
|
5578
|
+
this.publish({
|
|
5579
|
+
supported: true,
|
|
5580
|
+
state: "busy"
|
|
5581
|
+
});
|
|
5582
|
+
const current = () => this.current(bridge, generation) && attempt === this.attempt && this.snapshot.supported;
|
|
5583
|
+
Promise.resolve().then(() => {
|
|
5584
|
+
if (!current()) return void 0;
|
|
5585
|
+
return bridge.invoke(this.action, {});
|
|
5586
|
+
}).then(() => {
|
|
5587
|
+
if (current()) this.publish({
|
|
5588
|
+
supported: true,
|
|
5589
|
+
state: "success"
|
|
5590
|
+
});
|
|
5591
|
+
}, () => {
|
|
5592
|
+
if (current()) this.publish({
|
|
5593
|
+
supported: true,
|
|
5594
|
+
state: "error"
|
|
5595
|
+
});
|
|
5596
|
+
});
|
|
5597
|
+
};
|
|
5598
|
+
current(bridge, generation) {
|
|
5599
|
+
return this.active && generation === this.generation && this.bridge === bridge && this.getBridge() === bridge;
|
|
5600
|
+
}
|
|
5601
|
+
publish(snapshot) {
|
|
5602
|
+
if (snapshot.supported === this.snapshot.supported && snapshot.state === this.snapshot.state) return;
|
|
5603
|
+
this.snapshot = Object.freeze(snapshot);
|
|
5604
|
+
for (const listener of this.listeners) listener();
|
|
5605
|
+
}
|
|
5606
|
+
};
|
|
5607
|
+
//#endregion
|
|
4948
5608
|
//#region src/local-admin-host.ts
|
|
4949
5609
|
/** Browser-safe desktop-admin Host checks. Do not import Node APIs here. */
|
|
4950
5610
|
const DESKTOP_ADMIN_HEADER = "x-dsh-mobile-desktop-admin";
|
|
@@ -5005,65 +5665,282 @@ window.__ModuleLoader__.load({
|
|
|
5005
5665
|
return (protocol === "dsh-app:" ? hostname === "app" : (protocol === "" || protocol === "http:" || protocol === "https:") && isLocalAdminHostname(hostname)) && frontend !== "dedicated" && !new URLSearchParams(query).has("dsh-mobile-preview");
|
|
5006
5666
|
}
|
|
5007
5667
|
//#endregion
|
|
5668
|
+
//#region src/client-module-presentation.ts
|
|
5669
|
+
/** Group boot-protected entries before considering their package namespace.
|
|
5670
|
+
* @param entry - Catalog entry with server-owned required and dependency fields.
|
|
5671
|
+
* @returns Display group, never an authorization or compatibility decision.
|
|
5672
|
+
*/
|
|
5673
|
+
function clientModuleGroup(entry) {
|
|
5674
|
+
if (entry.required) return "required";
|
|
5675
|
+
return entry.id.startsWith("@deepseek-ai/") ? "advanced" : "extensions";
|
|
5676
|
+
}
|
|
5677
|
+
/** Format the package leaf mechanically; the catalog has no product titles.
|
|
5678
|
+
* @param id - Exact module ID from the server catalog.
|
|
5679
|
+
* @returns Unscoped package words without guessed function or plugin ownership.
|
|
5680
|
+
*/
|
|
5681
|
+
function clientModuleDisplayName(id) {
|
|
5682
|
+
const leaf = id.startsWith("@") && id.includes("/") ? id.slice(id.indexOf("/") + 1) : id;
|
|
5683
|
+
return (leaf.replace(/^dsh-(?:client-(?:ui-)?)?/u, "") || leaf || id).replace(/[-_]+/gu, " ");
|
|
5684
|
+
}
|
|
5685
|
+
function matchesQuery(id, query) {
|
|
5686
|
+
const needle = query.trim().toLowerCase();
|
|
5687
|
+
return needle === "" || `${id}\n${clientModuleDisplayName(id)}`.toLowerCase().includes(needle);
|
|
5688
|
+
}
|
|
5689
|
+
/** Match the visible title or exact ID and the current draft loading choice.
|
|
5690
|
+
* @param entry - Catalog entry to consider.
|
|
5691
|
+
* @param excluded - Current draft exclusions, not a runtime loading observation.
|
|
5692
|
+
* @param query - User-entered search text.
|
|
5693
|
+
* @param filter - All, selected for loading, or selected for omission.
|
|
5694
|
+
* @returns Whether the row belongs in the current filtered list.
|
|
5695
|
+
*/
|
|
5696
|
+
function clientModuleMatches(entry, excluded, query, filter) {
|
|
5697
|
+
const omitted = !entry.required && excluded.has(entry.id);
|
|
5698
|
+
return matchesQuery(entry.id, query) && (filter === "all" || (filter === "excluded" ? omitted : !omitted));
|
|
5699
|
+
}
|
|
5700
|
+
/** Editable selections retain unknown server exclusions and never cascade dependencies. */
|
|
5701
|
+
var ClientModuleDraft = class {
|
|
5702
|
+
entries;
|
|
5703
|
+
initial;
|
|
5704
|
+
excluded;
|
|
5705
|
+
entriesById;
|
|
5706
|
+
constructor(entries, excludedIds) {
|
|
5707
|
+
this.entries = entries;
|
|
5708
|
+
this.initial = new Set(excludedIds);
|
|
5709
|
+
this.excluded = new Set(excludedIds);
|
|
5710
|
+
this.entriesById = new Map(entries.map((entry) => [entry.id, entry]));
|
|
5711
|
+
}
|
|
5712
|
+
/** @returns Number of changed loading choices, including hidden filtered rows. */
|
|
5713
|
+
get changeCount() {
|
|
5714
|
+
let count = 0;
|
|
5715
|
+
for (const id of this.initial) if (!this.excluded.has(id)) count += 1;
|
|
5716
|
+
for (const id of this.excluded) if (!this.initial.has(id)) count += 1;
|
|
5717
|
+
return count;
|
|
5718
|
+
}
|
|
5719
|
+
/** @returns A snapshot of exclusions suitable for authenticated saving. */
|
|
5720
|
+
get excludedIds() {
|
|
5721
|
+
return [...this.excluded];
|
|
5722
|
+
}
|
|
5723
|
+
/** @returns Retained exclusions that cannot be saved against the current catalog. */
|
|
5724
|
+
get invalidExclusions() {
|
|
5725
|
+
return [...this.excluded].flatMap((id) => {
|
|
5726
|
+
const entry = this.entriesById.get(id);
|
|
5727
|
+
return entry === void 0 ? [{
|
|
5728
|
+
id,
|
|
5729
|
+
reason: "missing"
|
|
5730
|
+
}] : entry.required ? [{
|
|
5731
|
+
id,
|
|
5732
|
+
reason: "required"
|
|
5733
|
+
}] : [];
|
|
5734
|
+
});
|
|
5735
|
+
}
|
|
5736
|
+
/** Explicitly remove an old exclusion; ordinary optional choices use setLoaded.
|
|
5737
|
+
* @param id - Exact ID of a retained missing or now-required exclusion.
|
|
5738
|
+
* @returns Whether this draft changed; no server request is made.
|
|
5739
|
+
*/
|
|
5740
|
+
clearInvalidExclusion(id) {
|
|
5741
|
+
const entry = this.entriesById.get(id);
|
|
5742
|
+
if (!this.initial.has(id) || entry !== void 0 && !entry.required) return false;
|
|
5743
|
+
return this.excluded.delete(id);
|
|
5744
|
+
}
|
|
5745
|
+
/** @returns Whether a catalog entry is selected for loading, not already loaded. */
|
|
5746
|
+
isLoaded(id) {
|
|
5747
|
+
return this.entriesById.get(id)?.required === true || !this.excluded.has(id);
|
|
5748
|
+
}
|
|
5749
|
+
/** Change one optional choice; required and unknown entries cannot be edited.
|
|
5750
|
+
* @param id - Exact catalog ID.
|
|
5751
|
+
* @param loaded - Whether to load the entry on the next open.
|
|
5752
|
+
*/
|
|
5753
|
+
setLoaded(id, loaded) {
|
|
5754
|
+
const entry = this.entriesById.get(id);
|
|
5755
|
+
if (entry === void 0 || entry.required) return;
|
|
5756
|
+
if (loaded) this.excluded.delete(id);
|
|
5757
|
+
else this.excluded.add(id);
|
|
5758
|
+
}
|
|
5759
|
+
/** Filter a draft without changing hidden choices.
|
|
5760
|
+
* @param query - Search input.
|
|
5761
|
+
* @param filter - Requested loading selection.
|
|
5762
|
+
* @returns Catalog entries in their original stable order.
|
|
5763
|
+
*/
|
|
5764
|
+
matchingEntries(query, filter) {
|
|
5765
|
+
return this.entries.filter((entry) => clientModuleMatches(entry, this.excluded, query, filter));
|
|
5766
|
+
}
|
|
5767
|
+
/** Find invalid old exclusions by name or ID, independently from the protected catalog row.
|
|
5768
|
+
* @param query - Search input.
|
|
5769
|
+
* @param filter - Old exclusions appear in all and excluded selections, not loaded.
|
|
5770
|
+
* @returns Invalid draft exclusions in their retained order.
|
|
5771
|
+
*/
|
|
5772
|
+
matchingInvalidExclusions(query, filter) {
|
|
5773
|
+
return filter === "loaded" ? [] : this.invalidExclusions.filter((entry) => matchesQuery(entry.id, query));
|
|
5774
|
+
}
|
|
5775
|
+
};
|
|
5776
|
+
//#endregion
|
|
5008
5777
|
//#region src/client-module-ui.ts
|
|
5009
5778
|
/** Device-local module selection and the computer's defaults share a read-only catalog. */
|
|
5010
5779
|
const CLIENT_MODULE_COPY = {
|
|
5011
5780
|
zh: {
|
|
5012
5781
|
title: "移动页面模块",
|
|
5013
|
-
computer: "
|
|
5014
|
-
device: "
|
|
5782
|
+
computer: "设置此电脑的移动访问默认项;电脑插件不变。",
|
|
5783
|
+
device: "仅本设备,下次打开生效;电脑插件不变。",
|
|
5015
5784
|
open: "管理",
|
|
5785
|
+
help: "加载规则",
|
|
5016
5786
|
loading: "正在读取模块…",
|
|
5017
|
-
hint: "
|
|
5018
|
-
save: "
|
|
5787
|
+
hint: "勾选表示下次打开时加载。启动必需项受保护;依赖冲突会在保存时说明,不会自动取消其他模块。",
|
|
5788
|
+
save: "保存选择",
|
|
5019
5789
|
reset: "恢复默认",
|
|
5020
5790
|
close: "关闭",
|
|
5021
5791
|
required: "启动必需",
|
|
5022
5792
|
dependencies: "依赖:",
|
|
5023
|
-
|
|
5024
|
-
|
|
5793
|
+
noDependencies: "无声明的前端依赖",
|
|
5794
|
+
details: "模块详情",
|
|
5795
|
+
moduleId: "模块 ID",
|
|
5796
|
+
saved: "已保存,下次打开 DSH 生效。不会自动刷新当前页面或丢弃草稿。",
|
|
5797
|
+
failed: "暂时无法读取模块。请确认移动访问已启用后重试。",
|
|
5798
|
+
saveFailed: "未确认保存成功,已保留你的选择。可重新保存,或重新读取已保存设置。",
|
|
5025
5799
|
conflict: "无法保存:",
|
|
5026
|
-
computerSource: "
|
|
5027
|
-
deviceSource: "
|
|
5028
|
-
|
|
5800
|
+
computerSource: "当前来源:电脑默认设置",
|
|
5801
|
+
deviceSource: "当前来源:本设备设置",
|
|
5802
|
+
pluginSource: "当前来源:插件默认设置",
|
|
5803
|
+
resetConfirm: "恢复默认模块选择?仅下次打开生效,不会重新加载当前会话。",
|
|
5804
|
+
discardConfirm: "放弃未保存的模块选择并关闭?",
|
|
5805
|
+
reloadConfirm: "重新读取会替换未保存的选择。继续吗?",
|
|
5806
|
+
savingCloseConfirm: "保存结果尚未确认。现在关闭不会撤销可能已完成的保存,确定关闭吗?",
|
|
5807
|
+
search: "搜索模块",
|
|
5808
|
+
searchPlaceholder: "搜索名称或模块 ID",
|
|
5809
|
+
filter: "加载选择",
|
|
5810
|
+
all: "全部",
|
|
5811
|
+
loaded: "加载",
|
|
5812
|
+
excluded: "不加载",
|
|
5813
|
+
empty: "没有符合条件的模块。",
|
|
5814
|
+
clearFilters: "清除筛选",
|
|
5815
|
+
extensions: "可选扩展",
|
|
5816
|
+
extensionsHint: "可选择不加载的前端模块;一项模块不一定等于一个完整插件。",
|
|
5817
|
+
advanced: "DSH 可选组件(高级)",
|
|
5818
|
+
advancedHint: "DSH 的可选前端组件。只修改你了解的项目。",
|
|
5819
|
+
requiredGroup: "启动必需项",
|
|
5820
|
+
requiredHint: "这些模块保证页面启动,不能关闭。",
|
|
5821
|
+
changes: "未保存:{count} 项改动",
|
|
5822
|
+
unchanged: "没有未保存的改动",
|
|
5823
|
+
results: "匹配 {visible} / {total} 项",
|
|
5824
|
+
retry: "重新读取",
|
|
5825
|
+
saving: "正在保存…",
|
|
5826
|
+
readTimeout: "读取超时。当前页面和已保存设置未变,请重试。",
|
|
5827
|
+
writeTimeout: "保存超时,结果尚未确认。保留了你的选择;请重新读取确认,不会自动重试保存。",
|
|
5828
|
+
nextOpen: "仅下次打开生效",
|
|
5829
|
+
invalidSelections: "失效的旧选择",
|
|
5830
|
+
invalidHint: "这些旧排除已与当前模块目录不符。移除后仍需保存选择。",
|
|
5831
|
+
invalidMissing: "当前模块目录中不存在。",
|
|
5832
|
+
invalidRequired: "当前已是启动必需项,不能排除。",
|
|
5833
|
+
clearInvalid: "移除旧排除"
|
|
5029
5834
|
},
|
|
5030
5835
|
en: {
|
|
5031
5836
|
title: "Mobile page modules",
|
|
5032
|
-
computer: "
|
|
5033
|
-
device: "
|
|
5837
|
+
computer: "Mobile defaults for this computer. Desktop plugins are unchanged.",
|
|
5838
|
+
device: "For this device’s next open only. Computer plugins are unchanged.",
|
|
5034
5839
|
open: "Manage",
|
|
5840
|
+
help: "Loading rules",
|
|
5035
5841
|
loading: "Reading modules…",
|
|
5036
|
-
hint: "Checked means
|
|
5037
|
-
save: "Save
|
|
5842
|
+
hint: "Checked means load on the next open. Required entries are protected; saving explains dependency conflicts without unchecking other modules.",
|
|
5843
|
+
save: "Save selection",
|
|
5038
5844
|
reset: "Restore defaults",
|
|
5039
5845
|
close: "Close",
|
|
5040
5846
|
required: "Required for boot",
|
|
5041
5847
|
dependencies: "Depends on: ",
|
|
5042
|
-
|
|
5043
|
-
|
|
5848
|
+
noDependencies: "No declared frontend dependencies",
|
|
5849
|
+
details: "Module details",
|
|
5850
|
+
moduleId: "Module ID",
|
|
5851
|
+
saved: "Saved for the next DSH open. This page is not refreshed and its draft is kept.",
|
|
5852
|
+
failed: "Could not read modules. Enable mobile access and try again.",
|
|
5853
|
+
saveFailed: "Saving was not confirmed. Your choices are kept. Save again or reread the saved settings.",
|
|
5044
5854
|
conflict: "Could not save: ",
|
|
5045
|
-
computerSource: "
|
|
5046
|
-
deviceSource: "
|
|
5047
|
-
|
|
5855
|
+
computerSource: "Source: computer defaults",
|
|
5856
|
+
deviceSource: "Source: this device’s settings",
|
|
5857
|
+
pluginSource: "Source: plugin defaults",
|
|
5858
|
+
resetConfirm: "Restore default selections for the next open? The current conversation will not reload.",
|
|
5859
|
+
discardConfirm: "Discard unsaved module choices and close?",
|
|
5860
|
+
reloadConfirm: "Rereading replaces unsaved choices. Continue?",
|
|
5861
|
+
savingCloseConfirm: "Saving is not confirmed. Closing will not undo a save that may have completed. Close anyway?",
|
|
5862
|
+
search: "Search modules",
|
|
5863
|
+
searchPlaceholder: "Search name or module ID",
|
|
5864
|
+
filter: "Loading selection",
|
|
5865
|
+
all: "All",
|
|
5866
|
+
loaded: "Load",
|
|
5867
|
+
excluded: "Do not load",
|
|
5868
|
+
empty: "No modules match these filters.",
|
|
5869
|
+
clearFilters: "Clear filters",
|
|
5870
|
+
extensions: "Optional extensions",
|
|
5871
|
+
extensionsHint: "Optional frontend modules; one module is not necessarily a complete plugin.",
|
|
5872
|
+
advanced: "DSH optional components (advanced)",
|
|
5873
|
+
advancedHint: "Optional DSH frontend components. Change only entries you understand.",
|
|
5874
|
+
requiredGroup: "Required for boot",
|
|
5875
|
+
requiredHint: "These modules keep the page bootable and cannot be disabled.",
|
|
5876
|
+
changes: "Unsaved: {count} changes",
|
|
5877
|
+
unchanged: "No unsaved changes",
|
|
5878
|
+
results: "Matching {visible} / {total}",
|
|
5879
|
+
retry: "Reread settings",
|
|
5880
|
+
saving: "Saving…",
|
|
5881
|
+
readTimeout: "Reading timed out. This page and saved settings are unchanged; try again.",
|
|
5882
|
+
writeTimeout: "Saving timed out and its result is unconfirmed. Your choices are kept. Reread to confirm; saving is not retried automatically.",
|
|
5883
|
+
nextOpen: "Applies on next open only",
|
|
5884
|
+
invalidSelections: "Outdated selections",
|
|
5885
|
+
invalidHint: "These old exclusions no longer match the module catalog. Save your choices after removing them.",
|
|
5886
|
+
invalidMissing: "Not present in the current module catalog.",
|
|
5887
|
+
invalidRequired: "Now required for boot and cannot be excluded.",
|
|
5888
|
+
clearInvalid: "Remove old exclusion"
|
|
5048
5889
|
},
|
|
5049
5890
|
it: {
|
|
5050
5891
|
title: "Moduli della pagina mobile",
|
|
5051
|
-
computer: "
|
|
5052
|
-
device: "
|
|
5892
|
+
computer: "Predefiniti mobile del computer; i plugin non cambiano.",
|
|
5893
|
+
device: "Solo alla prossima apertura su questo dispositivo; i plugin del computer non cambiano.",
|
|
5053
5894
|
open: "Gestisci",
|
|
5895
|
+
help: "Regole di caricamento",
|
|
5054
5896
|
loading: "Lettura dei moduli…",
|
|
5055
|
-
hint: "Selezionato significa
|
|
5056
|
-
save: "Salva
|
|
5897
|
+
hint: "Selezionato significa caricare alla prossima apertura. I moduli obbligatori sono protetti; il salvataggio mostra i conflitti senza deselezionare altri moduli.",
|
|
5898
|
+
save: "Salva selezione",
|
|
5057
5899
|
reset: "Ripristina predefiniti",
|
|
5058
5900
|
close: "Chiudi",
|
|
5059
5901
|
required: "Obbligatorio",
|
|
5060
5902
|
dependencies: "Dipende da: ",
|
|
5061
|
-
|
|
5062
|
-
|
|
5903
|
+
noDependencies: "Nessuna dipendenza frontend dichiarata",
|
|
5904
|
+
details: "Dettagli modulo",
|
|
5905
|
+
moduleId: "ID modulo",
|
|
5906
|
+
saved: "Salvato per la prossima apertura di DSH. La pagina non si aggiorna e la bozza resta.",
|
|
5907
|
+
failed: "Impossibile leggere i moduli. Abilita accesso mobile e riprova.",
|
|
5908
|
+
saveFailed: "Salvataggio non confermato. Le scelte restano; salva di nuovo o rileggi le impostazioni salvate.",
|
|
5063
5909
|
conflict: "Impossibile salvare: ",
|
|
5064
|
-
computerSource: "
|
|
5065
|
-
deviceSource: "
|
|
5066
|
-
|
|
5910
|
+
computerSource: "Origine: predefiniti del computer",
|
|
5911
|
+
deviceSource: "Origine: impostazioni del dispositivo",
|
|
5912
|
+
pluginSource: "Origine: predefiniti del plugin",
|
|
5913
|
+
resetConfirm: "Ripristinare i predefiniti per la prossima apertura? La conversazione attuale non verrà ricaricata.",
|
|
5914
|
+
discardConfirm: "Scartare le scelte non salvate e chiudere?",
|
|
5915
|
+
reloadConfirm: "Rileggere sostituisce le scelte non salvate. Continuare?",
|
|
5916
|
+
savingCloseConfirm: "Salvataggio non confermato. Chiudere non annulla un salvataggio già completato. Chiudere comunque?",
|
|
5917
|
+
search: "Cerca moduli",
|
|
5918
|
+
searchPlaceholder: "Cerca nome o ID modulo",
|
|
5919
|
+
filter: "Selezione caricamento",
|
|
5920
|
+
all: "Tutti",
|
|
5921
|
+
loaded: "Carica",
|
|
5922
|
+
excluded: "Non caricare",
|
|
5923
|
+
empty: "Nessun modulo corrisponde ai filtri.",
|
|
5924
|
+
clearFilters: "Cancella filtri",
|
|
5925
|
+
extensions: "Estensioni facoltative",
|
|
5926
|
+
extensionsHint: "Moduli frontend facoltativi; un modulo non è necessariamente un plugin completo.",
|
|
5927
|
+
advanced: "Componenti DSH facoltativi (avanzate)",
|
|
5928
|
+
advancedHint: "Componenti frontend DSH facoltativi. Modifica solo quelli che conosci.",
|
|
5929
|
+
requiredGroup: "Moduli obbligatori",
|
|
5930
|
+
requiredHint: "Questi moduli permettono l’avvio e non possono essere disattivati.",
|
|
5931
|
+
changes: "Non salvate: {count} modifiche",
|
|
5932
|
+
unchanged: "Nessuna modifica non salvata",
|
|
5933
|
+
results: "Corrispondenti {visible} / {total}",
|
|
5934
|
+
retry: "Rileggi impostazioni",
|
|
5935
|
+
saving: "Salvataggio…",
|
|
5936
|
+
readTimeout: "Lettura scaduta. Pagina e impostazioni salvate non cambiano; riprova.",
|
|
5937
|
+
writeTimeout: "Salvataggio scaduto e non confermato. Le scelte restano. Rileggi per confermare; nessun tentativo automatico.",
|
|
5938
|
+
nextOpen: "Solo alla prossima apertura",
|
|
5939
|
+
invalidSelections: "Scelte obsolete",
|
|
5940
|
+
invalidHint: "Queste esclusioni precedenti non corrispondono più al catalogo. Salva le scelte dopo averle rimosse.",
|
|
5941
|
+
invalidMissing: "Assente dal catalogo attuale dei moduli.",
|
|
5942
|
+
invalidRequired: "Ora obbligatorio per l’avvio e non può essere escluso.",
|
|
5943
|
+
clearInvalid: "Rimuovi esclusione precedente"
|
|
5067
5944
|
}
|
|
5068
5945
|
};
|
|
5069
5946
|
var ClientModuleUiConflict = class extends Error {
|
|
@@ -5073,29 +5950,40 @@ window.__ModuleLoader__.load({
|
|
|
5073
5950
|
this.detail = detail;
|
|
5074
5951
|
}
|
|
5075
5952
|
};
|
|
5953
|
+
/** A deadline does not imply that a POST was rolled back by the server. */
|
|
5954
|
+
var ClientModuleRequestTimeout = class extends Error {
|
|
5955
|
+
mutation;
|
|
5956
|
+
constructor(mutation) {
|
|
5957
|
+
super("client_module_request_timeout");
|
|
5958
|
+
this.mutation = mutation;
|
|
5959
|
+
}
|
|
5960
|
+
};
|
|
5961
|
+
const CLIENT_MODULE_REQUEST_TIMEOUT_MS = {
|
|
5962
|
+
read: 15e3,
|
|
5963
|
+
write: 2e4
|
|
5964
|
+
};
|
|
5076
5965
|
/** Validate catalog response fields before creating interactive controls.
|
|
5077
5966
|
* @param value - Gateway or computer-admin JSON response.
|
|
5078
5967
|
* @returns The fields used by the module-selection dialog.
|
|
5079
5968
|
*/
|
|
5080
5969
|
function parseClientModuleView(value) {
|
|
5081
|
-
if (typeof value !== "object" || value === null || !("entries" in value) || !Array.isArray(value.entries) || !("excludedClientModules" in value) || !Array.isArray(value.excludedClientModules) || !("source" in value) ||
|
|
5082
|
-
|
|
5083
|
-
"
|
|
5084
|
-
|
|
5085
|
-
|
|
5970
|
+
if (typeof value !== "object" || value === null || !("entries" in value) || !Array.isArray(value.entries) || !("excludedClientModules" in value) || !Array.isArray(value.excludedClientModules) || !("source" in value) || value.source !== "device" && value.source !== "computer" && value.source !== "plugin") throw new Error("invalid_module_catalog");
|
|
5971
|
+
const entries = value.entries.map((entry) => {
|
|
5972
|
+
if (typeof entry !== "object" || entry === null || !("id" in entry) || typeof entry.id !== "string" || !("required" in entry) || typeof entry.required !== "boolean" || !("dependencies" in entry) || !Array.isArray(entry.dependencies)) throw new Error("invalid_module_catalog");
|
|
5973
|
+
const dependencies = entry.dependencies.map((dependency) => {
|
|
5974
|
+
if (typeof dependency !== "string") throw new Error("invalid_module_catalog");
|
|
5975
|
+
return dependency;
|
|
5976
|
+
});
|
|
5977
|
+
if (entry.id.trim() === "") throw new Error("invalid_module_catalog");
|
|
5978
|
+
return {
|
|
5979
|
+
id: entry.id,
|
|
5980
|
+
required: entry.required,
|
|
5981
|
+
dependencies
|
|
5982
|
+
};
|
|
5983
|
+
});
|
|
5984
|
+
if (new Set(entries.map((entry) => entry.id)).size !== entries.length) throw new Error("invalid_module_catalog");
|
|
5086
5985
|
return {
|
|
5087
|
-
entries
|
|
5088
|
-
if (typeof entry !== "object" || entry === null || !("id" in entry) || typeof entry.id !== "string" || !("required" in entry) || typeof entry.required !== "boolean" || !("dependencies" in entry) || !Array.isArray(entry.dependencies)) throw new Error("invalid_module_catalog");
|
|
5089
|
-
const dependencies = entry.dependencies.map((dependency) => {
|
|
5090
|
-
if (typeof dependency !== "string") throw new Error("invalid_module_catalog");
|
|
5091
|
-
return dependency;
|
|
5092
|
-
});
|
|
5093
|
-
return {
|
|
5094
|
-
id: entry.id,
|
|
5095
|
-
required: entry.required,
|
|
5096
|
-
dependencies
|
|
5097
|
-
};
|
|
5098
|
-
}),
|
|
5986
|
+
entries,
|
|
5099
5987
|
excludedClientModules: value.excludedClientModules.map((id) => {
|
|
5100
5988
|
if (typeof id !== "string") throw new Error("invalid_module_catalog");
|
|
5101
5989
|
return id;
|
|
@@ -5103,32 +5991,103 @@ window.__ModuleLoader__.load({
|
|
|
5103
5991
|
source: value.source === "device" ? "device" : value.source === "computer" ? "computer" : "plugin"
|
|
5104
5992
|
};
|
|
5105
5993
|
}
|
|
5994
|
+
/** Read or explicitly write module choices with a bounded, cancellable operation.
|
|
5995
|
+
* @param scope - Computer-admin endpoint or authenticated device endpoint.
|
|
5996
|
+
* @param signal - Owning dialog lifetime; cancellation cannot undo a completed write.
|
|
5997
|
+
* @param body - Omit for a read; supplied fields are sent once without automatic replay.
|
|
5998
|
+
* @returns Validated catalog fields returned by the selected endpoint.
|
|
5999
|
+
*/
|
|
6000
|
+
async function requestClientModuleView(scope, signal, body) {
|
|
6001
|
+
if (signal.aborted) throw new DOMException("Dialog closed", "AbortError");
|
|
6002
|
+
const controller = new AbortController();
|
|
6003
|
+
let rejectCancellation = () => {};
|
|
6004
|
+
const cancellation = new Promise((_, reject) => {
|
|
6005
|
+
rejectCancellation = reject;
|
|
6006
|
+
});
|
|
6007
|
+
const abort = () => {
|
|
6008
|
+
controller.abort();
|
|
6009
|
+
rejectCancellation(new DOMException("Dialog closed", "AbortError"));
|
|
6010
|
+
};
|
|
6011
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
6012
|
+
const timer = setTimeout(() => {
|
|
6013
|
+
rejectCancellation(new ClientModuleRequestTimeout(body !== void 0));
|
|
6014
|
+
controller.abort();
|
|
6015
|
+
}, body === void 0 ? CLIENT_MODULE_REQUEST_TIMEOUT_MS.read : CLIENT_MODULE_REQUEST_TIMEOUT_MS.write);
|
|
6016
|
+
const operation = async () => {
|
|
6017
|
+
const init = {
|
|
6018
|
+
method: body === void 0 ? "GET" : "POST",
|
|
6019
|
+
signal: controller.signal,
|
|
6020
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
6021
|
+
};
|
|
6022
|
+
init.headers = scope === "computer" ? localAdminRequestHeaders(init, location) : { "content-type": "application/json" };
|
|
6023
|
+
if (scope === "device" && body !== void 0) {
|
|
6024
|
+
const cookies = document.cookie.split(";").map((value) => value.trim());
|
|
6025
|
+
const csrfCookie = cookies.find((value) => value.startsWith("__Host-dsh_ma_csrf=")) ?? cookies.find((value) => value.startsWith("dsh_ma_csrf="));
|
|
6026
|
+
if (csrfCookie !== void 0) {
|
|
6027
|
+
const headers = new Headers(init.headers);
|
|
6028
|
+
headers.set("x-dsh-mobile-csrf", csrfCookie.slice(csrfCookie.indexOf("=") + 1));
|
|
6029
|
+
init.headers = headers;
|
|
6030
|
+
}
|
|
6031
|
+
}
|
|
6032
|
+
const response = await fetch(scope === "computer" ? "/api/mobile-access/client-modules" : "/mobile-access/client-modules", init);
|
|
6033
|
+
const value = await response.json();
|
|
6034
|
+
if (!response.ok) {
|
|
6035
|
+
const detail = typeof value === "object" && value !== null && "detail" in value && typeof value.detail === "string" ? value.detail.slice(0, 512) : "";
|
|
6036
|
+
if (response.status === 409 && detail !== "" && typeof value === "object" && value !== null && "error" in value && value.error === "excluded_client_modules_invalid") throw new ClientModuleUiConflict(detail);
|
|
6037
|
+
throw new Error("client_module_request_failed");
|
|
6038
|
+
}
|
|
6039
|
+
return parseClientModuleView(value);
|
|
6040
|
+
};
|
|
6041
|
+
try {
|
|
6042
|
+
return await Promise.race([operation(), cancellation]);
|
|
6043
|
+
} finally {
|
|
6044
|
+
clearTimeout(timer);
|
|
6045
|
+
signal.removeEventListener("abort", abort);
|
|
6046
|
+
}
|
|
6047
|
+
}
|
|
5106
6048
|
const CLIENT_MODULE_STYLES = `
|
|
5107
6049
|
.dsh-module-row{display:flex;align-items:center;gap:8px;padding:16px 0;border-bottom:.5px solid var(--dsw-alias-border-l2)}
|
|
5108
6050
|
.dsh-module-rowText{flex:1;min-width:0}.dsh-module-title{font-size:14px;line-height:22px;color:var(--dsw-alias-label-primary)}
|
|
5109
6051
|
.dsh-module-description{margin-top:4px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}
|
|
5110
6052
|
.dsh-module-button{min-height:48px;padding:0 14px;border:0;border-radius:24px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;cursor:pointer}
|
|
5111
6053
|
.dsh-module-button:focus-visible{outline:2px solid var(--dsw-alias-label-primary-bluish,#2563eb);outline-offset:2px}
|
|
5112
|
-
.dsh-module-
|
|
5113
|
-
.dsh-module-dialog
|
|
5114
|
-
.dsh-module-dialog
|
|
5115
|
-
.dsh-module-
|
|
5116
|
-
.dsh-module-
|
|
5117
|
-
.dsh-module-
|
|
6054
|
+
.dsh-module-button:not(:disabled):active{background:var(--dsw-alias-interactive-bg-active)}
|
|
6055
|
+
.dsh-module-dialog{box-sizing:border-box;width:min(640px,calc(100vw - 24px));max-height:calc(100dvh - 32px);padding:0;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-panel,20px);background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-primary,#171a21);font:16px/1.5 var(--dsw-font-family,sans-serif);overflow:hidden}
|
|
6056
|
+
.dsh-module-dialog[open]{display:flex;flex-direction:column}.dsh-module-dialog::backdrop{background:rgb(15 23 42 / 40%)}
|
|
6057
|
+
.dsh-module-header{display:flex;align-items:center;gap:12px;padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l2);flex:none}
|
|
6058
|
+
.dsh-module-header h2{flex:1;min-width:0;font-size:20px;line-height:1.4;margin:0;overflow-wrap:anywhere}.dsh-module-header .dsh-module-button{flex:none}
|
|
6059
|
+
.dsh-module-body{min-height:0;overflow:auto;overscroll-behavior:contain;padding:16px;scroll-padding:12px}.dsh-module-dialog p{margin:0 0 12px;overflow-wrap:anywhere}
|
|
6060
|
+
.dsh-module-hint,.dsh-module-source,.dsh-module-choice small,.dsh-module-groupHint,.dsh-module-results{font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-tertiary))}
|
|
6061
|
+
.dsh-module-tools{display:grid;grid-template-columns:minmax(0,1fr) minmax(100px,.45fr);gap:12px;margin:16px 0 8px}.dsh-module-field{display:flex;flex-direction:column;gap:4px;min-width:0;font-size:13px}
|
|
6062
|
+
.dsh-module-help>summary{box-sizing:border-box;min-height:48px;padding:12px 0;cursor:pointer;font-size:13px;color:var(--dsw-alias-label-secondary)}
|
|
6063
|
+
.dsh-module-field input,.dsh-module-field select{box-sizing:border-box;width:100%;min-width:0;min-height:48px;padding:8px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-base,var(--dsw-alias-bg-layer-1));color:var(--dsw-alias-label-primary);font:inherit;font-size:16px}
|
|
6064
|
+
.dsh-module-dialog :is(input,select,summary):focus-visible{outline:2px solid var(--dsw-alias-label-primary-bluish,#2563eb);outline-offset:2px}
|
|
6065
|
+
.dsh-module-list{display:flex;flex-direction:column;gap:16px;margin:12px 0}.dsh-module-group h3{margin:0 0 8px;font-size:16px;line-height:24px}
|
|
6066
|
+
.dsh-module-group>summary{min-height:48px;cursor:pointer;font-weight:600;overflow-wrap:anywhere;align-content:center}.dsh-module-group>summary+div{margin-top:8px}
|
|
6067
|
+
.dsh-module-entry{border-bottom:1px solid var(--dsw-alias-border-l2);padding:8px 0}.dsh-module-choice{display:flex;align-items:center;gap:12px;min-height:48px;cursor:pointer}
|
|
6068
|
+
.dsh-module-choice input{width:24px;height:24px;flex:none;margin:0}.dsh-module-choice span{min-width:0;overflow-wrap:anywhere}.dsh-module-choiceName{display:block;font-weight:500}
|
|
6069
|
+
.dsh-module-choice small{display:block}.dsh-module-entryDetails{margin-left:36px;min-width:0}.dsh-module-entryDetails summary{min-height:48px;align-content:center;cursor:pointer;font-size:13px;color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-tertiary))}
|
|
6070
|
+
.dsh-module-invalid-entry code{display:block;min-width:0;overflow-wrap:anywhere;white-space:normal;font-size:13px}.dsh-module-invalid-entry .dsh-module-button{max-width:100%;white-space:normal;line-height:20px;padding:8px 14px}
|
|
6071
|
+
.dsh-module-entryDetails p,.dsh-module-entryDetails code{overflow-wrap:anywhere;font-size:13px}.dsh-module-entryDetails code{white-space:normal}
|
|
6072
|
+
.dsh-module-footer{flex:none;padding:12px 16px max(12px,env(safe-area-inset-bottom));border-top:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1,#fff)}
|
|
6073
|
+
.dsh-module-footer p{font-size:13px;line-height:20px;margin:0 0 8px}.dsh-module-actions{display:flex;flex-wrap:wrap;gap:8px}.dsh-module-actions .dsh-module-button{flex:1;min-width:0;white-space:normal;line-height:20px;padding:8px 12px}
|
|
6074
|
+
.dsh-module-button:disabled{opacity:.5;cursor:default}.dsh-module-save{background:var(--dsw-alias-bg-module-platform);font-weight:600}
|
|
6075
|
+
.dsh-module-notice:empty{display:none}.dsh-module-notice{max-height:min(96px,24dvh);overflow:auto;font-size:14px;line-height:22px;overscroll-behavior:contain}.dsh-module-empty{padding:16px 0}
|
|
6076
|
+
@media(max-width:420px){.dsh-module-tools{grid-template-columns:minmax(0,1fr)}.dsh-module-body{padding:12px}.dsh-module-header,.dsh-module-footer{padding-left:12px;padding-right:12px}}
|
|
5118
6077
|
.dsh-module-error{color:var(--dsw-alias-state-error-primary,#b42318)}
|
|
5119
6078
|
`;
|
|
5120
6079
|
const openDialogs = /* @__PURE__ */ new Set();
|
|
5121
6080
|
/** Cancel pending dialogs when the owning plugin generation is disposed. */
|
|
5122
6081
|
function disposeClientModuleDialogs() {
|
|
5123
|
-
for (const
|
|
6082
|
+
for (const dialog of [...openDialogs]) dialog.dispose();
|
|
5124
6083
|
}
|
|
5125
6084
|
/** Close the foreground module modal before navigating its underlying DSH settings.
|
|
5126
6085
|
* @returns Whether an open module modal consumed the return action.
|
|
5127
6086
|
*/
|
|
5128
6087
|
function closeClientModuleDialog() {
|
|
5129
|
-
const
|
|
5130
|
-
if (
|
|
5131
|
-
|
|
6088
|
+
const dialog = [...openDialogs].at(-1);
|
|
6089
|
+
if (dialog === void 0) return false;
|
|
6090
|
+
dialog.requestClose();
|
|
5132
6091
|
return true;
|
|
5133
6092
|
}
|
|
5134
6093
|
/** Open one modal; closing cancels reads and restores focus without reloading the page.
|
|
@@ -5138,143 +6097,338 @@ window.__ModuleLoader__.load({
|
|
|
5138
6097
|
*/
|
|
5139
6098
|
function openClientModuleSettings(scope, locale, trigger) {
|
|
5140
6099
|
const copy = CLIENT_MODULE_COPY[locale];
|
|
5141
|
-
const endpoint = scope === "computer" ? "/api/mobile-access/client-modules" : "/mobile-access/client-modules";
|
|
5142
6100
|
const controller = new AbortController();
|
|
5143
6101
|
const dialog = document.createElement("dialog");
|
|
5144
6102
|
dialog.className = "dsh-module-dialog";
|
|
5145
6103
|
dialog.setAttribute("aria-label", copy.title);
|
|
6104
|
+
dialog.lang = locale;
|
|
6105
|
+
const button = (text) => {
|
|
6106
|
+
const result = document.createElement("button");
|
|
6107
|
+
result.type = "button";
|
|
6108
|
+
result.className = "dsh-module-button";
|
|
6109
|
+
result.textContent = text;
|
|
6110
|
+
return result;
|
|
6111
|
+
};
|
|
6112
|
+
const paragraph = (text, className = "") => {
|
|
6113
|
+
const result = document.createElement("p");
|
|
6114
|
+
result.textContent = text;
|
|
6115
|
+
result.className = className;
|
|
6116
|
+
return result;
|
|
6117
|
+
};
|
|
6118
|
+
const header = document.createElement("div");
|
|
6119
|
+
header.className = "dsh-module-header";
|
|
5146
6120
|
const title = document.createElement("h2");
|
|
5147
6121
|
title.textContent = copy.title;
|
|
5148
|
-
const
|
|
5149
|
-
|
|
5150
|
-
const
|
|
6122
|
+
const close = button(copy.close);
|
|
6123
|
+
header.append(title, close);
|
|
6124
|
+
const body = document.createElement("div");
|
|
6125
|
+
body.className = "dsh-module-body";
|
|
6126
|
+
const intro = paragraph(copy[scope]);
|
|
6127
|
+
const hint = paragraph(copy.hint, "dsh-module-hint");
|
|
6128
|
+
const help = document.createElement("details");
|
|
6129
|
+
help.className = "dsh-module-help";
|
|
6130
|
+
const helpTitle = document.createElement("summary");
|
|
6131
|
+
helpTitle.textContent = copy.help;
|
|
6132
|
+
help.append(helpTitle, hint);
|
|
6133
|
+
const source = paragraph("", "dsh-module-source");
|
|
6134
|
+
const status = paragraph("", "dsh-module-notice");
|
|
5151
6135
|
status.setAttribute("role", "status");
|
|
5152
6136
|
status.setAttribute("aria-live", "polite");
|
|
5153
6137
|
status.tabIndex = -1;
|
|
6138
|
+
const tools = document.createElement("div");
|
|
6139
|
+
tools.className = "dsh-module-tools";
|
|
6140
|
+
const searchLabel = document.createElement("label");
|
|
6141
|
+
searchLabel.className = "dsh-module-field";
|
|
6142
|
+
searchLabel.textContent = copy.search;
|
|
6143
|
+
const search = document.createElement("input");
|
|
6144
|
+
search.type = "search";
|
|
6145
|
+
search.placeholder = copy.searchPlaceholder;
|
|
6146
|
+
search.setAttribute("aria-label", copy.search);
|
|
6147
|
+
searchLabel.append(search);
|
|
6148
|
+
const filterLabel = document.createElement("label");
|
|
6149
|
+
filterLabel.className = "dsh-module-field";
|
|
6150
|
+
filterLabel.textContent = copy.filter;
|
|
6151
|
+
const filter = document.createElement("select");
|
|
6152
|
+
filter.dataset.clientModuleFilter = "true";
|
|
6153
|
+
filter.setAttribute("aria-label", copy.filter);
|
|
6154
|
+
for (const value of [
|
|
6155
|
+
"all",
|
|
6156
|
+
"loaded",
|
|
6157
|
+
"excluded"
|
|
6158
|
+
]) {
|
|
6159
|
+
const option = document.createElement("option");
|
|
6160
|
+
option.value = value;
|
|
6161
|
+
option.textContent = copy[value];
|
|
6162
|
+
filter.append(option);
|
|
6163
|
+
}
|
|
6164
|
+
filterLabel.append(filter);
|
|
6165
|
+
tools.append(searchLabel, filterLabel);
|
|
6166
|
+
const results = paragraph("", "dsh-module-results");
|
|
6167
|
+
results.setAttribute("aria-live", "polite");
|
|
5154
6168
|
const list = document.createElement("div");
|
|
5155
6169
|
list.className = "dsh-module-list";
|
|
6170
|
+
const footer = document.createElement("div");
|
|
6171
|
+
footer.className = "dsh-module-footer";
|
|
6172
|
+
const changes = paragraph(copy.unchanged);
|
|
6173
|
+
changes.setAttribute("role", "status");
|
|
6174
|
+
changes.setAttribute("aria-live", "polite");
|
|
5156
6175
|
const actions = document.createElement("div");
|
|
5157
6176
|
actions.className = "dsh-module-actions";
|
|
5158
|
-
const button = (text) => {
|
|
5159
|
-
const result = document.createElement("button");
|
|
5160
|
-
result.type = "button";
|
|
5161
|
-
result.className = "dsh-module-button";
|
|
5162
|
-
result.textContent = text;
|
|
5163
|
-
return result;
|
|
5164
|
-
};
|
|
5165
6177
|
const save = button(copy.save);
|
|
6178
|
+
save.classList.add("dsh-module-save");
|
|
5166
6179
|
const reset = button(copy.reset);
|
|
5167
|
-
const
|
|
5168
|
-
actions.append(save, reset,
|
|
5169
|
-
|
|
6180
|
+
const reread = button(copy.retry);
|
|
6181
|
+
actions.append(save, reset, reread);
|
|
6182
|
+
footer.append(changes, status, actions);
|
|
6183
|
+
body.append(intro, source, tools, results, list, help);
|
|
6184
|
+
dialog.append(header, body, footer);
|
|
5170
6185
|
let disposed = false;
|
|
5171
|
-
let busy
|
|
5172
|
-
let
|
|
6186
|
+
let busy;
|
|
6187
|
+
let generation = 0;
|
|
6188
|
+
let draft;
|
|
6189
|
+
let unconfirmedWrite = false;
|
|
6190
|
+
const expansion = /* @__PURE__ */ new Map([
|
|
6191
|
+
["extensions", true],
|
|
6192
|
+
["advanced", false],
|
|
6193
|
+
["required", false]
|
|
6194
|
+
]);
|
|
5173
6195
|
const choices = /* @__PURE__ */ new Map();
|
|
5174
|
-
const
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
6196
|
+
const invalidActions = /* @__PURE__ */ new Map();
|
|
6197
|
+
const filterValue = () => filter.value === "loaded" ? "loaded" : filter.value === "excluded" ? "excluded" : "all";
|
|
6198
|
+
const updateControls = () => {
|
|
6199
|
+
const changed = draft?.changeCount ?? 0;
|
|
6200
|
+
save.disabled = busy !== void 0 || changed === 0;
|
|
6201
|
+
reset.disabled = busy !== void 0 || draft === void 0;
|
|
6202
|
+
reread.disabled = busy !== void 0;
|
|
6203
|
+
search.disabled = busy !== void 0 || draft === void 0;
|
|
6204
|
+
filter.disabled = search.disabled;
|
|
6205
|
+
save.textContent = busy === "write" ? copy.saving : copy.save;
|
|
6206
|
+
changes.textContent = `${changed === 0 ? copy.unchanged : copy.changes.replace("{count}", String(changed))} · ${copy.nextOpen}`;
|
|
6207
|
+
list.setAttribute("aria-busy", String(busy !== void 0));
|
|
6208
|
+
for (const input of choices.values()) input.disabled = busy !== void 0 || input.dataset.required === "true";
|
|
6209
|
+
for (const action of invalidActions.values()) action.disabled = busy !== void 0;
|
|
6210
|
+
};
|
|
6211
|
+
const renderList = () => {
|
|
6212
|
+
choices.clear();
|
|
6213
|
+
invalidActions.clear();
|
|
6214
|
+
list.replaceChildren();
|
|
6215
|
+
if (draft === void 0) {
|
|
6216
|
+
results.textContent = "";
|
|
6217
|
+
return;
|
|
6218
|
+
}
|
|
6219
|
+
const visible = draft.matchingEntries(search.value, filterValue());
|
|
6220
|
+
const invalid = draft.matchingInvalidExclusions(search.value, filterValue());
|
|
6221
|
+
results.textContent = copy.results.replace("{visible}", String(visible.length + invalid.length)).replace("{total}", String(draft.entries.length + draft.invalidExclusions.length));
|
|
6222
|
+
const filtered = search.value.trim() !== "" || filterValue() !== "all";
|
|
6223
|
+
if (visible.length === 0 && invalid.length === 0) {
|
|
6224
|
+
const empty = document.createElement("div");
|
|
6225
|
+
empty.className = "dsh-module-empty";
|
|
6226
|
+
empty.append(paragraph(copy.empty));
|
|
6227
|
+
const clear = button(copy.clearFilters);
|
|
6228
|
+
clear.addEventListener("click", () => {
|
|
6229
|
+
search.value = "";
|
|
6230
|
+
filter.value = "all";
|
|
6231
|
+
renderList();
|
|
6232
|
+
search.focus({ preventScroll: true });
|
|
6233
|
+
});
|
|
6234
|
+
empty.append(clear);
|
|
6235
|
+
list.append(empty);
|
|
6236
|
+
}
|
|
6237
|
+
if (invalid.length > 0) {
|
|
6238
|
+
const section = document.createElement("section");
|
|
6239
|
+
section.className = "dsh-module-group";
|
|
6240
|
+
section.dataset.clientModuleGroup = "invalid";
|
|
6241
|
+
const heading = document.createElement("h3");
|
|
6242
|
+
heading.textContent = `${copy.invalidSelections} (${invalid.length})`;
|
|
6243
|
+
section.append(heading, paragraph(copy.invalidHint, "dsh-module-groupHint"));
|
|
6244
|
+
for (const entry of invalid) {
|
|
6245
|
+
const row = document.createElement("div");
|
|
6246
|
+
row.className = "dsh-module-entry dsh-module-invalid-entry";
|
|
6247
|
+
row.dataset.invalidClientModuleId = entry.id;
|
|
6248
|
+
const id = document.createElement("code");
|
|
6249
|
+
id.textContent = entry.id;
|
|
6250
|
+
const remove = button(copy.clearInvalid);
|
|
6251
|
+
remove.setAttribute("aria-label", `${copy.clearInvalid}: ${entry.id}`);
|
|
6252
|
+
remove.addEventListener("click", () => {
|
|
6253
|
+
if (disposed || busy !== void 0 || draft?.clearInvalidExclusion(entry.id) !== true) return;
|
|
6254
|
+
status.textContent = "";
|
|
6255
|
+
status.classList.remove("dsh-module-error");
|
|
6256
|
+
renderList();
|
|
6257
|
+
(invalidActions.values().next().value ?? save).focus({ preventScroll: true });
|
|
6258
|
+
});
|
|
6259
|
+
row.append(id, paragraph(entry.reason === "missing" ? copy.invalidMissing : copy.invalidRequired), remove);
|
|
6260
|
+
invalidActions.set(entry.id, remove);
|
|
6261
|
+
section.append(row);
|
|
5193
6262
|
}
|
|
6263
|
+
list.append(section);
|
|
5194
6264
|
}
|
|
5195
|
-
const
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
6265
|
+
for (const group of [
|
|
6266
|
+
"extensions",
|
|
6267
|
+
"advanced",
|
|
6268
|
+
"required"
|
|
6269
|
+
]) {
|
|
6270
|
+
const entries = visible.filter((entry) => clientModuleGroup(entry) === group);
|
|
6271
|
+
if (entries.length === 0) continue;
|
|
6272
|
+
const section = document.createElement(group === "extensions" ? "section" : "details");
|
|
6273
|
+
section.className = "dsh-module-group";
|
|
6274
|
+
section.dataset.group = group;
|
|
6275
|
+
section.dataset.clientModuleGroup = group === "advanced" ? "system" : group;
|
|
6276
|
+
const headingText = `${group === "required" ? copy.requiredGroup : copy[group]} (${entries.length})`;
|
|
6277
|
+
if (section instanceof HTMLDetailsElement) {
|
|
6278
|
+
section.open = filtered || expansion.get(group) === true;
|
|
6279
|
+
const heading = document.createElement("summary");
|
|
6280
|
+
heading.textContent = headingText;
|
|
6281
|
+
section.append(heading);
|
|
6282
|
+
section.addEventListener("toggle", () => {
|
|
6283
|
+
if (!filtered) expansion.set(group, section.open);
|
|
6284
|
+
});
|
|
6285
|
+
} else {
|
|
6286
|
+
const heading = document.createElement("h3");
|
|
6287
|
+
heading.textContent = headingText;
|
|
6288
|
+
section.append(heading);
|
|
6289
|
+
}
|
|
6290
|
+
const content = document.createElement("div");
|
|
6291
|
+
content.append(paragraph(group === "required" ? copy.requiredHint : group === "advanced" ? copy.advancedHint : copy.extensionsHint, "dsh-module-groupHint"));
|
|
6292
|
+
for (const entry of entries) {
|
|
6293
|
+
const row = document.createElement("div");
|
|
6294
|
+
row.className = "dsh-module-entry";
|
|
6295
|
+
row.dataset.clientModuleId = entry.id;
|
|
6296
|
+
const label = document.createElement("label");
|
|
6297
|
+
label.className = "dsh-module-choice";
|
|
6298
|
+
const input = document.createElement("input");
|
|
6299
|
+
input.type = "checkbox";
|
|
6300
|
+
input.checked = draft.isLoaded(entry.id);
|
|
6301
|
+
if (entry.required) input.dataset.required = "true";
|
|
6302
|
+
const name = document.createElement("span");
|
|
6303
|
+
const shortName = document.createElement("span");
|
|
6304
|
+
shortName.className = "dsh-module-choiceName";
|
|
6305
|
+
shortName.textContent = clientModuleDisplayName(entry.id);
|
|
6306
|
+
const selected = document.createElement("small");
|
|
6307
|
+
selected.textContent = entry.required ? copy.required : input.checked ? copy.loaded : copy.excluded;
|
|
6308
|
+
name.append(shortName, selected);
|
|
6309
|
+
label.append(input, name);
|
|
6310
|
+
row.append(label);
|
|
6311
|
+
const detail = document.createElement("details");
|
|
6312
|
+
detail.className = "dsh-module-entryDetails";
|
|
6313
|
+
const detailSummary = document.createElement("summary");
|
|
6314
|
+
detailSummary.textContent = copy.details;
|
|
6315
|
+
const id = paragraph(`${copy.moduleId}: `);
|
|
6316
|
+
const code = document.createElement("code");
|
|
6317
|
+
code.textContent = entry.id;
|
|
6318
|
+
id.append(code);
|
|
6319
|
+
detail.append(detailSummary, id, paragraph(entry.dependencies.length === 0 ? copy.noDependencies : `${copy.dependencies}${entry.dependencies.join(", ")}`));
|
|
6320
|
+
row.append(detail);
|
|
6321
|
+
input.addEventListener("change", () => {
|
|
6322
|
+
if (busy !== void 0 || draft === void 0) return;
|
|
6323
|
+
draft.setLoaded(entry.id, input.checked);
|
|
6324
|
+
input.checked = draft.isLoaded(entry.id);
|
|
6325
|
+
selected.textContent = entry.required ? copy.required : input.checked ? copy.loaded : copy.excluded;
|
|
6326
|
+
status.textContent = "";
|
|
6327
|
+
status.classList.remove("dsh-module-error");
|
|
6328
|
+
if (filterValue() !== "all") {
|
|
6329
|
+
renderList();
|
|
6330
|
+
filter.focus({ preventScroll: true });
|
|
6331
|
+
}
|
|
6332
|
+
updateControls();
|
|
6333
|
+
});
|
|
6334
|
+
choices.set(entry.id, input);
|
|
6335
|
+
content.append(row);
|
|
6336
|
+
}
|
|
6337
|
+
section.append(content);
|
|
6338
|
+
list.append(section);
|
|
5201
6339
|
}
|
|
5202
|
-
|
|
6340
|
+
updateControls();
|
|
5203
6341
|
};
|
|
5204
6342
|
const render = (view) => {
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
list.replaceChildren();
|
|
5209
|
-
for (const entry of view.entries) {
|
|
5210
|
-
const label = document.createElement("label");
|
|
5211
|
-
label.className = "dsh-module-choice";
|
|
5212
|
-
const input = document.createElement("input");
|
|
5213
|
-
input.type = "checkbox";
|
|
5214
|
-
input.checked = !view.excludedClientModules.includes(entry.id);
|
|
5215
|
-
input.disabled = entry.required;
|
|
5216
|
-
if (entry.required) input.dataset.required = "true";
|
|
5217
|
-
const name = document.createElement("span");
|
|
5218
|
-
name.textContent = entry.id;
|
|
5219
|
-
const hint = document.createElement("small");
|
|
5220
|
-
hint.textContent = entry.required ? copy.required : entry.dependencies.length === 0 ? "" : `${copy.dependencies}${entry.dependencies.join(", ")}`;
|
|
5221
|
-
name.append(hint);
|
|
5222
|
-
label.append(input, name);
|
|
5223
|
-
list.append(label);
|
|
5224
|
-
choices.set(entry.id, input);
|
|
5225
|
-
}
|
|
5226
|
-
status.classList.remove("dsh-module-error");
|
|
5227
|
-
status.textContent = view.source === "device" ? copy.deviceSource : copy.computerSource;
|
|
6343
|
+
draft = new ClientModuleDraft(view.entries, view.excludedClientModules);
|
|
6344
|
+
source.textContent = view.source === "device" ? copy.deviceSource : view.source === "computer" ? copy.computerSource : copy.pluginSource;
|
|
6345
|
+
renderList();
|
|
5228
6346
|
};
|
|
5229
|
-
const failed = (error) => {
|
|
5230
|
-
if (disposed || controller.signal.aborted) return;
|
|
6347
|
+
const failed = (error, mutation) => {
|
|
5231
6348
|
status.classList.add("dsh-module-error");
|
|
5232
|
-
status.textContent = error instanceof ClientModuleUiConflict ? `${copy.conflict}${error.detail}` : copy.failed;
|
|
6349
|
+
status.textContent = error instanceof ClientModuleUiConflict ? `${copy.conflict}${error.detail}` : error instanceof ClientModuleRequestTimeout ? error.mutation ? copy.writeTimeout : copy.readTimeout : mutation ? copy.saveFailed : copy.failed;
|
|
5233
6350
|
status.focus({ preventScroll: true });
|
|
5234
6351
|
};
|
|
5235
|
-
const
|
|
5236
|
-
if (busy ||
|
|
5237
|
-
|
|
5238
|
-
|
|
6352
|
+
const runRequest = (payload) => {
|
|
6353
|
+
if (disposed || busy !== void 0 || payload !== void 0 && draft === void 0) return;
|
|
6354
|
+
const ownGeneration = ++generation;
|
|
6355
|
+
busy = payload === void 0 ? "read" : "write";
|
|
6356
|
+
if (payload !== void 0) unconfirmedWrite = true;
|
|
6357
|
+
status.classList.remove("dsh-module-error");
|
|
6358
|
+
status.textContent = payload === void 0 ? copy.loading : copy.saving;
|
|
6359
|
+
updateControls();
|
|
6360
|
+
requestClientModuleView(scope, controller.signal, payload).then((view) => {
|
|
6361
|
+
if (disposed || ownGeneration !== generation) return;
|
|
6362
|
+
unconfirmedWrite = false;
|
|
5239
6363
|
render(view);
|
|
5240
|
-
|
|
5241
|
-
},
|
|
5242
|
-
if (
|
|
6364
|
+
status.textContent = payload === void 0 ? "" : copy.saved;
|
|
6365
|
+
}, (error) => {
|
|
6366
|
+
if (disposed || ownGeneration !== generation) return;
|
|
6367
|
+
if (error instanceof ClientModuleUiConflict) unconfirmedWrite = false;
|
|
6368
|
+
failed(error, payload !== void 0);
|
|
6369
|
+
}).finally(() => {
|
|
6370
|
+
if (disposed || ownGeneration !== generation) return;
|
|
6371
|
+
busy = void 0;
|
|
6372
|
+
updateControls();
|
|
5243
6373
|
});
|
|
5244
6374
|
};
|
|
6375
|
+
search.addEventListener("input", renderList);
|
|
6376
|
+
filter.addEventListener("change", renderList);
|
|
5245
6377
|
save.addEventListener("click", () => {
|
|
5246
|
-
|
|
6378
|
+
if ((draft?.changeCount ?? 0) > 0) runRequest({ excludedClientModules: draft?.excludedIds });
|
|
5247
6379
|
});
|
|
5248
6380
|
reset.addEventListener("click", () => {
|
|
5249
|
-
if (window.confirm(copy.resetConfirm))
|
|
6381
|
+
if (busy === void 0 && draft !== void 0 && window.confirm(copy.resetConfirm)) runRequest({ reset: true });
|
|
6382
|
+
});
|
|
6383
|
+
reread.addEventListener("click", () => {
|
|
6384
|
+
if (busy === void 0 && ((draft?.changeCount ?? 0) === 0 || window.confirm(copy.reloadConfirm))) runRequest();
|
|
5250
6385
|
});
|
|
5251
|
-
const onBack = (event) => {
|
|
5252
|
-
if (!event.cancelable || !dialog.open || event.defaultPrevented) return;
|
|
5253
|
-
event.preventDefault();
|
|
5254
|
-
event.stopImmediatePropagation();
|
|
5255
|
-
dispose();
|
|
5256
|
-
};
|
|
5257
|
-
window.addEventListener("dsh-mobile:native-back", onBack, true);
|
|
5258
6386
|
const dispose = () => {
|
|
5259
6387
|
if (disposed) return;
|
|
5260
6388
|
disposed = true;
|
|
6389
|
+
++generation;
|
|
5261
6390
|
controller.abort();
|
|
5262
|
-
openDialogs.delete(
|
|
6391
|
+
openDialogs.delete(openDialog);
|
|
5263
6392
|
window.removeEventListener("dsh-mobile:native-back", onBack, true);
|
|
6393
|
+
window.removeEventListener("keydown", onEscape, true);
|
|
5264
6394
|
if (dialog.open) dialog.close();
|
|
5265
6395
|
dialog.remove();
|
|
5266
6396
|
if (trigger?.isConnected) trigger.focus({ preventScroll: true });
|
|
5267
6397
|
};
|
|
5268
|
-
|
|
6398
|
+
const requestClose = () => {
|
|
6399
|
+
if (disposed) return;
|
|
6400
|
+
if (unconfirmedWrite && !window.confirm(copy.savingCloseConfirm)) return;
|
|
6401
|
+
if (!unconfirmedWrite && (draft?.changeCount ?? 0) > 0 && !window.confirm(copy.discardConfirm)) return;
|
|
6402
|
+
dispose();
|
|
6403
|
+
};
|
|
6404
|
+
const onBack = (event) => {
|
|
6405
|
+
if (!event.cancelable || !dialog.open || event.defaultPrevented || [...openDialogs].at(-1) !== openDialog) return;
|
|
6406
|
+
event.preventDefault();
|
|
6407
|
+
event.stopImmediatePropagation();
|
|
6408
|
+
requestClose();
|
|
6409
|
+
};
|
|
6410
|
+
const onEscape = (event) => {
|
|
6411
|
+
if (event.key !== "Escape" || event.isComposing || event.ctrlKey || event.altKey || event.metaKey || event.defaultPrevented || !dialog.open || [...openDialogs].at(-1) !== openDialog) return;
|
|
6412
|
+
event.preventDefault();
|
|
6413
|
+
event.stopImmediatePropagation();
|
|
6414
|
+
requestClose();
|
|
6415
|
+
};
|
|
6416
|
+
const openDialog = {
|
|
6417
|
+
dispose,
|
|
6418
|
+
requestClose
|
|
6419
|
+
};
|
|
6420
|
+
openDialogs.add(openDialog);
|
|
6421
|
+
window.addEventListener("dsh-mobile:native-back", onBack, true);
|
|
6422
|
+
window.addEventListener("keydown", onEscape, true);
|
|
6423
|
+
dialog.addEventListener("cancel", (event) => {
|
|
6424
|
+
event.preventDefault();
|
|
6425
|
+
requestClose();
|
|
6426
|
+
});
|
|
5269
6427
|
dialog.addEventListener("close", dispose, { once: true });
|
|
5270
|
-
close.addEventListener("click",
|
|
6428
|
+
close.addEventListener("click", requestClose);
|
|
5271
6429
|
document.body.append(dialog);
|
|
5272
6430
|
dialog.showModal();
|
|
5273
|
-
|
|
5274
|
-
status.textContent = copy.loading;
|
|
5275
|
-
request().then(render, failed).finally(() => {
|
|
5276
|
-
if (!disposed) setBusy(false);
|
|
5277
|
-
});
|
|
6431
|
+
runRequest();
|
|
5278
6432
|
}
|
|
5279
6433
|
/** Settings row used on mobile pages and on the computer's General settings.
|
|
5280
6434
|
* @param props - Selection scope and current locale.
|
|
@@ -5780,42 +6934,29 @@ window.__ModuleLoader__.load({
|
|
|
5780
6934
|
error: "Could not switch computers. Try again."
|
|
5781
6935
|
};
|
|
5782
6936
|
}
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
const
|
|
5786
|
-
const [state, setState] = (0, react.useState)("idle");
|
|
6937
|
+
function useMobileNativeSettingsAction(action) {
|
|
6938
|
+
const [controller] = (0, react.useState)(() => new MobileNativeSettingsActionController(action, () => window.__DSH_MOBILE_NATIVE__));
|
|
6939
|
+
const snapshot = (0, react.useSyncExternalStore)(controller.subscribe, controller.getSnapshot);
|
|
5787
6940
|
(0, react.useEffect)(() => {
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
const bridge = window.__DSH_MOBILE_NATIVE__;
|
|
5791
|
-
if (bridge === void 0) {
|
|
5792
|
-
setNativeReady(false);
|
|
5793
|
-
return;
|
|
5794
|
-
}
|
|
5795
|
-
Promise.resolve().then(() => bridge.capabilities()).then((capabilities) => {
|
|
5796
|
-
if (!disposed) setNativeReady(capabilities.includes("mobile.switch-computer"));
|
|
5797
|
-
}, () => {
|
|
5798
|
-
if (!disposed) setNativeReady(false);
|
|
5799
|
-
});
|
|
5800
|
-
};
|
|
5801
|
-
checkNativeAction();
|
|
5802
|
-
window.addEventListener("dsh-mobile-native-ready", checkNativeAction);
|
|
6941
|
+
controller.start();
|
|
6942
|
+
window.addEventListener("dsh-mobile-native-ready", controller.refresh);
|
|
5803
6943
|
return () => {
|
|
5804
|
-
|
|
5805
|
-
|
|
6944
|
+
window.removeEventListener("dsh-mobile-native-ready", controller.refresh);
|
|
6945
|
+
controller.stop();
|
|
5806
6946
|
};
|
|
5807
|
-
}, []);
|
|
5808
|
-
|
|
6947
|
+
}, [controller]);
|
|
6948
|
+
return {
|
|
6949
|
+
...snapshot,
|
|
6950
|
+
invoke: controller.invoke
|
|
6951
|
+
};
|
|
6952
|
+
}
|
|
6953
|
+
/** Render the native-app-only action in DSH's General settings row style. */
|
|
6954
|
+
function MobileSwitchComputerRow() {
|
|
6955
|
+
const { supported, state, invoke } = useMobileNativeSettingsAction("mobile.switch-computer");
|
|
6956
|
+
if (!supported) return null;
|
|
5809
6957
|
const locale = selectedMobileControlLocale();
|
|
5810
6958
|
const copy = mobileSwitchComputerCopy(locale);
|
|
5811
|
-
const
|
|
5812
|
-
const bridge = window.__DSH_MOBILE_NATIVE__;
|
|
5813
|
-
if (bridge === void 0 || state === "busy") return;
|
|
5814
|
-
setState("busy");
|
|
5815
|
-
bridge.invoke("mobile.switch-computer", {}).catch(() => {
|
|
5816
|
-
setState("error");
|
|
5817
|
-
});
|
|
5818
|
-
};
|
|
6959
|
+
const busy = state === "busy" || state === "success";
|
|
5819
6960
|
return (0, react.createElement)("div", {
|
|
5820
6961
|
className: "dsh-mobile-settings_row",
|
|
5821
6962
|
lang: locale
|
|
@@ -5825,10 +6966,10 @@ window.__ModuleLoader__.load({
|
|
|
5825
6966
|
}, state === "error" ? copy.error : copy.description)), (0, react.createElement)("button", {
|
|
5826
6967
|
type: "button",
|
|
5827
6968
|
className: "dsh-mobile-settings_selector",
|
|
5828
|
-
disabled:
|
|
5829
|
-
"aria-busy":
|
|
6969
|
+
disabled: busy,
|
|
6970
|
+
"aria-busy": busy ? "true" : void 0,
|
|
5830
6971
|
onClick: invoke
|
|
5831
|
-
},
|
|
6972
|
+
}, busy ? copy.busy : state === "error" ? copy.retry : copy.action));
|
|
5832
6973
|
}
|
|
5833
6974
|
function mobileTaskNotificationCopy(locale) {
|
|
5834
6975
|
if (locale === "zh") return {
|
|
@@ -5864,42 +7005,10 @@ window.__ModuleLoader__.load({
|
|
|
5864
7005
|
}
|
|
5865
7006
|
/** Render the Android-only notification permission entry in DSH General settings. */
|
|
5866
7007
|
function MobileTaskNotificationRow() {
|
|
5867
|
-
const
|
|
5868
|
-
|
|
5869
|
-
(0, react.useEffect)(() => {
|
|
5870
|
-
let disposed = false;
|
|
5871
|
-
const checkNativeAction = () => {
|
|
5872
|
-
const bridge = window.__DSH_MOBILE_NATIVE__;
|
|
5873
|
-
if (bridge === void 0) {
|
|
5874
|
-
setNativeReady(false);
|
|
5875
|
-
return;
|
|
5876
|
-
}
|
|
5877
|
-
Promise.resolve().then(() => bridge.capabilities()).then((capabilities) => {
|
|
5878
|
-
if (!disposed) setNativeReady(capabilities.includes("notification.settings"));
|
|
5879
|
-
}, () => {
|
|
5880
|
-
if (!disposed) setNativeReady(false);
|
|
5881
|
-
});
|
|
5882
|
-
};
|
|
5883
|
-
checkNativeAction();
|
|
5884
|
-
window.addEventListener("dsh-mobile-native-ready", checkNativeAction);
|
|
5885
|
-
return () => {
|
|
5886
|
-
disposed = true;
|
|
5887
|
-
window.removeEventListener("dsh-mobile-native-ready", checkNativeAction);
|
|
5888
|
-
};
|
|
5889
|
-
}, []);
|
|
5890
|
-
if (!nativeReady) return null;
|
|
7008
|
+
const { supported, state, invoke } = useMobileNativeSettingsAction("notification.settings");
|
|
7009
|
+
if (!supported) return null;
|
|
5891
7010
|
const locale = selectedMobileControlLocale();
|
|
5892
7011
|
const copy = mobileTaskNotificationCopy(locale);
|
|
5893
|
-
const invoke = () => {
|
|
5894
|
-
const bridge = window.__DSH_MOBILE_NATIVE__;
|
|
5895
|
-
if (bridge === void 0 || state === "busy") return;
|
|
5896
|
-
setState("busy");
|
|
5897
|
-
bridge.invoke("notification.settings", {}).then(() => {
|
|
5898
|
-
setState("success");
|
|
5899
|
-
}, () => {
|
|
5900
|
-
setState("error");
|
|
5901
|
-
});
|
|
5902
|
-
};
|
|
5903
7012
|
return (0, react.createElement)("div", {
|
|
5904
7013
|
className: "dsh-mobile-settings_row",
|
|
5905
7014
|
lang: locale
|
|
@@ -6069,6 +7178,11 @@ window.__ModuleLoader__.load({
|
|
|
6069
7178
|
cpolar_installer_missing: "cpolarExtractFailed",
|
|
6070
7179
|
cpolar_executable_missing: "cpolarExtractFailed",
|
|
6071
7180
|
cpolar_extract_failed: "cpolarExtractFailed",
|
|
7181
|
+
cpolar_extract_tools_failed: "cpolarExtractToolsFailed",
|
|
7182
|
+
cpolar_extract_archive_failed: "cpolarExtractArchiveFailed",
|
|
7183
|
+
cpolar_extract_database_failed: "cpolarExtractDatabaseFailed",
|
|
7184
|
+
cpolar_extract_cabinet_failed: "cpolarExtractCabinetFailed",
|
|
7185
|
+
cpolar_extract_file_failed: "cpolarExtractFileFailed",
|
|
6072
7186
|
cpolar_storage_failed: "cpolarStorageFailed",
|
|
6073
7187
|
cpolar_config_missing: "cpolarConfigMissing",
|
|
6074
7188
|
cpolar_config_invalid: "cpolarConfigInvalid",
|
|
@@ -6150,6 +7264,11 @@ window.__ModuleLoader__.load({
|
|
|
6150
7264
|
"cpolar_component_invalid",
|
|
6151
7265
|
"cpolar_config_missing",
|
|
6152
7266
|
"cpolar_config_invalid",
|
|
7267
|
+
"cpolar_extract_tools_failed",
|
|
7268
|
+
"cpolar_extract_archive_failed",
|
|
7269
|
+
"cpolar_extract_database_failed",
|
|
7270
|
+
"cpolar_extract_cabinet_failed",
|
|
7271
|
+
"cpolar_extract_file_failed",
|
|
6153
7272
|
"funnel_state_invalid",
|
|
6154
7273
|
"cpolar_start_timeout",
|
|
6155
7274
|
"cpolar_stopped",
|
|
@@ -10136,6 +11255,7 @@ window.__ModuleLoader__.load({
|
|
|
10136
11255
|
const activations = new PerIdActivationLifecycle();
|
|
10137
11256
|
const styleNodes = /* @__PURE__ */ new Map();
|
|
10138
11257
|
const styleEtags = /* @__PURE__ */ new Map();
|
|
11258
|
+
const scriptEtags = /* @__PURE__ */ new Map();
|
|
10139
11259
|
const scriptDigests = /* @__PURE__ */ new Map();
|
|
10140
11260
|
const activeHostGenerations = /* @__PURE__ */ new Map();
|
|
10141
11261
|
const activationKeys = /* @__PURE__ */ new Map();
|
|
@@ -10516,6 +11636,7 @@ window.__ModuleLoader__.load({
|
|
|
10516
11636
|
styleNodes.get(id)?.remove();
|
|
10517
11637
|
styleNodes.delete(id);
|
|
10518
11638
|
styleEtags.delete(id);
|
|
11639
|
+
scriptEtags.delete(id);
|
|
10519
11640
|
scriptDigests.delete(id);
|
|
10520
11641
|
activeHostGenerations.delete(id);
|
|
10521
11642
|
activationKeys.delete(id);
|
|
@@ -10524,6 +11645,7 @@ window.__ModuleLoader__.load({
|
|
|
10524
11645
|
const managedManifestIdSources = () => [
|
|
10525
11646
|
styleNodes.keys(),
|
|
10526
11647
|
styleEtags.keys(),
|
|
11648
|
+
scriptEtags.keys(),
|
|
10527
11649
|
scriptDigests.keys(),
|
|
10528
11650
|
managedDefinitionIds
|
|
10529
11651
|
];
|
|
@@ -10617,12 +11739,13 @@ window.__ModuleLoader__.load({
|
|
|
10617
11739
|
commitStyle(entry.id, styleChange, pendingCss, pendingStyleEtag);
|
|
10618
11740
|
activations.remove(entry.id);
|
|
10619
11741
|
activeHostGenerations.delete(entry.id);
|
|
11742
|
+
scriptEtags.delete(entry.id);
|
|
10620
11743
|
scriptDigests.delete(entry.id);
|
|
10621
11744
|
if (managedDefinitionIds.delete(entry.id)) definitions.delete(entry.id);
|
|
10622
11745
|
} else {
|
|
10623
11746
|
const scriptHeaders = {};
|
|
10624
|
-
const
|
|
10625
|
-
if (
|
|
11747
|
+
const storedEtag = scriptEtags.get(entry.id);
|
|
11748
|
+
if (storedEtag !== void 0) scriptHeaders["if-none-match"] = storedEtag;
|
|
10626
11749
|
const scriptResponse = await fetch(scriptUrl, {
|
|
10627
11750
|
credentials: "same-origin",
|
|
10628
11751
|
cache: "no-store",
|
|
@@ -10630,8 +11753,10 @@ window.__ModuleLoader__.load({
|
|
|
10630
11753
|
signal
|
|
10631
11754
|
});
|
|
10632
11755
|
let nextDigest;
|
|
11756
|
+
let nextEtag;
|
|
10633
11757
|
if (scriptResponse.status !== 304) {
|
|
10634
11758
|
if (!scriptResponse.ok) throw new Error("mobile extension script failed to load");
|
|
11759
|
+
nextEtag = scriptResponse.headers.get("etag") ?? void 0;
|
|
10635
11760
|
const source = await scriptResponse.text();
|
|
10636
11761
|
if (refreshAborted(signal)) return false;
|
|
10637
11762
|
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(source));
|
|
@@ -10673,6 +11798,10 @@ window.__ModuleLoader__.load({
|
|
|
10673
11798
|
}
|
|
10674
11799
|
managedDefinitionIds.add(entry.id);
|
|
10675
11800
|
if (nextDigest !== void 0) scriptDigests.set(entry.id, nextDigest);
|
|
11801
|
+
if (scriptResponse.status !== 304) {
|
|
11802
|
+
if (nextEtag === void 0 || nextEtag === "") scriptEtags.delete(entry.id);
|
|
11803
|
+
else scriptEtags.set(entry.id, nextEtag);
|
|
11804
|
+
}
|
|
10676
11805
|
}
|
|
10677
11806
|
} catch {
|
|
10678
11807
|
if (evaluatedDefinition) {
|