dsh-mobile 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/CONTRIBUTORS.md +12 -4
- package/README.en.md +11 -38
- package/README.md +11 -38
- package/SECURITY.md +3 -2
- package/apps/mobile/README.md +132 -0
- package/apps/mobile/release.json +5 -0
- package/cordis.patch.yml +2 -1
- package/docs/ATTACH_EXISTING_FRPS.md +1 -1
- package/docs/CADDY_MANAGED.en.md +15 -1
- package/docs/CADDY_MANAGED.md +15 -1
- package/docs/CLIENT_MODULES.en.md +2 -2
- package/docs/CLIENT_MODULES.md +2 -2
- package/docs/CLOUDFLARE_TUNNEL.en.md +2 -2
- package/docs/CLOUDFLARE_TUNNEL.md +3 -3
- package/docs/EXTENSION_WORKERS.en.md +49 -0
- package/docs/EXTENSION_WORKERS.md +49 -0
- package/docs/MOBILE_COMPOSER_TOOLBAR.en.md +19 -0
- package/docs/MOBILE_COMPOSER_TOOLBAR.md +19 -0
- package/docs/README.md +2 -0
- package/docs/SELF_HOSTED_FRP.en.md +1 -1
- package/docs/SELF_HOSTED_FRP.md +1 -1
- package/docs/SLOW_CONNECTIONS.en.md +1 -1
- package/docs/SLOW_CONNECTIONS.md +1 -1
- package/docs/TRUSTED_NETWORKS.en.md +1 -1
- package/docs/TRUSTED_NETWORKS.md +1 -1
- package/lib/cli.js +1 -0
- package/lib/cli.js.map +1 -1
- package/lib/client.js +866 -99
- package/lib/client.js.map +1 -1
- package/lib/extension-worker-runtime.mjs +1646 -0
- package/lib/extension-worker-runtime.mjs.map +1 -0
- package/lib/index.d.mts +199 -7
- package/lib/index.mjs +1645 -586
- package/lib/index.mjs.map +1 -1
- package/lib/mobile-compat.js +1 -1
- package/lib/mobile-compat.js.map +1 -1
- package/lib/mobile-layout.js +2 -1
- package/lib/mobile-layout.js.map +1 -1
- package/package.json +17 -6
- package/packages/question-fixes/LICENSE +201 -0
- package/packages/question-fixes/package.json +9 -2
- package/node_modules/dsh-mobile-question-fixes/lib/client.js +0 -78
- package/node_modules/dsh-mobile-question-fixes/lib/client.js.map +0 -1
- package/node_modules/dsh-mobile-question-fixes/lib/index.mjs +0 -14
- package/node_modules/dsh-mobile-question-fixes/lib/index.mjs.map +0 -1
- package/node_modules/dsh-mobile-question-fixes/package.json +0 -25
package/lib/client.js
CHANGED
|
@@ -6,6 +6,59 @@ window.__ModuleLoader__.load({
|
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
8
|
//#region src/client-messages.ts
|
|
9
|
+
const storageMessages = {
|
|
10
|
+
en: {
|
|
11
|
+
funnelStateInvalid: "The private Tailscale state folder cannot be accessed safely. Check its permissions or directory links; reinstalling the component is not required.",
|
|
12
|
+
componentPathUnsafe: "The component folder contains an unsafe link or cannot be accessed. Check the directory and permissions, then retry."
|
|
13
|
+
},
|
|
14
|
+
zh: {
|
|
15
|
+
funnelStateInvalid: "Tailscale 私有登录目录无法安全访问。请检查目录权限或链接,不必重新安装组件。",
|
|
16
|
+
componentPathUnsafe: "组件目录包含不安全的链接或无法访问。请检查目录和权限后重试。"
|
|
17
|
+
},
|
|
18
|
+
it: {
|
|
19
|
+
funnelStateInvalid: "La cartella privata Tailscale non è accessibile in sicurezza. Controlla permessi o collegamenti; non occorre reinstallare il componente.",
|
|
20
|
+
componentPathUnsafe: "La cartella del componente contiene un collegamento non sicuro o non è accessibile. Controlla directory e permessi, poi riprova."
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
/** Desktop-only stopped-worker recovery, independent of remote provider state. */
|
|
24
|
+
const EXTENSION_RECOVERY_MESSAGES = {
|
|
25
|
+
en: {
|
|
26
|
+
extensionRecoveryTitle: "An extension needs a restart",
|
|
27
|
+
extensionRecoveryHelp: "Only the stopped extension restarts. Unfinished actions are not retried.",
|
|
28
|
+
extensionRecoveryRestart: "Restart",
|
|
29
|
+
extensionRecoveryRestarting: "Restarting…",
|
|
30
|
+
extensionRecoveryRestartAria: "Restart extension {name}",
|
|
31
|
+
extensionRecoveryConfirm: "Restart extension “{name}”? Unfinished actions will not be retried. Previously completed changes will not be undone.",
|
|
32
|
+
extensionRecoveryRefresh: "Check again",
|
|
33
|
+
extensionRecoveryLoadFailed: "Could not check the extension status. Check again before restarting.",
|
|
34
|
+
extensionRecoveryFailed: "The extension did not restart. Check its configuration, then retry.",
|
|
35
|
+
extensionRecoveryChanged: "The extension files changed. Review the changes and reload the extension before trying again."
|
|
36
|
+
},
|
|
37
|
+
it: {
|
|
38
|
+
extensionRecoveryTitle: "Un’estensione richiede un riavvio",
|
|
39
|
+
extensionRecoveryHelp: "Si riavvia solo l’estensione arrestata. Le azioni incompiute non vengono ripetute.",
|
|
40
|
+
extensionRecoveryRestart: "Riavvia",
|
|
41
|
+
extensionRecoveryRestarting: "Riavvio…",
|
|
42
|
+
extensionRecoveryRestartAria: "Riavvia l’estensione {name}",
|
|
43
|
+
extensionRecoveryConfirm: "Riavviare l’estensione “{name}”? Le azioni incompiute non verranno ripetute. Le modifiche già completate non saranno annullate.",
|
|
44
|
+
extensionRecoveryRefresh: "Controlla di nuovo",
|
|
45
|
+
extensionRecoveryLoadFailed: "Impossibile verificare lo stato. Controlla di nuovo prima di riavviare.",
|
|
46
|
+
extensionRecoveryFailed: "L’estensione non si è riavviata. Controlla la configurazione e riprova.",
|
|
47
|
+
extensionRecoveryChanged: "I file dell’estensione sono cambiati. Verifica le modifiche e ricarica l’estensione prima di riprovare."
|
|
48
|
+
},
|
|
49
|
+
zh: {
|
|
50
|
+
extensionRecoveryTitle: "扩展需要重启",
|
|
51
|
+
extensionRecoveryHelp: "仅重启已停止的扩展,不会重新执行未完成的操作。",
|
|
52
|
+
extensionRecoveryRestart: "重启扩展",
|
|
53
|
+
extensionRecoveryRestarting: "正在重启…",
|
|
54
|
+
extensionRecoveryRestartAria: "重启扩展 {name}",
|
|
55
|
+
extensionRecoveryConfirm: "要重启扩展“{name}”吗?未完成的操作不会重新执行,已经完成的修改也不会撤销。",
|
|
56
|
+
extensionRecoveryRefresh: "重新检测",
|
|
57
|
+
extensionRecoveryLoadFailed: "暂时无法检查扩展状态,请重新检测后再操作。",
|
|
58
|
+
extensionRecoveryFailed: "扩展没有重启成功,请检查配置后重试。",
|
|
59
|
+
extensionRecoveryChanged: "扩展文件已经变化,请核对修改并重新加载扩展后再操作。"
|
|
60
|
+
}
|
|
61
|
+
};
|
|
9
62
|
const MOBILE_CONTROL_MESSAGES = {
|
|
10
63
|
en: {
|
|
11
64
|
mobileAccess: "Mobile access",
|
|
@@ -345,6 +398,16 @@ window.__ModuleLoader__.load({
|
|
|
345
398
|
},
|
|
346
399
|
zh: {}
|
|
347
400
|
};
|
|
401
|
+
for (const locale of [
|
|
402
|
+
"en",
|
|
403
|
+
"it",
|
|
404
|
+
"zh"
|
|
405
|
+
]) Object.assign(MOBILE_CONTROL_MESSAGES[locale], EXTENSION_RECOVERY_MESSAGES[locale]);
|
|
406
|
+
for (const locale of [
|
|
407
|
+
"en",
|
|
408
|
+
"it",
|
|
409
|
+
"zh"
|
|
410
|
+
]) Object.assign(MOBILE_CONTROL_MESSAGES[locale], storageMessages[locale]);
|
|
348
411
|
Object.assign(MOBILE_CONTROL_MESSAGES.zh, {
|
|
349
412
|
mobileAccess: "移动访问",
|
|
350
413
|
collapseMobileAccess: "收起移动访问",
|
|
@@ -1136,7 +1199,9 @@ window.__ModuleLoader__.load({
|
|
|
1136
1199
|
Object.assign(MOBILE_CONTROL_MESSAGES.en, {
|
|
1137
1200
|
selfHostedDescription: "Use your own VPS or HTTPS reverse proxy without a public tunnel service.",
|
|
1138
1201
|
originName: "Own reverse proxy",
|
|
1139
|
-
|
|
1202
|
+
caddyName: "Managed Caddy",
|
|
1203
|
+
caddyModePreviewNote: "HTTPS form preview differs from the current connection.",
|
|
1204
|
+
originDescription: "Use managed Caddy or your existing HTTPS reverse proxy with DSH Mobile’s private, authenticated backend.",
|
|
1140
1205
|
prepareOrigin: "Connect your HTTPS reverse proxy",
|
|
1141
1206
|
originPublicOrigin: "Public HTTPS address",
|
|
1142
1207
|
originListenHost: "Private listen IPv4",
|
|
@@ -1193,7 +1258,9 @@ window.__ModuleLoader__.load({
|
|
|
1193
1258
|
Object.assign(MOBILE_CONTROL_MESSAGES.it, {
|
|
1194
1259
|
selfHostedDescription: "Usa il tuo VPS o reverse proxy HTTPS senza un servizio di tunnel pubblico.",
|
|
1195
1260
|
originName: "Reverse proxy proprio",
|
|
1196
|
-
|
|
1261
|
+
caddyName: "Caddy gestito",
|
|
1262
|
+
caddyModePreviewNote: "L’anteprima del modulo HTTPS differisce dalla connessione attuale.",
|
|
1263
|
+
originDescription: "Usa Caddy gestito o il tuo reverse proxy HTTPS con il backend privato autenticato di DSH Mobile.",
|
|
1197
1264
|
prepareOrigin: "Collega il tuo reverse proxy HTTPS",
|
|
1198
1265
|
originPublicOrigin: "Indirizzo HTTPS pubblico",
|
|
1199
1266
|
originListenHost: "IPv4 privato di ascolto",
|
|
@@ -1250,7 +1317,9 @@ window.__ModuleLoader__.load({
|
|
|
1250
1317
|
Object.assign(MOBILE_CONTROL_MESSAGES.zh, {
|
|
1251
1318
|
selfHostedDescription: "使用自己的 VPS 或 HTTPS 反向代理,不依赖公共隧道服务。",
|
|
1252
1319
|
originName: "自有反向代理",
|
|
1253
|
-
|
|
1320
|
+
caddyName: "托管 Caddy",
|
|
1321
|
+
caddyModePreviewNote: "HTTPS 表单预览与当前连接不同。",
|
|
1322
|
+
originDescription: "使用托管 Caddy 或已有 HTTPS 反向代理,接入 DSH Mobile 带认证的私有后端。",
|
|
1254
1323
|
prepareOrigin: "接入自有 HTTPS 反向代理",
|
|
1255
1324
|
originPublicOrigin: "公网 HTTPS 地址",
|
|
1256
1325
|
originListenHost: "私有监听 IPv4",
|
|
@@ -2707,6 +2776,38 @@ window.__ModuleLoader__.load({
|
|
|
2707
2776
|
if (anchorTimer !== 0) window.clearTimeout(anchorTimer);
|
|
2708
2777
|
};
|
|
2709
2778
|
}
|
|
2779
|
+
//#endregion
|
|
2780
|
+
//#region src/mobile-composer-toolbar.ts
|
|
2781
|
+
/**
|
|
2782
|
+
* Compact the stock InputBar without changing its React ownership, handlers,
|
|
2783
|
+
* activity tree or the two groups observed by DSH's model-width controller.
|
|
2784
|
+
* Extra controls wrap inside their own group; menus remain overflow-visible.
|
|
2785
|
+
*/
|
|
2786
|
+
const MOBILE_COMPOSER_TOOLBAR_STYLES = `
|
|
2787
|
+
/* These markers are attached only to the stock composer on the mobile page.
|
|
2788
|
+
Body portals and inline dialogs are not part of the compact toolbar. */
|
|
2789
|
+
[data-dsh-mobile-composer-row] { min-width:0 !important; max-width:100% !important; }
|
|
2790
|
+
[data-dsh-mobile-composer-tools]:not([hidden]),[data-dsh-mobile-composer-controls]:not([hidden]) { flex-wrap:wrap !important; min-width:0 !important; max-width:100% !important; }
|
|
2791
|
+
[data-dsh-mobile-composer-tools] > :not(:where(dialog,[popover],[role="dialog"],[role="menu"],[role="listbox"],[data-trigger-menu])),
|
|
2792
|
+
[data-dsh-mobile-composer-tools] > [data-slot] > :not(:where(dialog,[popover],[role="dialog"],[role="menu"],[role="listbox"],[data-trigger-menu])),
|
|
2793
|
+
[data-dsh-mobile-composer-controls] > :not(:where(dialog,[popover],[role="dialog"],[role="menu"],[role="listbox"],[data-trigger-menu])),
|
|
2794
|
+
[data-dsh-mobile-composer-controls] > [data-slot] > :not(:where(dialog,[popover],[role="dialog"],[role="menu"],[role="listbox"],[data-trigger-menu])) { min-width:0 !important; max-width:100% !important; }
|
|
2795
|
+
[data-dsh-mobile-composer-tools] button:not(:where(dialog *,[popover] *,[role="dialog"] *,[role="menu"] *,[role="listbox"] *,[data-trigger-menu] *)),
|
|
2796
|
+
[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
|
+
@media (max-width:720px) {
|
|
2798
|
+
[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; }
|
|
2799
|
+
[data-dsh-mobile-composer-trailing] > button[class*="_primary"] { min-width:44px !important; flex-shrink:0 !important; }
|
|
2800
|
+
[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
|
+
[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; }
|
|
2802
|
+
/* The activity seat can be wider than a single icon. Intrinsic demand, not
|
|
2803
|
+
a fixed 100px reserve, decides whether this intact group needs to wrap. */
|
|
2804
|
+
[data-dsh-mobile-composer-trailing] { display:flex !important; flex:1 0 auto !important; flex-wrap:nowrap !important; width:auto !important; min-width:0 !important; max-width:100% !important; gap:6px !important; margin-left:0 !important; justify-content:flex-end !important; }
|
|
2805
|
+
[data-dsh-mobile-composer-controls]:not([hidden]) { display:flex !important; flex:1 1 0 !important; flex-wrap:wrap !important; justify-content:flex-end !important; min-width:44px !important; max-width:100% !important; gap:6px !important; }
|
|
2806
|
+
[data-dsh-mobile-composer-model] { flex:0 1 auto !important; width:auto !important; min-width:44px !important; max-width:100% !important; }
|
|
2807
|
+
[data-dsh-mobile-composer-model-trigger] { box-sizing:border-box !important; width:100% !important; max-width:100% !important; min-width:0 !important; padding-left:6px !important; padding-right:4px !important; }
|
|
2808
|
+
[data-dsh-mobile-composer-model-label] { flex:1 1 auto !important; max-width:none !important; min-width:0 !important; overflow:hidden !important; text-overflow:ellipsis !important; white-space:nowrap !important; }
|
|
2809
|
+
}
|
|
2810
|
+
`;
|
|
2710
2811
|
//#endregion
|
|
2711
2812
|
//#region src/native-mobile.ts
|
|
2712
2813
|
/**
|
|
@@ -2747,13 +2848,15 @@ window.__ModuleLoader__.load({
|
|
|
2747
2848
|
.dsh-mobile-settings_fontControls { display:flex; align-items:center; gap:8px; flex-shrink:0; }
|
|
2748
2849
|
.dsh-mobile-settings_fontControls button { min-width:48px; padding:0 12px; }
|
|
2749
2850
|
.dsh-mobile-settings_fontControls output { min-width:42px; text-align:center; font-variant-numeric:tabular-nums; }
|
|
2851
|
+
.dsh-mobile-settings_fontDetails { min-width:0; padding:0 0 16px; border-bottom:0.5px solid var(--dsw-alias-border-l2); }
|
|
2852
|
+
.dsh-mobile-settings_fontDetails > summary { display:list-item; min-height:48px; padding:14px 0; box-sizing:border-box; color:var(--dsw-alias-label-primary); font-size:14px; cursor:pointer; }
|
|
2853
|
+
.dsh-mobile-settings_fontDetails .dsh-mobile-settings_rowText { padding-right:8px; }
|
|
2854
|
+
.dsh-mobile-settings_fontFamily { display:flex; flex-wrap:wrap; align-items:center; gap:8px; min-width:0; padding:12px 0; }
|
|
2855
|
+
.dsh-mobile-settings_fontFamily > span { flex:1 1 100px; }
|
|
2856
|
+
.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
|
+
.dsh-mobile-settings_fontFamily input:focus-visible,.dsh-mobile-settings_fontDetails > summary:focus-visible { outline:2px solid var(--dsw-alias-label-primary-bluish,#2563eb); outline-offset:2px; }
|
|
2750
2858
|
/* The mobile row replaces the Host-backed font control, not the theme selector. */
|
|
2751
2859
|
html.dsh-native-mobile-active [data-slot="settings.general.item"]:has([data-mobile-font-setting]) > :not([data-mobile-font-setting])[class*="_row"]:has([class*="_control"] > [class*="_stepper"] > [class*="_arrows"]) { display:none !important; }
|
|
2752
|
-
/* Landscape and wide App windows also constrain standard extension controls. */
|
|
2753
|
-
[data-dsh-mobile-composer-row] { min-width:0 !important; max-width:100% !important; }
|
|
2754
|
-
[data-dsh-mobile-composer-tools]:not([hidden]),[data-dsh-mobile-composer-controls]:not([hidden]) { flex-wrap:wrap !important; min-width:0 !important; max-width:100% !important; }
|
|
2755
|
-
[data-dsh-mobile-composer-tools] > *,[data-dsh-mobile-composer-tools] > [data-slot] > *,[data-dsh-mobile-composer-controls] > *,[data-dsh-mobile-composer-controls] > [data-slot] > * { min-width:0 !important; max-width:100% !important; }
|
|
2756
|
-
[data-dsh-mobile-composer-tools] button,[data-dsh-mobile-composer-controls] button { white-space:normal !important; overflow-wrap:anywhere !important; }
|
|
2757
2860
|
@media ${NATIVE_MOBILE_OVERLAY_QUERY} {
|
|
2758
2861
|
html.dsh-native-mobile-active,html.dsh-native-mobile-active body { width:100%; height:100%; overflow:hidden; }
|
|
2759
2862
|
html.dsh-native-mobile-active { --dsh-mobile-motion-duration:200ms; --dsh-mobile-motion-ease:cubic-bezier(.22,1,.36,1); }
|
|
@@ -2770,6 +2873,9 @@ window.__ModuleLoader__.load({
|
|
|
2770
2873
|
the ellipsis action visible and give it a reliable hit target. */
|
|
2771
2874
|
html.dsh-native-mobile-active [data-dsh-mobile-sidebar] [class*="_rowActions"] { display:inline-flex !important; align-items:center !important; gap:8px !important; }
|
|
2772
2875
|
html.dsh-native-mobile-active [data-dsh-mobile-sidebar] [class*="_sessionRow"] [class*="_time"] { display:none !important; }
|
|
2876
|
+
/* The always-visible pin action already conveys pinned state. Desktop only
|
|
2877
|
+
hides this passive marker on hover; touch rows must hide it throughout. */
|
|
2878
|
+
html.dsh-native-mobile-active [data-dsh-mobile-sidebar] [class*="_sessionRow"] [class*="_pinIndicator"] { display:none !important; }
|
|
2773
2879
|
html.dsh-native-mobile-active [data-dsh-mobile-sidebar] [class*="_rowActions"] button { box-sizing:border-box !important; width:32px !important; min-width:32px !important; height:32px !important; min-height:32px !important; }
|
|
2774
2880
|
[data-dsh-mobile-frame] { grid-template-columns:0 minmax(0,1fr) 0 !important; width:100% !important; height:100dvh !important; overflow:hidden !important; }
|
|
2775
2881
|
[data-dsh-mobile-center] { grid-column:2 !important; width:100vw !important; min-width:0 !important; }
|
|
@@ -2827,6 +2933,11 @@ window.__ModuleLoader__.load({
|
|
|
2827
2933
|
[data-dsh-mobile-settings-options] [data-slot="settings.general.item"] [class*="_selector"] { box-sizing:border-box !important; align-self:flex-start !important; justify-content:space-between !important; min-width:0 !important; min-height:48px !important; max-width:100% !important; }
|
|
2828
2934
|
[data-dsh-mobile-settings-options] :is(input,select,textarea,button) { max-width:100%; }
|
|
2829
2935
|
[data-dsh-mobile-settings-options] :is(input,select,textarea) { box-sizing:border-box; width:100%; min-width:0; }
|
|
2936
|
+
/* Local steppers and native page-scale controls keep their own touch targets. */
|
|
2937
|
+
[data-dsh-mobile-settings-options] [data-slot="settings.general.item"] .dsh-mobile-settings_fontControls button { flex:0 0 auto !important; width:auto !important; min-width:48px !important; align-self:center !important; justify-content:center !important; }
|
|
2938
|
+
[data-dsh-mobile-settings-options] [data-slot="settings.general.item"] .dsh-mobile-settings_fontControls select { flex:0 0 auto !important; width:auto !important; min-width:76px !important; align-self:center !important; }
|
|
2939
|
+
[data-dsh-mobile-settings-options] [data-mobile-font-setting] .dsh-mobile-settings_row { flex-wrap:wrap !important; }
|
|
2940
|
+
[data-dsh-mobile-settings-options] [data-mobile-font-setting] .dsh-mobile-settings_rowText { flex:1 1 140px !important; width:auto !important; padding-right:8px !important; }
|
|
2830
2941
|
[data-dsh-mobile-settings-options] [class*="_head"] { min-width:0; flex-wrap:wrap; }
|
|
2831
2942
|
/* Provider names may shrink, but their edit/delete actions remain horizontal
|
|
2832
2943
|
and retain a full touch target on narrow screens. */
|
|
@@ -2931,23 +3042,6 @@ window.__ModuleLoader__.load({
|
|
|
2931
3042
|
/* Collapse the scroll viewport only; the editor, draft and attachments stay
|
|
2932
3043
|
owned by DSH. Toolbar focus keeps the card expanded during menu taps. */
|
|
2933
3044
|
[data-dsh-mobile-center] [data-composer-card]:not(:focus-within) > [data-input-scroll] { max-height:72px !important; overflow-y:auto !important; }
|
|
2934
|
-
/* Stock Send/Queue/Steer and Stop keep their handlers and disabled state. */
|
|
2935
|
-
[data-dsh-mobile-composer-row] button { min-width:44px !important; min-height:44px !important; touch-action:manipulation; }
|
|
2936
|
-
[data-dsh-mobile-composer-row] button[class*="_primary"] { min-width:44px !important; flex-shrink:0 !important; }
|
|
2937
|
-
/* Keep ordinary controls on one row. The trailing group reserves room for
|
|
2938
|
-
its model and primary buttons; extra tools wrap only when space is short. */
|
|
2939
|
-
[data-dsh-mobile-composer-row] { display:flex !important; flex-wrap:wrap !important; align-items:center !important; gap:4px 8px !important; min-width:0 !important; max-width:100% !important; }
|
|
2940
|
-
[data-dsh-mobile-composer-tools]:not([hidden]) { display:flex !important; flex:0 1 auto !important; flex-wrap:wrap !important; width:auto !important; min-width:0 !important; max-width:100% !important; gap:6px !important; }
|
|
2941
|
-
[data-dsh-mobile-composer-tools] > *,[data-dsh-mobile-composer-tools] > [data-slot] > * { min-width:0 !important; max-width:100% !important; }
|
|
2942
|
-
[data-dsh-mobile-composer-tools] button { white-space:normal !important; overflow-wrap:anywhere !important; }
|
|
2943
|
-
[data-dsh-mobile-composer-trailing] { display:flex !important; flex:1 0 100px !important; flex-wrap:nowrap !important; width:auto !important; min-width:min(100%,100px) !important; max-width:100% !important; gap:6px !important; margin-left:0 !important; justify-content:flex-end !important; }
|
|
2944
|
-
[data-dsh-mobile-composer-trailing]:has(> button[class*="_primary"] ~ button[class*="_primary"]) { flex-basis:144px !important; min-width:min(100%,144px) !important; }
|
|
2945
|
-
[data-dsh-mobile-composer-controls]:not([hidden]) { display:flex !important; flex:1 1 0 !important; flex-wrap:wrap !important; justify-content:flex-end !important; min-width:0 !important; max-width:100% !important; gap:6px !important; }
|
|
2946
|
-
[data-dsh-mobile-composer-controls] > *,[data-dsh-mobile-composer-controls] > [data-slot] > * { min-width:0 !important; max-width:100% !important; }
|
|
2947
|
-
[data-dsh-mobile-composer-controls] button { white-space:normal !important; overflow-wrap:anywhere !important; }
|
|
2948
|
-
[data-dsh-mobile-composer-model] { flex:0 1 auto !important; width:auto !important; min-width:44px !important; max-width:100% !important; }
|
|
2949
|
-
[data-dsh-mobile-composer-model-trigger] { box-sizing:border-box !important; width:100% !important; max-width:100% !important; min-width:0 !important; padding-left:6px !important; padding-right:4px !important; }
|
|
2950
|
-
[data-dsh-mobile-composer-model-label] { flex:1 1 auto !important; max-width:none !important; min-width:0 !important; overflow:hidden !important; text-overflow:ellipsis !important; white-space:nowrap !important; }
|
|
2951
3045
|
[data-dsh-mobile-center] [class*="_root"]:has(> [class*="_card"] textarea) { box-sizing:border-box !important; width:100% !important; padding:0 0 8px !important; }
|
|
2952
3046
|
[data-dsh-mobile-center] [class*="_root"]:has(> [class*="_card"] textarea) > [class=""]:last-child { display:none !important; }
|
|
2953
3047
|
}
|
|
@@ -2972,7 +3066,7 @@ window.__ModuleLoader__.load({
|
|
|
2972
3066
|
[data-dsh-mobile-settings-content][data-dsh-mobile-view-transition="true"],
|
|
2973
3067
|
[data-dsh-mobile-view][data-dsh-mobile-view-transition="true"] { animation:none !important; }
|
|
2974
3068
|
}
|
|
2975
|
-
|
|
3069
|
+
` + MOBILE_COMPOSER_TOOLBAR_STYLES;
|
|
2976
3070
|
function classToken(element, suffix) {
|
|
2977
3071
|
return Array.from(element.classList).some((value) => value.endsWith(suffix));
|
|
2978
3072
|
}
|
|
@@ -4306,6 +4400,92 @@ window.__ModuleLoader__.load({
|
|
|
4306
4400
|
//#region src/mobile-font.ts
|
|
4307
4401
|
/** Device-local typography for authenticated mobile pages, without Host settings writes. */
|
|
4308
4402
|
const MOBILE_FONT_STORAGE_KEY = "dsh-mobile.content-font-size.v1";
|
|
4403
|
+
const MOBILE_TYPOGRAPHY_STORAGE_KEY = "dsh-mobile.typography.v1";
|
|
4404
|
+
const MOBILE_FONT_ROLES = ["text", "code"];
|
|
4405
|
+
const roleSpecs = {
|
|
4406
|
+
text: {
|
|
4407
|
+
default: 16,
|
|
4408
|
+
min: 12,
|
|
4409
|
+
variable: "--dsh-content-font-size"
|
|
4410
|
+
},
|
|
4411
|
+
code: {
|
|
4412
|
+
default: 11,
|
|
4413
|
+
min: 10,
|
|
4414
|
+
variable: "--dsh-code-font-size"
|
|
4415
|
+
}
|
|
4416
|
+
};
|
|
4417
|
+
const genericFamilies = /* @__PURE__ */ new Set([
|
|
4418
|
+
"serif",
|
|
4419
|
+
"sans-serif",
|
|
4420
|
+
"monospace",
|
|
4421
|
+
"cursive",
|
|
4422
|
+
"fantasy",
|
|
4423
|
+
"system-ui",
|
|
4424
|
+
"ui-serif",
|
|
4425
|
+
"ui-sans-serif",
|
|
4426
|
+
"ui-monospace",
|
|
4427
|
+
"ui-rounded",
|
|
4428
|
+
"emoji",
|
|
4429
|
+
"math",
|
|
4430
|
+
"fangsong",
|
|
4431
|
+
"-apple-system",
|
|
4432
|
+
"blinkmacsystemfont"
|
|
4433
|
+
]);
|
|
4434
|
+
const MAX_FONT_FAMILY_LENGTH = 1024;
|
|
4435
|
+
/** Normalize a comma-separated local font list without loading or discovering fonts. */
|
|
4436
|
+
function normalizeMobileFontFamily(value) {
|
|
4437
|
+
let result = "";
|
|
4438
|
+
for (const part of value.slice(0, MAX_FONT_FAMILY_LENGTH * 2).split(",")) {
|
|
4439
|
+
const name = part.replace(/["'\\<>\u0000-\u001f\u007f]/gu, "").replace(/\s+/gu, " ").trim();
|
|
4440
|
+
if (name === "") continue;
|
|
4441
|
+
const generic = genericFamilies.has(name.toLowerCase());
|
|
4442
|
+
const separator = result === "" ? "" : ", ";
|
|
4443
|
+
const remaining = MAX_FONT_FAMILY_LENGTH - result.length - separator.length - (generic ? 0 : 2);
|
|
4444
|
+
if (remaining < 1 || generic && name.length > remaining) break;
|
|
4445
|
+
result += separator + (generic ? name : `"${name.slice(0, remaining)}"`);
|
|
4446
|
+
}
|
|
4447
|
+
return result;
|
|
4448
|
+
}
|
|
4449
|
+
function defaultTypography(textSize = 16) {
|
|
4450
|
+
return Object.freeze({
|
|
4451
|
+
sizes: Object.freeze({
|
|
4452
|
+
text: textSize,
|
|
4453
|
+
code: roleSpecs.code.default
|
|
4454
|
+
}),
|
|
4455
|
+
families: Object.freeze({
|
|
4456
|
+
text: "",
|
|
4457
|
+
code: ""
|
|
4458
|
+
})
|
|
4459
|
+
});
|
|
4460
|
+
}
|
|
4461
|
+
/** Durable local JSON is validated independently for each typography role. */
|
|
4462
|
+
function readTypography(value, legacySize) {
|
|
4463
|
+
const fallback = defaultTypography(legacySize);
|
|
4464
|
+
if (value === null) return fallback;
|
|
4465
|
+
let parsed;
|
|
4466
|
+
try {
|
|
4467
|
+
parsed = JSON.parse(value);
|
|
4468
|
+
} catch (error) {
|
|
4469
|
+
return fallback;
|
|
4470
|
+
}
|
|
4471
|
+
if (typeof parsed !== "object" || parsed === null || !("sizes" in parsed) || !("families" in parsed)) return fallback;
|
|
4472
|
+
const sizes = { ...fallback.sizes };
|
|
4473
|
+
const families = { ...fallback.families };
|
|
4474
|
+
for (const role of MOBILE_FONT_ROLES) {
|
|
4475
|
+
if (typeof parsed.sizes === "object" && parsed.sizes !== null && role in parsed.sizes) {
|
|
4476
|
+
const size = Reflect.get(parsed.sizes, role);
|
|
4477
|
+
if (typeof size === "number" && Number.isInteger(size) && size >= roleSpecs[role].min && size <= 32) sizes[role] = size;
|
|
4478
|
+
}
|
|
4479
|
+
if (typeof parsed.families === "object" && parsed.families !== null && role in parsed.families) {
|
|
4480
|
+
const family = Reflect.get(parsed.families, role);
|
|
4481
|
+
if (typeof family === "string") families[role] = normalizeMobileFontFamily(family);
|
|
4482
|
+
}
|
|
4483
|
+
}
|
|
4484
|
+
return Object.freeze({
|
|
4485
|
+
sizes: Object.freeze(sizes),
|
|
4486
|
+
families: Object.freeze(families)
|
|
4487
|
+
});
|
|
4488
|
+
}
|
|
4309
4489
|
/** Read one local storage value; malformed or unavailable preferences use the mobile default. */
|
|
4310
4490
|
function parseMobileFontSize(value) {
|
|
4311
4491
|
if (value === null || !/^\d+$/.test(value)) return 16;
|
|
@@ -4315,15 +4495,21 @@ window.__ModuleLoader__.load({
|
|
|
4315
4495
|
/** Local preference storage; unavailable browser storage still permits in-page adjustment. */
|
|
4316
4496
|
var MobileFontPreference = class {
|
|
4317
4497
|
storage;
|
|
4318
|
-
|
|
4498
|
+
typography = defaultTypography();
|
|
4319
4499
|
listeners = /* @__PURE__ */ new Set();
|
|
4320
4500
|
constructor(storage) {
|
|
4321
4501
|
this.storage = storage;
|
|
4502
|
+
let legacySize = 16;
|
|
4503
|
+
try {
|
|
4504
|
+
legacySize = parseMobileFontSize(storage?.getItem("dsh-mobile.content-font-size.v1") ?? null);
|
|
4505
|
+
} catch (error) {}
|
|
4506
|
+
this.typography = defaultTypography(legacySize);
|
|
4322
4507
|
try {
|
|
4323
|
-
this.
|
|
4508
|
+
this.typography = readTypography(storage?.getItem("dsh-mobile.typography.v1") ?? null, legacySize);
|
|
4324
4509
|
} catch (error) {}
|
|
4325
4510
|
}
|
|
4326
|
-
getSnapshot = () => this.
|
|
4511
|
+
getSnapshot = () => this.typography.sizes.text;
|
|
4512
|
+
getTypographySnapshot = () => this.typography;
|
|
4327
4513
|
subscribe = (listener) => {
|
|
4328
4514
|
this.listeners.add(listener);
|
|
4329
4515
|
return () => {
|
|
@@ -4332,11 +4518,45 @@ window.__ModuleLoader__.load({
|
|
|
4332
4518
|
};
|
|
4333
4519
|
/** Apply a user-selected size locally; never send it to DSH's settings RPC. */
|
|
4334
4520
|
set(size) {
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4521
|
+
this.setRoleSize("text", size);
|
|
4522
|
+
}
|
|
4523
|
+
/** Change one mobile font role independently; all values remain address-local. */
|
|
4524
|
+
setRoleSize(role, size) {
|
|
4525
|
+
if (!Number.isInteger(size) || size < roleSpecs[role].min || size > 32) throw new RangeError(`Mobile ${role} font size must be an integer from ${roleSpecs[role].min} to 32`);
|
|
4526
|
+
if (this.typography.sizes[role] === size) return;
|
|
4527
|
+
this.persist({
|
|
4528
|
+
sizes: Object.freeze({
|
|
4529
|
+
...this.typography.sizes,
|
|
4530
|
+
[role]: size
|
|
4531
|
+
}),
|
|
4532
|
+
families: this.typography.families
|
|
4533
|
+
});
|
|
4534
|
+
}
|
|
4535
|
+
/** Select existing local fonts; an empty list uses DSH's built-in font stack. */
|
|
4536
|
+
setRoleFamily(role, family) {
|
|
4537
|
+
const normalized = normalizeMobileFontFamily(family);
|
|
4538
|
+
if (this.typography.families[role] === normalized) return;
|
|
4539
|
+
this.persist({
|
|
4540
|
+
sizes: this.typography.sizes,
|
|
4541
|
+
families: Object.freeze({
|
|
4542
|
+
...this.typography.families,
|
|
4543
|
+
[role]: normalized
|
|
4544
|
+
})
|
|
4545
|
+
});
|
|
4546
|
+
}
|
|
4547
|
+
/** Restore mobile sizes and built-in font stacks without modifying Host settings. */
|
|
4548
|
+
reset() {
|
|
4549
|
+
const next = defaultTypography();
|
|
4550
|
+
if (MOBILE_FONT_ROLES.every((role) => this.typography.sizes[role] === next.sizes[role] && this.typography.families[role] === "")) return;
|
|
4551
|
+
this.persist(next);
|
|
4552
|
+
}
|
|
4553
|
+
persist(typography) {
|
|
4554
|
+
this.typography = Object.freeze(typography);
|
|
4555
|
+
try {
|
|
4556
|
+
this.storage?.setItem(MOBILE_TYPOGRAPHY_STORAGE_KEY, JSON.stringify(typography));
|
|
4557
|
+
} catch (error) {}
|
|
4338
4558
|
try {
|
|
4339
|
-
this.storage?.setItem(MOBILE_FONT_STORAGE_KEY, String(
|
|
4559
|
+
this.storage?.setItem(MOBILE_FONT_STORAGE_KEY, String(typography.sizes.text));
|
|
4340
4560
|
} catch (error) {}
|
|
4341
4561
|
for (const listener of this.listeners) listener();
|
|
4342
4562
|
}
|
|
@@ -4348,19 +4568,31 @@ window.__ModuleLoader__.load({
|
|
|
4348
4568
|
storage = window.localStorage;
|
|
4349
4569
|
} catch (error) {}
|
|
4350
4570
|
const preference = new MobileFontPreference(storage);
|
|
4351
|
-
const
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4571
|
+
const style = document.body.style;
|
|
4572
|
+
const properties = MOBILE_FONT_ROLES.flatMap((role) => [roleSpecs[role].variable, `--dsh-font-family-${role}`]);
|
|
4573
|
+
const previous = new Map(properties.map((property) => [property, {
|
|
4574
|
+
value: style.getPropertyValue(property),
|
|
4575
|
+
priority: style.getPropertyPriority(property)
|
|
4576
|
+
}]));
|
|
4577
|
+
const owned = /* @__PURE__ */ new Map();
|
|
4355
4578
|
const apply = () => {
|
|
4356
|
-
const
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4579
|
+
const typography = preference.getTypographySnapshot();
|
|
4580
|
+
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
|
+
const current = style.getPropertyValue(property);
|
|
4582
|
+
const priority = style.getPropertyPriority(property);
|
|
4583
|
+
const last = owned.get(property);
|
|
4584
|
+
if (last !== void 0 && (current !== last.value || priority !== last.priority)) previous.set(property, {
|
|
4585
|
+
value: current,
|
|
4586
|
+
priority
|
|
4587
|
+
});
|
|
4588
|
+
owned.set(property, {
|
|
4589
|
+
value,
|
|
4590
|
+
priority: ""
|
|
4591
|
+
});
|
|
4592
|
+
if (current === value && priority === "") continue;
|
|
4593
|
+
if (value === "") style.removeProperty(property);
|
|
4594
|
+
else style.setProperty(property, value);
|
|
4360
4595
|
}
|
|
4361
|
-
const value = `${preference.getSnapshot()}px`;
|
|
4362
|
-
ownedValue = value;
|
|
4363
|
-
if (current !== value) document.body.style.setProperty(property, value);
|
|
4364
4596
|
};
|
|
4365
4597
|
apply();
|
|
4366
4598
|
const unsubscribe = preference.subscribe(apply);
|
|
@@ -4374,59 +4606,343 @@ window.__ModuleLoader__.load({
|
|
|
4374
4606
|
dispose: () => {
|
|
4375
4607
|
observer.disconnect();
|
|
4376
4608
|
unsubscribe();
|
|
4377
|
-
|
|
4378
|
-
|
|
4609
|
+
for (const [property, value] of previous) {
|
|
4610
|
+
const last = owned.get(property);
|
|
4611
|
+
if (last === void 0 || style.getPropertyValue(property) !== last.value || style.getPropertyPriority(property) !== last.priority) continue;
|
|
4612
|
+
if (value.value === "") style.removeProperty(property);
|
|
4613
|
+
else style.setProperty(property, value.value, value.priority);
|
|
4614
|
+
}
|
|
4379
4615
|
}
|
|
4380
4616
|
};
|
|
4381
4617
|
}
|
|
4382
|
-
const messages = {
|
|
4618
|
+
const messages$1 = {
|
|
4383
4619
|
zh: {
|
|
4384
4620
|
title: "移动端字号",
|
|
4385
4621
|
description: "仅保存在当前访问地址的本地数据中,不影响电脑或其他设备。",
|
|
4386
4622
|
decrease: "减小移动端字号",
|
|
4387
|
-
increase: "增大移动端字号"
|
|
4623
|
+
increase: "增大移动端字号",
|
|
4624
|
+
more: "更多字体设置",
|
|
4625
|
+
reset: "恢复默认",
|
|
4626
|
+
defaultFamily: "默认字体",
|
|
4627
|
+
familyHint: "填写设备已有的字体,多个名称用逗号分隔;留空使用默认字体。",
|
|
4628
|
+
codeSize: "代码字号",
|
|
4629
|
+
textFamily: "正文字体",
|
|
4630
|
+
codeFamily: "代码字体"
|
|
4388
4631
|
},
|
|
4389
4632
|
en: {
|
|
4390
4633
|
title: "Mobile font size",
|
|
4391
4634
|
description: "Saved locally for this address, without changing your computer or other devices.",
|
|
4392
4635
|
decrease: "Decrease mobile font size",
|
|
4393
|
-
increase: "Increase mobile font size"
|
|
4636
|
+
increase: "Increase mobile font size",
|
|
4637
|
+
more: "More font settings",
|
|
4638
|
+
reset: "Restore defaults",
|
|
4639
|
+
defaultFamily: "Default font",
|
|
4640
|
+
familyHint: "Use fonts already on your device, separated by commas. Leave empty for the default.",
|
|
4641
|
+
codeSize: "Code font size",
|
|
4642
|
+
textFamily: "Text font",
|
|
4643
|
+
codeFamily: "Code font"
|
|
4394
4644
|
},
|
|
4395
4645
|
it: {
|
|
4396
4646
|
title: "Dimensione testo mobile",
|
|
4397
4647
|
description: "Salvata localmente per questo indirizzo, senza modificare il computer o altri dispositivi.",
|
|
4398
4648
|
decrease: "Riduci il testo mobile",
|
|
4399
|
-
increase: "Aumenta il testo mobile"
|
|
4649
|
+
increase: "Aumenta il testo mobile",
|
|
4650
|
+
more: "Altre impostazioni dei caratteri",
|
|
4651
|
+
reset: "Ripristina predefiniti",
|
|
4652
|
+
defaultFamily: "Carattere predefinito",
|
|
4653
|
+
familyHint: "Usa caratteri presenti sul dispositivo, separati da virgole. Lascia vuoto per il predefinito.",
|
|
4654
|
+
codeSize: "Dimensione codice",
|
|
4655
|
+
textFamily: "Carattere testo",
|
|
4656
|
+
codeFamily: "Carattere codice"
|
|
4400
4657
|
}
|
|
4401
4658
|
};
|
|
4659
|
+
function FontFamilyInput({ preference, role, value, label, placeholder }) {
|
|
4660
|
+
const [draft, setDraft] = (0, react.useState)(value);
|
|
4661
|
+
(0, react.useEffect)(() => {
|
|
4662
|
+
setDraft(value);
|
|
4663
|
+
}, [value]);
|
|
4664
|
+
return (0, react.createElement)("label", { className: "dsh-mobile-settings_fontFamily" }, (0, react.createElement)("span", { className: "dsh-mobile-settings_title" }, label), (0, react.createElement)("input", {
|
|
4665
|
+
type: "text",
|
|
4666
|
+
value: draft,
|
|
4667
|
+
maxLength: MAX_FONT_FAMILY_LENGTH,
|
|
4668
|
+
placeholder,
|
|
4669
|
+
autoComplete: "off",
|
|
4670
|
+
spellCheck: false,
|
|
4671
|
+
"data-mobile-font-family": role,
|
|
4672
|
+
onChange: (event) => {
|
|
4673
|
+
setDraft(event.currentTarget.value);
|
|
4674
|
+
},
|
|
4675
|
+
onBlur: () => {
|
|
4676
|
+
preference.setRoleFamily(role, draft);
|
|
4677
|
+
setDraft(preference.getTypographySnapshot().families[role]);
|
|
4678
|
+
}
|
|
4679
|
+
}));
|
|
4680
|
+
}
|
|
4402
4681
|
/** General settings row matching the existing Mobile controls and DSH theme tokens. */
|
|
4403
4682
|
function MobileFontSizeRow({ preference, locale }) {
|
|
4404
|
-
const
|
|
4405
|
-
const copy = messages[locale];
|
|
4683
|
+
const typography = (0, react.useSyncExternalStore)(preference.subscribe, preference.getTypographySnapshot);
|
|
4684
|
+
const copy = messages$1[locale];
|
|
4685
|
+
const controls = (role, title) => {
|
|
4686
|
+
const size = typography.sizes[role];
|
|
4687
|
+
return (0, react.createElement)("div", {
|
|
4688
|
+
className: "dsh-mobile-settings_fontControls",
|
|
4689
|
+
"data-mobile-font-role": role
|
|
4690
|
+
}, (0, react.createElement)("button", {
|
|
4691
|
+
type: "button",
|
|
4692
|
+
className: "dsh-mobile-settings_selector",
|
|
4693
|
+
"aria-label": `${copy.decrease}: ${title}`,
|
|
4694
|
+
disabled: size === roleSpecs[role].min,
|
|
4695
|
+
onClick: () => {
|
|
4696
|
+
preference.setRoleSize(role, size - 1);
|
|
4697
|
+
}
|
|
4698
|
+
}, "−"), (0, react.createElement)("output", {
|
|
4699
|
+
"aria-live": "polite",
|
|
4700
|
+
"aria-label": title
|
|
4701
|
+
}, `${size}px`), (0, react.createElement)("button", {
|
|
4702
|
+
type: "button",
|
|
4703
|
+
className: "dsh-mobile-settings_selector",
|
|
4704
|
+
"aria-label": `${copy.increase}: ${title}`,
|
|
4705
|
+
disabled: size === 32,
|
|
4706
|
+
onClick: () => {
|
|
4707
|
+
preference.setRoleSize(role, size + 1);
|
|
4708
|
+
}
|
|
4709
|
+
}, "+"));
|
|
4710
|
+
};
|
|
4406
4711
|
return (0, react.createElement)("div", {
|
|
4407
|
-
className: "dsh-mobile-settings_row",
|
|
4408
4712
|
lang: locale,
|
|
4409
4713
|
"data-mobile-font-setting": true
|
|
4410
|
-
}, (0, react.createElement)("div", { className: "dsh-mobile-settings_rowText" }, (0, react.createElement)("div", { className: "dsh-mobile-settings_title" }, copy.title), (0, react.createElement)("div", { className: "dsh-mobile-settings_desc" }, copy.description)), (0, react.createElement)("div", { className: "dsh-mobile-
|
|
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)), (0, react.createElement)("details", { className: "dsh-mobile-settings_fontDetails" }, (0, react.createElement)("summary", null, copy.more), (0, react.createElement)("p", { className: "dsh-mobile-settings_desc" }, copy.familyHint), (0, react.createElement)("div", { className: "dsh-mobile-settings_row" }, (0, react.createElement)("span", { className: "dsh-mobile-settings_rowText dsh-mobile-settings_title" }, copy.codeSize), controls("code", copy.codeSize)), ...MOBILE_FONT_ROLES.map((role) => (0, react.createElement)(FontFamilyInput, {
|
|
4715
|
+
key: role,
|
|
4716
|
+
preference,
|
|
4717
|
+
role,
|
|
4718
|
+
value: typography.families[role],
|
|
4719
|
+
label: role === "text" ? copy.textFamily : copy.codeFamily,
|
|
4720
|
+
placeholder: copy.defaultFamily
|
|
4721
|
+
})), (0, react.createElement)("button", {
|
|
4411
4722
|
type: "button",
|
|
4412
4723
|
className: "dsh-mobile-settings_selector",
|
|
4413
|
-
"aria-label": copy.decrease,
|
|
4414
|
-
disabled: size === 12,
|
|
4415
4724
|
onClick: () => {
|
|
4416
|
-
preference.
|
|
4725
|
+
preference.reset();
|
|
4726
|
+
}
|
|
4727
|
+
}, copy.reset)));
|
|
4728
|
+
}
|
|
4729
|
+
//#endregion
|
|
4730
|
+
//#region src/mobile-display-scale.ts
|
|
4731
|
+
/** Native App page scaling in DSH General settings, independent from local typography. */
|
|
4732
|
+
/** Validate the native reply before displaying or reusing its values in a request. */
|
|
4733
|
+
function parseMobileDisplayScale(value) {
|
|
4734
|
+
if (typeof value !== "object" || value === null || !("percent" in value) || !("minPercent" in value) || !("maxPercent" in value)) throw new TypeError("Native display scale reply is invalid");
|
|
4735
|
+
const { percent, minPercent, maxPercent } = value;
|
|
4736
|
+
if (typeof percent !== "number" || !Number.isInteger(percent) || typeof minPercent !== "number" || !Number.isInteger(minPercent) || typeof maxPercent !== "number" || !Number.isInteger(maxPercent) || minPercent < 80 || maxPercent > 125 || minPercent > 100 || maxPercent < 100 || percent < minPercent || percent > maxPercent) throw new TypeError("Native display scale reply is invalid");
|
|
4737
|
+
return Object.freeze({
|
|
4738
|
+
percent,
|
|
4739
|
+
minPercent,
|
|
4740
|
+
maxPercent
|
|
4741
|
+
});
|
|
4742
|
+
}
|
|
4743
|
+
/** Serial native requests; a replaced bridge or disposed row cannot publish stale results. */
|
|
4744
|
+
var MobileDisplayScaleController = class {
|
|
4745
|
+
getBridge;
|
|
4746
|
+
snapshot = {
|
|
4747
|
+
state: "hidden",
|
|
4748
|
+
value: null
|
|
4749
|
+
};
|
|
4750
|
+
listeners = /* @__PURE__ */ new Set();
|
|
4751
|
+
bridge;
|
|
4752
|
+
generation = 0;
|
|
4753
|
+
active = false;
|
|
4754
|
+
constructor(getBridge) {
|
|
4755
|
+
this.getBridge = getBridge;
|
|
4756
|
+
}
|
|
4757
|
+
getSnapshot = () => this.snapshot;
|
|
4758
|
+
subscribe = (listener) => {
|
|
4759
|
+
this.listeners.add(listener);
|
|
4760
|
+
return () => {
|
|
4761
|
+
this.listeners.delete(listener);
|
|
4762
|
+
};
|
|
4763
|
+
};
|
|
4764
|
+
start() {
|
|
4765
|
+
this.active = true;
|
|
4766
|
+
this.refresh();
|
|
4767
|
+
}
|
|
4768
|
+
stop() {
|
|
4769
|
+
this.active = false;
|
|
4770
|
+
this.generation += 1;
|
|
4771
|
+
this.bridge = void 0;
|
|
4772
|
+
}
|
|
4773
|
+
/** Discover both capabilities before showing a control or querying its current native value. */
|
|
4774
|
+
refresh = () => {
|
|
4775
|
+
if (!this.active) return;
|
|
4776
|
+
const generation = ++this.generation;
|
|
4777
|
+
const bridge = this.getBridge();
|
|
4778
|
+
this.bridge = bridge;
|
|
4779
|
+
this.publish({
|
|
4780
|
+
state: "hidden",
|
|
4781
|
+
value: null
|
|
4782
|
+
});
|
|
4783
|
+
if (bridge === void 0) return;
|
|
4784
|
+
Promise.resolve().then(() => bridge.capabilities()).then(async (capabilities) => {
|
|
4785
|
+
if (!this.current(bridge, generation)) return;
|
|
4786
|
+
if (!capabilities.includes("mobile.display-scale.get") || !capabilities.includes("mobile.display-scale.set")) return;
|
|
4787
|
+
this.publish({
|
|
4788
|
+
state: "loading",
|
|
4789
|
+
value: null
|
|
4790
|
+
});
|
|
4791
|
+
try {
|
|
4792
|
+
const value = parseMobileDisplayScale(await bridge.invoke("mobile.display-scale.get", {}));
|
|
4793
|
+
if (this.current(bridge, generation)) this.publish({
|
|
4794
|
+
state: "ready",
|
|
4795
|
+
value
|
|
4796
|
+
});
|
|
4797
|
+
} catch (error) {
|
|
4798
|
+
if (this.current(bridge, generation)) this.publish({
|
|
4799
|
+
state: "error",
|
|
4800
|
+
value: null
|
|
4801
|
+
});
|
|
4802
|
+
}
|
|
4803
|
+
}, () => {});
|
|
4804
|
+
};
|
|
4805
|
+
/** Apply a device-local value once, preserving the last confirmed value when the native request fails. */
|
|
4806
|
+
setPercent(percent) {
|
|
4807
|
+
const value = this.snapshot.value;
|
|
4808
|
+
const bridge = this.bridge;
|
|
4809
|
+
if (!this.active || bridge === void 0 || bridge !== this.getBridge() || value === null || this.snapshot.state === "busy" || this.snapshot.state === "loading") return;
|
|
4810
|
+
if (!Number.isInteger(percent) || percent < value.minPercent || percent > value.maxPercent) return;
|
|
4811
|
+
if (percent === value.percent && this.snapshot.state !== "error") return;
|
|
4812
|
+
const generation = this.generation;
|
|
4813
|
+
this.publish({
|
|
4814
|
+
state: "busy",
|
|
4815
|
+
value
|
|
4816
|
+
});
|
|
4817
|
+
Promise.resolve().then(() => this.current(bridge, generation) ? bridge.invoke("mobile.display-scale.set", { percent }) : void 0).then((reply) => {
|
|
4818
|
+
if (!this.current(bridge, generation)) return;
|
|
4819
|
+
try {
|
|
4820
|
+
this.publish({
|
|
4821
|
+
state: "ready",
|
|
4822
|
+
value: parseMobileDisplayScale(reply)
|
|
4823
|
+
});
|
|
4824
|
+
} catch (error) {
|
|
4825
|
+
this.publish({
|
|
4826
|
+
state: "error",
|
|
4827
|
+
value
|
|
4828
|
+
});
|
|
4829
|
+
}
|
|
4830
|
+
}, () => {
|
|
4831
|
+
if (this.current(bridge, generation)) this.publish({
|
|
4832
|
+
state: "error",
|
|
4833
|
+
value
|
|
4834
|
+
});
|
|
4835
|
+
});
|
|
4836
|
+
}
|
|
4837
|
+
current(bridge, generation) {
|
|
4838
|
+
return this.active && generation === this.generation && this.bridge === bridge && this.getBridge() === bridge;
|
|
4839
|
+
}
|
|
4840
|
+
publish(snapshot) {
|
|
4841
|
+
this.snapshot = Object.freeze(snapshot);
|
|
4842
|
+
for (const listener of this.listeners) listener();
|
|
4843
|
+
}
|
|
4844
|
+
};
|
|
4845
|
+
const messages = {
|
|
4846
|
+
zh: {
|
|
4847
|
+
title: "页面缩放",
|
|
4848
|
+
description: "调整整个页面的显示大小,仅保存在当前 App 中,与字号设置独立。",
|
|
4849
|
+
reset: "恢复 100%",
|
|
4850
|
+
loading: "读取中…",
|
|
4851
|
+
busy: "正在调整…",
|
|
4852
|
+
error: "无法调整页面缩放,请重试。",
|
|
4853
|
+
retry: "重试"
|
|
4854
|
+
},
|
|
4855
|
+
en: {
|
|
4856
|
+
title: "Page zoom",
|
|
4857
|
+
description: "Resize the whole page. Saved in this App, separately from font size.",
|
|
4858
|
+
reset: "Reset to 100%",
|
|
4859
|
+
loading: "Loading…",
|
|
4860
|
+
busy: "Applying…",
|
|
4861
|
+
error: "Could not change page zoom. Try again.",
|
|
4862
|
+
retry: "Retry"
|
|
4863
|
+
},
|
|
4864
|
+
it: {
|
|
4865
|
+
title: "Zoom pagina",
|
|
4866
|
+
description: "Ridimensiona tutta la pagina. Salvato in questa app, separatamente dalla dimensione del testo.",
|
|
4867
|
+
reset: "Ripristina 100%",
|
|
4868
|
+
loading: "Caricamento…",
|
|
4869
|
+
busy: "Applicazione…",
|
|
4870
|
+
error: "Impossibile modificare lo zoom. Riprova.",
|
|
4871
|
+
retry: "Riprova"
|
|
4872
|
+
}
|
|
4873
|
+
};
|
|
4874
|
+
/** Native-only General settings row using the existing DSH Mobile controls and locale. */
|
|
4875
|
+
function MobileDisplayScaleRow({ locale }) {
|
|
4876
|
+
const [controller] = (0, react.useState)(() => new MobileDisplayScaleController(() => window.__DSH_MOBILE_NATIVE__));
|
|
4877
|
+
const snapshot = (0, react.useSyncExternalStore)(controller.subscribe, controller.getSnapshot);
|
|
4878
|
+
(0, react.useEffect)(() => {
|
|
4879
|
+
controller.start();
|
|
4880
|
+
window.addEventListener("dsh-mobile-native-ready", controller.refresh);
|
|
4881
|
+
return () => {
|
|
4882
|
+
window.removeEventListener("dsh-mobile-native-ready", controller.refresh);
|
|
4883
|
+
controller.stop();
|
|
4884
|
+
};
|
|
4885
|
+
}, [controller]);
|
|
4886
|
+
if (snapshot.state === "hidden") return null;
|
|
4887
|
+
const copy = messages[locale];
|
|
4888
|
+
const value = snapshot.value;
|
|
4889
|
+
const busy = snapshot.state === "busy" || snapshot.state === "loading";
|
|
4890
|
+
const description = snapshot.state === "error" ? copy.error : snapshot.state === "loading" ? copy.loading : snapshot.state === "busy" ? copy.busy : copy.description;
|
|
4891
|
+
const options = value === null ? [] : [.../* @__PURE__ */ new Set([
|
|
4892
|
+
value.minPercent,
|
|
4893
|
+
value.maxPercent,
|
|
4894
|
+
value.percent,
|
|
4895
|
+
...[
|
|
4896
|
+
80,
|
|
4897
|
+
85,
|
|
4898
|
+
90,
|
|
4899
|
+
95,
|
|
4900
|
+
100,
|
|
4901
|
+
105,
|
|
4902
|
+
110,
|
|
4903
|
+
115,
|
|
4904
|
+
120,
|
|
4905
|
+
125
|
|
4906
|
+
].filter((percent) => percent >= value.minPercent && percent <= value.maxPercent)
|
|
4907
|
+
])].sort((a, b) => a - b);
|
|
4908
|
+
return (0, react.createElement)("div", {
|
|
4909
|
+
className: "dsh-mobile-settings_row",
|
|
4910
|
+
style: { flexWrap: "wrap" },
|
|
4911
|
+
lang: locale,
|
|
4912
|
+
"data-mobile-display-scale-setting": true,
|
|
4913
|
+
"aria-busy": busy
|
|
4914
|
+
}, (0, react.createElement)("div", {
|
|
4915
|
+
className: "dsh-mobile-settings_rowText",
|
|
4916
|
+
style: {
|
|
4917
|
+
flexBasis: 160,
|
|
4918
|
+
paddingRight: 8
|
|
4919
|
+
}
|
|
4920
|
+
}, (0, react.createElement)("div", { className: "dsh-mobile-settings_title" }, copy.title), (0, react.createElement)("div", {
|
|
4921
|
+
className: "dsh-mobile-settings_desc",
|
|
4922
|
+
"aria-live": "polite"
|
|
4923
|
+
}, description)), (0, react.createElement)("div", { className: "dsh-mobile-settings_fontControls" }, value === null ? null : (0, react.createElement)("select", {
|
|
4924
|
+
className: "dsh-mobile-settings_selector",
|
|
4925
|
+
"aria-label": copy.title,
|
|
4926
|
+
disabled: busy,
|
|
4927
|
+
value: value.percent,
|
|
4928
|
+
onChange: (event) => {
|
|
4929
|
+
controller.setPercent(Number(event.currentTarget.value));
|
|
4417
4930
|
}
|
|
4418
|
-
},
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
}, `${
|
|
4931
|
+
}, ...options.map((percent) => (0, react.createElement)("option", {
|
|
4932
|
+
key: percent,
|
|
4933
|
+
value: percent
|
|
4934
|
+
}, `${percent}%`))), snapshot.state === "error" ? (0, react.createElement)("button", {
|
|
4935
|
+
type: "button",
|
|
4936
|
+
className: "dsh-mobile-settings_selector",
|
|
4937
|
+
onClick: controller.refresh
|
|
4938
|
+
}, copy.retry) : (0, react.createElement)("button", {
|
|
4422
4939
|
type: "button",
|
|
4423
4940
|
className: "dsh-mobile-settings_selector",
|
|
4424
|
-
|
|
4425
|
-
disabled: size === 32,
|
|
4941
|
+
disabled: busy || value === null || value.percent === 100,
|
|
4426
4942
|
onClick: () => {
|
|
4427
|
-
|
|
4943
|
+
controller.setPercent(100);
|
|
4428
4944
|
}
|
|
4429
|
-
},
|
|
4945
|
+
}, copy.reset)));
|
|
4430
4946
|
}
|
|
4431
4947
|
//#endregion
|
|
4432
4948
|
//#region src/local-admin-host.ts
|
|
@@ -4778,6 +5294,244 @@ window.__ModuleLoader__.load({
|
|
|
4778
5294
|
}, copy.open));
|
|
4779
5295
|
}
|
|
4780
5296
|
//#endregion
|
|
5297
|
+
//#region src/extension-recovery-ui.ts
|
|
5298
|
+
/** Validate the Host response and select only stopped workers, never in-process hosts. */
|
|
5299
|
+
function parseUnavailableExtensionHosts(payload) {
|
|
5300
|
+
if (typeof payload !== "object" || payload === null || !("hosts" in payload) || !Array.isArray(payload.hosts)) return void 0;
|
|
5301
|
+
const ids = /* @__PURE__ */ new Set();
|
|
5302
|
+
const hosts = [];
|
|
5303
|
+
for (const value of payload.hosts) {
|
|
5304
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
5305
|
+
const entry = value;
|
|
5306
|
+
if (typeof entry.id !== "string" || !/^[a-z][a-z0-9-]{0,63}$/u.test(entry.id) || ids.has(entry.id) || typeof entry.name !== "string" || entry.name.trim() === "" || entry.mode !== "worker" && entry.mode !== "in-process" || entry.state !== "ready" && entry.state !== "unavailable" || typeof entry.generation !== "string" || !/^[a-f\d]{64}$/u.test(entry.generation)) return void 0;
|
|
5307
|
+
ids.add(entry.id);
|
|
5308
|
+
if (entry.mode === "worker" && entry.state === "unavailable") hosts.push({
|
|
5309
|
+
id: entry.id,
|
|
5310
|
+
name: entry.name,
|
|
5311
|
+
generation: entry.generation
|
|
5312
|
+
});
|
|
5313
|
+
}
|
|
5314
|
+
return hosts;
|
|
5315
|
+
}
|
|
5316
|
+
/** One section shared across desktop access views; recovery is never automatic. */
|
|
5317
|
+
function installExtensionRecoveryControl(options) {
|
|
5318
|
+
const { document, signal, request, confirm, translate: t } = options;
|
|
5319
|
+
const section = document.createElement("section");
|
|
5320
|
+
section.className = "dsh-mobile-control__extension-recovery";
|
|
5321
|
+
section.hidden = true;
|
|
5322
|
+
section.setAttribute("aria-label", t("extensionRecoveryTitle"));
|
|
5323
|
+
const title = document.createElement("h3");
|
|
5324
|
+
title.textContent = t("extensionRecoveryTitle");
|
|
5325
|
+
const help = document.createElement("p");
|
|
5326
|
+
help.textContent = t("extensionRecoveryHelp");
|
|
5327
|
+
const list = document.createElement("ul");
|
|
5328
|
+
const feedback = document.createElement("p");
|
|
5329
|
+
feedback.className = "dsh-mobile-control__extension-recovery-error";
|
|
5330
|
+
feedback.hidden = true;
|
|
5331
|
+
feedback.setAttribute("role", "status");
|
|
5332
|
+
feedback.setAttribute("aria-live", "polite");
|
|
5333
|
+
const retry = document.createElement("button");
|
|
5334
|
+
retry.type = "button";
|
|
5335
|
+
retry.className = "dsh-mobile-control__secondary";
|
|
5336
|
+
retry.textContent = t("extensionRecoveryRefresh");
|
|
5337
|
+
retry.hidden = true;
|
|
5338
|
+
section.append(title, help, list, feedback, retry);
|
|
5339
|
+
const rows = /* @__PURE__ */ new Map();
|
|
5340
|
+
let hosts = [];
|
|
5341
|
+
let busyId;
|
|
5342
|
+
let errorText = "";
|
|
5343
|
+
let errorSource;
|
|
5344
|
+
let revision = 0;
|
|
5345
|
+
let reading = false;
|
|
5346
|
+
let refreshPending = false;
|
|
5347
|
+
let disposed = false;
|
|
5348
|
+
const live = () => !disposed && !signal.aborted;
|
|
5349
|
+
const render = () => {
|
|
5350
|
+
if (!live()) return;
|
|
5351
|
+
section.hidden = hosts.length === 0;
|
|
5352
|
+
section.setAttribute("aria-busy", String(busyId !== void 0));
|
|
5353
|
+
for (const [id, entry] of rows) {
|
|
5354
|
+
if (hosts.some((host) => host.id === id)) continue;
|
|
5355
|
+
entry.row.remove();
|
|
5356
|
+
rows.delete(id);
|
|
5357
|
+
}
|
|
5358
|
+
for (const host of hosts) {
|
|
5359
|
+
let entry = rows.get(host.id);
|
|
5360
|
+
if (entry === void 0) {
|
|
5361
|
+
const row = document.createElement("li");
|
|
5362
|
+
const text = document.createElement("div");
|
|
5363
|
+
const name = document.createElement("strong");
|
|
5364
|
+
const id = document.createElement("code");
|
|
5365
|
+
const button = document.createElement("button");
|
|
5366
|
+
button.type = "button";
|
|
5367
|
+
button.className = "dsh-mobile-control__secondary";
|
|
5368
|
+
button.dataset.extensionRecoveryId = host.id;
|
|
5369
|
+
button.addEventListener("click", () => {
|
|
5370
|
+
recover(host.id);
|
|
5371
|
+
});
|
|
5372
|
+
text.append(name, id);
|
|
5373
|
+
row.append(text, button);
|
|
5374
|
+
list.append(row);
|
|
5375
|
+
entry = {
|
|
5376
|
+
row,
|
|
5377
|
+
name,
|
|
5378
|
+
id,
|
|
5379
|
+
button
|
|
5380
|
+
};
|
|
5381
|
+
rows.set(host.id, entry);
|
|
5382
|
+
}
|
|
5383
|
+
entry.name.textContent = host.name;
|
|
5384
|
+
entry.id.textContent = host.id;
|
|
5385
|
+
entry.button.disabled = busyId !== void 0;
|
|
5386
|
+
entry.button.textContent = t(busyId === host.id ? "extensionRecoveryRestarting" : "extensionRecoveryRestart");
|
|
5387
|
+
entry.button.setAttribute("aria-label", t("extensionRecoveryRestartAria", { name: host.name }));
|
|
5388
|
+
}
|
|
5389
|
+
feedback.hidden = errorText === "";
|
|
5390
|
+
feedback.textContent = errorText;
|
|
5391
|
+
retry.hidden = errorText === "";
|
|
5392
|
+
retry.disabled = reading || busyId !== void 0;
|
|
5393
|
+
};
|
|
5394
|
+
const accept = (payload, recovered = false) => {
|
|
5395
|
+
const parsed = parseUnavailableExtensionHosts(payload);
|
|
5396
|
+
if (parsed === void 0) throw new Error("extension_recovery_invalid_response");
|
|
5397
|
+
hosts = parsed;
|
|
5398
|
+
if (recovered || hosts.length === 0 || errorSource === "load") {
|
|
5399
|
+
errorText = "";
|
|
5400
|
+
errorSource = void 0;
|
|
5401
|
+
}
|
|
5402
|
+
render();
|
|
5403
|
+
};
|
|
5404
|
+
const refresh = async () => {
|
|
5405
|
+
if (!live()) return;
|
|
5406
|
+
if (reading || busyId !== void 0) {
|
|
5407
|
+
refreshPending = true;
|
|
5408
|
+
return;
|
|
5409
|
+
}
|
|
5410
|
+
reading = true;
|
|
5411
|
+
const current = ++revision;
|
|
5412
|
+
render();
|
|
5413
|
+
try {
|
|
5414
|
+
const payload = await request("/api/mobile-access/extensions/hosts");
|
|
5415
|
+
if (live() && current === revision) accept(payload);
|
|
5416
|
+
} catch (_error) {
|
|
5417
|
+
if (live() && current === revision && hosts.length > 0 && errorSource !== "recover") {
|
|
5418
|
+
errorText = t("extensionRecoveryLoadFailed");
|
|
5419
|
+
errorSource = "load";
|
|
5420
|
+
}
|
|
5421
|
+
} finally {
|
|
5422
|
+
reading = false;
|
|
5423
|
+
if (live()) {
|
|
5424
|
+
render();
|
|
5425
|
+
if (refreshPending && busyId === void 0) {
|
|
5426
|
+
refreshPending = false;
|
|
5427
|
+
await refresh();
|
|
5428
|
+
}
|
|
5429
|
+
}
|
|
5430
|
+
}
|
|
5431
|
+
};
|
|
5432
|
+
const recover = async (id) => {
|
|
5433
|
+
const host = hosts.find((entry) => entry.id === id);
|
|
5434
|
+
if (!live() || busyId !== void 0 || host === void 0 || !confirm(t("extensionRecoveryConfirm", { name: host.name }))) return;
|
|
5435
|
+
if (!live()) return;
|
|
5436
|
+
busyId = id;
|
|
5437
|
+
errorText = "";
|
|
5438
|
+
errorSource = void 0;
|
|
5439
|
+
const current = ++revision;
|
|
5440
|
+
render();
|
|
5441
|
+
try {
|
|
5442
|
+
const payload = await request("/api/mobile-access/extensions/recover", {
|
|
5443
|
+
method: "POST",
|
|
5444
|
+
body: JSON.stringify({
|
|
5445
|
+
id,
|
|
5446
|
+
confirm: true
|
|
5447
|
+
})
|
|
5448
|
+
});
|
|
5449
|
+
if (live() && current === revision) accept(payload, true);
|
|
5450
|
+
} catch (error) {
|
|
5451
|
+
if (live() && current === revision) {
|
|
5452
|
+
errorText = t(error instanceof Error && error.message === "extension_changed_during_activation" ? "extensionRecoveryChanged" : "extensionRecoveryFailed");
|
|
5453
|
+
errorSource = "recover";
|
|
5454
|
+
}
|
|
5455
|
+
} finally {
|
|
5456
|
+
busyId = void 0;
|
|
5457
|
+
if (live()) {
|
|
5458
|
+
render();
|
|
5459
|
+
if (refreshPending && !reading) {
|
|
5460
|
+
refreshPending = false;
|
|
5461
|
+
await refresh();
|
|
5462
|
+
}
|
|
5463
|
+
}
|
|
5464
|
+
}
|
|
5465
|
+
};
|
|
5466
|
+
retry.addEventListener("click", () => {
|
|
5467
|
+
errorText = "";
|
|
5468
|
+
errorSource = void 0;
|
|
5469
|
+
refresh();
|
|
5470
|
+
});
|
|
5471
|
+
return {
|
|
5472
|
+
section,
|
|
5473
|
+
refresh,
|
|
5474
|
+
dispose: () => {
|
|
5475
|
+
disposed = true;
|
|
5476
|
+
revision++;
|
|
5477
|
+
section.remove();
|
|
5478
|
+
rows.clear();
|
|
5479
|
+
}
|
|
5480
|
+
};
|
|
5481
|
+
}
|
|
5482
|
+
/** Theme-owned compact recovery rows; normal connection views keep their layout. */
|
|
5483
|
+
const EXTENSION_RECOVERY_STYLES = `
|
|
5484
|
+
.dsh-mobile-control__extension-recovery{margin:0 0 14px;padding:12px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary)}
|
|
5485
|
+
.dsh-mobile-control__extension-recovery[hidden]{display:none}
|
|
5486
|
+
.dsh-mobile-control__extension-recovery h3{margin:0 0 6px;font-size:13px;line-height:1.5;font-weight:600}
|
|
5487
|
+
.dsh-mobile-control__extension-recovery p{margin:0 0 8px;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-secondary)}
|
|
5488
|
+
.dsh-mobile-control__extension-recovery ul{list-style:none;margin:0;padding:0}
|
|
5489
|
+
.dsh-mobile-control__extension-recovery li{display:flex;align-items:center;gap:12px;padding:6px 0}
|
|
5490
|
+
.dsh-mobile-control__extension-recovery li>div{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;overflow-wrap:anywhere}
|
|
5491
|
+
.dsh-mobile-control__extension-recovery strong{font-size:12px;line-height:1.5}
|
|
5492
|
+
.dsh-mobile-control__extension-recovery code{font-size:11px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}
|
|
5493
|
+
.dsh-mobile-control__extension-recovery button{flex:none;box-sizing:border-box;min-height:48px;min-width:48px;white-space:normal}
|
|
5494
|
+
.dsh-mobile-control__extension-recovery button:disabled{cursor:wait;opacity:.55}
|
|
5495
|
+
.dsh-mobile-control__extension-recovery button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
|
|
5496
|
+
.dsh-mobile-control__extension-recovery button:active:not(:disabled){background:var(--dsw-alias-interactive-bg-active,var(--dsw-alias-interactive-bg-hover))}
|
|
5497
|
+
.dsh-mobile-control__extension-recovery .dsh-mobile-control__extension-recovery-error{color:var(--dsw-alias-label-primary)}
|
|
5498
|
+
`;
|
|
5499
|
+
//#endregion
|
|
5500
|
+
//#region src/native-capability.ts
|
|
5501
|
+
/** Cancel queued calls before native effects start; started effects are not rolled back. */
|
|
5502
|
+
function invokeBoundNativeCapability(bridge, currentBridge, action, input, signal) {
|
|
5503
|
+
const abortReason = () => signal.reason ?? new DOMException("mobile extension disposed", "AbortError");
|
|
5504
|
+
if (signal.aborted) return Promise.reject(abortReason());
|
|
5505
|
+
return new Promise((resolve, reject) => {
|
|
5506
|
+
let settled = false;
|
|
5507
|
+
const finish = (callback) => {
|
|
5508
|
+
if (settled) return;
|
|
5509
|
+
settled = true;
|
|
5510
|
+
signal.removeEventListener("abort", onAbort);
|
|
5511
|
+
callback();
|
|
5512
|
+
};
|
|
5513
|
+
const onAbort = () => {
|
|
5514
|
+
finish(() => {
|
|
5515
|
+
reject(abortReason());
|
|
5516
|
+
});
|
|
5517
|
+
};
|
|
5518
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
5519
|
+
Promise.resolve().then(() => {
|
|
5520
|
+
if (settled || signal.aborted) throw abortReason();
|
|
5521
|
+
if (currentBridge() !== bridge) throw new DOMException("native bridge changed", "AbortError");
|
|
5522
|
+
return bridge.invoke(action, input);
|
|
5523
|
+
}).then((value) => {
|
|
5524
|
+
finish(() => {
|
|
5525
|
+
resolve(value);
|
|
5526
|
+
});
|
|
5527
|
+
}, (error) => {
|
|
5528
|
+
finish(() => {
|
|
5529
|
+
reject(error);
|
|
5530
|
+
});
|
|
5531
|
+
});
|
|
5532
|
+
});
|
|
5533
|
+
}
|
|
5534
|
+
//#endregion
|
|
4781
5535
|
//#region src/client.ts
|
|
4782
5536
|
/** Map a host-reported provider onto the UI union, defaulting to the built-in one. */
|
|
4783
5537
|
function parseMobileRemoteProvider(value) {
|
|
@@ -5171,8 +5925,8 @@ window.__ModuleLoader__.load({
|
|
|
5171
5925
|
function clientReleaseInfo(data) {
|
|
5172
5926
|
const latestVersion = releaseVersion(data.latestVersion);
|
|
5173
5927
|
const androidVersion = releaseVersion(data.androidVersion);
|
|
5174
|
-
const
|
|
5175
|
-
const androidDownloadUrl =
|
|
5928
|
+
const expectedAndroidDownloadUrls = androidVersion === void 0 ? [] : ["v", "android-v"].map((prefix) => `${GITHUB_RELEASES_URL}/download/${prefix}${androidVersion}/dsh-mobile-android-v${androidVersion}.apk`);
|
|
5929
|
+
const androidDownloadUrl = typeof data.androidDownloadUrl === "string" && expectedAndroidDownloadUrls.includes(data.androidDownloadUrl) ? data.androidDownloadUrl : GITHUB_RELEASES_URL;
|
|
5176
5930
|
return {
|
|
5177
5931
|
updateAvailable: data.updateAvailable === true && latestVersion !== void 0,
|
|
5178
5932
|
...latestVersion === void 0 ? {} : { latestVersion },
|
|
@@ -5291,6 +6045,14 @@ window.__ModuleLoader__.load({
|
|
|
5291
6045
|
funnel_https_required: "funnelHttps",
|
|
5292
6046
|
funnel_start_failed: "funnelStart",
|
|
5293
6047
|
funnel_start_timeout: "funnelTimeout",
|
|
6048
|
+
funnel_state_invalid: "funnelStateInvalid",
|
|
6049
|
+
cpolar_path_invalid: "componentPathUnsafe",
|
|
6050
|
+
cloudflared_path_invalid: "componentPathUnsafe",
|
|
6051
|
+
cloudflared_tunnel_path_invalid: "componentPathUnsafe",
|
|
6052
|
+
frp_path_invalid: "componentPathUnsafe",
|
|
6053
|
+
frp_config_path_invalid: "componentPathUnsafe",
|
|
6054
|
+
frp_ingress_path_invalid: "componentPathUnsafe",
|
|
6055
|
+
caddy_path_invalid: "componentPathUnsafe",
|
|
5294
6056
|
tailscale_dns_missing: "tailscaleDnsMissing",
|
|
5295
6057
|
gateway_start_failed: "gatewayStartFailed",
|
|
5296
6058
|
control_channel_failed: "controlChannelFailed",
|
|
@@ -5388,6 +6150,7 @@ window.__ModuleLoader__.load({
|
|
|
5388
6150
|
"cpolar_component_invalid",
|
|
5389
6151
|
"cpolar_config_missing",
|
|
5390
6152
|
"cpolar_config_invalid",
|
|
6153
|
+
"funnel_state_invalid",
|
|
5391
6154
|
"cpolar_start_timeout",
|
|
5392
6155
|
"cpolar_stopped",
|
|
5393
6156
|
"cpolar_exited",
|
|
@@ -5462,6 +6225,13 @@ window.__ModuleLoader__.load({
|
|
|
5462
6225
|
...init,
|
|
5463
6226
|
signal: lifecycle.signal
|
|
5464
6227
|
}, timeoutMs);
|
|
6228
|
+
const extensionRecovery = installExtensionRecoveryControl({
|
|
6229
|
+
document,
|
|
6230
|
+
signal: lifecycle.signal,
|
|
6231
|
+
request: controlRequestJson,
|
|
6232
|
+
confirm: (message) => window.confirm(message),
|
|
6233
|
+
translate: t
|
|
6234
|
+
});
|
|
5465
6235
|
const root = element("div", "dsh-mobile-control");
|
|
5466
6236
|
root.lang = locale;
|
|
5467
6237
|
const panel = element("section", "dsh-mobile-control__panel");
|
|
@@ -6274,6 +7044,9 @@ window.__ModuleLoader__.load({
|
|
|
6274
7044
|
caddyModeManagedLabel.append(caddyModeManaged, caddyManagedText);
|
|
6275
7045
|
caddyModeExternalLabel.append(caddyModeExternal, caddyExternalText);
|
|
6276
7046
|
caddyModeRow.append(caddyModeLegend, caddyModeExternalLabel, caddyModeManagedLabel);
|
|
7047
|
+
const caddyModePreviewNote = element("p", "dsh-mobile-control__component-note");
|
|
7048
|
+
caddyModePreviewNote.textContent = t("caddyModePreviewNote");
|
|
7049
|
+
caddyModePreviewNote.hidden = true;
|
|
6277
7050
|
const caddyForm = element("div", "dsh-mobile-control__caddy-form");
|
|
6278
7051
|
caddyForm.hidden = true;
|
|
6279
7052
|
const caddyFields = element("div", "dsh-mobile-control__origin-fields");
|
|
@@ -6341,7 +7114,7 @@ window.__ModuleLoader__.load({
|
|
|
6341
7114
|
const caddyActions = element("div", "dsh-mobile-control__actions dsh-mobile-control__caddy-actions");
|
|
6342
7115
|
caddyActions.append(caddyInstall, caddySave, caddyPurgeButton);
|
|
6343
7116
|
caddyForm.append(caddyFields, caddyInstallNote, caddyStatus, caddyActions, caddyFeedback);
|
|
6344
|
-
originSetup.append(originSetupTitle, caddyModeRow, originHelp, originWarning, originFields, originFeedback, originFormActions, caddyForm, originBackend);
|
|
7117
|
+
originSetup.append(originSetupTitle, caddyModeRow, caddyModePreviewNote, originHelp, originWarning, originFields, originFeedback, originFormActions, caddyForm, originBackend);
|
|
6345
7118
|
const tailscaleInfo = element("details", "dsh-mobile-control__details");
|
|
6346
7119
|
const tailscaleInfoSummary = element("summary");
|
|
6347
7120
|
tailscaleInfoSummary.textContent = t("tailscaleHelp");
|
|
@@ -6670,7 +7443,7 @@ window.__ModuleLoader__.load({
|
|
|
6670
7443
|
lanView.append(lanSetup, access, qrBox, status, extensionStatus, actions, manageRow, devicePanel, trustedNetworks);
|
|
6671
7444
|
remoteView.append(remoteIntro, httpFrameWarning, providerSection, remoteWorkspace);
|
|
6672
7445
|
diagnosticsView.append(diagnosticsIntro, diagnosticsSummary, diagnosticsToolbar, diagnosticsFeedback, diagnosticsChecks, wsPathsSection, diagnosticsDetails);
|
|
6673
|
-
panel.append(header, releaseNotice, updateCard, appDownload, switcher, lanView, remoteView, diagnosticsView);
|
|
7446
|
+
panel.append(header, releaseNotice, updateCard, appDownload, extensionRecovery.section, switcher, lanView, remoteView, diagnosticsView);
|
|
6674
7447
|
root.append(panel);
|
|
6675
7448
|
document.body.append(root);
|
|
6676
7449
|
let running = false;
|
|
@@ -6790,6 +7563,7 @@ window.__ModuleLoader__.load({
|
|
|
6790
7563
|
providerInfoButton.setAttribute("aria-expanded", "false");
|
|
6791
7564
|
});
|
|
6792
7565
|
const selectView = (view) => {
|
|
7566
|
+
if (!panel.hidden) extensionRecovery.refresh();
|
|
6793
7567
|
if (view !== "diagnostics") previousAccessView = view;
|
|
6794
7568
|
lanView.hidden = view !== "lan";
|
|
6795
7569
|
remoteView.hidden = view !== "remote";
|
|
@@ -6820,6 +7594,7 @@ window.__ModuleLoader__.load({
|
|
|
6820
7594
|
});
|
|
6821
7595
|
const setOpen = (open) => {
|
|
6822
7596
|
panel.hidden = !open;
|
|
7597
|
+
if (open) extensionRecovery.refresh();
|
|
6823
7598
|
for (const trigger of document.querySelectorAll(".dsh-mobile-control__trigger")) trigger.setAttribute("aria-expanded", String(open));
|
|
6824
7599
|
};
|
|
6825
7600
|
const render = (data) => {
|
|
@@ -7042,6 +7817,7 @@ window.__ModuleLoader__.load({
|
|
|
7042
7817
|
const cloudflared = remoteProvider === "cloudflared";
|
|
7043
7818
|
const frp = remoteProvider === "frp";
|
|
7044
7819
|
const origin = remoteProvider === "origin";
|
|
7820
|
+
const managedOrigin = origin && data.originMode === "managed";
|
|
7045
7821
|
const tailscale = remoteProvider === "tailscale";
|
|
7046
7822
|
tailscaleChoice.classList.toggle("is-selected", tailscale);
|
|
7047
7823
|
cpolarChoice.classList.toggle("is-selected", cpolar);
|
|
@@ -7056,7 +7832,7 @@ window.__ModuleLoader__.load({
|
|
|
7056
7832
|
cloudflaredChoice.tabIndex = cloudflared ? 0 : -1;
|
|
7057
7833
|
frpChoice.setAttribute("aria-pressed", String(frp));
|
|
7058
7834
|
originChoice.setAttribute("aria-pressed", String(origin));
|
|
7059
|
-
providerSetupName.textContent = cpolar ? "cpolar" : cloudflared ? "cloudflared" : frp ? t("frpName") : origin ? t("originName") : "Tailscale Funnel";
|
|
7835
|
+
providerSetupName.textContent = cpolar ? "cpolar" : cloudflared ? "cloudflared" : frp ? t("frpName") : origin ? t(managedOrigin ? "caddyName" : "originName") : "Tailscale Funnel";
|
|
7060
7836
|
tailscaleChoice.disabled = remoteProviderBusy;
|
|
7061
7837
|
cpolarChoice.disabled = remoteProviderBusy;
|
|
7062
7838
|
cloudflaredChoice.disabled = remoteProviderBusy;
|
|
@@ -7251,6 +8027,7 @@ window.__ModuleLoader__.load({
|
|
|
7251
8027
|
const caddySupported = caddyComponentInfo !== void 0 && caddyComponentInfo.supported === true;
|
|
7252
8028
|
if (!caddyModePreviewDirty) caddyModeManagedSelected = data.originMode === "managed";
|
|
7253
8029
|
const showManaged = origin && caddyModeManagedSelected;
|
|
8030
|
+
caddyModePreviewNote.hidden = !origin || !caddyModePreviewDirty || caddyModeManagedSelected === managedOrigin;
|
|
7254
8031
|
const caddyConfiguration = typeof data.caddyConfiguration === "object" && data.caddyConfiguration !== null ? data.caddyConfiguration : {};
|
|
7255
8032
|
const caddyState = typeof data.caddyState === "object" && data.caddyState !== null ? data.caddyState : {};
|
|
7256
8033
|
caddyModeRow.hidden = !origin;
|
|
@@ -7284,7 +8061,6 @@ window.__ModuleLoader__.load({
|
|
|
7284
8061
|
caddySecretKey,
|
|
7285
8062
|
caddyPort
|
|
7286
8063
|
]) input.disabled = remoteProviderBusy || !caddySupported;
|
|
7287
|
-
selfHostedBadge.textContent = origin ? originListening ? t("originBackendReady") : t("advanced") : frpConfigured && frpInstalled ? t("ready") : t("advanced");
|
|
7288
8064
|
const state = typeof data.state === "string" ? data.state : "error";
|
|
7289
8065
|
const errorCode = typeof data.errorCode === "string" ? data.errorCode : "";
|
|
7290
8066
|
const remoteOrigin = typeof data.origin === "string" ? data.origin : "";
|
|
@@ -7297,10 +8073,11 @@ window.__ModuleLoader__.load({
|
|
|
7297
8073
|
}[errorCode] ?? "";
|
|
7298
8074
|
const needsFunnelSetup = state === "error" && remoteSetupUrl !== "";
|
|
7299
8075
|
remoteReady = remoteRunning && state === "ready" && remoteOrigin !== "";
|
|
8076
|
+
selfHostedBadge.textContent = origin ? managedOrigin ? t(remoteReady ? "ready" : "advanced") : originListening ? t("originBackendReady") : t("advanced") : frpConfigured && frpInstalled ? t("ready") : t("advanced");
|
|
7300
8077
|
remoteStateBadge.classList.toggle("is-ready", remoteReady);
|
|
7301
8078
|
remoteStateBadge.classList.toggle("is-busy", state === "starting" || state === "connecting" || state === "needs-login");
|
|
7302
8079
|
remoteStateBadge.classList.toggle("is-attention", state === "error" || state === "unavailable");
|
|
7303
|
-
remoteStateBadge.textContent = remoteReady ? origin ? t("originBackendReady") : t("ready") : state === "starting" || state === "connecting" || state === "needs-login" ? t("remoteStateConnecting") : state === "error" || state === "unavailable" ? t("remoteStateAttention") : t("remoteStateOff");
|
|
8080
|
+
remoteStateBadge.textContent = remoteReady ? origin && !managedOrigin ? t("originBackendReady") : t("ready") : state === "starting" || state === "connecting" || state === "needs-login" ? t("remoteStateConnecting") : state === "error" || state === "unavailable" ? t("remoteStateAttention") : t("remoteStateOff");
|
|
7304
8081
|
remoteAccess.hidden = !remoteReady;
|
|
7305
8082
|
remoteAccessLink.href = remoteOrigin;
|
|
7306
8083
|
remoteAccessLink.textContent = remoteOrigin;
|
|
@@ -7312,7 +8089,7 @@ window.__ModuleLoader__.load({
|
|
|
7312
8089
|
starting: cpolar ? t("remoteStartingCpolar") : cloudflared ? t("remoteStartingCloudflared") : frp ? t("remoteStartingFrp") : origin ? t("originStarting") : t("remoteStartingTailscale"),
|
|
7313
8090
|
"needs-login": t("remoteNeedsLogin"),
|
|
7314
8091
|
connecting: cpolar ? t("remoteConnectingCpolar") : cloudflared ? t("remoteConnectingCloudflared") : frp ? t("remoteConnectingFrp") : t("remoteConnectingTailscale"),
|
|
7315
|
-
ready: origin ? t("originReady") : t("remoteReady"),
|
|
8092
|
+
ready: origin ? t(managedOrigin ? "caddyVerified" : "originReady") : t("remoteReady"),
|
|
7316
8093
|
error: t("remoteError")
|
|
7317
8094
|
};
|
|
7318
8095
|
const errorLabels = translatedRemoteErrors();
|
|
@@ -8711,6 +9488,9 @@ window.__ModuleLoader__.load({
|
|
|
8711
9488
|
loadDiagnostics();
|
|
8712
9489
|
});
|
|
8713
9490
|
diagnosticsRun.addEventListener("click", loadDiagnostics);
|
|
9491
|
+
diagnosticsRun.addEventListener("click", () => {
|
|
9492
|
+
extensionRecovery.refresh();
|
|
9493
|
+
});
|
|
8714
9494
|
const closeUpdateCard = () => {
|
|
8715
9495
|
updateCard.hidden = true;
|
|
8716
9496
|
if (pluginUpdateAvailable && pluginLatestVersion !== "") updatePlugin.hidden = !diagnosticsView.hidden;
|
|
@@ -8802,10 +9582,14 @@ window.__ModuleLoader__.load({
|
|
|
8802
9582
|
if (!diagnosticsView.hidden) renderWsBlocked(wsPathsBlocked);
|
|
8803
9583
|
}, () => {});
|
|
8804
9584
|
};
|
|
8805
|
-
const wsBlockedPoll = window.setInterval(
|
|
9585
|
+
const wsBlockedPoll = window.setInterval(() => {
|
|
9586
|
+
pollWsBlocked();
|
|
9587
|
+
if (!panel.hidden) extensionRecovery.refresh();
|
|
9588
|
+
}, 2e4);
|
|
8806
9589
|
return {
|
|
8807
9590
|
remove: () => {
|
|
8808
9591
|
lifecycle.abort();
|
|
9592
|
+
extensionRecovery.dispose();
|
|
8809
9593
|
window.clearInterval(remotePoll);
|
|
8810
9594
|
window.clearInterval(wsBlockedPoll);
|
|
8811
9595
|
window.removeEventListener("focus", retryAfterSetup);
|
|
@@ -9412,30 +10196,7 @@ window.__ModuleLoader__.load({
|
|
|
9412
10196
|
const abortReason = () => signal.reason ?? new DOMException("mobile extension disposed", "AbortError");
|
|
9413
10197
|
if (signal.aborted) throw abortReason();
|
|
9414
10198
|
const bridge = window.__DSH_MOBILE_NATIVE__;
|
|
9415
|
-
if (bridge !== void 0) return
|
|
9416
|
-
let settled = false;
|
|
9417
|
-
const finish = (callback) => {
|
|
9418
|
-
if (settled) return;
|
|
9419
|
-
settled = true;
|
|
9420
|
-
signal.removeEventListener("abort", onAbort);
|
|
9421
|
-
callback();
|
|
9422
|
-
};
|
|
9423
|
-
const onAbort = () => {
|
|
9424
|
-
finish(() => {
|
|
9425
|
-
reject(abortReason());
|
|
9426
|
-
});
|
|
9427
|
-
};
|
|
9428
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
9429
|
-
Promise.resolve().then(() => bridge.invoke(action, input)).then((value) => {
|
|
9430
|
-
finish(() => {
|
|
9431
|
-
resolve(materializeNativeFile(value));
|
|
9432
|
-
});
|
|
9433
|
-
}, (error) => {
|
|
9434
|
-
finish(() => {
|
|
9435
|
-
reject(error);
|
|
9436
|
-
});
|
|
9437
|
-
});
|
|
9438
|
-
});
|
|
10199
|
+
if (bridge !== void 0) return invokeBoundNativeCapability(bridge, () => window.__DSH_MOBILE_NATIVE__, action, input, signal).then(materializeNativeFile);
|
|
9439
10200
|
if (action === "share" && typeof navigator.share === "function") {
|
|
9440
10201
|
await navigator.share(input ?? {});
|
|
9441
10202
|
return { ok: true };
|
|
@@ -10051,7 +10812,7 @@ window.__ModuleLoader__.load({
|
|
|
10051
10812
|
.dsh-mobile-control__origin-modes{min-width:0;margin:0 0 14px;padding:0;border:0}.dsh-mobile-control__origin-modes legend{margin:0 0 8px;padding:0;color:var(--dsw-alias-label-secondary,#606873);font:600 12px/1.4 system-ui}.dsh-mobile-control__origin-mode{display:flex;box-sizing:border-box;min-height:44px;align-items:center;gap:10px;padding:10px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:10px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);font:12px/1.5 system-ui;cursor:pointer}.dsh-mobile-control__origin-mode+.dsh-mobile-control__origin-mode{margin-top:8px}.dsh-mobile-control__origin-mode:has(input:checked){border-color:var(--dsw-alias-label-primary-bluish,#2563eb)}.dsh-mobile-control__origin-mode:focus-within{outline:2px solid var(--dsw-alias-state-business-primary,#2563eb);outline-offset:2px}.dsh-mobile-control__origin-mode:has(input:disabled){cursor:wait;opacity:.55}.dsh-mobile-control__origin-mode input[type=radio]{flex:0 0 18px;box-sizing:border-box;width:18px;height:18px;min-height:18px;margin:0;padding:0;accent-color:var(--dsw-alias-label-primary-bluish,#2563eb)}.dsh-mobile-control__origin-mode span{min-width:0;overflow-wrap:anywhere}
|
|
10052
10813
|
/* Base the columns on the card width, not the desktop viewport behind the popup. */
|
|
10053
10814
|
.dsh-mobile-control__origin-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,140px),1fr));gap:12px}.dsh-mobile-control__origin-fields>.dsh-mobile-control__field{min-width:0;grid-column:auto;font-size:12px}.dsh-mobile-control__origin-fields>.dsh-mobile-control__field--full{grid-column:1/-1}.dsh-mobile-control__origin-fields select{box-sizing:border-box;width:100%;min-height:44px;padding:9px 10px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);font:16px/1.4 system-ui}.dsh-mobile-control__origin-fields select:disabled{cursor:not-allowed;opacity:.55}.dsh-mobile-control__caddy-form{display:grid;gap:12px}.dsh-mobile-control__caddy-form>.dsh-mobile-control__component-note,.dsh-mobile-control__caddy-form>.dsh-mobile-control__component-status,.dsh-mobile-control__caddy-form>.dsh-mobile-control__origin-feedback{margin:0;overflow-wrap:anywhere}.dsh-mobile-control__origin-setup .dsh-mobile-control__actions.dsh-mobile-control__caddy-actions{grid-template-columns:minmax(0,1fr);margin:0}.dsh-mobile-control__caddy-actions button{box-sizing:border-box;min-width:0;min-height:44px;padding:10px 12px;border-radius:10px;font:600 12px/1.4 system-ui;cursor:pointer}.dsh-mobile-control__origin-warning{margin:12px 0;padding:10px 12px;border-left:3px solid var(--dsw-alias-state-warn-primary,#a56810);border-radius:4px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-primary,#384152);font-size:12px;line-height:1.6}.dsh-mobile-control__origin-feedback{font-size:12px;line-height:1.6;overflow-wrap:anywhere}.dsh-mobile-control__origin-feedback.is-error{color:var(--dsw-alias-state-error-primary,#bc3030)}.dsh-mobile-control__origin-fields input[aria-invalid=true]{border-color:var(--dsw-alias-state-error-primary,#bc3030)}.dsh-mobile-control__origin-backend{display:flex;min-width:0;flex-direction:column;align-items:stretch;gap:8px;margin-top:12px;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l2,#dbe1e8)}.dsh-mobile-control__origin-backend button{align-self:flex-start;min-height:44px}.dsh-mobile-control__self-hosted-body .is-origin{margin-top:8px}
|
|
10054
|
-
|
|
10815
|
+
` + EXTENSION_RECOVERY_STYLES;
|
|
10055
10816
|
/** Mount the desktop control or mobile feature enhancements. */
|
|
10056
10817
|
function apply(ctx) {
|
|
10057
10818
|
ctx.effect(() => {
|
|
@@ -10086,6 +10847,11 @@ window.__ModuleLoader__.load({
|
|
|
10086
10847
|
preference: localFont.preference,
|
|
10087
10848
|
locale: selectedMobileControlLocale()
|
|
10088
10849
|
}));
|
|
10850
|
+
const removeDisplayScale = ctx.slots.register({
|
|
10851
|
+
name: "settings.general.item",
|
|
10852
|
+
id: "dsh-mobile-display-scale",
|
|
10853
|
+
order: 12
|
|
10854
|
+
}, () => (0, react.createElement)(MobileDisplayScaleRow, { locale: selectedMobileControlLocale() }));
|
|
10089
10855
|
const removeSwitchComputer = ctx.slots.register({
|
|
10090
10856
|
name: "settings.general.item",
|
|
10091
10857
|
id: "dsh-mobile-switch-computer",
|
|
@@ -10099,6 +10865,7 @@ window.__ModuleLoader__.load({
|
|
|
10099
10865
|
return () => {
|
|
10100
10866
|
removeTaskNotifications();
|
|
10101
10867
|
removeSwitchComputer();
|
|
10868
|
+
removeDisplayScale();
|
|
10102
10869
|
removeFontSize();
|
|
10103
10870
|
};
|
|
10104
10871
|
});
|