dsh-mobile 0.6.4 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -4
- package/CONTRIBUTORS.md +8 -6
- package/README.en.md +9 -6
- package/README.md +9 -6
- package/apps/mobile/README.md +7 -7
- 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 +969 -231
- 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 +6 -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, {
|
|
@@ -3236,6 +3251,8 @@ window.__ModuleLoader__.load({
|
|
|
3236
3251
|
.dsh-mobile-settings_fontFamily > span { flex:1 1 100px; }
|
|
3237
3252
|
.dsh-mobile-settings_fontFamily input { box-sizing:border-box; flex:1 1 180px; width:100%; min-width:0; min-height:48px; padding:8px 12px; border:0.5px solid var(--dsw-alias-border-l2); border-radius:10px; background:var(--dsw-alias-bg-module-platform); color:var(--dsw-alias-label-primary); font:inherit; }
|
|
3238
3253
|
.dsh-mobile-settings_fontFamily input:focus-visible,.dsh-mobile-settings_fontDetails > summary:focus-visible { outline:2px solid var(--dsw-alias-label-primary-bluish,#2563eb); outline-offset:2px; }
|
|
3254
|
+
html.dsh-native-mobile-active [data-dsh-mobile-settings-header] { box-sizing:border-box !important; height:56px !important; min-height:56px !important; padding:4px 12px !important; }
|
|
3255
|
+
html.dsh-native-mobile-active [data-dsh-mobile-settings-header] [class*="_close"] { box-sizing:border-box !important; flex:none !important; width:48px !important; min-width:48px !important; height:48px !important; min-height:48px !important; }
|
|
3239
3256
|
/* The mobile row replaces the Host-backed font control, not the theme selector. */
|
|
3240
3257
|
html.dsh-native-mobile-active [data-slot="settings.general.item"]:has([data-mobile-font-setting]) > :not([data-mobile-font-setting])[class*="_row"]:has([class*="_control"] > [class*="_stepper"] > [class*="_arrows"]) { display:none !important; }
|
|
3241
3258
|
@media ${NATIVE_MOBILE_OVERLAY_QUERY} {
|
|
@@ -3305,8 +3322,6 @@ window.__ModuleLoader__.load({
|
|
|
3305
3322
|
[data-dsh-mobile-settings-list] [class*="_navCell"] { flex:0 0 auto !important; min-width:max-content !important; height:44px !important; padding:10px 12px !important; }
|
|
3306
3323
|
[data-dsh-mobile-settings-list] [aria-current="true"] { border-color:transparent !important; outline:0 !important; box-shadow:none !important; }
|
|
3307
3324
|
[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
3325
|
[data-dsh-mobile-settings-options] { box-sizing:border-box !important; width:100% !important; padding:4px 16px max(24px,env(safe-area-inset-bottom)) !important; overflow-x:hidden !important; }
|
|
3311
3326
|
[data-dsh-mobile-settings-options] > * { width:100% !important; min-width:0 !important; }
|
|
3312
3327
|
[data-dsh-mobile-settings-options] [data-slot="settings.general.item"] > [class*="_row"] { flex-direction:column !important; align-items:stretch !important; gap:12px !important; }
|
|
@@ -4778,6 +4793,63 @@ window.__ModuleLoader__.load({
|
|
|
4778
4793
|
};
|
|
4779
4794
|
}
|
|
4780
4795
|
//#endregion
|
|
4796
|
+
//#region src/mobile-font-capabilities.ts
|
|
4797
|
+
const NO_ADVANCED_MOBILE_TYPOGRAPHY = Object.freeze({
|
|
4798
|
+
codeSize: false,
|
|
4799
|
+
textFamily: false,
|
|
4800
|
+
codeFamily: false
|
|
4801
|
+
});
|
|
4802
|
+
/**
|
|
4803
|
+
* Probe computed fonts derived from DSH's public Markdown tokens. Input values are
|
|
4804
|
+
* restored synchronously before painting; merely defining the input variable is not support.
|
|
4805
|
+
* @param document - Live page document in the current browsing context.
|
|
4806
|
+
* @returns Independently observed advanced typography support.
|
|
4807
|
+
*/
|
|
4808
|
+
function readMobileTypographyCapabilities(document) {
|
|
4809
|
+
const style = document.body.style;
|
|
4810
|
+
const text = document.createElement("span");
|
|
4811
|
+
const code = document.createElement("span");
|
|
4812
|
+
for (const probe of [text, code]) {
|
|
4813
|
+
probe.setAttribute("aria-hidden", "true");
|
|
4814
|
+
probe.style.cssText = "position:fixed;visibility:hidden;pointer-events:none;contain:strict;width:0;height:0;overflow:hidden";
|
|
4815
|
+
}
|
|
4816
|
+
text.style.font = "var(--dsw-font-markdown-base, 14px sans-serif)";
|
|
4817
|
+
code.style.font = "var(--dsw-font-markdown-code-block, 11px monospace)";
|
|
4818
|
+
document.body.append(text, code);
|
|
4819
|
+
const previous = /* @__PURE__ */ new Map();
|
|
4820
|
+
const sample = (property, value) => {
|
|
4821
|
+
if (!previous.has(property)) previous.set(property, {
|
|
4822
|
+
value: style.getPropertyValue(property),
|
|
4823
|
+
priority: style.getPropertyPriority(property)
|
|
4824
|
+
});
|
|
4825
|
+
style.setProperty(property, value);
|
|
4826
|
+
};
|
|
4827
|
+
try {
|
|
4828
|
+
sample("--dsh-code-font-size", "23px");
|
|
4829
|
+
const firstSize = getComputedStyle(code).fontSize;
|
|
4830
|
+
sample("--dsh-code-font-size", "24px");
|
|
4831
|
+
const secondSize = getComputedStyle(code).fontSize;
|
|
4832
|
+
sample("--dsh-font-family-text", "\"dsh-mobile-text-capability-a\"");
|
|
4833
|
+
const firstTextFamily = getComputedStyle(text).fontFamily;
|
|
4834
|
+
sample("--dsh-font-family-text", "\"dsh-mobile-text-capability-b\"");
|
|
4835
|
+
const secondTextFamily = getComputedStyle(text).fontFamily;
|
|
4836
|
+
sample("--dsh-font-family-code", "\"dsh-mobile-code-capability-a\"");
|
|
4837
|
+
const firstCodeFamily = getComputedStyle(code).fontFamily;
|
|
4838
|
+
sample("--dsh-font-family-code", "\"dsh-mobile-code-capability-b\"");
|
|
4839
|
+
const secondCodeFamily = getComputedStyle(code).fontFamily;
|
|
4840
|
+
return Object.freeze({
|
|
4841
|
+
codeSize: firstSize === "23px" && secondSize === "24px",
|
|
4842
|
+
textFamily: firstTextFamily.includes("dsh-mobile-text-capability-a") && secondTextFamily.includes("dsh-mobile-text-capability-b"),
|
|
4843
|
+
codeFamily: firstCodeFamily.includes("dsh-mobile-code-capability-a") && secondCodeFamily.includes("dsh-mobile-code-capability-b")
|
|
4844
|
+
});
|
|
4845
|
+
} finally {
|
|
4846
|
+
for (const [property, value] of previous) if (value.value === "") style.removeProperty(property);
|
|
4847
|
+
else style.setProperty(property, value.value, value.priority);
|
|
4848
|
+
text.remove();
|
|
4849
|
+
code.remove();
|
|
4850
|
+
}
|
|
4851
|
+
}
|
|
4852
|
+
//#endregion
|
|
4781
4853
|
//#region src/mobile-font.ts
|
|
4782
4854
|
/** Device-local typography for authenticated mobile pages, without Host settings writes. */
|
|
4783
4855
|
const MOBILE_FONT_STORAGE_KEY = "dsh-mobile.content-font-size.v1";
|
|
@@ -4878,6 +4950,8 @@ window.__ModuleLoader__.load({
|
|
|
4878
4950
|
storage;
|
|
4879
4951
|
typography = defaultTypography();
|
|
4880
4952
|
listeners = /* @__PURE__ */ new Set();
|
|
4953
|
+
capabilities = NO_ADVANCED_MOBILE_TYPOGRAPHY;
|
|
4954
|
+
capabilityListeners = /* @__PURE__ */ new Set();
|
|
4881
4955
|
constructor(storage) {
|
|
4882
4956
|
this.storage = storage;
|
|
4883
4957
|
let legacySize = 16;
|
|
@@ -4897,6 +4971,19 @@ window.__ModuleLoader__.load({
|
|
|
4897
4971
|
this.listeners.delete(listener);
|
|
4898
4972
|
};
|
|
4899
4973
|
};
|
|
4974
|
+
getCapabilitiesSnapshot = () => this.capabilities;
|
|
4975
|
+
subscribeCapabilities = (listener) => {
|
|
4976
|
+
this.capabilityListeners.add(listener);
|
|
4977
|
+
return () => {
|
|
4978
|
+
this.capabilityListeners.delete(listener);
|
|
4979
|
+
};
|
|
4980
|
+
};
|
|
4981
|
+
/** Update observed theme support without changing stored values or Host settings. */
|
|
4982
|
+
updateCapabilities(capabilities) {
|
|
4983
|
+
if (this.capabilities.codeSize === capabilities.codeSize && this.capabilities.textFamily === capabilities.textFamily && this.capabilities.codeFamily === capabilities.codeFamily) return;
|
|
4984
|
+
this.capabilities = Object.freeze({ ...capabilities });
|
|
4985
|
+
for (const listener of this.capabilityListeners) listener();
|
|
4986
|
+
}
|
|
4900
4987
|
/** Apply a user-selected size locally; never send it to DSH's settings RPC. */
|
|
4901
4988
|
set(size) {
|
|
4902
4989
|
this.setRoleSize("text", size);
|
|
@@ -4931,6 +5018,23 @@ window.__ModuleLoader__.load({
|
|
|
4931
5018
|
if (MOBILE_FONT_ROLES.every((role) => this.typography.sizes[role] === next.sizes[role] && this.typography.families[role] === "")) return;
|
|
4932
5019
|
this.persist(next);
|
|
4933
5020
|
}
|
|
5021
|
+
/** Reset visible supported controls while retaining preferences an older theme cannot consume. */
|
|
5022
|
+
resetSupported() {
|
|
5023
|
+
const defaults = defaultTypography();
|
|
5024
|
+
const sizes = {
|
|
5025
|
+
text: defaults.sizes.text,
|
|
5026
|
+
code: this.capabilities.codeSize ? defaults.sizes.code : this.typography.sizes.code
|
|
5027
|
+
};
|
|
5028
|
+
const families = {
|
|
5029
|
+
text: this.capabilities.textFamily ? "" : this.typography.families.text,
|
|
5030
|
+
code: this.capabilities.codeFamily ? "" : this.typography.families.code
|
|
5031
|
+
};
|
|
5032
|
+
if (MOBILE_FONT_ROLES.every((role) => this.typography.sizes[role] === sizes[role] && this.typography.families[role] === families[role])) return;
|
|
5033
|
+
this.persist({
|
|
5034
|
+
sizes: Object.freeze(sizes),
|
|
5035
|
+
families: Object.freeze(families)
|
|
5036
|
+
});
|
|
5037
|
+
}
|
|
4934
5038
|
persist(typography) {
|
|
4935
5039
|
this.typography = Object.freeze(typography);
|
|
4936
5040
|
try {
|
|
@@ -4942,7 +5046,11 @@ window.__ModuleLoader__.load({
|
|
|
4942
5046
|
for (const listener of this.listeners) listener();
|
|
4943
5047
|
}
|
|
4944
5048
|
};
|
|
4945
|
-
/**
|
|
5049
|
+
/**
|
|
5050
|
+
* Keep mobile typography independent from Host theme changes, including body-level portals.
|
|
5051
|
+
* Recheck head-owned stylesheets and body presentation attributes, not conversation DOM updates.
|
|
5052
|
+
* Arbitrary stylesheets inserted into body are not observed.
|
|
5053
|
+
*/
|
|
4946
5054
|
function installMobileFontPreference() {
|
|
4947
5055
|
let storage = null;
|
|
4948
5056
|
try {
|
|
@@ -4958,11 +5066,26 @@ window.__ModuleLoader__.load({
|
|
|
4958
5066
|
const owned = /* @__PURE__ */ new Map();
|
|
4959
5067
|
const apply = () => {
|
|
4960
5068
|
const typography = preference.getTypographySnapshot();
|
|
5069
|
+
const capabilities = preference.getCapabilitiesSnapshot();
|
|
4961
5070
|
for (const role of MOBILE_FONT_ROLES) for (const [property, value] of [[roleSpecs[role].variable, `${typography.sizes[role]}px`], [`--dsh-font-family-${role}`, typography.families[role]]]) {
|
|
4962
5071
|
const current = style.getPropertyValue(property);
|
|
4963
5072
|
const priority = style.getPropertyPriority(property);
|
|
4964
5073
|
const last = owned.get(property);
|
|
4965
|
-
if (
|
|
5074
|
+
if (!(property === roleSpecs[role].variable ? role === "text" || capabilities.codeSize : role === "text" ? capabilities.textFamily : capabilities.codeFamily)) {
|
|
5075
|
+
if (last !== void 0) {
|
|
5076
|
+
if (current === last.value && priority === last.priority) {
|
|
5077
|
+
const foreign = previous.get(property);
|
|
5078
|
+
if (foreign.value === "") style.removeProperty(property);
|
|
5079
|
+
else style.setProperty(property, foreign.value, foreign.priority);
|
|
5080
|
+
} else previous.set(property, {
|
|
5081
|
+
value: current,
|
|
5082
|
+
priority
|
|
5083
|
+
});
|
|
5084
|
+
owned.delete(property);
|
|
5085
|
+
}
|
|
5086
|
+
continue;
|
|
5087
|
+
}
|
|
5088
|
+
if (last === void 0 || current !== last.value || priority !== last.priority) previous.set(property, {
|
|
4966
5089
|
value: current,
|
|
4967
5090
|
priority
|
|
4968
5091
|
});
|
|
@@ -4975,17 +5098,55 @@ window.__ModuleLoader__.load({
|
|
|
4975
5098
|
else style.setProperty(property, value);
|
|
4976
5099
|
}
|
|
4977
5100
|
};
|
|
4978
|
-
|
|
4979
|
-
const
|
|
4980
|
-
const
|
|
5101
|
+
let lastBodyState = "";
|
|
5102
|
+
const bodyState = () => `${style.cssText}|${document.body.className}|${document.body.getAttribute("data-ds-dark-theme")}`;
|
|
5103
|
+
const applyAndRemember = () => {
|
|
5104
|
+
apply();
|
|
5105
|
+
lastBodyState = bodyState();
|
|
5106
|
+
};
|
|
5107
|
+
const refresh = () => {
|
|
5108
|
+
preference.updateCapabilities(readMobileTypographyCapabilities(document));
|
|
5109
|
+
applyAndRemember();
|
|
5110
|
+
};
|
|
5111
|
+
refresh();
|
|
5112
|
+
const unsubscribe = preference.subscribe(applyAndRemember);
|
|
5113
|
+
const observer = new MutationObserver(() => {
|
|
5114
|
+
if (bodyState() !== lastBodyState) refresh();
|
|
5115
|
+
});
|
|
4981
5116
|
observer.observe(document.body, {
|
|
4982
5117
|
attributes: true,
|
|
4983
|
-
attributeFilter: [
|
|
5118
|
+
attributeFilter: [
|
|
5119
|
+
"style",
|
|
5120
|
+
"class",
|
|
5121
|
+
"data-ds-dark-theme"
|
|
5122
|
+
]
|
|
5123
|
+
});
|
|
5124
|
+
const themeObserver = new MutationObserver((records) => {
|
|
5125
|
+
const isSheet = (node) => node instanceof Element && (node.matches("style,link[rel=\"stylesheet\"]") || node.querySelector("style,link[rel=\"stylesheet\"]") !== null);
|
|
5126
|
+
if (records.some((record) => record.target instanceof Element && record.target.closest("style,link[rel=\"stylesheet\"]") !== null || [...record.addedNodes, ...record.removedNodes].some(isSheet))) refresh();
|
|
5127
|
+
});
|
|
5128
|
+
themeObserver.observe(document.head, {
|
|
5129
|
+
subtree: true,
|
|
5130
|
+
childList: true,
|
|
5131
|
+
characterData: true,
|
|
5132
|
+
attributes: true,
|
|
5133
|
+
attributeFilter: [
|
|
5134
|
+
"href",
|
|
5135
|
+
"media",
|
|
5136
|
+
"disabled",
|
|
5137
|
+
"rel"
|
|
5138
|
+
]
|
|
4984
5139
|
});
|
|
5140
|
+
const sheetLoaded = (event) => {
|
|
5141
|
+
if (event.target instanceof Element && event.target.matches("link[rel=\"stylesheet\"]")) refresh();
|
|
5142
|
+
};
|
|
5143
|
+
document.head.addEventListener("load", sheetLoaded, true);
|
|
4985
5144
|
return {
|
|
4986
5145
|
preference,
|
|
4987
5146
|
dispose: () => {
|
|
4988
5147
|
observer.disconnect();
|
|
5148
|
+
themeObserver.disconnect();
|
|
5149
|
+
document.head.removeEventListener("load", sheetLoaded, true);
|
|
4989
5150
|
unsubscribe();
|
|
4990
5151
|
for (const [property, value] of previous) {
|
|
4991
5152
|
const last = owned.get(property);
|
|
@@ -5008,7 +5169,8 @@ window.__ModuleLoader__.load({
|
|
|
5008
5169
|
familyHint: "填写设备已有的字体,多个名称用逗号分隔;留空使用默认字体。",
|
|
5009
5170
|
codeSize: "代码字号",
|
|
5010
5171
|
textFamily: "正文字体",
|
|
5011
|
-
codeFamily: "代码字体"
|
|
5172
|
+
codeFamily: "代码字体",
|
|
5173
|
+
limited: "只显示当前 DSH 主题支持的字体选项;其他已保存的设置会保留。"
|
|
5012
5174
|
},
|
|
5013
5175
|
en: {
|
|
5014
5176
|
title: "Mobile font size",
|
|
@@ -5021,7 +5183,8 @@ window.__ModuleLoader__.load({
|
|
|
5021
5183
|
familyHint: "Use fonts already on your device, separated by commas. Leave empty for the default.",
|
|
5022
5184
|
codeSize: "Code font size",
|
|
5023
5185
|
textFamily: "Text font",
|
|
5024
|
-
codeFamily: "Code font"
|
|
5186
|
+
codeFamily: "Code font",
|
|
5187
|
+
limited: "Only font options supported by the current DSH theme are shown. Other saved preferences are retained."
|
|
5025
5188
|
},
|
|
5026
5189
|
it: {
|
|
5027
5190
|
title: "Dimensione testo mobile",
|
|
@@ -5034,7 +5197,8 @@ window.__ModuleLoader__.load({
|
|
|
5034
5197
|
familyHint: "Usa caratteri presenti sul dispositivo, separati da virgole. Lascia vuoto per il predefinito.",
|
|
5035
5198
|
codeSize: "Dimensione codice",
|
|
5036
5199
|
textFamily: "Carattere testo",
|
|
5037
|
-
codeFamily: "Carattere codice"
|
|
5200
|
+
codeFamily: "Carattere codice",
|
|
5201
|
+
limited: "Sono mostrate solo le opzioni supportate dal tema DSH corrente. Le altre preferenze salvate vengono mantenute."
|
|
5038
5202
|
}
|
|
5039
5203
|
};
|
|
5040
5204
|
function FontFamilyInput({ preference, role, value, label, placeholder }) {
|
|
@@ -5062,6 +5226,7 @@ window.__ModuleLoader__.load({
|
|
|
5062
5226
|
/** General settings row matching the existing Mobile controls and DSH theme tokens. */
|
|
5063
5227
|
function MobileFontSizeRow({ preference, locale }) {
|
|
5064
5228
|
const typography = (0, react.useSyncExternalStore)(preference.subscribe, preference.getTypographySnapshot);
|
|
5229
|
+
const capabilities = (0, react.useSyncExternalStore)(preference.subscribeCapabilities, preference.getCapabilitiesSnapshot);
|
|
5065
5230
|
const copy = messages$1[locale];
|
|
5066
5231
|
const controls = (role, title) => {
|
|
5067
5232
|
const size = typography.sizes[role];
|
|
@@ -5092,18 +5257,22 @@ window.__ModuleLoader__.load({
|
|
|
5092
5257
|
return (0, react.createElement)("div", {
|
|
5093
5258
|
lang: locale,
|
|
5094
5259
|
"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)),
|
|
5260
|
+
}, (0, react.createElement)("div", { className: "dsh-mobile-settings_row" }, (0, react.createElement)("div", { className: "dsh-mobile-settings_rowText" }, (0, react.createElement)("div", { className: "dsh-mobile-settings_title" }, copy.title), (0, react.createElement)("div", { className: "dsh-mobile-settings_desc" }, copy.description)), controls("text", copy.title)), !Object.values(capabilities).every(Boolean) && (0, react.createElement)("p", {
|
|
5261
|
+
className: "dsh-mobile-settings_desc",
|
|
5262
|
+
"data-mobile-font-limited": true
|
|
5263
|
+
}, copy.limited), Object.values(capabilities).some(Boolean) && (0, react.createElement)("details", { className: "dsh-mobile-settings_fontDetails" }, (0, react.createElement)("summary", null, copy.more), (capabilities.textFamily || capabilities.codeFamily) && (0, react.createElement)("p", { className: "dsh-mobile-settings_desc" }, copy.familyHint), capabilities.codeSize && (0, react.createElement)("div", { className: "dsh-mobile-settings_row" }, (0, react.createElement)("span", { className: "dsh-mobile-settings_rowText dsh-mobile-settings_title" }, copy.codeSize), controls("code", copy.codeSize)), ...MOBILE_FONT_ROLES.filter((role) => role === "text" ? capabilities.textFamily : capabilities.codeFamily).map((role) => (0, react.createElement)(FontFamilyInput, {
|
|
5096
5264
|
key: role,
|
|
5097
5265
|
preference,
|
|
5098
5266
|
role,
|
|
5099
5267
|
value: typography.families[role],
|
|
5100
5268
|
label: role === "text" ? copy.textFamily : copy.codeFamily,
|
|
5101
5269
|
placeholder: copy.defaultFamily
|
|
5102
|
-
})), (0, react.createElement)("button", {
|
|
5270
|
+
}))), (0, react.createElement)("div", { className: "dsh-mobile-settings_row" }, (0, react.createElement)("button", {
|
|
5103
5271
|
type: "button",
|
|
5104
5272
|
className: "dsh-mobile-settings_selector",
|
|
5273
|
+
"data-mobile-font-reset": true,
|
|
5105
5274
|
onClick: () => {
|
|
5106
|
-
preference.
|
|
5275
|
+
preference.resetSupported();
|
|
5107
5276
|
}
|
|
5108
5277
|
}, copy.reset)));
|
|
5109
5278
|
}
|
|
@@ -5326,6 +5495,116 @@ window.__ModuleLoader__.load({
|
|
|
5326
5495
|
}, copy.reset)));
|
|
5327
5496
|
}
|
|
5328
5497
|
//#endregion
|
|
5498
|
+
//#region src/mobile-native-settings-action.ts
|
|
5499
|
+
/** Own capability discovery and explicit calls; native actions retain the App's own timeout. */
|
|
5500
|
+
var MobileNativeSettingsActionController = class {
|
|
5501
|
+
action;
|
|
5502
|
+
getBridge;
|
|
5503
|
+
snapshot = Object.freeze({
|
|
5504
|
+
supported: false,
|
|
5505
|
+
state: "idle"
|
|
5506
|
+
});
|
|
5507
|
+
listeners = /* @__PURE__ */ new Set();
|
|
5508
|
+
bridge;
|
|
5509
|
+
active = false;
|
|
5510
|
+
generation = 0;
|
|
5511
|
+
probe = 0;
|
|
5512
|
+
attempt = 0;
|
|
5513
|
+
constructor(action, getBridge) {
|
|
5514
|
+
this.action = action;
|
|
5515
|
+
this.getBridge = getBridge;
|
|
5516
|
+
}
|
|
5517
|
+
getSnapshot = () => this.snapshot;
|
|
5518
|
+
subscribe = (listener) => {
|
|
5519
|
+
this.listeners.add(listener);
|
|
5520
|
+
return () => {
|
|
5521
|
+
this.listeners.delete(listener);
|
|
5522
|
+
};
|
|
5523
|
+
};
|
|
5524
|
+
start() {
|
|
5525
|
+
this.active = true;
|
|
5526
|
+
this.publish({
|
|
5527
|
+
supported: false,
|
|
5528
|
+
state: "idle"
|
|
5529
|
+
});
|
|
5530
|
+
this.refresh();
|
|
5531
|
+
}
|
|
5532
|
+
stop() {
|
|
5533
|
+
this.active = false;
|
|
5534
|
+
this.generation += 1;
|
|
5535
|
+
this.probe += 1;
|
|
5536
|
+
this.attempt += 1;
|
|
5537
|
+
this.bridge = void 0;
|
|
5538
|
+
}
|
|
5539
|
+
/** Recheck support without replaying or invalidating an in-flight call to the same adapter. */
|
|
5540
|
+
refresh = () => {
|
|
5541
|
+
if (!this.active) return;
|
|
5542
|
+
const bridge = this.getBridge();
|
|
5543
|
+
const probe = ++this.probe;
|
|
5544
|
+
if (bridge !== this.bridge) {
|
|
5545
|
+
this.bridge = bridge;
|
|
5546
|
+
this.generation += 1;
|
|
5547
|
+
this.attempt += 1;
|
|
5548
|
+
this.publish({
|
|
5549
|
+
supported: false,
|
|
5550
|
+
state: "idle"
|
|
5551
|
+
});
|
|
5552
|
+
}
|
|
5553
|
+
if (bridge === void 0) return;
|
|
5554
|
+
const generation = this.generation;
|
|
5555
|
+
const settle = (supported) => {
|
|
5556
|
+
if (!this.current(bridge, generation) || probe !== this.probe) return;
|
|
5557
|
+
if (!supported) this.attempt += 1;
|
|
5558
|
+
this.publish({
|
|
5559
|
+
supported,
|
|
5560
|
+
state: supported ? this.snapshot.state : "idle"
|
|
5561
|
+
});
|
|
5562
|
+
};
|
|
5563
|
+
Promise.resolve().then(() => {
|
|
5564
|
+
if (!this.current(bridge, generation) || probe !== this.probe) return void 0;
|
|
5565
|
+
return bridge.capabilities();
|
|
5566
|
+
}).then((capabilities) => {
|
|
5567
|
+
settle(capabilities?.includes(this.action) === true);
|
|
5568
|
+
}, () => {
|
|
5569
|
+
settle(false);
|
|
5570
|
+
});
|
|
5571
|
+
};
|
|
5572
|
+
/** Send at most one explicit action; late replies cannot update a replaced or unmounted row. */
|
|
5573
|
+
invoke = () => {
|
|
5574
|
+
const bridge = this.bridge;
|
|
5575
|
+
if (bridge === void 0 || !this.snapshot.supported || !this.current(bridge, this.generation) || this.snapshot.state === "busy" || this.action === "mobile.switch-computer" && this.snapshot.state === "success") return;
|
|
5576
|
+
const generation = this.generation;
|
|
5577
|
+
const attempt = ++this.attempt;
|
|
5578
|
+
this.publish({
|
|
5579
|
+
supported: true,
|
|
5580
|
+
state: "busy"
|
|
5581
|
+
});
|
|
5582
|
+
const current = () => this.current(bridge, generation) && attempt === this.attempt && this.snapshot.supported;
|
|
5583
|
+
Promise.resolve().then(() => {
|
|
5584
|
+
if (!current()) return void 0;
|
|
5585
|
+
return bridge.invoke(this.action, {});
|
|
5586
|
+
}).then(() => {
|
|
5587
|
+
if (current()) this.publish({
|
|
5588
|
+
supported: true,
|
|
5589
|
+
state: "success"
|
|
5590
|
+
});
|
|
5591
|
+
}, () => {
|
|
5592
|
+
if (current()) this.publish({
|
|
5593
|
+
supported: true,
|
|
5594
|
+
state: "error"
|
|
5595
|
+
});
|
|
5596
|
+
});
|
|
5597
|
+
};
|
|
5598
|
+
current(bridge, generation) {
|
|
5599
|
+
return this.active && generation === this.generation && this.bridge === bridge && this.getBridge() === bridge;
|
|
5600
|
+
}
|
|
5601
|
+
publish(snapshot) {
|
|
5602
|
+
if (snapshot.supported === this.snapshot.supported && snapshot.state === this.snapshot.state) return;
|
|
5603
|
+
this.snapshot = Object.freeze(snapshot);
|
|
5604
|
+
for (const listener of this.listeners) listener();
|
|
5605
|
+
}
|
|
5606
|
+
};
|
|
5607
|
+
//#endregion
|
|
5329
5608
|
//#region src/local-admin-host.ts
|
|
5330
5609
|
/** Browser-safe desktop-admin Host checks. Do not import Node APIs here. */
|
|
5331
5610
|
const DESKTOP_ADMIN_HEADER = "x-dsh-mobile-desktop-admin";
|
|
@@ -5386,65 +5665,282 @@ window.__ModuleLoader__.load({
|
|
|
5386
5665
|
return (protocol === "dsh-app:" ? hostname === "app" : (protocol === "" || protocol === "http:" || protocol === "https:") && isLocalAdminHostname(hostname)) && frontend !== "dedicated" && !new URLSearchParams(query).has("dsh-mobile-preview");
|
|
5387
5666
|
}
|
|
5388
5667
|
//#endregion
|
|
5668
|
+
//#region src/client-module-presentation.ts
|
|
5669
|
+
/** Group boot-protected entries before considering their package namespace.
|
|
5670
|
+
* @param entry - Catalog entry with server-owned required and dependency fields.
|
|
5671
|
+
* @returns Display group, never an authorization or compatibility decision.
|
|
5672
|
+
*/
|
|
5673
|
+
function clientModuleGroup(entry) {
|
|
5674
|
+
if (entry.required) return "required";
|
|
5675
|
+
return entry.id.startsWith("@deepseek-ai/") ? "advanced" : "extensions";
|
|
5676
|
+
}
|
|
5677
|
+
/** Format the package leaf mechanically; the catalog has no product titles.
|
|
5678
|
+
* @param id - Exact module ID from the server catalog.
|
|
5679
|
+
* @returns Unscoped package words without guessed function or plugin ownership.
|
|
5680
|
+
*/
|
|
5681
|
+
function clientModuleDisplayName(id) {
|
|
5682
|
+
const leaf = id.startsWith("@") && id.includes("/") ? id.slice(id.indexOf("/") + 1) : id;
|
|
5683
|
+
return (leaf.replace(/^dsh-(?:client-(?:ui-)?)?/u, "") || leaf || id).replace(/[-_]+/gu, " ");
|
|
5684
|
+
}
|
|
5685
|
+
function matchesQuery(id, query) {
|
|
5686
|
+
const needle = query.trim().toLowerCase();
|
|
5687
|
+
return needle === "" || `${id}\n${clientModuleDisplayName(id)}`.toLowerCase().includes(needle);
|
|
5688
|
+
}
|
|
5689
|
+
/** Match the visible title or exact ID and the current draft loading choice.
|
|
5690
|
+
* @param entry - Catalog entry to consider.
|
|
5691
|
+
* @param excluded - Current draft exclusions, not a runtime loading observation.
|
|
5692
|
+
* @param query - User-entered search text.
|
|
5693
|
+
* @param filter - All, selected for loading, or selected for omission.
|
|
5694
|
+
* @returns Whether the row belongs in the current filtered list.
|
|
5695
|
+
*/
|
|
5696
|
+
function clientModuleMatches(entry, excluded, query, filter) {
|
|
5697
|
+
const omitted = !entry.required && excluded.has(entry.id);
|
|
5698
|
+
return matchesQuery(entry.id, query) && (filter === "all" || (filter === "excluded" ? omitted : !omitted));
|
|
5699
|
+
}
|
|
5700
|
+
/** Editable selections retain unknown server exclusions and never cascade dependencies. */
|
|
5701
|
+
var ClientModuleDraft = class {
|
|
5702
|
+
entries;
|
|
5703
|
+
initial;
|
|
5704
|
+
excluded;
|
|
5705
|
+
entriesById;
|
|
5706
|
+
constructor(entries, excludedIds) {
|
|
5707
|
+
this.entries = entries;
|
|
5708
|
+
this.initial = new Set(excludedIds);
|
|
5709
|
+
this.excluded = new Set(excludedIds);
|
|
5710
|
+
this.entriesById = new Map(entries.map((entry) => [entry.id, entry]));
|
|
5711
|
+
}
|
|
5712
|
+
/** @returns Number of changed loading choices, including hidden filtered rows. */
|
|
5713
|
+
get changeCount() {
|
|
5714
|
+
let count = 0;
|
|
5715
|
+
for (const id of this.initial) if (!this.excluded.has(id)) count += 1;
|
|
5716
|
+
for (const id of this.excluded) if (!this.initial.has(id)) count += 1;
|
|
5717
|
+
return count;
|
|
5718
|
+
}
|
|
5719
|
+
/** @returns A snapshot of exclusions suitable for authenticated saving. */
|
|
5720
|
+
get excludedIds() {
|
|
5721
|
+
return [...this.excluded];
|
|
5722
|
+
}
|
|
5723
|
+
/** @returns Retained exclusions that cannot be saved against the current catalog. */
|
|
5724
|
+
get invalidExclusions() {
|
|
5725
|
+
return [...this.excluded].flatMap((id) => {
|
|
5726
|
+
const entry = this.entriesById.get(id);
|
|
5727
|
+
return entry === void 0 ? [{
|
|
5728
|
+
id,
|
|
5729
|
+
reason: "missing"
|
|
5730
|
+
}] : entry.required ? [{
|
|
5731
|
+
id,
|
|
5732
|
+
reason: "required"
|
|
5733
|
+
}] : [];
|
|
5734
|
+
});
|
|
5735
|
+
}
|
|
5736
|
+
/** Explicitly remove an old exclusion; ordinary optional choices use setLoaded.
|
|
5737
|
+
* @param id - Exact ID of a retained missing or now-required exclusion.
|
|
5738
|
+
* @returns Whether this draft changed; no server request is made.
|
|
5739
|
+
*/
|
|
5740
|
+
clearInvalidExclusion(id) {
|
|
5741
|
+
const entry = this.entriesById.get(id);
|
|
5742
|
+
if (!this.initial.has(id) || entry !== void 0 && !entry.required) return false;
|
|
5743
|
+
return this.excluded.delete(id);
|
|
5744
|
+
}
|
|
5745
|
+
/** @returns Whether a catalog entry is selected for loading, not already loaded. */
|
|
5746
|
+
isLoaded(id) {
|
|
5747
|
+
return this.entriesById.get(id)?.required === true || !this.excluded.has(id);
|
|
5748
|
+
}
|
|
5749
|
+
/** Change one optional choice; required and unknown entries cannot be edited.
|
|
5750
|
+
* @param id - Exact catalog ID.
|
|
5751
|
+
* @param loaded - Whether to load the entry on the next open.
|
|
5752
|
+
*/
|
|
5753
|
+
setLoaded(id, loaded) {
|
|
5754
|
+
const entry = this.entriesById.get(id);
|
|
5755
|
+
if (entry === void 0 || entry.required) return;
|
|
5756
|
+
if (loaded) this.excluded.delete(id);
|
|
5757
|
+
else this.excluded.add(id);
|
|
5758
|
+
}
|
|
5759
|
+
/** Filter a draft without changing hidden choices.
|
|
5760
|
+
* @param query - Search input.
|
|
5761
|
+
* @param filter - Requested loading selection.
|
|
5762
|
+
* @returns Catalog entries in their original stable order.
|
|
5763
|
+
*/
|
|
5764
|
+
matchingEntries(query, filter) {
|
|
5765
|
+
return this.entries.filter((entry) => clientModuleMatches(entry, this.excluded, query, filter));
|
|
5766
|
+
}
|
|
5767
|
+
/** Find invalid old exclusions by name or ID, independently from the protected catalog row.
|
|
5768
|
+
* @param query - Search input.
|
|
5769
|
+
* @param filter - Old exclusions appear in all and excluded selections, not loaded.
|
|
5770
|
+
* @returns Invalid draft exclusions in their retained order.
|
|
5771
|
+
*/
|
|
5772
|
+
matchingInvalidExclusions(query, filter) {
|
|
5773
|
+
return filter === "loaded" ? [] : this.invalidExclusions.filter((entry) => matchesQuery(entry.id, query));
|
|
5774
|
+
}
|
|
5775
|
+
};
|
|
5776
|
+
//#endregion
|
|
5389
5777
|
//#region src/client-module-ui.ts
|
|
5390
5778
|
/** Device-local module selection and the computer's defaults share a read-only catalog. */
|
|
5391
5779
|
const CLIENT_MODULE_COPY = {
|
|
5392
5780
|
zh: {
|
|
5393
5781
|
title: "移动页面模块",
|
|
5394
|
-
computer: "
|
|
5395
|
-
device: "
|
|
5782
|
+
computer: "设置此电脑的移动访问默认项;电脑插件不变。",
|
|
5783
|
+
device: "仅本设备,下次打开生效;电脑插件不变。",
|
|
5396
5784
|
open: "管理",
|
|
5785
|
+
help: "加载规则",
|
|
5397
5786
|
loading: "正在读取模块…",
|
|
5398
|
-
hint: "
|
|
5399
|
-
save: "
|
|
5787
|
+
hint: "勾选表示下次打开时加载。启动必需项受保护;依赖冲突会在保存时说明,不会自动取消其他模块。",
|
|
5788
|
+
save: "保存选择",
|
|
5400
5789
|
reset: "恢复默认",
|
|
5401
5790
|
close: "关闭",
|
|
5402
5791
|
required: "启动必需",
|
|
5403
5792
|
dependencies: "依赖:",
|
|
5404
|
-
|
|
5405
|
-
|
|
5793
|
+
noDependencies: "无声明的前端依赖",
|
|
5794
|
+
details: "模块详情",
|
|
5795
|
+
moduleId: "模块 ID",
|
|
5796
|
+
saved: "已保存,下次打开 DSH 生效。不会自动刷新当前页面或丢弃草稿。",
|
|
5797
|
+
failed: "暂时无法读取模块。请确认移动访问已启用后重试。",
|
|
5798
|
+
saveFailed: "未确认保存成功,已保留你的选择。可重新保存,或重新读取已保存设置。",
|
|
5406
5799
|
conflict: "无法保存:",
|
|
5407
|
-
computerSource: "
|
|
5408
|
-
deviceSource: "
|
|
5409
|
-
|
|
5800
|
+
computerSource: "当前来源:电脑默认设置",
|
|
5801
|
+
deviceSource: "当前来源:本设备设置",
|
|
5802
|
+
pluginSource: "当前来源:插件默认设置",
|
|
5803
|
+
resetConfirm: "恢复默认模块选择?仅下次打开生效,不会重新加载当前会话。",
|
|
5804
|
+
discardConfirm: "放弃未保存的模块选择并关闭?",
|
|
5805
|
+
reloadConfirm: "重新读取会替换未保存的选择。继续吗?",
|
|
5806
|
+
savingCloseConfirm: "保存结果尚未确认。现在关闭不会撤销可能已完成的保存,确定关闭吗?",
|
|
5807
|
+
search: "搜索模块",
|
|
5808
|
+
searchPlaceholder: "搜索名称或模块 ID",
|
|
5809
|
+
filter: "加载选择",
|
|
5810
|
+
all: "全部",
|
|
5811
|
+
loaded: "加载",
|
|
5812
|
+
excluded: "不加载",
|
|
5813
|
+
empty: "没有符合条件的模块。",
|
|
5814
|
+
clearFilters: "清除筛选",
|
|
5815
|
+
extensions: "可选扩展",
|
|
5816
|
+
extensionsHint: "可选择不加载的前端模块;一项模块不一定等于一个完整插件。",
|
|
5817
|
+
advanced: "DSH 可选组件(高级)",
|
|
5818
|
+
advancedHint: "DSH 的可选前端组件。只修改你了解的项目。",
|
|
5819
|
+
requiredGroup: "启动必需项",
|
|
5820
|
+
requiredHint: "这些模块保证页面启动,不能关闭。",
|
|
5821
|
+
changes: "未保存:{count} 项改动",
|
|
5822
|
+
unchanged: "没有未保存的改动",
|
|
5823
|
+
results: "匹配 {visible} / {total} 项",
|
|
5824
|
+
retry: "重新读取",
|
|
5825
|
+
saving: "正在保存…",
|
|
5826
|
+
readTimeout: "读取超时。当前页面和已保存设置未变,请重试。",
|
|
5827
|
+
writeTimeout: "保存超时,结果尚未确认。保留了你的选择;请重新读取确认,不会自动重试保存。",
|
|
5828
|
+
nextOpen: "仅下次打开生效",
|
|
5829
|
+
invalidSelections: "失效的旧选择",
|
|
5830
|
+
invalidHint: "这些旧排除已与当前模块目录不符。移除后仍需保存选择。",
|
|
5831
|
+
invalidMissing: "当前模块目录中不存在。",
|
|
5832
|
+
invalidRequired: "当前已是启动必需项,不能排除。",
|
|
5833
|
+
clearInvalid: "移除旧排除"
|
|
5410
5834
|
},
|
|
5411
5835
|
en: {
|
|
5412
5836
|
title: "Mobile page modules",
|
|
5413
|
-
computer: "
|
|
5414
|
-
device: "
|
|
5837
|
+
computer: "Mobile defaults for this computer. Desktop plugins are unchanged.",
|
|
5838
|
+
device: "For this device’s next open only. Computer plugins are unchanged.",
|
|
5415
5839
|
open: "Manage",
|
|
5840
|
+
help: "Loading rules",
|
|
5416
5841
|
loading: "Reading modules…",
|
|
5417
|
-
hint: "Checked means
|
|
5418
|
-
save: "Save
|
|
5842
|
+
hint: "Checked means load on the next open. Required entries are protected; saving explains dependency conflicts without unchecking other modules.",
|
|
5843
|
+
save: "Save selection",
|
|
5419
5844
|
reset: "Restore defaults",
|
|
5420
5845
|
close: "Close",
|
|
5421
5846
|
required: "Required for boot",
|
|
5422
5847
|
dependencies: "Depends on: ",
|
|
5423
|
-
|
|
5424
|
-
|
|
5848
|
+
noDependencies: "No declared frontend dependencies",
|
|
5849
|
+
details: "Module details",
|
|
5850
|
+
moduleId: "Module ID",
|
|
5851
|
+
saved: "Saved for the next DSH open. This page is not refreshed and its draft is kept.",
|
|
5852
|
+
failed: "Could not read modules. Enable mobile access and try again.",
|
|
5853
|
+
saveFailed: "Saving was not confirmed. Your choices are kept. Save again or reread the saved settings.",
|
|
5425
5854
|
conflict: "Could not save: ",
|
|
5426
|
-
computerSource: "
|
|
5427
|
-
deviceSource: "
|
|
5428
|
-
|
|
5855
|
+
computerSource: "Source: computer defaults",
|
|
5856
|
+
deviceSource: "Source: this device’s settings",
|
|
5857
|
+
pluginSource: "Source: plugin defaults",
|
|
5858
|
+
resetConfirm: "Restore default selections for the next open? The current conversation will not reload.",
|
|
5859
|
+
discardConfirm: "Discard unsaved module choices and close?",
|
|
5860
|
+
reloadConfirm: "Rereading replaces unsaved choices. Continue?",
|
|
5861
|
+
savingCloseConfirm: "Saving is not confirmed. Closing will not undo a save that may have completed. Close anyway?",
|
|
5862
|
+
search: "Search modules",
|
|
5863
|
+
searchPlaceholder: "Search name or module ID",
|
|
5864
|
+
filter: "Loading selection",
|
|
5865
|
+
all: "All",
|
|
5866
|
+
loaded: "Load",
|
|
5867
|
+
excluded: "Do not load",
|
|
5868
|
+
empty: "No modules match these filters.",
|
|
5869
|
+
clearFilters: "Clear filters",
|
|
5870
|
+
extensions: "Optional extensions",
|
|
5871
|
+
extensionsHint: "Optional frontend modules; one module is not necessarily a complete plugin.",
|
|
5872
|
+
advanced: "DSH optional components (advanced)",
|
|
5873
|
+
advancedHint: "Optional DSH frontend components. Change only entries you understand.",
|
|
5874
|
+
requiredGroup: "Required for boot",
|
|
5875
|
+
requiredHint: "These modules keep the page bootable and cannot be disabled.",
|
|
5876
|
+
changes: "Unsaved: {count} changes",
|
|
5877
|
+
unchanged: "No unsaved changes",
|
|
5878
|
+
results: "Matching {visible} / {total}",
|
|
5879
|
+
retry: "Reread settings",
|
|
5880
|
+
saving: "Saving…",
|
|
5881
|
+
readTimeout: "Reading timed out. This page and saved settings are unchanged; try again.",
|
|
5882
|
+
writeTimeout: "Saving timed out and its result is unconfirmed. Your choices are kept. Reread to confirm; saving is not retried automatically.",
|
|
5883
|
+
nextOpen: "Applies on next open only",
|
|
5884
|
+
invalidSelections: "Outdated selections",
|
|
5885
|
+
invalidHint: "These old exclusions no longer match the module catalog. Save your choices after removing them.",
|
|
5886
|
+
invalidMissing: "Not present in the current module catalog.",
|
|
5887
|
+
invalidRequired: "Now required for boot and cannot be excluded.",
|
|
5888
|
+
clearInvalid: "Remove old exclusion"
|
|
5429
5889
|
},
|
|
5430
5890
|
it: {
|
|
5431
5891
|
title: "Moduli della pagina mobile",
|
|
5432
|
-
computer: "
|
|
5433
|
-
device: "
|
|
5892
|
+
computer: "Predefiniti mobile del computer; i plugin non cambiano.",
|
|
5893
|
+
device: "Solo alla prossima apertura su questo dispositivo; i plugin del computer non cambiano.",
|
|
5434
5894
|
open: "Gestisci",
|
|
5895
|
+
help: "Regole di caricamento",
|
|
5435
5896
|
loading: "Lettura dei moduli…",
|
|
5436
|
-
hint: "Selezionato significa
|
|
5437
|
-
save: "Salva
|
|
5897
|
+
hint: "Selezionato significa caricare alla prossima apertura. I moduli obbligatori sono protetti; il salvataggio mostra i conflitti senza deselezionare altri moduli.",
|
|
5898
|
+
save: "Salva selezione",
|
|
5438
5899
|
reset: "Ripristina predefiniti",
|
|
5439
5900
|
close: "Chiudi",
|
|
5440
5901
|
required: "Obbligatorio",
|
|
5441
5902
|
dependencies: "Dipende da: ",
|
|
5442
|
-
|
|
5443
|
-
|
|
5903
|
+
noDependencies: "Nessuna dipendenza frontend dichiarata",
|
|
5904
|
+
details: "Dettagli modulo",
|
|
5905
|
+
moduleId: "ID modulo",
|
|
5906
|
+
saved: "Salvato per la prossima apertura di DSH. La pagina non si aggiorna e la bozza resta.",
|
|
5907
|
+
failed: "Impossibile leggere i moduli. Abilita accesso mobile e riprova.",
|
|
5908
|
+
saveFailed: "Salvataggio non confermato. Le scelte restano; salva di nuovo o rileggi le impostazioni salvate.",
|
|
5444
5909
|
conflict: "Impossibile salvare: ",
|
|
5445
|
-
computerSource: "
|
|
5446
|
-
deviceSource: "
|
|
5447
|
-
|
|
5910
|
+
computerSource: "Origine: predefiniti del computer",
|
|
5911
|
+
deviceSource: "Origine: impostazioni del dispositivo",
|
|
5912
|
+
pluginSource: "Origine: predefiniti del plugin",
|
|
5913
|
+
resetConfirm: "Ripristinare i predefiniti per la prossima apertura? La conversazione attuale non verrà ricaricata.",
|
|
5914
|
+
discardConfirm: "Scartare le scelte non salvate e chiudere?",
|
|
5915
|
+
reloadConfirm: "Rileggere sostituisce le scelte non salvate. Continuare?",
|
|
5916
|
+
savingCloseConfirm: "Salvataggio non confermato. Chiudere non annulla un salvataggio già completato. Chiudere comunque?",
|
|
5917
|
+
search: "Cerca moduli",
|
|
5918
|
+
searchPlaceholder: "Cerca nome o ID modulo",
|
|
5919
|
+
filter: "Selezione caricamento",
|
|
5920
|
+
all: "Tutti",
|
|
5921
|
+
loaded: "Carica",
|
|
5922
|
+
excluded: "Non caricare",
|
|
5923
|
+
empty: "Nessun modulo corrisponde ai filtri.",
|
|
5924
|
+
clearFilters: "Cancella filtri",
|
|
5925
|
+
extensions: "Estensioni facoltative",
|
|
5926
|
+
extensionsHint: "Moduli frontend facoltativi; un modulo non è necessariamente un plugin completo.",
|
|
5927
|
+
advanced: "Componenti DSH facoltativi (avanzate)",
|
|
5928
|
+
advancedHint: "Componenti frontend DSH facoltativi. Modifica solo quelli che conosci.",
|
|
5929
|
+
requiredGroup: "Moduli obbligatori",
|
|
5930
|
+
requiredHint: "Questi moduli permettono l’avvio e non possono essere disattivati.",
|
|
5931
|
+
changes: "Non salvate: {count} modifiche",
|
|
5932
|
+
unchanged: "Nessuna modifica non salvata",
|
|
5933
|
+
results: "Corrispondenti {visible} / {total}",
|
|
5934
|
+
retry: "Rileggi impostazioni",
|
|
5935
|
+
saving: "Salvataggio…",
|
|
5936
|
+
readTimeout: "Lettura scaduta. Pagina e impostazioni salvate non cambiano; riprova.",
|
|
5937
|
+
writeTimeout: "Salvataggio scaduto e non confermato. Le scelte restano. Rileggi per confermare; nessun tentativo automatico.",
|
|
5938
|
+
nextOpen: "Solo alla prossima apertura",
|
|
5939
|
+
invalidSelections: "Scelte obsolete",
|
|
5940
|
+
invalidHint: "Queste esclusioni precedenti non corrispondono più al catalogo. Salva le scelte dopo averle rimosse.",
|
|
5941
|
+
invalidMissing: "Assente dal catalogo attuale dei moduli.",
|
|
5942
|
+
invalidRequired: "Ora obbligatorio per l’avvio e non può essere escluso.",
|
|
5943
|
+
clearInvalid: "Rimuovi esclusione precedente"
|
|
5448
5944
|
}
|
|
5449
5945
|
};
|
|
5450
5946
|
var ClientModuleUiConflict = class extends Error {
|
|
@@ -5454,29 +5950,40 @@ window.__ModuleLoader__.load({
|
|
|
5454
5950
|
this.detail = detail;
|
|
5455
5951
|
}
|
|
5456
5952
|
};
|
|
5953
|
+
/** A deadline does not imply that a POST was rolled back by the server. */
|
|
5954
|
+
var ClientModuleRequestTimeout = class extends Error {
|
|
5955
|
+
mutation;
|
|
5956
|
+
constructor(mutation) {
|
|
5957
|
+
super("client_module_request_timeout");
|
|
5958
|
+
this.mutation = mutation;
|
|
5959
|
+
}
|
|
5960
|
+
};
|
|
5961
|
+
const CLIENT_MODULE_REQUEST_TIMEOUT_MS = {
|
|
5962
|
+
read: 15e3,
|
|
5963
|
+
write: 2e4
|
|
5964
|
+
};
|
|
5457
5965
|
/** Validate catalog response fields before creating interactive controls.
|
|
5458
5966
|
* @param value - Gateway or computer-admin JSON response.
|
|
5459
5967
|
* @returns The fields used by the module-selection dialog.
|
|
5460
5968
|
*/
|
|
5461
5969
|
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
|
-
|
|
5970
|
+
if (typeof value !== "object" || value === null || !("entries" in value) || !Array.isArray(value.entries) || !("excludedClientModules" in value) || !Array.isArray(value.excludedClientModules) || !("source" in value) || value.source !== "device" && value.source !== "computer" && value.source !== "plugin") throw new Error("invalid_module_catalog");
|
|
5971
|
+
const entries = value.entries.map((entry) => {
|
|
5972
|
+
if (typeof entry !== "object" || entry === null || !("id" in entry) || typeof entry.id !== "string" || !("required" in entry) || typeof entry.required !== "boolean" || !("dependencies" in entry) || !Array.isArray(entry.dependencies)) throw new Error("invalid_module_catalog");
|
|
5973
|
+
const dependencies = entry.dependencies.map((dependency) => {
|
|
5974
|
+
if (typeof dependency !== "string") throw new Error("invalid_module_catalog");
|
|
5975
|
+
return dependency;
|
|
5976
|
+
});
|
|
5977
|
+
if (entry.id.trim() === "") throw new Error("invalid_module_catalog");
|
|
5978
|
+
return {
|
|
5979
|
+
id: entry.id,
|
|
5980
|
+
required: entry.required,
|
|
5981
|
+
dependencies
|
|
5982
|
+
};
|
|
5983
|
+
});
|
|
5984
|
+
if (new Set(entries.map((entry) => entry.id)).size !== entries.length) throw new Error("invalid_module_catalog");
|
|
5467
5985
|
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
|
-
}),
|
|
5986
|
+
entries,
|
|
5480
5987
|
excludedClientModules: value.excludedClientModules.map((id) => {
|
|
5481
5988
|
if (typeof id !== "string") throw new Error("invalid_module_catalog");
|
|
5482
5989
|
return id;
|
|
@@ -5484,32 +5991,103 @@ window.__ModuleLoader__.load({
|
|
|
5484
5991
|
source: value.source === "device" ? "device" : value.source === "computer" ? "computer" : "plugin"
|
|
5485
5992
|
};
|
|
5486
5993
|
}
|
|
5994
|
+
/** Read or explicitly write module choices with a bounded, cancellable operation.
|
|
5995
|
+
* @param scope - Computer-admin endpoint or authenticated device endpoint.
|
|
5996
|
+
* @param signal - Owning dialog lifetime; cancellation cannot undo a completed write.
|
|
5997
|
+
* @param body - Omit for a read; supplied fields are sent once without automatic replay.
|
|
5998
|
+
* @returns Validated catalog fields returned by the selected endpoint.
|
|
5999
|
+
*/
|
|
6000
|
+
async function requestClientModuleView(scope, signal, body) {
|
|
6001
|
+
if (signal.aborted) throw new DOMException("Dialog closed", "AbortError");
|
|
6002
|
+
const controller = new AbortController();
|
|
6003
|
+
let rejectCancellation = () => {};
|
|
6004
|
+
const cancellation = new Promise((_, reject) => {
|
|
6005
|
+
rejectCancellation = reject;
|
|
6006
|
+
});
|
|
6007
|
+
const abort = () => {
|
|
6008
|
+
controller.abort();
|
|
6009
|
+
rejectCancellation(new DOMException("Dialog closed", "AbortError"));
|
|
6010
|
+
};
|
|
6011
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
6012
|
+
const timer = setTimeout(() => {
|
|
6013
|
+
rejectCancellation(new ClientModuleRequestTimeout(body !== void 0));
|
|
6014
|
+
controller.abort();
|
|
6015
|
+
}, body === void 0 ? CLIENT_MODULE_REQUEST_TIMEOUT_MS.read : CLIENT_MODULE_REQUEST_TIMEOUT_MS.write);
|
|
6016
|
+
const operation = async () => {
|
|
6017
|
+
const init = {
|
|
6018
|
+
method: body === void 0 ? "GET" : "POST",
|
|
6019
|
+
signal: controller.signal,
|
|
6020
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
6021
|
+
};
|
|
6022
|
+
init.headers = scope === "computer" ? localAdminRequestHeaders(init, location) : { "content-type": "application/json" };
|
|
6023
|
+
if (scope === "device" && body !== void 0) {
|
|
6024
|
+
const cookies = document.cookie.split(";").map((value) => value.trim());
|
|
6025
|
+
const csrfCookie = cookies.find((value) => value.startsWith("__Host-dsh_ma_csrf=")) ?? cookies.find((value) => value.startsWith("dsh_ma_csrf="));
|
|
6026
|
+
if (csrfCookie !== void 0) {
|
|
6027
|
+
const headers = new Headers(init.headers);
|
|
6028
|
+
headers.set("x-dsh-mobile-csrf", csrfCookie.slice(csrfCookie.indexOf("=") + 1));
|
|
6029
|
+
init.headers = headers;
|
|
6030
|
+
}
|
|
6031
|
+
}
|
|
6032
|
+
const response = await fetch(scope === "computer" ? "/api/mobile-access/client-modules" : "/mobile-access/client-modules", init);
|
|
6033
|
+
const value = await response.json();
|
|
6034
|
+
if (!response.ok) {
|
|
6035
|
+
const detail = typeof value === "object" && value !== null && "detail" in value && typeof value.detail === "string" ? value.detail.slice(0, 512) : "";
|
|
6036
|
+
if (response.status === 409 && detail !== "" && typeof value === "object" && value !== null && "error" in value && value.error === "excluded_client_modules_invalid") throw new ClientModuleUiConflict(detail);
|
|
6037
|
+
throw new Error("client_module_request_failed");
|
|
6038
|
+
}
|
|
6039
|
+
return parseClientModuleView(value);
|
|
6040
|
+
};
|
|
6041
|
+
try {
|
|
6042
|
+
return await Promise.race([operation(), cancellation]);
|
|
6043
|
+
} finally {
|
|
6044
|
+
clearTimeout(timer);
|
|
6045
|
+
signal.removeEventListener("abort", abort);
|
|
6046
|
+
}
|
|
6047
|
+
}
|
|
5487
6048
|
const CLIENT_MODULE_STYLES = `
|
|
5488
6049
|
.dsh-module-row{display:flex;align-items:center;gap:8px;padding:16px 0;border-bottom:.5px solid var(--dsw-alias-border-l2)}
|
|
5489
6050
|
.dsh-module-rowText{flex:1;min-width:0}.dsh-module-title{font-size:14px;line-height:22px;color:var(--dsw-alias-label-primary)}
|
|
5490
6051
|
.dsh-module-description{margin-top:4px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}
|
|
5491
6052
|
.dsh-module-button{min-height:48px;padding:0 14px;border:0;border-radius:24px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;cursor:pointer}
|
|
5492
6053
|
.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-
|
|
6054
|
+
.dsh-module-button:not(:disabled):active{background:var(--dsw-alias-interactive-bg-active)}
|
|
6055
|
+
.dsh-module-dialog{box-sizing:border-box;width:min(640px,calc(100vw - 24px));max-height:calc(100dvh - 32px);padding:0;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-panel,20px);background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-primary,#171a21);font:16px/1.5 var(--dsw-font-family,sans-serif);overflow:hidden}
|
|
6056
|
+
.dsh-module-dialog[open]{display:flex;flex-direction:column}.dsh-module-dialog::backdrop{background:rgb(15 23 42 / 40%)}
|
|
6057
|
+
.dsh-module-header{display:flex;align-items:center;gap:12px;padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l2);flex:none}
|
|
6058
|
+
.dsh-module-header h2{flex:1;min-width:0;font-size:20px;line-height:1.4;margin:0;overflow-wrap:anywhere}.dsh-module-header .dsh-module-button{flex:none}
|
|
6059
|
+
.dsh-module-body{min-height:0;overflow:auto;overscroll-behavior:contain;padding:16px;scroll-padding:12px}.dsh-module-dialog p{margin:0 0 12px;overflow-wrap:anywhere}
|
|
6060
|
+
.dsh-module-hint,.dsh-module-source,.dsh-module-choice small,.dsh-module-groupHint,.dsh-module-results{font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-tertiary))}
|
|
6061
|
+
.dsh-module-tools{display:grid;grid-template-columns:minmax(0,1fr) minmax(100px,.45fr);gap:12px;margin:16px 0 8px}.dsh-module-field{display:flex;flex-direction:column;gap:4px;min-width:0;font-size:13px}
|
|
6062
|
+
.dsh-module-help>summary{box-sizing:border-box;min-height:48px;padding:12px 0;cursor:pointer;font-size:13px;color:var(--dsw-alias-label-secondary)}
|
|
6063
|
+
.dsh-module-field input,.dsh-module-field select{box-sizing:border-box;width:100%;min-width:0;min-height:48px;padding:8px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-base,var(--dsw-alias-bg-layer-1));color:var(--dsw-alias-label-primary);font:inherit;font-size:16px}
|
|
6064
|
+
.dsh-module-dialog :is(input,select,summary):focus-visible{outline:2px solid var(--dsw-alias-label-primary-bluish,#2563eb);outline-offset:2px}
|
|
6065
|
+
.dsh-module-list{display:flex;flex-direction:column;gap:16px;margin:12px 0}.dsh-module-group h3{margin:0 0 8px;font-size:16px;line-height:24px}
|
|
6066
|
+
.dsh-module-group>summary{min-height:48px;cursor:pointer;font-weight:600;overflow-wrap:anywhere;align-content:center}.dsh-module-group>summary+div{margin-top:8px}
|
|
6067
|
+
.dsh-module-entry{border-bottom:1px solid var(--dsw-alias-border-l2);padding:8px 0}.dsh-module-choice{display:flex;align-items:center;gap:12px;min-height:48px;cursor:pointer}
|
|
6068
|
+
.dsh-module-choice input{width:24px;height:24px;flex:none;margin:0}.dsh-module-choice span{min-width:0;overflow-wrap:anywhere}.dsh-module-choiceName{display:block;font-weight:500}
|
|
6069
|
+
.dsh-module-choice small{display:block}.dsh-module-entryDetails{margin-left:36px;min-width:0}.dsh-module-entryDetails summary{min-height:48px;align-content:center;cursor:pointer;font-size:13px;color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-tertiary))}
|
|
6070
|
+
.dsh-module-invalid-entry code{display:block;min-width:0;overflow-wrap:anywhere;white-space:normal;font-size:13px}.dsh-module-invalid-entry .dsh-module-button{max-width:100%;white-space:normal;line-height:20px;padding:8px 14px}
|
|
6071
|
+
.dsh-module-entryDetails p,.dsh-module-entryDetails code{overflow-wrap:anywhere;font-size:13px}.dsh-module-entryDetails code{white-space:normal}
|
|
6072
|
+
.dsh-module-footer{flex:none;padding:12px 16px max(12px,env(safe-area-inset-bottom));border-top:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1,#fff)}
|
|
6073
|
+
.dsh-module-footer p{font-size:13px;line-height:20px;margin:0 0 8px}.dsh-module-actions{display:flex;flex-wrap:wrap;gap:8px}.dsh-module-actions .dsh-module-button{flex:1;min-width:0;white-space:normal;line-height:20px;padding:8px 12px}
|
|
6074
|
+
.dsh-module-button:disabled{opacity:.5;cursor:default}.dsh-module-save{background:var(--dsw-alias-bg-module-platform);font-weight:600}
|
|
6075
|
+
.dsh-module-notice:empty{display:none}.dsh-module-notice{max-height:min(96px,24dvh);overflow:auto;font-size:14px;line-height:22px;overscroll-behavior:contain}.dsh-module-empty{padding:16px 0}
|
|
6076
|
+
@media(max-width:420px){.dsh-module-tools{grid-template-columns:minmax(0,1fr)}.dsh-module-body{padding:12px}.dsh-module-header,.dsh-module-footer{padding-left:12px;padding-right:12px}}
|
|
5499
6077
|
.dsh-module-error{color:var(--dsw-alias-state-error-primary,#b42318)}
|
|
5500
6078
|
`;
|
|
5501
6079
|
const openDialogs = /* @__PURE__ */ new Set();
|
|
5502
6080
|
/** Cancel pending dialogs when the owning plugin generation is disposed. */
|
|
5503
6081
|
function disposeClientModuleDialogs() {
|
|
5504
|
-
for (const
|
|
6082
|
+
for (const dialog of [...openDialogs]) dialog.dispose();
|
|
5505
6083
|
}
|
|
5506
6084
|
/** Close the foreground module modal before navigating its underlying DSH settings.
|
|
5507
6085
|
* @returns Whether an open module modal consumed the return action.
|
|
5508
6086
|
*/
|
|
5509
6087
|
function closeClientModuleDialog() {
|
|
5510
|
-
const
|
|
5511
|
-
if (
|
|
5512
|
-
|
|
6088
|
+
const dialog = [...openDialogs].at(-1);
|
|
6089
|
+
if (dialog === void 0) return false;
|
|
6090
|
+
dialog.requestClose();
|
|
5513
6091
|
return true;
|
|
5514
6092
|
}
|
|
5515
6093
|
/** Open one modal; closing cancels reads and restores focus without reloading the page.
|
|
@@ -5519,143 +6097,338 @@ window.__ModuleLoader__.load({
|
|
|
5519
6097
|
*/
|
|
5520
6098
|
function openClientModuleSettings(scope, locale, trigger) {
|
|
5521
6099
|
const copy = CLIENT_MODULE_COPY[locale];
|
|
5522
|
-
const endpoint = scope === "computer" ? "/api/mobile-access/client-modules" : "/mobile-access/client-modules";
|
|
5523
6100
|
const controller = new AbortController();
|
|
5524
6101
|
const dialog = document.createElement("dialog");
|
|
5525
6102
|
dialog.className = "dsh-module-dialog";
|
|
5526
6103
|
dialog.setAttribute("aria-label", copy.title);
|
|
6104
|
+
dialog.lang = locale;
|
|
6105
|
+
const button = (text) => {
|
|
6106
|
+
const result = document.createElement("button");
|
|
6107
|
+
result.type = "button";
|
|
6108
|
+
result.className = "dsh-module-button";
|
|
6109
|
+
result.textContent = text;
|
|
6110
|
+
return result;
|
|
6111
|
+
};
|
|
6112
|
+
const paragraph = (text, className = "") => {
|
|
6113
|
+
const result = document.createElement("p");
|
|
6114
|
+
result.textContent = text;
|
|
6115
|
+
result.className = className;
|
|
6116
|
+
return result;
|
|
6117
|
+
};
|
|
6118
|
+
const header = document.createElement("div");
|
|
6119
|
+
header.className = "dsh-module-header";
|
|
5527
6120
|
const title = document.createElement("h2");
|
|
5528
6121
|
title.textContent = copy.title;
|
|
5529
|
-
const
|
|
5530
|
-
|
|
5531
|
-
const
|
|
6122
|
+
const close = button(copy.close);
|
|
6123
|
+
header.append(title, close);
|
|
6124
|
+
const body = document.createElement("div");
|
|
6125
|
+
body.className = "dsh-module-body";
|
|
6126
|
+
const intro = paragraph(copy[scope]);
|
|
6127
|
+
const hint = paragraph(copy.hint, "dsh-module-hint");
|
|
6128
|
+
const help = document.createElement("details");
|
|
6129
|
+
help.className = "dsh-module-help";
|
|
6130
|
+
const helpTitle = document.createElement("summary");
|
|
6131
|
+
helpTitle.textContent = copy.help;
|
|
6132
|
+
help.append(helpTitle, hint);
|
|
6133
|
+
const source = paragraph("", "dsh-module-source");
|
|
6134
|
+
const status = paragraph("", "dsh-module-notice");
|
|
5532
6135
|
status.setAttribute("role", "status");
|
|
5533
6136
|
status.setAttribute("aria-live", "polite");
|
|
5534
6137
|
status.tabIndex = -1;
|
|
6138
|
+
const tools = document.createElement("div");
|
|
6139
|
+
tools.className = "dsh-module-tools";
|
|
6140
|
+
const searchLabel = document.createElement("label");
|
|
6141
|
+
searchLabel.className = "dsh-module-field";
|
|
6142
|
+
searchLabel.textContent = copy.search;
|
|
6143
|
+
const search = document.createElement("input");
|
|
6144
|
+
search.type = "search";
|
|
6145
|
+
search.placeholder = copy.searchPlaceholder;
|
|
6146
|
+
search.setAttribute("aria-label", copy.search);
|
|
6147
|
+
searchLabel.append(search);
|
|
6148
|
+
const filterLabel = document.createElement("label");
|
|
6149
|
+
filterLabel.className = "dsh-module-field";
|
|
6150
|
+
filterLabel.textContent = copy.filter;
|
|
6151
|
+
const filter = document.createElement("select");
|
|
6152
|
+
filter.dataset.clientModuleFilter = "true";
|
|
6153
|
+
filter.setAttribute("aria-label", copy.filter);
|
|
6154
|
+
for (const value of [
|
|
6155
|
+
"all",
|
|
6156
|
+
"loaded",
|
|
6157
|
+
"excluded"
|
|
6158
|
+
]) {
|
|
6159
|
+
const option = document.createElement("option");
|
|
6160
|
+
option.value = value;
|
|
6161
|
+
option.textContent = copy[value];
|
|
6162
|
+
filter.append(option);
|
|
6163
|
+
}
|
|
6164
|
+
filterLabel.append(filter);
|
|
6165
|
+
tools.append(searchLabel, filterLabel);
|
|
6166
|
+
const results = paragraph("", "dsh-module-results");
|
|
6167
|
+
results.setAttribute("aria-live", "polite");
|
|
5535
6168
|
const list = document.createElement("div");
|
|
5536
6169
|
list.className = "dsh-module-list";
|
|
6170
|
+
const footer = document.createElement("div");
|
|
6171
|
+
footer.className = "dsh-module-footer";
|
|
6172
|
+
const changes = paragraph(copy.unchanged);
|
|
6173
|
+
changes.setAttribute("role", "status");
|
|
6174
|
+
changes.setAttribute("aria-live", "polite");
|
|
5537
6175
|
const actions = document.createElement("div");
|
|
5538
6176
|
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
6177
|
const save = button(copy.save);
|
|
6178
|
+
save.classList.add("dsh-module-save");
|
|
5547
6179
|
const reset = button(copy.reset);
|
|
5548
|
-
const
|
|
5549
|
-
actions.append(save, reset,
|
|
5550
|
-
|
|
6180
|
+
const reread = button(copy.retry);
|
|
6181
|
+
actions.append(save, reset, reread);
|
|
6182
|
+
footer.append(changes, status, actions);
|
|
6183
|
+
body.append(intro, source, tools, results, list, help);
|
|
6184
|
+
dialog.append(header, body, footer);
|
|
5551
6185
|
let disposed = false;
|
|
5552
|
-
let busy
|
|
5553
|
-
let
|
|
6186
|
+
let busy;
|
|
6187
|
+
let generation = 0;
|
|
6188
|
+
let draft;
|
|
6189
|
+
let unconfirmedWrite = false;
|
|
6190
|
+
const expansion = /* @__PURE__ */ new Map([
|
|
6191
|
+
["extensions", true],
|
|
6192
|
+
["advanced", false],
|
|
6193
|
+
["required", false]
|
|
6194
|
+
]);
|
|
5554
6195
|
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
|
-
|
|
6196
|
+
const invalidActions = /* @__PURE__ */ new Map();
|
|
6197
|
+
const filterValue = () => filter.value === "loaded" ? "loaded" : filter.value === "excluded" ? "excluded" : "all";
|
|
6198
|
+
const updateControls = () => {
|
|
6199
|
+
const changed = draft?.changeCount ?? 0;
|
|
6200
|
+
save.disabled = busy !== void 0 || changed === 0;
|
|
6201
|
+
reset.disabled = busy !== void 0 || draft === void 0;
|
|
6202
|
+
reread.disabled = busy !== void 0;
|
|
6203
|
+
search.disabled = busy !== void 0 || draft === void 0;
|
|
6204
|
+
filter.disabled = search.disabled;
|
|
6205
|
+
save.textContent = busy === "write" ? copy.saving : copy.save;
|
|
6206
|
+
changes.textContent = `${changed === 0 ? copy.unchanged : copy.changes.replace("{count}", String(changed))} · ${copy.nextOpen}`;
|
|
6207
|
+
list.setAttribute("aria-busy", String(busy !== void 0));
|
|
6208
|
+
for (const input of choices.values()) input.disabled = busy !== void 0 || input.dataset.required === "true";
|
|
6209
|
+
for (const action of invalidActions.values()) action.disabled = busy !== void 0;
|
|
6210
|
+
};
|
|
6211
|
+
const renderList = () => {
|
|
6212
|
+
choices.clear();
|
|
6213
|
+
invalidActions.clear();
|
|
6214
|
+
list.replaceChildren();
|
|
6215
|
+
if (draft === void 0) {
|
|
6216
|
+
results.textContent = "";
|
|
6217
|
+
return;
|
|
6218
|
+
}
|
|
6219
|
+
const visible = draft.matchingEntries(search.value, filterValue());
|
|
6220
|
+
const invalid = draft.matchingInvalidExclusions(search.value, filterValue());
|
|
6221
|
+
results.textContent = copy.results.replace("{visible}", String(visible.length + invalid.length)).replace("{total}", String(draft.entries.length + draft.invalidExclusions.length));
|
|
6222
|
+
const filtered = search.value.trim() !== "" || filterValue() !== "all";
|
|
6223
|
+
if (visible.length === 0 && invalid.length === 0) {
|
|
6224
|
+
const empty = document.createElement("div");
|
|
6225
|
+
empty.className = "dsh-module-empty";
|
|
6226
|
+
empty.append(paragraph(copy.empty));
|
|
6227
|
+
const clear = button(copy.clearFilters);
|
|
6228
|
+
clear.addEventListener("click", () => {
|
|
6229
|
+
search.value = "";
|
|
6230
|
+
filter.value = "all";
|
|
6231
|
+
renderList();
|
|
6232
|
+
search.focus({ preventScroll: true });
|
|
6233
|
+
});
|
|
6234
|
+
empty.append(clear);
|
|
6235
|
+
list.append(empty);
|
|
6236
|
+
}
|
|
6237
|
+
if (invalid.length > 0) {
|
|
6238
|
+
const section = document.createElement("section");
|
|
6239
|
+
section.className = "dsh-module-group";
|
|
6240
|
+
section.dataset.clientModuleGroup = "invalid";
|
|
6241
|
+
const heading = document.createElement("h3");
|
|
6242
|
+
heading.textContent = `${copy.invalidSelections} (${invalid.length})`;
|
|
6243
|
+
section.append(heading, paragraph(copy.invalidHint, "dsh-module-groupHint"));
|
|
6244
|
+
for (const entry of invalid) {
|
|
6245
|
+
const row = document.createElement("div");
|
|
6246
|
+
row.className = "dsh-module-entry dsh-module-invalid-entry";
|
|
6247
|
+
row.dataset.invalidClientModuleId = entry.id;
|
|
6248
|
+
const id = document.createElement("code");
|
|
6249
|
+
id.textContent = entry.id;
|
|
6250
|
+
const remove = button(copy.clearInvalid);
|
|
6251
|
+
remove.setAttribute("aria-label", `${copy.clearInvalid}: ${entry.id}`);
|
|
6252
|
+
remove.addEventListener("click", () => {
|
|
6253
|
+
if (disposed || busy !== void 0 || draft?.clearInvalidExclusion(entry.id) !== true) return;
|
|
6254
|
+
status.textContent = "";
|
|
6255
|
+
status.classList.remove("dsh-module-error");
|
|
6256
|
+
renderList();
|
|
6257
|
+
(invalidActions.values().next().value ?? save).focus({ preventScroll: true });
|
|
6258
|
+
});
|
|
6259
|
+
row.append(id, paragraph(entry.reason === "missing" ? copy.invalidMissing : copy.invalidRequired), remove);
|
|
6260
|
+
invalidActions.set(entry.id, remove);
|
|
6261
|
+
section.append(row);
|
|
5574
6262
|
}
|
|
6263
|
+
list.append(section);
|
|
5575
6264
|
}
|
|
5576
|
-
const
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
6265
|
+
for (const group of [
|
|
6266
|
+
"extensions",
|
|
6267
|
+
"advanced",
|
|
6268
|
+
"required"
|
|
6269
|
+
]) {
|
|
6270
|
+
const entries = visible.filter((entry) => clientModuleGroup(entry) === group);
|
|
6271
|
+
if (entries.length === 0) continue;
|
|
6272
|
+
const section = document.createElement(group === "extensions" ? "section" : "details");
|
|
6273
|
+
section.className = "dsh-module-group";
|
|
6274
|
+
section.dataset.group = group;
|
|
6275
|
+
section.dataset.clientModuleGroup = group === "advanced" ? "system" : group;
|
|
6276
|
+
const headingText = `${group === "required" ? copy.requiredGroup : copy[group]} (${entries.length})`;
|
|
6277
|
+
if (section instanceof HTMLDetailsElement) {
|
|
6278
|
+
section.open = filtered || expansion.get(group) === true;
|
|
6279
|
+
const heading = document.createElement("summary");
|
|
6280
|
+
heading.textContent = headingText;
|
|
6281
|
+
section.append(heading);
|
|
6282
|
+
section.addEventListener("toggle", () => {
|
|
6283
|
+
if (!filtered) expansion.set(group, section.open);
|
|
6284
|
+
});
|
|
6285
|
+
} else {
|
|
6286
|
+
const heading = document.createElement("h3");
|
|
6287
|
+
heading.textContent = headingText;
|
|
6288
|
+
section.append(heading);
|
|
6289
|
+
}
|
|
6290
|
+
const content = document.createElement("div");
|
|
6291
|
+
content.append(paragraph(group === "required" ? copy.requiredHint : group === "advanced" ? copy.advancedHint : copy.extensionsHint, "dsh-module-groupHint"));
|
|
6292
|
+
for (const entry of entries) {
|
|
6293
|
+
const row = document.createElement("div");
|
|
6294
|
+
row.className = "dsh-module-entry";
|
|
6295
|
+
row.dataset.clientModuleId = entry.id;
|
|
6296
|
+
const label = document.createElement("label");
|
|
6297
|
+
label.className = "dsh-module-choice";
|
|
6298
|
+
const input = document.createElement("input");
|
|
6299
|
+
input.type = "checkbox";
|
|
6300
|
+
input.checked = draft.isLoaded(entry.id);
|
|
6301
|
+
if (entry.required) input.dataset.required = "true";
|
|
6302
|
+
const name = document.createElement("span");
|
|
6303
|
+
const shortName = document.createElement("span");
|
|
6304
|
+
shortName.className = "dsh-module-choiceName";
|
|
6305
|
+
shortName.textContent = clientModuleDisplayName(entry.id);
|
|
6306
|
+
const selected = document.createElement("small");
|
|
6307
|
+
selected.textContent = entry.required ? copy.required : input.checked ? copy.loaded : copy.excluded;
|
|
6308
|
+
name.append(shortName, selected);
|
|
6309
|
+
label.append(input, name);
|
|
6310
|
+
row.append(label);
|
|
6311
|
+
const detail = document.createElement("details");
|
|
6312
|
+
detail.className = "dsh-module-entryDetails";
|
|
6313
|
+
const detailSummary = document.createElement("summary");
|
|
6314
|
+
detailSummary.textContent = copy.details;
|
|
6315
|
+
const id = paragraph(`${copy.moduleId}: `);
|
|
6316
|
+
const code = document.createElement("code");
|
|
6317
|
+
code.textContent = entry.id;
|
|
6318
|
+
id.append(code);
|
|
6319
|
+
detail.append(detailSummary, id, paragraph(entry.dependencies.length === 0 ? copy.noDependencies : `${copy.dependencies}${entry.dependencies.join(", ")}`));
|
|
6320
|
+
row.append(detail);
|
|
6321
|
+
input.addEventListener("change", () => {
|
|
6322
|
+
if (busy !== void 0 || draft === void 0) return;
|
|
6323
|
+
draft.setLoaded(entry.id, input.checked);
|
|
6324
|
+
input.checked = draft.isLoaded(entry.id);
|
|
6325
|
+
selected.textContent = entry.required ? copy.required : input.checked ? copy.loaded : copy.excluded;
|
|
6326
|
+
status.textContent = "";
|
|
6327
|
+
status.classList.remove("dsh-module-error");
|
|
6328
|
+
if (filterValue() !== "all") {
|
|
6329
|
+
renderList();
|
|
6330
|
+
filter.focus({ preventScroll: true });
|
|
6331
|
+
}
|
|
6332
|
+
updateControls();
|
|
6333
|
+
});
|
|
6334
|
+
choices.set(entry.id, input);
|
|
6335
|
+
content.append(row);
|
|
6336
|
+
}
|
|
6337
|
+
section.append(content);
|
|
6338
|
+
list.append(section);
|
|
5582
6339
|
}
|
|
5583
|
-
|
|
6340
|
+
updateControls();
|
|
5584
6341
|
};
|
|
5585
6342
|
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;
|
|
6343
|
+
draft = new ClientModuleDraft(view.entries, view.excludedClientModules);
|
|
6344
|
+
source.textContent = view.source === "device" ? copy.deviceSource : view.source === "computer" ? copy.computerSource : copy.pluginSource;
|
|
6345
|
+
renderList();
|
|
5609
6346
|
};
|
|
5610
|
-
const failed = (error) => {
|
|
5611
|
-
if (disposed || controller.signal.aborted) return;
|
|
6347
|
+
const failed = (error, mutation) => {
|
|
5612
6348
|
status.classList.add("dsh-module-error");
|
|
5613
|
-
status.textContent = error instanceof ClientModuleUiConflict ? `${copy.conflict}${error.detail}` : copy.failed;
|
|
6349
|
+
status.textContent = error instanceof ClientModuleUiConflict ? `${copy.conflict}${error.detail}` : error instanceof ClientModuleRequestTimeout ? error.mutation ? copy.writeTimeout : copy.readTimeout : mutation ? copy.saveFailed : copy.failed;
|
|
5614
6350
|
status.focus({ preventScroll: true });
|
|
5615
6351
|
};
|
|
5616
|
-
const
|
|
5617
|
-
if (busy ||
|
|
5618
|
-
|
|
5619
|
-
|
|
6352
|
+
const runRequest = (payload) => {
|
|
6353
|
+
if (disposed || busy !== void 0 || payload !== void 0 && draft === void 0) return;
|
|
6354
|
+
const ownGeneration = ++generation;
|
|
6355
|
+
busy = payload === void 0 ? "read" : "write";
|
|
6356
|
+
if (payload !== void 0) unconfirmedWrite = true;
|
|
6357
|
+
status.classList.remove("dsh-module-error");
|
|
6358
|
+
status.textContent = payload === void 0 ? copy.loading : copy.saving;
|
|
6359
|
+
updateControls();
|
|
6360
|
+
requestClientModuleView(scope, controller.signal, payload).then((view) => {
|
|
6361
|
+
if (disposed || ownGeneration !== generation) return;
|
|
6362
|
+
unconfirmedWrite = false;
|
|
5620
6363
|
render(view);
|
|
5621
|
-
|
|
5622
|
-
},
|
|
5623
|
-
if (
|
|
6364
|
+
status.textContent = payload === void 0 ? "" : copy.saved;
|
|
6365
|
+
}, (error) => {
|
|
6366
|
+
if (disposed || ownGeneration !== generation) return;
|
|
6367
|
+
if (error instanceof ClientModuleUiConflict) unconfirmedWrite = false;
|
|
6368
|
+
failed(error, payload !== void 0);
|
|
6369
|
+
}).finally(() => {
|
|
6370
|
+
if (disposed || ownGeneration !== generation) return;
|
|
6371
|
+
busy = void 0;
|
|
6372
|
+
updateControls();
|
|
5624
6373
|
});
|
|
5625
6374
|
};
|
|
6375
|
+
search.addEventListener("input", renderList);
|
|
6376
|
+
filter.addEventListener("change", renderList);
|
|
5626
6377
|
save.addEventListener("click", () => {
|
|
5627
|
-
|
|
6378
|
+
if ((draft?.changeCount ?? 0) > 0) runRequest({ excludedClientModules: draft?.excludedIds });
|
|
5628
6379
|
});
|
|
5629
6380
|
reset.addEventListener("click", () => {
|
|
5630
|
-
if (window.confirm(copy.resetConfirm))
|
|
6381
|
+
if (busy === void 0 && draft !== void 0 && window.confirm(copy.resetConfirm)) runRequest({ reset: true });
|
|
6382
|
+
});
|
|
6383
|
+
reread.addEventListener("click", () => {
|
|
6384
|
+
if (busy === void 0 && ((draft?.changeCount ?? 0) === 0 || window.confirm(copy.reloadConfirm))) runRequest();
|
|
5631
6385
|
});
|
|
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
6386
|
const dispose = () => {
|
|
5640
6387
|
if (disposed) return;
|
|
5641
6388
|
disposed = true;
|
|
6389
|
+
++generation;
|
|
5642
6390
|
controller.abort();
|
|
5643
|
-
openDialogs.delete(
|
|
6391
|
+
openDialogs.delete(openDialog);
|
|
5644
6392
|
window.removeEventListener("dsh-mobile:native-back", onBack, true);
|
|
6393
|
+
window.removeEventListener("keydown", onEscape, true);
|
|
5645
6394
|
if (dialog.open) dialog.close();
|
|
5646
6395
|
dialog.remove();
|
|
5647
6396
|
if (trigger?.isConnected) trigger.focus({ preventScroll: true });
|
|
5648
6397
|
};
|
|
5649
|
-
|
|
6398
|
+
const requestClose = () => {
|
|
6399
|
+
if (disposed) return;
|
|
6400
|
+
if (unconfirmedWrite && !window.confirm(copy.savingCloseConfirm)) return;
|
|
6401
|
+
if (!unconfirmedWrite && (draft?.changeCount ?? 0) > 0 && !window.confirm(copy.discardConfirm)) return;
|
|
6402
|
+
dispose();
|
|
6403
|
+
};
|
|
6404
|
+
const onBack = (event) => {
|
|
6405
|
+
if (!event.cancelable || !dialog.open || event.defaultPrevented || [...openDialogs].at(-1) !== openDialog) return;
|
|
6406
|
+
event.preventDefault();
|
|
6407
|
+
event.stopImmediatePropagation();
|
|
6408
|
+
requestClose();
|
|
6409
|
+
};
|
|
6410
|
+
const onEscape = (event) => {
|
|
6411
|
+
if (event.key !== "Escape" || event.isComposing || event.ctrlKey || event.altKey || event.metaKey || event.defaultPrevented || !dialog.open || [...openDialogs].at(-1) !== openDialog) return;
|
|
6412
|
+
event.preventDefault();
|
|
6413
|
+
event.stopImmediatePropagation();
|
|
6414
|
+
requestClose();
|
|
6415
|
+
};
|
|
6416
|
+
const openDialog = {
|
|
6417
|
+
dispose,
|
|
6418
|
+
requestClose
|
|
6419
|
+
};
|
|
6420
|
+
openDialogs.add(openDialog);
|
|
6421
|
+
window.addEventListener("dsh-mobile:native-back", onBack, true);
|
|
6422
|
+
window.addEventListener("keydown", onEscape, true);
|
|
6423
|
+
dialog.addEventListener("cancel", (event) => {
|
|
6424
|
+
event.preventDefault();
|
|
6425
|
+
requestClose();
|
|
6426
|
+
});
|
|
5650
6427
|
dialog.addEventListener("close", dispose, { once: true });
|
|
5651
|
-
close.addEventListener("click",
|
|
6428
|
+
close.addEventListener("click", requestClose);
|
|
5652
6429
|
document.body.append(dialog);
|
|
5653
6430
|
dialog.showModal();
|
|
5654
|
-
|
|
5655
|
-
status.textContent = copy.loading;
|
|
5656
|
-
request().then(render, failed).finally(() => {
|
|
5657
|
-
if (!disposed) setBusy(false);
|
|
5658
|
-
});
|
|
6431
|
+
runRequest();
|
|
5659
6432
|
}
|
|
5660
6433
|
/** Settings row used on mobile pages and on the computer's General settings.
|
|
5661
6434
|
* @param props - Selection scope and current locale.
|
|
@@ -6161,42 +6934,29 @@ window.__ModuleLoader__.load({
|
|
|
6161
6934
|
error: "Could not switch computers. Try again."
|
|
6162
6935
|
};
|
|
6163
6936
|
}
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
const
|
|
6167
|
-
const [state, setState] = (0, react.useState)("idle");
|
|
6937
|
+
function useMobileNativeSettingsAction(action) {
|
|
6938
|
+
const [controller] = (0, react.useState)(() => new MobileNativeSettingsActionController(action, () => window.__DSH_MOBILE_NATIVE__));
|
|
6939
|
+
const snapshot = (0, react.useSyncExternalStore)(controller.subscribe, controller.getSnapshot);
|
|
6168
6940
|
(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);
|
|
6941
|
+
controller.start();
|
|
6942
|
+
window.addEventListener("dsh-mobile-native-ready", controller.refresh);
|
|
6184
6943
|
return () => {
|
|
6185
|
-
|
|
6186
|
-
|
|
6944
|
+
window.removeEventListener("dsh-mobile-native-ready", controller.refresh);
|
|
6945
|
+
controller.stop();
|
|
6187
6946
|
};
|
|
6188
|
-
}, []);
|
|
6189
|
-
|
|
6947
|
+
}, [controller]);
|
|
6948
|
+
return {
|
|
6949
|
+
...snapshot,
|
|
6950
|
+
invoke: controller.invoke
|
|
6951
|
+
};
|
|
6952
|
+
}
|
|
6953
|
+
/** Render the native-app-only action in DSH's General settings row style. */
|
|
6954
|
+
function MobileSwitchComputerRow() {
|
|
6955
|
+
const { supported, state, invoke } = useMobileNativeSettingsAction("mobile.switch-computer");
|
|
6956
|
+
if (!supported) return null;
|
|
6190
6957
|
const locale = selectedMobileControlLocale();
|
|
6191
6958
|
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
|
-
};
|
|
6959
|
+
const busy = state === "busy" || state === "success";
|
|
6200
6960
|
return (0, react.createElement)("div", {
|
|
6201
6961
|
className: "dsh-mobile-settings_row",
|
|
6202
6962
|
lang: locale
|
|
@@ -6206,10 +6966,10 @@ window.__ModuleLoader__.load({
|
|
|
6206
6966
|
}, state === "error" ? copy.error : copy.description)), (0, react.createElement)("button", {
|
|
6207
6967
|
type: "button",
|
|
6208
6968
|
className: "dsh-mobile-settings_selector",
|
|
6209
|
-
disabled:
|
|
6210
|
-
"aria-busy":
|
|
6969
|
+
disabled: busy,
|
|
6970
|
+
"aria-busy": busy ? "true" : void 0,
|
|
6211
6971
|
onClick: invoke
|
|
6212
|
-
},
|
|
6972
|
+
}, busy ? copy.busy : state === "error" ? copy.retry : copy.action));
|
|
6213
6973
|
}
|
|
6214
6974
|
function mobileTaskNotificationCopy(locale) {
|
|
6215
6975
|
if (locale === "zh") return {
|
|
@@ -6245,42 +7005,10 @@ window.__ModuleLoader__.load({
|
|
|
6245
7005
|
}
|
|
6246
7006
|
/** Render the Android-only notification permission entry in DSH General settings. */
|
|
6247
7007
|
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;
|
|
7008
|
+
const { supported, state, invoke } = useMobileNativeSettingsAction("notification.settings");
|
|
7009
|
+
if (!supported) return null;
|
|
6272
7010
|
const locale = selectedMobileControlLocale();
|
|
6273
7011
|
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
7012
|
return (0, react.createElement)("div", {
|
|
6285
7013
|
className: "dsh-mobile-settings_row",
|
|
6286
7014
|
lang: locale
|
|
@@ -6450,6 +7178,11 @@ window.__ModuleLoader__.load({
|
|
|
6450
7178
|
cpolar_installer_missing: "cpolarExtractFailed",
|
|
6451
7179
|
cpolar_executable_missing: "cpolarExtractFailed",
|
|
6452
7180
|
cpolar_extract_failed: "cpolarExtractFailed",
|
|
7181
|
+
cpolar_extract_tools_failed: "cpolarExtractToolsFailed",
|
|
7182
|
+
cpolar_extract_archive_failed: "cpolarExtractArchiveFailed",
|
|
7183
|
+
cpolar_extract_database_failed: "cpolarExtractDatabaseFailed",
|
|
7184
|
+
cpolar_extract_cabinet_failed: "cpolarExtractCabinetFailed",
|
|
7185
|
+
cpolar_extract_file_failed: "cpolarExtractFileFailed",
|
|
6453
7186
|
cpolar_storage_failed: "cpolarStorageFailed",
|
|
6454
7187
|
cpolar_config_missing: "cpolarConfigMissing",
|
|
6455
7188
|
cpolar_config_invalid: "cpolarConfigInvalid",
|
|
@@ -6531,6 +7264,11 @@ window.__ModuleLoader__.load({
|
|
|
6531
7264
|
"cpolar_component_invalid",
|
|
6532
7265
|
"cpolar_config_missing",
|
|
6533
7266
|
"cpolar_config_invalid",
|
|
7267
|
+
"cpolar_extract_tools_failed",
|
|
7268
|
+
"cpolar_extract_archive_failed",
|
|
7269
|
+
"cpolar_extract_database_failed",
|
|
7270
|
+
"cpolar_extract_cabinet_failed",
|
|
7271
|
+
"cpolar_extract_file_failed",
|
|
6534
7272
|
"funnel_state_invalid",
|
|
6535
7273
|
"cpolar_start_timeout",
|
|
6536
7274
|
"cpolar_stopped",
|