dsh-mobile 0.6.4 → 0.6.6
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 +27 -4
- package/CONTRIBUTORS.md +13 -6
- package/README.en.md +10 -6
- package/README.md +10 -6
- package/apps/mobile/README.md +14 -8
- package/apps/mobile/release.json +3 -3
- package/docs/CLIENT_MODULES.en.md +14 -4
- package/docs/CLIENT_MODULES.md +14 -4
- package/docs/README.md +1 -0
- package/docs/SKIN_COMPATIBILITY.en.md +25 -0
- package/docs/SKIN_COMPATIBILITY.md +25 -0
- package/lib/client.js +1163 -365
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +79 -0
- package/lib/index.mjs +6461 -6000
- package/lib/index.mjs.map +1 -1
- package/lib/mobile-layout.js +7 -1
- package/lib/mobile-layout.js.map +1 -1
- package/package.json +7 -1
- package/packages/question-fixes/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -598,7 +598,12 @@ window.__ModuleLoader__.load({
|
|
|
598
598
|
cpolarDownloadTimeout: "The cpolar download timed out. Check this computer’s network or proxy, then retry.",
|
|
599
599
|
cpolarDownloadHttp: "The official cpolar download returned HTTP {status}. Retry later or check the official download page.",
|
|
600
600
|
cpolarIntegrityFailed: "The cpolar download did not match the pinned build, so it was not installed. Retry later or update DSH Mobile; do not bypass verification.",
|
|
601
|
-
cpolarExtractFailed: "The cpolar download
|
|
601
|
+
cpolarExtractFailed: "The cpolar download was verified, but extraction failed. Check the extraction stage in the DSH log; the message alone does not establish a security-software block.",
|
|
602
|
+
cpolarExtractToolsFailed: "A required Windows extraction tool is unavailable. Check system PowerShell, tar and expand; no administrator startup or service installation is required.",
|
|
603
|
+
cpolarExtractArchiveFailed: "Could not extract the verified cpolar ZIP or locate its MSI. Check the ZIP extraction stage in the DSH log.",
|
|
604
|
+
cpolarExtractDatabaseFailed: "Could not read the cpolar MSI database or its embedded cabinet. Check the MSI read stage in the DSH log; do not install the MSI as a workaround.",
|
|
605
|
+
cpolarExtractCabinetFailed: "Could not extract the cpolar executable from its cabinet. Check the cabinet extraction stage and private-directory permissions.",
|
|
606
|
+
cpolarExtractFileFailed: "Could not read or copy the extracted cpolar executable. Check private-directory permissions and disk space; your existing component is retained.",
|
|
602
607
|
cpolarStorageFailed: "Could not write the cpolar component in DSH Mobile’s private directory. Check disk space, permissions, and security software, then retry."
|
|
603
608
|
});
|
|
604
609
|
Object.assign(MOBILE_CONTROL_MESSAGES.it, {
|
|
@@ -623,7 +628,12 @@ window.__ModuleLoader__.load({
|
|
|
623
628
|
cpolarDownloadTimeout: "Download di cpolar scaduto. Controlla rete o proxy del computer e riprova.",
|
|
624
629
|
cpolarDownloadHttp: "Il download ufficiale di cpolar ha restituito HTTP {status}. Riprova più tardi o controlla la pagina ufficiale.",
|
|
625
630
|
cpolarIntegrityFailed: "Il pacchetto cpolar non corrisponde alla versione verificata e non è stato installato. Riprova più tardi o aggiorna DSH Mobile; non aggirare la verifica.",
|
|
626
|
-
cpolarExtractFailed: "Download di cpolar
|
|
631
|
+
cpolarExtractFailed: "Download di cpolar verificato, ma estrazione non riuscita. Controlla la fase nel log DSH; il messaggio non dimostra un blocco del software di sicurezza.",
|
|
632
|
+
cpolarExtractToolsFailed: "Uno strumento di estrazione Windows richiesto non è disponibile. Controlla PowerShell di sistema, tar ed expand; non serve avviare come amministratore o installare un servizio.",
|
|
633
|
+
cpolarExtractArchiveFailed: "Impossibile estrarre lo ZIP cpolar verificato o trovare il suo MSI. Controlla la fase ZIP nel log DSH.",
|
|
634
|
+
cpolarExtractDatabaseFailed: "Impossibile leggere il database MSI cpolar o il cabinet incorporato. Controlla la fase di lettura MSI nel log DSH; non installare il MSI per aggirare il problema.",
|
|
635
|
+
cpolarExtractCabinetFailed: "Impossibile estrarre il programma cpolar dal cabinet. Controlla la fase cabinet e i permessi della directory privata.",
|
|
636
|
+
cpolarExtractFileFailed: "Impossibile leggere o copiare il programma cpolar estratto. Controlla permessi e spazio nella directory privata; il componente esistente è conservato.",
|
|
627
637
|
cpolarStorageFailed: "Impossibile scrivere cpolar nella directory privata di DSH Mobile. Controlla spazio, permessi e software di sicurezza, poi riprova."
|
|
628
638
|
});
|
|
629
639
|
Object.assign(MOBILE_CONTROL_MESSAGES.zh, {
|
|
@@ -648,7 +658,12 @@ window.__ModuleLoader__.load({
|
|
|
648
658
|
cpolarDownloadTimeout: "下载 cpolar 组件超时。请检查这台电脑的网络或代理,然后重试。",
|
|
649
659
|
cpolarDownloadHttp: "cpolar 官网下载返回 HTTP {status}。请稍后重试,或检查官网下载页。",
|
|
650
660
|
cpolarIntegrityFailed: "下载的 cpolar 组件未通过固定版本校验,因此没有安装。请稍后重试或更新 DSH Mobile,不要跳过校验。",
|
|
651
|
-
cpolarExtractFailed: "cpolar
|
|
661
|
+
cpolarExtractFailed: "cpolar 下载已校验,但提取失败。请查看 DSH 日志中的具体阶段,不能仅凭此提示认定安全软件拦截。",
|
|
662
|
+
cpolarExtractToolsFailed: "所需的 Windows 提取工具不可用。请检查系统 PowerShell、tar 和 expand;无需以管理员启动 DSH 或安装系统服务。",
|
|
663
|
+
cpolarExtractArchiveFailed: "无法展开已校验的 cpolar ZIP 或找到其中的 MSI。请查看 DSH 日志中的 ZIP 提取阶段。",
|
|
664
|
+
cpolarExtractDatabaseFailed: "无法读取 cpolar MSI 数据库或内嵌 CAB。请查看 DSH 日志中的 MSI 读取阶段,不要用运行 MSI 安装程序绕过此问题。",
|
|
665
|
+
cpolarExtractCabinetFailed: "无法从 CAB 中提取 cpolar 程序。请查看 CAB 提取阶段及私有目录权限。",
|
|
666
|
+
cpolarExtractFileFailed: "无法读取或复制已提取的 cpolar 程序。请检查私有目录权限和磁盘空间;已有组件不会被替换。",
|
|
652
667
|
cpolarStorageFailed: "无法把 cpolar 写入 DSH Mobile 私有目录。请检查磁盘空间、目录权限和安全软件,然后重试。"
|
|
653
668
|
});
|
|
654
669
|
Object.assign(MOBILE_CONTROL_MESSAGES.en, {
|
|
@@ -3189,6 +3204,127 @@ window.__ModuleLoader__.load({
|
|
|
3189
3204
|
[data-dsh-mobile-composer-model-label] { flex:1 1 auto !important; max-width:none !important; min-width:0 !important; overflow:hidden !important; text-overflow:ellipsis !important; white-space:nowrap !important; }
|
|
3190
3205
|
}
|
|
3191
3206
|
`;
|
|
3207
|
+
//#endregion
|
|
3208
|
+
//#region src/browser-file-picker.ts
|
|
3209
|
+
/** Browser chooser lifetime shared by camera controls and the extension SDK. */
|
|
3210
|
+
const BROWSER_FILE_PICKER_DEADLINE_MS = 3e5;
|
|
3211
|
+
const browserEnvironment = {
|
|
3212
|
+
createSource: () => {
|
|
3213
|
+
const input = document.createElement("input");
|
|
3214
|
+
return {
|
|
3215
|
+
input,
|
|
3216
|
+
open: () => {
|
|
3217
|
+
document.body.append(input);
|
|
3218
|
+
input.click();
|
|
3219
|
+
},
|
|
3220
|
+
remove: () => {
|
|
3221
|
+
input.remove();
|
|
3222
|
+
}
|
|
3223
|
+
};
|
|
3224
|
+
},
|
|
3225
|
+
deadline: (callback, milliseconds) => {
|
|
3226
|
+
const timer = window.setTimeout(callback, milliseconds);
|
|
3227
|
+
return () => {
|
|
3228
|
+
window.clearTimeout(timer);
|
|
3229
|
+
};
|
|
3230
|
+
}
|
|
3231
|
+
};
|
|
3232
|
+
/** Select files without treating focus/visibility restoration as cancellation.
|
|
3233
|
+
* @param options - MIME selection, lifetime and optional camera capture mode.
|
|
3234
|
+
* @param environment - Browser DOM and timer owner; tests may supply an owned source.
|
|
3235
|
+
* @returns Selected files, or undefined for explicit cancellation or deadline; abort rejects.
|
|
3236
|
+
*/
|
|
3237
|
+
function pickBrowserFiles(options, environment = browserEnvironment) {
|
|
3238
|
+
const abortReason = () => options.abortReason?.() ?? options.signal.reason ?? new DOMException("File selection aborted", "AbortError");
|
|
3239
|
+
if (options.signal.aborted) return Promise.reject(abortReason());
|
|
3240
|
+
return new Promise((resolve, reject) => {
|
|
3241
|
+
let source;
|
|
3242
|
+
let input;
|
|
3243
|
+
let settled = false;
|
|
3244
|
+
let cancelDeadline;
|
|
3245
|
+
const cleanup = () => {
|
|
3246
|
+
const errors = [];
|
|
3247
|
+
const attempt = (operation) => {
|
|
3248
|
+
try {
|
|
3249
|
+
operation();
|
|
3250
|
+
} catch (error) {
|
|
3251
|
+
errors.push(error);
|
|
3252
|
+
}
|
|
3253
|
+
};
|
|
3254
|
+
if (cancelDeadline !== void 0) attempt(cancelDeadline);
|
|
3255
|
+
cancelDeadline = void 0;
|
|
3256
|
+
attempt(() => {
|
|
3257
|
+
input?.removeEventListener("change", onChange);
|
|
3258
|
+
});
|
|
3259
|
+
attempt(() => {
|
|
3260
|
+
input?.removeEventListener("cancel", onCancel);
|
|
3261
|
+
});
|
|
3262
|
+
attempt(() => {
|
|
3263
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
3264
|
+
});
|
|
3265
|
+
attempt(() => {
|
|
3266
|
+
source?.remove();
|
|
3267
|
+
});
|
|
3268
|
+
return errors;
|
|
3269
|
+
};
|
|
3270
|
+
const finish = (outcome) => {
|
|
3271
|
+
if (settled) return;
|
|
3272
|
+
settled = true;
|
|
3273
|
+
const cleanupErrors = cleanup();
|
|
3274
|
+
if (outcome.kind === "failed") reject(outcome.error);
|
|
3275
|
+
else if (cleanupErrors.length > 0) reject(cleanupErrors[0]);
|
|
3276
|
+
else if (outcome.kind === "cancelled") resolve(void 0);
|
|
3277
|
+
else resolve(outcome.files);
|
|
3278
|
+
};
|
|
3279
|
+
const onChange = () => {
|
|
3280
|
+
if (settled || input === void 0) return;
|
|
3281
|
+
try {
|
|
3282
|
+
const files = input.files === null ? [] : [...input.files];
|
|
3283
|
+
finish(files.length === 0 ? { kind: "cancelled" } : {
|
|
3284
|
+
kind: "files",
|
|
3285
|
+
files: Object.freeze(files)
|
|
3286
|
+
});
|
|
3287
|
+
} catch (error) {
|
|
3288
|
+
finish({
|
|
3289
|
+
kind: "failed",
|
|
3290
|
+
error
|
|
3291
|
+
});
|
|
3292
|
+
}
|
|
3293
|
+
};
|
|
3294
|
+
const onCancel = () => {
|
|
3295
|
+
finish({ kind: "cancelled" });
|
|
3296
|
+
};
|
|
3297
|
+
const onAbort = () => {
|
|
3298
|
+
finish({
|
|
3299
|
+
kind: "failed",
|
|
3300
|
+
error: abortReason()
|
|
3301
|
+
});
|
|
3302
|
+
};
|
|
3303
|
+
try {
|
|
3304
|
+
source = environment.createSource();
|
|
3305
|
+
input = source.input;
|
|
3306
|
+
input.type = "file";
|
|
3307
|
+
input.accept = options.accept;
|
|
3308
|
+
if (options.capture !== void 0) input.capture = options.capture;
|
|
3309
|
+
input.multiple = options.multiple ?? false;
|
|
3310
|
+
input.hidden = true;
|
|
3311
|
+
input.addEventListener("change", onChange);
|
|
3312
|
+
input.addEventListener("cancel", onCancel);
|
|
3313
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
3314
|
+
cancelDeadline = environment.deadline(onCancel, BROWSER_FILE_PICKER_DEADLINE_MS);
|
|
3315
|
+
if (options.signal.aborted) {
|
|
3316
|
+
onAbort();
|
|
3317
|
+
return;
|
|
3318
|
+
}
|
|
3319
|
+
source.open();
|
|
3320
|
+
} catch (error) {
|
|
3321
|
+
finish({
|
|
3322
|
+
kind: "failed",
|
|
3323
|
+
error
|
|
3324
|
+
});
|
|
3325
|
+
}
|
|
3326
|
+
});
|
|
3327
|
+
}
|
|
3192
3328
|
//#endregion
|
|
3193
3329
|
//#region src/native-mobile.ts
|
|
3194
3330
|
/**
|
|
@@ -3236,6 +3372,8 @@ window.__ModuleLoader__.load({
|
|
|
3236
3372
|
.dsh-mobile-settings_fontFamily > span { flex:1 1 100px; }
|
|
3237
3373
|
.dsh-mobile-settings_fontFamily input { box-sizing:border-box; flex:1 1 180px; width:100%; min-width:0; min-height:48px; padding:8px 12px; border:0.5px solid var(--dsw-alias-border-l2); border-radius:10px; background:var(--dsw-alias-bg-module-platform); color:var(--dsw-alias-label-primary); font:inherit; }
|
|
3238
3374
|
.dsh-mobile-settings_fontFamily input:focus-visible,.dsh-mobile-settings_fontDetails > summary:focus-visible { outline:2px solid var(--dsw-alias-label-primary-bluish,#2563eb); outline-offset:2px; }
|
|
3375
|
+
html.dsh-native-mobile-active [data-dsh-mobile-settings-header] { box-sizing:border-box !important; height:56px !important; min-height:56px !important; padding:4px 12px !important; }
|
|
3376
|
+
html.dsh-native-mobile-active [data-dsh-mobile-settings-header] [class*="_close"] { box-sizing:border-box !important; flex:none !important; width:48px !important; min-width:48px !important; height:48px !important; min-height:48px !important; }
|
|
3239
3377
|
/* The mobile row replaces the Host-backed font control, not the theme selector. */
|
|
3240
3378
|
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; }
|
|
3241
3379
|
@media ${NATIVE_MOBILE_OVERLAY_QUERY} {
|
|
@@ -3305,8 +3443,6 @@ window.__ModuleLoader__.load({
|
|
|
3305
3443
|
[data-dsh-mobile-settings-list] [class*="_navCell"] { flex:0 0 auto !important; min-width:max-content !important; height:44px !important; padding:10px 12px !important; }
|
|
3306
3444
|
[data-dsh-mobile-settings-list] [aria-current="true"] { border-color:transparent !important; outline:0 !important; box-shadow:none !important; }
|
|
3307
3445
|
[data-dsh-mobile-settings-content] { flex:1 1 auto !important; width:100% !important; min-height:0 !important; }
|
|
3308
|
-
[data-dsh-mobile-settings-header] { height:48px !important; min-height:48px !important; padding:10px 12px 6px !important; }
|
|
3309
|
-
[data-dsh-mobile-settings-header] [class*="_close"] { width:36px !important; height:36px !important; }
|
|
3310
3446
|
[data-dsh-mobile-settings-options] { box-sizing:border-box !important; width:100% !important; padding:4px 16px max(24px,env(safe-area-inset-bottom)) !important; overflow-x:hidden !important; }
|
|
3311
3447
|
[data-dsh-mobile-settings-options] > * { width:100% !important; min-width:0 !important; }
|
|
3312
3448
|
[data-dsh-mobile-settings-options] [data-slot="settings.general.item"] > [class*="_row"] { flex-direction:column !important; align-items:stretch !important; gap:12px !important; }
|
|
@@ -3609,19 +3745,66 @@ window.__ModuleLoader__.load({
|
|
|
3609
3745
|
}
|
|
3610
3746
|
};
|
|
3611
3747
|
}
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3748
|
+
/** A read-only, file-only transfer for engines without usable native constructors. */
|
|
3749
|
+
function controlledFileTransfer(files, initialDropEffect) {
|
|
3750
|
+
const snapshot = [...files];
|
|
3751
|
+
const fileList = Object.assign(snapshot, { item: (index) => snapshot[index] ?? null });
|
|
3752
|
+
const items = Object.assign(snapshot.map((file) => ({
|
|
3753
|
+
kind: "file",
|
|
3754
|
+
type: file.type,
|
|
3755
|
+
getAsFile: () => file,
|
|
3756
|
+
getAsString: () => {},
|
|
3757
|
+
webkitGetAsEntry: () => null
|
|
3758
|
+
})), {
|
|
3759
|
+
add: () => null,
|
|
3760
|
+
clear: () => {},
|
|
3761
|
+
remove: () => {}
|
|
3616
3762
|
});
|
|
3617
|
-
|
|
3763
|
+
Object.freeze(fileList);
|
|
3764
|
+
Object.freeze(items);
|
|
3765
|
+
return {
|
|
3618
3766
|
dropEffect: initialDropEffect,
|
|
3619
3767
|
effectAllowed: "copy",
|
|
3620
|
-
files:
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3768
|
+
files: fileList,
|
|
3769
|
+
items,
|
|
3770
|
+
types: Object.freeze(["Files"]),
|
|
3771
|
+
clearData: () => {},
|
|
3772
|
+
getData: () => "",
|
|
3773
|
+
setData: () => {},
|
|
3774
|
+
setDragImage: () => {}
|
|
3775
|
+
};
|
|
3776
|
+
}
|
|
3777
|
+
function nativeFileTransfer(files, initialDropEffect) {
|
|
3778
|
+
const transfer = new DataTransfer();
|
|
3779
|
+
for (const file of files) if (transfer.items.add(file) === null) throw new Error("file_transfer_unavailable");
|
|
3780
|
+
if (transfer.files.length !== files.length) throw new Error("file_transfer_unavailable");
|
|
3781
|
+
transfer.effectAllowed = "copy";
|
|
3782
|
+
transfer.dropEffect = "copy";
|
|
3783
|
+
if (transfer.effectAllowed !== "copy" || transfer.dropEffect !== "copy") throw new Error("file_transfer_unavailable");
|
|
3784
|
+
transfer.dropEffect = initialDropEffect;
|
|
3785
|
+
if (transfer.dropEffect !== initialDropEffect) throw new Error("file_transfer_unavailable");
|
|
3786
|
+
return transfer;
|
|
3787
|
+
}
|
|
3788
|
+
function controlledFileDragEvent(type, files, initialDropEffect) {
|
|
3789
|
+
let transfer;
|
|
3790
|
+
if (typeof DataTransfer === "function") try {
|
|
3791
|
+
transfer = nativeFileTransfer(files, initialDropEffect);
|
|
3792
|
+
} catch (_error) {
|
|
3793
|
+
transfer = controlledFileTransfer(files, initialDropEffect);
|
|
3794
|
+
}
|
|
3795
|
+
else transfer = controlledFileTransfer(files, initialDropEffect);
|
|
3796
|
+
if (typeof DragEvent === "function") try {
|
|
3797
|
+
const event = new DragEvent(type, {
|
|
3798
|
+
bubbles: true,
|
|
3799
|
+
cancelable: true,
|
|
3800
|
+
dataTransfer: transfer
|
|
3801
|
+
});
|
|
3802
|
+
if (event.dataTransfer !== null) return event;
|
|
3803
|
+
} catch (_error) {}
|
|
3804
|
+
return Object.assign(new Event(type, {
|
|
3805
|
+
bubbles: true,
|
|
3806
|
+
cancelable: true
|
|
3807
|
+
}), { dataTransfer: transfer });
|
|
3625
3808
|
}
|
|
3626
3809
|
/** Ask the official document dragover listener whether the current composer accepts files. */
|
|
3627
3810
|
function preflightComposerImageDrop(target, files) {
|
|
@@ -4071,7 +4254,6 @@ window.__ModuleLoader__.load({
|
|
|
4071
4254
|
const preflightFile = new File([], "dsh-mobile-preflight.png", { type: "image/png" });
|
|
4072
4255
|
const canAcceptComposerDrop = () => preflightComposerImageDrop(document, [preflightFile]);
|
|
4073
4256
|
const mediaPickerAbortController = new AbortController();
|
|
4074
|
-
const browserPickerCleanups = /* @__PURE__ */ new Set();
|
|
4075
4257
|
let boundEnterEditor = null;
|
|
4076
4258
|
let boundEnterCard = null;
|
|
4077
4259
|
let disposeEnterBinding;
|
|
@@ -4118,63 +4300,18 @@ window.__ModuleLoader__.load({
|
|
|
4118
4300
|
};
|
|
4119
4301
|
const deliverImages = (files, context) => {
|
|
4120
4302
|
if (files.length === 0 || !mediaRequestIsCurrent(context)) return;
|
|
4121
|
-
dispatchComposerImageDrop(document, files);
|
|
4303
|
+
if (!dispatchComposerImageDrop(document, files)) showMediaToast(label("Allegati immagine non disponibili", "Image attachments are unavailable", "图片附件不可用"));
|
|
4122
4304
|
};
|
|
4123
4305
|
const launchBrowserCamera = (context) => {
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
const scheduleCleanup = () => {
|
|
4134
|
-
if (!cleaned && cleanupTimer === 0) cleanupTimer = window.setTimeout(cleanup, 1e3);
|
|
4135
|
-
};
|
|
4136
|
-
const onVisibilityChange = () => {
|
|
4137
|
-
if (document.visibilityState === "visible") scheduleCleanup();
|
|
4138
|
-
};
|
|
4139
|
-
const onChange = () => {
|
|
4140
|
-
if (cleaned) return;
|
|
4141
|
-
const files = input.files === null ? [] : [...input.files];
|
|
4142
|
-
cleanup();
|
|
4143
|
-
deliverImages(files, context);
|
|
4144
|
-
};
|
|
4145
|
-
const cleanup = () => {
|
|
4146
|
-
if (cleaned) return;
|
|
4147
|
-
cleaned = true;
|
|
4148
|
-
if (cleanupTimer !== 0) window.clearTimeout(cleanupTimer);
|
|
4149
|
-
if (watchdogTimer !== 0) window.clearTimeout(watchdogTimer);
|
|
4150
|
-
cleanupTimer = 0;
|
|
4151
|
-
watchdogTimer = 0;
|
|
4152
|
-
input.removeEventListener("change", onChange);
|
|
4153
|
-
input.removeEventListener("cancel", cleanup);
|
|
4154
|
-
window.removeEventListener("focus", scheduleCleanup);
|
|
4155
|
-
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
4156
|
-
signal.removeEventListener("abort", cleanup);
|
|
4157
|
-
browserPickerCleanups.delete(cleanup);
|
|
4158
|
-
input.remove();
|
|
4159
|
-
};
|
|
4160
|
-
input.addEventListener("change", onChange);
|
|
4161
|
-
input.addEventListener("cancel", cleanup);
|
|
4162
|
-
window.addEventListener("focus", scheduleCleanup);
|
|
4163
|
-
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
4164
|
-
signal.addEventListener("abort", cleanup, { once: true });
|
|
4165
|
-
watchdogTimer = window.setTimeout(cleanup, 3e5);
|
|
4166
|
-
browserPickerCleanups.add(cleanup);
|
|
4167
|
-
if (signal.aborted) {
|
|
4168
|
-
cleanup();
|
|
4169
|
-
return;
|
|
4170
|
-
}
|
|
4171
|
-
try {
|
|
4172
|
-
document.body.append(input);
|
|
4173
|
-
input.click();
|
|
4174
|
-
} catch {
|
|
4175
|
-
cleanup();
|
|
4176
|
-
if (mediaRequestIsCurrent(context)) showMediaToast(label("Impossibile aprire la fotocamera", "Could not open the camera", "无法打开相机"));
|
|
4177
|
-
}
|
|
4306
|
+
pickBrowserFiles({
|
|
4307
|
+
accept: "image/*",
|
|
4308
|
+
capture: "environment",
|
|
4309
|
+
signal: mediaPickerAbortController.signal
|
|
4310
|
+
}).then((files) => {
|
|
4311
|
+
if (files !== void 0) deliverImages(files, context);
|
|
4312
|
+
}).catch((_error) => {
|
|
4313
|
+
if (mediaRequestIsCurrent(context)) showMediaToast(label("Impossibile aggiungere la foto", "Could not attach the photo", "无法附加照片"));
|
|
4314
|
+
});
|
|
4178
4315
|
};
|
|
4179
4316
|
const dismissCommandMenu = () => {
|
|
4180
4317
|
(boundComposer?.querySelector("button[aria-haspopup=\"listbox\"][aria-expanded=\"true\"]"))?.click();
|
|
@@ -4632,7 +4769,6 @@ window.__ModuleLoader__.load({
|
|
|
4632
4769
|
mediaRequestGeneration += 1;
|
|
4633
4770
|
mediaPickerAbortController.abort();
|
|
4634
4771
|
restoreLanguageMarker();
|
|
4635
|
-
for (const cleanup of [...browserPickerCleanups]) cleanup();
|
|
4636
4772
|
disposeEnterBinding?.();
|
|
4637
4773
|
disposeBrowserComposerSoftEnter();
|
|
4638
4774
|
observer.disconnect();
|
|
@@ -4778,6 +4914,63 @@ window.__ModuleLoader__.load({
|
|
|
4778
4914
|
};
|
|
4779
4915
|
}
|
|
4780
4916
|
//#endregion
|
|
4917
|
+
//#region src/mobile-font-capabilities.ts
|
|
4918
|
+
const NO_ADVANCED_MOBILE_TYPOGRAPHY = Object.freeze({
|
|
4919
|
+
codeSize: false,
|
|
4920
|
+
textFamily: false,
|
|
4921
|
+
codeFamily: false
|
|
4922
|
+
});
|
|
4923
|
+
/**
|
|
4924
|
+
* Probe computed fonts derived from DSH's public Markdown tokens. Input values are
|
|
4925
|
+
* restored synchronously before painting; merely defining the input variable is not support.
|
|
4926
|
+
* @param document - Live page document in the current browsing context.
|
|
4927
|
+
* @returns Independently observed advanced typography support.
|
|
4928
|
+
*/
|
|
4929
|
+
function readMobileTypographyCapabilities(document) {
|
|
4930
|
+
const style = document.body.style;
|
|
4931
|
+
const text = document.createElement("span");
|
|
4932
|
+
const code = document.createElement("span");
|
|
4933
|
+
for (const probe of [text, code]) {
|
|
4934
|
+
probe.setAttribute("aria-hidden", "true");
|
|
4935
|
+
probe.style.cssText = "position:fixed;visibility:hidden;pointer-events:none;contain:strict;width:0;height:0;overflow:hidden";
|
|
4936
|
+
}
|
|
4937
|
+
text.style.font = "var(--dsw-font-markdown-base, 14px sans-serif)";
|
|
4938
|
+
code.style.font = "var(--dsw-font-markdown-code-block, 11px monospace)";
|
|
4939
|
+
document.body.append(text, code);
|
|
4940
|
+
const previous = /* @__PURE__ */ new Map();
|
|
4941
|
+
const sample = (property, value) => {
|
|
4942
|
+
if (!previous.has(property)) previous.set(property, {
|
|
4943
|
+
value: style.getPropertyValue(property),
|
|
4944
|
+
priority: style.getPropertyPriority(property)
|
|
4945
|
+
});
|
|
4946
|
+
style.setProperty(property, value);
|
|
4947
|
+
};
|
|
4948
|
+
try {
|
|
4949
|
+
sample("--dsh-code-font-size", "23px");
|
|
4950
|
+
const firstSize = getComputedStyle(code).fontSize;
|
|
4951
|
+
sample("--dsh-code-font-size", "24px");
|
|
4952
|
+
const secondSize = getComputedStyle(code).fontSize;
|
|
4953
|
+
sample("--dsh-font-family-text", "\"dsh-mobile-text-capability-a\"");
|
|
4954
|
+
const firstTextFamily = getComputedStyle(text).fontFamily;
|
|
4955
|
+
sample("--dsh-font-family-text", "\"dsh-mobile-text-capability-b\"");
|
|
4956
|
+
const secondTextFamily = getComputedStyle(text).fontFamily;
|
|
4957
|
+
sample("--dsh-font-family-code", "\"dsh-mobile-code-capability-a\"");
|
|
4958
|
+
const firstCodeFamily = getComputedStyle(code).fontFamily;
|
|
4959
|
+
sample("--dsh-font-family-code", "\"dsh-mobile-code-capability-b\"");
|
|
4960
|
+
const secondCodeFamily = getComputedStyle(code).fontFamily;
|
|
4961
|
+
return Object.freeze({
|
|
4962
|
+
codeSize: firstSize === "23px" && secondSize === "24px",
|
|
4963
|
+
textFamily: firstTextFamily.includes("dsh-mobile-text-capability-a") && secondTextFamily.includes("dsh-mobile-text-capability-b"),
|
|
4964
|
+
codeFamily: firstCodeFamily.includes("dsh-mobile-code-capability-a") && secondCodeFamily.includes("dsh-mobile-code-capability-b")
|
|
4965
|
+
});
|
|
4966
|
+
} finally {
|
|
4967
|
+
for (const [property, value] of previous) if (value.value === "") style.removeProperty(property);
|
|
4968
|
+
else style.setProperty(property, value.value, value.priority);
|
|
4969
|
+
text.remove();
|
|
4970
|
+
code.remove();
|
|
4971
|
+
}
|
|
4972
|
+
}
|
|
4973
|
+
//#endregion
|
|
4781
4974
|
//#region src/mobile-font.ts
|
|
4782
4975
|
/** Device-local typography for authenticated mobile pages, without Host settings writes. */
|
|
4783
4976
|
const MOBILE_FONT_STORAGE_KEY = "dsh-mobile.content-font-size.v1";
|
|
@@ -4878,6 +5071,8 @@ window.__ModuleLoader__.load({
|
|
|
4878
5071
|
storage;
|
|
4879
5072
|
typography = defaultTypography();
|
|
4880
5073
|
listeners = /* @__PURE__ */ new Set();
|
|
5074
|
+
capabilities = NO_ADVANCED_MOBILE_TYPOGRAPHY;
|
|
5075
|
+
capabilityListeners = /* @__PURE__ */ new Set();
|
|
4881
5076
|
constructor(storage) {
|
|
4882
5077
|
this.storage = storage;
|
|
4883
5078
|
let legacySize = 16;
|
|
@@ -4897,6 +5092,19 @@ window.__ModuleLoader__.load({
|
|
|
4897
5092
|
this.listeners.delete(listener);
|
|
4898
5093
|
};
|
|
4899
5094
|
};
|
|
5095
|
+
getCapabilitiesSnapshot = () => this.capabilities;
|
|
5096
|
+
subscribeCapabilities = (listener) => {
|
|
5097
|
+
this.capabilityListeners.add(listener);
|
|
5098
|
+
return () => {
|
|
5099
|
+
this.capabilityListeners.delete(listener);
|
|
5100
|
+
};
|
|
5101
|
+
};
|
|
5102
|
+
/** Update observed theme support without changing stored values or Host settings. */
|
|
5103
|
+
updateCapabilities(capabilities) {
|
|
5104
|
+
if (this.capabilities.codeSize === capabilities.codeSize && this.capabilities.textFamily === capabilities.textFamily && this.capabilities.codeFamily === capabilities.codeFamily) return;
|
|
5105
|
+
this.capabilities = Object.freeze({ ...capabilities });
|
|
5106
|
+
for (const listener of this.capabilityListeners) listener();
|
|
5107
|
+
}
|
|
4900
5108
|
/** Apply a user-selected size locally; never send it to DSH's settings RPC. */
|
|
4901
5109
|
set(size) {
|
|
4902
5110
|
this.setRoleSize("text", size);
|
|
@@ -4931,6 +5139,23 @@ window.__ModuleLoader__.load({
|
|
|
4931
5139
|
if (MOBILE_FONT_ROLES.every((role) => this.typography.sizes[role] === next.sizes[role] && this.typography.families[role] === "")) return;
|
|
4932
5140
|
this.persist(next);
|
|
4933
5141
|
}
|
|
5142
|
+
/** Reset visible supported controls while retaining preferences an older theme cannot consume. */
|
|
5143
|
+
resetSupported() {
|
|
5144
|
+
const defaults = defaultTypography();
|
|
5145
|
+
const sizes = {
|
|
5146
|
+
text: defaults.sizes.text,
|
|
5147
|
+
code: this.capabilities.codeSize ? defaults.sizes.code : this.typography.sizes.code
|
|
5148
|
+
};
|
|
5149
|
+
const families = {
|
|
5150
|
+
text: this.capabilities.textFamily ? "" : this.typography.families.text,
|
|
5151
|
+
code: this.capabilities.codeFamily ? "" : this.typography.families.code
|
|
5152
|
+
};
|
|
5153
|
+
if (MOBILE_FONT_ROLES.every((role) => this.typography.sizes[role] === sizes[role] && this.typography.families[role] === families[role])) return;
|
|
5154
|
+
this.persist({
|
|
5155
|
+
sizes: Object.freeze(sizes),
|
|
5156
|
+
families: Object.freeze(families)
|
|
5157
|
+
});
|
|
5158
|
+
}
|
|
4934
5159
|
persist(typography) {
|
|
4935
5160
|
this.typography = Object.freeze(typography);
|
|
4936
5161
|
try {
|
|
@@ -4942,7 +5167,11 @@ window.__ModuleLoader__.load({
|
|
|
4942
5167
|
for (const listener of this.listeners) listener();
|
|
4943
5168
|
}
|
|
4944
5169
|
};
|
|
4945
|
-
/**
|
|
5170
|
+
/**
|
|
5171
|
+
* Keep mobile typography independent from Host theme changes, including body-level portals.
|
|
5172
|
+
* Recheck head-owned stylesheets and body presentation attributes, not conversation DOM updates.
|
|
5173
|
+
* Arbitrary stylesheets inserted into body are not observed.
|
|
5174
|
+
*/
|
|
4946
5175
|
function installMobileFontPreference() {
|
|
4947
5176
|
let storage = null;
|
|
4948
5177
|
try {
|
|
@@ -4958,11 +5187,26 @@ window.__ModuleLoader__.load({
|
|
|
4958
5187
|
const owned = /* @__PURE__ */ new Map();
|
|
4959
5188
|
const apply = () => {
|
|
4960
5189
|
const typography = preference.getTypographySnapshot();
|
|
5190
|
+
const capabilities = preference.getCapabilitiesSnapshot();
|
|
4961
5191
|
for (const role of MOBILE_FONT_ROLES) for (const [property, value] of [[roleSpecs[role].variable, `${typography.sizes[role]}px`], [`--dsh-font-family-${role}`, typography.families[role]]]) {
|
|
4962
5192
|
const current = style.getPropertyValue(property);
|
|
4963
5193
|
const priority = style.getPropertyPriority(property);
|
|
4964
5194
|
const last = owned.get(property);
|
|
4965
|
-
if (
|
|
5195
|
+
if (!(property === roleSpecs[role].variable ? role === "text" || capabilities.codeSize : role === "text" ? capabilities.textFamily : capabilities.codeFamily)) {
|
|
5196
|
+
if (last !== void 0) {
|
|
5197
|
+
if (current === last.value && priority === last.priority) {
|
|
5198
|
+
const foreign = previous.get(property);
|
|
5199
|
+
if (foreign.value === "") style.removeProperty(property);
|
|
5200
|
+
else style.setProperty(property, foreign.value, foreign.priority);
|
|
5201
|
+
} else previous.set(property, {
|
|
5202
|
+
value: current,
|
|
5203
|
+
priority
|
|
5204
|
+
});
|
|
5205
|
+
owned.delete(property);
|
|
5206
|
+
}
|
|
5207
|
+
continue;
|
|
5208
|
+
}
|
|
5209
|
+
if (last === void 0 || current !== last.value || priority !== last.priority) previous.set(property, {
|
|
4966
5210
|
value: current,
|
|
4967
5211
|
priority
|
|
4968
5212
|
});
|
|
@@ -4975,17 +5219,55 @@ window.__ModuleLoader__.load({
|
|
|
4975
5219
|
else style.setProperty(property, value);
|
|
4976
5220
|
}
|
|
4977
5221
|
};
|
|
4978
|
-
|
|
4979
|
-
const
|
|
4980
|
-
const
|
|
5222
|
+
let lastBodyState = "";
|
|
5223
|
+
const bodyState = () => `${style.cssText}|${document.body.className}|${document.body.getAttribute("data-ds-dark-theme")}`;
|
|
5224
|
+
const applyAndRemember = () => {
|
|
5225
|
+
apply();
|
|
5226
|
+
lastBodyState = bodyState();
|
|
5227
|
+
};
|
|
5228
|
+
const refresh = () => {
|
|
5229
|
+
preference.updateCapabilities(readMobileTypographyCapabilities(document));
|
|
5230
|
+
applyAndRemember();
|
|
5231
|
+
};
|
|
5232
|
+
refresh();
|
|
5233
|
+
const unsubscribe = preference.subscribe(applyAndRemember);
|
|
5234
|
+
const observer = new MutationObserver(() => {
|
|
5235
|
+
if (bodyState() !== lastBodyState) refresh();
|
|
5236
|
+
});
|
|
4981
5237
|
observer.observe(document.body, {
|
|
4982
5238
|
attributes: true,
|
|
4983
|
-
attributeFilter: [
|
|
5239
|
+
attributeFilter: [
|
|
5240
|
+
"style",
|
|
5241
|
+
"class",
|
|
5242
|
+
"data-ds-dark-theme"
|
|
5243
|
+
]
|
|
5244
|
+
});
|
|
5245
|
+
const themeObserver = new MutationObserver((records) => {
|
|
5246
|
+
const isSheet = (node) => node instanceof Element && (node.matches("style,link[rel=\"stylesheet\"]") || node.querySelector("style,link[rel=\"stylesheet\"]") !== null);
|
|
5247
|
+
if (records.some((record) => record.target instanceof Element && record.target.closest("style,link[rel=\"stylesheet\"]") !== null || [...record.addedNodes, ...record.removedNodes].some(isSheet))) refresh();
|
|
5248
|
+
});
|
|
5249
|
+
themeObserver.observe(document.head, {
|
|
5250
|
+
subtree: true,
|
|
5251
|
+
childList: true,
|
|
5252
|
+
characterData: true,
|
|
5253
|
+
attributes: true,
|
|
5254
|
+
attributeFilter: [
|
|
5255
|
+
"href",
|
|
5256
|
+
"media",
|
|
5257
|
+
"disabled",
|
|
5258
|
+
"rel"
|
|
5259
|
+
]
|
|
4984
5260
|
});
|
|
5261
|
+
const sheetLoaded = (event) => {
|
|
5262
|
+
if (event.target instanceof Element && event.target.matches("link[rel=\"stylesheet\"]")) refresh();
|
|
5263
|
+
};
|
|
5264
|
+
document.head.addEventListener("load", sheetLoaded, true);
|
|
4985
5265
|
return {
|
|
4986
5266
|
preference,
|
|
4987
5267
|
dispose: () => {
|
|
4988
5268
|
observer.disconnect();
|
|
5269
|
+
themeObserver.disconnect();
|
|
5270
|
+
document.head.removeEventListener("load", sheetLoaded, true);
|
|
4989
5271
|
unsubscribe();
|
|
4990
5272
|
for (const [property, value] of previous) {
|
|
4991
5273
|
const last = owned.get(property);
|
|
@@ -5008,7 +5290,8 @@ window.__ModuleLoader__.load({
|
|
|
5008
5290
|
familyHint: "填写设备已有的字体,多个名称用逗号分隔;留空使用默认字体。",
|
|
5009
5291
|
codeSize: "代码字号",
|
|
5010
5292
|
textFamily: "正文字体",
|
|
5011
|
-
codeFamily: "代码字体"
|
|
5293
|
+
codeFamily: "代码字体",
|
|
5294
|
+
limited: "只显示当前 DSH 主题支持的字体选项;其他已保存的设置会保留。"
|
|
5012
5295
|
},
|
|
5013
5296
|
en: {
|
|
5014
5297
|
title: "Mobile font size",
|
|
@@ -5021,7 +5304,8 @@ window.__ModuleLoader__.load({
|
|
|
5021
5304
|
familyHint: "Use fonts already on your device, separated by commas. Leave empty for the default.",
|
|
5022
5305
|
codeSize: "Code font size",
|
|
5023
5306
|
textFamily: "Text font",
|
|
5024
|
-
codeFamily: "Code font"
|
|
5307
|
+
codeFamily: "Code font",
|
|
5308
|
+
limited: "Only font options supported by the current DSH theme are shown. Other saved preferences are retained."
|
|
5025
5309
|
},
|
|
5026
5310
|
it: {
|
|
5027
5311
|
title: "Dimensione testo mobile",
|
|
@@ -5034,7 +5318,8 @@ window.__ModuleLoader__.load({
|
|
|
5034
5318
|
familyHint: "Usa caratteri presenti sul dispositivo, separati da virgole. Lascia vuoto per il predefinito.",
|
|
5035
5319
|
codeSize: "Dimensione codice",
|
|
5036
5320
|
textFamily: "Carattere testo",
|
|
5037
|
-
codeFamily: "Carattere codice"
|
|
5321
|
+
codeFamily: "Carattere codice",
|
|
5322
|
+
limited: "Sono mostrate solo le opzioni supportate dal tema DSH corrente. Le altre preferenze salvate vengono mantenute."
|
|
5038
5323
|
}
|
|
5039
5324
|
};
|
|
5040
5325
|
function FontFamilyInput({ preference, role, value, label, placeholder }) {
|
|
@@ -5062,6 +5347,7 @@ window.__ModuleLoader__.load({
|
|
|
5062
5347
|
/** General settings row matching the existing Mobile controls and DSH theme tokens. */
|
|
5063
5348
|
function MobileFontSizeRow({ preference, locale }) {
|
|
5064
5349
|
const typography = (0, react.useSyncExternalStore)(preference.subscribe, preference.getTypographySnapshot);
|
|
5350
|
+
const capabilities = (0, react.useSyncExternalStore)(preference.subscribeCapabilities, preference.getCapabilitiesSnapshot);
|
|
5065
5351
|
const copy = messages$1[locale];
|
|
5066
5352
|
const controls = (role, title) => {
|
|
5067
5353
|
const size = typography.sizes[role];
|
|
@@ -5092,18 +5378,22 @@ window.__ModuleLoader__.load({
|
|
|
5092
5378
|
return (0, react.createElement)("div", {
|
|
5093
5379
|
lang: locale,
|
|
5094
5380
|
"data-mobile-font-setting": true
|
|
5095
|
-
}, (0, react.createElement)("div", { className: "dsh-mobile-settings_row" }, (0, react.createElement)("div", { className: "dsh-mobile-settings_rowText" }, (0, react.createElement)("div", { className: "dsh-mobile-settings_title" }, copy.title), (0, react.createElement)("div", { className: "dsh-mobile-settings_desc" }, copy.description)), controls("text", copy.title)),
|
|
5381
|
+
}, (0, react.createElement)("div", { className: "dsh-mobile-settings_row" }, (0, react.createElement)("div", { className: "dsh-mobile-settings_rowText" }, (0, react.createElement)("div", { className: "dsh-mobile-settings_title" }, copy.title), (0, react.createElement)("div", { className: "dsh-mobile-settings_desc" }, copy.description)), controls("text", copy.title)), !Object.values(capabilities).every(Boolean) && (0, react.createElement)("p", {
|
|
5382
|
+
className: "dsh-mobile-settings_desc",
|
|
5383
|
+
"data-mobile-font-limited": true
|
|
5384
|
+
}, copy.limited), Object.values(capabilities).some(Boolean) && (0, react.createElement)("details", { className: "dsh-mobile-settings_fontDetails" }, (0, react.createElement)("summary", null, copy.more), (capabilities.textFamily || capabilities.codeFamily) && (0, react.createElement)("p", { className: "dsh-mobile-settings_desc" }, copy.familyHint), capabilities.codeSize && (0, react.createElement)("div", { className: "dsh-mobile-settings_row" }, (0, react.createElement)("span", { className: "dsh-mobile-settings_rowText dsh-mobile-settings_title" }, copy.codeSize), controls("code", copy.codeSize)), ...MOBILE_FONT_ROLES.filter((role) => role === "text" ? capabilities.textFamily : capabilities.codeFamily).map((role) => (0, react.createElement)(FontFamilyInput, {
|
|
5096
5385
|
key: role,
|
|
5097
5386
|
preference,
|
|
5098
5387
|
role,
|
|
5099
5388
|
value: typography.families[role],
|
|
5100
5389
|
label: role === "text" ? copy.textFamily : copy.codeFamily,
|
|
5101
5390
|
placeholder: copy.defaultFamily
|
|
5102
|
-
})), (0, react.createElement)("button", {
|
|
5391
|
+
}))), (0, react.createElement)("div", { className: "dsh-mobile-settings_row" }, (0, react.createElement)("button", {
|
|
5103
5392
|
type: "button",
|
|
5104
5393
|
className: "dsh-mobile-settings_selector",
|
|
5394
|
+
"data-mobile-font-reset": true,
|
|
5105
5395
|
onClick: () => {
|
|
5106
|
-
preference.
|
|
5396
|
+
preference.resetSupported();
|
|
5107
5397
|
}
|
|
5108
5398
|
}, copy.reset)));
|
|
5109
5399
|
}
|
|
@@ -5326,6 +5616,116 @@ window.__ModuleLoader__.load({
|
|
|
5326
5616
|
}, copy.reset)));
|
|
5327
5617
|
}
|
|
5328
5618
|
//#endregion
|
|
5619
|
+
//#region src/mobile-native-settings-action.ts
|
|
5620
|
+
/** Own capability discovery and explicit calls; native actions retain the App's own timeout. */
|
|
5621
|
+
var MobileNativeSettingsActionController = class {
|
|
5622
|
+
action;
|
|
5623
|
+
getBridge;
|
|
5624
|
+
snapshot = Object.freeze({
|
|
5625
|
+
supported: false,
|
|
5626
|
+
state: "idle"
|
|
5627
|
+
});
|
|
5628
|
+
listeners = /* @__PURE__ */ new Set();
|
|
5629
|
+
bridge;
|
|
5630
|
+
active = false;
|
|
5631
|
+
generation = 0;
|
|
5632
|
+
probe = 0;
|
|
5633
|
+
attempt = 0;
|
|
5634
|
+
constructor(action, getBridge) {
|
|
5635
|
+
this.action = action;
|
|
5636
|
+
this.getBridge = getBridge;
|
|
5637
|
+
}
|
|
5638
|
+
getSnapshot = () => this.snapshot;
|
|
5639
|
+
subscribe = (listener) => {
|
|
5640
|
+
this.listeners.add(listener);
|
|
5641
|
+
return () => {
|
|
5642
|
+
this.listeners.delete(listener);
|
|
5643
|
+
};
|
|
5644
|
+
};
|
|
5645
|
+
start() {
|
|
5646
|
+
this.active = true;
|
|
5647
|
+
this.publish({
|
|
5648
|
+
supported: false,
|
|
5649
|
+
state: "idle"
|
|
5650
|
+
});
|
|
5651
|
+
this.refresh();
|
|
5652
|
+
}
|
|
5653
|
+
stop() {
|
|
5654
|
+
this.active = false;
|
|
5655
|
+
this.generation += 1;
|
|
5656
|
+
this.probe += 1;
|
|
5657
|
+
this.attempt += 1;
|
|
5658
|
+
this.bridge = void 0;
|
|
5659
|
+
}
|
|
5660
|
+
/** Recheck support without replaying or invalidating an in-flight call to the same adapter. */
|
|
5661
|
+
refresh = () => {
|
|
5662
|
+
if (!this.active) return;
|
|
5663
|
+
const bridge = this.getBridge();
|
|
5664
|
+
const probe = ++this.probe;
|
|
5665
|
+
if (bridge !== this.bridge) {
|
|
5666
|
+
this.bridge = bridge;
|
|
5667
|
+
this.generation += 1;
|
|
5668
|
+
this.attempt += 1;
|
|
5669
|
+
this.publish({
|
|
5670
|
+
supported: false,
|
|
5671
|
+
state: "idle"
|
|
5672
|
+
});
|
|
5673
|
+
}
|
|
5674
|
+
if (bridge === void 0) return;
|
|
5675
|
+
const generation = this.generation;
|
|
5676
|
+
const settle = (supported) => {
|
|
5677
|
+
if (!this.current(bridge, generation) || probe !== this.probe) return;
|
|
5678
|
+
if (!supported) this.attempt += 1;
|
|
5679
|
+
this.publish({
|
|
5680
|
+
supported,
|
|
5681
|
+
state: supported ? this.snapshot.state : "idle"
|
|
5682
|
+
});
|
|
5683
|
+
};
|
|
5684
|
+
Promise.resolve().then(() => {
|
|
5685
|
+
if (!this.current(bridge, generation) || probe !== this.probe) return void 0;
|
|
5686
|
+
return bridge.capabilities();
|
|
5687
|
+
}).then((capabilities) => {
|
|
5688
|
+
settle(capabilities?.includes(this.action) === true);
|
|
5689
|
+
}, () => {
|
|
5690
|
+
settle(false);
|
|
5691
|
+
});
|
|
5692
|
+
};
|
|
5693
|
+
/** Send at most one explicit action; late replies cannot update a replaced or unmounted row. */
|
|
5694
|
+
invoke = () => {
|
|
5695
|
+
const bridge = this.bridge;
|
|
5696
|
+
if (bridge === void 0 || !this.snapshot.supported || !this.current(bridge, this.generation) || this.snapshot.state === "busy" || this.action === "mobile.switch-computer" && this.snapshot.state === "success") return;
|
|
5697
|
+
const generation = this.generation;
|
|
5698
|
+
const attempt = ++this.attempt;
|
|
5699
|
+
this.publish({
|
|
5700
|
+
supported: true,
|
|
5701
|
+
state: "busy"
|
|
5702
|
+
});
|
|
5703
|
+
const current = () => this.current(bridge, generation) && attempt === this.attempt && this.snapshot.supported;
|
|
5704
|
+
Promise.resolve().then(() => {
|
|
5705
|
+
if (!current()) return void 0;
|
|
5706
|
+
return bridge.invoke(this.action, {});
|
|
5707
|
+
}).then(() => {
|
|
5708
|
+
if (current()) this.publish({
|
|
5709
|
+
supported: true,
|
|
5710
|
+
state: "success"
|
|
5711
|
+
});
|
|
5712
|
+
}, () => {
|
|
5713
|
+
if (current()) this.publish({
|
|
5714
|
+
supported: true,
|
|
5715
|
+
state: "error"
|
|
5716
|
+
});
|
|
5717
|
+
});
|
|
5718
|
+
};
|
|
5719
|
+
current(bridge, generation) {
|
|
5720
|
+
return this.active && generation === this.generation && this.bridge === bridge && this.getBridge() === bridge;
|
|
5721
|
+
}
|
|
5722
|
+
publish(snapshot) {
|
|
5723
|
+
if (snapshot.supported === this.snapshot.supported && snapshot.state === this.snapshot.state) return;
|
|
5724
|
+
this.snapshot = Object.freeze(snapshot);
|
|
5725
|
+
for (const listener of this.listeners) listener();
|
|
5726
|
+
}
|
|
5727
|
+
};
|
|
5728
|
+
//#endregion
|
|
5329
5729
|
//#region src/local-admin-host.ts
|
|
5330
5730
|
/** Browser-safe desktop-admin Host checks. Do not import Node APIs here. */
|
|
5331
5731
|
const DESKTOP_ADMIN_HEADER = "x-dsh-mobile-desktop-admin";
|
|
@@ -5386,65 +5786,282 @@ window.__ModuleLoader__.load({
|
|
|
5386
5786
|
return (protocol === "dsh-app:" ? hostname === "app" : (protocol === "" || protocol === "http:" || protocol === "https:") && isLocalAdminHostname(hostname)) && frontend !== "dedicated" && !new URLSearchParams(query).has("dsh-mobile-preview");
|
|
5387
5787
|
}
|
|
5388
5788
|
//#endregion
|
|
5789
|
+
//#region src/client-module-presentation.ts
|
|
5790
|
+
/** Group boot-protected entries before considering their package namespace.
|
|
5791
|
+
* @param entry - Catalog entry with server-owned required and dependency fields.
|
|
5792
|
+
* @returns Display group, never an authorization or compatibility decision.
|
|
5793
|
+
*/
|
|
5794
|
+
function clientModuleGroup(entry) {
|
|
5795
|
+
if (entry.required) return "required";
|
|
5796
|
+
return entry.id.startsWith("@deepseek-ai/") ? "advanced" : "extensions";
|
|
5797
|
+
}
|
|
5798
|
+
/** Format the package leaf mechanically; the catalog has no product titles.
|
|
5799
|
+
* @param id - Exact module ID from the server catalog.
|
|
5800
|
+
* @returns Unscoped package words without guessed function or plugin ownership.
|
|
5801
|
+
*/
|
|
5802
|
+
function clientModuleDisplayName(id) {
|
|
5803
|
+
const leaf = id.startsWith("@") && id.includes("/") ? id.slice(id.indexOf("/") + 1) : id;
|
|
5804
|
+
return (leaf.replace(/^dsh-(?:client-(?:ui-)?)?/u, "") || leaf || id).replace(/[-_]+/gu, " ");
|
|
5805
|
+
}
|
|
5806
|
+
function matchesQuery(id, query) {
|
|
5807
|
+
const needle = query.trim().toLowerCase();
|
|
5808
|
+
return needle === "" || `${id}\n${clientModuleDisplayName(id)}`.toLowerCase().includes(needle);
|
|
5809
|
+
}
|
|
5810
|
+
/** Match the visible title or exact ID and the current draft loading choice.
|
|
5811
|
+
* @param entry - Catalog entry to consider.
|
|
5812
|
+
* @param excluded - Current draft exclusions, not a runtime loading observation.
|
|
5813
|
+
* @param query - User-entered search text.
|
|
5814
|
+
* @param filter - All, selected for loading, or selected for omission.
|
|
5815
|
+
* @returns Whether the row belongs in the current filtered list.
|
|
5816
|
+
*/
|
|
5817
|
+
function clientModuleMatches(entry, excluded, query, filter) {
|
|
5818
|
+
const omitted = !entry.required && excluded.has(entry.id);
|
|
5819
|
+
return matchesQuery(entry.id, query) && (filter === "all" || (filter === "excluded" ? omitted : !omitted));
|
|
5820
|
+
}
|
|
5821
|
+
/** Editable selections retain unknown server exclusions and never cascade dependencies. */
|
|
5822
|
+
var ClientModuleDraft = class {
|
|
5823
|
+
entries;
|
|
5824
|
+
initial;
|
|
5825
|
+
excluded;
|
|
5826
|
+
entriesById;
|
|
5827
|
+
constructor(entries, excludedIds) {
|
|
5828
|
+
this.entries = entries;
|
|
5829
|
+
this.initial = new Set(excludedIds);
|
|
5830
|
+
this.excluded = new Set(excludedIds);
|
|
5831
|
+
this.entriesById = new Map(entries.map((entry) => [entry.id, entry]));
|
|
5832
|
+
}
|
|
5833
|
+
/** @returns Number of changed loading choices, including hidden filtered rows. */
|
|
5834
|
+
get changeCount() {
|
|
5835
|
+
let count = 0;
|
|
5836
|
+
for (const id of this.initial) if (!this.excluded.has(id)) count += 1;
|
|
5837
|
+
for (const id of this.excluded) if (!this.initial.has(id)) count += 1;
|
|
5838
|
+
return count;
|
|
5839
|
+
}
|
|
5840
|
+
/** @returns A snapshot of exclusions suitable for authenticated saving. */
|
|
5841
|
+
get excludedIds() {
|
|
5842
|
+
return [...this.excluded];
|
|
5843
|
+
}
|
|
5844
|
+
/** @returns Retained exclusions that cannot be saved against the current catalog. */
|
|
5845
|
+
get invalidExclusions() {
|
|
5846
|
+
return [...this.excluded].flatMap((id) => {
|
|
5847
|
+
const entry = this.entriesById.get(id);
|
|
5848
|
+
return entry === void 0 ? [{
|
|
5849
|
+
id,
|
|
5850
|
+
reason: "missing"
|
|
5851
|
+
}] : entry.required ? [{
|
|
5852
|
+
id,
|
|
5853
|
+
reason: "required"
|
|
5854
|
+
}] : [];
|
|
5855
|
+
});
|
|
5856
|
+
}
|
|
5857
|
+
/** Explicitly remove an old exclusion; ordinary optional choices use setLoaded.
|
|
5858
|
+
* @param id - Exact ID of a retained missing or now-required exclusion.
|
|
5859
|
+
* @returns Whether this draft changed; no server request is made.
|
|
5860
|
+
*/
|
|
5861
|
+
clearInvalidExclusion(id) {
|
|
5862
|
+
const entry = this.entriesById.get(id);
|
|
5863
|
+
if (!this.initial.has(id) || entry !== void 0 && !entry.required) return false;
|
|
5864
|
+
return this.excluded.delete(id);
|
|
5865
|
+
}
|
|
5866
|
+
/** @returns Whether a catalog entry is selected for loading, not already loaded. */
|
|
5867
|
+
isLoaded(id) {
|
|
5868
|
+
return this.entriesById.get(id)?.required === true || !this.excluded.has(id);
|
|
5869
|
+
}
|
|
5870
|
+
/** Change one optional choice; required and unknown entries cannot be edited.
|
|
5871
|
+
* @param id - Exact catalog ID.
|
|
5872
|
+
* @param loaded - Whether to load the entry on the next open.
|
|
5873
|
+
*/
|
|
5874
|
+
setLoaded(id, loaded) {
|
|
5875
|
+
const entry = this.entriesById.get(id);
|
|
5876
|
+
if (entry === void 0 || entry.required) return;
|
|
5877
|
+
if (loaded) this.excluded.delete(id);
|
|
5878
|
+
else this.excluded.add(id);
|
|
5879
|
+
}
|
|
5880
|
+
/** Filter a draft without changing hidden choices.
|
|
5881
|
+
* @param query - Search input.
|
|
5882
|
+
* @param filter - Requested loading selection.
|
|
5883
|
+
* @returns Catalog entries in their original stable order.
|
|
5884
|
+
*/
|
|
5885
|
+
matchingEntries(query, filter) {
|
|
5886
|
+
return this.entries.filter((entry) => clientModuleMatches(entry, this.excluded, query, filter));
|
|
5887
|
+
}
|
|
5888
|
+
/** Find invalid old exclusions by name or ID, independently from the protected catalog row.
|
|
5889
|
+
* @param query - Search input.
|
|
5890
|
+
* @param filter - Old exclusions appear in all and excluded selections, not loaded.
|
|
5891
|
+
* @returns Invalid draft exclusions in their retained order.
|
|
5892
|
+
*/
|
|
5893
|
+
matchingInvalidExclusions(query, filter) {
|
|
5894
|
+
return filter === "loaded" ? [] : this.invalidExclusions.filter((entry) => matchesQuery(entry.id, query));
|
|
5895
|
+
}
|
|
5896
|
+
};
|
|
5897
|
+
//#endregion
|
|
5389
5898
|
//#region src/client-module-ui.ts
|
|
5390
5899
|
/** Device-local module selection and the computer's defaults share a read-only catalog. */
|
|
5391
5900
|
const CLIENT_MODULE_COPY = {
|
|
5392
5901
|
zh: {
|
|
5393
5902
|
title: "移动页面模块",
|
|
5394
|
-
computer: "
|
|
5395
|
-
device: "
|
|
5903
|
+
computer: "设置此电脑的移动访问默认项;电脑插件不变。",
|
|
5904
|
+
device: "仅本设备,下次打开生效;电脑插件不变。",
|
|
5396
5905
|
open: "管理",
|
|
5906
|
+
help: "加载规则",
|
|
5397
5907
|
loading: "正在读取模块…",
|
|
5398
|
-
hint: "
|
|
5399
|
-
save: "
|
|
5908
|
+
hint: "勾选表示下次打开时加载。启动必需项受保护;依赖冲突会在保存时说明,不会自动取消其他模块。",
|
|
5909
|
+
save: "保存选择",
|
|
5400
5910
|
reset: "恢复默认",
|
|
5401
5911
|
close: "关闭",
|
|
5402
5912
|
required: "启动必需",
|
|
5403
5913
|
dependencies: "依赖:",
|
|
5404
|
-
|
|
5405
|
-
|
|
5914
|
+
noDependencies: "无声明的前端依赖",
|
|
5915
|
+
details: "模块详情",
|
|
5916
|
+
moduleId: "模块 ID",
|
|
5917
|
+
saved: "已保存,下次打开 DSH 生效。不会自动刷新当前页面或丢弃草稿。",
|
|
5918
|
+
failed: "暂时无法读取模块。请确认移动访问已启用后重试。",
|
|
5919
|
+
saveFailed: "未确认保存成功,已保留你的选择。可重新保存,或重新读取已保存设置。",
|
|
5406
5920
|
conflict: "无法保存:",
|
|
5407
|
-
computerSource: "
|
|
5408
|
-
deviceSource: "
|
|
5409
|
-
|
|
5921
|
+
computerSource: "当前来源:电脑默认设置",
|
|
5922
|
+
deviceSource: "当前来源:本设备设置",
|
|
5923
|
+
pluginSource: "当前来源:插件默认设置",
|
|
5924
|
+
resetConfirm: "恢复默认模块选择?仅下次打开生效,不会重新加载当前会话。",
|
|
5925
|
+
discardConfirm: "放弃未保存的模块选择并关闭?",
|
|
5926
|
+
reloadConfirm: "重新读取会替换未保存的选择。继续吗?",
|
|
5927
|
+
savingCloseConfirm: "保存结果尚未确认。现在关闭不会撤销可能已完成的保存,确定关闭吗?",
|
|
5928
|
+
search: "搜索模块",
|
|
5929
|
+
searchPlaceholder: "搜索名称或模块 ID",
|
|
5930
|
+
filter: "加载选择",
|
|
5931
|
+
all: "全部",
|
|
5932
|
+
loaded: "加载",
|
|
5933
|
+
excluded: "不加载",
|
|
5934
|
+
empty: "没有符合条件的模块。",
|
|
5935
|
+
clearFilters: "清除筛选",
|
|
5936
|
+
extensions: "可选扩展",
|
|
5937
|
+
extensionsHint: "可选择不加载的前端模块;一项模块不一定等于一个完整插件。",
|
|
5938
|
+
advanced: "DSH 可选组件(高级)",
|
|
5939
|
+
advancedHint: "DSH 的可选前端组件。只修改你了解的项目。",
|
|
5940
|
+
requiredGroup: "启动必需项",
|
|
5941
|
+
requiredHint: "这些模块保证页面启动,不能关闭。",
|
|
5942
|
+
changes: "未保存:{count} 项改动",
|
|
5943
|
+
unchanged: "没有未保存的改动",
|
|
5944
|
+
results: "匹配 {visible} / {total} 项",
|
|
5945
|
+
retry: "重新读取",
|
|
5946
|
+
saving: "正在保存…",
|
|
5947
|
+
readTimeout: "读取超时。当前页面和已保存设置未变,请重试。",
|
|
5948
|
+
writeTimeout: "保存超时,结果尚未确认。保留了你的选择;请重新读取确认,不会自动重试保存。",
|
|
5949
|
+
nextOpen: "仅下次打开生效",
|
|
5950
|
+
invalidSelections: "失效的旧选择",
|
|
5951
|
+
invalidHint: "这些旧排除已与当前模块目录不符。移除后仍需保存选择。",
|
|
5952
|
+
invalidMissing: "当前模块目录中不存在。",
|
|
5953
|
+
invalidRequired: "当前已是启动必需项,不能排除。",
|
|
5954
|
+
clearInvalid: "移除旧排除"
|
|
5410
5955
|
},
|
|
5411
5956
|
en: {
|
|
5412
5957
|
title: "Mobile page modules",
|
|
5413
|
-
computer: "
|
|
5414
|
-
device: "
|
|
5958
|
+
computer: "Mobile defaults for this computer. Desktop plugins are unchanged.",
|
|
5959
|
+
device: "For this device’s next open only. Computer plugins are unchanged.",
|
|
5415
5960
|
open: "Manage",
|
|
5961
|
+
help: "Loading rules",
|
|
5416
5962
|
loading: "Reading modules…",
|
|
5417
|
-
hint: "Checked means
|
|
5418
|
-
save: "Save
|
|
5963
|
+
hint: "Checked means load on the next open. Required entries are protected; saving explains dependency conflicts without unchecking other modules.",
|
|
5964
|
+
save: "Save selection",
|
|
5419
5965
|
reset: "Restore defaults",
|
|
5420
5966
|
close: "Close",
|
|
5421
5967
|
required: "Required for boot",
|
|
5422
5968
|
dependencies: "Depends on: ",
|
|
5423
|
-
|
|
5424
|
-
|
|
5969
|
+
noDependencies: "No declared frontend dependencies",
|
|
5970
|
+
details: "Module details",
|
|
5971
|
+
moduleId: "Module ID",
|
|
5972
|
+
saved: "Saved for the next DSH open. This page is not refreshed and its draft is kept.",
|
|
5973
|
+
failed: "Could not read modules. Enable mobile access and try again.",
|
|
5974
|
+
saveFailed: "Saving was not confirmed. Your choices are kept. Save again or reread the saved settings.",
|
|
5425
5975
|
conflict: "Could not save: ",
|
|
5426
|
-
computerSource: "
|
|
5427
|
-
deviceSource: "
|
|
5428
|
-
|
|
5976
|
+
computerSource: "Source: computer defaults",
|
|
5977
|
+
deviceSource: "Source: this device’s settings",
|
|
5978
|
+
pluginSource: "Source: plugin defaults",
|
|
5979
|
+
resetConfirm: "Restore default selections for the next open? The current conversation will not reload.",
|
|
5980
|
+
discardConfirm: "Discard unsaved module choices and close?",
|
|
5981
|
+
reloadConfirm: "Rereading replaces unsaved choices. Continue?",
|
|
5982
|
+
savingCloseConfirm: "Saving is not confirmed. Closing will not undo a save that may have completed. Close anyway?",
|
|
5983
|
+
search: "Search modules",
|
|
5984
|
+
searchPlaceholder: "Search name or module ID",
|
|
5985
|
+
filter: "Loading selection",
|
|
5986
|
+
all: "All",
|
|
5987
|
+
loaded: "Load",
|
|
5988
|
+
excluded: "Do not load",
|
|
5989
|
+
empty: "No modules match these filters.",
|
|
5990
|
+
clearFilters: "Clear filters",
|
|
5991
|
+
extensions: "Optional extensions",
|
|
5992
|
+
extensionsHint: "Optional frontend modules; one module is not necessarily a complete plugin.",
|
|
5993
|
+
advanced: "DSH optional components (advanced)",
|
|
5994
|
+
advancedHint: "Optional DSH frontend components. Change only entries you understand.",
|
|
5995
|
+
requiredGroup: "Required for boot",
|
|
5996
|
+
requiredHint: "These modules keep the page bootable and cannot be disabled.",
|
|
5997
|
+
changes: "Unsaved: {count} changes",
|
|
5998
|
+
unchanged: "No unsaved changes",
|
|
5999
|
+
results: "Matching {visible} / {total}",
|
|
6000
|
+
retry: "Reread settings",
|
|
6001
|
+
saving: "Saving…",
|
|
6002
|
+
readTimeout: "Reading timed out. This page and saved settings are unchanged; try again.",
|
|
6003
|
+
writeTimeout: "Saving timed out and its result is unconfirmed. Your choices are kept. Reread to confirm; saving is not retried automatically.",
|
|
6004
|
+
nextOpen: "Applies on next open only",
|
|
6005
|
+
invalidSelections: "Outdated selections",
|
|
6006
|
+
invalidHint: "These old exclusions no longer match the module catalog. Save your choices after removing them.",
|
|
6007
|
+
invalidMissing: "Not present in the current module catalog.",
|
|
6008
|
+
invalidRequired: "Now required for boot and cannot be excluded.",
|
|
6009
|
+
clearInvalid: "Remove old exclusion"
|
|
5429
6010
|
},
|
|
5430
6011
|
it: {
|
|
5431
6012
|
title: "Moduli della pagina mobile",
|
|
5432
|
-
computer: "
|
|
5433
|
-
device: "
|
|
6013
|
+
computer: "Predefiniti mobile del computer; i plugin non cambiano.",
|
|
6014
|
+
device: "Solo alla prossima apertura su questo dispositivo; i plugin del computer non cambiano.",
|
|
5434
6015
|
open: "Gestisci",
|
|
6016
|
+
help: "Regole di caricamento",
|
|
5435
6017
|
loading: "Lettura dei moduli…",
|
|
5436
|
-
hint: "Selezionato significa
|
|
5437
|
-
save: "Salva
|
|
6018
|
+
hint: "Selezionato significa caricare alla prossima apertura. I moduli obbligatori sono protetti; il salvataggio mostra i conflitti senza deselezionare altri moduli.",
|
|
6019
|
+
save: "Salva selezione",
|
|
5438
6020
|
reset: "Ripristina predefiniti",
|
|
5439
6021
|
close: "Chiudi",
|
|
5440
6022
|
required: "Obbligatorio",
|
|
5441
6023
|
dependencies: "Dipende da: ",
|
|
5442
|
-
|
|
5443
|
-
|
|
6024
|
+
noDependencies: "Nessuna dipendenza frontend dichiarata",
|
|
6025
|
+
details: "Dettagli modulo",
|
|
6026
|
+
moduleId: "ID modulo",
|
|
6027
|
+
saved: "Salvato per la prossima apertura di DSH. La pagina non si aggiorna e la bozza resta.",
|
|
6028
|
+
failed: "Impossibile leggere i moduli. Abilita accesso mobile e riprova.",
|
|
6029
|
+
saveFailed: "Salvataggio non confermato. Le scelte restano; salva di nuovo o rileggi le impostazioni salvate.",
|
|
5444
6030
|
conflict: "Impossibile salvare: ",
|
|
5445
|
-
computerSource: "
|
|
5446
|
-
deviceSource: "
|
|
5447
|
-
|
|
6031
|
+
computerSource: "Origine: predefiniti del computer",
|
|
6032
|
+
deviceSource: "Origine: impostazioni del dispositivo",
|
|
6033
|
+
pluginSource: "Origine: predefiniti del plugin",
|
|
6034
|
+
resetConfirm: "Ripristinare i predefiniti per la prossima apertura? La conversazione attuale non verrà ricaricata.",
|
|
6035
|
+
discardConfirm: "Scartare le scelte non salvate e chiudere?",
|
|
6036
|
+
reloadConfirm: "Rileggere sostituisce le scelte non salvate. Continuare?",
|
|
6037
|
+
savingCloseConfirm: "Salvataggio non confermato. Chiudere non annulla un salvataggio già completato. Chiudere comunque?",
|
|
6038
|
+
search: "Cerca moduli",
|
|
6039
|
+
searchPlaceholder: "Cerca nome o ID modulo",
|
|
6040
|
+
filter: "Selezione caricamento",
|
|
6041
|
+
all: "Tutti",
|
|
6042
|
+
loaded: "Carica",
|
|
6043
|
+
excluded: "Non caricare",
|
|
6044
|
+
empty: "Nessun modulo corrisponde ai filtri.",
|
|
6045
|
+
clearFilters: "Cancella filtri",
|
|
6046
|
+
extensions: "Estensioni facoltative",
|
|
6047
|
+
extensionsHint: "Moduli frontend facoltativi; un modulo non è necessariamente un plugin completo.",
|
|
6048
|
+
advanced: "Componenti DSH facoltativi (avanzate)",
|
|
6049
|
+
advancedHint: "Componenti frontend DSH facoltativi. Modifica solo quelli che conosci.",
|
|
6050
|
+
requiredGroup: "Moduli obbligatori",
|
|
6051
|
+
requiredHint: "Questi moduli permettono l’avvio e non possono essere disattivati.",
|
|
6052
|
+
changes: "Non salvate: {count} modifiche",
|
|
6053
|
+
unchanged: "Nessuna modifica non salvata",
|
|
6054
|
+
results: "Corrispondenti {visible} / {total}",
|
|
6055
|
+
retry: "Rileggi impostazioni",
|
|
6056
|
+
saving: "Salvataggio…",
|
|
6057
|
+
readTimeout: "Lettura scaduta. Pagina e impostazioni salvate non cambiano; riprova.",
|
|
6058
|
+
writeTimeout: "Salvataggio scaduto e non confermato. Le scelte restano. Rileggi per confermare; nessun tentativo automatico.",
|
|
6059
|
+
nextOpen: "Solo alla prossima apertura",
|
|
6060
|
+
invalidSelections: "Scelte obsolete",
|
|
6061
|
+
invalidHint: "Queste esclusioni precedenti non corrispondono più al catalogo. Salva le scelte dopo averle rimosse.",
|
|
6062
|
+
invalidMissing: "Assente dal catalogo attuale dei moduli.",
|
|
6063
|
+
invalidRequired: "Ora obbligatorio per l’avvio e non può essere escluso.",
|
|
6064
|
+
clearInvalid: "Rimuovi esclusione precedente"
|
|
5448
6065
|
}
|
|
5449
6066
|
};
|
|
5450
6067
|
var ClientModuleUiConflict = class extends Error {
|
|
@@ -5454,29 +6071,40 @@ window.__ModuleLoader__.load({
|
|
|
5454
6071
|
this.detail = detail;
|
|
5455
6072
|
}
|
|
5456
6073
|
};
|
|
6074
|
+
/** A deadline does not imply that a POST was rolled back by the server. */
|
|
6075
|
+
var ClientModuleRequestTimeout = class extends Error {
|
|
6076
|
+
mutation;
|
|
6077
|
+
constructor(mutation) {
|
|
6078
|
+
super("client_module_request_timeout");
|
|
6079
|
+
this.mutation = mutation;
|
|
6080
|
+
}
|
|
6081
|
+
};
|
|
6082
|
+
const CLIENT_MODULE_REQUEST_TIMEOUT_MS = {
|
|
6083
|
+
read: 15e3,
|
|
6084
|
+
write: 2e4
|
|
6085
|
+
};
|
|
5457
6086
|
/** Validate catalog response fields before creating interactive controls.
|
|
5458
6087
|
* @param value - Gateway or computer-admin JSON response.
|
|
5459
6088
|
* @returns The fields used by the module-selection dialog.
|
|
5460
6089
|
*/
|
|
5461
6090
|
function parseClientModuleView(value) {
|
|
5462
|
-
if (typeof value !== "object" || value === null || !("entries" in value) || !Array.isArray(value.entries) || !("excludedClientModules" in value) || !Array.isArray(value.excludedClientModules) || !("source" in value) ||
|
|
5463
|
-
|
|
5464
|
-
"
|
|
5465
|
-
|
|
5466
|
-
|
|
6091
|
+
if (typeof value !== "object" || value === null || !("entries" in value) || !Array.isArray(value.entries) || !("excludedClientModules" in value) || !Array.isArray(value.excludedClientModules) || !("source" in value) || value.source !== "device" && value.source !== "computer" && value.source !== "plugin") throw new Error("invalid_module_catalog");
|
|
6092
|
+
const entries = value.entries.map((entry) => {
|
|
6093
|
+
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");
|
|
6094
|
+
const dependencies = entry.dependencies.map((dependency) => {
|
|
6095
|
+
if (typeof dependency !== "string") throw new Error("invalid_module_catalog");
|
|
6096
|
+
return dependency;
|
|
6097
|
+
});
|
|
6098
|
+
if (entry.id.trim() === "") throw new Error("invalid_module_catalog");
|
|
6099
|
+
return {
|
|
6100
|
+
id: entry.id,
|
|
6101
|
+
required: entry.required,
|
|
6102
|
+
dependencies
|
|
6103
|
+
};
|
|
6104
|
+
});
|
|
6105
|
+
if (new Set(entries.map((entry) => entry.id)).size !== entries.length) throw new Error("invalid_module_catalog");
|
|
5467
6106
|
return {
|
|
5468
|
-
entries
|
|
5469
|
-
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");
|
|
5470
|
-
const dependencies = entry.dependencies.map((dependency) => {
|
|
5471
|
-
if (typeof dependency !== "string") throw new Error("invalid_module_catalog");
|
|
5472
|
-
return dependency;
|
|
5473
|
-
});
|
|
5474
|
-
return {
|
|
5475
|
-
id: entry.id,
|
|
5476
|
-
required: entry.required,
|
|
5477
|
-
dependencies
|
|
5478
|
-
};
|
|
5479
|
-
}),
|
|
6107
|
+
entries,
|
|
5480
6108
|
excludedClientModules: value.excludedClientModules.map((id) => {
|
|
5481
6109
|
if (typeof id !== "string") throw new Error("invalid_module_catalog");
|
|
5482
6110
|
return id;
|
|
@@ -5484,32 +6112,103 @@ window.__ModuleLoader__.load({
|
|
|
5484
6112
|
source: value.source === "device" ? "device" : value.source === "computer" ? "computer" : "plugin"
|
|
5485
6113
|
};
|
|
5486
6114
|
}
|
|
6115
|
+
/** Read or explicitly write module choices with a bounded, cancellable operation.
|
|
6116
|
+
* @param scope - Computer-admin endpoint or authenticated device endpoint.
|
|
6117
|
+
* @param signal - Owning dialog lifetime; cancellation cannot undo a completed write.
|
|
6118
|
+
* @param body - Omit for a read; supplied fields are sent once without automatic replay.
|
|
6119
|
+
* @returns Validated catalog fields returned by the selected endpoint.
|
|
6120
|
+
*/
|
|
6121
|
+
async function requestClientModuleView(scope, signal, body) {
|
|
6122
|
+
if (signal.aborted) throw new DOMException("Dialog closed", "AbortError");
|
|
6123
|
+
const controller = new AbortController();
|
|
6124
|
+
let rejectCancellation = () => {};
|
|
6125
|
+
const cancellation = new Promise((_, reject) => {
|
|
6126
|
+
rejectCancellation = reject;
|
|
6127
|
+
});
|
|
6128
|
+
const abort = () => {
|
|
6129
|
+
controller.abort();
|
|
6130
|
+
rejectCancellation(new DOMException("Dialog closed", "AbortError"));
|
|
6131
|
+
};
|
|
6132
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
6133
|
+
const timer = setTimeout(() => {
|
|
6134
|
+
rejectCancellation(new ClientModuleRequestTimeout(body !== void 0));
|
|
6135
|
+
controller.abort();
|
|
6136
|
+
}, body === void 0 ? CLIENT_MODULE_REQUEST_TIMEOUT_MS.read : CLIENT_MODULE_REQUEST_TIMEOUT_MS.write);
|
|
6137
|
+
const operation = async () => {
|
|
6138
|
+
const init = {
|
|
6139
|
+
method: body === void 0 ? "GET" : "POST",
|
|
6140
|
+
signal: controller.signal,
|
|
6141
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
6142
|
+
};
|
|
6143
|
+
init.headers = scope === "computer" ? localAdminRequestHeaders(init, location) : { "content-type": "application/json" };
|
|
6144
|
+
if (scope === "device" && body !== void 0) {
|
|
6145
|
+
const cookies = document.cookie.split(";").map((value) => value.trim());
|
|
6146
|
+
const csrfCookie = cookies.find((value) => value.startsWith("__Host-dsh_ma_csrf=")) ?? cookies.find((value) => value.startsWith("dsh_ma_csrf="));
|
|
6147
|
+
if (csrfCookie !== void 0) {
|
|
6148
|
+
const headers = new Headers(init.headers);
|
|
6149
|
+
headers.set("x-dsh-mobile-csrf", csrfCookie.slice(csrfCookie.indexOf("=") + 1));
|
|
6150
|
+
init.headers = headers;
|
|
6151
|
+
}
|
|
6152
|
+
}
|
|
6153
|
+
const response = await fetch(scope === "computer" ? "/api/mobile-access/client-modules" : "/mobile-access/client-modules", init);
|
|
6154
|
+
const value = await response.json();
|
|
6155
|
+
if (!response.ok) {
|
|
6156
|
+
const detail = typeof value === "object" && value !== null && "detail" in value && typeof value.detail === "string" ? value.detail.slice(0, 512) : "";
|
|
6157
|
+
if (response.status === 409 && detail !== "" && typeof value === "object" && value !== null && "error" in value && value.error === "excluded_client_modules_invalid") throw new ClientModuleUiConflict(detail);
|
|
6158
|
+
throw new Error("client_module_request_failed");
|
|
6159
|
+
}
|
|
6160
|
+
return parseClientModuleView(value);
|
|
6161
|
+
};
|
|
6162
|
+
try {
|
|
6163
|
+
return await Promise.race([operation(), cancellation]);
|
|
6164
|
+
} finally {
|
|
6165
|
+
clearTimeout(timer);
|
|
6166
|
+
signal.removeEventListener("abort", abort);
|
|
6167
|
+
}
|
|
6168
|
+
}
|
|
5487
6169
|
const CLIENT_MODULE_STYLES = `
|
|
5488
6170
|
.dsh-module-row{display:flex;align-items:center;gap:8px;padding:16px 0;border-bottom:.5px solid var(--dsw-alias-border-l2)}
|
|
5489
6171
|
.dsh-module-rowText{flex:1;min-width:0}.dsh-module-title{font-size:14px;line-height:22px;color:var(--dsw-alias-label-primary)}
|
|
5490
6172
|
.dsh-module-description{margin-top:4px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}
|
|
5491
6173
|
.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}
|
|
5492
6174
|
.dsh-module-button:focus-visible{outline:2px solid var(--dsw-alias-label-primary-bluish,#2563eb);outline-offset:2px}
|
|
5493
|
-
.dsh-module-
|
|
5494
|
-
.dsh-module-dialog
|
|
5495
|
-
.dsh-module-dialog
|
|
5496
|
-
.dsh-module-
|
|
5497
|
-
.dsh-module-
|
|
5498
|
-
.dsh-module-
|
|
6175
|
+
.dsh-module-button:not(:disabled):active{background:var(--dsw-alias-interactive-bg-active)}
|
|
6176
|
+
.dsh-module-dialog{box-sizing:border-box;width:min(640px,calc(100vw - 24px));max-height:calc(100dvh - 32px);padding:0;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-panel,20px);background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-primary,#171a21);font:16px/1.5 var(--dsw-font-family,sans-serif);overflow:hidden}
|
|
6177
|
+
.dsh-module-dialog[open]{display:flex;flex-direction:column}.dsh-module-dialog::backdrop{background:rgb(15 23 42 / 40%)}
|
|
6178
|
+
.dsh-module-header{display:flex;align-items:center;gap:12px;padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l2);flex:none}
|
|
6179
|
+
.dsh-module-header h2{flex:1;min-width:0;font-size:20px;line-height:1.4;margin:0;overflow-wrap:anywhere}.dsh-module-header .dsh-module-button{flex:none}
|
|
6180
|
+
.dsh-module-body{min-height:0;overflow:auto;overscroll-behavior:contain;padding:16px;scroll-padding:12px}.dsh-module-dialog p{margin:0 0 12px;overflow-wrap:anywhere}
|
|
6181
|
+
.dsh-module-hint,.dsh-module-source,.dsh-module-choice small,.dsh-module-groupHint,.dsh-module-results{font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-tertiary))}
|
|
6182
|
+
.dsh-module-tools{display:grid;grid-template-columns:minmax(0,1fr) minmax(100px,.45fr);gap:12px;margin:16px 0 8px}.dsh-module-field{display:flex;flex-direction:column;gap:4px;min-width:0;font-size:13px}
|
|
6183
|
+
.dsh-module-help>summary{box-sizing:border-box;min-height:48px;padding:12px 0;cursor:pointer;font-size:13px;color:var(--dsw-alias-label-secondary)}
|
|
6184
|
+
.dsh-module-field input,.dsh-module-field select{box-sizing:border-box;width:100%;min-width:0;min-height:48px;padding:8px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-base,var(--dsw-alias-bg-layer-1));color:var(--dsw-alias-label-primary);font:inherit;font-size:16px}
|
|
6185
|
+
.dsh-module-dialog :is(input,select,summary):focus-visible{outline:2px solid var(--dsw-alias-label-primary-bluish,#2563eb);outline-offset:2px}
|
|
6186
|
+
.dsh-module-list{display:flex;flex-direction:column;gap:16px;margin:12px 0}.dsh-module-group h3{margin:0 0 8px;font-size:16px;line-height:24px}
|
|
6187
|
+
.dsh-module-group>summary{min-height:48px;cursor:pointer;font-weight:600;overflow-wrap:anywhere;align-content:center}.dsh-module-group>summary+div{margin-top:8px}
|
|
6188
|
+
.dsh-module-entry{border-bottom:1px solid var(--dsw-alias-border-l2);padding:8px 0}.dsh-module-choice{display:flex;align-items:center;gap:12px;min-height:48px;cursor:pointer}
|
|
6189
|
+
.dsh-module-choice input{width:24px;height:24px;flex:none;margin:0}.dsh-module-choice span{min-width:0;overflow-wrap:anywhere}.dsh-module-choiceName{display:block;font-weight:500}
|
|
6190
|
+
.dsh-module-choice small{display:block}.dsh-module-entryDetails{margin-left:36px;min-width:0}.dsh-module-entryDetails summary{min-height:48px;align-content:center;cursor:pointer;font-size:13px;color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-tertiary))}
|
|
6191
|
+
.dsh-module-invalid-entry code{display:block;min-width:0;overflow-wrap:anywhere;white-space:normal;font-size:13px}.dsh-module-invalid-entry .dsh-module-button{max-width:100%;white-space:normal;line-height:20px;padding:8px 14px}
|
|
6192
|
+
.dsh-module-entryDetails p,.dsh-module-entryDetails code{overflow-wrap:anywhere;font-size:13px}.dsh-module-entryDetails code{white-space:normal}
|
|
6193
|
+
.dsh-module-footer{flex:none;padding:12px 16px max(12px,env(safe-area-inset-bottom));border-top:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1,#fff)}
|
|
6194
|
+
.dsh-module-footer p{font-size:13px;line-height:20px;margin:0 0 8px}.dsh-module-actions{display:flex;flex-wrap:wrap;gap:8px}.dsh-module-actions .dsh-module-button{flex:1;min-width:0;white-space:normal;line-height:20px;padding:8px 12px}
|
|
6195
|
+
.dsh-module-button:disabled{opacity:.5;cursor:default}.dsh-module-save{background:var(--dsw-alias-bg-module-platform);font-weight:600}
|
|
6196
|
+
.dsh-module-notice:empty{display:none}.dsh-module-notice{max-height:min(96px,24dvh);overflow:auto;font-size:14px;line-height:22px;overscroll-behavior:contain}.dsh-module-empty{padding:16px 0}
|
|
6197
|
+
@media(max-width:420px){.dsh-module-tools{grid-template-columns:minmax(0,1fr)}.dsh-module-body{padding:12px}.dsh-module-header,.dsh-module-footer{padding-left:12px;padding-right:12px}}
|
|
5499
6198
|
.dsh-module-error{color:var(--dsw-alias-state-error-primary,#b42318)}
|
|
5500
6199
|
`;
|
|
5501
6200
|
const openDialogs = /* @__PURE__ */ new Set();
|
|
5502
6201
|
/** Cancel pending dialogs when the owning plugin generation is disposed. */
|
|
5503
6202
|
function disposeClientModuleDialogs() {
|
|
5504
|
-
for (const
|
|
6203
|
+
for (const dialog of [...openDialogs]) dialog.dispose();
|
|
5505
6204
|
}
|
|
5506
6205
|
/** Close the foreground module modal before navigating its underlying DSH settings.
|
|
5507
6206
|
* @returns Whether an open module modal consumed the return action.
|
|
5508
6207
|
*/
|
|
5509
6208
|
function closeClientModuleDialog() {
|
|
5510
|
-
const
|
|
5511
|
-
if (
|
|
5512
|
-
|
|
6209
|
+
const dialog = [...openDialogs].at(-1);
|
|
6210
|
+
if (dialog === void 0) return false;
|
|
6211
|
+
dialog.requestClose();
|
|
5513
6212
|
return true;
|
|
5514
6213
|
}
|
|
5515
6214
|
/** Open one modal; closing cancels reads and restores focus without reloading the page.
|
|
@@ -5519,143 +6218,338 @@ window.__ModuleLoader__.load({
|
|
|
5519
6218
|
*/
|
|
5520
6219
|
function openClientModuleSettings(scope, locale, trigger) {
|
|
5521
6220
|
const copy = CLIENT_MODULE_COPY[locale];
|
|
5522
|
-
const endpoint = scope === "computer" ? "/api/mobile-access/client-modules" : "/mobile-access/client-modules";
|
|
5523
6221
|
const controller = new AbortController();
|
|
5524
6222
|
const dialog = document.createElement("dialog");
|
|
5525
6223
|
dialog.className = "dsh-module-dialog";
|
|
5526
6224
|
dialog.setAttribute("aria-label", copy.title);
|
|
6225
|
+
dialog.lang = locale;
|
|
6226
|
+
const button = (text) => {
|
|
6227
|
+
const result = document.createElement("button");
|
|
6228
|
+
result.type = "button";
|
|
6229
|
+
result.className = "dsh-module-button";
|
|
6230
|
+
result.textContent = text;
|
|
6231
|
+
return result;
|
|
6232
|
+
};
|
|
6233
|
+
const paragraph = (text, className = "") => {
|
|
6234
|
+
const result = document.createElement("p");
|
|
6235
|
+
result.textContent = text;
|
|
6236
|
+
result.className = className;
|
|
6237
|
+
return result;
|
|
6238
|
+
};
|
|
6239
|
+
const header = document.createElement("div");
|
|
6240
|
+
header.className = "dsh-module-header";
|
|
5527
6241
|
const title = document.createElement("h2");
|
|
5528
6242
|
title.textContent = copy.title;
|
|
5529
|
-
const
|
|
5530
|
-
|
|
5531
|
-
const
|
|
6243
|
+
const close = button(copy.close);
|
|
6244
|
+
header.append(title, close);
|
|
6245
|
+
const body = document.createElement("div");
|
|
6246
|
+
body.className = "dsh-module-body";
|
|
6247
|
+
const intro = paragraph(copy[scope]);
|
|
6248
|
+
const hint = paragraph(copy.hint, "dsh-module-hint");
|
|
6249
|
+
const help = document.createElement("details");
|
|
6250
|
+
help.className = "dsh-module-help";
|
|
6251
|
+
const helpTitle = document.createElement("summary");
|
|
6252
|
+
helpTitle.textContent = copy.help;
|
|
6253
|
+
help.append(helpTitle, hint);
|
|
6254
|
+
const source = paragraph("", "dsh-module-source");
|
|
6255
|
+
const status = paragraph("", "dsh-module-notice");
|
|
5532
6256
|
status.setAttribute("role", "status");
|
|
5533
6257
|
status.setAttribute("aria-live", "polite");
|
|
5534
6258
|
status.tabIndex = -1;
|
|
6259
|
+
const tools = document.createElement("div");
|
|
6260
|
+
tools.className = "dsh-module-tools";
|
|
6261
|
+
const searchLabel = document.createElement("label");
|
|
6262
|
+
searchLabel.className = "dsh-module-field";
|
|
6263
|
+
searchLabel.textContent = copy.search;
|
|
6264
|
+
const search = document.createElement("input");
|
|
6265
|
+
search.type = "search";
|
|
6266
|
+
search.placeholder = copy.searchPlaceholder;
|
|
6267
|
+
search.setAttribute("aria-label", copy.search);
|
|
6268
|
+
searchLabel.append(search);
|
|
6269
|
+
const filterLabel = document.createElement("label");
|
|
6270
|
+
filterLabel.className = "dsh-module-field";
|
|
6271
|
+
filterLabel.textContent = copy.filter;
|
|
6272
|
+
const filter = document.createElement("select");
|
|
6273
|
+
filter.dataset.clientModuleFilter = "true";
|
|
6274
|
+
filter.setAttribute("aria-label", copy.filter);
|
|
6275
|
+
for (const value of [
|
|
6276
|
+
"all",
|
|
6277
|
+
"loaded",
|
|
6278
|
+
"excluded"
|
|
6279
|
+
]) {
|
|
6280
|
+
const option = document.createElement("option");
|
|
6281
|
+
option.value = value;
|
|
6282
|
+
option.textContent = copy[value];
|
|
6283
|
+
filter.append(option);
|
|
6284
|
+
}
|
|
6285
|
+
filterLabel.append(filter);
|
|
6286
|
+
tools.append(searchLabel, filterLabel);
|
|
6287
|
+
const results = paragraph("", "dsh-module-results");
|
|
6288
|
+
results.setAttribute("aria-live", "polite");
|
|
5535
6289
|
const list = document.createElement("div");
|
|
5536
6290
|
list.className = "dsh-module-list";
|
|
6291
|
+
const footer = document.createElement("div");
|
|
6292
|
+
footer.className = "dsh-module-footer";
|
|
6293
|
+
const changes = paragraph(copy.unchanged);
|
|
6294
|
+
changes.setAttribute("role", "status");
|
|
6295
|
+
changes.setAttribute("aria-live", "polite");
|
|
5537
6296
|
const actions = document.createElement("div");
|
|
5538
6297
|
actions.className = "dsh-module-actions";
|
|
5539
|
-
const button = (text) => {
|
|
5540
|
-
const result = document.createElement("button");
|
|
5541
|
-
result.type = "button";
|
|
5542
|
-
result.className = "dsh-module-button";
|
|
5543
|
-
result.textContent = text;
|
|
5544
|
-
return result;
|
|
5545
|
-
};
|
|
5546
6298
|
const save = button(copy.save);
|
|
6299
|
+
save.classList.add("dsh-module-save");
|
|
5547
6300
|
const reset = button(copy.reset);
|
|
5548
|
-
const
|
|
5549
|
-
actions.append(save, reset,
|
|
5550
|
-
|
|
6301
|
+
const reread = button(copy.retry);
|
|
6302
|
+
actions.append(save, reset, reread);
|
|
6303
|
+
footer.append(changes, status, actions);
|
|
6304
|
+
body.append(intro, source, tools, results, list, help);
|
|
6305
|
+
dialog.append(header, body, footer);
|
|
5551
6306
|
let disposed = false;
|
|
5552
|
-
let busy
|
|
5553
|
-
let
|
|
6307
|
+
let busy;
|
|
6308
|
+
let generation = 0;
|
|
6309
|
+
let draft;
|
|
6310
|
+
let unconfirmedWrite = false;
|
|
6311
|
+
const expansion = /* @__PURE__ */ new Map([
|
|
6312
|
+
["extensions", true],
|
|
6313
|
+
["advanced", false],
|
|
6314
|
+
["required", false]
|
|
6315
|
+
]);
|
|
5554
6316
|
const choices = /* @__PURE__ */ new Map();
|
|
5555
|
-
const
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
6317
|
+
const invalidActions = /* @__PURE__ */ new Map();
|
|
6318
|
+
const filterValue = () => filter.value === "loaded" ? "loaded" : filter.value === "excluded" ? "excluded" : "all";
|
|
6319
|
+
const updateControls = () => {
|
|
6320
|
+
const changed = draft?.changeCount ?? 0;
|
|
6321
|
+
save.disabled = busy !== void 0 || changed === 0;
|
|
6322
|
+
reset.disabled = busy !== void 0 || draft === void 0;
|
|
6323
|
+
reread.disabled = busy !== void 0;
|
|
6324
|
+
search.disabled = busy !== void 0 || draft === void 0;
|
|
6325
|
+
filter.disabled = search.disabled;
|
|
6326
|
+
save.textContent = busy === "write" ? copy.saving : copy.save;
|
|
6327
|
+
changes.textContent = `${changed === 0 ? copy.unchanged : copy.changes.replace("{count}", String(changed))} · ${copy.nextOpen}`;
|
|
6328
|
+
list.setAttribute("aria-busy", String(busy !== void 0));
|
|
6329
|
+
for (const input of choices.values()) input.disabled = busy !== void 0 || input.dataset.required === "true";
|
|
6330
|
+
for (const action of invalidActions.values()) action.disabled = busy !== void 0;
|
|
6331
|
+
};
|
|
6332
|
+
const renderList = () => {
|
|
6333
|
+
choices.clear();
|
|
6334
|
+
invalidActions.clear();
|
|
6335
|
+
list.replaceChildren();
|
|
6336
|
+
if (draft === void 0) {
|
|
6337
|
+
results.textContent = "";
|
|
6338
|
+
return;
|
|
6339
|
+
}
|
|
6340
|
+
const visible = draft.matchingEntries(search.value, filterValue());
|
|
6341
|
+
const invalid = draft.matchingInvalidExclusions(search.value, filterValue());
|
|
6342
|
+
results.textContent = copy.results.replace("{visible}", String(visible.length + invalid.length)).replace("{total}", String(draft.entries.length + draft.invalidExclusions.length));
|
|
6343
|
+
const filtered = search.value.trim() !== "" || filterValue() !== "all";
|
|
6344
|
+
if (visible.length === 0 && invalid.length === 0) {
|
|
6345
|
+
const empty = document.createElement("div");
|
|
6346
|
+
empty.className = "dsh-module-empty";
|
|
6347
|
+
empty.append(paragraph(copy.empty));
|
|
6348
|
+
const clear = button(copy.clearFilters);
|
|
6349
|
+
clear.addEventListener("click", () => {
|
|
6350
|
+
search.value = "";
|
|
6351
|
+
filter.value = "all";
|
|
6352
|
+
renderList();
|
|
6353
|
+
search.focus({ preventScroll: true });
|
|
6354
|
+
});
|
|
6355
|
+
empty.append(clear);
|
|
6356
|
+
list.append(empty);
|
|
6357
|
+
}
|
|
6358
|
+
if (invalid.length > 0) {
|
|
6359
|
+
const section = document.createElement("section");
|
|
6360
|
+
section.className = "dsh-module-group";
|
|
6361
|
+
section.dataset.clientModuleGroup = "invalid";
|
|
6362
|
+
const heading = document.createElement("h3");
|
|
6363
|
+
heading.textContent = `${copy.invalidSelections} (${invalid.length})`;
|
|
6364
|
+
section.append(heading, paragraph(copy.invalidHint, "dsh-module-groupHint"));
|
|
6365
|
+
for (const entry of invalid) {
|
|
6366
|
+
const row = document.createElement("div");
|
|
6367
|
+
row.className = "dsh-module-entry dsh-module-invalid-entry";
|
|
6368
|
+
row.dataset.invalidClientModuleId = entry.id;
|
|
6369
|
+
const id = document.createElement("code");
|
|
6370
|
+
id.textContent = entry.id;
|
|
6371
|
+
const remove = button(copy.clearInvalid);
|
|
6372
|
+
remove.setAttribute("aria-label", `${copy.clearInvalid}: ${entry.id}`);
|
|
6373
|
+
remove.addEventListener("click", () => {
|
|
6374
|
+
if (disposed || busy !== void 0 || draft?.clearInvalidExclusion(entry.id) !== true) return;
|
|
6375
|
+
status.textContent = "";
|
|
6376
|
+
status.classList.remove("dsh-module-error");
|
|
6377
|
+
renderList();
|
|
6378
|
+
(invalidActions.values().next().value ?? save).focus({ preventScroll: true });
|
|
6379
|
+
});
|
|
6380
|
+
row.append(id, paragraph(entry.reason === "missing" ? copy.invalidMissing : copy.invalidRequired), remove);
|
|
6381
|
+
invalidActions.set(entry.id, remove);
|
|
6382
|
+
section.append(row);
|
|
5574
6383
|
}
|
|
6384
|
+
list.append(section);
|
|
5575
6385
|
}
|
|
5576
|
-
const
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
6386
|
+
for (const group of [
|
|
6387
|
+
"extensions",
|
|
6388
|
+
"advanced",
|
|
6389
|
+
"required"
|
|
6390
|
+
]) {
|
|
6391
|
+
const entries = visible.filter((entry) => clientModuleGroup(entry) === group);
|
|
6392
|
+
if (entries.length === 0) continue;
|
|
6393
|
+
const section = document.createElement(group === "extensions" ? "section" : "details");
|
|
6394
|
+
section.className = "dsh-module-group";
|
|
6395
|
+
section.dataset.group = group;
|
|
6396
|
+
section.dataset.clientModuleGroup = group === "advanced" ? "system" : group;
|
|
6397
|
+
const headingText = `${group === "required" ? copy.requiredGroup : copy[group]} (${entries.length})`;
|
|
6398
|
+
if (section instanceof HTMLDetailsElement) {
|
|
6399
|
+
section.open = filtered || expansion.get(group) === true;
|
|
6400
|
+
const heading = document.createElement("summary");
|
|
6401
|
+
heading.textContent = headingText;
|
|
6402
|
+
section.append(heading);
|
|
6403
|
+
section.addEventListener("toggle", () => {
|
|
6404
|
+
if (!filtered) expansion.set(group, section.open);
|
|
6405
|
+
});
|
|
6406
|
+
} else {
|
|
6407
|
+
const heading = document.createElement("h3");
|
|
6408
|
+
heading.textContent = headingText;
|
|
6409
|
+
section.append(heading);
|
|
6410
|
+
}
|
|
6411
|
+
const content = document.createElement("div");
|
|
6412
|
+
content.append(paragraph(group === "required" ? copy.requiredHint : group === "advanced" ? copy.advancedHint : copy.extensionsHint, "dsh-module-groupHint"));
|
|
6413
|
+
for (const entry of entries) {
|
|
6414
|
+
const row = document.createElement("div");
|
|
6415
|
+
row.className = "dsh-module-entry";
|
|
6416
|
+
row.dataset.clientModuleId = entry.id;
|
|
6417
|
+
const label = document.createElement("label");
|
|
6418
|
+
label.className = "dsh-module-choice";
|
|
6419
|
+
const input = document.createElement("input");
|
|
6420
|
+
input.type = "checkbox";
|
|
6421
|
+
input.checked = draft.isLoaded(entry.id);
|
|
6422
|
+
if (entry.required) input.dataset.required = "true";
|
|
6423
|
+
const name = document.createElement("span");
|
|
6424
|
+
const shortName = document.createElement("span");
|
|
6425
|
+
shortName.className = "dsh-module-choiceName";
|
|
6426
|
+
shortName.textContent = clientModuleDisplayName(entry.id);
|
|
6427
|
+
const selected = document.createElement("small");
|
|
6428
|
+
selected.textContent = entry.required ? copy.required : input.checked ? copy.loaded : copy.excluded;
|
|
6429
|
+
name.append(shortName, selected);
|
|
6430
|
+
label.append(input, name);
|
|
6431
|
+
row.append(label);
|
|
6432
|
+
const detail = document.createElement("details");
|
|
6433
|
+
detail.className = "dsh-module-entryDetails";
|
|
6434
|
+
const detailSummary = document.createElement("summary");
|
|
6435
|
+
detailSummary.textContent = copy.details;
|
|
6436
|
+
const id = paragraph(`${copy.moduleId}: `);
|
|
6437
|
+
const code = document.createElement("code");
|
|
6438
|
+
code.textContent = entry.id;
|
|
6439
|
+
id.append(code);
|
|
6440
|
+
detail.append(detailSummary, id, paragraph(entry.dependencies.length === 0 ? copy.noDependencies : `${copy.dependencies}${entry.dependencies.join(", ")}`));
|
|
6441
|
+
row.append(detail);
|
|
6442
|
+
input.addEventListener("change", () => {
|
|
6443
|
+
if (busy !== void 0 || draft === void 0) return;
|
|
6444
|
+
draft.setLoaded(entry.id, input.checked);
|
|
6445
|
+
input.checked = draft.isLoaded(entry.id);
|
|
6446
|
+
selected.textContent = entry.required ? copy.required : input.checked ? copy.loaded : copy.excluded;
|
|
6447
|
+
status.textContent = "";
|
|
6448
|
+
status.classList.remove("dsh-module-error");
|
|
6449
|
+
if (filterValue() !== "all") {
|
|
6450
|
+
renderList();
|
|
6451
|
+
filter.focus({ preventScroll: true });
|
|
6452
|
+
}
|
|
6453
|
+
updateControls();
|
|
6454
|
+
});
|
|
6455
|
+
choices.set(entry.id, input);
|
|
6456
|
+
content.append(row);
|
|
6457
|
+
}
|
|
6458
|
+
section.append(content);
|
|
6459
|
+
list.append(section);
|
|
5582
6460
|
}
|
|
5583
|
-
|
|
6461
|
+
updateControls();
|
|
5584
6462
|
};
|
|
5585
6463
|
const render = (view) => {
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
list.replaceChildren();
|
|
5590
|
-
for (const entry of view.entries) {
|
|
5591
|
-
const label = document.createElement("label");
|
|
5592
|
-
label.className = "dsh-module-choice";
|
|
5593
|
-
const input = document.createElement("input");
|
|
5594
|
-
input.type = "checkbox";
|
|
5595
|
-
input.checked = !view.excludedClientModules.includes(entry.id);
|
|
5596
|
-
input.disabled = entry.required;
|
|
5597
|
-
if (entry.required) input.dataset.required = "true";
|
|
5598
|
-
const name = document.createElement("span");
|
|
5599
|
-
name.textContent = entry.id;
|
|
5600
|
-
const hint = document.createElement("small");
|
|
5601
|
-
hint.textContent = entry.required ? copy.required : entry.dependencies.length === 0 ? "" : `${copy.dependencies}${entry.dependencies.join(", ")}`;
|
|
5602
|
-
name.append(hint);
|
|
5603
|
-
label.append(input, name);
|
|
5604
|
-
list.append(label);
|
|
5605
|
-
choices.set(entry.id, input);
|
|
5606
|
-
}
|
|
5607
|
-
status.classList.remove("dsh-module-error");
|
|
5608
|
-
status.textContent = view.source === "device" ? copy.deviceSource : copy.computerSource;
|
|
6464
|
+
draft = new ClientModuleDraft(view.entries, view.excludedClientModules);
|
|
6465
|
+
source.textContent = view.source === "device" ? copy.deviceSource : view.source === "computer" ? copy.computerSource : copy.pluginSource;
|
|
6466
|
+
renderList();
|
|
5609
6467
|
};
|
|
5610
|
-
const failed = (error) => {
|
|
5611
|
-
if (disposed || controller.signal.aborted) return;
|
|
6468
|
+
const failed = (error, mutation) => {
|
|
5612
6469
|
status.classList.add("dsh-module-error");
|
|
5613
|
-
status.textContent = error instanceof ClientModuleUiConflict ? `${copy.conflict}${error.detail}` : copy.failed;
|
|
6470
|
+
status.textContent = error instanceof ClientModuleUiConflict ? `${copy.conflict}${error.detail}` : error instanceof ClientModuleRequestTimeout ? error.mutation ? copy.writeTimeout : copy.readTimeout : mutation ? copy.saveFailed : copy.failed;
|
|
5614
6471
|
status.focus({ preventScroll: true });
|
|
5615
6472
|
};
|
|
5616
|
-
const
|
|
5617
|
-
if (busy ||
|
|
5618
|
-
|
|
5619
|
-
|
|
6473
|
+
const runRequest = (payload) => {
|
|
6474
|
+
if (disposed || busy !== void 0 || payload !== void 0 && draft === void 0) return;
|
|
6475
|
+
const ownGeneration = ++generation;
|
|
6476
|
+
busy = payload === void 0 ? "read" : "write";
|
|
6477
|
+
if (payload !== void 0) unconfirmedWrite = true;
|
|
6478
|
+
status.classList.remove("dsh-module-error");
|
|
6479
|
+
status.textContent = payload === void 0 ? copy.loading : copy.saving;
|
|
6480
|
+
updateControls();
|
|
6481
|
+
requestClientModuleView(scope, controller.signal, payload).then((view) => {
|
|
6482
|
+
if (disposed || ownGeneration !== generation) return;
|
|
6483
|
+
unconfirmedWrite = false;
|
|
5620
6484
|
render(view);
|
|
5621
|
-
|
|
5622
|
-
},
|
|
5623
|
-
if (
|
|
6485
|
+
status.textContent = payload === void 0 ? "" : copy.saved;
|
|
6486
|
+
}, (error) => {
|
|
6487
|
+
if (disposed || ownGeneration !== generation) return;
|
|
6488
|
+
if (error instanceof ClientModuleUiConflict) unconfirmedWrite = false;
|
|
6489
|
+
failed(error, payload !== void 0);
|
|
6490
|
+
}).finally(() => {
|
|
6491
|
+
if (disposed || ownGeneration !== generation) return;
|
|
6492
|
+
busy = void 0;
|
|
6493
|
+
updateControls();
|
|
5624
6494
|
});
|
|
5625
6495
|
};
|
|
6496
|
+
search.addEventListener("input", renderList);
|
|
6497
|
+
filter.addEventListener("change", renderList);
|
|
5626
6498
|
save.addEventListener("click", () => {
|
|
5627
|
-
|
|
6499
|
+
if ((draft?.changeCount ?? 0) > 0) runRequest({ excludedClientModules: draft?.excludedIds });
|
|
5628
6500
|
});
|
|
5629
6501
|
reset.addEventListener("click", () => {
|
|
5630
|
-
if (window.confirm(copy.resetConfirm))
|
|
6502
|
+
if (busy === void 0 && draft !== void 0 && window.confirm(copy.resetConfirm)) runRequest({ reset: true });
|
|
6503
|
+
});
|
|
6504
|
+
reread.addEventListener("click", () => {
|
|
6505
|
+
if (busy === void 0 && ((draft?.changeCount ?? 0) === 0 || window.confirm(copy.reloadConfirm))) runRequest();
|
|
5631
6506
|
});
|
|
5632
|
-
const onBack = (event) => {
|
|
5633
|
-
if (!event.cancelable || !dialog.open || event.defaultPrevented) return;
|
|
5634
|
-
event.preventDefault();
|
|
5635
|
-
event.stopImmediatePropagation();
|
|
5636
|
-
dispose();
|
|
5637
|
-
};
|
|
5638
|
-
window.addEventListener("dsh-mobile:native-back", onBack, true);
|
|
5639
6507
|
const dispose = () => {
|
|
5640
6508
|
if (disposed) return;
|
|
5641
6509
|
disposed = true;
|
|
6510
|
+
++generation;
|
|
5642
6511
|
controller.abort();
|
|
5643
|
-
openDialogs.delete(
|
|
6512
|
+
openDialogs.delete(openDialog);
|
|
5644
6513
|
window.removeEventListener("dsh-mobile:native-back", onBack, true);
|
|
6514
|
+
window.removeEventListener("keydown", onEscape, true);
|
|
5645
6515
|
if (dialog.open) dialog.close();
|
|
5646
6516
|
dialog.remove();
|
|
5647
6517
|
if (trigger?.isConnected) trigger.focus({ preventScroll: true });
|
|
5648
6518
|
};
|
|
5649
|
-
|
|
6519
|
+
const requestClose = () => {
|
|
6520
|
+
if (disposed) return;
|
|
6521
|
+
if (unconfirmedWrite && !window.confirm(copy.savingCloseConfirm)) return;
|
|
6522
|
+
if (!unconfirmedWrite && (draft?.changeCount ?? 0) > 0 && !window.confirm(copy.discardConfirm)) return;
|
|
6523
|
+
dispose();
|
|
6524
|
+
};
|
|
6525
|
+
const onBack = (event) => {
|
|
6526
|
+
if (!event.cancelable || !dialog.open || event.defaultPrevented || [...openDialogs].at(-1) !== openDialog) return;
|
|
6527
|
+
event.preventDefault();
|
|
6528
|
+
event.stopImmediatePropagation();
|
|
6529
|
+
requestClose();
|
|
6530
|
+
};
|
|
6531
|
+
const onEscape = (event) => {
|
|
6532
|
+
if (event.key !== "Escape" || event.isComposing || event.ctrlKey || event.altKey || event.metaKey || event.defaultPrevented || !dialog.open || [...openDialogs].at(-1) !== openDialog) return;
|
|
6533
|
+
event.preventDefault();
|
|
6534
|
+
event.stopImmediatePropagation();
|
|
6535
|
+
requestClose();
|
|
6536
|
+
};
|
|
6537
|
+
const openDialog = {
|
|
6538
|
+
dispose,
|
|
6539
|
+
requestClose
|
|
6540
|
+
};
|
|
6541
|
+
openDialogs.add(openDialog);
|
|
6542
|
+
window.addEventListener("dsh-mobile:native-back", onBack, true);
|
|
6543
|
+
window.addEventListener("keydown", onEscape, true);
|
|
6544
|
+
dialog.addEventListener("cancel", (event) => {
|
|
6545
|
+
event.preventDefault();
|
|
6546
|
+
requestClose();
|
|
6547
|
+
});
|
|
5650
6548
|
dialog.addEventListener("close", dispose, { once: true });
|
|
5651
|
-
close.addEventListener("click",
|
|
6549
|
+
close.addEventListener("click", requestClose);
|
|
5652
6550
|
document.body.append(dialog);
|
|
5653
6551
|
dialog.showModal();
|
|
5654
|
-
|
|
5655
|
-
status.textContent = copy.loading;
|
|
5656
|
-
request().then(render, failed).finally(() => {
|
|
5657
|
-
if (!disposed) setBusy(false);
|
|
5658
|
-
});
|
|
6552
|
+
runRequest();
|
|
5659
6553
|
}
|
|
5660
6554
|
/** Settings row used on mobile pages and on the computer's General settings.
|
|
5661
6555
|
* @param props - Selection scope and current locale.
|
|
@@ -6161,42 +7055,29 @@ window.__ModuleLoader__.load({
|
|
|
6161
7055
|
error: "Could not switch computers. Try again."
|
|
6162
7056
|
};
|
|
6163
7057
|
}
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
const
|
|
6167
|
-
const [state, setState] = (0, react.useState)("idle");
|
|
7058
|
+
function useMobileNativeSettingsAction(action) {
|
|
7059
|
+
const [controller] = (0, react.useState)(() => new MobileNativeSettingsActionController(action, () => window.__DSH_MOBILE_NATIVE__));
|
|
7060
|
+
const snapshot = (0, react.useSyncExternalStore)(controller.subscribe, controller.getSnapshot);
|
|
6168
7061
|
(0, react.useEffect)(() => {
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
const bridge = window.__DSH_MOBILE_NATIVE__;
|
|
6172
|
-
if (bridge === void 0) {
|
|
6173
|
-
setNativeReady(false);
|
|
6174
|
-
return;
|
|
6175
|
-
}
|
|
6176
|
-
Promise.resolve().then(() => bridge.capabilities()).then((capabilities) => {
|
|
6177
|
-
if (!disposed) setNativeReady(capabilities.includes("mobile.switch-computer"));
|
|
6178
|
-
}, () => {
|
|
6179
|
-
if (!disposed) setNativeReady(false);
|
|
6180
|
-
});
|
|
6181
|
-
};
|
|
6182
|
-
checkNativeAction();
|
|
6183
|
-
window.addEventListener("dsh-mobile-native-ready", checkNativeAction);
|
|
7062
|
+
controller.start();
|
|
7063
|
+
window.addEventListener("dsh-mobile-native-ready", controller.refresh);
|
|
6184
7064
|
return () => {
|
|
6185
|
-
|
|
6186
|
-
|
|
7065
|
+
window.removeEventListener("dsh-mobile-native-ready", controller.refresh);
|
|
7066
|
+
controller.stop();
|
|
6187
7067
|
};
|
|
6188
|
-
}, []);
|
|
6189
|
-
|
|
7068
|
+
}, [controller]);
|
|
7069
|
+
return {
|
|
7070
|
+
...snapshot,
|
|
7071
|
+
invoke: controller.invoke
|
|
7072
|
+
};
|
|
7073
|
+
}
|
|
7074
|
+
/** Render the native-app-only action in DSH's General settings row style. */
|
|
7075
|
+
function MobileSwitchComputerRow() {
|
|
7076
|
+
const { supported, state, invoke } = useMobileNativeSettingsAction("mobile.switch-computer");
|
|
7077
|
+
if (!supported) return null;
|
|
6190
7078
|
const locale = selectedMobileControlLocale();
|
|
6191
7079
|
const copy = mobileSwitchComputerCopy(locale);
|
|
6192
|
-
const
|
|
6193
|
-
const bridge = window.__DSH_MOBILE_NATIVE__;
|
|
6194
|
-
if (bridge === void 0 || state === "busy") return;
|
|
6195
|
-
setState("busy");
|
|
6196
|
-
bridge.invoke("mobile.switch-computer", {}).catch(() => {
|
|
6197
|
-
setState("error");
|
|
6198
|
-
});
|
|
6199
|
-
};
|
|
7080
|
+
const busy = state === "busy" || state === "success";
|
|
6200
7081
|
return (0, react.createElement)("div", {
|
|
6201
7082
|
className: "dsh-mobile-settings_row",
|
|
6202
7083
|
lang: locale
|
|
@@ -6206,10 +7087,10 @@ window.__ModuleLoader__.load({
|
|
|
6206
7087
|
}, state === "error" ? copy.error : copy.description)), (0, react.createElement)("button", {
|
|
6207
7088
|
type: "button",
|
|
6208
7089
|
className: "dsh-mobile-settings_selector",
|
|
6209
|
-
disabled:
|
|
6210
|
-
"aria-busy":
|
|
7090
|
+
disabled: busy,
|
|
7091
|
+
"aria-busy": busy ? "true" : void 0,
|
|
6211
7092
|
onClick: invoke
|
|
6212
|
-
},
|
|
7093
|
+
}, busy ? copy.busy : state === "error" ? copy.retry : copy.action));
|
|
6213
7094
|
}
|
|
6214
7095
|
function mobileTaskNotificationCopy(locale) {
|
|
6215
7096
|
if (locale === "zh") return {
|
|
@@ -6245,42 +7126,10 @@ window.__ModuleLoader__.load({
|
|
|
6245
7126
|
}
|
|
6246
7127
|
/** Render the Android-only notification permission entry in DSH General settings. */
|
|
6247
7128
|
function MobileTaskNotificationRow() {
|
|
6248
|
-
const
|
|
6249
|
-
|
|
6250
|
-
(0, react.useEffect)(() => {
|
|
6251
|
-
let disposed = false;
|
|
6252
|
-
const checkNativeAction = () => {
|
|
6253
|
-
const bridge = window.__DSH_MOBILE_NATIVE__;
|
|
6254
|
-
if (bridge === void 0) {
|
|
6255
|
-
setNativeReady(false);
|
|
6256
|
-
return;
|
|
6257
|
-
}
|
|
6258
|
-
Promise.resolve().then(() => bridge.capabilities()).then((capabilities) => {
|
|
6259
|
-
if (!disposed) setNativeReady(capabilities.includes("notification.settings"));
|
|
6260
|
-
}, () => {
|
|
6261
|
-
if (!disposed) setNativeReady(false);
|
|
6262
|
-
});
|
|
6263
|
-
};
|
|
6264
|
-
checkNativeAction();
|
|
6265
|
-
window.addEventListener("dsh-mobile-native-ready", checkNativeAction);
|
|
6266
|
-
return () => {
|
|
6267
|
-
disposed = true;
|
|
6268
|
-
window.removeEventListener("dsh-mobile-native-ready", checkNativeAction);
|
|
6269
|
-
};
|
|
6270
|
-
}, []);
|
|
6271
|
-
if (!nativeReady) return null;
|
|
7129
|
+
const { supported, state, invoke } = useMobileNativeSettingsAction("notification.settings");
|
|
7130
|
+
if (!supported) return null;
|
|
6272
7131
|
const locale = selectedMobileControlLocale();
|
|
6273
7132
|
const copy = mobileTaskNotificationCopy(locale);
|
|
6274
|
-
const invoke = () => {
|
|
6275
|
-
const bridge = window.__DSH_MOBILE_NATIVE__;
|
|
6276
|
-
if (bridge === void 0 || state === "busy") return;
|
|
6277
|
-
setState("busy");
|
|
6278
|
-
bridge.invoke("notification.settings", {}).then(() => {
|
|
6279
|
-
setState("success");
|
|
6280
|
-
}, () => {
|
|
6281
|
-
setState("error");
|
|
6282
|
-
});
|
|
6283
|
-
};
|
|
6284
7133
|
return (0, react.createElement)("div", {
|
|
6285
7134
|
className: "dsh-mobile-settings_row",
|
|
6286
7135
|
lang: locale
|
|
@@ -6450,6 +7299,11 @@ window.__ModuleLoader__.load({
|
|
|
6450
7299
|
cpolar_installer_missing: "cpolarExtractFailed",
|
|
6451
7300
|
cpolar_executable_missing: "cpolarExtractFailed",
|
|
6452
7301
|
cpolar_extract_failed: "cpolarExtractFailed",
|
|
7302
|
+
cpolar_extract_tools_failed: "cpolarExtractToolsFailed",
|
|
7303
|
+
cpolar_extract_archive_failed: "cpolarExtractArchiveFailed",
|
|
7304
|
+
cpolar_extract_database_failed: "cpolarExtractDatabaseFailed",
|
|
7305
|
+
cpolar_extract_cabinet_failed: "cpolarExtractCabinetFailed",
|
|
7306
|
+
cpolar_extract_file_failed: "cpolarExtractFileFailed",
|
|
6453
7307
|
cpolar_storage_failed: "cpolarStorageFailed",
|
|
6454
7308
|
cpolar_config_missing: "cpolarConfigMissing",
|
|
6455
7309
|
cpolar_config_invalid: "cpolarConfigInvalid",
|
|
@@ -6531,6 +7385,11 @@ window.__ModuleLoader__.load({
|
|
|
6531
7385
|
"cpolar_component_invalid",
|
|
6532
7386
|
"cpolar_config_missing",
|
|
6533
7387
|
"cpolar_config_invalid",
|
|
7388
|
+
"cpolar_extract_tools_failed",
|
|
7389
|
+
"cpolar_extract_archive_failed",
|
|
7390
|
+
"cpolar_extract_database_failed",
|
|
7391
|
+
"cpolar_extract_cabinet_failed",
|
|
7392
|
+
"cpolar_extract_file_failed",
|
|
6534
7393
|
"funnel_state_invalid",
|
|
6535
7394
|
"cpolar_start_timeout",
|
|
6536
7395
|
"cpolar_stopped",
|
|
@@ -10588,73 +11447,12 @@ window.__ModuleLoader__.load({
|
|
|
10588
11447
|
await navigator.clipboard.writeText(typeof input === "object" && input !== null && "text" in input ? String(input.text) : "");
|
|
10589
11448
|
return { ok: true };
|
|
10590
11449
|
}
|
|
10591
|
-
if (action === "files.pick" || action === "camera.capture") {
|
|
10592
|
-
|
|
10593
|
-
|
|
10594
|
-
|
|
10595
|
-
|
|
10596
|
-
|
|
10597
|
-
return new Promise((resolve, reject) => {
|
|
10598
|
-
let settled = false;
|
|
10599
|
-
let cleaned = false;
|
|
10600
|
-
let cleanupTimer = 0;
|
|
10601
|
-
let watchdogTimer = 0;
|
|
10602
|
-
const cleanup = () => {
|
|
10603
|
-
if (cleaned) return;
|
|
10604
|
-
cleaned = true;
|
|
10605
|
-
if (cleanupTimer !== 0) window.clearTimeout(cleanupTimer);
|
|
10606
|
-
if (watchdogTimer !== 0) window.clearTimeout(watchdogTimer);
|
|
10607
|
-
window.removeEventListener("focus", scheduleCancel);
|
|
10608
|
-
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
10609
|
-
signal.removeEventListener("abort", onAbort);
|
|
10610
|
-
inputElement.removeEventListener("change", onChange);
|
|
10611
|
-
inputElement.removeEventListener("cancel", onCancel);
|
|
10612
|
-
inputElement.remove();
|
|
10613
|
-
};
|
|
10614
|
-
const finish = (callback) => {
|
|
10615
|
-
if (settled) return;
|
|
10616
|
-
settled = true;
|
|
10617
|
-
cleanup();
|
|
10618
|
-
callback();
|
|
10619
|
-
};
|
|
10620
|
-
const onChange = () => {
|
|
10621
|
-
const file = inputElement.files?.[0];
|
|
10622
|
-
finish(() => {
|
|
10623
|
-
resolve(file);
|
|
10624
|
-
});
|
|
10625
|
-
};
|
|
10626
|
-
const onCancel = () => {
|
|
10627
|
-
finish(() => {
|
|
10628
|
-
resolve(void 0);
|
|
10629
|
-
});
|
|
10630
|
-
};
|
|
10631
|
-
const onAbort = () => {
|
|
10632
|
-
finish(() => {
|
|
10633
|
-
reject(abortReason());
|
|
10634
|
-
});
|
|
10635
|
-
};
|
|
10636
|
-
const scheduleCancel = () => {
|
|
10637
|
-
if (!settled && cleanupTimer === 0) cleanupTimer = window.setTimeout(onCancel, 1e3);
|
|
10638
|
-
};
|
|
10639
|
-
const onVisibilityChange = () => {
|
|
10640
|
-
if (document.visibilityState === "visible") scheduleCancel();
|
|
10641
|
-
};
|
|
10642
|
-
inputElement.addEventListener("change", onChange, { once: true });
|
|
10643
|
-
inputElement.addEventListener("cancel", onCancel, { once: true });
|
|
10644
|
-
window.addEventListener("focus", scheduleCancel);
|
|
10645
|
-
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
10646
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
10647
|
-
watchdogTimer = window.setTimeout(onCancel, 3e5);
|
|
10648
|
-
try {
|
|
10649
|
-
document.body.append(inputElement);
|
|
10650
|
-
inputElement.click();
|
|
10651
|
-
} catch (error) {
|
|
10652
|
-
finish(() => {
|
|
10653
|
-
reject(error);
|
|
10654
|
-
});
|
|
10655
|
-
}
|
|
10656
|
-
});
|
|
10657
|
-
}
|
|
11450
|
+
if (action === "files.pick" || action === "camera.capture") return pickBrowserFiles({
|
|
11451
|
+
signal,
|
|
11452
|
+
accept: action === "camera.capture" ? "image/*" : "*/*",
|
|
11453
|
+
...action === "camera.capture" ? { capture: "environment" } : {},
|
|
11454
|
+
abortReason
|
|
11455
|
+
}).then((files) => files?.[0]);
|
|
10658
11456
|
throw new Error("native capability is unavailable");
|
|
10659
11457
|
};
|
|
10660
11458
|
const makeApi = (id, hostGeneration, controller, surfaces, surfaceIds) => {
|