dsh-mobile 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.en.md +4 -2
- package/README.md +6 -2
- package/docs/MOBILE_PLUGIN_ADAPTATION.en.md +18 -0
- package/docs/MOBILE_PLUGIN_ADAPTATION.md +18 -0
- package/docs/MOBILE_PLUGIN_BASELINE.en.md +117 -0
- package/docs/MOBILE_PLUGIN_BASELINE.md +117 -0
- package/docs/README.md +1 -0
- package/lib/cli.js.map +1 -1
- package/lib/client.js +166 -7
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +5 -0
- package/lib/index.mjs +178 -13
- package/lib/index.mjs.map +1 -1
- package/lib/mobile-layout.js +11 -2
- package/lib/mobile-layout.js.map +1 -1
- package/package.json +3 -1
- package/packages/question-fixes/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -803,7 +803,22 @@ window.__ModuleLoader__.load({
|
|
|
803
803
|
lanSetupConfiguring: "Creating private certificates and local network rules…",
|
|
804
804
|
lanSetupRequired: "Local network setup has not been completed. Choose a network above to continue.",
|
|
805
805
|
lanSetupRestart: "Setup complete for {origin}. Restart DSH once to start local network access.",
|
|
806
|
-
lanSetupFailed: "Setup was not completed. Confirm the network selection and approve the Windows prompt, then try again."
|
|
806
|
+
lanSetupFailed: "Setup was not completed. Confirm the network selection and approve the Windows prompt, then try again.",
|
|
807
|
+
lanChangeTitle: "Change network",
|
|
808
|
+
lanChangeIntro: "Move local network access to another interface. The private CA and paired devices are kept; connected phones reconnect with the new address.",
|
|
809
|
+
lanChangeSelect: "New network",
|
|
810
|
+
lanChangeRefresh: "Scan again",
|
|
811
|
+
lanChangeConfirm: "Change network",
|
|
812
|
+
lanChangeBusy: "Changing network…",
|
|
813
|
+
lanChangeOk: "Network saved. If local access is stopped, enable it before connecting to the new address.",
|
|
814
|
+
lanChangeOkDisconnected: "Network changed and the listener restarted. Connected phones were disconnected; generate a new pairing QR to reconnect.",
|
|
815
|
+
lanChangeCurrent: "current",
|
|
816
|
+
lanChangeUnchanged: "That is already the current network.",
|
|
817
|
+
lanChangeUnavailable: "That network is no longer available. Scan again.",
|
|
818
|
+
lanChangeUnsupported: "This setup uses a fixed address, so it cannot change networks here.",
|
|
819
|
+
lanChangeFailed: "The change did not complete. Refresh the status before retrying; the previous configuration may have been restored.",
|
|
820
|
+
lanChangeRefreshFailed: "Could not refresh network status. A submitted change may have completed; check the current status before retrying.",
|
|
821
|
+
lanChangeRecoveryRequired: "The previous network could not be restored. Check local network configuration before enabling access again."
|
|
807
822
|
});
|
|
808
823
|
Object.assign(MOBILE_CONTROL_MESSAGES.it, {
|
|
809
824
|
lanSetupTitle: "Completa la configurazione della rete locale",
|
|
@@ -818,7 +833,22 @@ window.__ModuleLoader__.load({
|
|
|
818
833
|
lanSetupConfiguring: "Creazione dei certificati privati e delle regole di rete locale…",
|
|
819
834
|
lanSetupRequired: "La configurazione della rete locale non è completa. Scegli una rete qui sopra per continuare.",
|
|
820
835
|
lanSetupRestart: "Configurazione completata per {origin}. Riavvia DSH una volta per attivare l’accesso locale.",
|
|
821
|
-
lanSetupFailed: "Configurazione non completata. Controlla la rete e approva la richiesta di Windows, quindi riprova."
|
|
836
|
+
lanSetupFailed: "Configurazione non completata. Controlla la rete e approva la richiesta di Windows, quindi riprova.",
|
|
837
|
+
lanChangeTitle: "Cambia rete",
|
|
838
|
+
lanChangeIntro: "Sposta l’accesso alla rete locale su un’altra interfaccia. La CA privata e i dispositivi abbinati restano; i telefoni collegati si riconnettono con il nuovo indirizzo.",
|
|
839
|
+
lanChangeSelect: "Nuova rete",
|
|
840
|
+
lanChangeRefresh: "Cerca di nuovo",
|
|
841
|
+
lanChangeConfirm: "Cambia rete",
|
|
842
|
+
lanChangeBusy: "Cambio della rete…",
|
|
843
|
+
lanChangeOk: "Rete salvata. Se l’accesso locale è disattivato, attivalo prima di collegarti al nuovo indirizzo.",
|
|
844
|
+
lanChangeOkDisconnected: "Rete cambiata e listener riavviato. I telefoni collegati sono stati disconnessi; genera un nuovo QR di abbinamento per riconnetterti.",
|
|
845
|
+
lanChangeCurrent: "attuale",
|
|
846
|
+
lanChangeUnchanged: "Questa è già la rete attuale.",
|
|
847
|
+
lanChangeUnavailable: "La rete non è più disponibile. Cerca di nuovo.",
|
|
848
|
+
lanChangeUnsupported: "Questa configurazione usa un indirizzo fisso: non può cambiare rete qui.",
|
|
849
|
+
lanChangeFailed: "Cambio non completato. Aggiorna lo stato prima di riprovare; la configurazione precedente potrebbe essere stata ripristinata.",
|
|
850
|
+
lanChangeRefreshFailed: "Impossibile aggiornare lo stato. Una modifica inviata potrebbe essere stata completata; verifica prima di riprovare.",
|
|
851
|
+
lanChangeRecoveryRequired: "Impossibile ripristinare la rete precedente. Controlla la configurazione locale prima di riattivare l’accesso."
|
|
822
852
|
});
|
|
823
853
|
Object.assign(MOBILE_CONTROL_MESSAGES.zh, {
|
|
824
854
|
lanSetupTitle: "完成局域网配置",
|
|
@@ -833,7 +863,22 @@ window.__ModuleLoader__.load({
|
|
|
833
863
|
lanSetupConfiguring: "正在生成私有证书并配置局域网规则…",
|
|
834
864
|
lanSetupRequired: "尚未完成局域网配置,请选择上方网络继续。",
|
|
835
865
|
lanSetupRestart: "已为 {origin} 完成配置。请重启一次 DSH,局域网访问会自动开启。",
|
|
836
|
-
lanSetupFailed: "配置未完成。请确认网卡选择并允许 Windows 授权提示,然后重试。"
|
|
866
|
+
lanSetupFailed: "配置未完成。请确认网卡选择并允许 Windows 授权提示,然后重试。",
|
|
867
|
+
lanChangeTitle: "更换网络",
|
|
868
|
+
lanChangeIntro: "把局域网访问切换到另一块网卡。私有 CA 与已配对设备都会保留,已连接的手机需要用新地址重新连接。",
|
|
869
|
+
lanChangeSelect: "新网络",
|
|
870
|
+
lanChangeRefresh: "重新扫描",
|
|
871
|
+
lanChangeConfirm: "更换网络",
|
|
872
|
+
lanChangeBusy: "正在更换网络…",
|
|
873
|
+
lanChangeOk: "网络配置已保存。若局域网访问已关闭,请开启后再用新地址连接。",
|
|
874
|
+
lanChangeOkDisconnected: "网络已更换并重启监听,已连接的手机已断开;请生成新的配对二维码重新连接。",
|
|
875
|
+
lanChangeCurrent: "当前",
|
|
876
|
+
lanChangeUnchanged: "这已经是当前网络。",
|
|
877
|
+
lanChangeUnavailable: "该网络已不可用,请重新扫描。",
|
|
878
|
+
lanChangeUnsupported: "当前配置使用固定地址,无法在这里更换网络。",
|
|
879
|
+
lanChangeFailed: "更换未完成。请先刷新状态再决定是否重试;先前配置可能已恢复。",
|
|
880
|
+
lanChangeRefreshFailed: "无法刷新网络状态,已提交的更换可能已完成;请先确认当前状态,不要重复提交。",
|
|
881
|
+
lanChangeRecoveryRequired: "无法恢复先前网络,请先检查局域网配置,再决定是否重新开启访问。"
|
|
837
882
|
});
|
|
838
883
|
Object.assign(MOBILE_CONTROL_MESSAGES.en, {
|
|
839
884
|
selfHostedConnections: "Self-hosted connection",
|
|
@@ -3933,7 +3978,7 @@ window.__ModuleLoader__.load({
|
|
|
3933
3978
|
[data-dsh-mobile-details] { position:fixed !important; z-index:250 !important; inset:0 0 0 auto !important; width:min(94vw,460px) !important; max-width:none !important; transform:translateX(100%); transition:transform var(--dsh-mobile-motion-duration) var(--dsh-mobile-motion-ease); background:var(--dsw-alias-bg-layer-1, #fff); box-shadow:-18px 0 46px rgb(15 23 42 / 18%); }
|
|
3934
3979
|
[data-dsh-mobile-details][data-open="true"] { transform:translateX(0); }
|
|
3935
3980
|
[data-dsh-mobile-handle] { display:none !important; }
|
|
3936
|
-
[data-dsh-mobile-settings] { flex-direction:column !important; width:100vw !important; height:100dvh !important; max-width:none !important; border-radius:0 !important; animation:dsh-mobile-panel-in var(--dsh-mobile-motion-duration) var(--dsh-mobile-motion-ease); }
|
|
3981
|
+
[data-dsh-mobile-settings="true"] { flex-direction:column !important; width:100vw !important; height:100dvh !important; max-width:none !important; border-radius:0 !important; animation:dsh-mobile-panel-in var(--dsh-mobile-motion-duration) var(--dsh-mobile-motion-ease); }
|
|
3937
3982
|
[data-dsh-mobile-settings-nav] { flex:none !important; width:100% !important; padding:max(14px,env(safe-area-inset-top)) 12px 8px !important; gap:10px !important; border-bottom:1px solid var(--dsw-alias-border-l2,#e8ebef); }
|
|
3938
3983
|
[data-dsh-mobile-settings-nav] [class*="_navTitle"] { padding:0 8px !important; font-size:18px !important; line-height:28px !important; }
|
|
3939
3984
|
[data-dsh-mobile-settings-list] { flex-direction:row !important; gap:4px !important; overflow-x:auto !important; scrollbar-width:none; }
|
|
@@ -4150,7 +4195,17 @@ window.__ModuleLoader__.load({
|
|
|
4150
4195
|
const children = Array.from(dialog.children);
|
|
4151
4196
|
const nav = children.find((child) => classToken(child, "_nav"));
|
|
4152
4197
|
const content = children.find((child) => classToken(child, "_content"));
|
|
4153
|
-
if (nav === void 0
|
|
4198
|
+
if (nav === void 0) continue;
|
|
4199
|
+
if (content === void 0) {
|
|
4200
|
+
const header = children.find((child) => classToken(child, "_header"));
|
|
4201
|
+
const options = children.find((child) => classToken(child, "_options"));
|
|
4202
|
+
const title = children.find((child) => classToken(child, "_navTitle"));
|
|
4203
|
+
if (dialog.getAttribute("data-shortcut-modal") !== "settings" || header === void 0 || options === void 0 || title === void 0) continue;
|
|
4204
|
+
dialog.dataset.dshMobileSettings = "grid";
|
|
4205
|
+
header.setAttribute("data-dsh-mobile-settings-header", "true");
|
|
4206
|
+
marked += 1;
|
|
4207
|
+
continue;
|
|
4208
|
+
}
|
|
4154
4209
|
dialog.dataset.dshMobileSettings = "true";
|
|
4155
4210
|
nav.dataset.dshMobileSettingsNav = "true";
|
|
4156
4211
|
content.dataset.dshMobileSettingsContent = "true";
|
|
@@ -11395,6 +11450,31 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11395
11450
|
const lanSetupStatus = element("p", "dsh-mobile-control__status");
|
|
11396
11451
|
lanSetupStatus.setAttribute("role", "status");
|
|
11397
11452
|
lanSetupStatus.setAttribute("aria-live", "polite");
|
|
11453
|
+
const lanChange = element("section", "dsh-mobile-control__lan-setup");
|
|
11454
|
+
lanChange.hidden = true;
|
|
11455
|
+
const lanChangeTitle = element("h3", "dsh-mobile-control__section-title");
|
|
11456
|
+
lanChangeTitle.textContent = t("lanChangeTitle");
|
|
11457
|
+
const lanChangeIntro = element("p", "dsh-mobile-control__intro");
|
|
11458
|
+
lanChangeIntro.textContent = t("lanChangeIntro");
|
|
11459
|
+
const lanChangeForm = element("div", "dsh-mobile-control__lan-setup-form");
|
|
11460
|
+
const lanChangeLabel = element("label", "dsh-mobile-control__field");
|
|
11461
|
+
lanChangeLabel.textContent = t("lanChangeSelect");
|
|
11462
|
+
const lanChangeSelect = element("select");
|
|
11463
|
+
lanChangeSelect.setAttribute("aria-label", t("lanChangeSelect"));
|
|
11464
|
+
lanChangeLabel.append(lanChangeSelect);
|
|
11465
|
+
const lanChangeActions = element("div", "dsh-mobile-control__lan-setup-actions");
|
|
11466
|
+
const lanChangeRefresh = element("button", "dsh-mobile-control__secondary");
|
|
11467
|
+
lanChangeRefresh.type = "button";
|
|
11468
|
+
lanChangeRefresh.textContent = t("lanChangeRefresh");
|
|
11469
|
+
const lanChangeConfirm = element("button", "dsh-mobile-control__primary");
|
|
11470
|
+
lanChangeConfirm.type = "button";
|
|
11471
|
+
lanChangeConfirm.textContent = t("lanChangeConfirm");
|
|
11472
|
+
lanChangeActions.append(lanChangeRefresh, lanChangeConfirm);
|
|
11473
|
+
const lanChangeStatus = element("p", "dsh-mobile-control__status");
|
|
11474
|
+
lanChangeStatus.setAttribute("role", "status");
|
|
11475
|
+
lanChangeStatus.setAttribute("aria-live", "polite");
|
|
11476
|
+
lanChangeForm.append(lanChangeLabel, lanChangeActions);
|
|
11477
|
+
lanChange.append(lanChangeTitle, lanChangeIntro, lanChangeForm, lanChangeStatus);
|
|
11398
11478
|
const trustedNetworks = element("details", "dsh-mobile-control__details");
|
|
11399
11479
|
const trustedNetworksSummary = element("summary");
|
|
11400
11480
|
trustedNetworksSummary.textContent = t("trustedNetworksTitle");
|
|
@@ -12730,7 +12810,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
12730
12810
|
diagnosticsDetails.append(diagnosticsDetailsSummary, diagnosticsReport);
|
|
12731
12811
|
header.append(title, headerActions);
|
|
12732
12812
|
actions.append(toggle, pair, linkPair);
|
|
12733
|
-
lanView.append(lanSetup, access, qrBox, lanPairingValidity, status, extensionStatus, actions, trustedNetworks);
|
|
12813
|
+
lanView.append(lanSetup, lanChange, access, qrBox, lanPairingValidity, status, extensionStatus, actions, trustedNetworks);
|
|
12734
12814
|
const channelPane = element("div", "dsh-mobile-control__channel-pane");
|
|
12735
12815
|
channelPane.append(lanView, remoteWorkspace);
|
|
12736
12816
|
const remoteColumns = element("div", "dsh-mobile-control__remote-columns");
|
|
@@ -12745,6 +12825,9 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
12745
12825
|
let lanConfigured = true;
|
|
12746
12826
|
let lanRestartRequired = false;
|
|
12747
12827
|
let lanSetupLoaded = false;
|
|
12828
|
+
let lanChangeLoaded = false;
|
|
12829
|
+
let lanChangeBusy = false;
|
|
12830
|
+
let lanChangeRequest;
|
|
12748
12831
|
let remoteRunning = false;
|
|
12749
12832
|
let remoteReady = false;
|
|
12750
12833
|
let remoteProvider = "tailscale";
|
|
@@ -12961,6 +13044,8 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
12961
13044
|
lanChoiceBadge.textContent = running ? t("lanBadgeOn") : t("lanBadgeOff");
|
|
12962
13045
|
origin = running && typeof data.origin === "string" ? data.origin : "";
|
|
12963
13046
|
lanSetup.hidden = lanConfigured && !lanRestartRequired;
|
|
13047
|
+
lanChange.hidden = !lanConfigured || lanRestartRequired || data.rebindSupported !== true;
|
|
13048
|
+
if (!lanChange.hidden && !lanChangeLoaded) loadLanChange();
|
|
12964
13049
|
lanSetupForm.hidden = lanRestartRequired;
|
|
12965
13050
|
lanSetupIntro.hidden = lanRestartRequired;
|
|
12966
13051
|
status.hidden = !lanConfigured || lanRestartRequired;
|
|
@@ -12974,7 +13059,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
12974
13059
|
accessLink.textContent = origin;
|
|
12975
13060
|
accessLink.title = origin;
|
|
12976
13061
|
status.classList.toggle("is-running", running);
|
|
12977
|
-
status.textContent = !lanConfigured ? t("lanSetupRequired") : lanRestartRequired ? t("lanSetupRestart", { origin: typeof data.pendingOrigin === "string" ? data.pendingOrigin : "" }) : running ? t("lanOn") : t("lanOff");
|
|
13062
|
+
status.textContent = data.recoveryRequired === true ? t("lanChangeRecoveryRequired") : !lanConfigured ? t("lanSetupRequired") : lanRestartRequired ? t("lanSetupRestart", { origin: typeof data.pendingOrigin === "string" ? data.pendingOrigin : "" }) : running ? t("lanOn") : t("lanOff");
|
|
12978
13063
|
const extensionData = data.extensions;
|
|
12979
13064
|
if (extensionData !== null && typeof extensionData === "object") {
|
|
12980
13065
|
const loaded = typeof extensionData.loaded === "number" ? extensionData.loaded : 0;
|
|
@@ -13065,6 +13150,80 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
13065
13150
|
lanSetupConfigure.disabled = lanRestartRequired || lanSetupSelect.options.length === 0;
|
|
13066
13151
|
});
|
|
13067
13152
|
});
|
|
13153
|
+
const syncLanChange = () => {
|
|
13154
|
+
const waiting = lanChangeBusy || lanChangeRequest !== void 0;
|
|
13155
|
+
lanChangeSelect.disabled = waiting;
|
|
13156
|
+
lanChangeRefresh.disabled = waiting;
|
|
13157
|
+
lanChangeConfirm.disabled = waiting || lanChangeSelect.options.length === 0;
|
|
13158
|
+
};
|
|
13159
|
+
const renderLanChange = (data) => {
|
|
13160
|
+
const networks = Array.isArray(data.networks) ? data.networks.filter((entry) => typeof entry === "object" && entry !== null) : [];
|
|
13161
|
+
lanChangeSelect.replaceChildren();
|
|
13162
|
+
for (const network of networks) {
|
|
13163
|
+
const name = typeof network.name === "string" ? network.name : "";
|
|
13164
|
+
const address = typeof network.address === "string" ? network.address : "";
|
|
13165
|
+
if (name === "") continue;
|
|
13166
|
+
const option = element("option");
|
|
13167
|
+
option.value = name;
|
|
13168
|
+
option.textContent = `${name} · ${address}${network.current === true ? ` · ${t("lanChangeCurrent")}` : ""}`;
|
|
13169
|
+
if (network.current === true) option.selected = true;
|
|
13170
|
+
lanChangeSelect.append(option);
|
|
13171
|
+
}
|
|
13172
|
+
syncLanChange();
|
|
13173
|
+
};
|
|
13174
|
+
const loadLanChange = () => {
|
|
13175
|
+
if (lanChangeRequest !== void 0) return lanChangeRequest;
|
|
13176
|
+
lanChangeLoaded = true;
|
|
13177
|
+
lanChangeRequest = Promise.all([controlRequestJson("/api/mobile-access/lan/networks"), controlRequestJson("/api/mobile-access/lan/control")]).then(([data, control]) => {
|
|
13178
|
+
if (!lifecycle.signal.aborted) {
|
|
13179
|
+
renderLanChange(data);
|
|
13180
|
+
render(control);
|
|
13181
|
+
}
|
|
13182
|
+
}, () => {
|
|
13183
|
+
if (!lifecycle.signal.aborted) lanChangeStatus.textContent = t("lanChangeRefreshFailed");
|
|
13184
|
+
}).finally(() => {
|
|
13185
|
+
lanChangeRequest = void 0;
|
|
13186
|
+
if (!lifecycle.signal.aborted) syncLanChange();
|
|
13187
|
+
});
|
|
13188
|
+
syncLanChange();
|
|
13189
|
+
return lanChangeRequest;
|
|
13190
|
+
};
|
|
13191
|
+
lanChangeRefresh.addEventListener("click", () => {
|
|
13192
|
+
loadLanChange();
|
|
13193
|
+
});
|
|
13194
|
+
lanChangeSelect.addEventListener("change", syncLanChange);
|
|
13195
|
+
lanChangeConfirm.addEventListener("click", () => {
|
|
13196
|
+
if (lanChangeSelect.value === "" || lanChangeBusy || lanChangeRequest !== void 0) return;
|
|
13197
|
+
lanChangeBusy = true;
|
|
13198
|
+
syncLanChange();
|
|
13199
|
+
lanChangeConfirm.textContent = t("lanChangeBusy");
|
|
13200
|
+
lanChangeStatus.textContent = t("lanChangeBusy");
|
|
13201
|
+
controlRequestJson("/api/mobile-access/lan/rebind", {
|
|
13202
|
+
method: "POST",
|
|
13203
|
+
body: JSON.stringify({ networkInterface: lanChangeSelect.value })
|
|
13204
|
+
}, LONG_CONTROL_REQUEST_TIMEOUT_MS).then(async (data) => {
|
|
13205
|
+
if (lifecycle.signal.aborted) return;
|
|
13206
|
+
lanChangeStatus.textContent = data.disconnected === true && data.ready === true ? t("lanChangeOkDisconnected") : t("lanChangeOk");
|
|
13207
|
+
try {
|
|
13208
|
+
const control = await controlRequestJson("/api/mobile-access/lan/control");
|
|
13209
|
+
if (lifecycle.signal.aborted) return;
|
|
13210
|
+
render(control);
|
|
13211
|
+
await loadLanChange();
|
|
13212
|
+
if (!lifecycle.signal.aborted && data.ready === true) openPairing("key");
|
|
13213
|
+
} catch {
|
|
13214
|
+
if (!lifecycle.signal.aborted) lanChangeStatus.textContent = t("lanChangeRefreshFailed");
|
|
13215
|
+
}
|
|
13216
|
+
}, (error) => {
|
|
13217
|
+
if (lifecycle.signal.aborted) return;
|
|
13218
|
+
const text = String(error);
|
|
13219
|
+
lanChangeStatus.textContent = text.includes("lan_rebind_unchanged") ? t("lanChangeUnchanged") : text.includes("lan_rebind_network_unavailable") ? t("lanChangeUnavailable") : text.includes("lan_rebind_unsupported") ? t("lanChangeUnsupported") : text.includes("lan_rebind_recovery_required") ? t("lanChangeRecoveryRequired") : t("lanChangeFailed");
|
|
13220
|
+
}).finally(() => {
|
|
13221
|
+
lanChangeBusy = false;
|
|
13222
|
+
if (lifecycle.signal.aborted) return;
|
|
13223
|
+
lanChangeConfirm.textContent = t("lanChangeConfirm");
|
|
13224
|
+
syncLanChange();
|
|
13225
|
+
});
|
|
13226
|
+
});
|
|
13068
13227
|
const clearQrTarget = (target) => {
|
|
13069
13228
|
if (typeof target.replaceChildren === "function") {
|
|
13070
13229
|
target.replaceChildren();
|