dsh-mobile 0.5.5 → 0.6.1
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 +45 -1
- package/CONTRIBUTORS.md +23 -6
- package/README.en.md +120 -39
- package/README.md +120 -41
- package/SECURITY.md +16 -5
- package/THIRD_PARTY_NOTICES.md +9 -1
- package/docs/ATTACH_EXISTING_FRPS.en.md +9 -8
- package/docs/ATTACH_EXISTING_FRPS.md +16 -13
- package/docs/CADDY_MANAGED.en.md +61 -0
- package/docs/CADDY_MANAGED.md +61 -0
- package/docs/CLIENT_MODULES.en.md +33 -0
- package/docs/CLIENT_MODULES.md +33 -0
- package/docs/CLOUDFLARE_TUNNEL.en.md +14 -8
- package/docs/CLOUDFLARE_TUNNEL.md +14 -8
- package/docs/README.md +30 -18
- package/docs/SELF_HOSTED_FRP.en.md +6 -6
- package/docs/SELF_HOSTED_FRP.md +6 -6
- package/docs/SELF_HOSTED_ORIGIN.en.md +67 -17
- package/docs/SELF_HOSTED_ORIGIN.md +1 -1
- package/docs/SLOW_CONNECTIONS.en.md +16 -0
- package/docs/SLOW_CONNECTIONS.md +16 -0
- package/docs/TRUSTED_NETWORKS.en.md +26 -0
- package/docs/TRUSTED_NETWORKS.md +26 -0
- package/lib/cli.js +33 -5
- package/lib/cli.js.map +1 -1
- package/lib/client.js +1184 -24
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +112 -15
- package/lib/index.mjs +2173 -330
- 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 +57 -9
- package/lib/mobile-layout.js.map +1 -1
- package/package.json +14 -3
package/lib/client.js
CHANGED
|
@@ -514,7 +514,24 @@ window.__ModuleLoader__.load({
|
|
|
514
514
|
wsPathsAdvancedHint: "通常不需要手动填写:用上方按钮即可。只有当你确切知道所需路径时才填。"
|
|
515
515
|
});
|
|
516
516
|
Object.assign(MOBILE_CONTROL_MESSAGES.en, {
|
|
517
|
+
trustedNetworksTitle: "Additional trusted networks · Advanced",
|
|
518
|
+
trustedNetworksHelp: "Keep the LAN subnet and explicitly add only networks you control, such as a routed WireGuard subnet. Device pairing and HTTPS are still required. Separate CIDRs with spaces, commas, or newlines.",
|
|
519
|
+
trustedNetworksLabel: "Additional source networks",
|
|
520
|
+
trustedNetworksSave: "Save networks",
|
|
521
|
+
trustedNetworksUnavailable: "Available only with managed LAN setup.",
|
|
522
|
+
trustedNetworksRestart: "Saved. Restart DSH to apply; existing connections are not interrupted now.",
|
|
523
|
+
trustedNetworksWindows: "On Windows, the firewall must also allow these source networks; the default rule allows LocalSubnet only.",
|
|
524
|
+
trustedNetworksFailed: "Could not read this setting. Retry after reopening this section.",
|
|
525
|
+
trustedNetworksInvalid: "Could not save. Enter valid network CIDRs, for example 11.24.0.0/24, and check that DSH can write its private setup file.",
|
|
517
526
|
cpolarDownloadFailed: "Could not download cpolar from its official site. Check this computer’s network or proxy, then retry.",
|
|
527
|
+
caddyUnavailable: "The fixed Caddy component is not published for this candidate. Keep using an existing reverse proxy or another remote provider; unverified downloads will not run.",
|
|
528
|
+
caddyVerified: "Managed HTTPS entry verified for this computer.",
|
|
529
|
+
caddyInstalled: "Caddy component installed; the connection is not yet verified.",
|
|
530
|
+
caddyConnectionFailed: "Managed HTTPS connection was not established. Check the domain, public inbound access, port mapping and DNS credentials.",
|
|
531
|
+
caddyModeChangeConfirm: "Switching this origin mode closes its current connection. Continue?",
|
|
532
|
+
caddyInstallConfirm: "Download and verify the fixed Caddy build into DSH Mobile’s private directory? No system service, PATH or startup item will be installed.",
|
|
533
|
+
caddyInstallFailed: "Caddy was not installed. Check that the fixed artifact is available and verified; do not bypass its checksum.",
|
|
534
|
+
caddyPurgeFailed: "Cleanup was not completed. Stop the managed connection and retry.",
|
|
518
535
|
cpolarDownloadTimeout: "The cpolar download timed out. Check this computer’s network or proxy, then retry.",
|
|
519
536
|
cpolarDownloadHttp: "The official cpolar download returned HTTP {status}. Retry later or check the official download page.",
|
|
520
537
|
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.",
|
|
@@ -522,7 +539,24 @@ window.__ModuleLoader__.load({
|
|
|
522
539
|
cpolarStorageFailed: "Could not write the cpolar component in DSH Mobile’s private directory. Check disk space, permissions, and security software, then retry."
|
|
523
540
|
});
|
|
524
541
|
Object.assign(MOBILE_CONTROL_MESSAGES.it, {
|
|
542
|
+
trustedNetworksTitle: "Reti attendibili aggiuntive · Avanzate",
|
|
543
|
+
trustedNetworksHelp: "Mantieni la sottorete LAN e aggiungi solo reti che controlli, come una sottorete WireGuard instradata. Abbinamento e HTTPS restano obbligatori. Separa i CIDR con spazi, virgole o righe nuove.",
|
|
544
|
+
trustedNetworksLabel: "Reti sorgenti aggiuntive",
|
|
545
|
+
trustedNetworksSave: "Salva reti",
|
|
546
|
+
trustedNetworksUnavailable: "Disponibile solo con la configurazione LAN gestita.",
|
|
547
|
+
trustedNetworksRestart: "Salvato. Riavvia DSH per applicare; le connessioni attuali non vengono interrotte.",
|
|
548
|
+
trustedNetworksWindows: "Su Windows anche il firewall deve consentire queste reti sorgenti; la regola predefinita consente solo LocalSubnet.",
|
|
549
|
+
trustedNetworksFailed: "Impossibile leggere questa impostazione. Riapri la sezione e riprova.",
|
|
550
|
+
trustedNetworksInvalid: "Salvataggio non riuscito. Inserisci CIDR validi, ad esempio 11.24.0.0/24, e verifica che DSH possa scrivere il file privato di configurazione.",
|
|
525
551
|
cpolarDownloadFailed: "Impossibile scaricare cpolar dal sito ufficiale. Controlla rete o proxy del computer e riprova.",
|
|
552
|
+
caddyUnavailable: "Il componente Caddy fisso non è pubblicato per questa versione candidata. Usa il tuo proxy o un altro provider; i download non verificati non vengono eseguiti.",
|
|
553
|
+
caddyVerified: "Ingresso HTTPS gestito verificato per questo computer.",
|
|
554
|
+
caddyInstalled: "Componente Caddy installato; connessione non ancora verificata.",
|
|
555
|
+
caddyConnectionFailed: "Connessione HTTPS gestita non stabilita. Controlla dominio, accesso pubblico, porte e credenziali DNS.",
|
|
556
|
+
caddyModeChangeConfirm: "Cambiare modalità origin chiude la connessione attuale. Continuare?",
|
|
557
|
+
caddyInstallConfirm: "Scaricare e verificare Caddy nella directory privata DSH Mobile? Nessun servizio, PATH o avvio automatico viene installato.",
|
|
558
|
+
caddyInstallFailed: "Caddy non installato. Verifica disponibilità e integrità del file fisso; non ignorare il checksum.",
|
|
559
|
+
caddyPurgeFailed: "Pulizia non completata. Arresta la connessione gestita e riprova.",
|
|
526
560
|
cpolarDownloadTimeout: "Download di cpolar scaduto. Controlla rete o proxy del computer e riprova.",
|
|
527
561
|
cpolarDownloadHttp: "Il download ufficiale di cpolar ha restituito HTTP {status}. Riprova più tardi o controlla la pagina ufficiale.",
|
|
528
562
|
cpolarIntegrityFailed: "Il pacchetto cpolar non corrisponde alla versione verificata e non è stato installato. Riprova più tardi o aggiorna DSH Mobile; non aggirare la verifica.",
|
|
@@ -531,6 +565,23 @@ window.__ModuleLoader__.load({
|
|
|
531
565
|
});
|
|
532
566
|
Object.assign(MOBILE_CONTROL_MESSAGES.zh, {
|
|
533
567
|
cpolarDownloadFailed: "无法从 cpolar 官网下载组件。请检查这台电脑的网络或代理,然后重试。",
|
|
568
|
+
caddyUnavailable: "本次候选尚未发布固定 Caddy 组件。请继续使用已有反向代理或其他远程方式;不会执行未经校验的下载。",
|
|
569
|
+
caddyVerified: "托管 HTTPS 入口已验证为这台电脑。",
|
|
570
|
+
caddyInstalled: "Caddy 组件已安装,远程连接尚未验证。",
|
|
571
|
+
caddyConnectionFailed: "托管 HTTPS 连接未建立,请检查域名、公网入站、端口映射和 DNS 凭据。",
|
|
572
|
+
caddyModeChangeConfirm: "切换此自有代理模式会关闭它当前的连接,是否继续?",
|
|
573
|
+
caddyInstallConfirm: "从固定来源下载并校验 Caddy 到 DSH Mobile 私有目录?不会安装系统服务、写入 PATH 或设置开机启动。",
|
|
574
|
+
caddyInstallFailed: "Caddy 没有安装成功,请检查固定组件是否可用并通过校验,不要跳过校验。",
|
|
575
|
+
caddyPurgeFailed: "尚未完成清理,请停止受管连接后重试。",
|
|
576
|
+
trustedNetworksTitle: "额外可信网段 · 高级",
|
|
577
|
+
trustedNetworksHelp: "保留当前局域网网段,只追加你可控的网络,例如路由接入的 WireGuard 网段。配对与 HTTPS 校验仍然保留。每行一个 CIDR,也可用空格或逗号分隔。",
|
|
578
|
+
trustedNetworksLabel: "额外允许的来源网段",
|
|
579
|
+
trustedNetworksSave: "保存网段",
|
|
580
|
+
trustedNetworksUnavailable: "仅托管局域网配置支持此设置。",
|
|
581
|
+
trustedNetworksRestart: "已保存,重启 DSH 后生效;不会立即中断当前连接。",
|
|
582
|
+
trustedNetworksWindows: "Windows 防火墙也需要放行这些来源网段;默认规则只允许 LocalSubnet。",
|
|
583
|
+
trustedNetworksFailed: "暂时无法读取设置,请重新打开此区域后重试。",
|
|
584
|
+
trustedNetworksInvalid: "保存失败。请填写有效的网络 CIDR,例如 11.24.0.0/24,并确认 DSH 可写入私有配置文件。",
|
|
534
585
|
cpolarDownloadTimeout: "下载 cpolar 组件超时。请检查这台电脑的网络或代理,然后重试。",
|
|
535
586
|
cpolarDownloadHttp: "cpolar 官网下载返回 HTTP {status}。请稍后重试,或检查官网下载页。",
|
|
536
587
|
cpolarIntegrityFailed: "下载的 cpolar 组件未通过固定版本校验,因此没有安装。请稍后重试或更新 DSH Mobile,不要跳过校验。",
|
|
@@ -1099,6 +1150,21 @@ window.__ModuleLoader__.load({
|
|
|
1099
1150
|
originCopyBackend: "Copy backend address",
|
|
1100
1151
|
originBackendCopied: "HTTP backend address copied.",
|
|
1101
1152
|
originPurge: "Clear proxy settings",
|
|
1153
|
+
originHttpsTitle: "HTTPS remote access",
|
|
1154
|
+
originHttpsMode: "HTTPS handling",
|
|
1155
|
+
caddyModeManaged: "Managed Caddy (plugin handles HTTPS and certificates)",
|
|
1156
|
+
caddyModeExternal: "I already run my own reverse proxy",
|
|
1157
|
+
caddySectionTitle: "Managed Caddy upstream",
|
|
1158
|
+
caddyInstall: "Install managed Caddy",
|
|
1159
|
+
caddyInstallNote: "Downloaded from the official Caddy custom build (caddy core + Tencent Cloud DNS plugin for DNS-01), verified by checksum, stored in the plugin private directory. No system service, PATH entry, or startup item is added.",
|
|
1160
|
+
caddyDomain: "Public domain",
|
|
1161
|
+
caddyDnsProvider: "DNS provider (DNS-01)",
|
|
1162
|
+
caddySecretId: "Tencent Cloud SecretId",
|
|
1163
|
+
caddySecretKey: "Tencent Cloud SecretKey",
|
|
1164
|
+
caddyListenPort: "Public HTTPS port",
|
|
1165
|
+
caddySaveConnect: "Save and start Caddy",
|
|
1166
|
+
caddyPurge: "Remove managed Caddy and settings",
|
|
1167
|
+
caddyReady: "Managed Caddy is running; certificates are issued and renewed automatically.",
|
|
1102
1168
|
originPurgeConfirm: "Stop this private HTTP backend and delete only its reverse-proxy settings? Paired remote devices, LAN access, and other providers are kept.",
|
|
1103
1169
|
originPurged: "The backend is stopped and its proxy settings are cleared. Paired devices are kept.",
|
|
1104
1170
|
originSaving: "Saving settings and starting the private backend…",
|
|
@@ -1141,6 +1207,21 @@ window.__ModuleLoader__.load({
|
|
|
1141
1207
|
originCopyBackend: "Copia indirizzo backend",
|
|
1142
1208
|
originBackendCopied: "Indirizzo backend HTTP copiato.",
|
|
1143
1209
|
originPurge: "Cancella impostazioni proxy",
|
|
1210
|
+
originHttpsTitle: "Accesso remoto HTTPS",
|
|
1211
|
+
originHttpsMode: "Gestione HTTPS",
|
|
1212
|
+
caddyModeManaged: "Caddy gestito (il plugin gestisce HTTPS e certificati)",
|
|
1213
|
+
caddyModeExternal: "Uso già il mio reverse proxy",
|
|
1214
|
+
caddySectionTitle: "Upstream Caddy gestito",
|
|
1215
|
+
caddyInstall: "Installa Caddy gestito",
|
|
1216
|
+
caddyInstallNote: "Scaricato dalla build personalizzata ufficiale di Caddy (core + plugin DNS Tencent Cloud per DNS-01), verificato tramite checksum e conservato nella directory privata del plugin. Non aggiunge servizi di sistema, voci PATH o avvio automatico.",
|
|
1217
|
+
caddyDomain: "Dominio pubblico",
|
|
1218
|
+
caddyDnsProvider: "Provider DNS (DNS-01)",
|
|
1219
|
+
caddySecretId: "SecretId Tencent Cloud",
|
|
1220
|
+
caddySecretKey: "SecretKey Tencent Cloud",
|
|
1221
|
+
caddyListenPort: "Porta HTTPS pubblica",
|
|
1222
|
+
caddySaveConnect: "Salva e avvia Caddy",
|
|
1223
|
+
caddyPurge: "Rimuovi Caddy gestito e impostazioni",
|
|
1224
|
+
caddyReady: "Caddy gestito in esecuzione; i certificati vengono emessi e rinnovati automaticamente.",
|
|
1144
1225
|
originPurgeConfirm: "Fermare questo backend HTTP privato ed eliminare solo le sue impostazioni reverse proxy? Dispositivi remoti abbinati, accesso LAN e altri provider vengono conservati.",
|
|
1145
1226
|
originPurged: "Backend fermato e impostazioni proxy cancellate. I dispositivi abbinati sono conservati.",
|
|
1146
1227
|
originSaving: "Salvataggio impostazioni e avvio del backend privato…",
|
|
@@ -1183,6 +1264,21 @@ window.__ModuleLoader__.load({
|
|
|
1183
1264
|
originCopyBackend: "复制后端地址",
|
|
1184
1265
|
originBackendCopied: "已复制 HTTP 后端地址。",
|
|
1185
1266
|
originPurge: "清除代理配置",
|
|
1267
|
+
originHttpsTitle: "HTTPS 远程接入",
|
|
1268
|
+
originHttpsMode: "HTTPS 接入方式",
|
|
1269
|
+
caddyModeManaged: "托管 Caddy(插件负责 HTTPS 与证书)",
|
|
1270
|
+
caddyModeExternal: "我已自行配置反向代理",
|
|
1271
|
+
caddySectionTitle: "托管 Caddy 上游",
|
|
1272
|
+
caddyInstall: "安装托管 Caddy",
|
|
1273
|
+
caddyInstallNote: "从 Caddy 官方定制构建下载(caddy 核心 + 腾讯云 DNS 插件,用于 DNS-01),经校验和验证后保存在插件私有目录。不添加系统服务、PATH 条目或开机启动项。",
|
|
1274
|
+
caddyDomain: "公网域名",
|
|
1275
|
+
caddyDnsProvider: "DNS 提供商(DNS-01)",
|
|
1276
|
+
caddySecretId: "腾讯云 SecretId",
|
|
1277
|
+
caddySecretKey: "腾讯云 SecretKey",
|
|
1278
|
+
caddyListenPort: "公网 HTTPS 端口",
|
|
1279
|
+
caddySaveConnect: "保存并启动 Caddy",
|
|
1280
|
+
caddyPurge: "移除托管 Caddy 与配置",
|
|
1281
|
+
caddyReady: "托管 Caddy 正在运行;证书会自动签发并续期。",
|
|
1186
1282
|
originPurgeConfirm: "停止此私有 HTTP 后端,并且只删除它的反向代理配置?已配对的远程设备、局域网访问和其他提供方均保留。",
|
|
1187
1283
|
originPurged: "已停止后端并清除代理配置,已配对设备仍保留。",
|
|
1188
1284
|
originSaving: "正在保存配置并启动私有后端…",
|
|
@@ -2648,6 +2744,16 @@ window.__ModuleLoader__.load({
|
|
|
2648
2744
|
.dsh-mobile-settings_selector:active:not(:disabled) { background:var(--dsw-alias-interactive-bg-active,var(--dsw-alias-interactive-bg-hover)); }
|
|
2649
2745
|
.dsh-mobile-settings_selector:focus-visible { outline:2px solid var(--dsw-alias-label-primary-bluish,#2563eb); outline-offset:2px; }
|
|
2650
2746
|
.dsh-mobile-settings_selector:disabled { cursor:wait; opacity:.55; }
|
|
2747
|
+
.dsh-mobile-settings_fontControls { display:flex; align-items:center; gap:8px; flex-shrink:0; }
|
|
2748
|
+
.dsh-mobile-settings_fontControls button { min-width:48px; padding:0 12px; }
|
|
2749
|
+
.dsh-mobile-settings_fontControls output { min-width:42px; text-align:center; font-variant-numeric:tabular-nums; }
|
|
2750
|
+
/* The mobile row replaces the Host-backed font control, not the theme selector. */
|
|
2751
|
+
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; }
|
|
2651
2757
|
@media ${NATIVE_MOBILE_OVERLAY_QUERY} {
|
|
2652
2758
|
html.dsh-native-mobile-active,html.dsh-native-mobile-active body { width:100%; height:100%; overflow:hidden; }
|
|
2653
2759
|
html.dsh-native-mobile-active { --dsh-mobile-motion-duration:200ms; --dsh-mobile-motion-ease:cubic-bezier(.22,1,.36,1); }
|
|
@@ -2826,20 +2932,33 @@ window.__ModuleLoader__.load({
|
|
|
2826
2932
|
owned by DSH. Toolbar focus keeps the card expanded during menu taps. */
|
|
2827
2933
|
[data-dsh-mobile-center] [data-composer-card]:not(:focus-within) > [data-input-scroll] { max-height:72px !important; overflow-y:auto !important; }
|
|
2828
2934
|
/* Stock Send/Queue/Steer and Stop keep their handlers and disabled state. */
|
|
2829
|
-
[data-dsh-mobile-composer-row] button { min-height:44px !important; touch-action:manipulation; }
|
|
2935
|
+
[data-dsh-mobile-composer-row] button { min-width:44px !important; min-height:44px !important; touch-action:manipulation; }
|
|
2830
2936
|
[data-dsh-mobile-composer-row] button[class*="_primary"] { min-width:44px !important; flex-shrink:0 !important; }
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
[data-dsh-mobile-composer-
|
|
2835
|
-
[data-dsh-mobile-composer-tools]
|
|
2836
|
-
[data-dsh-mobile-composer-
|
|
2837
|
-
[data-dsh-mobile-composer-
|
|
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; }
|
|
2838
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; }
|
|
2839
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; }
|
|
2840
2951
|
[data-dsh-mobile-center] [class*="_root"]:has(> [class*="_card"] textarea) { box-sizing:border-box !important; width:100% !important; padding:0 0 8px !important; }
|
|
2841
2952
|
[data-dsh-mobile-center] [class*="_root"]:has(> [class*="_card"] textarea) > [class=""]:last-child { display:none !important; }
|
|
2842
2953
|
}
|
|
2954
|
+
@media (max-width:720px) and (max-height:500px) {
|
|
2955
|
+
/* An open software keyboard leaves less room for the focused draft. */
|
|
2956
|
+
html.dsh-native-mobile-active [data-dsh-mobile-center] [data-composer-card] > [data-input-scroll] { max-height:72px !important; overflow-y:auto !important; }
|
|
2957
|
+
}
|
|
2958
|
+
@media (max-width:720px) and (max-height:400px) {
|
|
2959
|
+
[data-dsh-mobile-center] [data-composer-card] { gap:8px !important; }
|
|
2960
|
+
html.dsh-native-mobile-active [data-dsh-mobile-center] [data-composer-card] > [data-input-scroll] { max-height:48px !important; }
|
|
2961
|
+
}
|
|
2843
2962
|
@keyframes dsh-mobile-fade-in { from { opacity:0; } }
|
|
2844
2963
|
@keyframes dsh-mobile-panel-in { from { opacity:.72; transform:translateY(6px); } }
|
|
2845
2964
|
@keyframes dsh-mobile-view-in { from { opacity:.58; transform:translateY(5px); } }
|
|
@@ -3107,7 +3226,7 @@ window.__ModuleLoader__.load({
|
|
|
3107
3226
|
/** Only a known on-screen keyboard in an active App composer may change Enter into a line break. */
|
|
3108
3227
|
function isSoftKeyboardEnterLineBreak(event, context) {
|
|
3109
3228
|
if (context.nativeState?.imeVisible !== true || context.nativeState.noHardwareKeyboard !== true || !context.editable || !context.activeSession || context.commandMenuOpen || context.recentlyComposing) return false;
|
|
3110
|
-
if (!event.isTrusted || event.key !== "Enter" || event.isComposing || event.keyCode === 229 || event.repeat) return false;
|
|
3229
|
+
if (!event.isTrusted || event.key !== "Enter" || event.defaultPrevented || event.isComposing || event.keyCode === 229 || event.repeat) return false;
|
|
3111
3230
|
return !event.shiftKey && !event.altKey && !event.ctrlKey && !event.metaKey && !event.getModifierState("AltGraph");
|
|
3112
3231
|
}
|
|
3113
3232
|
/** Attach the App-only Enter translation while retaining DSH's original physical-key semantics. */
|
|
@@ -3146,10 +3265,193 @@ window.__ModuleLoader__.load({
|
|
|
3146
3265
|
editor.removeEventListener("keydown", onKeyDown, { capture: true });
|
|
3147
3266
|
};
|
|
3148
3267
|
}
|
|
3268
|
+
/**
|
|
3269
|
+
* Touch-primary device hint for the browser Enter translation. Duplicates the literal of
|
|
3270
|
+
* `TOUCH_PRIMARY_QUERY` in mobile-layout.ts, which builds a separate bundle entry that
|
|
3271
|
+
* this module must not import; a source test keeps the two from drifting apart.
|
|
3272
|
+
*/
|
|
3273
|
+
const BROWSER_TOUCH_PRIMARY_QUERY = "(hover: none), (pointer: coarse)";
|
|
3274
|
+
/** The stock composer editor a phone-browser Enter may keep as a native line break. */
|
|
3275
|
+
const BROWSER_COMPOSER_EDITOR_QUERY = "[data-composer-input][contenteditable=\"true\"]";
|
|
3276
|
+
/**
|
|
3277
|
+
* Whether the App adapter or its earlier origin-scoped WebMessage channel owns Enter.
|
|
3278
|
+
* @param view - the page's current native bridge indicators.
|
|
3279
|
+
* @returns true without assuming that native keyboard state is already available.
|
|
3280
|
+
*/
|
|
3281
|
+
function nativeAppOwnsComposerEnter(view) {
|
|
3282
|
+
const channel = view.dshMobileNative;
|
|
3283
|
+
return view.__DSH_MOBILE_NATIVE__ !== void 0 || typeof channel === "object" && channel !== null && "postMessage" in channel && typeof channel.postMessage === "function";
|
|
3284
|
+
}
|
|
3285
|
+
/**
|
|
3286
|
+
* Whether a phone-browser plain Enter keeps its native editing action instead of reaching
|
|
3287
|
+
* DSH's submit shortcut. Unlike the App gate there is no soft-keyboard proof, so this
|
|
3288
|
+
* mirrors the shipped question-card policy: the touch-primary hint plus the same event,
|
|
3289
|
+
* editor, and IME guards. A held key (`repeat`) keeps the native behavior every editor
|
|
3290
|
+
* has, and already-prevented or modified Enters stay under stock control — Cmd/Ctrl+Enter
|
|
3291
|
+
* keeps its stock submit path, which is how an attached hardware keyboard still sends.
|
|
3292
|
+
*/
|
|
3293
|
+
function isBrowserTouchEnterLineBreak(event, context) {
|
|
3294
|
+
if (context.appBridge || !context.browserTouch || !context.editable || !context.activeSession || context.commandMenuOpen || context.recentlyComposing || !context.hasDraft) return false;
|
|
3295
|
+
if (!event.isTrusted || event.key !== "Enter" || event.defaultPrevented || event.isComposing || event.keyCode === 229) return false;
|
|
3296
|
+
return !event.shiftKey && !event.altKey && !event.ctrlKey && !event.metaKey && !event.getModifierState("AltGraph");
|
|
3297
|
+
}
|
|
3298
|
+
/**
|
|
3299
|
+
* Whether the stock composer still holds a draft. Mirrors `composerHasDraft` in
|
|
3300
|
+
* mobile-layout.ts, which builds a separate bundle entry this module must not import;
|
|
3301
|
+
* keep the two shapes aligned (checked against text, embedded decorators, attachments).
|
|
3302
|
+
*/
|
|
3303
|
+
function browserComposerHasDraft(editor, card) {
|
|
3304
|
+
return (editor.textContent ?? "").replaceAll("", "").trim() !== "" || editor.querySelector("[contenteditable=\"false\"],[data-lexical-decorator],img") !== null || card.querySelector("[data-slot=\"conversation.input.attachments\"] [role=\"group\"] > *") !== null;
|
|
3305
|
+
}
|
|
3306
|
+
/**
|
|
3307
|
+
* Composition tracking for the document-capture Enter translation. iOS Safari fires the
|
|
3308
|
+
* composition-confirming Enter with `isComposing === false` in the same task as
|
|
3309
|
+
* `compositionend`, so ownership extends ten milliseconds past the event — the same window
|
|
3310
|
+
* the stock composer and the App binder use.
|
|
3311
|
+
*/
|
|
3312
|
+
function createDocumentCompositionGuard(target) {
|
|
3313
|
+
let composing = false;
|
|
3314
|
+
let composingUntil = 0;
|
|
3315
|
+
const onCompositionStart = () => {
|
|
3316
|
+
composing = true;
|
|
3317
|
+
};
|
|
3318
|
+
const onCompositionEnd = () => {
|
|
3319
|
+
composing = false;
|
|
3320
|
+
composingUntil = performance.now() + 10;
|
|
3321
|
+
};
|
|
3322
|
+
target.addEventListener("compositionstart", onCompositionStart, { capture: true });
|
|
3323
|
+
target.addEventListener("compositionend", onCompositionEnd, { capture: true });
|
|
3324
|
+
return {
|
|
3325
|
+
recentlyComposing: () => composing || performance.now() < composingUntil,
|
|
3326
|
+
dispose: () => {
|
|
3327
|
+
target.removeEventListener("compositionstart", onCompositionStart, { capture: true });
|
|
3328
|
+
target.removeEventListener("compositionend", onCompositionEnd, { capture: true });
|
|
3329
|
+
}
|
|
3330
|
+
};
|
|
3331
|
+
}
|
|
3332
|
+
/** Locate the stock composer editor under a keydown target: the editor itself or a node inside it. */
|
|
3333
|
+
function resolveBrowserComposerEditor(target) {
|
|
3334
|
+
const candidate = target;
|
|
3335
|
+
if (candidate === null || typeof candidate.closest !== "function") return null;
|
|
3336
|
+
const editor = candidate.closest.call(target, BROWSER_COMPOSER_EDITOR_QUERY);
|
|
3337
|
+
if (editor === null) return null;
|
|
3338
|
+
const card = editor.closest("[data-composer-card]");
|
|
3339
|
+
return card === null ? null : {
|
|
3340
|
+
editor,
|
|
3341
|
+
card
|
|
3342
|
+
};
|
|
3343
|
+
}
|
|
3344
|
+
/**
|
|
3345
|
+
* Phone-browser Enter translation for the stock composer. Lexical refuses keydown Enter
|
|
3346
|
+
* on Apple browsers and performs the line break from the native `beforeinput` flow, so
|
|
3347
|
+
* unlike the App binding this hides the keydown with `stopPropagation` only — no
|
|
3348
|
+
* `preventDefault`, no synthetic event — letting the browser's own insertParagraph produce
|
|
3349
|
+
* the same draft line break desktop Shift+Enter yields, while stock submit never runs.
|
|
3350
|
+
* `stopPropagation` (never the immediate variant) keeps same-node document listeners such
|
|
3351
|
+
* as the send-keyboard typing tracker and the question-card handler running. Document
|
|
3352
|
+
* capture runs before React root handlers and needs no per-editor binding lifecycle.
|
|
3353
|
+
*/
|
|
3354
|
+
function bindBrowserComposerSoftEnter(view, context) {
|
|
3355
|
+
const guard = createDocumentCompositionGuard(view);
|
|
3356
|
+
const touchPrimary = typeof window === "object" && typeof window.matchMedia === "function" ? window.matchMedia(BROWSER_TOUCH_PRIMARY_QUERY) : void 0;
|
|
3357
|
+
const onKeyDown = (event) => {
|
|
3358
|
+
const target = resolveBrowserComposerEditor(event.target);
|
|
3359
|
+
if (target === null) return;
|
|
3360
|
+
if (!isBrowserTouchEnterLineBreak(event, {
|
|
3361
|
+
...context(target.editor, target.card),
|
|
3362
|
+
browserTouch: touchPrimary?.matches === true,
|
|
3363
|
+
hasDraft: browserComposerHasDraft(target.editor, target.card),
|
|
3364
|
+
recentlyComposing: guard.recentlyComposing() || target.editor.hasAttribute("data-composer-composing")
|
|
3365
|
+
})) return;
|
|
3366
|
+
event.stopPropagation();
|
|
3367
|
+
};
|
|
3368
|
+
view.addEventListener("keydown", onKeyDown, { capture: true });
|
|
3369
|
+
return () => {
|
|
3370
|
+
view.removeEventListener("keydown", onKeyDown, { capture: true });
|
|
3371
|
+
guard.dispose();
|
|
3372
|
+
};
|
|
3373
|
+
}
|
|
3149
3374
|
/** Check that an asynchronous picker result still belongs to its originating session and composer. */
|
|
3150
3375
|
function isComposerMediaOriginCurrent(origin, current) {
|
|
3151
3376
|
return !current.disposed && origin.generation === current.generation && origin.href === current.href && origin.composer !== null && current.composerConnected && origin.composer === current.composer && origin.sessionRoot !== null && origin.sessionRoot === current.sessionRoot && origin.sessionId !== null && origin.sessionId === current.sessionId;
|
|
3152
3377
|
}
|
|
3378
|
+
/**
|
|
3379
|
+
* The App action that leaves the page for its paired-computer list. Mirrors the literal the
|
|
3380
|
+
* General-settings row invokes; the gesture below reads the same name from the bridge
|
|
3381
|
+
* capability list, so an App that does not advertise it keeps the stock drawer behavior.
|
|
3382
|
+
*/
|
|
3383
|
+
const SWITCH_COMPUTER_NATIVE_ACTION = "mobile.switch-computer";
|
|
3384
|
+
/** Keeps the drawer from following the finger Android lifts at the end of a completed hold. */
|
|
3385
|
+
const SWITCH_COMPUTER_CLICK_SUPPRESSION_MS = 1200;
|
|
3386
|
+
/**
|
|
3387
|
+
* Hold policy for the collapsed header's drawer toggle. The App owns the paired-computer
|
|
3388
|
+
* list, so a hold only acts once the bridge advertises the action; every other press keeps
|
|
3389
|
+
* the stock toggle. DOM events stay in the installer so this policy stays testable.
|
|
3390
|
+
*/
|
|
3391
|
+
function createSwitchComputerHold(options) {
|
|
3392
|
+
let hold;
|
|
3393
|
+
let supported = false;
|
|
3394
|
+
let probing = false;
|
|
3395
|
+
let disposed = false;
|
|
3396
|
+
let suppressClickUntil = 0;
|
|
3397
|
+
let firedAt = 0;
|
|
3398
|
+
const probeSupport = () => {
|
|
3399
|
+
if (disposed || supported || probing) return;
|
|
3400
|
+
probing = true;
|
|
3401
|
+
Promise.resolve().then(options.canSwitchComputer).then((value) => {
|
|
3402
|
+
probing = false;
|
|
3403
|
+
if (!disposed && value) supported = true;
|
|
3404
|
+
}, () => {
|
|
3405
|
+
probing = false;
|
|
3406
|
+
});
|
|
3407
|
+
};
|
|
3408
|
+
const stopTimer = () => {
|
|
3409
|
+
if (hold === void 0) return;
|
|
3410
|
+
options.clearTimer(hold.timer);
|
|
3411
|
+
hold = void 0;
|
|
3412
|
+
};
|
|
3413
|
+
const fire = (current) => {
|
|
3414
|
+
if (hold !== current || current.fired || !supported) return;
|
|
3415
|
+
current.fired = true;
|
|
3416
|
+
firedAt = options.now();
|
|
3417
|
+
suppressClickUntil = firedAt + SWITCH_COMPUTER_CLICK_SUPPRESSION_MS;
|
|
3418
|
+
options.switchComputer();
|
|
3419
|
+
};
|
|
3420
|
+
return {
|
|
3421
|
+
start: (x, y) => {
|
|
3422
|
+
if (disposed) return;
|
|
3423
|
+
stopTimer();
|
|
3424
|
+
suppressClickUntil = 0;
|
|
3425
|
+
firedAt = 0;
|
|
3426
|
+
probeSupport();
|
|
3427
|
+
const current = {
|
|
3428
|
+
x,
|
|
3429
|
+
y,
|
|
3430
|
+
timer: 0,
|
|
3431
|
+
fired: false
|
|
3432
|
+
};
|
|
3433
|
+
current.timer = options.setTimer(() => fire(current), 500);
|
|
3434
|
+
hold = current;
|
|
3435
|
+
},
|
|
3436
|
+
move: (x, y) => {
|
|
3437
|
+
if (hold === void 0 || hold.fired) return;
|
|
3438
|
+
if (Math.abs(x - hold.x) > 14 || Math.abs(y - hold.y) > 14) stopTimer();
|
|
3439
|
+
},
|
|
3440
|
+
end: stopTimer,
|
|
3441
|
+
consumeClickSuppression: (detail) => {
|
|
3442
|
+
if (detail === 0 || suppressClickUntil === 0 || options.now() > suppressClickUntil) return false;
|
|
3443
|
+
suppressClickUntil = 0;
|
|
3444
|
+
return true;
|
|
3445
|
+
},
|
|
3446
|
+
blocksContextMenu: () => firedAt !== 0 && options.now() - firedAt <= 1200,
|
|
3447
|
+
dispose: () => {
|
|
3448
|
+
disposed = true;
|
|
3449
|
+
stopTimer();
|
|
3450
|
+
suppressClickUntil = 0;
|
|
3451
|
+
firedAt = 0;
|
|
3452
|
+
}
|
|
3453
|
+
};
|
|
3454
|
+
}
|
|
3153
3455
|
/** Add mobile semantics without replacing feature trees. */
|
|
3154
3456
|
function installNativeMobileSurface(backServices) {
|
|
3155
3457
|
document.documentElement.classList.add("dsh-native-mobile-active");
|
|
@@ -3200,6 +3502,59 @@ window.__ModuleLoader__.load({
|
|
|
3200
3502
|
};
|
|
3201
3503
|
document.addEventListener("pointerdown", onPointerDown, true);
|
|
3202
3504
|
document.addEventListener("keydown", onKeyDown, true);
|
|
3505
|
+
const switchComputerHold = createSwitchComputerHold({
|
|
3506
|
+
now: () => window.performance.now(),
|
|
3507
|
+
setTimer: (callback, delayMs) => window.setTimeout(callback, delayMs),
|
|
3508
|
+
clearTimer: (handle) => {
|
|
3509
|
+
window.clearTimeout(handle);
|
|
3510
|
+
},
|
|
3511
|
+
canSwitchComputer: async () => {
|
|
3512
|
+
const bridge = window.__DSH_MOBILE_NATIVE__;
|
|
3513
|
+
if (bridge === void 0) return false;
|
|
3514
|
+
try {
|
|
3515
|
+
return (await bridge.capabilities()).includes(SWITCH_COMPUTER_NATIVE_ACTION);
|
|
3516
|
+
} catch {
|
|
3517
|
+
return false;
|
|
3518
|
+
}
|
|
3519
|
+
},
|
|
3520
|
+
switchComputer: () => {
|
|
3521
|
+
const bridge = window.__DSH_MOBILE_NATIVE__;
|
|
3522
|
+
if (bridge === void 0) return;
|
|
3523
|
+
Promise.resolve().then(() => bridge.invoke(SWITCH_COMPUTER_NATIVE_ACTION, {})).catch(() => void 0);
|
|
3524
|
+
}
|
|
3525
|
+
});
|
|
3526
|
+
const isDrawerToggleTarget = (target) => target instanceof Element && target.closest("[data-dsh-mobile-toggle]") !== null;
|
|
3527
|
+
const onTogglePointerDown = (event) => {
|
|
3528
|
+
if (!event.isPrimary || event.button !== 0 || !isDrawerToggleTarget(event.target)) return;
|
|
3529
|
+
switchComputerHold.start(event.clientX, event.clientY);
|
|
3530
|
+
};
|
|
3531
|
+
const onTogglePointerMove = (event) => {
|
|
3532
|
+
switchComputerHold.move(event.clientX, event.clientY);
|
|
3533
|
+
};
|
|
3534
|
+
const onTogglePointerEnd = () => {
|
|
3535
|
+
switchComputerHold.end();
|
|
3536
|
+
};
|
|
3537
|
+
const onToggleVisibilityChange = () => {
|
|
3538
|
+
if (document.hidden) switchComputerHold.end();
|
|
3539
|
+
};
|
|
3540
|
+
const onToggleClickCapture = (event) => {
|
|
3541
|
+
if (!isDrawerToggleTarget(event.target) || !switchComputerHold.consumeClickSuppression(event.detail)) return;
|
|
3542
|
+
event.preventDefault();
|
|
3543
|
+
event.stopPropagation();
|
|
3544
|
+
};
|
|
3545
|
+
const onToggleContextMenu = (event) => {
|
|
3546
|
+
if (!isDrawerToggleTarget(event.target) || !switchComputerHold.blocksContextMenu()) return;
|
|
3547
|
+
event.preventDefault();
|
|
3548
|
+
event.stopPropagation();
|
|
3549
|
+
};
|
|
3550
|
+
document.addEventListener("pointerdown", onTogglePointerDown, true);
|
|
3551
|
+
document.addEventListener("pointermove", onTogglePointerMove, true);
|
|
3552
|
+
document.addEventListener("pointerup", onTogglePointerEnd, true);
|
|
3553
|
+
document.addEventListener("pointercancel", onTogglePointerEnd, true);
|
|
3554
|
+
document.addEventListener("click", onToggleClickCapture, true);
|
|
3555
|
+
document.addEventListener("contextmenu", onToggleContextMenu, true);
|
|
3556
|
+
window.addEventListener("blur", onTogglePointerEnd);
|
|
3557
|
+
document.addEventListener("visibilitychange", onToggleVisibilityChange);
|
|
3203
3558
|
const overlayQuery = window.matchMedia(NATIVE_MOBILE_OVERLAY_QUERY);
|
|
3204
3559
|
const backdrop = document.createElement("button");
|
|
3205
3560
|
backdrop.type = "button";
|
|
@@ -3258,6 +3613,12 @@ window.__ModuleLoader__.load({
|
|
|
3258
3613
|
return token;
|
|
3259
3614
|
});
|
|
3260
3615
|
};
|
|
3616
|
+
const disposeBrowserComposerSoftEnter = bindBrowserComposerSoftEnter(document, (editor, composerCard) => ({
|
|
3617
|
+
appBridge: nativeAppOwnsComposerEnter(window),
|
|
3618
|
+
editable: editor.isConnected && editor.getAttribute("contenteditable") === "true" && editor.getAttribute("aria-disabled") !== "true" && editor.getAttribute("aria-haspopup") !== "menu" && composerCard.getAttribute("aria-busy") !== "true" && editor.getAttribute("inputmode") !== "none",
|
|
3619
|
+
activeSession: activeSessionForSoftEnter(composerCard, currentSessionOrigin().sessionId),
|
|
3620
|
+
commandMenuOpen: composerCard.querySelector("[data-trigger-menu],button[aria-haspopup=\"listbox\"][aria-expanded=\"true\"]") !== null
|
|
3621
|
+
}));
|
|
3261
3622
|
const mediaRequestContext = () => {
|
|
3262
3623
|
const session = currentSessionOrigin();
|
|
3263
3624
|
return {
|
|
@@ -3701,6 +4062,8 @@ window.__ModuleLoader__.load({
|
|
|
3701
4062
|
composerTrailing.dataset.dshMobileComposerTrailing = "true";
|
|
3702
4063
|
const modelTrigger = composerTrailing.querySelector("button[aria-label^=\"选择模型\"],button[aria-label^=\"Select model\"],button[aria-label^=\"Seleziona modello\"]");
|
|
3703
4064
|
if (modelTrigger !== null) {
|
|
4065
|
+
const controls = modelTrigger.closest("[class*=\"_standardControls\"]");
|
|
4066
|
+
if (controls !== null) controls.dataset.dshMobileComposerControls = "true";
|
|
3704
4067
|
modelTrigger.dataset.dshMobileComposerModelTrigger = "true";
|
|
3705
4068
|
modelTrigger.parentElement?.setAttribute("data-dsh-mobile-composer-model", "true");
|
|
3706
4069
|
modelTrigger.querySelector("[class*=\"_triggerLabel\"]")?.setAttribute("data-dsh-mobile-composer-model-label", "true");
|
|
@@ -3724,9 +4087,8 @@ window.__ModuleLoader__.load({
|
|
|
3724
4087
|
sidebarRoot.dataset.dshMobileSidebarRoot = "true";
|
|
3725
4088
|
for (const brand of sidebarRoot.querySelectorAll("[class*=\"_fallbackBrandName\"]")) if (brand.textContent?.trim() === "DSH Local Build") brand.textContent = "DeepSeek Harness";
|
|
3726
4089
|
if (toggle !== void 0) toggle.dataset.dshMobileToggle = "true";
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
backdrop.hidden = !drawerScrimVisible(collapsed, overlayQuery.matches);
|
|
4090
|
+
if (frame !== void 0) sidebar.dataset.open = String(!classToken(sidebarRoot, "_collapsed"));
|
|
4091
|
+
backdrop.hidden = frame === void 0 || !drawerScrimVisible(classToken(sidebarRoot, "_collapsed"), overlayQuery.matches);
|
|
3730
4092
|
};
|
|
3731
4093
|
const schedule = () => {
|
|
3732
4094
|
if (scheduled === 0) scheduled = requestAnimationFrame(sync);
|
|
@@ -3797,6 +4159,7 @@ window.__ModuleLoader__.load({
|
|
|
3797
4159
|
restoreLanguageMarker();
|
|
3798
4160
|
for (const cleanup of [...browserPickerCleanups]) cleanup();
|
|
3799
4161
|
disposeEnterBinding?.();
|
|
4162
|
+
disposeBrowserComposerSoftEnter();
|
|
3800
4163
|
observer.disconnect();
|
|
3801
4164
|
overlayQuery.removeEventListener("change", schedule);
|
|
3802
4165
|
document.removeEventListener("click", onBranchClick, true);
|
|
@@ -3823,6 +4186,15 @@ window.__ModuleLoader__.load({
|
|
|
3823
4186
|
historyScroller?.removeEventListener("scroll", onHistoryScroll);
|
|
3824
4187
|
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
3825
4188
|
document.removeEventListener("keydown", onKeyDown, true);
|
|
4189
|
+
document.removeEventListener("pointerdown", onTogglePointerDown, true);
|
|
4190
|
+
document.removeEventListener("pointermove", onTogglePointerMove, true);
|
|
4191
|
+
document.removeEventListener("pointerup", onTogglePointerEnd, true);
|
|
4192
|
+
document.removeEventListener("pointercancel", onTogglePointerEnd, true);
|
|
4193
|
+
document.removeEventListener("click", onToggleClickCapture, true);
|
|
4194
|
+
document.removeEventListener("contextmenu", onToggleContextMenu, true);
|
|
4195
|
+
window.removeEventListener("blur", onTogglePointerEnd);
|
|
4196
|
+
document.removeEventListener("visibilitychange", onToggleVisibilityChange);
|
|
4197
|
+
switchComputerHold.dispose();
|
|
3826
4198
|
document.removeEventListener("click", animateNavigation);
|
|
3827
4199
|
backdrop.remove();
|
|
3828
4200
|
document.documentElement.classList.remove("dsh-native-mobile-active");
|
|
@@ -3931,6 +4303,132 @@ window.__ModuleLoader__.load({
|
|
|
3931
4303
|
};
|
|
3932
4304
|
}
|
|
3933
4305
|
//#endregion
|
|
4306
|
+
//#region src/mobile-font.ts
|
|
4307
|
+
/** Device-local typography for authenticated mobile pages, without Host settings writes. */
|
|
4308
|
+
const MOBILE_FONT_STORAGE_KEY = "dsh-mobile.content-font-size.v1";
|
|
4309
|
+
/** Read one local storage value; malformed or unavailable preferences use the mobile default. */
|
|
4310
|
+
function parseMobileFontSize(value) {
|
|
4311
|
+
if (value === null || !/^\d+$/.test(value)) return 16;
|
|
4312
|
+
const size = Number(value);
|
|
4313
|
+
return Number.isInteger(size) && size >= 12 && size <= 32 ? size : 16;
|
|
4314
|
+
}
|
|
4315
|
+
/** Local preference storage; unavailable browser storage still permits in-page adjustment. */
|
|
4316
|
+
var MobileFontPreference = class {
|
|
4317
|
+
storage;
|
|
4318
|
+
size = 16;
|
|
4319
|
+
listeners = /* @__PURE__ */ new Set();
|
|
4320
|
+
constructor(storage) {
|
|
4321
|
+
this.storage = storage;
|
|
4322
|
+
try {
|
|
4323
|
+
this.size = parseMobileFontSize(storage?.getItem("dsh-mobile.content-font-size.v1") ?? null);
|
|
4324
|
+
} catch (error) {}
|
|
4325
|
+
}
|
|
4326
|
+
getSnapshot = () => this.size;
|
|
4327
|
+
subscribe = (listener) => {
|
|
4328
|
+
this.listeners.add(listener);
|
|
4329
|
+
return () => {
|
|
4330
|
+
this.listeners.delete(listener);
|
|
4331
|
+
};
|
|
4332
|
+
};
|
|
4333
|
+
/** Apply a user-selected size locally; never send it to DSH's settings RPC. */
|
|
4334
|
+
set(size) {
|
|
4335
|
+
if (!Number.isInteger(size) || size < 12 || size > 32) throw new RangeError("Mobile font size must be an integer from 12 to 32");
|
|
4336
|
+
if (this.size === size) return;
|
|
4337
|
+
this.size = size;
|
|
4338
|
+
try {
|
|
4339
|
+
this.storage?.setItem(MOBILE_FONT_STORAGE_KEY, String(size));
|
|
4340
|
+
} catch (error) {}
|
|
4341
|
+
for (const listener of this.listeners) listener();
|
|
4342
|
+
}
|
|
4343
|
+
};
|
|
4344
|
+
/** Keep mobile typography independent from Host theme changes, including body-level portals. */
|
|
4345
|
+
function installMobileFontPreference() {
|
|
4346
|
+
let storage = null;
|
|
4347
|
+
try {
|
|
4348
|
+
storage = window.localStorage;
|
|
4349
|
+
} catch (error) {}
|
|
4350
|
+
const preference = new MobileFontPreference(storage);
|
|
4351
|
+
const property = "--dsh-content-font-size";
|
|
4352
|
+
let previous = document.body.style.getPropertyValue(property);
|
|
4353
|
+
let previousPriority = document.body.style.getPropertyPriority(property);
|
|
4354
|
+
let ownedValue;
|
|
4355
|
+
const apply = () => {
|
|
4356
|
+
const current = document.body.style.getPropertyValue(property);
|
|
4357
|
+
if (ownedValue !== void 0 && current !== ownedValue) {
|
|
4358
|
+
previous = current;
|
|
4359
|
+
previousPriority = document.body.style.getPropertyPriority(property);
|
|
4360
|
+
}
|
|
4361
|
+
const value = `${preference.getSnapshot()}px`;
|
|
4362
|
+
ownedValue = value;
|
|
4363
|
+
if (current !== value) document.body.style.setProperty(property, value);
|
|
4364
|
+
};
|
|
4365
|
+
apply();
|
|
4366
|
+
const unsubscribe = preference.subscribe(apply);
|
|
4367
|
+
const observer = new MutationObserver(apply);
|
|
4368
|
+
observer.observe(document.body, {
|
|
4369
|
+
attributes: true,
|
|
4370
|
+
attributeFilter: ["style"]
|
|
4371
|
+
});
|
|
4372
|
+
return {
|
|
4373
|
+
preference,
|
|
4374
|
+
dispose: () => {
|
|
4375
|
+
observer.disconnect();
|
|
4376
|
+
unsubscribe();
|
|
4377
|
+
if (previous === "") document.body.style.removeProperty(property);
|
|
4378
|
+
else document.body.style.setProperty(property, previous, previousPriority);
|
|
4379
|
+
}
|
|
4380
|
+
};
|
|
4381
|
+
}
|
|
4382
|
+
const messages = {
|
|
4383
|
+
zh: {
|
|
4384
|
+
title: "移动端字号",
|
|
4385
|
+
description: "仅保存在当前访问地址的本地数据中,不影响电脑或其他设备。",
|
|
4386
|
+
decrease: "减小移动端字号",
|
|
4387
|
+
increase: "增大移动端字号"
|
|
4388
|
+
},
|
|
4389
|
+
en: {
|
|
4390
|
+
title: "Mobile font size",
|
|
4391
|
+
description: "Saved locally for this address, without changing your computer or other devices.",
|
|
4392
|
+
decrease: "Decrease mobile font size",
|
|
4393
|
+
increase: "Increase mobile font size"
|
|
4394
|
+
},
|
|
4395
|
+
it: {
|
|
4396
|
+
title: "Dimensione testo mobile",
|
|
4397
|
+
description: "Salvata localmente per questo indirizzo, senza modificare il computer o altri dispositivi.",
|
|
4398
|
+
decrease: "Riduci il testo mobile",
|
|
4399
|
+
increase: "Aumenta il testo mobile"
|
|
4400
|
+
}
|
|
4401
|
+
};
|
|
4402
|
+
/** General settings row matching the existing Mobile controls and DSH theme tokens. */
|
|
4403
|
+
function MobileFontSizeRow({ preference, locale }) {
|
|
4404
|
+
const size = (0, react.useSyncExternalStore)(preference.subscribe, preference.getSnapshot);
|
|
4405
|
+
const copy = messages[locale];
|
|
4406
|
+
return (0, react.createElement)("div", {
|
|
4407
|
+
className: "dsh-mobile-settings_row",
|
|
4408
|
+
lang: locale,
|
|
4409
|
+
"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-settings_fontControls" }, (0, react.createElement)("button", {
|
|
4411
|
+
type: "button",
|
|
4412
|
+
className: "dsh-mobile-settings_selector",
|
|
4413
|
+
"aria-label": copy.decrease,
|
|
4414
|
+
disabled: size === 12,
|
|
4415
|
+
onClick: () => {
|
|
4416
|
+
preference.set(size - 1);
|
|
4417
|
+
}
|
|
4418
|
+
}, "−"), (0, react.createElement)("output", {
|
|
4419
|
+
"aria-live": "polite",
|
|
4420
|
+
"aria-label": copy.title
|
|
4421
|
+
}, `${size}px`), (0, react.createElement)("button", {
|
|
4422
|
+
type: "button",
|
|
4423
|
+
className: "dsh-mobile-settings_selector",
|
|
4424
|
+
"aria-label": copy.increase,
|
|
4425
|
+
disabled: size === 32,
|
|
4426
|
+
onClick: () => {
|
|
4427
|
+
preference.set(size + 1);
|
|
4428
|
+
}
|
|
4429
|
+
}, "+")));
|
|
4430
|
+
}
|
|
4431
|
+
//#endregion
|
|
3934
4432
|
//#region src/local-admin-host.ts
|
|
3935
4433
|
/** Browser-safe desktop-admin Host checks. Do not import Node APIs here. */
|
|
3936
4434
|
const DESKTOP_ADMIN_HEADER = "x-dsh-mobile-desktop-admin";
|
|
@@ -3991,6 +4489,295 @@ window.__ModuleLoader__.load({
|
|
|
3991
4489
|
return (protocol === "dsh-app:" ? hostname === "app" : (protocol === "" || protocol === "http:" || protocol === "https:") && isLocalAdminHostname(hostname)) && frontend !== "dedicated" && !new URLSearchParams(query).has("dsh-mobile-preview");
|
|
3992
4490
|
}
|
|
3993
4491
|
//#endregion
|
|
4492
|
+
//#region src/client-module-ui.ts
|
|
4493
|
+
/** Device-local module selection and the computer's defaults share a read-only catalog. */
|
|
4494
|
+
const CLIENT_MODULE_COPY = {
|
|
4495
|
+
zh: {
|
|
4496
|
+
title: "移动页面模块",
|
|
4497
|
+
computer: "为移动访问设置默认加载模块,不卸载电脑上的插件。",
|
|
4498
|
+
device: "只为本设备选择加载模块,不改变其他手机或电脑。",
|
|
4499
|
+
open: "管理",
|
|
4500
|
+
loading: "正在读取模块…",
|
|
4501
|
+
hint: "勾选表示加载。启动必需的模块不能取消;依赖冲突会在保存时说明。",
|
|
4502
|
+
save: "保存,下次打开生效",
|
|
4503
|
+
reset: "恢复默认",
|
|
4504
|
+
close: "关闭",
|
|
4505
|
+
required: "启动必需",
|
|
4506
|
+
dependencies: "依赖:",
|
|
4507
|
+
saved: "已保存,不会自动刷新当前页面。重新打开 DSH 后生效。",
|
|
4508
|
+
failed: "暂时无法完成。请确认移动访问已启用,或检查下面的依赖冲突。",
|
|
4509
|
+
conflict: "无法保存:",
|
|
4510
|
+
computerSource: "使用电脑默认设置",
|
|
4511
|
+
deviceSource: "使用本设备设置",
|
|
4512
|
+
resetConfirm: "恢复默认模块选择?不会重新加载当前会话。"
|
|
4513
|
+
},
|
|
4514
|
+
en: {
|
|
4515
|
+
title: "Mobile page modules",
|
|
4516
|
+
computer: "Choose default modules for mobile access without uninstalling computer plugins.",
|
|
4517
|
+
device: "Choose modules for this device only; other phones and computers are unchanged.",
|
|
4518
|
+
open: "Manage",
|
|
4519
|
+
loading: "Reading modules…",
|
|
4520
|
+
hint: "Checked means loaded. Required boot modules cannot be unchecked; saving explains dependency conflicts.",
|
|
4521
|
+
save: "Save for next open",
|
|
4522
|
+
reset: "Restore defaults",
|
|
4523
|
+
close: "Close",
|
|
4524
|
+
required: "Required for boot",
|
|
4525
|
+
dependencies: "Depends on: ",
|
|
4526
|
+
saved: "Saved. This page is not reloaded automatically. Reopen DSH to apply.",
|
|
4527
|
+
failed: "Could not complete this operation. Enable mobile access or check the dependency conflict below.",
|
|
4528
|
+
conflict: "Could not save: ",
|
|
4529
|
+
computerSource: "Using computer defaults",
|
|
4530
|
+
deviceSource: "Using this device’s settings",
|
|
4531
|
+
resetConfirm: "Restore default module selection? The current conversation will not reload."
|
|
4532
|
+
},
|
|
4533
|
+
it: {
|
|
4534
|
+
title: "Moduli della pagina mobile",
|
|
4535
|
+
computer: "Scegli i moduli predefiniti senza disinstallare i plugin del computer.",
|
|
4536
|
+
device: "Scegli i moduli solo per questo dispositivo, senza cambiare gli altri.",
|
|
4537
|
+
open: "Gestisci",
|
|
4538
|
+
loading: "Lettura dei moduli…",
|
|
4539
|
+
hint: "Selezionato significa caricato. I moduli obbligatori non possono essere rimossi; il salvataggio mostra i conflitti.",
|
|
4540
|
+
save: "Salva per la prossima apertura",
|
|
4541
|
+
reset: "Ripristina predefiniti",
|
|
4542
|
+
close: "Chiudi",
|
|
4543
|
+
required: "Obbligatorio",
|
|
4544
|
+
dependencies: "Dipende da: ",
|
|
4545
|
+
saved: "Salvato. La pagina non viene ricaricata automaticamente. Riapri DSH per applicare.",
|
|
4546
|
+
failed: "Operazione non completata. Abilita accesso mobile o controlla il conflitto sotto.",
|
|
4547
|
+
conflict: "Impossibile salvare: ",
|
|
4548
|
+
computerSource: "Predefiniti del computer",
|
|
4549
|
+
deviceSource: "Impostazioni del dispositivo",
|
|
4550
|
+
resetConfirm: "Ripristinare i moduli predefiniti? La conversazione attuale non verrà ricaricata."
|
|
4551
|
+
}
|
|
4552
|
+
};
|
|
4553
|
+
var ClientModuleUiConflict = class extends Error {
|
|
4554
|
+
detail;
|
|
4555
|
+
constructor(detail) {
|
|
4556
|
+
super("client_module_conflict");
|
|
4557
|
+
this.detail = detail;
|
|
4558
|
+
}
|
|
4559
|
+
};
|
|
4560
|
+
/** Validate catalog response fields before creating interactive controls.
|
|
4561
|
+
* @param value - Gateway or computer-admin JSON response.
|
|
4562
|
+
* @returns The fields used by the module-selection dialog.
|
|
4563
|
+
*/
|
|
4564
|
+
function parseClientModuleView(value) {
|
|
4565
|
+
if (typeof value !== "object" || value === null || !("entries" in value) || !Array.isArray(value.entries) || !("excludedClientModules" in value) || !Array.isArray(value.excludedClientModules) || !("source" in value) || ![
|
|
4566
|
+
"device",
|
|
4567
|
+
"computer",
|
|
4568
|
+
"plugin"
|
|
4569
|
+
].includes(String(value.source))) throw new Error("invalid_module_catalog");
|
|
4570
|
+
return {
|
|
4571
|
+
entries: value.entries.map((entry) => {
|
|
4572
|
+
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");
|
|
4573
|
+
const dependencies = entry.dependencies.map((dependency) => {
|
|
4574
|
+
if (typeof dependency !== "string") throw new Error("invalid_module_catalog");
|
|
4575
|
+
return dependency;
|
|
4576
|
+
});
|
|
4577
|
+
return {
|
|
4578
|
+
id: entry.id,
|
|
4579
|
+
required: entry.required,
|
|
4580
|
+
dependencies
|
|
4581
|
+
};
|
|
4582
|
+
}),
|
|
4583
|
+
excludedClientModules: value.excludedClientModules.map((id) => {
|
|
4584
|
+
if (typeof id !== "string") throw new Error("invalid_module_catalog");
|
|
4585
|
+
return id;
|
|
4586
|
+
}),
|
|
4587
|
+
source: value.source === "device" ? "device" : value.source === "computer" ? "computer" : "plugin"
|
|
4588
|
+
};
|
|
4589
|
+
}
|
|
4590
|
+
const CLIENT_MODULE_STYLES = `
|
|
4591
|
+
.dsh-module-row{display:flex;align-items:center;gap:8px;padding:16px 0;border-bottom:.5px solid var(--dsw-alias-border-l2)}
|
|
4592
|
+
.dsh-module-rowText{flex:1;min-width:0}.dsh-module-title{font-size:14px;line-height:22px;color:var(--dsw-alias-label-primary)}
|
|
4593
|
+
.dsh-module-description{margin-top:4px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}
|
|
4594
|
+
.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}
|
|
4595
|
+
.dsh-module-button:focus-visible{outline:2px solid var(--dsw-alias-label-primary-bluish,#2563eb);outline-offset:2px}
|
|
4596
|
+
.dsh-module-dialog{box-sizing:border-box;width:min(640px,calc(100vw - 24px));max-height:calc(100dvh - 32px);padding:20px;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:auto}
|
|
4597
|
+
.dsh-module-dialog::backdrop{background:rgb(15 23 42 / 40%)}.dsh-module-dialog h2{font-size:20px;margin:0 0 12px}
|
|
4598
|
+
.dsh-module-dialog p{overflow-wrap:anywhere}.dsh-module-list{display:flex;flex-direction:column;gap:4px;margin:12px 0}
|
|
4599
|
+
.dsh-module-choice{display:flex;align-items:flex-start;gap:12px;min-height:48px;padding:8px 0;border-bottom:1px solid var(--dsw-alias-border-l2)}
|
|
4600
|
+
.dsh-module-choice input{width:24px;height:24px;flex:none;margin:2px 0}.dsh-module-choice span{min-width:0;overflow-wrap:anywhere}
|
|
4601
|
+
.dsh-module-choice small{display:block;font-size:12px;color:var(--dsw-alias-label-tertiary);line-height:18px}.dsh-module-actions{display:flex;flex-wrap:wrap;gap:8px}
|
|
4602
|
+
.dsh-module-error{color:var(--dsw-alias-state-error-primary,#b42318)}
|
|
4603
|
+
`;
|
|
4604
|
+
const openDialogs = /* @__PURE__ */ new Set();
|
|
4605
|
+
/** Cancel pending dialogs when the owning plugin generation is disposed. */
|
|
4606
|
+
function disposeClientModuleDialogs() {
|
|
4607
|
+
for (const dispose of [...openDialogs]) dispose();
|
|
4608
|
+
}
|
|
4609
|
+
/** Close the foreground module modal before navigating its underlying DSH settings.
|
|
4610
|
+
* @returns Whether an open module modal consumed the return action.
|
|
4611
|
+
*/
|
|
4612
|
+
function closeClientModuleDialog() {
|
|
4613
|
+
const dispose = [...openDialogs].at(-1);
|
|
4614
|
+
if (dispose === void 0) return false;
|
|
4615
|
+
dispose();
|
|
4616
|
+
return true;
|
|
4617
|
+
}
|
|
4618
|
+
/** Open one modal; closing cancels reads and restores focus without reloading the page.
|
|
4619
|
+
* @param scope - Computer defaults or the authenticated device's own override.
|
|
4620
|
+
* @param locale - Current DSH display language.
|
|
4621
|
+
* @param trigger - Control to focus again when the modal closes.
|
|
4622
|
+
*/
|
|
4623
|
+
function openClientModuleSettings(scope, locale, trigger) {
|
|
4624
|
+
const copy = CLIENT_MODULE_COPY[locale];
|
|
4625
|
+
const endpoint = scope === "computer" ? "/api/mobile-access/client-modules" : "/mobile-access/client-modules";
|
|
4626
|
+
const controller = new AbortController();
|
|
4627
|
+
const dialog = document.createElement("dialog");
|
|
4628
|
+
dialog.className = "dsh-module-dialog";
|
|
4629
|
+
dialog.setAttribute("aria-label", copy.title);
|
|
4630
|
+
const title = document.createElement("h2");
|
|
4631
|
+
title.textContent = copy.title;
|
|
4632
|
+
const intro = document.createElement("p");
|
|
4633
|
+
intro.textContent = `${copy[scope]} ${copy.hint}`;
|
|
4634
|
+
const status = document.createElement("p");
|
|
4635
|
+
status.setAttribute("role", "status");
|
|
4636
|
+
status.setAttribute("aria-live", "polite");
|
|
4637
|
+
status.tabIndex = -1;
|
|
4638
|
+
const list = document.createElement("div");
|
|
4639
|
+
list.className = "dsh-module-list";
|
|
4640
|
+
const actions = document.createElement("div");
|
|
4641
|
+
actions.className = "dsh-module-actions";
|
|
4642
|
+
const button = (text) => {
|
|
4643
|
+
const result = document.createElement("button");
|
|
4644
|
+
result.type = "button";
|
|
4645
|
+
result.className = "dsh-module-button";
|
|
4646
|
+
result.textContent = text;
|
|
4647
|
+
return result;
|
|
4648
|
+
};
|
|
4649
|
+
const save = button(copy.save);
|
|
4650
|
+
const reset = button(copy.reset);
|
|
4651
|
+
const close = button(copy.close);
|
|
4652
|
+
actions.append(save, reset, close);
|
|
4653
|
+
dialog.append(title, intro, status, list, actions);
|
|
4654
|
+
let disposed = false;
|
|
4655
|
+
let busy = false;
|
|
4656
|
+
let loaded = false;
|
|
4657
|
+
const choices = /* @__PURE__ */ new Map();
|
|
4658
|
+
const setBusy = (value) => {
|
|
4659
|
+
busy = value;
|
|
4660
|
+
save.disabled = value || !loaded;
|
|
4661
|
+
reset.disabled = value || !loaded;
|
|
4662
|
+
for (const input of choices.values()) if (!input.dataset.required) input.disabled = value;
|
|
4663
|
+
};
|
|
4664
|
+
const request = async (body) => {
|
|
4665
|
+
const init = {
|
|
4666
|
+
method: body === void 0 ? "GET" : "POST",
|
|
4667
|
+
signal: controller.signal,
|
|
4668
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
4669
|
+
};
|
|
4670
|
+
init.headers = scope === "computer" ? localAdminRequestHeaders(init, location) : { "content-type": "application/json" };
|
|
4671
|
+
if (scope === "device" && body !== void 0) {
|
|
4672
|
+
const csrfCookie = document.cookie.split(";").map((value) => value.trim()).find((value) => value.startsWith("__Host-dsh_ma_csrf=")) ?? document.cookie.split(";").map((value) => value.trim()).find((value) => value.startsWith("dsh_ma_csrf="));
|
|
4673
|
+
if (csrfCookie !== void 0) {
|
|
4674
|
+
const headers = new Headers(init.headers);
|
|
4675
|
+
headers.set("x-dsh-mobile-csrf", csrfCookie.slice(csrfCookie.indexOf("=") + 1));
|
|
4676
|
+
init.headers = headers;
|
|
4677
|
+
}
|
|
4678
|
+
}
|
|
4679
|
+
const response = await fetch(endpoint, init);
|
|
4680
|
+
const value = await response.json();
|
|
4681
|
+
if (!response.ok) {
|
|
4682
|
+
const detail = typeof value === "object" && value !== null && "detail" in value && typeof value.detail === "string" ? value.detail.slice(0, 512) : "";
|
|
4683
|
+
if (response.status === 409 && detail !== "" && typeof value === "object" && value !== null && "error" in value && value.error === "excluded_client_modules_invalid") throw new ClientModuleUiConflict(detail);
|
|
4684
|
+
throw new Error("client_module_request_failed");
|
|
4685
|
+
}
|
|
4686
|
+
return parseClientModuleView(value);
|
|
4687
|
+
};
|
|
4688
|
+
const render = (view) => {
|
|
4689
|
+
if (disposed) return;
|
|
4690
|
+
loaded = true;
|
|
4691
|
+
choices.clear();
|
|
4692
|
+
list.replaceChildren();
|
|
4693
|
+
for (const entry of view.entries) {
|
|
4694
|
+
const label = document.createElement("label");
|
|
4695
|
+
label.className = "dsh-module-choice";
|
|
4696
|
+
const input = document.createElement("input");
|
|
4697
|
+
input.type = "checkbox";
|
|
4698
|
+
input.checked = !view.excludedClientModules.includes(entry.id);
|
|
4699
|
+
input.disabled = entry.required;
|
|
4700
|
+
if (entry.required) input.dataset.required = "true";
|
|
4701
|
+
const name = document.createElement("span");
|
|
4702
|
+
name.textContent = entry.id;
|
|
4703
|
+
const hint = document.createElement("small");
|
|
4704
|
+
hint.textContent = entry.required ? copy.required : entry.dependencies.length === 0 ? "" : `${copy.dependencies}${entry.dependencies.join(", ")}`;
|
|
4705
|
+
name.append(hint);
|
|
4706
|
+
label.append(input, name);
|
|
4707
|
+
list.append(label);
|
|
4708
|
+
choices.set(entry.id, input);
|
|
4709
|
+
}
|
|
4710
|
+
status.classList.remove("dsh-module-error");
|
|
4711
|
+
status.textContent = view.source === "device" ? copy.deviceSource : copy.computerSource;
|
|
4712
|
+
};
|
|
4713
|
+
const failed = (error) => {
|
|
4714
|
+
if (disposed || controller.signal.aborted) return;
|
|
4715
|
+
status.classList.add("dsh-module-error");
|
|
4716
|
+
status.textContent = error instanceof ClientModuleUiConflict ? `${copy.conflict}${error.detail}` : copy.failed;
|
|
4717
|
+
status.focus({ preventScroll: true });
|
|
4718
|
+
};
|
|
4719
|
+
const mutate = (body) => {
|
|
4720
|
+
if (busy || !loaded) return;
|
|
4721
|
+
setBusy(true);
|
|
4722
|
+
request(body).then((view) => {
|
|
4723
|
+
render(view);
|
|
4724
|
+
if (!disposed) status.textContent = copy.saved;
|
|
4725
|
+
}, failed).finally(() => {
|
|
4726
|
+
if (!disposed) setBusy(false);
|
|
4727
|
+
});
|
|
4728
|
+
};
|
|
4729
|
+
save.addEventListener("click", () => {
|
|
4730
|
+
mutate({ excludedClientModules: [...choices].filter(([, input]) => !input.checked).map(([id]) => id) });
|
|
4731
|
+
});
|
|
4732
|
+
reset.addEventListener("click", () => {
|
|
4733
|
+
if (window.confirm(copy.resetConfirm)) mutate({ reset: true });
|
|
4734
|
+
});
|
|
4735
|
+
const onBack = (event) => {
|
|
4736
|
+
if (!event.cancelable || !dialog.open || event.defaultPrevented) return;
|
|
4737
|
+
event.preventDefault();
|
|
4738
|
+
event.stopImmediatePropagation();
|
|
4739
|
+
dispose();
|
|
4740
|
+
};
|
|
4741
|
+
window.addEventListener("dsh-mobile:native-back", onBack, true);
|
|
4742
|
+
const dispose = () => {
|
|
4743
|
+
if (disposed) return;
|
|
4744
|
+
disposed = true;
|
|
4745
|
+
controller.abort();
|
|
4746
|
+
openDialogs.delete(dispose);
|
|
4747
|
+
window.removeEventListener("dsh-mobile:native-back", onBack, true);
|
|
4748
|
+
if (dialog.open) dialog.close();
|
|
4749
|
+
dialog.remove();
|
|
4750
|
+
if (trigger?.isConnected) trigger.focus({ preventScroll: true });
|
|
4751
|
+
};
|
|
4752
|
+
openDialogs.add(dispose);
|
|
4753
|
+
dialog.addEventListener("close", dispose, { once: true });
|
|
4754
|
+
close.addEventListener("click", dispose);
|
|
4755
|
+
document.body.append(dialog);
|
|
4756
|
+
dialog.showModal();
|
|
4757
|
+
setBusy(true);
|
|
4758
|
+
status.textContent = copy.loading;
|
|
4759
|
+
request().then(render, failed).finally(() => {
|
|
4760
|
+
if (!disposed) setBusy(false);
|
|
4761
|
+
});
|
|
4762
|
+
}
|
|
4763
|
+
/** Settings row used on mobile pages and on the computer's General settings.
|
|
4764
|
+
* @param props - Selection scope and current locale.
|
|
4765
|
+
* @returns A native-style General settings row.
|
|
4766
|
+
*/
|
|
4767
|
+
function ClientModuleSettingsRow(props) {
|
|
4768
|
+
const copy = CLIENT_MODULE_COPY[props.locale];
|
|
4769
|
+
return (0, react.createElement)("div", {
|
|
4770
|
+
className: "dsh-module-row",
|
|
4771
|
+
lang: props.locale
|
|
4772
|
+
}, (0, react.createElement)("div", { className: "dsh-module-rowText" }, (0, react.createElement)("div", { className: "dsh-module-title" }, copy.title), (0, react.createElement)("div", { className: "dsh-module-description" }, copy[props.scope])), (0, react.createElement)("button", {
|
|
4773
|
+
type: "button",
|
|
4774
|
+
className: "dsh-module-button",
|
|
4775
|
+
onClick: (event) => {
|
|
4776
|
+
openClientModuleSettings(props.scope, props.locale, event.currentTarget);
|
|
4777
|
+
}
|
|
4778
|
+
}, copy.open));
|
|
4779
|
+
}
|
|
4780
|
+
//#endregion
|
|
3994
4781
|
//#region src/client.ts
|
|
3995
4782
|
/** Map a host-reported provider onto the UI union, defaulting to the built-in one. */
|
|
3996
4783
|
function parseMobileRemoteProvider(value) {
|
|
@@ -4025,6 +4812,10 @@ window.__ModuleLoader__.load({
|
|
|
4025
4812
|
event.preventDefault();
|
|
4026
4813
|
event.stopImmediatePropagation();
|
|
4027
4814
|
};
|
|
4815
|
+
if (closeClientModuleDialog()) {
|
|
4816
|
+
claim();
|
|
4817
|
+
return;
|
|
4818
|
+
}
|
|
4028
4819
|
const surface = [...page.querySelectorAll("[data-dsh-mobile-surface-placement=\"page\"], [data-dsh-mobile-surface-placement=\"overlay\"]")].filter((element) => visibleBackLayer(view, element)).at(-1);
|
|
4029
4820
|
if (surface !== void 0) {
|
|
4030
4821
|
setMobileSurfaceOpen(surface, false);
|
|
@@ -4756,6 +5547,83 @@ window.__ModuleLoader__.load({
|
|
|
4756
5547
|
const lanSetupStatus = element("p", "dsh-mobile-control__status");
|
|
4757
5548
|
lanSetupStatus.setAttribute("role", "status");
|
|
4758
5549
|
lanSetupStatus.setAttribute("aria-live", "polite");
|
|
5550
|
+
const trustedNetworks = element("details", "dsh-mobile-control__details");
|
|
5551
|
+
const trustedNetworksSummary = element("summary");
|
|
5552
|
+
trustedNetworksSummary.textContent = t("trustedNetworksTitle");
|
|
5553
|
+
const trustedNetworksHelp = element("p", "dsh-mobile-control__component-note");
|
|
5554
|
+
trustedNetworksHelp.textContent = t("trustedNetworksHelp");
|
|
5555
|
+
const trustedNetworksLabel = element("label", "dsh-mobile-control__field");
|
|
5556
|
+
trustedNetworksLabel.textContent = t("trustedNetworksLabel");
|
|
5557
|
+
const trustedNetworksInput = element("textarea");
|
|
5558
|
+
trustedNetworksInput.rows = 2;
|
|
5559
|
+
trustedNetworksInput.placeholder = "11.24.0.0/24\n2001:db8::/32";
|
|
5560
|
+
trustedNetworksInput.autocomplete = "off";
|
|
5561
|
+
trustedNetworksInput.spellcheck = false;
|
|
5562
|
+
const trustedNetworksSave = element("button", "dsh-mobile-control__secondary");
|
|
5563
|
+
trustedNetworksSave.type = "button";
|
|
5564
|
+
trustedNetworksSave.textContent = t("trustedNetworksSave");
|
|
5565
|
+
const trustedNetworksStatus = element("p", "dsh-mobile-control__status");
|
|
5566
|
+
trustedNetworksStatus.setAttribute("role", "status");
|
|
5567
|
+
trustedNetworksStatus.setAttribute("aria-live", "polite");
|
|
5568
|
+
trustedNetworksLabel.append(trustedNetworksInput);
|
|
5569
|
+
trustedNetworks.append(trustedNetworksSummary, trustedNetworksHelp, trustedNetworksLabel, trustedNetworksSave, trustedNetworksStatus);
|
|
5570
|
+
let trustedNetworksLoaded = false;
|
|
5571
|
+
let trustedNetworksLoading = false;
|
|
5572
|
+
let trustedNetworksSupported = false;
|
|
5573
|
+
let trustedNetworksDirty = false;
|
|
5574
|
+
let trustedNetworksBusy = false;
|
|
5575
|
+
const syncTrustedNetworksControls = () => {
|
|
5576
|
+
const enabled = trustedNetworksLoaded && trustedNetworksSupported && !trustedNetworksLoading && !trustedNetworksBusy;
|
|
5577
|
+
trustedNetworksInput.disabled = !enabled;
|
|
5578
|
+
trustedNetworksSave.disabled = !enabled;
|
|
5579
|
+
trustedNetworks.setAttribute("aria-busy", String(trustedNetworksLoading || trustedNetworksBusy));
|
|
5580
|
+
};
|
|
5581
|
+
syncTrustedNetworksControls();
|
|
5582
|
+
const renderTrustedNetworks = (data) => {
|
|
5583
|
+
trustedNetworksLoaded = true;
|
|
5584
|
+
trustedNetworksSupported = data.supported === true;
|
|
5585
|
+
syncTrustedNetworksControls();
|
|
5586
|
+
if (!trustedNetworksDirty) trustedNetworksInput.value = Array.isArray(data.extraAllowedCidrs) ? data.extraAllowedCidrs.filter((value) => typeof value === "string").join("\n") : "";
|
|
5587
|
+
trustedNetworksStatus.textContent = !trustedNetworksSupported ? t("trustedNetworksUnavailable") : data.restartRequired === true ? t("trustedNetworksRestart") : "";
|
|
5588
|
+
trustedNetworksHelp.textContent = `${t("trustedNetworksHelp")}${data.windowsFirewall === true ? ` ${t("trustedNetworksWindows")}` : ""}`;
|
|
5589
|
+
};
|
|
5590
|
+
trustedNetworks.addEventListener("toggle", () => {
|
|
5591
|
+
if (!trustedNetworks.open || trustedNetworksLoaded || trustedNetworksLoading) return;
|
|
5592
|
+
trustedNetworksLoading = true;
|
|
5593
|
+
syncTrustedNetworksControls();
|
|
5594
|
+
trustedNetworksStatus.textContent = t("loadingStatus");
|
|
5595
|
+
controlRequestJson("/api/mobile-access/lan/trusted-networks").then(renderTrustedNetworks, () => {
|
|
5596
|
+
trustedNetworksLoaded = false;
|
|
5597
|
+
trustedNetworksSupported = false;
|
|
5598
|
+
trustedNetworksStatus.textContent = t("trustedNetworksFailed");
|
|
5599
|
+
}).finally(() => {
|
|
5600
|
+
trustedNetworksLoading = false;
|
|
5601
|
+
syncTrustedNetworksControls();
|
|
5602
|
+
});
|
|
5603
|
+
});
|
|
5604
|
+
trustedNetworksInput.addEventListener("input", () => {
|
|
5605
|
+
trustedNetworksDirty = true;
|
|
5606
|
+
trustedNetworksInput.removeAttribute("aria-invalid");
|
|
5607
|
+
});
|
|
5608
|
+
trustedNetworksSave.addEventListener("click", () => {
|
|
5609
|
+
if (trustedNetworksBusy || !trustedNetworksLoaded || !trustedNetworksSupported) return;
|
|
5610
|
+
trustedNetworksBusy = true;
|
|
5611
|
+
syncTrustedNetworksControls();
|
|
5612
|
+
const extraAllowedCidrs = trustedNetworksInput.value.trim().split(/[\s,]+/u).filter(Boolean);
|
|
5613
|
+
controlRequestJson("/api/mobile-access/lan/trusted-networks", {
|
|
5614
|
+
method: "POST",
|
|
5615
|
+
body: JSON.stringify({ extraAllowedCidrs })
|
|
5616
|
+
}).then((data) => {
|
|
5617
|
+
trustedNetworksDirty = false;
|
|
5618
|
+
renderTrustedNetworks(data);
|
|
5619
|
+
}, () => {
|
|
5620
|
+
trustedNetworksStatus.textContent = t("trustedNetworksInvalid");
|
|
5621
|
+
trustedNetworksInput.setAttribute("aria-invalid", "true");
|
|
5622
|
+
}).finally(() => {
|
|
5623
|
+
trustedNetworksBusy = false;
|
|
5624
|
+
syncTrustedNetworksControls();
|
|
5625
|
+
});
|
|
5626
|
+
});
|
|
4759
5627
|
lanSetupLabel.append(lanSetupSelect);
|
|
4760
5628
|
lanSetupActions.append(lanSetupRefresh, lanSetupConfigure);
|
|
4761
5629
|
lanSetupForm.append(lanSetupLabel, lanSetupNote, lanSetupActions);
|
|
@@ -5314,14 +6182,14 @@ window.__ModuleLoader__.load({
|
|
|
5314
6182
|
const originSetup = element("section", "dsh-mobile-control__origin-setup");
|
|
5315
6183
|
originSetup.hidden = true;
|
|
5316
6184
|
const originSetupTitle = element("h3", "dsh-mobile-control__section-title");
|
|
5317
|
-
originSetupTitle.textContent = t("
|
|
6185
|
+
originSetupTitle.textContent = t("originHttpsTitle");
|
|
5318
6186
|
const originHelp = element("p", "dsh-mobile-control__component-note");
|
|
5319
6187
|
originHelp.id = "dsh-mobile-origin-help";
|
|
5320
6188
|
originHelp.textContent = t("originHelp");
|
|
5321
6189
|
const originWarning = element("p", "dsh-mobile-control__origin-warning");
|
|
5322
6190
|
originWarning.textContent = t("originWarning");
|
|
5323
6191
|
const originFields = element("div", "dsh-mobile-control__origin-fields");
|
|
5324
|
-
const originPublicLabel = element("label", "dsh-mobile-control__field");
|
|
6192
|
+
const originPublicLabel = element("label", "dsh-mobile-control__field dsh-mobile-control__field--full");
|
|
5325
6193
|
originPublicLabel.textContent = t("originPublicOrigin");
|
|
5326
6194
|
const originPublic = element("input");
|
|
5327
6195
|
originPublic.type = "url";
|
|
@@ -5342,7 +6210,7 @@ window.__ModuleLoader__.load({
|
|
|
5342
6210
|
originPort.max = "65535";
|
|
5343
6211
|
originPort.step = "1";
|
|
5344
6212
|
originPort.value = "3444";
|
|
5345
|
-
const originCidrsLabel = element("label", "dsh-mobile-control__field");
|
|
6213
|
+
const originCidrsLabel = element("label", "dsh-mobile-control__field dsh-mobile-control__field--full");
|
|
5346
6214
|
originCidrsLabel.textContent = t("originAllowedCidrs");
|
|
5347
6215
|
const originCidrs = element("input");
|
|
5348
6216
|
originCidrs.type = "text";
|
|
@@ -5386,7 +6254,94 @@ window.__ModuleLoader__.load({
|
|
|
5386
6254
|
originCopyBackend.type = "button";
|
|
5387
6255
|
originCopyBackend.textContent = t("originCopyBackend");
|
|
5388
6256
|
originBackend.append(originBackendLabel, originBackendAddress, originCopyBackend);
|
|
5389
|
-
|
|
6257
|
+
const caddyModeRow = element("fieldset", "dsh-mobile-control__origin-modes");
|
|
6258
|
+
const caddyModeLegend = element("legend");
|
|
6259
|
+
caddyModeLegend.textContent = t("originHttpsMode");
|
|
6260
|
+
const caddyModeManagedLabel = element("label", "dsh-mobile-control__origin-mode");
|
|
6261
|
+
const caddyModeManaged = element("input");
|
|
6262
|
+
caddyModeManaged.type = "radio";
|
|
6263
|
+
caddyModeManaged.name = "caddy-mode";
|
|
6264
|
+
caddyModeManaged.value = "managed";
|
|
6265
|
+
const caddyModeExternalLabel = element("label", "dsh-mobile-control__origin-mode");
|
|
6266
|
+
const caddyModeExternal = element("input");
|
|
6267
|
+
caddyModeExternal.type = "radio";
|
|
6268
|
+
caddyModeExternal.name = "caddy-mode";
|
|
6269
|
+
caddyModeExternal.value = "external";
|
|
6270
|
+
const caddyManagedText = element("span");
|
|
6271
|
+
caddyManagedText.textContent = t("caddyModeManaged");
|
|
6272
|
+
const caddyExternalText = element("span");
|
|
6273
|
+
caddyExternalText.textContent = t("caddyModeExternal");
|
|
6274
|
+
caddyModeManagedLabel.append(caddyModeManaged, caddyManagedText);
|
|
6275
|
+
caddyModeExternalLabel.append(caddyModeExternal, caddyExternalText);
|
|
6276
|
+
caddyModeRow.append(caddyModeLegend, caddyModeExternalLabel, caddyModeManagedLabel);
|
|
6277
|
+
const caddyForm = element("div", "dsh-mobile-control__caddy-form");
|
|
6278
|
+
caddyForm.hidden = true;
|
|
6279
|
+
const caddyFields = element("div", "dsh-mobile-control__origin-fields");
|
|
6280
|
+
const caddyDomainLabel = element("label", "dsh-mobile-control__field dsh-mobile-control__field--full");
|
|
6281
|
+
caddyDomainLabel.textContent = t("caddyDomain");
|
|
6282
|
+
const caddyDomain = element("input");
|
|
6283
|
+
caddyDomain.type = "url";
|
|
6284
|
+
caddyDomain.placeholder = "https://phone.example.com:8443";
|
|
6285
|
+
caddyDomain.maxLength = 512;
|
|
6286
|
+
caddyDomain.autocomplete = "off";
|
|
6287
|
+
caddyDomain.spellcheck = false;
|
|
6288
|
+
const caddyProviderLabel = element("label", "dsh-mobile-control__field dsh-mobile-control__field--full");
|
|
6289
|
+
caddyProviderLabel.textContent = t("caddyDnsProvider");
|
|
6290
|
+
const caddyProvider = element("select");
|
|
6291
|
+
const caddyProviderOption = document.createElement("option");
|
|
6292
|
+
caddyProviderOption.value = "tencentcloud";
|
|
6293
|
+
caddyProviderOption.textContent = "Tencent Cloud DNSPod";
|
|
6294
|
+
caddyProvider.append(caddyProviderOption);
|
|
6295
|
+
const caddySecretIdLabel = element("label", "dsh-mobile-control__field");
|
|
6296
|
+
caddySecretIdLabel.textContent = t("caddySecretId");
|
|
6297
|
+
const caddySecretId = element("input");
|
|
6298
|
+
caddySecretId.type = "password";
|
|
6299
|
+
caddySecretId.autocomplete = "off";
|
|
6300
|
+
caddySecretId.spellcheck = false;
|
|
6301
|
+
const caddySecretKeyLabel = element("label", "dsh-mobile-control__field");
|
|
6302
|
+
caddySecretKeyLabel.textContent = t("caddySecretKey");
|
|
6303
|
+
const caddySecretKey = element("input");
|
|
6304
|
+
caddySecretKey.type = "password";
|
|
6305
|
+
caddySecretKey.autocomplete = "off";
|
|
6306
|
+
caddySecretKey.spellcheck = false;
|
|
6307
|
+
const caddyPortLabel = element("label", "dsh-mobile-control__field dsh-mobile-control__field--full");
|
|
6308
|
+
caddyPortLabel.textContent = t("caddyListenPort");
|
|
6309
|
+
const caddyPort = element("input");
|
|
6310
|
+
caddyPort.type = "number";
|
|
6311
|
+
caddyPort.inputMode = "numeric";
|
|
6312
|
+
caddyPort.min = "1024";
|
|
6313
|
+
caddyPort.max = "65535";
|
|
6314
|
+
caddyPort.step = "1";
|
|
6315
|
+
caddyPort.value = "8443";
|
|
6316
|
+
caddyDomainLabel.append(caddyDomain);
|
|
6317
|
+
caddyProviderLabel.append(caddyProvider);
|
|
6318
|
+
caddySecretIdLabel.append(caddySecretId);
|
|
6319
|
+
caddySecretKeyLabel.append(caddySecretKey);
|
|
6320
|
+
caddyPortLabel.append(caddyPort);
|
|
6321
|
+
caddyFields.append(caddyDomainLabel, caddyProviderLabel, caddySecretIdLabel, caddySecretKeyLabel, caddyPortLabel);
|
|
6322
|
+
const caddyInstall = element("button", "dsh-mobile-control__primary");
|
|
6323
|
+
caddyInstall.type = "button";
|
|
6324
|
+
caddyInstall.textContent = t("caddyInstall");
|
|
6325
|
+
const caddyInstallNote = element("p", "dsh-mobile-control__component-note");
|
|
6326
|
+
caddyInstallNote.textContent = t("caddyInstallNote");
|
|
6327
|
+
const caddyStatus = element("p", "dsh-mobile-control__component-status");
|
|
6328
|
+
caddyStatus.hidden = true;
|
|
6329
|
+
const caddySave = element("button", "dsh-mobile-control__primary");
|
|
6330
|
+
caddySave.type = "button";
|
|
6331
|
+
caddySave.textContent = t("caddySaveConnect");
|
|
6332
|
+
caddySave.hidden = true;
|
|
6333
|
+
const caddyPurgeButton = element("button", "dsh-mobile-control__danger");
|
|
6334
|
+
caddyPurgeButton.type = "button";
|
|
6335
|
+
caddyPurgeButton.textContent = t("caddyPurge");
|
|
6336
|
+
caddyPurgeButton.hidden = true;
|
|
6337
|
+
const caddyFeedback = element("p", "dsh-mobile-control__origin-feedback");
|
|
6338
|
+
caddyFeedback.setAttribute("role", "status");
|
|
6339
|
+
caddyFeedback.setAttribute("aria-live", "polite");
|
|
6340
|
+
caddyFeedback.hidden = true;
|
|
6341
|
+
const caddyActions = element("div", "dsh-mobile-control__actions dsh-mobile-control__caddy-actions");
|
|
6342
|
+
caddyActions.append(caddyInstall, caddySave, caddyPurgeButton);
|
|
6343
|
+
caddyForm.append(caddyFields, caddyInstallNote, caddyStatus, caddyActions, caddyFeedback);
|
|
6344
|
+
originSetup.append(originSetupTitle, caddyModeRow, originHelp, originWarning, originFields, originFeedback, originFormActions, caddyForm, originBackend);
|
|
5390
6345
|
const tailscaleInfo = element("details", "dsh-mobile-control__details");
|
|
5391
6346
|
const tailscaleInfoSummary = element("summary");
|
|
5392
6347
|
tailscaleInfoSummary.textContent = t("tailscaleHelp");
|
|
@@ -5712,7 +6667,7 @@ window.__ModuleLoader__.load({
|
|
|
5712
6667
|
diagnosticsDetails.append(diagnosticsDetailsSummary, diagnosticsReport);
|
|
5713
6668
|
header.append(title, headerActions);
|
|
5714
6669
|
actions.append(toggle, pair, linkPair);
|
|
5715
|
-
lanView.append(lanSetup, access, qrBox, status, extensionStatus, actions, manageRow, devicePanel);
|
|
6670
|
+
lanView.append(lanSetup, access, qrBox, status, extensionStatus, actions, manageRow, devicePanel, trustedNetworks);
|
|
5716
6671
|
remoteView.append(remoteIntro, httpFrameWarning, providerSection, remoteWorkspace);
|
|
5717
6672
|
diagnosticsView.append(diagnosticsIntro, diagnosticsSummary, diagnosticsToolbar, diagnosticsFeedback, diagnosticsChecks, wsPathsSection, diagnosticsDetails);
|
|
5718
6673
|
panel.append(header, releaseNotice, updateCard, appDownload, switcher, lanView, remoteView, diagnosticsView);
|
|
@@ -5747,6 +6702,23 @@ window.__ModuleLoader__.load({
|
|
|
5747
6702
|
let originConfigured = false;
|
|
5748
6703
|
let originFormDirty = false;
|
|
5749
6704
|
let originFormBusy = false;
|
|
6705
|
+
let caddyFormBusy = false;
|
|
6706
|
+
let caddyModeManagedSelected = false;
|
|
6707
|
+
let caddyFormDirty = false;
|
|
6708
|
+
let caddyModePreviewDirty = false;
|
|
6709
|
+
const showCaddyFeedback = (message, isError = false) => {
|
|
6710
|
+
caddyFeedback.textContent = message;
|
|
6711
|
+
caddyFeedback.classList.toggle("is-error", isError);
|
|
6712
|
+
caddyFeedback.hidden = false;
|
|
6713
|
+
};
|
|
6714
|
+
const setCaddyFormBusy = (busy) => {
|
|
6715
|
+
caddyFormBusy = busy;
|
|
6716
|
+
remoteProviderBusy = busy;
|
|
6717
|
+
remoteSnapshotEpoch += 1;
|
|
6718
|
+
caddyInstall.setAttribute("aria-busy", String(busy));
|
|
6719
|
+
caddySave.setAttribute("aria-busy", String(busy));
|
|
6720
|
+
renderRemote(latestRemoteState);
|
|
6721
|
+
};
|
|
5750
6722
|
let remoteSnapshotEpoch = 0;
|
|
5751
6723
|
let latestRemoteState = {};
|
|
5752
6724
|
let frpInstalled = false;
|
|
@@ -6274,6 +7246,44 @@ window.__ModuleLoader__.load({
|
|
|
6274
7246
|
originBackendLabel.textContent = originListening ? t("originBackendListening") : t("originBackendSaved");
|
|
6275
7247
|
originBackendAddress.textContent = backendOrigin;
|
|
6276
7248
|
originCopyBackend.disabled = remoteProviderBusy || backendOrigin === "";
|
|
7249
|
+
const caddyComponentInfo = originProvider.component;
|
|
7250
|
+
const caddyInstalled = caddyComponentInfo !== void 0 && caddyComponentInfo.installed === true;
|
|
7251
|
+
const caddySupported = caddyComponentInfo !== void 0 && caddyComponentInfo.supported === true;
|
|
7252
|
+
if (!caddyModePreviewDirty) caddyModeManagedSelected = data.originMode === "managed";
|
|
7253
|
+
const showManaged = origin && caddyModeManagedSelected;
|
|
7254
|
+
const caddyConfiguration = typeof data.caddyConfiguration === "object" && data.caddyConfiguration !== null ? data.caddyConfiguration : {};
|
|
7255
|
+
const caddyState = typeof data.caddyState === "object" && data.caddyState !== null ? data.caddyState : {};
|
|
7256
|
+
caddyModeRow.hidden = !origin;
|
|
7257
|
+
caddyForm.hidden = !showManaged;
|
|
7258
|
+
originFields.hidden = showManaged;
|
|
7259
|
+
originFormActions.hidden = showManaged;
|
|
7260
|
+
originHelp.hidden = showManaged;
|
|
7261
|
+
originWarning.hidden = showManaged;
|
|
7262
|
+
if (showManaged) originBackend.hidden = true;
|
|
7263
|
+
caddyInstall.hidden = !showManaged || caddyInstalled;
|
|
7264
|
+
caddyInstallNote.hidden = !showManaged;
|
|
7265
|
+
caddySave.hidden = !showManaged || !caddyInstalled;
|
|
7266
|
+
caddyPurgeButton.hidden = !showManaged || !caddyInstalled && caddyConfiguration.configured !== true;
|
|
7267
|
+
caddyStatus.hidden = !showManaged;
|
|
7268
|
+
caddyStatus.textContent = !caddySupported && !caddyInstalled ? t("caddyUnavailable") : caddyState.enabled === true && caddyState.state === "ready" ? t("caddyVerified") : caddyState.state === "starting" || caddyState.state === "connecting" ? t("remoteStateConnecting") : caddyState.state === "error" ? t("caddyConnectionFailed") : caddyInstalled ? t("caddyInstalled") : t("caddyInstallNote");
|
|
7269
|
+
caddyModeManaged.checked = caddyModeManagedSelected;
|
|
7270
|
+
caddyModeExternal.checked = !caddyModeManagedSelected;
|
|
7271
|
+
caddyModeManaged.disabled = remoteProviderBusy;
|
|
7272
|
+
caddyModeExternal.disabled = remoteProviderBusy;
|
|
7273
|
+
if (!caddyFormBusy && !caddyFormDirty) {
|
|
7274
|
+
caddyDomain.value = typeof caddyConfiguration.publicOrigin === "string" ? caddyConfiguration.publicOrigin : "";
|
|
7275
|
+
caddyPort.value = typeof caddyConfiguration.listenPort === "number" ? String(caddyConfiguration.listenPort) : "8443";
|
|
7276
|
+
}
|
|
7277
|
+
caddyInstall.disabled = remoteProviderBusy || !caddySupported || caddyInstalled;
|
|
7278
|
+
caddySave.disabled = remoteProviderBusy || !caddySupported || !caddyInstalled;
|
|
7279
|
+
caddyPurgeButton.disabled = remoteProviderBusy;
|
|
7280
|
+
for (const input of [
|
|
7281
|
+
caddyDomain,
|
|
7282
|
+
caddyProvider,
|
|
7283
|
+
caddySecretId,
|
|
7284
|
+
caddySecretKey,
|
|
7285
|
+
caddyPort
|
|
7286
|
+
]) input.disabled = remoteProviderBusy || !caddySupported;
|
|
6277
7287
|
selfHostedBadge.textContent = origin ? originListening ? t("originBackendReady") : t("advanced") : frpConfigured && frpInstalled ? t("ready") : t("advanced");
|
|
6278
7288
|
const state = typeof data.state === "string" ? data.state : "error";
|
|
6279
7289
|
const errorCode = typeof data.errorCode === "string" ? data.errorCode : "";
|
|
@@ -6315,14 +7325,15 @@ window.__ModuleLoader__.load({
|
|
|
6315
7325
|
remoteSetup.disabled = remoteSetupUrl === "" || remoteReconnectBusy;
|
|
6316
7326
|
remoteSetupRetry.disabled = remoteReconnectBusy;
|
|
6317
7327
|
remoteToggle.textContent = remoteRunning ? t("disableRemote") : t("enableRemote");
|
|
6318
|
-
const
|
|
7328
|
+
const originPrepared = data.originMode === "managed" ? caddySupported && caddyInstalled && caddyConfiguration.configured === true : originConfigured && !caddyModeManagedSelected;
|
|
7329
|
+
const providerPrepared = cpolar ? cpolarInstalled && cpolarConfigured : cloudflared ? cloudflaredInstalled : frp ? frpInstalled && frpConfigured : origin ? originPrepared : true;
|
|
6319
7330
|
remoteToggle.disabled = remoteProviderBusy || !providerPrepared && !(origin && remoteRunning);
|
|
6320
7331
|
remoteLogin.hidden = !tailscale || state !== "needs-login" || remoteLoginUrl === "";
|
|
6321
7332
|
remoteReconnect.hidden = needsFunnelSetup || state !== "error" && state !== "unavailable" || !providerPrepared;
|
|
6322
7333
|
remoteActions.hidden = !providerPrepared && !(origin && remoteRunning);
|
|
6323
|
-
remotePair.disabled = !remoteReady ||
|
|
6324
|
-
remoteCopyLink.disabled = !remoteReady ||
|
|
6325
|
-
remoteDevices.disabled = !remoteReady ||
|
|
7334
|
+
remotePair.disabled = !remoteReady || remoteProviderBusy;
|
|
7335
|
+
remoteCopyLink.disabled = !remoteReady || remoteProviderBusy;
|
|
7336
|
+
remoteDevices.disabled = !remoteReady || remoteProviderBusy;
|
|
6326
7337
|
if (origin) {
|
|
6327
7338
|
remoteReconnect.disabled = remoteProviderBusy || remoteReconnectBusy;
|
|
6328
7339
|
remoteReset.disabled = remoteProviderBusy;
|
|
@@ -6367,7 +7378,7 @@ window.__ModuleLoader__.load({
|
|
|
6367
7378
|
});
|
|
6368
7379
|
let remoteLoadInFlight = false;
|
|
6369
7380
|
const loadRemote = () => {
|
|
6370
|
-
if (remoteLoadInFlight || originFormBusy) return;
|
|
7381
|
+
if (remoteLoadInFlight || originFormBusy || caddyFormBusy) return;
|
|
6371
7382
|
const epoch = remoteSnapshotEpoch;
|
|
6372
7383
|
remoteLoadInFlight = true;
|
|
6373
7384
|
controlRequestJson("/api/mobile-access/remote/control").then((data) => {
|
|
@@ -7185,6 +8196,123 @@ window.__ModuleLoader__.load({
|
|
|
7185
8196
|
clearOriginValidation();
|
|
7186
8197
|
originFeedback.hidden = true;
|
|
7187
8198
|
});
|
|
8199
|
+
const syncCaddyModeVisibility = () => {
|
|
8200
|
+
caddyModeManagedSelected = caddyModeManaged.checked;
|
|
8201
|
+
caddyModePreviewDirty = true;
|
|
8202
|
+
renderRemote(latestRemoteState);
|
|
8203
|
+
};
|
|
8204
|
+
caddyModeManaged.addEventListener("change", () => {
|
|
8205
|
+
if (remoteProviderBusy) return;
|
|
8206
|
+
originFeedback.hidden = true;
|
|
8207
|
+
syncCaddyModeVisibility();
|
|
8208
|
+
});
|
|
8209
|
+
caddyModeExternal.addEventListener("change", () => {
|
|
8210
|
+
if (remoteProviderBusy) return;
|
|
8211
|
+
if (latestRemoteState.originMode === "managed" && !window.confirm(t("caddyModeChangeConfirm"))) {
|
|
8212
|
+
caddyModeManaged.checked = true;
|
|
8213
|
+
caddyModeExternal.checked = false;
|
|
8214
|
+
return;
|
|
8215
|
+
}
|
|
8216
|
+
originFeedback.hidden = true;
|
|
8217
|
+
syncCaddyModeVisibility();
|
|
8218
|
+
if (latestRemoteState.originMode !== "managed") return;
|
|
8219
|
+
setCaddyFormBusy(true);
|
|
8220
|
+
controlRequestJson("/api/mobile-access/remote/origin/mode", {
|
|
8221
|
+
method: "POST",
|
|
8222
|
+
body: JSON.stringify({ mode: "external" })
|
|
8223
|
+
}).then((data) => {
|
|
8224
|
+
caddyModePreviewDirty = false;
|
|
8225
|
+
renderRemote(data);
|
|
8226
|
+
}, () => {
|
|
8227
|
+
caddyModePreviewDirty = false;
|
|
8228
|
+
renderRemote(latestRemoteState);
|
|
8229
|
+
showOriginFeedback(t("caddyConnectionFailed"), true);
|
|
8230
|
+
}).finally(() => {
|
|
8231
|
+
setCaddyFormBusy(false);
|
|
8232
|
+
loadRemote();
|
|
8233
|
+
});
|
|
8234
|
+
});
|
|
8235
|
+
for (const input of [
|
|
8236
|
+
caddyDomain,
|
|
8237
|
+
caddyProvider,
|
|
8238
|
+
caddySecretId,
|
|
8239
|
+
caddySecretKey,
|
|
8240
|
+
caddyPort
|
|
8241
|
+
]) input.addEventListener("input", () => {
|
|
8242
|
+
caddyFormDirty = true;
|
|
8243
|
+
});
|
|
8244
|
+
caddyProvider.addEventListener("change", () => {
|
|
8245
|
+
caddyFormDirty = true;
|
|
8246
|
+
});
|
|
8247
|
+
caddyInstall.addEventListener("click", () => {
|
|
8248
|
+
if (remoteProviderBusy || caddyInstall.disabled || !window.confirm(t("caddyInstallConfirm"))) return;
|
|
8249
|
+
setCaddyFormBusy(true);
|
|
8250
|
+
showCaddyFeedback(t("checkingComponent"));
|
|
8251
|
+
controlRequestJson("/api/mobile-access/remote/caddy/component/install", {
|
|
8252
|
+
method: "POST",
|
|
8253
|
+
body: JSON.stringify({ confirm: true })
|
|
8254
|
+
}).then((data) => {
|
|
8255
|
+
renderRemote(data);
|
|
8256
|
+
showCaddyFeedback(t("caddyInstalled"));
|
|
8257
|
+
}, () => showCaddyFeedback(t("caddyInstallFailed"), true)).finally(() => {
|
|
8258
|
+
setCaddyFormBusy(false);
|
|
8259
|
+
loadRemote();
|
|
8260
|
+
});
|
|
8261
|
+
});
|
|
8262
|
+
caddySave.addEventListener("click", () => {
|
|
8263
|
+
if (remoteProviderBusy || caddySave.disabled || !caddyModeManagedSelected) return;
|
|
8264
|
+
clearOriginValidation();
|
|
8265
|
+
for (const input of [
|
|
8266
|
+
caddyDomain,
|
|
8267
|
+
caddySecretId,
|
|
8268
|
+
caddySecretKey,
|
|
8269
|
+
caddyPort
|
|
8270
|
+
]) if (!input.reportValidity()) return;
|
|
8271
|
+
const body = {
|
|
8272
|
+
settings: {
|
|
8273
|
+
version: 1,
|
|
8274
|
+
publicOrigin: caddyDomain.value.trim(),
|
|
8275
|
+
dnsProvider: caddyProvider.value,
|
|
8276
|
+
listenPort: Number(caddyPort.value)
|
|
8277
|
+
},
|
|
8278
|
+
secretId: caddySecretId.value.trim(),
|
|
8279
|
+
secretKey: caddySecretKey.value.trim(),
|
|
8280
|
+
connect: true
|
|
8281
|
+
};
|
|
8282
|
+
setCaddyFormBusy(true);
|
|
8283
|
+
showCaddyFeedback(t("saving"));
|
|
8284
|
+
controlRequestJson("/api/mobile-access/remote/caddy/settings", {
|
|
8285
|
+
method: "POST",
|
|
8286
|
+
body: JSON.stringify(body)
|
|
8287
|
+
}).then((data) => {
|
|
8288
|
+
caddySecretId.value = "";
|
|
8289
|
+
caddySecretKey.value = "";
|
|
8290
|
+
caddyFormDirty = false;
|
|
8291
|
+
caddyModePreviewDirty = false;
|
|
8292
|
+
renderRemote(data);
|
|
8293
|
+
if (data.state === "error") showCaddyFeedback(t("caddyConnectionFailed"), true);
|
|
8294
|
+
}, () => showCaddyFeedback(t("caddyConnectionFailed"), true)).finally(() => {
|
|
8295
|
+
setCaddyFormBusy(false);
|
|
8296
|
+
loadRemote();
|
|
8297
|
+
});
|
|
8298
|
+
});
|
|
8299
|
+
caddyPurgeButton.addEventListener("click", () => {
|
|
8300
|
+
if (remoteProviderBusy || !window.confirm(t("caddyPurgeConfirm"))) return;
|
|
8301
|
+
setCaddyFormBusy(true);
|
|
8302
|
+
controlRequestJson("/api/mobile-access/remote/caddy/component/purge", {
|
|
8303
|
+
method: "POST",
|
|
8304
|
+
body: JSON.stringify({ confirm: true })
|
|
8305
|
+
}).then((data) => {
|
|
8306
|
+
caddySecretId.value = "";
|
|
8307
|
+
caddySecretKey.value = "";
|
|
8308
|
+
caddyFormDirty = false;
|
|
8309
|
+
caddyModePreviewDirty = false;
|
|
8310
|
+
renderRemote(data);
|
|
8311
|
+
}, () => showCaddyFeedback(t("caddyPurgeFailed"), true)).finally(() => {
|
|
8312
|
+
setCaddyFormBusy(false);
|
|
8313
|
+
loadRemote();
|
|
8314
|
+
});
|
|
8315
|
+
});
|
|
7188
8316
|
originConfigure.addEventListener("click", () => {
|
|
7189
8317
|
if (remoteProviderBusy || remoteProvider !== "origin") return;
|
|
7190
8318
|
clearOriginValidation();
|
|
@@ -7205,7 +8333,10 @@ window.__ModuleLoader__.load({
|
|
|
7205
8333
|
controlRequestJson("/api/mobile-access/remote/origin/configure", {
|
|
7206
8334
|
method: "POST",
|
|
7207
8335
|
body: JSON.stringify(form)
|
|
7208
|
-
}).then(() => controlRequestJson("/api/mobile-access/remote/
|
|
8336
|
+
}).then(() => controlRequestJson("/api/mobile-access/remote/origin/mode", {
|
|
8337
|
+
method: "POST",
|
|
8338
|
+
body: JSON.stringify({ mode: "external" })
|
|
8339
|
+
})).then(() => controlRequestJson("/api/mobile-access/remote/control", {
|
|
7209
8340
|
method: "POST",
|
|
7210
8341
|
body: JSON.stringify({ running: true })
|
|
7211
8342
|
})).then((data) => {
|
|
@@ -8915,7 +10046,11 @@ window.__ModuleLoader__.load({
|
|
|
8915
10046
|
.dsh-mobile-control__manage-row{display:flex;justify-content:space-between;gap:8px;margin-top:10px}.dsh-mobile-control__manage{flex:1 1 0;min-width:0;min-height:36px;padding:6px 8px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:10px;background:transparent;color:inherit;font:12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__devices{margin-top:10px;border:1px solid var(--dsw-alias-border-l2,#e1e5eb);border-radius:10px;padding:8px;max-height:220px;overflow-y:auto}.dsh-mobile-control__device-empty{color:var(--dsw-alias-label-secondary,#606873);font-size:12px;margin:0}.dsh-mobile-control__device{display:flex;align-items:center;gap:8px;padding:6px 2px}.dsh-mobile-control__device + .dsh-mobile-control__device{border-top:1px solid var(--dsw-alias-border-l2,#e1e5eb)}.dsh-mobile-control__device-label{flex:1 1 0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px}.dsh-mobile-control__device-meta{flex:none;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;white-space:nowrap}.dsh-mobile-control__device-revoke{flex:none;min-height:36px;padding:4px 8px;border:1px solid var(--dsw-alias-state-error-primary,#dc2626);border-radius:8px;background:transparent;color:var(--dsw-alias-label-primary,#dc2626);font:12px/1.2 system-ui;cursor:pointer}.dsh-mobile-control__ws-paths{margin:10px 0 0;padding:12px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__ws-paths-list{margin:0 0 8px;padding:0;list-style:none}.dsh-mobile-control__ws-paths-empty{color:var(--dsw-alias-label-secondary,#606873);font-size:12px}.dsh-mobile-control__ws-paths-item{display:flex;align-items:center;gap:8px;padding:6px 0}.dsh-mobile-control__ws-paths-item + .dsh-mobile-control__ws-paths-item{border-top:1px solid var(--dsw-alias-border-l2,#e1e5eb)}.dsh-mobile-control__ws-paths-item code{flex:1 1 0;min-width:0;overflow:hidden;font:11px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__ws-paths-remove{flex:none;min-height:36px;padding:4px 8px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:8px;background:transparent;color:inherit;font:12px/1.2 system-ui;cursor:pointer}.dsh-mobile-control__trigger,.dsh-mobile-control__diagnostic-entry{position:relative}.dsh-mobile-control__ws-dot{position:absolute;top:2px;right:2px;z-index:1;min-width:16px;height:16px;padding:0 4px;border-radius:999px;background:var(--dsw-alias-state-error-primary,#dc2626);color:#fff;font:650 11px/16px system-ui;text-align:center;cursor:pointer}.dsh-mobile-control__ws-dot::after{content:'';position:absolute;inset:-5px}.dsh-mobile-control__ws-sr-status{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.dsh-mobile-control__ws-paths-row{display:flex;gap:8px}.dsh-mobile-control__ws-paths-row .dsh-mobile-control__primary{width:auto;flex:none;padding:9px 14px;border-radius:10px}.dsh-mobile-control__ws-paths-group{padding:6px 0}.dsh-mobile-control__ws-paths-group + .dsh-mobile-control__ws-paths-group{border-top:1px solid var(--dsw-alias-border-l2,#e1e5eb)}.dsh-mobile-control__ws-paths-group-head{display:flex;align-items:center;gap:8px}.dsh-mobile-control__ws-count{flex:none;padding:1px 7px;border-radius:999px;background:var(--dsw-alias-state-business-tertiary,#e8f0ff);color:var(--dsw-alias-label-primary,#1d4ed8);font:650 11px/1.6 system-ui}.dsh-mobile-control__ws-paths-item.is-child{padding-left:12px}.dsh-mobile-control__ws-paths-group-head .dsh-mobile-control__primary,.dsh-mobile-control__ws-paths-item .dsh-mobile-control__primary{width:auto;flex:none;min-height:36px;padding:6px 12px;border-radius:10px}.dsh-mobile-control__ws-paths-row input{flex:1 1 0;min-width:0;box-sizing:border-box;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__ws-paths-row button{flex:none}.dsh-mobile-control__ws-paths-detected-title{margin:0 0 2px;font:600 12px/1.4 system-ui}.dsh-mobile-control__ws-paths-advanced{margin-top:8px}.dsh-mobile-control__ws-paths-advanced summary{cursor:pointer;color:var(--dsw-alias-label-secondary,#606873);font:12px/1.4 system-ui;user-select:none}.dsh-mobile-control__ws-paths-advanced summary:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,currentColor);outline-offset:2px;border-radius:4px}.dsh-mobile-control__ws-paths-advanced[open] summary{margin-bottom:6px}.dsh-mobile-control__ws-paths-advanced .dsh-mobile-control__status{margin:6px 0 0}
|
|
8916
10047
|
@media (max-width:359px){.dsh-mobile-control__provider{min-height:68px}}@media (prefers-reduced-motion:reduce){.dsh-mobile-control__provider,.dsh-mobile-control__cpolar-connect{transition:none}.dsh-mobile-control__diagnostic-summary.is-running .dsh-mobile-control__diagnostic-summary-icon::before,.dsh-mobile-control__diagnostic-checks{animation:none}}
|
|
8917
10048
|
|
|
8918
|
-
.dsh-mobile-control__origin-setup{margin:0;padding:12px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__origin-setup[hidden],.dsh-mobile-control__origin-
|
|
10049
|
+
.dsh-mobile-control__origin-setup{margin:0;padding:12px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__origin-setup[hidden],.dsh-mobile-control__origin-setup [hidden]{display:none}
|
|
10050
|
+
/* Mode choices are not text fields; preserve native radio sizing and keyboard behavior. */
|
|
10051
|
+
.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
|
+
/* Base the columns on the card width, not the desktop viewport behind the popup. */
|
|
10053
|
+
.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}
|
|
8919
10054
|
`;
|
|
8920
10055
|
/** Mount the desktop control or mobile feature enhancements. */
|
|
8921
10056
|
function apply(ctx) {
|
|
@@ -8928,10 +10063,29 @@ window.__ModuleLoader__.load({
|
|
|
8928
10063
|
const style = element("style");
|
|
8929
10064
|
style.dataset.plugin = "dsh-mobile";
|
|
8930
10065
|
style.textContent = desktopAdmin ? CONTROL_STYLES : NATIVE_MOBILE_STYLES;
|
|
10066
|
+
style.textContent += CLIENT_MODULE_STYLES;
|
|
8931
10067
|
document.head.append(style);
|
|
8932
10068
|
const removeVoiceSession = installVoiceSession(!desktopAdmin);
|
|
10069
|
+
const removeModuleSettings = ctx.slots.inject("settings.general.item", () => ctx.slots.register({
|
|
10070
|
+
name: "settings.general.item",
|
|
10071
|
+
id: "dsh-mobile-client-modules",
|
|
10072
|
+
order: 120
|
|
10073
|
+
}, () => (0, react.createElement)(ClientModuleSettingsRow, {
|
|
10074
|
+
scope: desktopAdmin ? "computer" : "device",
|
|
10075
|
+
locale: selectedMobileControlLocale()
|
|
10076
|
+
})));
|
|
8933
10077
|
if (!desktopAdmin) {
|
|
10078
|
+
const localFont = installMobileFontPreference();
|
|
8934
10079
|
const removeSettingsAction = ctx.slots.inject("settings.general.item", () => {
|
|
10080
|
+
const removeFontSize = ctx.slots.register({
|
|
10081
|
+
name: "settings.general.item",
|
|
10082
|
+
id: "font-size",
|
|
10083
|
+
order: 11,
|
|
10084
|
+
priority: -1
|
|
10085
|
+
}, () => (0, react.createElement)(MobileFontSizeRow, {
|
|
10086
|
+
preference: localFont.preference,
|
|
10087
|
+
locale: selectedMobileControlLocale()
|
|
10088
|
+
}));
|
|
8935
10089
|
const removeSwitchComputer = ctx.slots.register({
|
|
8936
10090
|
name: "settings.general.item",
|
|
8937
10091
|
id: "dsh-mobile-switch-computer",
|
|
@@ -8945,6 +10099,7 @@ window.__ModuleLoader__.load({
|
|
|
8945
10099
|
return () => {
|
|
8946
10100
|
removeTaskNotifications();
|
|
8947
10101
|
removeSwitchComputer();
|
|
10102
|
+
removeFontSize();
|
|
8948
10103
|
};
|
|
8949
10104
|
});
|
|
8950
10105
|
const removeCustom = installCustomAssets();
|
|
@@ -8953,10 +10108,13 @@ window.__ModuleLoader__.load({
|
|
|
8953
10108
|
getSidebarRight: () => ctx.get("sidebarRight")
|
|
8954
10109
|
}));
|
|
8955
10110
|
return () => {
|
|
10111
|
+
disposeClientModuleDialogs();
|
|
10112
|
+
removeModuleSettings();
|
|
8956
10113
|
removeSettingsAction();
|
|
8957
10114
|
removeCustom();
|
|
8958
10115
|
removeSurface();
|
|
8959
10116
|
removeVoiceSession();
|
|
10117
|
+
localFont.dispose();
|
|
8960
10118
|
style.remove();
|
|
8961
10119
|
};
|
|
8962
10120
|
}
|
|
@@ -9001,6 +10159,8 @@ window.__ModuleLoader__.load({
|
|
|
9001
10159
|
};
|
|
9002
10160
|
});
|
|
9003
10161
|
return () => {
|
|
10162
|
+
disposeClientModuleDialogs();
|
|
10163
|
+
removeModuleSettings();
|
|
9004
10164
|
removeControl();
|
|
9005
10165
|
removeVoiceSession();
|
|
9006
10166
|
style.remove();
|