@linxin666/dsh-remote-web-ui 0.3.6 → 0.3.9
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/README.i18n.yaml +2 -2
- package/README.md +77 -468
- package/README.zh.md +83 -113
- package/lib/client.js +955 -203
- package/lib/client.js.map +1 -1
- package/lib/index.js +1136 -1466
- package/lib/types/client/FooterRemoteEntry.d.ts +6 -7
- package/lib/types/client/FooterRemoteEntry.d.ts.map +1 -1
- package/lib/types/client/FooterRemoteEntry.js +1 -1
- package/lib/types/client/RemoteEntry.d.ts +7 -4
- package/lib/types/client/RemoteEntry.d.ts.map +1 -1
- package/lib/types/client/RemoteEntry.js +7 -10
- package/lib/types/client/RemotePanel.d.ts +2 -2
- package/lib/types/client/RemotePanel.d.ts.map +1 -1
- package/lib/types/client/RemotePanel.js +2 -2
- package/lib/types/client/RemoteSettingsCard.d.ts +9 -5
- package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
- package/lib/types/client/RemoteSettingsCard.js +56 -3
- package/lib/types/client/deep-link.d.ts +10 -13
- package/lib/types/client/deep-link.d.ts.map +1 -1
- package/lib/types/client/deep-link.js +14 -74
- package/lib/types/client/index.d.ts +2 -6
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +73 -39
- package/lib/types/client/locales.d.ts +26 -12
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +36 -22
- package/lib/types/client/mobile-adapt.d.ts +40 -0
- package/lib/types/client/mobile-adapt.d.ts.map +1 -0
- package/lib/types/client/mobile-adapt.js +967 -0
- package/lib/types/client/pair-api.d.ts +24 -6
- package/lib/types/client/pair-api.d.ts.map +1 -1
- package/lib/types/client/pair-api.js +9 -14
- package/lib/types/client/remote-channel.d.ts +3 -0
- package/lib/types/client/remote-channel.d.ts.map +1 -1
- package/lib/types/client/remote-channel.js +35 -3
- package/lib/types/client/settings-form.d.ts +35 -43
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +60 -53
- package/lib/types/firewall.d.ts +75 -0
- package/lib/types/firewall.d.ts.map +1 -0
- package/lib/types/firewall.js +184 -0
- package/lib/types/gate.d.ts +11 -5
- package/lib/types/gate.d.ts.map +1 -1
- package/lib/types/gate.js +11 -5
- package/lib/types/index.d.ts +39 -10
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +206 -58
- package/lib/types/inner-auth.d.ts +32 -0
- package/lib/types/inner-auth.d.ts.map +1 -0
- package/lib/types/inner-auth.js +72 -0
- package/lib/types/lan-bind-plan.d.ts +55 -0
- package/lib/types/lan-bind-plan.d.ts.map +1 -0
- package/lib/types/lan-bind-plan.js +60 -0
- package/lib/types/lan-bind.d.ts +69 -0
- package/lib/types/lan-bind.d.ts.map +1 -0
- package/lib/types/lan-bind.js +128 -0
- package/lib/types/loopback-proxy.d.ts +9 -2
- package/lib/types/loopback-proxy.d.ts.map +1 -1
- package/lib/types/loopback-proxy.js +24 -2
- package/lib/types/pairing.d.ts +15 -3
- package/lib/types/pairing.d.ts.map +1 -1
- package/lib/types/pairing.js +20 -6
- package/lib/types/remote-api.d.ts +30 -13
- package/lib/types/remote-api.d.ts.map +1 -1
- package/lib/types/remote-api.js +55 -42
- package/lib/types/remote-channel-boot.d.ts +21 -2
- package/lib/types/remote-channel-boot.d.ts.map +1 -1
- package/lib/types/remote-channel-boot.js +65 -3
- package/lib/types/remote-channel-rules.d.ts +8 -0
- package/lib/types/remote-channel-rules.d.ts.map +1 -1
- package/lib/types/remote-channel-rules.js +9 -2
- package/lib/types/remote-methods.d.ts +45 -11
- package/lib/types/remote-methods.d.ts.map +1 -1
- package/lib/types/remote-methods.js +56 -27
- package/lib/types/remote-presence-pet.d.ts +51 -0
- package/lib/types/remote-presence-pet.d.ts.map +1 -0
- package/lib/types/remote-presence-pet.js +101 -0
- package/lib/types/routes.d.ts +76 -4
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +336 -19
- package/lib/types/tunnel.d.ts.map +1 -1
- package/lib/types/tunnel.js +7 -1
- package/package.json +20 -18
- package/src/client/FooterRemoteEntry.tsx +9 -10
- package/src/client/RemoteEntry.tsx +13 -14
- package/src/client/RemotePanel.tsx +5 -16
- package/src/client/RemoteSettingsCard.tsx +72 -13
- package/src/client/deep-link.ts +14 -75
- package/src/client/index.ts +77 -39
- package/src/client/locales.ts +36 -22
- package/src/client/mobile-adapt.ts +913 -0
- package/src/client/pair-api.ts +26 -15
- package/src/client/remote-channel.ts +31 -3
- package/src/client/settings-form.ts +78 -92
- package/src/dsh-home.ts +1 -1
- package/src/firewall.ts +230 -0
- package/src/gate.ts +11 -5
- package/src/index.ts +235 -65
- package/src/inner-auth.ts +85 -0
- package/src/lan-bind-plan.ts +81 -0
- package/src/lan-bind.ts +145 -0
- package/src/loopback-proxy.ts +34 -0
- package/src/pairing.persist.test.ts +1 -1
- package/src/pairing.ts +20 -6
- package/src/remote-api.ts +67 -45
- package/src/remote-channel-boot.ts +70 -3
- package/src/remote-channel-rules.ts +17 -2
- package/src/remote-methods.ts +58 -27
- package/src/remote-presence-pet.ts +129 -0
- package/src/routes.ts +358 -19
- package/src/tunnel.ts +7 -1
- package/assets/apple-touch-icon.png +0 -0
- package/assets/mobile-icon-192.png +0 -0
- package/assets/mobile-icon-512.png +0 -0
- package/assets/mobile-service-worker.js +0 -63
- package/lib/mobile.js +0 -17658
- package/lib/mobile.js.map +0 -1
- package/lib/types/mobile/PairRequiredView.d.ts +0 -7
- package/lib/types/mobile/PairRequiredView.d.ts.map +0 -1
- package/lib/types/mobile/PairRequiredView.js +0 -28
- package/lib/types/mobile/PairRequiredView.test.d.ts +0 -2
- package/lib/types/mobile/PairRequiredView.test.d.ts.map +0 -1
- package/lib/types/mobile/PairRequiredView.test.js +0 -24
- package/lib/types/mobile/api.d.ts +0 -145
- package/lib/types/mobile/api.d.ts.map +0 -1
- package/lib/types/mobile/api.js +0 -103
- package/lib/types/mobile/index.d.ts +0 -10
- package/lib/types/mobile/index.d.ts.map +0 -1
- package/lib/types/mobile/index.js +0 -38
- package/lib/types/mobile/markdown.d.ts +0 -34
- package/lib/types/mobile/markdown.d.ts.map +0 -1
- package/lib/types/mobile/markdown.js +0 -285
- package/lib/types/mobile/markdown.test.d.ts +0 -2
- package/lib/types/mobile/markdown.test.d.ts.map +0 -1
- package/lib/types/mobile/markdown.test.js +0 -79
- package/lib/types/mobile/messages.d.ts +0 -149
- package/lib/types/mobile/messages.d.ts.map +0 -1
- package/lib/types/mobile/messages.js +0 -561
- package/lib/types/mobile/messages.test.d.ts +0 -2
- package/lib/types/mobile/messages.test.d.ts.map +0 -1
- package/lib/types/mobile/messages.test.js +0 -356
- package/lib/types/mobile/mobile-styles.d.ts +0 -6
- package/lib/types/mobile/mobile-styles.d.ts.map +0 -1
- package/lib/types/mobile/mobile-styles.js +0 -1438
- package/lib/types/mobile/mobile-theme.d.ts +0 -22
- package/lib/types/mobile/mobile-theme.d.ts.map +0 -1
- package/lib/types/mobile/mobile-theme.js +0 -70
- package/lib/types/mobile/mobile-theme.test.d.ts +0 -2
- package/lib/types/mobile/mobile-theme.test.d.ts.map +0 -1
- package/lib/types/mobile/mobile-theme.test.js +0 -71
- package/lib/types/mobile/mux.d.ts +0 -128
- package/lib/types/mobile/mux.d.ts.map +0 -1
- package/lib/types/mobile/mux.js +0 -305
- package/lib/types/mobile/mux.test.d.ts +0 -2
- package/lib/types/mobile/mux.test.d.ts.map +0 -1
- package/lib/types/mobile/mux.test.js +0 -267
- package/lib/types/mobile/pairing.d.ts +0 -31
- package/lib/types/mobile/pairing.d.ts.map +0 -1
- package/lib/types/mobile/pairing.js +0 -62
- package/lib/types/mobile/rpc.d.ts +0 -38
- package/lib/types/mobile/rpc.d.ts.map +0 -1
- package/lib/types/mobile/rpc.js +0 -83
- package/lib/types/mobile/theme-toggle.d.ts +0 -12
- package/lib/types/mobile/theme-toggle.d.ts.map +0 -1
- package/lib/types/mobile/theme-toggle.js +0 -26
- package/lib/types/mobile/views/App.d.ts +0 -57
- package/lib/types/mobile/views/App.d.ts.map +0 -1
- package/lib/types/mobile/views/App.js +0 -150
- package/lib/types/mobile/views/App.test.d.ts +0 -2
- package/lib/types/mobile/views/App.test.d.ts.map +0 -1
- package/lib/types/mobile/views/App.test.js +0 -52
- package/lib/types/mobile/views/ChatView.d.ts +0 -63
- package/lib/types/mobile/views/ChatView.d.ts.map +0 -1
- package/lib/types/mobile/views/ChatView.js +0 -821
- package/lib/types/mobile/views/ChatView.stream.test.d.ts +0 -2
- package/lib/types/mobile/views/ChatView.stream.test.d.ts.map +0 -1
- package/lib/types/mobile/views/ChatView.stream.test.js +0 -236
- package/lib/types/mobile/views/ChatView.test.d.ts +0 -2
- package/lib/types/mobile/views/ChatView.test.d.ts.map +0 -1
- package/lib/types/mobile/views/ChatView.test.js +0 -718
- package/lib/types/mobile/views/SessionListView.d.ts +0 -27
- package/lib/types/mobile/views/SessionListView.d.ts.map +0 -1
- package/lib/types/mobile/views/SessionListView.js +0 -139
- package/lib/types/mobile/views/SessionListView.test.d.ts +0 -2
- package/lib/types/mobile/views/SessionListView.test.d.ts.map +0 -1
- package/lib/types/mobile/views/SessionListView.test.js +0 -167
- package/lib/types/mobile/views/WorkspaceView.d.ts +0 -21
- package/lib/types/mobile/views/WorkspaceView.d.ts.map +0 -1
- package/lib/types/mobile/views/WorkspaceView.js +0 -93
- package/lib/types/mobile/views/WorkspaceView.test.d.ts +0 -2
- package/lib/types/mobile/views/WorkspaceView.test.d.ts.map +0 -1
- package/lib/types/mobile/views/WorkspaceView.test.js +0 -183
- package/lib/types/mobile-api.d.ts +0 -65
- package/lib/types/mobile-api.d.ts.map +0 -1
- package/lib/types/mobile-api.js +0 -404
- package/lib/types/mobile-pending.d.ts +0 -38
- package/lib/types/mobile-pending.d.ts.map +0 -1
- package/lib/types/mobile-pending.js +0 -59
- package/lib/types/mobile-pwa.d.ts +0 -6
- package/lib/types/mobile-pwa.d.ts.map +0 -1
- package/lib/types/mobile-pwa.js +0 -17
- package/lib/types/mobile-routes.d.ts +0 -13
- package/lib/types/mobile-routes.d.ts.map +0 -1
- package/lib/types/mobile-routes.js +0 -217
- package/lib/types/paired-model-catalog.d.ts +0 -17
- package/lib/types/paired-model-catalog.d.ts.map +0 -1
- package/lib/types/paired-model-catalog.js +0 -311
- package/src/mobile/PairRequiredView.test.tsx +0 -28
- package/src/mobile/PairRequiredView.tsx +0 -58
- package/src/mobile/api.ts +0 -229
- package/src/mobile/index.tsx +0 -41
- package/src/mobile/markdown.test.ts +0 -91
- package/src/mobile/markdown.ts +0 -291
- package/src/mobile/messages.test.ts +0 -402
- package/src/mobile/messages.ts +0 -685
- package/src/mobile/mobile-styles.ts +0 -1439
- package/src/mobile/mobile-theme.test.ts +0 -84
- package/src/mobile/mobile-theme.ts +0 -76
- package/src/mobile/mux.test.ts +0 -314
- package/src/mobile/mux.ts +0 -336
- package/src/mobile/pairing.ts +0 -77
- package/src/mobile/rpc.ts +0 -90
- package/src/mobile/theme-toggle.tsx +0 -47
- package/src/mobile/views/App.test.tsx +0 -62
- package/src/mobile/views/App.tsx +0 -213
- package/src/mobile/views/ChatView.stream.test.tsx +0 -268
- package/src/mobile/views/ChatView.test.tsx +0 -816
- package/src/mobile/views/ChatView.tsx +0 -1342
- package/src/mobile/views/SessionListView.test.tsx +0 -207
- package/src/mobile/views/SessionListView.tsx +0 -244
- package/src/mobile/views/WorkspaceView.test.tsx +0 -227
- package/src/mobile/views/WorkspaceView.tsx +0 -245
- package/src/mobile-api.ts +0 -418
- package/src/mobile-pending.ts +0 -88
- package/src/mobile-pwa.ts +0 -19
- package/src/mobile-routes.ts +0 -231
- package/src/paired-model-catalog.ts +0 -272
package/lib/client.js
CHANGED
|
@@ -32,7 +32,6 @@ window.__ModuleLoader__.load({
|
|
|
32
32
|
let react_dom = require("react-dom");
|
|
33
33
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
34
34
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
35
|
-
let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
|
|
36
35
|
//#region ../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
|
|
37
36
|
function r(e) {
|
|
38
37
|
var t, f, n = "";
|
|
@@ -1241,7 +1240,6 @@ window.__ModuleLoader__.load({
|
|
|
1241
1240
|
/**
|
|
1242
1241
|
* Mint a fresh pairing token (one active token at a time — this invalidates
|
|
1243
1242
|
* any previous link).
|
|
1244
|
-
* @param workspaceId - optional current workspace to deep-link the phone into.
|
|
1245
1243
|
* @param address - optional LAN IP literal the QR must be built from (the
|
|
1246
1244
|
* default is the first interface); unknown literals refuse with
|
|
1247
1245
|
* 'unknown-address'.
|
|
@@ -1249,14 +1247,11 @@ window.__ModuleLoader__.load({
|
|
|
1249
1247
|
* 0.0.0.0), or the forbidden refusal (the loopback-only fence rejected this
|
|
1250
1248
|
* origin — the panel is a desktop control endpoint).
|
|
1251
1249
|
*/
|
|
1252
|
-
async function issuePair(
|
|
1250
|
+
async function issuePair(address) {
|
|
1253
1251
|
const response = await fetch("/api/pair/issue", {
|
|
1254
1252
|
method: "POST",
|
|
1255
1253
|
headers: { "content-type": "application/json" },
|
|
1256
|
-
body: JSON.stringify({
|
|
1257
|
-
...workspaceId !== void 0 ? { workspaceId } : {},
|
|
1258
|
-
...address !== void 0 ? { address } : {}
|
|
1259
|
-
})
|
|
1254
|
+
body: JSON.stringify({ ...address !== void 0 ? { address } : {} })
|
|
1260
1255
|
});
|
|
1261
1256
|
if (!response.ok) {
|
|
1262
1257
|
if (response.status === 409) return {
|
|
@@ -1323,21 +1318,16 @@ window.__ModuleLoader__.load({
|
|
|
1323
1318
|
async function sendHeartbeat() {
|
|
1324
1319
|
await fetch("/api/pair/heartbeat", { method: "POST" });
|
|
1325
1320
|
}
|
|
1326
|
-
/** Whether the current page URL carries a pairing token
|
|
1321
|
+
/** Whether the current page URL carries a pairing token. */
|
|
1327
1322
|
function readPairParams(search) {
|
|
1328
|
-
const
|
|
1329
|
-
|
|
1330
|
-
const workspace = params.get("workspace");
|
|
1331
|
-
return {
|
|
1332
|
-
...pair !== null && pair !== "" ? { pair } : {},
|
|
1333
|
-
...workspace !== null && workspace !== "" ? { workspace } : {}
|
|
1334
|
-
};
|
|
1323
|
+
const pair = new URLSearchParams(search).get("pair");
|
|
1324
|
+
return pair !== null && pair !== "" ? { pair } : {};
|
|
1335
1325
|
}
|
|
1336
|
-
/**
|
|
1337
|
-
function
|
|
1338
|
-
const
|
|
1339
|
-
|
|
1340
|
-
return
|
|
1326
|
+
/** Read the LAN-bind facts (loopback-only endpoint). */
|
|
1327
|
+
async function readLanBindStatus() {
|
|
1328
|
+
const response = await fetch("/api/pair/lan-bind");
|
|
1329
|
+
if (!response.ok) throw new Error(`remote-web-ui: lan-bind status failed with ${String(response.status)}`);
|
|
1330
|
+
return await response.json();
|
|
1341
1331
|
}
|
|
1342
1332
|
/** Human-readable expiry clock, e.g. "10:35". */
|
|
1343
1333
|
function formatClock(epochMs) {
|
|
@@ -1632,15 +1622,15 @@ window.__ModuleLoader__.load({
|
|
|
1632
1622
|
className: remote_module_css_default.hint,
|
|
1633
1623
|
children: state.public ? t("pair.publicHint") : t("pair.hint")
|
|
1634
1624
|
}),
|
|
1635
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1625
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1636
1626
|
className: remote_module_css_default.pairLinks,
|
|
1637
|
-
children:
|
|
1627
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1638
1628
|
className: remote_module_css_default.pairLinkRow,
|
|
1639
1629
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1640
1630
|
className: remote_module_css_default.pairLinkText,
|
|
1641
1631
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1642
1632
|
className: remote_module_css_default.pairLinkLabel,
|
|
1643
|
-
children: t("pair.
|
|
1633
|
+
children: t("pair.linkLabel")
|
|
1644
1634
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
1645
1635
|
className: remote_module_css_default.link,
|
|
1646
1636
|
title: state.url,
|
|
@@ -1649,28 +1639,10 @@ window.__ModuleLoader__.load({
|
|
|
1649
1639
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1650
1640
|
type: "button",
|
|
1651
1641
|
className: remote_module_css_default.copyLink,
|
|
1652
|
-
onClick: () => onCopy(
|
|
1653
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, { size: 14 }), copied
|
|
1642
|
+
onClick: () => onCopy(state.url),
|
|
1643
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, { size: 14 }), copied ? t("action.copied") : t("action.copyLink")]
|
|
1654
1644
|
})]
|
|
1655
|
-
})
|
|
1656
|
-
className: remote_module_css_default.pairLinkRow,
|
|
1657
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1658
|
-
className: remote_module_css_default.pairLinkText,
|
|
1659
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1660
|
-
className: remote_module_css_default.pairLinkLabel,
|
|
1661
|
-
children: t("pair.desktopLabel")
|
|
1662
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
1663
|
-
className: remote_module_css_default.link,
|
|
1664
|
-
title: desktopPairUrl(state.url),
|
|
1665
|
-
children: desktopPairUrl(state.url)
|
|
1666
|
-
})]
|
|
1667
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1668
|
-
type: "button",
|
|
1669
|
-
className: remote_module_css_default.copyLink,
|
|
1670
|
-
onClick: () => onCopy("desktop", desktopPairUrl(state.url)),
|
|
1671
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, { size: 14 }), copied === "desktop" ? t("action.copied") : t("action.copyDesktop")]
|
|
1672
|
-
})]
|
|
1673
|
-
})]
|
|
1645
|
+
})
|
|
1674
1646
|
}),
|
|
1675
1647
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1676
1648
|
className: remote_module_css_default.oneTimeHint,
|
|
@@ -2311,17 +2283,16 @@ window.__ModuleLoader__.load({
|
|
|
2311
2283
|
* @param props - composed slot props (contract in this package).
|
|
2312
2284
|
* @returns the entry element tree.
|
|
2313
2285
|
*/
|
|
2314
|
-
function RemoteEntry({ wide,
|
|
2286
|
+
function RemoteEntry({ wide, t }) {
|
|
2315
2287
|
const [open, setOpen] = (0, react.useState)(false);
|
|
2316
2288
|
const [state, setState] = (0, react.useState)({ kind: "lan-required" });
|
|
2317
2289
|
const stateRef = (0, react.useRef)(state);
|
|
2318
2290
|
(0, react.useEffect)(() => {
|
|
2319
2291
|
stateRef.current = state;
|
|
2320
2292
|
}, [state]);
|
|
2321
|
-
const [copied, setCopied] = (0, react.useState)(
|
|
2293
|
+
const [copied, setCopied] = (0, react.useState)(false);
|
|
2322
2294
|
const eventSource = (0, react.useRef)(void 0);
|
|
2323
2295
|
const openSeq = (0, react.useRef)(0);
|
|
2324
|
-
const workspaceId = useWorkspaces((s) => s.recentWorkspaceId);
|
|
2325
2296
|
const closeEventSource = (0, react.useCallback)(() => {
|
|
2326
2297
|
eventSource.current?.close();
|
|
2327
2298
|
eventSource.current = void 0;
|
|
@@ -2329,7 +2300,7 @@ window.__ModuleLoader__.load({
|
|
|
2329
2300
|
const mint = (0, react.useCallback)(async (address) => {
|
|
2330
2301
|
let result;
|
|
2331
2302
|
try {
|
|
2332
|
-
result = await issuePair(
|
|
2303
|
+
result = await issuePair(address);
|
|
2333
2304
|
} catch {
|
|
2334
2305
|
return { kind: "unreachable" };
|
|
2335
2306
|
}
|
|
@@ -2353,7 +2324,7 @@ window.__ModuleLoader__.load({
|
|
|
2353
2324
|
address: address ?? result.lanAddresses[0] ?? "",
|
|
2354
2325
|
lanAddresses: result.lanAddresses
|
|
2355
2326
|
};
|
|
2356
|
-
}, [
|
|
2327
|
+
}, []);
|
|
2357
2328
|
const openPanel = (0, react.useCallback)(async () => {
|
|
2358
2329
|
const seq = ++openSeq.current;
|
|
2359
2330
|
setOpen(true);
|
|
@@ -2429,12 +2400,12 @@ window.__ModuleLoader__.load({
|
|
|
2429
2400
|
const handlePickPublic = (0, react.useCallback)(() => {
|
|
2430
2401
|
mint().then(setState);
|
|
2431
2402
|
}, [mint]);
|
|
2432
|
-
const handleCopy = (0, react.useCallback)((
|
|
2403
|
+
const handleCopy = (0, react.useCallback)((url) => {
|
|
2433
2404
|
copyText(url).then((ok) => {
|
|
2434
2405
|
if (!ok) return;
|
|
2435
|
-
setCopied(
|
|
2406
|
+
setCopied(true);
|
|
2436
2407
|
window.setTimeout(() => {
|
|
2437
|
-
setCopied(
|
|
2408
|
+
setCopied(false);
|
|
2438
2409
|
}, 1500);
|
|
2439
2410
|
});
|
|
2440
2411
|
}, []);
|
|
@@ -2494,8 +2465,6 @@ window.__ModuleLoader__.load({
|
|
|
2494
2465
|
function FooterRemoteEntry(props) {
|
|
2495
2466
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RemoteEntry, {
|
|
2496
2467
|
wide: props.wide,
|
|
2497
|
-
useWorkspaces: () => void 0,
|
|
2498
|
-
useSessions: () => void 0,
|
|
2499
2468
|
t: props.t
|
|
2500
2469
|
});
|
|
2501
2470
|
}
|
|
@@ -2987,6 +2956,19 @@ window.__ModuleLoader__.load({
|
|
|
2987
2956
|
});
|
|
2988
2957
|
}
|
|
2989
2958
|
//#endregion
|
|
2959
|
+
//#region \0dsh-store-engine
|
|
2960
|
+
const platform = ["@deepseek-ai/dsh-client", "-store"].join("");
|
|
2961
|
+
const legacy = ["@deepseek-ai/dsh-client-runtime", "/client"].join("");
|
|
2962
|
+
let engine;
|
|
2963
|
+
try {
|
|
2964
|
+
engine = require(platform);
|
|
2965
|
+
} catch {
|
|
2966
|
+
engine = require(legacy);
|
|
2967
|
+
}
|
|
2968
|
+
const createSnapshotStore = engine.createSnapshotStore;
|
|
2969
|
+
engine.defineStore;
|
|
2970
|
+
engine.shallowEqual;
|
|
2971
|
+
//#endregion
|
|
2990
2972
|
//#region src/client/settings-form.ts
|
|
2991
2973
|
/** A whole- or decimal-number field. An empty draft clears the field; any other draft that is not a finite number within the constraints blocks the save. */
|
|
2992
2974
|
function numberField(field, constraints = {}) {
|
|
@@ -3080,7 +3062,7 @@ window.__ModuleLoader__.load({
|
|
|
3080
3062
|
}
|
|
3081
3063
|
/** Publish a projection of this form, rebuilt whenever the scope or a draft changes. */
|
|
3082
3064
|
bind(project) {
|
|
3083
|
-
const store =
|
|
3065
|
+
const store = createSnapshotStore(project());
|
|
3084
3066
|
this.listeners.add(() => {
|
|
3085
3067
|
store.set(project());
|
|
3086
3068
|
});
|
|
@@ -3095,7 +3077,7 @@ window.__ModuleLoader__.load({
|
|
|
3095
3077
|
exposed: snapshot.status === "ready",
|
|
3096
3078
|
writable: snapshot.writable,
|
|
3097
3079
|
dirty: plan.length > 0,
|
|
3098
|
-
invalid: plan.some((item) => item.
|
|
3080
|
+
invalid: plan.some((item) => item.judge === void 0),
|
|
3099
3081
|
saving: this.saving,
|
|
3100
3082
|
failed: this.failed,
|
|
3101
3083
|
...this.failedReason === void 0 ? {} : { failedReason: this.failedReason }
|
|
@@ -3145,45 +3127,52 @@ window.__ModuleLoader__.load({
|
|
|
3145
3127
|
};
|
|
3146
3128
|
}
|
|
3147
3129
|
/**
|
|
3148
|
-
* Write every staged edit, then re-seed from
|
|
3130
|
+
* Write every staged edit in one atomic scope mutation, then re-seed from
|
|
3131
|
+
* what the Host accepted.
|
|
3149
3132
|
*
|
|
3150
|
-
*
|
|
3151
|
-
*
|
|
3152
|
-
*
|
|
3153
|
-
*
|
|
3154
|
-
*
|
|
3155
|
-
*
|
|
3156
|
-
*
|
|
3133
|
+
* The whole batch rides one mutate, so cross-field validate hooks
|
|
3134
|
+
* (baseURL+model) judge it as a unit: the Host either applies every write
|
|
3135
|
+
* or refuses the batch. The 0.1.2 scope contract never rejects a refused
|
|
3136
|
+
* mutation — the scope recovers with a fresh Host view and resolves — so
|
|
3137
|
+
* resolution alone proves nothing: the outcome is judged by reading the
|
|
3138
|
+
* settled snapshot back, one planned write at a time, and one missed write
|
|
3139
|
+
* fails the whole save. A scope that still rejects on refusal (the dsh-web
|
|
3140
|
+
* bridge scope) reports through the same failure path with its rejection
|
|
3141
|
+
* message. A save that did not land keeps its drafts, so the user can
|
|
3142
|
+
* correct them instead of retyping.
|
|
3143
|
+
* @returns settlement after the mutation and the read-back.
|
|
3157
3144
|
*/
|
|
3158
3145
|
async save() {
|
|
3159
3146
|
const plan = this.plan();
|
|
3160
|
-
const valid = plan.filter((item) => item.
|
|
3147
|
+
const valid = plan.filter((item) => item.judge !== void 0);
|
|
3161
3148
|
if (plan.length === 0 || this.saving || valid.length !== plan.length) return;
|
|
3162
|
-
const plannedWrites = valid.map((item) => item.op);
|
|
3163
3149
|
const pending = /* @__PURE__ */ new Map();
|
|
3164
3150
|
for (const item of plan) pending.set(item.field, this.staged.get(item.field));
|
|
3165
3151
|
this.saving = true;
|
|
3166
3152
|
this.failed = false;
|
|
3167
3153
|
this.failedReason = void 0;
|
|
3168
3154
|
this.publish();
|
|
3169
|
-
const
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
}
|
|
3177
|
-
|
|
3155
|
+
const ops = valid.map((item) => item.op.op === "set" ? {
|
|
3156
|
+
op: "set",
|
|
3157
|
+
path: [item.field],
|
|
3158
|
+
value: item.op.value
|
|
3159
|
+
} : {
|
|
3160
|
+
op: "unset",
|
|
3161
|
+
path: [item.field]
|
|
3162
|
+
});
|
|
3163
|
+
let failedReason;
|
|
3164
|
+
try {
|
|
3165
|
+
await this.scope.mutate(ops);
|
|
3166
|
+
} catch (error) {
|
|
3167
|
+
failedReason = error instanceof Error ? error.message : String(error);
|
|
3168
|
+
}
|
|
3169
|
+
const landed = failedReason === void 0 && valid.every((item) => item.judge());
|
|
3170
|
+
for (const [field, before] of pending) if (landed && this.staged.get(field) === before) this.staged.delete(field);
|
|
3178
3171
|
this.saving = false;
|
|
3179
|
-
this.failed = landed
|
|
3172
|
+
this.failed = !landed;
|
|
3173
|
+
this.failedReason = failedReason;
|
|
3180
3174
|
this.publish();
|
|
3181
3175
|
}
|
|
3182
|
-
/** The scope's batch surface when it supports one; undefined conservatively otherwise. */
|
|
3183
|
-
batchedScope() {
|
|
3184
|
-
const candidate = this.scope;
|
|
3185
|
-
return typeof candidate?.mutate === "function" ? candidate : void 0;
|
|
3186
|
-
}
|
|
3187
3176
|
/**
|
|
3188
3177
|
* Every staged edit a save would write. An entry whose draft is not a value
|
|
3189
3178
|
* its field accepts carries no write: the form is still dirty, and the save
|
|
@@ -3202,7 +3191,7 @@ window.__ModuleLoader__.load({
|
|
|
3202
3191
|
field,
|
|
3203
3192
|
op: "unset"
|
|
3204
3193
|
},
|
|
3205
|
-
|
|
3194
|
+
judge: () => this.landedUnset(field)
|
|
3206
3195
|
});
|
|
3207
3196
|
continue;
|
|
3208
3197
|
}
|
|
@@ -3214,7 +3203,7 @@ window.__ModuleLoader__.load({
|
|
|
3214
3203
|
field,
|
|
3215
3204
|
op: "unset"
|
|
3216
3205
|
},
|
|
3217
|
-
|
|
3206
|
+
judge: void 0
|
|
3218
3207
|
});
|
|
3219
3208
|
else if (write.kind === "clear") plan.push({
|
|
3220
3209
|
field,
|
|
@@ -3222,7 +3211,7 @@ window.__ModuleLoader__.load({
|
|
|
3222
3211
|
field,
|
|
3223
3212
|
op: "unset"
|
|
3224
3213
|
},
|
|
3225
|
-
|
|
3214
|
+
judge: () => this.landedUnset(field)
|
|
3226
3215
|
});
|
|
3227
3216
|
else plan.push({
|
|
3228
3217
|
field,
|
|
@@ -3231,20 +3220,26 @@ window.__ModuleLoader__.load({
|
|
|
3231
3220
|
op: "set",
|
|
3232
3221
|
value: write.value
|
|
3233
3222
|
},
|
|
3234
|
-
|
|
3223
|
+
judge: () => this.landedSet(field, write.value)
|
|
3235
3224
|
});
|
|
3236
3225
|
}
|
|
3237
3226
|
return plan;
|
|
3238
3227
|
}
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
await this.scope.set(field, value);
|
|
3228
|
+
/**
|
|
3229
|
+
* Read-back judgment for a planned set: the user layer must hold the
|
|
3230
|
+
* intended value once the mutation has settled.
|
|
3231
|
+
*/
|
|
3232
|
+
landedSet(field, value) {
|
|
3245
3233
|
if (this.specOf(field).secret) return true;
|
|
3246
3234
|
return this.userLayer()?.[field] === value;
|
|
3247
3235
|
}
|
|
3236
|
+
/**
|
|
3237
|
+
* Read-back judgment for a planned unset: the field must be gone from the
|
|
3238
|
+
* user layer once the mutation has settled.
|
|
3239
|
+
*/
|
|
3240
|
+
landedUnset(field) {
|
|
3241
|
+
return !this.stored(field);
|
|
3242
|
+
}
|
|
3248
3243
|
stage(field, edit) {
|
|
3249
3244
|
this.staged.set(field, edit);
|
|
3250
3245
|
this.failed = false;
|
|
@@ -3278,6 +3273,11 @@ window.__ModuleLoader__.load({
|
|
|
3278
3273
|
};
|
|
3279
3274
|
//#endregion
|
|
3280
3275
|
//#region src/client/RemoteSettingsCard.tsx
|
|
3276
|
+
/**
|
|
3277
|
+
* The remote-control settings card: pairing security and device limits.
|
|
3278
|
+
* Registers into the `web-ui.plugin.item` child slot the Web UI plugin group
|
|
3279
|
+
* renders, bound to the `remote-web-ui` settings namespace.
|
|
3280
|
+
*/
|
|
3281
3281
|
/** Bridges the `remote-web-ui` scope onto the card's staged form. */
|
|
3282
3282
|
var RemoteSettingsCardController = class {
|
|
3283
3283
|
form;
|
|
@@ -3294,7 +3294,7 @@ window.__ModuleLoader__.load({
|
|
|
3294
3294
|
booleanField("requirePairingForLan"),
|
|
3295
3295
|
textField("publicBaseUrl"),
|
|
3296
3296
|
booleanField("autoTunnel"),
|
|
3297
|
-
booleanField("
|
|
3297
|
+
booleanField("lanBind")
|
|
3298
3298
|
]);
|
|
3299
3299
|
this.store = this.form.bind(() => this.projection());
|
|
3300
3300
|
}
|
|
@@ -3310,7 +3310,7 @@ window.__ModuleLoader__.load({
|
|
|
3310
3310
|
requirePairingForLan: this.form.field("requirePairingForLan"),
|
|
3311
3311
|
publicBaseUrl: this.form.field("publicBaseUrl"),
|
|
3312
3312
|
autoTunnel: this.form.field("autoTunnel"),
|
|
3313
|
-
|
|
3313
|
+
lanBind: this.form.field("lanBind")
|
|
3314
3314
|
};
|
|
3315
3315
|
}
|
|
3316
3316
|
/**
|
|
@@ -3486,25 +3486,74 @@ window.__ModuleLoader__.load({
|
|
|
3486
3486
|
props.resetField("autoTunnel");
|
|
3487
3487
|
}
|
|
3488
3488
|
}),
|
|
3489
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LanBindStatus, { t }),
|
|
3489
3490
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(BooleanField, {
|
|
3490
|
-
id: "settings-remote-
|
|
3491
|
-
label: t("settings.
|
|
3492
|
-
hint: t("settings.
|
|
3491
|
+
id: "settings-remote-lan-bind",
|
|
3492
|
+
label: t("settings.lanBind"),
|
|
3493
|
+
hint: t("settings.lanBindHint"),
|
|
3493
3494
|
inheritLabel: t("settings.inherit"),
|
|
3494
3495
|
onLabel: t("settings.on"),
|
|
3495
3496
|
offLabel: t("settings.off"),
|
|
3496
3497
|
...fieldProps,
|
|
3497
|
-
...state.
|
|
3498
|
+
...state.lanBind,
|
|
3498
3499
|
onEdit: (text) => {
|
|
3499
|
-
props.edit("
|
|
3500
|
+
props.edit("lanBind", text);
|
|
3500
3501
|
},
|
|
3501
3502
|
onReset: () => {
|
|
3502
|
-
props.resetField("
|
|
3503
|
+
props.resetField("lanBind");
|
|
3503
3504
|
}
|
|
3504
3505
|
})
|
|
3505
3506
|
]
|
|
3506
3507
|
});
|
|
3507
3508
|
}
|
|
3509
|
+
/**
|
|
3510
|
+
* Live LAN-bind facts above the toggle: the managed block's host, the live
|
|
3511
|
+
* bind, the firewall summary, and the reachable LAN URLs. Polls the
|
|
3512
|
+
* loopback-only status endpoint; a failure (non-loopback origin) renders
|
|
3513
|
+
* nothing — the pairing panel carries the loopback banner instead.
|
|
3514
|
+
*/
|
|
3515
|
+
function LanBindStatus({ t }) {
|
|
3516
|
+
const [frame, setFrame] = (0, react.useState)(void 0);
|
|
3517
|
+
(0, react.useEffect)(() => {
|
|
3518
|
+
let alive = true;
|
|
3519
|
+
const read = () => {
|
|
3520
|
+
readLanBindStatus().then((value) => {
|
|
3521
|
+
if (alive) setFrame(value);
|
|
3522
|
+
}).catch(() => {});
|
|
3523
|
+
};
|
|
3524
|
+
read();
|
|
3525
|
+
const timer = window.setInterval(read, 1e4);
|
|
3526
|
+
return () => {
|
|
3527
|
+
alive = false;
|
|
3528
|
+
window.clearInterval(timer);
|
|
3529
|
+
};
|
|
3530
|
+
}, []);
|
|
3531
|
+
if (frame === void 0) return null;
|
|
3532
|
+
const lanOn = frame.blockHost === "0.0.0.0";
|
|
3533
|
+
const firewallText = frame.firewall.managed ? t(frame.firewall.ok ? "lan.firewall.ok" : "lan.firewall.bad") : t("lan.firewall.unmanaged");
|
|
3534
|
+
const lines = [t("lan.bind", {
|
|
3535
|
+
host: frame.bindHost,
|
|
3536
|
+
port: String(frame.port)
|
|
3537
|
+
}) + " · " + firewallText];
|
|
3538
|
+
if (lanOn && frame.lanUrls.length > 0) lines.push(t("lan.urls", { urls: frame.lanUrls.join(" ") }));
|
|
3539
|
+
if (!lanOn) lines.push(t("lan.off"));
|
|
3540
|
+
if (frame.setting === null) lines.push(t("lan.untouched"));
|
|
3541
|
+
if (frame.pendingRestart === true) lines.push(t("lan.pendingRestart"));
|
|
3542
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3543
|
+
style: {
|
|
3544
|
+
display: "flex",
|
|
3545
|
+
flexDirection: "column",
|
|
3546
|
+
gap: 2,
|
|
3547
|
+
fontSize: 12,
|
|
3548
|
+
lineHeight: "18px",
|
|
3549
|
+
opacity: .85
|
|
3550
|
+
},
|
|
3551
|
+
children: lines.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3552
|
+
style: { wordBreak: "break-all" },
|
|
3553
|
+
children: line
|
|
3554
|
+
}, index))
|
|
3555
|
+
});
|
|
3556
|
+
}
|
|
3508
3557
|
//#endregion
|
|
3509
3558
|
//#region src/client/locales.ts
|
|
3510
3559
|
/** `remote` namespace dictionaries: the remote-access surface copy. */
|
|
@@ -3512,15 +3561,15 @@ window.__ModuleLoader__.load({
|
|
|
3512
3561
|
const zh = {
|
|
3513
3562
|
"entry.label": "远程访问",
|
|
3514
3563
|
"title": "远程访问",
|
|
3515
|
-
"subtitle": "
|
|
3564
|
+
"subtitle": "通过手机或另一台电脑配对,远程使用同一份 Web 界面(官方界面 + 移动端适配)",
|
|
3516
3565
|
"card.title": "设备配对",
|
|
3517
3566
|
"public.badge": "公网",
|
|
3518
3567
|
"status.waiting": "等待设备连接",
|
|
3519
3568
|
"status.connected": "已连接 {n} 台设备",
|
|
3520
3569
|
"status.disconnected": "已配对设备离线",
|
|
3521
3570
|
"status.stopped": "已停止远程访问",
|
|
3522
|
-
"status.lanRequired": "
|
|
3523
|
-
"status.lanRequiredHint": "当前服务仅绑定在 127.0.0.1
|
|
3571
|
+
"status.lanRequired": "此功能需要局域网绑定或公网地址才能使用",
|
|
3572
|
+
"status.lanRequiredHint": "当前服务仅绑定在 127.0.0.1 且未配置公网地址,手机无法访问。可在下方设置卡片打开“局域网访问”,或用 dsh web --host 0.0.0.0 重新启动,或填写内网穿透的公网地址。",
|
|
3524
3573
|
"status.loopbackRequired": "配对面板仅限本机使用",
|
|
3525
3574
|
"status.loopbackRequiredHint": "请通过 http://127.0.0.1 打开此页面后重试;手机请使用配对链接访问。",
|
|
3526
3575
|
"status.unreachable": "无法连接配对服务",
|
|
@@ -3533,9 +3582,8 @@ window.__ModuleLoader__.load({
|
|
|
3533
3582
|
"address.hint": "远程设备不在同一网络时请使用公网地址;局域网地址仅限同一网络内使用。",
|
|
3534
3583
|
"pair.expires": "二维码有效至 {time}",
|
|
3535
3584
|
"pair.expired": "二维码已过期,请刷新",
|
|
3536
|
-
"pair.
|
|
3537
|
-
"pair.
|
|
3538
|
-
"pair.oneTimeHint": "两条链接共用同一枚一次性令牌;任意设备配对成功后,另一条链接立即失效。",
|
|
3585
|
+
"pair.linkLabel": "配对链接",
|
|
3586
|
+
"pair.oneTimeHint": "链接含一次性令牌;任一设备配对成功后立即失效,配对下一台设备请刷新二维码。",
|
|
3539
3587
|
"pair.failed.title": "配对失败",
|
|
3540
3588
|
"pair.failed.detail": "链接无效或已使用,请回到电脑端刷新二维码后重新扫码。",
|
|
3541
3589
|
"fence.unpaired.title": "此设备未配对,无法访问工作区数据",
|
|
@@ -3557,8 +3605,7 @@ window.__ModuleLoader__.load({
|
|
|
3557
3605
|
"action.stop": "停止",
|
|
3558
3606
|
"action.refresh": "刷新二维码",
|
|
3559
3607
|
"action.copy": "复制链接",
|
|
3560
|
-
"action.
|
|
3561
|
-
"action.copyDesktop": "复制电脑链接",
|
|
3608
|
+
"action.copyLink": "复制链接",
|
|
3562
3609
|
"action.copied": "已复制",
|
|
3563
3610
|
"devices.title": "已授权设备",
|
|
3564
3611
|
"devices.empty": "还没有已配对的设备。扫码或打开链接后会出现在这里。",
|
|
@@ -3593,14 +3640,23 @@ window.__ModuleLoader__.load({
|
|
|
3593
3640
|
"settings.publicBaseUrlHint": "内网穿透的公网地址,如 Cloudflare Tunnel:https://xxx.trycloudflare.com。填写后二维码将生成公网链接,手机不在同一网络也能配对;留空则仅局域网可用。",
|
|
3594
3641
|
"settings.autoTunnel": "自动公网隧道",
|
|
3595
3642
|
"settings.autoTunnelHint": "开启后插件自动启动 Cloudflare quick tunnel(无需安装任何工具),并自动更新公网地址与信任配置,手机随时可用公网配对;关闭时使用上方手动填写的公网地址。",
|
|
3596
|
-
"settings.
|
|
3597
|
-
"settings.
|
|
3643
|
+
"settings.lanBind": "局域网访问(绑定 0.0.0.0)",
|
|
3644
|
+
"settings.lanBindHint": "开启后插件把绑定默认改写为 0.0.0.0 并写入 profile 补丁(显式 --host 仍优先),同时维护主机防火墙放行(Windows/Linux;macOS 无需管理);关闭回退 127.0.0.1。绑定变化通常在重启 dsh web 后生效。",
|
|
3645
|
+
"lan.cardTitle": "局域网访问",
|
|
3646
|
+
"lan.bind": "绑定:{host}:{port}",
|
|
3647
|
+
"lan.off": "当前仅本机可访问。",
|
|
3648
|
+
"lan.urls": "局域网地址:{urls}",
|
|
3649
|
+
"lan.firewall.ok": "防火墙已放行",
|
|
3650
|
+
"lan.firewall.bad": "防火墙未放行(需管理员权限)",
|
|
3651
|
+
"lan.firewall.unmanaged": "防火墙不由插件管理(未检测到受支持的防火墙)",
|
|
3652
|
+
"lan.untouched": "开关尚未使用:打开后插件才会改写 profile 补丁。",
|
|
3653
|
+
"lan.pendingRestart": "绑定将在重启 dsh web 后生效。",
|
|
3598
3654
|
"settings.inherit": "继承",
|
|
3599
3655
|
"settings.on": "开",
|
|
3600
3656
|
"settings.off": "关",
|
|
3601
3657
|
"settings.overridden": "已覆盖",
|
|
3602
3658
|
"settings.reset": "恢复默认",
|
|
3603
|
-
"settings.notExposed": "当前 DSH 版本未向设置页暴露本插件的配置命名空间,表单不可用。可编辑
|
|
3659
|
+
"settings.notExposed": "当前 DSH 版本未向设置页暴露本插件的配置命名空间,表单不可用。可编辑 $DSH_HOME/settings.yaml 直接配置,或确认提供该命名空间的插件已挂载其设置域并重启。",
|
|
3604
3660
|
"settings.readOnly": "当前部署的设置只读。",
|
|
3605
3661
|
"settings.expand": "展开设置",
|
|
3606
3662
|
"settings.collapse": "收起设置",
|
|
@@ -3656,15 +3712,15 @@ window.__ModuleLoader__.load({
|
|
|
3656
3712
|
const en = {
|
|
3657
3713
|
"entry.label": "Remote access",
|
|
3658
3714
|
"title": "Remote access",
|
|
3659
|
-
"subtitle": "Pair a phone or another computer to
|
|
3715
|
+
"subtitle": "Pair a phone or another computer to share the same Web GUI (official UI + mobile adaptation)",
|
|
3660
3716
|
"card.title": "Pair a device",
|
|
3661
3717
|
"public.badge": "Public",
|
|
3662
3718
|
"status.waiting": "Waiting for a device",
|
|
3663
3719
|
"status.connected": "{n} device(s) connected",
|
|
3664
3720
|
"status.disconnected": "Paired devices offline",
|
|
3665
3721
|
"status.stopped": "Remote access stopped",
|
|
3666
|
-
"status.lanRequired": "This feature needs
|
|
3667
|
-
"status.lanRequiredHint": "The server is bound to 127.0.0.1 and no public address is configured, so a phone cannot reach it.
|
|
3722
|
+
"status.lanRequired": "This feature needs a LAN bind or a public address",
|
|
3723
|
+
"status.lanRequiredHint": "The server is bound to 127.0.0.1 and no public address is configured, so a phone cannot reach it. Turn on LAN access in the settings card below, restart with dsh web --host 0.0.0.0, or set the tunneled public address in settings.",
|
|
3668
3724
|
"status.loopbackRequired": "The pairing panel works on this machine only",
|
|
3669
3725
|
"status.loopbackRequiredHint": "Open this page at http://127.0.0.1 to mint a QR code; phones use the paired link.",
|
|
3670
3726
|
"status.unreachable": "Cannot reach the pairing service",
|
|
@@ -3677,9 +3733,8 @@ window.__ModuleLoader__.load({
|
|
|
3677
3733
|
"address.hint": "Use the public address when the remote device is on another network; LAN addresses only work on the same network.",
|
|
3678
3734
|
"pair.expires": "QR code valid until {time}",
|
|
3679
3735
|
"pair.expired": "QR code expired — refresh it",
|
|
3680
|
-
"pair.
|
|
3681
|
-
"pair.
|
|
3682
|
-
"pair.oneTimeHint": "Both links share one single-use token. Pairing either device invalidates the other link immediately.",
|
|
3736
|
+
"pair.linkLabel": "Pairing link",
|
|
3737
|
+
"pair.oneTimeHint": "The link carries one single-use token; it dies as soon as any device pairs. Refresh the QR to pair the next device.",
|
|
3683
3738
|
"pair.failed.title": "Pairing failed",
|
|
3684
3739
|
"pair.failed.detail": "The link is invalid or was already used. Refresh the QR code on your computer and scan again.",
|
|
3685
3740
|
"fence.unpaired.title": "This device is not paired and cannot reach workspace data",
|
|
@@ -3701,8 +3756,7 @@ window.__ModuleLoader__.load({
|
|
|
3701
3756
|
"action.stop": "Stop",
|
|
3702
3757
|
"action.refresh": "Refresh QR",
|
|
3703
3758
|
"action.copy": "Copy link",
|
|
3704
|
-
"action.
|
|
3705
|
-
"action.copyDesktop": "Copy computer link",
|
|
3759
|
+
"action.copyLink": "Copy link",
|
|
3706
3760
|
"action.copied": "Copied",
|
|
3707
3761
|
"devices.title": "Authorized devices",
|
|
3708
3762
|
"devices.empty": "No paired devices yet. Scan the QR or open the link to add one.",
|
|
@@ -3737,14 +3791,23 @@ window.__ModuleLoader__.load({
|
|
|
3737
3791
|
"settings.publicBaseUrlHint": "The public URL of a tunnel in front of this server, e.g. a Cloudflare Tunnel: https://xxx.trycloudflare.com. When set, the QR link is built from it so a phone anywhere can pair; leave blank for LAN-only usage.",
|
|
3738
3792
|
"settings.autoTunnel": "Auto public tunnel",
|
|
3739
3793
|
"settings.autoTunnelHint": "When on, the plugin runs its own Cloudflare quick tunnel (no tool installation needed) and keeps the public address and trust config in sync automatically, so a phone anywhere can pair at any time; when off, the manually entered public address above applies.",
|
|
3740
|
-
"settings.
|
|
3741
|
-
"settings.
|
|
3794
|
+
"settings.lanBind": "LAN access (bind 0.0.0.0)",
|
|
3795
|
+
"settings.lanBindHint": "When on, the plugin writes a managed block into the profile patch defaulting the bind to 0.0.0.0 (an explicit --host flag still wins), and maintains the matching host firewall rule (Windows/Linux; other platforms need none). When off, the block pins 127.0.0.1. The bind change usually takes effect after dsh web restarts.",
|
|
3796
|
+
"lan.cardTitle": "LAN access",
|
|
3797
|
+
"lan.bind": "Bind: {host}:{port}",
|
|
3798
|
+
"lan.off": "Currently localhost-only.",
|
|
3799
|
+
"lan.urls": "LAN URLs: {urls}",
|
|
3800
|
+
"lan.firewall.ok": "firewall open",
|
|
3801
|
+
"lan.firewall.bad": "firewall blocked (needs admin)",
|
|
3802
|
+
"lan.firewall.unmanaged": "firewall not managed (no supported firewall detected)",
|
|
3803
|
+
"lan.untouched": "The toggle has never been used; the profile patch is only written once you flip it.",
|
|
3804
|
+
"lan.pendingRestart": "The bind change takes effect after dsh web restarts.",
|
|
3742
3805
|
"settings.inherit": "Inherit",
|
|
3743
3806
|
"settings.on": "On",
|
|
3744
3807
|
"settings.off": "Off",
|
|
3745
3808
|
"settings.overridden": "Overridden",
|
|
3746
3809
|
"settings.reset": "Reset to default",
|
|
3747
|
-
"settings.notExposed": "This DSH version does not expose this plugin's settings namespace to the configuration page, so the form is unavailable. Edit
|
|
3810
|
+
"settings.notExposed": "This DSH version does not expose this plugin's settings namespace to the configuration page, so the form is unavailable. Edit $DSH_HOME/settings.yaml directly, or confirm that the plugin owning the namespace is mounted with its settings domain and restart.",
|
|
3748
3811
|
"settings.readOnly": "This deployment stores settings read-only.",
|
|
3749
3812
|
"settings.expand": "Show settings",
|
|
3750
3813
|
"settings.collapse": "Hide settings",
|
|
@@ -3800,8 +3863,6 @@ window.__ModuleLoader__.load({
|
|
|
3800
3863
|
//#region src/client/deep-link.ts
|
|
3801
3864
|
/** sessionStorage key for the failed-pair notice. */
|
|
3802
3865
|
const PAIR_FAILED_MARKER = "dsh-remote-pair-failed";
|
|
3803
|
-
/** Poll budget for the runtime services (activation order is unconstrained). */
|
|
3804
|
-
const SERVICE_WAIT_MS = 1e4;
|
|
3805
3866
|
/** The browser implementation of {@link PageSurface}. */
|
|
3806
3867
|
const browserPage = {
|
|
3807
3868
|
get href() {
|
|
@@ -3817,26 +3878,18 @@ window.__ModuleLoader__.load({
|
|
|
3817
3878
|
window.location.reload();
|
|
3818
3879
|
}
|
|
3819
3880
|
};
|
|
3820
|
-
/** Whether this browser looks like a phone/tablet (the simplified mobile surface). */
|
|
3821
|
-
function isMobileSurface() {
|
|
3822
|
-
if (typeof navigator === "undefined") return false;
|
|
3823
|
-
return /Android|iPhone|iPad|iPod|Mobile|mobile/i.test(navigator.userAgent);
|
|
3824
|
-
}
|
|
3825
3881
|
/**
|
|
3826
|
-
* Run the pair
|
|
3827
|
-
* @param ctx - client root context (
|
|
3882
|
+
* Run the pair boot flow for this page load.
|
|
3883
|
+
* @param ctx - client root context (unused since the deep-link retirement;
|
|
3884
|
+
* kept for call-site stability).
|
|
3828
3885
|
* @param search - the current location.search.
|
|
3829
3886
|
* @param page - the page surface (defaults to the browser).
|
|
3830
3887
|
*/
|
|
3831
3888
|
function runPairBootFlow(ctx, search, page = browserPage) {
|
|
3832
3889
|
const params = readPairParams(search);
|
|
3833
|
-
if (params.pair !== void 0)
|
|
3834
|
-
runAccept(params.pair, page);
|
|
3835
|
-
return;
|
|
3836
|
-
}
|
|
3837
|
-
if (params.workspace !== void 0) runDeepLink(ctx, params.workspace, page);
|
|
3890
|
+
if (params.pair !== void 0) runAccept(params.pair, page);
|
|
3838
3891
|
}
|
|
3839
|
-
/** Accept the token, then
|
|
3892
|
+
/** Accept the token, then reload into the paired official UI. */
|
|
3840
3893
|
async function runAccept(token, page) {
|
|
3841
3894
|
let ok = false;
|
|
3842
3895
|
try {
|
|
@@ -3848,42 +3901,22 @@ window.__ModuleLoader__.load({
|
|
|
3848
3901
|
const url = new URL(page.href);
|
|
3849
3902
|
url.searchParams.delete("pair");
|
|
3850
3903
|
page.replaceState(`${url.pathname}${url.search}${url.hash}`);
|
|
3851
|
-
if (ok)
|
|
3852
|
-
url.pathname = "/m/";
|
|
3853
|
-
page.navigate(`${url.pathname}${url.search}${url.hash}`);
|
|
3854
|
-
} else page.reload();
|
|
3904
|
+
if (ok) page.reload();
|
|
3855
3905
|
}
|
|
3906
|
+
/** Connection-plugin method prefix under the gated channel. */
|
|
3907
|
+
const REMOTE_API_PREFIX$1 = `/remote/api`;
|
|
3908
|
+
({ mux: `${REMOTE_API_PREFIX$1}/remote.mux` }).mux, `${REMOTE_API_PREFIX$1}`;
|
|
3856
3909
|
/**
|
|
3857
|
-
*
|
|
3858
|
-
*
|
|
3859
|
-
*
|
|
3860
|
-
*
|
|
3861
|
-
*
|
|
3862
|
-
*
|
|
3863
|
-
*
|
|
3864
|
-
* @param page - the page surface.
|
|
3910
|
+
* The cookieless device credential: the boot patch reads the device id from
|
|
3911
|
+
* the /pair-app URL, keeps it in sessionStorage, and attaches it to every
|
|
3912
|
+
* gated HTTP call as this header (and to WebSocket upgrades as the `device`
|
|
3913
|
+
* query parameter - WS handshakes cannot carry headers from the Web API).
|
|
3914
|
+
* The channel gate accepts it exactly like the device cookie, so the mobile
|
|
3915
|
+
* flow works even with browser cookies fully blocked; the cookie remains the
|
|
3916
|
+
* primary credential on normal browsers.
|
|
3865
3917
|
*/
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
const deadline = Date.now() + SERVICE_WAIT_MS;
|
|
3869
|
-
while (Date.now() < deadline) {
|
|
3870
|
-
const workspaces = ctx.get("workspaces");
|
|
3871
|
-
const sessions = ctx.get("sessions");
|
|
3872
|
-
if (workspaces !== void 0 && sessions !== void 0) {
|
|
3873
|
-
if (workspaces.list.getSnapshot().items.some((item) => item.workspaceId === target)) {
|
|
3874
|
-
try {
|
|
3875
|
-
const sessionId = await workspaces.connectWorkspace(target);
|
|
3876
|
-
sessions.open(sessionId);
|
|
3877
|
-
} catch {}
|
|
3878
|
-
break;
|
|
3879
|
-
}
|
|
3880
|
-
}
|
|
3881
|
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
3882
|
-
}
|
|
3883
|
-
const url = new URL(page.href);
|
|
3884
|
-
url.searchParams.delete("workspace");
|
|
3885
|
-
page.replaceState(`${url.pathname}${url.search}${url.hash}`);
|
|
3886
|
-
}
|
|
3918
|
+
const REMOTE_DEVICE_HEADER = "x-dsh-remote-device";
|
|
3919
|
+
const REMOTE_DEVICE_QUERY = "device";
|
|
3887
3920
|
//#endregion
|
|
3888
3921
|
//#region src/remote-channel-rules.ts
|
|
3889
3922
|
/**
|
|
@@ -3907,12 +3940,14 @@ window.__ModuleLoader__.load({
|
|
|
3907
3940
|
gitPrefix: "/git/",
|
|
3908
3941
|
petPrefix: "/pet/",
|
|
3909
3942
|
wsPaths: [
|
|
3910
|
-
"/api/
|
|
3911
|
-
"/api/events.host",
|
|
3943
|
+
"/api/remote.mux",
|
|
3912
3944
|
"/sidebar/ws/terminal",
|
|
3913
3945
|
"/sidebar/ws/agent-terminals",
|
|
3914
3946
|
"/api/dsh-ssh/terminal"
|
|
3915
|
-
]
|
|
3947
|
+
],
|
|
3948
|
+
deviceHeader: REMOTE_DEVICE_HEADER,
|
|
3949
|
+
deviceKey: "dsh-remote-device",
|
|
3950
|
+
deviceQuery: REMOTE_DEVICE_QUERY
|
|
3916
3951
|
};
|
|
3917
3952
|
/** The window global the boot patch publishes its seat under. */
|
|
3918
3953
|
const REMOTE_CHANNEL_BOOT_GLOBAL = "__DSH_REMOTE_CHANNEL_BOOT__";
|
|
@@ -4065,13 +4100,42 @@ window.__ModuleLoader__.load({
|
|
|
4065
4100
|
const OriginalEventSource = window.EventSource;
|
|
4066
4101
|
const sameOrigin = (url) => url.origin === window.location.origin;
|
|
4067
4102
|
const rewrite = (raw) => rewriteRawUrl(raw, window.location.href, window.location.origin);
|
|
4103
|
+
const device = (() => {
|
|
4104
|
+
try {
|
|
4105
|
+
return window.sessionStorage?.getItem(RULES.deviceKey) ?? null;
|
|
4106
|
+
} catch {
|
|
4107
|
+
return null;
|
|
4108
|
+
}
|
|
4109
|
+
})();
|
|
4110
|
+
const attach = (init) => {
|
|
4111
|
+
if (device === null) return init;
|
|
4112
|
+
const headers = init?.headers;
|
|
4113
|
+
if (typeof Headers !== "undefined" && headers instanceof Headers) {
|
|
4114
|
+
try {
|
|
4115
|
+
headers.set(RULES.deviceHeader, device);
|
|
4116
|
+
} catch {}
|
|
4117
|
+
return init;
|
|
4118
|
+
}
|
|
4119
|
+
if (typeof headers === "object" && headers !== null) return {
|
|
4120
|
+
...init,
|
|
4121
|
+
headers: {
|
|
4122
|
+
...headers,
|
|
4123
|
+
[RULES.deviceHeader]: device
|
|
4124
|
+
}
|
|
4125
|
+
};
|
|
4126
|
+
return init;
|
|
4127
|
+
};
|
|
4128
|
+
const withDeviceQuery = (url) => {
|
|
4129
|
+
if (device !== null) url.searchParams.set(RULES.deviceQuery, device);
|
|
4130
|
+
return url;
|
|
4131
|
+
};
|
|
4068
4132
|
const patchedFetch = (input, init) => {
|
|
4069
4133
|
const url = new URL(typeof input === "string" || input instanceof URL ? input.toString() : input.url, window.location.href);
|
|
4070
4134
|
if (sameOrigin(url) && shouldRewriteFetchPath(url.pathname)) {
|
|
4071
4135
|
const rewritten = new URL(url);
|
|
4072
4136
|
rewritten.pathname = rewritePath(url.pathname);
|
|
4073
4137
|
const next = typeof input === "string" || input instanceof URL ? rewritten.toString() : new Request(rewritten, input);
|
|
4074
|
-
return Promise.resolve(originalFetch.call(window, next, init)).then(async (response) => {
|
|
4138
|
+
return Promise.resolve(originalFetch.call(window, next, attach(init))).then(async (response) => {
|
|
4075
4139
|
if (await isUnpairedDenied(response.clone())) options.onUnpaired?.();
|
|
4076
4140
|
else options.onPaired?.();
|
|
4077
4141
|
return response;
|
|
@@ -4086,7 +4150,7 @@ window.__ModuleLoader__.load({
|
|
|
4086
4150
|
if (wsOrigin !== "" && wsOrigin === window.location.origin && shouldRewriteWsPath(parsed.pathname)) {
|
|
4087
4151
|
const rewritten = new URL(parsed);
|
|
4088
4152
|
rewritten.pathname = rewritePath(parsed.pathname);
|
|
4089
|
-
super(rewritten, protocols);
|
|
4153
|
+
super(withDeviceQuery(rewritten), protocols);
|
|
4090
4154
|
return;
|
|
4091
4155
|
}
|
|
4092
4156
|
super(url, protocols);
|
|
@@ -4106,7 +4170,7 @@ window.__ModuleLoader__.load({
|
|
|
4106
4170
|
if (sameOrigin(parsed) && shouldRewriteFetchPath(parsed.pathname)) {
|
|
4107
4171
|
const rewritten = new URL(parsed);
|
|
4108
4172
|
rewritten.pathname = rewritePath(parsed.pathname);
|
|
4109
|
-
super(rewritten, eventSourceInitDict);
|
|
4173
|
+
super(withDeviceQuery(rewritten), eventSourceInitDict);
|
|
4110
4174
|
return;
|
|
4111
4175
|
}
|
|
4112
4176
|
super(url, eventSourceInitDict);
|
|
@@ -4269,7 +4333,7 @@ window.__ModuleLoader__.load({
|
|
|
4269
4333
|
/** The building package's version, when the bundle carries it. */
|
|
4270
4334
|
function bakedVersion() {
|
|
4271
4335
|
try {
|
|
4272
|
-
return "0.3.
|
|
4336
|
+
return "0.3.9";
|
|
4273
4337
|
} catch {
|
|
4274
4338
|
return;
|
|
4275
4339
|
}
|
|
@@ -4332,15 +4396,698 @@ window.__ModuleLoader__.load({
|
|
|
4332
4396
|
} catch {}
|
|
4333
4397
|
}
|
|
4334
4398
|
//#endregion
|
|
4399
|
+
//#region src/client/mobile-adapt.ts
|
|
4400
|
+
/** Storage key for the manual desktop opt-out. */
|
|
4401
|
+
const FORCE_DESKTOP_KEY = "dsh-remote-force-desktop";
|
|
4402
|
+
/** Storage key for the dragged whale position. */
|
|
4403
|
+
const WHALE_POS_KEY = "dsh-remote-whale-pos";
|
|
4404
|
+
/** Injected stylesheet identity. */
|
|
4405
|
+
const ADAPT_CSS_ID = "dsh-remote-web-ui/mobile-adapt.css";
|
|
4406
|
+
/** Body class while the adaptation is active. */
|
|
4407
|
+
const ACTIVE_CLASS = "dsh-remote-portrait";
|
|
4408
|
+
/** Body class while the collapsed rail is hidden behind the whale. */
|
|
4409
|
+
const RAIL_HIDDEN_CLASS = "dsh-remote-rail-hidden";
|
|
4410
|
+
/** Whale button id. */
|
|
4411
|
+
const WHALE_ID = "dshRemoteWhale";
|
|
4412
|
+
/** Compact picker: synthesized model button id. */
|
|
4413
|
+
const MODEL_BTN_ID = "dshRemoteModelPick";
|
|
4414
|
+
/** Compact picker: synthesized effort button id. */
|
|
4415
|
+
const EFFORT_BTN_ID = "dshRemoteEffortPick";
|
|
4416
|
+
/** Body class while the compact picker buttons are wired. */
|
|
4417
|
+
const COMPACT_CLASS = "dsh-remote-compact-picker";
|
|
4418
|
+
/**
|
|
4419
|
+
* Whether the current viewport is a portrait touch device small enough to
|
|
4420
|
+
* need the adaptation.
|
|
4421
|
+
*/
|
|
4422
|
+
function isMobilePortrait() {
|
|
4423
|
+
if (typeof window === "undefined") return false;
|
|
4424
|
+
if (typeof window.matchMedia !== "function") return false;
|
|
4425
|
+
if (!window.matchMedia("(orientation: portrait)").matches) return false;
|
|
4426
|
+
if (!window.matchMedia("(pointer: coarse)").matches) return false;
|
|
4427
|
+
if (window.innerWidth >= 1100) return false;
|
|
4428
|
+
if (window.sessionStorage.getItem(FORCE_DESKTOP_KEY) === "1") return false;
|
|
4429
|
+
return true;
|
|
4430
|
+
}
|
|
4431
|
+
/**
|
|
4432
|
+
* The injected rules. Grouped by surface; version comments trace rules back
|
|
4433
|
+
* to the dsh-LAN reference they were ported from.
|
|
4434
|
+
*/
|
|
4435
|
+
const ADAPT_CSS = [
|
|
4436
|
+
"html,body{height:100%}",
|
|
4437
|
+
"[class$=\"_frame\"]{width:100%;height:100dvh}",
|
|
4438
|
+
"[class$=\"_railFish\"] button,[class$=\"_panelIcon\"],[class$=\"_newSession\"]{min-width:44px;min-height:44px}",
|
|
4439
|
+
"[class$=\"_scroll\"]{padding:8px 10px}",
|
|
4440
|
+
"[class$=\"_input\"],textarea,input{font-size:16px}",
|
|
4441
|
+
"[class$=\"_composer\"]{padding-bottom:calc(4px + env(safe-area-inset-bottom))}",
|
|
4442
|
+
"[class$=\"_scrollBody\"] [class$=\"_root\"]{font-size:14.5px}",
|
|
4443
|
+
"[class$=\"_scrollBody\"] [class$=\"_bubble\"]{font-size:14.5px}",
|
|
4444
|
+
"[class$=\"_titleRow\"] *{font-size:13px}",
|
|
4445
|
+
"[class$=\"_sidebarCol\"] [class$=\"_root\"],[class$=\"_sidebarCol\"] [class$=\"_newSession\"],[class$=\"_sidebarCol\"] [class$=\"_trigger\"],[class$=\"_sidebarCol\"] [class$=\"_title\"]{font-size:13px}",
|
|
4446
|
+
"[class$=\"_sidebarCol\"] [class$=\"_meta\"],[class$=\"_sidebarCol\"] [class$=\"_time\"]{font-size:11.5px}",
|
|
4447
|
+
"[class$=\"_frame\"][data-sidebar-collapsed]{grid-template-columns:0 minmax(0,1fr) 0 !important}",
|
|
4448
|
+
"[class$=\"_frame\"][data-sidebar-collapsed] [class$=\"_sidebarCol\"]{grid-column:1/2}",
|
|
4449
|
+
"[class$=\"_frame\"][data-sidebar-collapsed] [class$=\"_centerCol\"]{grid-column:2/3}",
|
|
4450
|
+
"[class$=\"_frame\"][data-sidebar-collapsed] [class$=\"_detailsCol\"]{grid-column:3/4}",
|
|
4451
|
+
`body.${RAIL_HIDDEN_CLASS} [class$="_titleRow"]{padding-left:52px}`,
|
|
4452
|
+
`#${WHALE_ID}{position:fixed;top:calc(4px + env(safe-area-inset-top));left:calc(8px + env(safe-area-inset-left));z-index:2147482999;width:34px;height:34px;min-width:34px;padding:0;border-radius:10px;background:var(--dsw-alias-bg-module-platform);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 1px 6px rgba(0,0,0,.25)}`,
|
|
4453
|
+
`#${WHALE_ID} svg{width:20px;height:15px;display:block}`,
|
|
4454
|
+
`#${WHALE_ID}:active{opacity:.72}`,
|
|
4455
|
+
`#${WHALE_ID}{touch-action:none}`,
|
|
4456
|
+
"[class$=\"_sidebarCol\"] [class$=\"_logoRow\"] [class*=\"_iconButton\"]{width:18px;height:18px}",
|
|
4457
|
+
"[class$=\"_sidebarCol\"] [class$=\"_logoRow\"] [class*=\"_iconButton\"] svg{width:13px;height:13px;min-width:0;min-height:0}",
|
|
4458
|
+
"[class$=\"_sidebarCol\"] [class$=\"_projectRow\"] [class$=\"_rowActions\"]{display:inline-flex}",
|
|
4459
|
+
"[class$=\"_sidebarCol\"] [class$=\"_sessionRow\"],[class$=\"_sidebarCol\"] [class$=\"_projectRow\"]{-webkit-user-drag:none;user-select:none}",
|
|
4460
|
+
"[class$=\"_headerUtilities\"]{display:none}",
|
|
4461
|
+
"[class$=\"_composerSeat\"] [class$=\"_card\"]{margin-bottom:1px}",
|
|
4462
|
+
"[class$=\"_composerSeat\"] [class$=\"_row\"]{flex-wrap:wrap;row-gap:0;padding:2px 8px 1px;position:relative}",
|
|
4463
|
+
"[class$=\"_composerSeat\"] [class$=\"_add\"]{position:absolute;left:8px;top:50%;transform:translateY(-50%)}",
|
|
4464
|
+
"[class$=\"_composerSeat\"] [class$=\"_modes\"]{min-width:0;padding-left:38px}",
|
|
4465
|
+
"[class$=\"_composerSeat\"] [class$=\"_trailing\"]{flex-basis:100%;position:relative;min-height:32px;justify-content:flex-start;padding-left:38px;padding-right:78px}",
|
|
4466
|
+
"[class$=\"_composerSeat\"] [class$=\"_trailing\"] *{font-size:12px}",
|
|
4467
|
+
"[class$=\"_composerSeat\"] [class$=\"_modes\"] [class$=\"_trigger\"]{height:24px;min-height:24px;font-size:12px}",
|
|
4468
|
+
"[class$=\"_composerSeat\"] [class$=\"_trailing\"] [class$=\"_trigger\"]{height:24px;min-height:24px;font-size:11px}",
|
|
4469
|
+
"[class$=\"_composerSeat\"] [class$=\"_trailing\"] > [class$=\"_root\"]:has([class$=\"_track\"]){position:absolute;right:52px;top:50%;transform:translateY(-50%)}",
|
|
4470
|
+
"[class$=\"_composerSeat\"] [class$=\"_primary\"]{position:absolute;right:8px;top:50%;transform:translateY(-50%)}",
|
|
4471
|
+
"[class$=\"_composerSeat\"]{transform:none !important}",
|
|
4472
|
+
"[class$=\"_composerSeat\"] [class$=\"_menu\"]{position:fixed !important;left:8px !important;right:8px !important;top:auto !important;bottom:calc(8px + env(safe-area-inset-bottom)) !important;width:auto !important;max-width:none !important;max-height:70dvh !important;overflow-y:auto !important;z-index:2147482000}",
|
|
4473
|
+
"[class$=\"_composerSeat\"] [class$=\"_menu\"] [class$=\"_cell\"]{height:44px;min-height:44px;font-size:13px}",
|
|
4474
|
+
`body.${COMPACT_CLASS} [class$="_composerSeat"] [class$="_trailing"] [class$="_trigger"]:has([class$="_triggerEffort"]){display:none}`,
|
|
4475
|
+
`body.${COMPACT_CLASS} [class$="_composerSeat"] [class$="_trailing"]{flex-basis:auto;position:static;min-height:0;padding:0;width:0}`,
|
|
4476
|
+
`body.${COMPACT_CLASS} [class$="_composerSeat"] [class$="_trailing"] > [class$="_root"]:has([class$="_track"]){right:44px}`,
|
|
4477
|
+
`#${MODEL_BTN_ID},#${EFFORT_BTN_ID}{width:26px;height:32px;min-width:26px;padding:0;border-radius:9px;background:var(--dsw-alias-bg-module-platform);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);display:flex;align-items:center;justify-content:center;cursor:pointer;flex:none;margin-left:4px}`,
|
|
4478
|
+
`#${MODEL_BTN_ID} svg,#${EFFORT_BTN_ID} svg{width:16px;height:16px;display:block}`,
|
|
4479
|
+
`#${MODEL_BTN_ID}:active,#${EFFORT_BTN_ID}:active{opacity:.7}`,
|
|
4480
|
+
"[class$=\"_composerSeat\"] [data-slot=\"conversation.composer.dock\"] [class$=\"_root\"]{font-size:10px;white-space:normal;word-break:break-word;overflow-wrap:anywhere;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-align:left;line-height:13px;letter-spacing:-0.2px;padding-left:0;padding-right:0}",
|
|
4481
|
+
"[class$=\"_scrollBody\"] [class$=\"_root\"]{font-size:13px}",
|
|
4482
|
+
"[class*=\"_bubble\"][role=\"tooltip\"]{display:none}",
|
|
4483
|
+
"[class$=\"_composerSeat\"] [class$=\"_frame\"]{box-sizing:border-box;width:100%;max-width:100%;padding-left:12px;padding-right:12px;height:auto;max-height:calc(100dvh - 96px);align-items:flex-start;overflow-y:auto}",
|
|
4484
|
+
"[class$=\"_composerSeat\"] [class$=\"_frame\"] [class$=\"_card\"]{max-width:none;width:100%}",
|
|
4485
|
+
"[class$=\"_header\"] [class$=\"_titleCluster\"] [class$=\"_headerActions\"]{display:none}",
|
|
4486
|
+
"[class$=\"_header\"] [class$=\"_tabs\"]{margin-right:-58px}",
|
|
4487
|
+
"[class$=\"_header\"] [class$=\"_tabs\"] [class*=\"_tab\"]{font-size:12px;white-space:nowrap}",
|
|
4488
|
+
"[class$=\"_header\"] [class$=\"_tabs\"] [class$=\"_headerActions\"]{margin-left:auto;display:flex;align-items:center;gap:6px;flex:none}",
|
|
4489
|
+
`body.${ACTIVE_CLASS} [class$=\"_detailsCol\"]{display:none !important}`,
|
|
4490
|
+
`body.${ACTIVE_CLASS} [data-dsh-plugin=\"ssh\"],`,
|
|
4491
|
+
`body.${ACTIVE_CLASS} [data-dsh-plugin=\"skill-explorer\"],`,
|
|
4492
|
+
`body.${ACTIVE_CLASS} [data-dsh-plugin=\"task-board\"],`,
|
|
4493
|
+
`body.${ACTIVE_CLASS} [data-dsh-plugin=\"git-graph\"],`,
|
|
4494
|
+
`body.${ACTIVE_CLASS} [data-dsh-plugin=\"pet\"],`,
|
|
4495
|
+
`body.${ACTIVE_CLASS} [data-dsh-plugin=\"perf\"],`,
|
|
4496
|
+
`body.${ACTIVE_CLASS} [data-dsh-plugin=\"usage\"]{display:none !important}`,
|
|
4497
|
+
`body.${ACTIVE_CLASS} [class$=\"_overlayLayer\"] [class$=\"_workbench\"]{display:none !important}`,
|
|
4498
|
+
"[class$=\"_overlay\"] [class$=\"_panel\"]{flex-direction:column;max-height:calc(100dvh - 32px)}",
|
|
4499
|
+
"[class$=\"_overlay\"] [class$=\"_panel\"] [class$=\"_nav\"]{flex-direction:row;gap:4px;width:100%;padding:12px 12px 0;overflow-x:auto;overflow-y:hidden}",
|
|
4500
|
+
"[class$=\"_overlay\"] [class$=\"_panel\"] [class$=\"_navTitle\"]{display:none}",
|
|
4501
|
+
"[class$=\"_overlay\"] [class$=\"_panel\"] [class$=\"_navList\"]{flex-direction:row;gap:4px}",
|
|
4502
|
+
"[class$=\"_overlay\"] [class$=\"_panel\"] [class$=\"_navCell\"]{height:34px;padding:0 12px;gap:6px;flex:none;border-radius:10px}",
|
|
4503
|
+
"[class$=\"_overlay\"] [class$=\"_panel\"] [class$=\"_navLabel\"]{font-size:13px}",
|
|
4504
|
+
"[class$=\"_overlay\"] [class$=\"_panel\"] [class$=\"_content\"]{flex:1;min-height:0}"
|
|
4505
|
+
];
|
|
4506
|
+
/** Cube glyph for the compact model button (a plain box outline). */
|
|
4507
|
+
const CUBE_ICON = "<path d=\"M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z\"/><path d=\"m3.3 7 8.7 5 8.7-5\"/><path d=\"M12 22V12\"/>";
|
|
4508
|
+
/** Level glyph for the compact effort button (three rising bars). */
|
|
4509
|
+
const LEVELS_ICON = "<path d=\"M4 6h16\"/><path d=\"M7 12h10\"/><path d=\"M10 18h4\"/>";
|
|
4510
|
+
/** The DeepSeek fish glyph (the official brand mark path). */
|
|
4511
|
+
const FISH_PATH = "M22.9168 1.43018C22.6713 1.31018 22.5658 1.53918 22.4223 1.65519C22.3733 1.69269 22.3318 1.74169 22.2903 1.78669C21.9317 2.1697 21.5127 2.42121 20.9657 2.39121C20.1657 2.34621 19.4827 2.59771 18.8787 3.20973C18.7502 2.45521 18.3236 2.0047 17.6746 1.71569C17.3351 1.56568 16.9916 1.41518 16.7536 1.08867C16.5876 0.856163 16.5421 0.597155 16.4591 0.341647C16.4061 0.187643 16.3536 0.0301382 16.1761 0.00363739C15.9836 -0.0263635 15.9081 0.135141 15.8326 0.270145C15.5306 0.822162 15.4136 1.43018 15.4251 2.0462C15.4516 3.43174 16.0366 4.53527 17.1991 5.3203C17.3311 5.4103 17.3651 5.5003 17.3236 5.63181C17.2441 5.90231 17.1501 6.16482 17.0671 6.43533C17.0141 6.60784 16.9351 6.64584 16.7501 6.57033C16.1121 6.30383 15.5611 5.90931 15.074 5.4328C14.2475 4.63328 13.5 3.75075 12.568 3.05973C12.349 2.89822 12.13 2.74822 11.9034 2.60522C10.9524 1.68169 12.028 0.923165 12.277 0.833162C12.5375 0.739159 12.3675 0.41615 11.5259 0.42015C10.6844 0.42365 9.91439 0.705658 8.93286 1.08117C8.78935 1.13767 8.63835 1.17867 8.48384 1.21267C7.59332 1.04367 6.66829 1.00617 5.70226 1.11517C3.88321 1.31768 2.43016 2.1777 1.36213 3.64575C0.0790928 5.4103 -0.222916 7.41536 0.146595 9.50642C0.535106 11.7105 1.66014 13.535 3.38869 14.9616C5.18125 16.4406 7.24581 17.1657 9.60138 17.0266C11.0319 16.9441 12.6245 16.7526 14.421 15.2321C14.874 15.4576 15.3496 15.5476 16.1381 15.6151C16.7456 15.6716 17.3306 15.5851 17.7836 15.4911C18.4931 15.3411 18.4441 14.6841 18.1876 14.5636C16.1081 13.595 16.5646 13.9891 16.1496 13.67C17.2061 12.42 18.8202 10.1979 19.3182 7.17235C19.3672 6.83834 19.4297 6.36783 19.4222 6.09732C19.4182 5.93231 19.4562 5.86831 19.6447 5.84931C20.1657 5.78931 20.6712 5.64681 21.1357 5.3913C22.4833 4.65528 23.0268 3.44624 23.1548 1.9972C23.1738 1.77569 23.1508 1.54668 22.9168 1.43018Z";
|
|
4512
|
+
/**
|
|
4513
|
+
* Install the adaptation layer. Runs once per page (idempotent); the
|
|
4514
|
+
* evaluate loop re-applies on orientation/resize and reverts off-portrait.
|
|
4515
|
+
*/
|
|
4516
|
+
function startMobileAdapt() {
|
|
4517
|
+
if (typeof window === "undefined" || typeof document === "undefined") return;
|
|
4518
|
+
if (window.__dshRemoteAdaptInstalled === true) return;
|
|
4519
|
+
window.__dshRemoteAdaptInstalled = true;
|
|
4520
|
+
const w = window;
|
|
4521
|
+
let active = false;
|
|
4522
|
+
let savedViewportContent = null;
|
|
4523
|
+
let whaleEl = null;
|
|
4524
|
+
let whaleObserver = null;
|
|
4525
|
+
let whaleTimer = null;
|
|
4526
|
+
let whaleSuppressClick = false;
|
|
4527
|
+
let whaleShown = false;
|
|
4528
|
+
let drag = null;
|
|
4529
|
+
let swipeTouch = null;
|
|
4530
|
+
let lastComposerTap = 0;
|
|
4531
|
+
let adaptEnabled = true;
|
|
4532
|
+
/**
|
|
4533
|
+
* Idempotently (re-)install the adaptation stylesheet. The rules live in
|
|
4534
|
+
* one <style> tag keyed by data-plugin-css; the sync tick re-runs this so
|
|
4535
|
+
* a tag lost to any external DOM cleanup is restored within one tick
|
|
4536
|
+
* instead of silently dropping the suppressions (portrait pet hiding,
|
|
4537
|
+
* rail compaction) while the body class stays.
|
|
4538
|
+
*/
|
|
4539
|
+
function ensureAdaptStyle() {
|
|
4540
|
+
if (document.querySelector(`style[data-plugin-css="${ADAPT_CSS_ID}"]`) !== null) return;
|
|
4541
|
+
const tag = document.createElement("style");
|
|
4542
|
+
tag.dataset.plugin = "remote-web-ui";
|
|
4543
|
+
tag.dataset.pluginCss = ADAPT_CSS_ID;
|
|
4544
|
+
tag.textContent = ADAPT_CSS.join("");
|
|
4545
|
+
document.head.appendChild(tag);
|
|
4546
|
+
}
|
|
4547
|
+
function apply() {
|
|
4548
|
+
if (active) return;
|
|
4549
|
+
active = true;
|
|
4550
|
+
document.body.classList.add(ACTIVE_CLASS);
|
|
4551
|
+
try {
|
|
4552
|
+
w.__dshRemoteAdapt?.closeDetails?.();
|
|
4553
|
+
} catch {}
|
|
4554
|
+
ensureAdaptStyle();
|
|
4555
|
+
const meta = document.querySelector("meta[name=viewport]");
|
|
4556
|
+
if (meta instanceof HTMLMetaElement && meta.getAttribute("content") !== null && !(meta.getAttribute("content") ?? "").includes("viewport-fit")) {
|
|
4557
|
+
savedViewportContent = meta.getAttribute("content");
|
|
4558
|
+
meta.setAttribute("content", `${savedViewportContent}, viewport-fit=cover`);
|
|
4559
|
+
}
|
|
4560
|
+
ensureWhale();
|
|
4561
|
+
syncWhale();
|
|
4562
|
+
setWhaleTimer(true);
|
|
4563
|
+
seatHeaderActions();
|
|
4564
|
+
}
|
|
4565
|
+
function revert() {
|
|
4566
|
+
if (!active) return;
|
|
4567
|
+
active = false;
|
|
4568
|
+
unseatHeaderActions();
|
|
4569
|
+
removeCompactPicker();
|
|
4570
|
+
document.body.classList.remove(ACTIVE_CLASS);
|
|
4571
|
+
document.body.classList.remove(RAIL_HIDDEN_CLASS);
|
|
4572
|
+
document.querySelector(`style[data-plugin-css="${ADAPT_CSS_ID}"]`)?.remove();
|
|
4573
|
+
const meta = document.querySelector("meta[name=viewport]");
|
|
4574
|
+
if (meta instanceof HTMLMetaElement && savedViewportContent !== null) {
|
|
4575
|
+
meta.setAttribute("content", savedViewportContent);
|
|
4576
|
+
savedViewportContent = null;
|
|
4577
|
+
}
|
|
4578
|
+
if (whaleEl !== null) whaleEl.style.display = "none";
|
|
4579
|
+
setWhaleTimer(false);
|
|
4580
|
+
}
|
|
4581
|
+
/** Start/stop the 600ms sync tick; a no-op when already in the asked state. */
|
|
4582
|
+
function setWhaleTimer(on) {
|
|
4583
|
+
if (on && whaleTimer === null && active) whaleTimer = window.setInterval(syncWhale, 600);
|
|
4584
|
+
else if (!on && whaleTimer !== null) {
|
|
4585
|
+
window.clearInterval(whaleTimer);
|
|
4586
|
+
whaleTimer = null;
|
|
4587
|
+
}
|
|
4588
|
+
}
|
|
4589
|
+
/**
|
|
4590
|
+
* Compact picker (v79): a phone row cannot fit the desktop text triggers,
|
|
4591
|
+
* so the model/effort entries become two icon buttons in the trailing
|
|
4592
|
+
* row. Both forward to the official picker trigger (its menu renders as
|
|
4593
|
+
* the bottom sheet) and then drill straight into the asked cell — model
|
|
4594
|
+
* list or effort list — so one tap lands on the list, matching the
|
|
4595
|
+
* cube-model / brain-effort mapping. The official context ring next to
|
|
4596
|
+
* the send button keeps its own semantics untouched.
|
|
4597
|
+
*/
|
|
4598
|
+
function removeCompactPicker() {
|
|
4599
|
+
document.body.classList.remove(COMPACT_CLASS);
|
|
4600
|
+
document.getElementById(MODEL_BTN_ID)?.remove();
|
|
4601
|
+
document.getElementById(EFFORT_BTN_ID)?.remove();
|
|
4602
|
+
}
|
|
4603
|
+
function drillIntoPicker(cellPattern) {
|
|
4604
|
+
const trigger = document.querySelector("[class$=\"_composerSeat\"] [class$=\"_trailing\"] [class$=\"_trigger\"]:has([class$=\"_triggerEffort\"])");
|
|
4605
|
+
if (trigger === null) return;
|
|
4606
|
+
trigger.click();
|
|
4607
|
+
let tries = 0;
|
|
4608
|
+
const tapCell = () => {
|
|
4609
|
+
tries += 1;
|
|
4610
|
+
const cell = Array.from(document.querySelectorAll("[class$=\"_composerSeat\"] [class$=\"_menu\"] [class$=\"_cell\"]")).find((c) => cellPattern.test(c.textContent ?? ""));
|
|
4611
|
+
if (cell !== void 0) {
|
|
4612
|
+
cell.click();
|
|
4613
|
+
return;
|
|
4614
|
+
}
|
|
4615
|
+
if (tries < 8) window.setTimeout(tapCell, 150);
|
|
4616
|
+
};
|
|
4617
|
+
window.setTimeout(tapCell, 150);
|
|
4618
|
+
}
|
|
4619
|
+
function makeCompactButton(id, title, icon, cellPattern) {
|
|
4620
|
+
const btn = document.createElement("button");
|
|
4621
|
+
btn.id = id;
|
|
4622
|
+
btn.type = "button";
|
|
4623
|
+
btn.dataset.dshPlugin = "remote-web-ui";
|
|
4624
|
+
btn.title = title;
|
|
4625
|
+
btn.setAttribute("aria-label", title);
|
|
4626
|
+
btn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${icon}</svg>`;
|
|
4627
|
+
btn.addEventListener("click", () => {
|
|
4628
|
+
drillIntoPicker(cellPattern);
|
|
4629
|
+
});
|
|
4630
|
+
return btn;
|
|
4631
|
+
}
|
|
4632
|
+
function syncCompactPicker() {
|
|
4633
|
+
if (!active) return;
|
|
4634
|
+
const tools = document.querySelector("[class$=\"_composerSeat\"] [class$=\"_tools\"]");
|
|
4635
|
+
const trigger = tools?.parentElement?.querySelector("[class$=\"_triggerEffort\"]")?.parentElement;
|
|
4636
|
+
if (tools === null || trigger === null) {
|
|
4637
|
+
removeCompactPicker();
|
|
4638
|
+
return;
|
|
4639
|
+
}
|
|
4640
|
+
const zh = (navigator.language ?? "").toLowerCase().startsWith("zh");
|
|
4641
|
+
if (document.getElementById(MODEL_BTN_ID) === null) tools.appendChild(makeCompactButton(MODEL_BTN_ID, zh ? "选择模型" : "Pick model", CUBE_ICON, /模型|Model/));
|
|
4642
|
+
if (document.getElementById(EFFORT_BTN_ID) === null) tools.appendChild(makeCompactButton(EFFORT_BTN_ID, zh ? "选择推理等级" : "Pick reasoning effort", LEVELS_ICON, /推理等级|Reasoning|Effort/i));
|
|
4643
|
+
document.body.classList.add(COMPACT_CLASS);
|
|
4644
|
+
}
|
|
4645
|
+
/**
|
|
4646
|
+
* Toggle the sidebar through the wired layout face, then verify the flip:
|
|
4647
|
+
* the official LayoutController can be mounted yet inert (its bound store
|
|
4648
|
+
* actions race the root entry on this cohort — observed on the running
|
|
4649
|
+
* local build, where the face call silently did nothing and the whale
|
|
4650
|
+
* was dead). When the frame state did not change shortly after the call,
|
|
4651
|
+
* drive the official rail/logo toggle instead: that button owns its own
|
|
4652
|
+
* store actions and flips the same state on every cohort we support.
|
|
4653
|
+
*/
|
|
4654
|
+
function toggleSidebarVerified() {
|
|
4655
|
+
const frame = document.querySelector("[class$=\"_frame\"]");
|
|
4656
|
+
const collapsedBefore = frame instanceof HTMLElement ? frame.hasAttribute("data-sidebar-collapsed") : null;
|
|
4657
|
+
w.__dshRemoteAdapt?.toggleSidebar?.();
|
|
4658
|
+
if (collapsedBefore === null) return;
|
|
4659
|
+
window.setTimeout(() => {
|
|
4660
|
+
if (!active) return;
|
|
4661
|
+
const frameNow = document.querySelector("[class$=\"_frame\"]");
|
|
4662
|
+
if (!(frameNow instanceof HTMLElement)) return;
|
|
4663
|
+
if (frameNow.hasAttribute("data-sidebar-collapsed") !== collapsedBefore) return;
|
|
4664
|
+
document.querySelector("[class$=\"_railFish\"] button, [class$=\"_logoRow\"] [class*=\"_iconButton\"]")?.click();
|
|
4665
|
+
}, 150);
|
|
4666
|
+
}
|
|
4667
|
+
function ensureWhale() {
|
|
4668
|
+
if (whaleEl !== null || !document.body) return;
|
|
4669
|
+
const whale = document.createElement("button");
|
|
4670
|
+
whale.id = WHALE_ID;
|
|
4671
|
+
whale.type = "button";
|
|
4672
|
+
whale.dataset.dshPlugin = "remote-web-ui";
|
|
4673
|
+
const whaleLabel = (navigator.language ?? "").toLowerCase().startsWith("zh") ? "打开侧边栏" : "Open sidebar";
|
|
4674
|
+
whale.title = whaleLabel;
|
|
4675
|
+
whale.setAttribute("aria-label", whaleLabel);
|
|
4676
|
+
whale.innerHTML = `<svg viewBox="0 0 23.16 17.04" fill="none" aria-hidden="true"><path d="${FISH_PATH}" fill="currentColor"/></svg>`;
|
|
4677
|
+
whale.addEventListener("click", () => {
|
|
4678
|
+
if (whaleSuppressClick) {
|
|
4679
|
+
whaleSuppressClick = false;
|
|
4680
|
+
return;
|
|
4681
|
+
}
|
|
4682
|
+
toggleSidebarVerified();
|
|
4683
|
+
});
|
|
4684
|
+
whale.addEventListener("pointerdown", (e) => {
|
|
4685
|
+
if (whaleEl === null) return;
|
|
4686
|
+
if (e.button !== 0 && e.pointerType === "mouse") return;
|
|
4687
|
+
if (active) {
|
|
4688
|
+
const ta = document.querySelector("[class$=\"_composerSeat\"] textarea, [class$=\"_composerSeat\"] input");
|
|
4689
|
+
if (ta !== null && document.activeElement === ta) {
|
|
4690
|
+
ta.blur();
|
|
4691
|
+
lastComposerTap = 0;
|
|
4692
|
+
}
|
|
4693
|
+
}
|
|
4694
|
+
drag = {
|
|
4695
|
+
x: e.clientX,
|
|
4696
|
+
y: e.clientY,
|
|
4697
|
+
left: whale.offsetLeft,
|
|
4698
|
+
top: whale.offsetTop,
|
|
4699
|
+
moved: false
|
|
4700
|
+
};
|
|
4701
|
+
try {
|
|
4702
|
+
whale.setPointerCapture(e.pointerId);
|
|
4703
|
+
} catch {}
|
|
4704
|
+
e.preventDefault();
|
|
4705
|
+
e.stopPropagation();
|
|
4706
|
+
});
|
|
4707
|
+
whale.addEventListener("pointermove", (e) => {
|
|
4708
|
+
if (whaleEl === null || drag === null) return;
|
|
4709
|
+
const dx = e.clientX - drag.x;
|
|
4710
|
+
const dy = e.clientY - drag.y;
|
|
4711
|
+
drag.moved = drag.moved || Math.abs(dx) > 3 || Math.abs(dy) > 3;
|
|
4712
|
+
whaleEl.style.left = `${Math.min(Math.max(4, drag.left + dx), window.innerWidth - 38)}px`;
|
|
4713
|
+
whaleEl.style.top = `${Math.min(Math.max(4, drag.top + dy), window.innerHeight - 38)}px`;
|
|
4714
|
+
e.preventDefault();
|
|
4715
|
+
e.stopPropagation();
|
|
4716
|
+
});
|
|
4717
|
+
const endWhaleDrag = () => {
|
|
4718
|
+
if (whaleEl === null || drag === null) return;
|
|
4719
|
+
const moved = drag.moved;
|
|
4720
|
+
drag = null;
|
|
4721
|
+
whaleSuppressClick = moved;
|
|
4722
|
+
try {
|
|
4723
|
+
window.localStorage.setItem(WHALE_POS_KEY, JSON.stringify({
|
|
4724
|
+
x: whaleEl.offsetLeft,
|
|
4725
|
+
y: whaleEl.offsetTop
|
|
4726
|
+
}));
|
|
4727
|
+
} catch {}
|
|
4728
|
+
};
|
|
4729
|
+
whale.addEventListener("pointerup", endWhaleDrag);
|
|
4730
|
+
whale.addEventListener("pointercancel", endWhaleDrag);
|
|
4731
|
+
whaleEl = whale;
|
|
4732
|
+
document.body.appendChild(whale);
|
|
4733
|
+
}
|
|
4734
|
+
/** Restore a dragged position when the whale becomes visible again. */
|
|
4735
|
+
function applyWhalePos() {
|
|
4736
|
+
if (whaleEl === null) return;
|
|
4737
|
+
let pos = null;
|
|
4738
|
+
try {
|
|
4739
|
+
pos = JSON.parse(window.localStorage.getItem(WHALE_POS_KEY) ?? "null");
|
|
4740
|
+
} catch {}
|
|
4741
|
+
if (pos !== null && typeof pos.x === "number" && typeof pos.y === "number") {
|
|
4742
|
+
const x = Math.min(Math.max(4, pos.x), window.innerWidth - 38);
|
|
4743
|
+
const y = Math.min(Math.max(4, pos.y), window.innerHeight - 38);
|
|
4744
|
+
whaleEl.style.left = `${x}px`;
|
|
4745
|
+
whaleEl.style.top = `${y}px`;
|
|
4746
|
+
}
|
|
4747
|
+
}
|
|
4748
|
+
function disableRowDrag() {
|
|
4749
|
+
if (!active) return;
|
|
4750
|
+
const rows = document.querySelectorAll("[class$=\"_sidebarCol\"] [class$=\"_sessionRow\"], [class$=\"_sidebarCol\"] [class$=\"_projectRow\"]");
|
|
4751
|
+
for (const row of rows) if (row.getAttribute("draggable") !== "false") row.setAttribute("draggable", "false");
|
|
4752
|
+
}
|
|
4753
|
+
function syncWhale() {
|
|
4754
|
+
if (active) ensureAdaptStyle();
|
|
4755
|
+
if (whaleEl === null) return;
|
|
4756
|
+
if (!active) {
|
|
4757
|
+
whaleEl.style.display = "none";
|
|
4758
|
+
return;
|
|
4759
|
+
}
|
|
4760
|
+
const collapsed = document.querySelector("[class$=\"_frame\"][data-sidebar-collapsed]") !== null;
|
|
4761
|
+
const overlayUp = document.querySelector("[class$=\"_overlay\"]") !== null;
|
|
4762
|
+
const show = collapsed && !overlayUp;
|
|
4763
|
+
if (show && !whaleShown) applyWhalePos();
|
|
4764
|
+
whaleEl.style.display = show ? "" : "none";
|
|
4765
|
+
whaleShown = show;
|
|
4766
|
+
document.body.classList.toggle(RAIL_HIDDEN_CLASS, collapsed);
|
|
4767
|
+
disableRowDrag();
|
|
4768
|
+
seatHeaderActions();
|
|
4769
|
+
alignActionsText();
|
|
4770
|
+
syncCompactPicker();
|
|
4771
|
+
}
|
|
4772
|
+
function seatHeaderActions() {
|
|
4773
|
+
if (!active) return;
|
|
4774
|
+
const tabs = document.querySelector("[class$=\"_header\"] [class$=\"_tabs\"]");
|
|
4775
|
+
const fresh = document.querySelector("[class$=\"_titleCluster\"] [class$=\"_headerActions\"]");
|
|
4776
|
+
const seated = tabs !== null ? tabs.querySelector(":scope > [class$=\"_headerActions\"]") : null;
|
|
4777
|
+
if (fresh !== null && tabs !== null) {
|
|
4778
|
+
seated?.remove();
|
|
4779
|
+
tabs.appendChild(fresh);
|
|
4780
|
+
}
|
|
4781
|
+
}
|
|
4782
|
+
function alignActionsText() {
|
|
4783
|
+
if (!active) return;
|
|
4784
|
+
const tabs = document.querySelector("[class$=\"_header\"] [class$=\"_tabs\"]");
|
|
4785
|
+
if (tabs === null) return;
|
|
4786
|
+
const actions = tabs.querySelector(":scope > [class$=\"_headerActions\"]");
|
|
4787
|
+
const tabBtn = tabs.querySelector(":scope > [class*=\"_tab\"]");
|
|
4788
|
+
if (actions === null || tabBtn === null) return;
|
|
4789
|
+
const textBottom = (el) => {
|
|
4790
|
+
if (el === null) return null;
|
|
4791
|
+
const text = Array.from(el.childNodes).find((n) => n.nodeType === 3 && n.textContent !== null && n.textContent.trim() !== "");
|
|
4792
|
+
if (text === void 0) return null;
|
|
4793
|
+
try {
|
|
4794
|
+
const range = document.createRange();
|
|
4795
|
+
range.selectNode(text);
|
|
4796
|
+
return range.getBoundingClientRect().bottom;
|
|
4797
|
+
} catch {
|
|
4798
|
+
return null;
|
|
4799
|
+
}
|
|
4800
|
+
};
|
|
4801
|
+
const tabBottom = textBottom(tabBtn);
|
|
4802
|
+
if (tabBottom === null) return;
|
|
4803
|
+
const curMatch = /translateY\((-?[\d.]+)px\)/.exec(actions.style.transform ?? "");
|
|
4804
|
+
const cur = curMatch !== null ? parseFloat(curMatch[1] ?? "0") : 0;
|
|
4805
|
+
let maxBottom = null;
|
|
4806
|
+
for (const sel of ["[class$=\"_label\"]", "[class$=\"_count\"]"]) {
|
|
4807
|
+
const b = textBottom(actions.querySelector(sel));
|
|
4808
|
+
if (b !== null && (maxBottom === null || b - cur > maxBottom)) maxBottom = b - cur;
|
|
4809
|
+
}
|
|
4810
|
+
if (maxBottom === null) return;
|
|
4811
|
+
const delta = Math.round((tabBottom - maxBottom) * 10) / 10;
|
|
4812
|
+
if (Math.abs(delta) < .5) {
|
|
4813
|
+
if (actions.style.transform !== "") actions.style.transform = "";
|
|
4814
|
+
return;
|
|
4815
|
+
}
|
|
4816
|
+
actions.style.transform = `translateY(${delta}px)`;
|
|
4817
|
+
}
|
|
4818
|
+
function unseatHeaderActions() {
|
|
4819
|
+
const tabs = document.querySelector("[class$=\"_header\"] [class$=\"_tabs\"]");
|
|
4820
|
+
const seated = tabs !== null ? tabs.querySelector(":scope > [class$=\"_headerActions\"]") : null;
|
|
4821
|
+
const wrap = document.querySelector("[class$=\"_titleCluster\"] > div");
|
|
4822
|
+
if (seated !== null) if (wrap !== null) wrap.appendChild(seated);
|
|
4823
|
+
else seated.remove();
|
|
4824
|
+
}
|
|
4825
|
+
function ensureWhaleObserver() {
|
|
4826
|
+
if (whaleObserver !== null || typeof MutationObserver === "undefined" || !document.body) return;
|
|
4827
|
+
whaleObserver = new MutationObserver(() => {
|
|
4828
|
+
syncWhale();
|
|
4829
|
+
});
|
|
4830
|
+
whaleObserver.observe(document.body, {
|
|
4831
|
+
attributes: true,
|
|
4832
|
+
attributeFilter: ["data-sidebar-collapsed"],
|
|
4833
|
+
subtree: true
|
|
4834
|
+
});
|
|
4835
|
+
}
|
|
4836
|
+
function evaluate() {
|
|
4837
|
+
if (!adaptEnabled) {
|
|
4838
|
+
revert();
|
|
4839
|
+
return;
|
|
4840
|
+
}
|
|
4841
|
+
if (isMobilePortrait()) {
|
|
4842
|
+
apply();
|
|
4843
|
+
ensureWhaleObserver();
|
|
4844
|
+
} else revert();
|
|
4845
|
+
}
|
|
4846
|
+
evaluate();
|
|
4847
|
+
window.addEventListener("orientationchange", evaluate);
|
|
4848
|
+
window.addEventListener("resize", evaluate);
|
|
4849
|
+
document.addEventListener("visibilitychange", () => {
|
|
4850
|
+
setWhaleTimer(document.visibilityState !== "hidden");
|
|
4851
|
+
});
|
|
4852
|
+
function onKeydownCapture(e) {
|
|
4853
|
+
if (!active) return;
|
|
4854
|
+
if (e.key !== "Enter" || e.shiftKey || e.ctrlKey || e.metaKey || e.altKey) return;
|
|
4855
|
+
const t = e.target;
|
|
4856
|
+
if (!(t instanceof HTMLElement)) return;
|
|
4857
|
+
if (t.closest("[class$=\"_input\"]") === null) return;
|
|
4858
|
+
if (e.isComposing || e.keyCode === 229) return;
|
|
4859
|
+
e.preventDefault();
|
|
4860
|
+
e.stopPropagation();
|
|
4861
|
+
try {
|
|
4862
|
+
document.execCommand("insertText", false, "\n");
|
|
4863
|
+
} catch {}
|
|
4864
|
+
}
|
|
4865
|
+
document.addEventListener("keydown", onKeydownCapture, true);
|
|
4866
|
+
function collapseSidebar() {
|
|
4867
|
+
toggleSidebarVerified();
|
|
4868
|
+
}
|
|
4869
|
+
function onClickCapture(e) {
|
|
4870
|
+
if (!active) return;
|
|
4871
|
+
const frame = document.querySelector("[class$=\"_frame\"]");
|
|
4872
|
+
if (frame === null || frame.hasAttribute("data-sidebar-collapsed")) return;
|
|
4873
|
+
const t = e.target;
|
|
4874
|
+
if (!(t instanceof Element)) return;
|
|
4875
|
+
if (t.closest("[class$=\"_sidebarCol\"]") !== null) {
|
|
4876
|
+
const topNewSessionClicked = t.closest("[class$=\"_newSession\"], [class$=\"_brand\"]") !== null;
|
|
4877
|
+
let projectNewSessionClicked = false;
|
|
4878
|
+
const projectActions = t.closest("[class$=\"_projectRow\"] [class$=\"_rowActions\"]");
|
|
4879
|
+
if (projectActions !== null) {
|
|
4880
|
+
const btn = t.closest("button");
|
|
4881
|
+
const btns = projectActions.querySelectorAll("button");
|
|
4882
|
+
projectNewSessionClicked = btn !== null && btns.length > 0 && btn === btns[btns.length - 1];
|
|
4883
|
+
}
|
|
4884
|
+
let shouldCollapse = false;
|
|
4885
|
+
if (t.closest("[class$=\"_sessionRow\"]") !== null) shouldCollapse = t.closest("[class$=\"_rowActions\"]") === null;
|
|
4886
|
+
else if (t.closest("[class$=\"_settingsArea\"]") !== null || topNewSessionClicked || projectNewSessionClicked) shouldCollapse = true;
|
|
4887
|
+
if (shouldCollapse) collapseSidebar();
|
|
4888
|
+
return;
|
|
4889
|
+
}
|
|
4890
|
+
if (t.closest("[class$=\"_overlay\"], [class$=\"_dialog\"], [class$=\"_menu\"], [class*=\"_portal\"], [id=\"dshRemoteWhale\"], [id=\"dshLanGate\"]") !== null) return;
|
|
4891
|
+
collapseSidebar();
|
|
4892
|
+
}
|
|
4893
|
+
document.addEventListener("click", onClickCapture, true);
|
|
4894
|
+
function insideHScrollable(el) {
|
|
4895
|
+
let n = el;
|
|
4896
|
+
while (n !== null && n !== document.body) {
|
|
4897
|
+
const cs = getComputedStyle(n);
|
|
4898
|
+
if ((cs.overflowX === "auto" || cs.overflowX === "scroll") && n.scrollWidth > n.clientWidth + 4) return true;
|
|
4899
|
+
n = n.parentElement;
|
|
4900
|
+
}
|
|
4901
|
+
return false;
|
|
4902
|
+
}
|
|
4903
|
+
document.addEventListener("touchstart", (e) => {
|
|
4904
|
+
if (!active) return;
|
|
4905
|
+
const t = e.target;
|
|
4906
|
+
if (t instanceof Element && (t.tagName === "TEXTAREA" || t.tagName === "INPUT" || t.closest(`#${WHALE_ID}`) !== null || insideHScrollable(t) || t.closest("table, [class$=\"_table\"], [class$=\"_tablePane\"]") !== null)) {
|
|
4907
|
+
swipeTouch = null;
|
|
4908
|
+
return;
|
|
4909
|
+
}
|
|
4910
|
+
const ct = e.changedTouches[0];
|
|
4911
|
+
if (ct === void 0) return;
|
|
4912
|
+
swipeTouch = {
|
|
4913
|
+
x: ct.clientX,
|
|
4914
|
+
y: ct.clientY,
|
|
4915
|
+
id: ct.identifier
|
|
4916
|
+
};
|
|
4917
|
+
}, {
|
|
4918
|
+
capture: true,
|
|
4919
|
+
passive: true
|
|
4920
|
+
});
|
|
4921
|
+
document.addEventListener("touchend", (e) => {
|
|
4922
|
+
if (swipeTouch === null) return;
|
|
4923
|
+
const ct = e.changedTouches[0];
|
|
4924
|
+
if (ct === void 0 || ct.identifier !== swipeTouch.id) return;
|
|
4925
|
+
const dx = ct.clientX - swipeTouch.x;
|
|
4926
|
+
const dy = ct.clientY - swipeTouch.y;
|
|
4927
|
+
swipeTouch = null;
|
|
4928
|
+
if (!active) return;
|
|
4929
|
+
if (Math.abs(dx) < 60 || Math.abs(dx) < Math.abs(dy) * 1.5) return;
|
|
4930
|
+
const frame = document.querySelector("[class$=\"_frame\"]");
|
|
4931
|
+
if (frame === null) return;
|
|
4932
|
+
if (document.querySelector("[class$=\"_overlay\"], [class$=\"_dialog\"], [class$=\"_menu\"], [class*=\"_portal\"]") !== null) return;
|
|
4933
|
+
const collapsed = frame.hasAttribute("data-sidebar-collapsed");
|
|
4934
|
+
if (dx < 0 && !collapsed) collapseSidebar();
|
|
4935
|
+
else if (dx > 0 && collapsed) toggleSidebarVerified();
|
|
4936
|
+
}, {
|
|
4937
|
+
capture: true,
|
|
4938
|
+
passive: true
|
|
4939
|
+
});
|
|
4940
|
+
document.addEventListener("touchcancel", () => {
|
|
4941
|
+
swipeTouch = null;
|
|
4942
|
+
}, true);
|
|
4943
|
+
const LONG_PRESS_MS = 500;
|
|
4944
|
+
const LONG_PRESS_MOVE = 10;
|
|
4945
|
+
let longPress = null;
|
|
4946
|
+
let suppressSessionClickUntil = 0;
|
|
4947
|
+
let suppressSessionClickRow = null;
|
|
4948
|
+
let longPressMenuGuardUntil = 0;
|
|
4949
|
+
function clearLongPress() {
|
|
4950
|
+
if (longPress !== null) {
|
|
4951
|
+
if (longPress.timer !== null) window.clearTimeout(longPress.timer);
|
|
4952
|
+
longPress = null;
|
|
4953
|
+
}
|
|
4954
|
+
}
|
|
4955
|
+
function openSessionMenu(row) {
|
|
4956
|
+
const actions = row.querySelector("[class$=\"_rowActions\"]");
|
|
4957
|
+
const btn = actions?.querySelector("button");
|
|
4958
|
+
if (actions === null || btn === null) return;
|
|
4959
|
+
longPressMenuGuardUntil = Date.now() + 1200;
|
|
4960
|
+
actions.style.display = "inline-flex";
|
|
4961
|
+
try {
|
|
4962
|
+
btn.click();
|
|
4963
|
+
} finally {
|
|
4964
|
+
window.setTimeout(() => {
|
|
4965
|
+
actions.style.display = "";
|
|
4966
|
+
}, 50);
|
|
4967
|
+
}
|
|
4968
|
+
}
|
|
4969
|
+
document.addEventListener("touchstart", (e) => {
|
|
4970
|
+
if (!active) return;
|
|
4971
|
+
const t = e.target;
|
|
4972
|
+
if (!(t instanceof Element)) {
|
|
4973
|
+
clearLongPress();
|
|
4974
|
+
return;
|
|
4975
|
+
}
|
|
4976
|
+
const row = t.closest("[class$=\"_sessionRow\"]");
|
|
4977
|
+
if (row === null || t.closest("[class$=\"_rowActions\"]") !== null) {
|
|
4978
|
+
clearLongPress();
|
|
4979
|
+
return;
|
|
4980
|
+
}
|
|
4981
|
+
const ct = e.changedTouches[0];
|
|
4982
|
+
if (ct === void 0) return;
|
|
4983
|
+
clearLongPress();
|
|
4984
|
+
const lp = {
|
|
4985
|
+
row,
|
|
4986
|
+
x: ct.clientX,
|
|
4987
|
+
y: ct.clientY,
|
|
4988
|
+
timer: null,
|
|
4989
|
+
triggered: false
|
|
4990
|
+
};
|
|
4991
|
+
lp.timer = window.setTimeout(() => {
|
|
4992
|
+
if (longPress !== lp) return;
|
|
4993
|
+
lp.triggered = true;
|
|
4994
|
+
openSessionMenu(lp.row);
|
|
4995
|
+
try {
|
|
4996
|
+
navigator.vibrate?.(10);
|
|
4997
|
+
} catch {}
|
|
4998
|
+
}, LONG_PRESS_MS);
|
|
4999
|
+
longPress = lp;
|
|
5000
|
+
}, {
|
|
5001
|
+
capture: true,
|
|
5002
|
+
passive: true
|
|
5003
|
+
});
|
|
5004
|
+
document.addEventListener("touchmove", (e) => {
|
|
5005
|
+
if (!active || longPress === null) return;
|
|
5006
|
+
const ct = e.changedTouches[0];
|
|
5007
|
+
if (ct === void 0) return;
|
|
5008
|
+
if (Math.abs(ct.clientX - longPress.x) > LONG_PRESS_MOVE || Math.abs(ct.clientY - longPress.y) > LONG_PRESS_MOVE) clearLongPress();
|
|
5009
|
+
}, {
|
|
5010
|
+
capture: true,
|
|
5011
|
+
passive: true
|
|
5012
|
+
});
|
|
5013
|
+
document.addEventListener("touchend", (e) => {
|
|
5014
|
+
if (!active || longPress === null) return;
|
|
5015
|
+
if (e.changedTouches[0] === void 0) return;
|
|
5016
|
+
const lp = longPress;
|
|
5017
|
+
clearLongPress();
|
|
5018
|
+
if (lp.triggered) {
|
|
5019
|
+
e.preventDefault();
|
|
5020
|
+
suppressSessionClickUntil = Date.now() + 800;
|
|
5021
|
+
suppressSessionClickRow = lp.row;
|
|
5022
|
+
}
|
|
5023
|
+
}, {
|
|
5024
|
+
capture: true,
|
|
5025
|
+
passive: false
|
|
5026
|
+
});
|
|
5027
|
+
document.addEventListener("touchcancel", () => {
|
|
5028
|
+
clearLongPress();
|
|
5029
|
+
}, true);
|
|
5030
|
+
window.addEventListener("click", (e) => {
|
|
5031
|
+
if (!active || Date.now() > suppressSessionClickUntil) return;
|
|
5032
|
+
const t = e.target;
|
|
5033
|
+
if (t instanceof Element && suppressSessionClickRow !== null && (t === suppressSessionClickRow || suppressSessionClickRow.contains(t))) {
|
|
5034
|
+
e.stopPropagation();
|
|
5035
|
+
e.preventDefault();
|
|
5036
|
+
suppressSessionClickUntil = 0;
|
|
5037
|
+
suppressSessionClickRow = null;
|
|
5038
|
+
}
|
|
5039
|
+
}, true);
|
|
5040
|
+
document.addEventListener("contextmenu", (e) => {
|
|
5041
|
+
if (!active) return;
|
|
5042
|
+
const t = e.target;
|
|
5043
|
+
if (!(t instanceof Element)) return;
|
|
5044
|
+
if (t.closest("[class$=\"_sessionRow\"]") !== null && (longPress !== null && longPress.triggered || Date.now() <= suppressSessionClickUntil)) e.preventDefault();
|
|
5045
|
+
}, true);
|
|
5046
|
+
document.addEventListener("pointerleave", (e) => {
|
|
5047
|
+
if (!active || Date.now() > longPressMenuGuardUntil) return;
|
|
5048
|
+
const t = e.target;
|
|
5049
|
+
if (t instanceof Element && (t.closest("[class$=\"_rowActions\"]") !== null || t.closest("[class$=\"_sessionRow\"]") !== null || t.closest("[class$=\"_projectRow\"]") !== null)) e.stopPropagation();
|
|
5050
|
+
}, true);
|
|
5051
|
+
const lanOrigFocus = HTMLElement.prototype.focus;
|
|
5052
|
+
HTMLElement.prototype.focus = function(options) {
|
|
5053
|
+
if (active && (this.tagName === "TEXTAREA" || this.tagName === "INPUT") && this.closest("[class$=\"_composerSeat\"]") !== null && Date.now() - lastComposerTap >= 800) return;
|
|
5054
|
+
lanOrigFocus.call(this, options);
|
|
5055
|
+
};
|
|
5056
|
+
let lastComposerTapRegistered = false;
|
|
5057
|
+
if (!lastComposerTapRegistered) {
|
|
5058
|
+
lastComposerTapRegistered = true;
|
|
5059
|
+
document.addEventListener("pointerdown", (e) => {
|
|
5060
|
+
if (!active) return;
|
|
5061
|
+
const t = e.target;
|
|
5062
|
+
if (t instanceof Element && (t.tagName === "TEXTAREA" || t.tagName === "INPUT") && t.closest("[class$=\"_composerSeat\"]") !== null) lastComposerTap = Date.now();
|
|
5063
|
+
}, true);
|
|
5064
|
+
}
|
|
5065
|
+
w.__dshRemoteAdapt = {
|
|
5066
|
+
evaluate,
|
|
5067
|
+
toggleSidebar: null,
|
|
5068
|
+
closeDetails: null,
|
|
5069
|
+
setEnabled(on) {
|
|
5070
|
+
adaptEnabled = on;
|
|
5071
|
+
if (on) evaluate();
|
|
5072
|
+
else revert();
|
|
5073
|
+
},
|
|
5074
|
+
flushCloseDetails() {
|
|
5075
|
+
if (active) w.__dshRemoteAdapt?.closeDetails?.();
|
|
5076
|
+
}
|
|
5077
|
+
};
|
|
5078
|
+
}
|
|
5079
|
+
//#endregion
|
|
4335
5080
|
//#region src/client/index.ts
|
|
4336
5081
|
/**
|
|
4337
|
-
*
|
|
4338
|
-
*
|
|
4339
|
-
*
|
|
4340
|
-
*
|
|
4341
|
-
*
|
|
4342
|
-
*
|
|
5082
|
+
* Remote control — browser half. Registers the `remote` dictionaries, the
|
|
5083
|
+
* sidebar-foot entry (phone trigger + pairing panel + update trigger), and
|
|
5084
|
+
* the pair boot flow (accept + presence heartbeats) plus the one-time
|
|
5085
|
+
* failed-pair notice. The portrait-touch adaptation of the official UI
|
|
5086
|
+
* starts at module scope (startMobileAdapt) so its focus guard is installed
|
|
5087
|
+
* before the app boots. Export discipline: packages/client/AGENTS.md — the
|
|
5088
|
+
* /client surface carries only what cordis loading needs plus types.
|
|
4343
5089
|
*/
|
|
5090
|
+
startMobileAdapt();
|
|
4344
5091
|
/** Dictionary namespace owned by this plugin. */
|
|
4345
5092
|
const NS = "remote";
|
|
4346
5093
|
/** Settings namespace the remote-control card edits (the Host plugin registers it). */
|
|
@@ -4371,33 +5118,38 @@ window.__ModuleLoader__.load({
|
|
|
4371
5118
|
return () => {};
|
|
4372
5119
|
}
|
|
4373
5120
|
}, "remote-web-ui: dictionaries");
|
|
5121
|
+
const layout = ctx.get("layout");
|
|
5122
|
+
const adapt = window.__dshRemoteAdapt;
|
|
5123
|
+
const layoutCall = (call) => {
|
|
5124
|
+
try {
|
|
5125
|
+
call?.();
|
|
5126
|
+
} catch {}
|
|
5127
|
+
};
|
|
5128
|
+
if (layout !== void 0 && adapt !== void 0) {
|
|
5129
|
+
if (typeof layout.toggleSidebar === "function") adapt.toggleSidebar = () => {
|
|
5130
|
+
layoutCall(layout.toggleSidebar);
|
|
5131
|
+
};
|
|
5132
|
+
if (typeof layout.closeDetails === "function") adapt.closeDetails = () => {
|
|
5133
|
+
layoutCall(layout.closeDetails);
|
|
5134
|
+
};
|
|
5135
|
+
}
|
|
5136
|
+
if (adapt !== void 0 && adapt.toggleSidebar === null) adapt.toggleSidebar = () => {
|
|
5137
|
+
document.querySelector("[class$=\"_railFish\"] button, [class$=\"_logoRow\"] [class*=\"_iconButton\"]")?.click();
|
|
5138
|
+
};
|
|
4374
5139
|
const t = ctx.locale.bind(NS);
|
|
4375
5140
|
const settingsScope = (ctx.get("webUiSettings") ?? ctx.settingsScope).bind({ namespace: REMOTE_WEB_UI_NS });
|
|
4376
5141
|
const enabled = () => {
|
|
4377
5142
|
const snapshot = settingsScope.getSnapshot();
|
|
4378
5143
|
return snapshot.status === "ready" ? snapshot.value?.enabled ?? true : snapshot.status === "unavailable";
|
|
4379
5144
|
};
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
} catch {}
|
|
4389
|
-
else if (!enabled() && disposeEntry !== void 0) {
|
|
4390
|
-
disposeEntry();
|
|
4391
|
-
disposeEntry = void 0;
|
|
4392
|
-
}
|
|
4393
|
-
};
|
|
4394
|
-
const unsubscribe = settingsScope.subscribe(syncEntry);
|
|
4395
|
-
syncEntry();
|
|
4396
|
-
return () => {
|
|
4397
|
-
unsubscribe();
|
|
4398
|
-
disposeEntry?.();
|
|
4399
|
-
};
|
|
4400
|
-
});
|
|
5145
|
+
const syncAdaptEnabled = () => {
|
|
5146
|
+
window.__dshRemoteAdapt?.setEnabled?.(enabled());
|
|
5147
|
+
};
|
|
5148
|
+
settingsScope.subscribe(syncAdaptEnabled);
|
|
5149
|
+
syncAdaptEnabled();
|
|
5150
|
+
try {
|
|
5151
|
+
adapt?.flushCloseDetails?.();
|
|
5152
|
+
} catch {}
|
|
4401
5153
|
ctx.slots.inject("sidebar.footer.action", () => {
|
|
4402
5154
|
let disposeEntry;
|
|
4403
5155
|
const syncEntry = () => {
|