dsh-pocket 2.6.3 → 2.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/client.js +245 -236
- package/client/mobile/fileGuard.ts +118 -0
- package/client/mobile/mobile-apply.tsx +10 -11
- package/client/mobile/mobile.css.ts +11 -44
- package/package.json +1 -1
- package/client/mobile/MobileComposerFullscreen.tsx +0 -77
package/client/client.js
CHANGED
|
@@ -37,7 +37,7 @@ __export(index_exports, {
|
|
|
37
37
|
redactStatus: () => redactStatus
|
|
38
38
|
});
|
|
39
39
|
module.exports = __toCommonJS(index_exports);
|
|
40
|
-
var
|
|
40
|
+
var import_react2 = require("react");
|
|
41
41
|
|
|
42
42
|
// client/api.js
|
|
43
43
|
var POCKET_RPC_CHANNEL = "/dsh-pocket";
|
|
@@ -337,57 +337,100 @@ function MobileDrawerFooter({ useSessions, downloadSessionLog, toggleSidebar, t
|
|
|
337
337
|
));
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
-
// client/mobile/
|
|
341
|
-
var
|
|
342
|
-
var
|
|
343
|
-
function
|
|
344
|
-
|
|
340
|
+
// client/mobile/fileGuard.ts
|
|
341
|
+
var GUARD_MSG = "\u624B\u673A\u4E0A\u65E0\u6CD5\u76F4\u63A5\u6253\u5F00\u7535\u8111\u4E0A\u7684\u6587\u4EF6";
|
|
342
|
+
var WS_LABELS = ["\u6DFB\u52A0\u5DE5\u4F5C\u533A", "\u6DFB\u52A0\u5DE5\u4F5C\u533A\u2026", "Add workspace", "Add workspace\u2026"];
|
|
343
|
+
function looksLikeFilePath(text) {
|
|
344
|
+
const t = (text ?? "").trim();
|
|
345
|
+
if (t.length < 3 || t.length > 320) return false;
|
|
346
|
+
if (/^(\/|~\/|\.\.?\/|[A-Za-z]:\\)/.test(t)) return true;
|
|
347
|
+
if (/\/[\w.\-]+\.\w{1,12}$/.test(t)) return true;
|
|
348
|
+
if (/[\w.\-]+\/[\w.\-]+\.\w{1,12}/.test(t)) return true;
|
|
349
|
+
return false;
|
|
345
350
|
}
|
|
346
|
-
function
|
|
347
|
-
|
|
348
|
-
if (on) document.body.setAttribute(FULLSCREEN_ATTR, "1");
|
|
349
|
-
else document.body.removeAttribute(FULLSCREEN_ATTR);
|
|
351
|
+
function isInsidePocket(el) {
|
|
352
|
+
return el !== null && el.closest('[data-mobile-nav="frame"]') !== null;
|
|
350
353
|
}
|
|
351
|
-
function
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
padding: "0 8px",
|
|
380
|
-
height: 32,
|
|
381
|
-
minWidth: 32,
|
|
382
|
-
borderRadius: 6,
|
|
383
|
-
display: "inline-flex",
|
|
384
|
-
alignItems: "center",
|
|
385
|
-
justifyContent: "center",
|
|
386
|
-
font: "inherit",
|
|
387
|
-
fontSize: 16,
|
|
388
|
-
lineHeight: 1
|
|
354
|
+
function startFileGuard() {
|
|
355
|
+
let toastEl = null;
|
|
356
|
+
let toastTimer = null;
|
|
357
|
+
const showToast = (text) => {
|
|
358
|
+
if (toastEl === null) {
|
|
359
|
+
toastEl = document.createElement("div");
|
|
360
|
+
toastEl.setAttribute("data-mobile-nav", "file-guard-toast");
|
|
361
|
+
Object.assign(toastEl.style, {
|
|
362
|
+
position: "fixed",
|
|
363
|
+
left: "50%",
|
|
364
|
+
bottom: "64px",
|
|
365
|
+
transform: "translateX(-50%)",
|
|
366
|
+
maxWidth: "84vw",
|
|
367
|
+
zIndex: "9999",
|
|
368
|
+
padding: "10px 14px",
|
|
369
|
+
borderRadius: "10px",
|
|
370
|
+
background: "rgba(20,22,28,.92)",
|
|
371
|
+
color: "#fff",
|
|
372
|
+
fontSize: "13px",
|
|
373
|
+
lineHeight: "1.4",
|
|
374
|
+
textAlign: "center",
|
|
375
|
+
fontFamily: "inherit",
|
|
376
|
+
boxShadow: "0 4px 16px rgba(0,0,0,.28)",
|
|
377
|
+
pointerEvents: "none",
|
|
378
|
+
opacity: "0",
|
|
379
|
+
transition: "opacity .18s ease"
|
|
380
|
+
});
|
|
381
|
+
document.body.appendChild(toastEl);
|
|
389
382
|
}
|
|
390
|
-
|
|
383
|
+
toastEl.textContent = text;
|
|
384
|
+
requestAnimationFrame(() => {
|
|
385
|
+
if (toastEl !== null) toastEl.style.opacity = "1";
|
|
386
|
+
});
|
|
387
|
+
if (toastTimer !== null) window.clearTimeout(toastTimer);
|
|
388
|
+
toastTimer = window.setTimeout(() => {
|
|
389
|
+
if (toastEl !== null) toastEl.style.opacity = "0";
|
|
390
|
+
}, 2600);
|
|
391
|
+
};
|
|
392
|
+
const onClick = (event) => {
|
|
393
|
+
const target = event.target;
|
|
394
|
+
if (target === null || isInsidePocket(target)) return;
|
|
395
|
+
const el = target.closest("button, a");
|
|
396
|
+
if (el === null) return;
|
|
397
|
+
if (!looksLikeFilePath(el.textContent)) return;
|
|
398
|
+
event.preventDefault();
|
|
399
|
+
event.stopImmediatePropagation();
|
|
400
|
+
showToast(GUARD_MSG);
|
|
401
|
+
};
|
|
402
|
+
document.addEventListener("click", onClick, true);
|
|
403
|
+
const hideWsEntries = () => {
|
|
404
|
+
const checkOne = (node) => {
|
|
405
|
+
if (node.nodeType !== 1) return;
|
|
406
|
+
const el = node;
|
|
407
|
+
const txt = (el.getAttribute("aria-label") ?? el.textContent ?? "").trim();
|
|
408
|
+
if (WS_LABELS.includes(txt)) {
|
|
409
|
+
el.style.display = "none";
|
|
410
|
+
el.setAttribute("data-mobile-nav-hide", "add-workspace");
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
const sel = '[role="menuitem"],[role="option"],li,button,a';
|
|
414
|
+
document.querySelectorAll(sel).forEach(checkOne);
|
|
415
|
+
const observer = new MutationObserver((mutations) => {
|
|
416
|
+
for (const m of mutations) {
|
|
417
|
+
m.addedNodes.forEach((n) => {
|
|
418
|
+
if (n.nodeType !== 1) return;
|
|
419
|
+
checkOne(n);
|
|
420
|
+
n.querySelectorAll?.(sel).forEach(checkOne);
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
425
|
+
return () => observer.disconnect();
|
|
426
|
+
};
|
|
427
|
+
const disconnectWs = hideWsEntries();
|
|
428
|
+
return () => {
|
|
429
|
+
document.removeEventListener("click", onClick, true);
|
|
430
|
+
disconnectWs();
|
|
431
|
+
if (toastTimer !== null) window.clearTimeout(toastTimer);
|
|
432
|
+
toastEl?.remove();
|
|
433
|
+
};
|
|
391
434
|
}
|
|
392
435
|
|
|
393
436
|
// client/mobile/mobile.css.ts
|
|
@@ -1250,6 +1293,17 @@ var MOBILE_CSS = `
|
|
|
1250
1293
|
[data-phase="hero"] [class$="_stack"] {
|
|
1251
1294
|
gap: 0 !important;
|
|
1252
1295
|
}
|
|
1296
|
+
|
|
1297
|
+
/* ---------- \u9690\u85CF\u300C\u6DFB\u52A0\u5DE5\u4F5C\u533A\u300D\u5165\u53E3\uFF08\u624B\u673A\u4E0A\u914D\u5DE5\u4F5C\u533A\u65E0\u610F\u4E49\uFF0Cissue #17 \u4FEE\u6B63\uFF09 ----------
|
|
1298
|
+
\u56FE\u6807\u6309\u94AE\u7684 aria-label \u968F\u8BED\u8A00\u53D8\u5316\uFF08zh\u300C\u6DFB\u52A0\u5DE5\u4F5C\u533A\u300D/ en\u300CAdd workspace\u300D\uFF09\uFF0C
|
|
1299
|
+
\u4E24\u79CD\u90FD\u8986\u76D6\uFF1B\u4E0B\u62C9\u83DC\u5355\u91CC\u7684\u300C\u6DFB\u52A0\u5DE5\u4F5C\u533A\u2026\u300D\u9879\u7531 fileGuard.ts \u7684 MutationObserver
|
|
1300
|
+
\u6309\u6587\u6848\u515C\u5E95\u9690\u85CF\uFF08CSS \u9009\u4E0D\u5230\u7EAF\u6587\u672C\u8282\u70B9\uFF09\u3002\u53EA\u5728\u7A84\u5C4F\u751F\u6548\u2014\u2014\u684C\u9762\u7AEF\u7167\u5E38\u4FDD\u7559\u3002 */
|
|
1301
|
+
button[aria-label="\u6DFB\u52A0\u5DE5\u4F5C\u533A"],
|
|
1302
|
+
button[aria-label="\u6DFB\u52A0\u5DE5\u4F5C\u533A\u2026"],
|
|
1303
|
+
button[aria-label="Add workspace"],
|
|
1304
|
+
button[aria-label="Add workspace\u2026"] {
|
|
1305
|
+
display: none !important;
|
|
1306
|
+
}
|
|
1253
1307
|
}
|
|
1254
1308
|
|
|
1255
1309
|
/* ---------- desktop: the mobile controls must never appear ---------- */
|
|
@@ -1264,52 +1318,8 @@ var MOBILE_CSS = `
|
|
|
1264
1318
|
[data-mobile-nav="drawer-actions"] {
|
|
1265
1319
|
display: none !important;
|
|
1266
1320
|
}
|
|
1267
|
-
/* \u684C\u9762\u7AEF\u6C38\u8FDC\u4E0D\u9700\u8981\u300C\u26F6 \u653E\u5927\u8F93\u5165\u300D\u6309\u94AE\uFF08composer \u5DF2\u7ECF\u6709\u5B8C\u6574\u5DE5\u5177\u884C\uFF09 */
|
|
1268
|
-
[data-mobile-nav="composer-fullscreen"] {
|
|
1269
|
-
display: none !important;
|
|
1270
|
-
}
|
|
1271
1321
|
}
|
|
1272
1322
|
|
|
1273
|
-
/* ---------- \u653E\u5927\u8F93\u5165\uFF08issue #23\uFF09 ----------
|
|
1274
|
-
\u7A84\u5C4F\u9ED8\u8BA4\u9690\u85CF\u63D2\u4EF6\u6CE8\u518C\u5230 conversation.input.* slot \u7684 UI\uFF08\u4EC5\u7559\u5B98\u65B9 resident
|
|
1275
|
-
chrome\uFF09\uFF0C\u70B9\u300C\u26F6\u300D\u6309\u94AE\u628A composer \u5361\u7247\u56FA\u5B9A\u5230\u5168\u5C4F\u540E\u6240\u6709\u63D2\u4EF6 UI \u6062\u590D\u663E\u793A\u2014\u2014
|
|
1276
|
-
\u65B0\u63D2\u4EF6\u96F6\u9002\u914D\u3002
|
|
1277
|
-
\u5B9E\u73B0\u8BF4\u660E\uFF1A\u5F53\u524D dsh 0.1.1 \u6CA1\u628A input slot \u6E32\u67D3\u6210 [data-slot] \u5305\u88C5\uFF080.1.2+ \u624D\u6709\uFF09\uFF0C
|
|
1278
|
-
\u6240\u4EE5\u8FD9\u91CC\u7528\u300C\u767D\u540D\u5355\u300D\u53CD\u9009\uFF1AmobileApply \u6CE8\u518C\u7684 mobile \u81EA\u8EAB\u8282\u70B9\u52A0
|
|
1279
|
-
data-mobile-nav="composer-fullscreen" \u4E0D\u88AB\u9690\u85CF\uFF1B\u5176\u5B83\u63D2\u4EF6 UI \u9690\u85CF\u3002 */
|
|
1280
|
-
@media (max-width: 1023px) {
|
|
1281
|
-
/* \u9ED8\u8BA4\uFF08\u975E\u5168\u5C4F\uFF09\uFF1A\u9690\u85CF input slot \u5BB9\u5668\u4E0B\u7684\u6240\u6709\u76F4\u63A5\u5B50\u5143\u7D20\uFF08\u5305\u542B\u63D2\u4EF6\u548C\u5B98\u65B9\uFF09
|
|
1282
|
-
\u2014\u2014\u5B98\u65B9\u81EA\u5DF1\u5982\u679C\u60F3\u5728\u7A84\u5C4F\u4E5F\u9732\u51FA\uFF0C\u6CE8 input slot \u65F6\u52A0 data-mobile-nav-keep="1" */
|
|
1283
|
-
[data-dsh-pocket-composer-fullscreen]:not([data-dsh-pocket-composer-fullscreen="1"])
|
|
1284
|
-
[class$="_card"]:has(textarea) [data-slot^="conversation.input."] > :not([data-mobile-nav-keep]),
|
|
1285
|
-
[data-dsh-pocket-composer-fullscreen]:not([data-dsh-pocket-composer-fullscreen="1"])
|
|
1286
|
-
[class$="_card"]:has(textarea) [data-slot^="conversation.input."]:empty {
|
|
1287
|
-
display: none !important;
|
|
1288
|
-
}
|
|
1289
|
-
/* \u653E\u5927\u6309\u94AE\uFF1A\u5E38\u9A7B\u5728 conversation.input.right slot \u65C1\uFF0C\u684C\u9762\u7AEF\u5DF2\u88AB\u4E0A\u9762\u89C4\u5219\u9690\u85CF */
|
|
1290
|
-
[data-mobile-nav="composer-fullscreen"] {
|
|
1291
|
-
display: inline-flex;
|
|
1292
|
-
}
|
|
1293
|
-
/* \u5168\u5C4F\u6A21\u5F0F\uFF1Acomposer \u5361\u7247\u56FA\u5B9A\u5230\u89C6\u53E3\uFF0Ctextarea \u62C9\u9AD8\uFF0C\u5DE5\u5177\u884C\u53EF\u6362\u884C */
|
|
1294
|
-
[data-dsh-pocket-composer-fullscreen="1"] [class$="_card"]:has(textarea) {
|
|
1295
|
-
position: fixed !important;
|
|
1296
|
-
inset: 0 !important;
|
|
1297
|
-
z-index: 9999 !important;
|
|
1298
|
-
border-radius: 0 !important;
|
|
1299
|
-
margin: 0 !important;
|
|
1300
|
-
height: 100dvh !important;
|
|
1301
|
-
display: flex !important;
|
|
1302
|
-
flex-direction: column !important;
|
|
1303
|
-
}
|
|
1304
|
-
[data-dsh-pocket-composer-fullscreen="1"] [class$="_card"]:has(textarea) textarea {
|
|
1305
|
-
flex: 1 1 auto !important;
|
|
1306
|
-
min-height: 50dvh !important;
|
|
1307
|
-
max-height: none !important;
|
|
1308
|
-
}
|
|
1309
|
-
[data-dsh-pocket-composer-fullscreen="1"] [class$="_card"]:has(textarea) [data-slot^="conversation.input."] {
|
|
1310
|
-
flex-wrap: wrap !important;
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
1323
|
`;
|
|
1314
1324
|
|
|
1315
1325
|
// client/mobile/locales.ts
|
|
@@ -1525,6 +1535,11 @@ function mobileApply(ctx) {
|
|
|
1525
1535
|
observer.disconnect();
|
|
1526
1536
|
};
|
|
1527
1537
|
}, "dsh-mobile-nav: sheet rise animation replay");
|
|
1538
|
+
ctx.effect(() => {
|
|
1539
|
+
if (!narrow.matches) return () => {
|
|
1540
|
+
};
|
|
1541
|
+
return startFileGuard();
|
|
1542
|
+
}, "dsh-mobile-nav: file open guard + hide add-workspace (issue #17)");
|
|
1528
1543
|
ctx.slots.inject("conversation.session.header.actions", () => ctx.slots.register({
|
|
1529
1544
|
name: "conversation.session.header.actions",
|
|
1530
1545
|
id: "mobile-nav-toggle",
|
|
@@ -1534,12 +1549,6 @@ function mobileApply(ctx) {
|
|
|
1534
1549
|
toggleSidebar: () => ctx.layout.toggleSidebar()
|
|
1535
1550
|
})
|
|
1536
1551
|
}, MobileNavToggle));
|
|
1537
|
-
ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
|
|
1538
|
-
name: "conversation.input.right",
|
|
1539
|
-
id: "mobile-composer-fullscreen",
|
|
1540
|
-
order: 100,
|
|
1541
|
-
locale: NS
|
|
1542
|
-
}, MobileComposerFullscreen));
|
|
1543
1552
|
ctx.slots.inject("shell.overlay", () => ctx.slots.register({
|
|
1544
1553
|
name: "shell.overlay",
|
|
1545
1554
|
id: "mobile-nav-overlay",
|
|
@@ -1779,15 +1788,15 @@ var styles = {
|
|
|
1779
1788
|
warn: { color: "var(--dsw-alias-state-warn-primary,#b45309)", fontSize: 12, lineHeight: 1.5 }
|
|
1780
1789
|
};
|
|
1781
1790
|
function PocketSettingsTab({ rpcCall, t }) {
|
|
1782
|
-
const [status, setStatus] = (0,
|
|
1783
|
-
const [busy, setBusy] = (0,
|
|
1784
|
-
const [error, setError] = (0,
|
|
1785
|
-
const [tunnelState, setTunnelState] = (0,
|
|
1786
|
-
const [restartNotice, setRestartNotice] = (0,
|
|
1787
|
-
const [updateInfo, setUpdateInfo] = (0,
|
|
1788
|
-
const [isDesktop, setIsDesktop] = (0,
|
|
1789
|
-
const [now, setNow] = (0,
|
|
1790
|
-
(0,
|
|
1791
|
+
const [status, setStatus] = (0, import_react2.useState)(null);
|
|
1792
|
+
const [busy, setBusy] = (0, import_react2.useState)(false);
|
|
1793
|
+
const [error, setError] = (0, import_react2.useState)(null);
|
|
1794
|
+
const [tunnelState, setTunnelState] = (0, import_react2.useState)(null);
|
|
1795
|
+
const [restartNotice, setRestartNotice] = (0, import_react2.useState)(false);
|
|
1796
|
+
const [updateInfo, setUpdateInfo] = (0, import_react2.useState)(null);
|
|
1797
|
+
const [isDesktop, setIsDesktop] = (0, import_react2.useState)(false);
|
|
1798
|
+
const [now, setNow] = (0, import_react2.useState)(Date.now());
|
|
1799
|
+
(0, import_react2.useEffect)(() => {
|
|
1791
1800
|
const t2 = setInterval(() => setNow(Date.now()), 1e3);
|
|
1792
1801
|
return () => clearInterval(t2);
|
|
1793
1802
|
}, []);
|
|
@@ -1819,18 +1828,18 @@ function PocketSettingsTab({ rpcCall, t }) {
|
|
|
1819
1828
|
} catch {
|
|
1820
1829
|
}
|
|
1821
1830
|
};
|
|
1822
|
-
(0,
|
|
1831
|
+
(0, import_react2.useEffect)(() => {
|
|
1823
1832
|
load();
|
|
1824
1833
|
const t2 = setInterval(load, 3e3);
|
|
1825
1834
|
return () => clearInterval(t2);
|
|
1826
1835
|
}, []);
|
|
1827
|
-
(0,
|
|
1836
|
+
(0, import_react2.useEffect)(() => {
|
|
1828
1837
|
try {
|
|
1829
1838
|
sessionStorage.removeItem("dshp-auto-reloaded");
|
|
1830
1839
|
} catch {
|
|
1831
1840
|
}
|
|
1832
1841
|
}, []);
|
|
1833
|
-
(0,
|
|
1842
|
+
(0, import_react2.useEffect)(() => {
|
|
1834
1843
|
if (isDesktop) return;
|
|
1835
1844
|
let alive = true;
|
|
1836
1845
|
const check = async () => {
|
|
@@ -1886,8 +1895,8 @@ function PocketSettingsTab({ rpcCall, t }) {
|
|
|
1886
1895
|
setUpdateInfo((u) => ({ ...u, updating: false, result: "fail", output: err.message }));
|
|
1887
1896
|
}
|
|
1888
1897
|
};
|
|
1889
|
-
const [disclaimerOpen, setDisclaimerOpen] = (0,
|
|
1890
|
-
const [disclaimerChecked, setDisclaimerChecked] = (0,
|
|
1898
|
+
const [disclaimerOpen, setDisclaimerOpen] = (0, import_react2.useState)(false);
|
|
1899
|
+
const [disclaimerChecked, setDisclaimerChecked] = (0, import_react2.useState)(false);
|
|
1891
1900
|
const doStartTunnel = async () => {
|
|
1892
1901
|
const cfg = status?.tunnelConfig;
|
|
1893
1902
|
if (cfg?.mode === "named" && (!cfg.hostname || !cfg.tokenSet)) {
|
|
@@ -1920,7 +1929,7 @@ function PocketSettingsTab({ rpcCall, t }) {
|
|
|
1920
1929
|
} catch {
|
|
1921
1930
|
}
|
|
1922
1931
|
};
|
|
1923
|
-
const [tunnelCfg, setTunnelCfg] = (0,
|
|
1932
|
+
const [tunnelCfg, setTunnelCfg] = (0, import_react2.useState)(null);
|
|
1924
1933
|
const switchToQuick = async () => {
|
|
1925
1934
|
try {
|
|
1926
1935
|
setStatus(await call(POCKET_ENDPOINTS.tunnelSetConfig, { mode: "quick" }));
|
|
@@ -1941,7 +1950,7 @@ function PocketSettingsTab({ rpcCall, t }) {
|
|
|
1941
1950
|
setTunnelCfg((c) => ({ ...c, err: err.message }));
|
|
1942
1951
|
}
|
|
1943
1952
|
};
|
|
1944
|
-
const [resetOpen, setResetOpen] = (0,
|
|
1953
|
+
const [resetOpen, setResetOpen] = (0, import_react2.useState)(false);
|
|
1945
1954
|
const doFactoryReset = async () => {
|
|
1946
1955
|
setResetOpen(false);
|
|
1947
1956
|
setBusy(true);
|
|
@@ -1973,7 +1982,7 @@ function PocketSettingsTab({ rpcCall, t }) {
|
|
|
1973
1982
|
} catch {
|
|
1974
1983
|
}
|
|
1975
1984
|
};
|
|
1976
|
-
const [lanToggleOpen, setLanToggleOpen] = (0,
|
|
1985
|
+
const [lanToggleOpen, setLanToggleOpen] = (0, import_react2.useState)(null);
|
|
1977
1986
|
const requestLanToggle = (on) => setLanToggleOpen(on);
|
|
1978
1987
|
const confirmLanToggle = async () => {
|
|
1979
1988
|
const on = lanToggleOpen;
|
|
@@ -1993,7 +2002,7 @@ function PocketSettingsTab({ rpcCall, t }) {
|
|
|
1993
2002
|
setError(err.message);
|
|
1994
2003
|
}
|
|
1995
2004
|
};
|
|
1996
|
-
const [customPin, setCustomPin] = (0,
|
|
2005
|
+
const [customPin, setCustomPin] = (0, import_react2.useState)(null);
|
|
1997
2006
|
const saveCustomPin = async (which) => {
|
|
1998
2007
|
try {
|
|
1999
2008
|
const r = await call(POCKET_ENDPOINTS.pinSetCustom, { which, value: customPin?.value ?? "" });
|
|
@@ -2009,11 +2018,11 @@ function PocketSettingsTab({ rpcCall, t }) {
|
|
|
2009
2018
|
setCustomPin((c) => ({ ...c, err: err.message }));
|
|
2010
2019
|
}
|
|
2011
2020
|
};
|
|
2012
|
-
const customPinRow = (which) => (0,
|
|
2021
|
+
const customPinRow = (which) => (0, import_react2.createElement)(
|
|
2013
2022
|
"div",
|
|
2014
2023
|
{ style: { marginTop: 6, fontSize: 12, color: "var(--dsw-alias-label-secondary,#6b7280)", lineHeight: 1.5 } },
|
|
2015
2024
|
t("customizing"),
|
|
2016
|
-
(0,
|
|
2025
|
+
(0, import_react2.createElement)("input", {
|
|
2017
2026
|
style: { width: 130, margin: "0 6px", padding: "4px 8px", fontSize: 14, letterSpacing: 1, textAlign: "center", border: "1px solid var(--dsw-alias-border-l2,#d1d5db)", borderRadius: 6, outline: "none" },
|
|
2018
2027
|
type: "password",
|
|
2019
2028
|
maxLength: 8,
|
|
@@ -2025,11 +2034,11 @@ function PocketSettingsTab({ rpcCall, t }) {
|
|
|
2025
2034
|
if (e.key === "Escape") setCustomPin(null);
|
|
2026
2035
|
}
|
|
2027
2036
|
}),
|
|
2028
|
-
(0,
|
|
2029
|
-
(0,
|
|
2030
|
-
customPin?.err ? (0,
|
|
2037
|
+
(0, import_react2.createElement)("button", { style: { ...styles.btn, height: 26, padding: "0 10px", fontSize: 12, marginLeft: 2 }, onClick: () => saveCustomPin(which) }, t("save")),
|
|
2038
|
+
(0, import_react2.createElement)("button", { style: { ...styles.btn, height: 26, padding: "0 10px", fontSize: 12 }, onClick: () => setCustomPin(null) }, t("cancel")),
|
|
2039
|
+
customPin?.err ? (0, import_react2.createElement)("div", { style: { color: "var(--dsw-alias-state-error-primary,#dc2626)", marginTop: 4 } }, errText(customPin.err)) : null
|
|
2031
2040
|
);
|
|
2032
|
-
const customBtn = (which) => (0,
|
|
2041
|
+
const customBtn = (which) => (0, import_react2.createElement)("button", { style: { ...styles.btn, height: 26, padding: "0 10px", fontSize: 12, marginLeft: 8 }, onClick: () => setCustomPin({ which, value: "", err: null }) }, t("customize"));
|
|
2033
2042
|
const lanUrl = status?.lanUrl;
|
|
2034
2043
|
const tunnelUrl = status?.tunnelUrl;
|
|
2035
2044
|
const tunnelPhase = tunnelState?.phase ?? "idle";
|
|
@@ -2045,14 +2054,14 @@ function PocketSettingsTab({ rpcCall, t }) {
|
|
|
2045
2054
|
if (i < 0) return s;
|
|
2046
2055
|
return (t("ok") === zh2.ok ? s.slice(0, i) : s.slice(i + 3)).trim();
|
|
2047
2056
|
};
|
|
2048
|
-
const [toast, setToast] = (0,
|
|
2049
|
-
const toastTimer = (0,
|
|
2057
|
+
const [toast, setToast] = (0, import_react2.useState)(null);
|
|
2058
|
+
const toastTimer = (0, import_react2.useRef)(null);
|
|
2050
2059
|
const showToast = (text) => {
|
|
2051
2060
|
setToast(text);
|
|
2052
2061
|
clearTimeout(toastTimer.current);
|
|
2053
2062
|
toastTimer.current = setTimeout(() => setToast(null), 2600);
|
|
2054
2063
|
};
|
|
2055
|
-
(0,
|
|
2064
|
+
(0, import_react2.useEffect)(() => () => clearTimeout(toastTimer.current), []);
|
|
2056
2065
|
const modeBtnStyle = (active) => ({
|
|
2057
2066
|
...styles.btn,
|
|
2058
2067
|
height: 28,
|
|
@@ -2062,49 +2071,49 @@ function PocketSettingsTab({ rpcCall, t }) {
|
|
|
2062
2071
|
background: active ? "var(--dsw-alias-button-primary-fill, var(--dsw-alias-brand-primary,#4f6ef7))" : "var(--dsw-alias-bg-layer-1,#fff)",
|
|
2063
2072
|
color: active ? "var(--dsw-alias-label-primary-foreground, #fff)" : "var(--dsw-alias-label-primary,inherit)"
|
|
2064
2073
|
});
|
|
2065
|
-
const Switch = (on, onClick) => (0,
|
|
2074
|
+
const Switch = (on, onClick) => (0, import_react2.createElement)("button", {
|
|
2066
2075
|
role: "switch",
|
|
2067
2076
|
"aria-checked": !!on,
|
|
2068
2077
|
style: { flexShrink: 0, width: 40, height: 22, borderRadius: 11, border: "none", padding: 0, position: "relative", cursor: "pointer", font: "inherit", background: on ? "var(--dsw-alias-button-primary-fill, var(--dsw-alias-brand-primary,#4f6ef7))" : "var(--dsw-alias-border-l2,#d1d5db)" },
|
|
2069
2078
|
onClick
|
|
2070
|
-
}, (0,
|
|
2071
|
-
const qrArea = (src, url, hint) => (0,
|
|
2079
|
+
}, (0, import_react2.createElement)("span", { style: { position: "absolute", top: 2, left: on ? 20 : 2, width: 18, height: 18, borderRadius: "50%", background: "#fff" } }));
|
|
2080
|
+
const qrArea = (src, url, hint) => (0, import_react2.createElement)(
|
|
2072
2081
|
"div",
|
|
2073
2082
|
{ style: { background: "var(--dsw-alias-bg-layer-2,#f3f4f6)", borderRadius: 10, padding: "10px 12px", textAlign: "center", margin: "10px 0" } },
|
|
2074
|
-
(0,
|
|
2075
|
-
(0,
|
|
2076
|
-
(0,
|
|
2083
|
+
(0, import_react2.createElement)("img", { src, alt: "QR", style: styles.qr }),
|
|
2084
|
+
(0, import_react2.createElement)("div", { style: styles.code }, url),
|
|
2085
|
+
(0, import_react2.createElement)("div", { style: styles.muted }, hint)
|
|
2077
2086
|
);
|
|
2078
|
-
const row = (label, control, extra) => (0,
|
|
2087
|
+
const row = (label, control, extra) => (0, import_react2.createElement)(
|
|
2079
2088
|
"div",
|
|
2080
2089
|
{ style: { borderTop: "1px solid var(--dsw-alias-border-l2,#e5e7eb)", paddingTop: 9, marginTop: 9 } },
|
|
2081
|
-
(0,
|
|
2090
|
+
(0, import_react2.createElement)(
|
|
2082
2091
|
"div",
|
|
2083
2092
|
{ style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8 } },
|
|
2084
|
-
(0,
|
|
2093
|
+
(0, import_react2.createElement)("span", { style: { fontSize: 13 } }, label),
|
|
2085
2094
|
control
|
|
2086
2095
|
),
|
|
2087
2096
|
extra ?? null
|
|
2088
2097
|
);
|
|
2089
|
-
const [advOpen, setAdvOpen] = (0,
|
|
2090
|
-
return (0,
|
|
2098
|
+
const [advOpen, setAdvOpen] = (0, import_react2.useState)(false);
|
|
2099
|
+
return (0, import_react2.createElement)(
|
|
2091
2100
|
"div",
|
|
2092
2101
|
{ style: styles.card },
|
|
2093
|
-
(0,
|
|
2102
|
+
(0, import_react2.createElement)(
|
|
2094
2103
|
"div",
|
|
2095
2104
|
{ style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8 } },
|
|
2096
|
-
(0,
|
|
2105
|
+
(0, import_react2.createElement)(
|
|
2097
2106
|
"div",
|
|
2098
2107
|
null,
|
|
2099
|
-
(0,
|
|
2100
|
-
(0,
|
|
2108
|
+
(0, import_react2.createElement)("strong", null, t("title")),
|
|
2109
|
+
(0, import_react2.createElement)("div", { style: styles.muted }, t("subtitle"))
|
|
2101
2110
|
),
|
|
2102
|
-
(0,
|
|
2111
|
+
(0, import_react2.createElement)(
|
|
2103
2112
|
"div",
|
|
2104
2113
|
{ style: { fontSize: 12, color: "var(--dsw-alias-label-tertiary,#8b93a1)", textAlign: "right" } },
|
|
2105
|
-
(0,
|
|
2106
|
-
(0,
|
|
2107
|
-
(0,
|
|
2114
|
+
(0, import_react2.createElement)("div", { style: { whiteSpace: "nowrap" } }, t("developer")),
|
|
2115
|
+
(0, import_react2.createElement)("div", { style: { whiteSpace: "nowrap" } }, t("starAsk")),
|
|
2116
|
+
(0, import_react2.createElement)(
|
|
2108
2117
|
"a",
|
|
2109
2118
|
{ href: "https://github.com/shaobeichen/dsh-pocket", target: "_blank", rel: "noreferrer", style: { color: "var(--dsw-alias-brand-primary,#4f6ef7)", fontSize: 12, lineHeight: 1.6, textDecoration: "underline" } },
|
|
2110
2119
|
t("starCta")
|
|
@@ -2113,49 +2122,49 @@ function PocketSettingsTab({ rpcCall, t }) {
|
|
|
2113
2122
|
),
|
|
2114
2123
|
// 桌面端不显示更新/重启横幅(更新由 DSH Desktop 管理),也不需要额外提示
|
|
2115
2124
|
// 重启后提示(进程在后台运行,停止方法)——左侧蓝色色条(桌面端不会触发本插件的自重启)
|
|
2116
|
-
!isDesktop && restartNotice ? (0,
|
|
2125
|
+
!isDesktop && restartNotice ? (0, import_react2.createElement)(
|
|
2117
2126
|
"div",
|
|
2118
2127
|
{ style: { ...styles.block, borderLeft: "4px solid var(--dsw-alias-brand-primary,#4f6ef7)", borderRadius: 8, background: "var(--dsw-alias-bg-layer-2,#f3f4f6)", padding: "10px 12px" } },
|
|
2119
|
-
(0,
|
|
2128
|
+
(0, import_react2.createElement)(
|
|
2120
2129
|
"div",
|
|
2121
2130
|
{ style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8 } },
|
|
2122
|
-
(0,
|
|
2123
|
-
(0,
|
|
2131
|
+
(0, import_react2.createElement)("div", { style: { fontWeight: 600, fontSize: 13 } }, t("restarted")),
|
|
2132
|
+
(0, import_react2.createElement)("button", { style: styles.btn, onClick: () => setRestartNotice(false) }, t("ok"))
|
|
2124
2133
|
),
|
|
2125
|
-
(0,
|
|
2134
|
+
(0, import_react2.createElement)("div", { style: styles.muted, marginTop: 4, wordBreak: "break-all" }, fmt(t, "bgHint", { cmd: status?.killHint ?? `lsof -ti :${status?.dshPort ?? 3080} | xargs kill -9` }))
|
|
2126
2135
|
) : null,
|
|
2127
2136
|
// 更新提示——左侧黄色色条(提示有新版本);单状态:有更新/更新中/已更新自动重启,不并存
|
|
2128
2137
|
// 桌面端不渲染(更新由 DSH Desktop 管理)
|
|
2129
|
-
!isDesktop && updateInfo ? (0,
|
|
2138
|
+
!isDesktop && updateInfo ? (0, import_react2.createElement)(
|
|
2130
2139
|
"div",
|
|
2131
2140
|
{ style: { ...styles.block, borderLeft: "4px solid var(--dsw-alias-state-warn-primary,#b45309)", borderRadius: 8, background: "var(--dsw-alias-bg-layer-2,#f3f4f6)", padding: "10px 12px" } },
|
|
2132
|
-
(0,
|
|
2141
|
+
(0, import_react2.createElement)(
|
|
2133
2142
|
"div",
|
|
2134
2143
|
{ style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8 } },
|
|
2135
|
-
(0,
|
|
2144
|
+
(0, import_react2.createElement)(
|
|
2136
2145
|
"div",
|
|
2137
2146
|
{ style: { fontWeight: 600, fontSize: 13 } },
|
|
2138
2147
|
updateInfo.updated ? fmt(t, "updatedRestart", { ver: updateInfo.current }) : updateInfo.result === "ok" ? updateInfo.autoRestart ? fmt(t, "updateAutoRestarting", { ver: updateInfo.latest }) : fmt(t, "updatedOk", { ver: updateInfo.latest }) : fmt(t, "updateAvailable", { ver: updateInfo.latest })
|
|
2139
2148
|
),
|
|
2140
|
-
updateInfo.result !== "ok" ? (0,
|
|
2149
|
+
updateInfo.result !== "ok" ? (0, import_react2.createElement)("button", { style: styles.primary, onClick: runUpdate, disabled: updateInfo.updating }, updateInfo.updating ? t("updating") : fmt(t, "updateTo", { ver: updateInfo.latest })) : updateInfo.autoRestart ? (0, import_react2.createElement)("button", { style: styles.btn, disabled: true }, t("restartingNow")) : (0, import_react2.createElement)("button", { style: styles.primary, onClick: restartPocket, disabled: updateInfo.restarting }, updateInfo.restarting ? t("restarting") : t("restartNow"))
|
|
2141
2150
|
),
|
|
2142
|
-
(0,
|
|
2151
|
+
(0, import_react2.createElement)(
|
|
2143
2152
|
"div",
|
|
2144
2153
|
{ style: styles.muted, marginTop: 4 },
|
|
2145
2154
|
updateInfo.updating ? fmt(t, "updatingDetail", { s: elapsed(updateInfo.startedAt) }) : updateInfo.restarting ? fmt(t, "restartingDetail", { s: elapsed(updateInfo.startedAt) }) : updateInfo.result === "ok" ? updateInfo.autoRestart ? t("updatedAutoDetail") : t("updatedRestartDetail") : updateInfo.result === "fail" ? fmt(t, "updateFailed", { err: errText(updateInfo.output) || t("unknownError") }) : fmt(t, "versionRange", { cur: updateInfo.current, latest: updateInfo.latest })
|
|
2146
2155
|
)
|
|
2147
2156
|
) : null,
|
|
2148
2157
|
// 局域网:标题行自带总开关 → 二维码+地址 → 设置行(访问密码 / 高级·手动选地址)
|
|
2149
|
-
(0,
|
|
2158
|
+
(0, import_react2.createElement)(
|
|
2150
2159
|
"div",
|
|
2151
2160
|
{ style: styles.block },
|
|
2152
|
-
(0,
|
|
2161
|
+
(0, import_react2.createElement)(
|
|
2153
2162
|
"div",
|
|
2154
2163
|
{ style: { display: "flex", alignItems: "center", justifyContent: "space-between" } },
|
|
2155
|
-
(0,
|
|
2164
|
+
(0, import_react2.createElement)("span", { style: { fontWeight: 600, fontSize: 13 } }, t("lanAccess")),
|
|
2156
2165
|
Switch(status?.lanEnabled !== false, () => requestLanToggle(status?.lanEnabled === false))
|
|
2157
2166
|
),
|
|
2158
|
-
status?.lanEnabled === false ? (0,
|
|
2167
|
+
status?.lanEnabled === false ? (0, import_react2.createElement)("div", { style: { marginTop: 8, fontSize: 12, color: "var(--dsw-alias-state-warn-primary,#b45309)", lineHeight: 1.5 } }, t("lanDisabledHint")) : lanUrl ? (0, import_react2.createElement)(
|
|
2159
2168
|
"div",
|
|
2160
2169
|
null,
|
|
2161
2170
|
qrArea(status.lanQr, lanUrl, t("lanHint")),
|
|
@@ -2163,100 +2172,100 @@ function PocketSettingsTab({ rpcCall, t }) {
|
|
|
2163
2172
|
row(
|
|
2164
2173
|
t("lanPin"),
|
|
2165
2174
|
Switch(status?.lanAuthEnabled !== false, () => setLanAuth(status?.lanAuthEnabled === false)),
|
|
2166
|
-
status?.lanAuthEnabled === false ? (0,
|
|
2175
|
+
status?.lanAuthEnabled === false ? (0, import_react2.createElement)("div", { style: { ...styles.muted, marginTop: 6 } }, t("lanPinOff")) : customPin?.which === "lan" ? customPinRow("lan") : (0, import_react2.createElement)(
|
|
2167
2176
|
"div",
|
|
2168
2177
|
{ style: { marginTop: 6, display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" } },
|
|
2169
|
-
(0,
|
|
2170
|
-
(0,
|
|
2178
|
+
(0, import_react2.createElement)("span", { style: { fontFamily: "ui-monospace,Menlo,monospace", fontSize: 13, letterSpacing: 1 } }, status.lanToken),
|
|
2179
|
+
(0, import_react2.createElement)("button", { style: { ...styles.btn, height: 26, padding: "0 10px", fontSize: 12 }, onClick: refreshLanPin }, t("refresh")),
|
|
2171
2180
|
customBtn("lan"),
|
|
2172
|
-
status?.lanPinCustom ? (0,
|
|
2181
|
+
status?.lanPinCustom ? (0, import_react2.createElement)("span", { style: { fontSize: 11, color: "var(--dsw-alias-state-warn-primary,#b45309)" } }, t("pinCustomHint")) : null
|
|
2173
2182
|
)
|
|
2174
2183
|
),
|
|
2175
2184
|
// 高级:手动选地址(默认收起)
|
|
2176
2185
|
row(
|
|
2177
2186
|
t("advAddress"),
|
|
2178
|
-
(0,
|
|
2187
|
+
(0, import_react2.createElement)(
|
|
2179
2188
|
"button",
|
|
2180
2189
|
{ style: { border: "none", background: "none", font: "inherit", cursor: "pointer", fontSize: 12, color: "var(--dsw-alias-label-tertiary,#8b93a1)", padding: 0 }, onClick: () => setAdvOpen((v) => !v) },
|
|
2181
2190
|
(status?.lanIpOverride || t("lanAddressAuto")) + " \u203A"
|
|
2182
2191
|
),
|
|
2183
|
-
advOpen ? (0,
|
|
2192
|
+
advOpen ? (0, import_react2.createElement)(
|
|
2184
2193
|
"div",
|
|
2185
2194
|
{ style: { marginTop: 8 } },
|
|
2186
|
-
(0,
|
|
2195
|
+
(0, import_react2.createElement)(
|
|
2187
2196
|
"label",
|
|
2188
2197
|
{ style: { display: "flex", alignItems: "center", gap: 8, fontSize: 12, color: "var(--dsw-alias-label-secondary,#6b7280)" } },
|
|
2189
2198
|
t("lanAddress"),
|
|
2190
|
-
(0,
|
|
2199
|
+
(0, import_react2.createElement)(
|
|
2191
2200
|
"select",
|
|
2192
2201
|
{
|
|
2193
2202
|
value: status?.lanIpOverride || "",
|
|
2194
2203
|
onChange: (e) => setLanAddress(e.target.value),
|
|
2195
2204
|
style: { font: "inherit", height: 30, padding: "0 8px", borderRadius: 8, border: "1px solid var(--dsw-alias-border-l2,#d1d5db)", background: "var(--dsw-alias-bg-layer-1,#fff)", color: "var(--dsw-alias-label-primary,inherit)" }
|
|
2196
2205
|
},
|
|
2197
|
-
(0,
|
|
2198
|
-
(status?.lanCandidates || []).map((ip) => (0,
|
|
2206
|
+
(0, import_react2.createElement)("option", { value: "" }, t("lanAddressAuto")),
|
|
2207
|
+
(status?.lanCandidates || []).map((ip) => (0, import_react2.createElement)("option", { key: ip, value: ip }, ip))
|
|
2199
2208
|
)
|
|
2200
2209
|
)
|
|
2201
2210
|
) : null
|
|
2202
2211
|
)
|
|
2203
|
-
) : (0,
|
|
2212
|
+
) : (0, import_react2.createElement)("div", { style: styles.muted }, t("lanStarting"))
|
|
2204
2213
|
),
|
|
2205
2214
|
// 公网:标题行自带 开启/关闭 → 开启后:二维码+地址、地址模式行、访问密码行
|
|
2206
|
-
(0,
|
|
2215
|
+
(0, import_react2.createElement)(
|
|
2207
2216
|
"div",
|
|
2208
2217
|
{ style: styles.block },
|
|
2209
|
-
(0,
|
|
2218
|
+
(0, import_react2.createElement)(
|
|
2210
2219
|
"div",
|
|
2211
2220
|
{ style: { display: "flex", alignItems: "center", justifyContent: "space-between" } },
|
|
2212
|
-
(0,
|
|
2213
|
-
tunnelUrl ? (0,
|
|
2221
|
+
(0, import_react2.createElement)("span", { style: { fontWeight: 600, fontSize: 13 } }, t("wanAccess")),
|
|
2222
|
+
tunnelUrl ? (0, import_react2.createElement)("button", { style: { ...styles.btn, height: 28, padding: "0 12px", fontSize: 12, color: "var(--dsw-alias-state-error-primary,#dc2626)" }, onClick: stopTunnel }, t("stopTunnel")) : (0, import_react2.createElement)("button", { style: { ...styles.primary, height: 28, padding: "0 14px", fontSize: 12 }, onClick: startTunnel, disabled: busy || tunnelStarting }, busy || tunnelStarting ? t("opening") : t("enable"))
|
|
2214
2223
|
),
|
|
2215
|
-
tunnelStarting ? (0,
|
|
2224
|
+
tunnelStarting ? (0, import_react2.createElement)(
|
|
2216
2225
|
"div",
|
|
2217
2226
|
{ style: { marginTop: 8, fontSize: 12, color: "var(--dsw-alias-label-secondary,#6b7280)" } },
|
|
2218
2227
|
tunnelPhase === "downloading" ? fmt(t, "downloading", { s: elapsed(tunnelStateStarted) }) : fmt(t, "connecting", { s: elapsed(tunnelStateStarted), suffix: elapsed(tunnelStateStarted) > 30 ? t("slowHint") : "" })
|
|
2219
|
-
) : tunnelPhase === "error" ? (0,
|
|
2228
|
+
) : tunnelPhase === "error" ? (0, import_react2.createElement)(
|
|
2220
2229
|
"div",
|
|
2221
2230
|
{ style: { marginTop: 8, fontSize: 12, color: "var(--dsw-alias-state-error-primary,#dc2626)" } },
|
|
2222
2231
|
fmt(t, "error", { detail: errText(tunnelStateDetail) || t("unknownError") })
|
|
2223
|
-
) : !tunnelUrl && !isDesktop ? (0,
|
|
2224
|
-
tunnelUrl ? (0,
|
|
2232
|
+
) : !tunnelUrl && !isDesktop ? (0, import_react2.createElement)("div", { style: { ...styles.muted, marginTop: 8 } }, t("wanOffHint")) : null,
|
|
2233
|
+
tunnelUrl ? (0, import_react2.createElement)(
|
|
2225
2234
|
"div",
|
|
2226
2235
|
null,
|
|
2227
2236
|
qrArea(status.tunnelQr, tunnelUrl, namedMode ? t("namedRunningHint") : t("wanHint")),
|
|
2228
2237
|
// 地址模式行(随机/固定;固定域名选中或编辑时高亮)
|
|
2229
2238
|
row(
|
|
2230
2239
|
t("modeLabel"),
|
|
2231
|
-
(0,
|
|
2240
|
+
(0, import_react2.createElement)(
|
|
2232
2241
|
"span",
|
|
2233
2242
|
{ style: { display: "inline-flex", gap: 6 } },
|
|
2234
|
-
(0,
|
|
2235
|
-
(0,
|
|
2243
|
+
(0, import_react2.createElement)("button", { style: modeBtnStyle(!namedActive), onClick: namedMode ? switchToQuick : tunnelCfg ? () => setTunnelCfg(null) : void 0 }, t("modeQuick")),
|
|
2244
|
+
(0, import_react2.createElement)("button", { style: modeBtnStyle(namedActive), onClick: () => setTunnelCfg(tunnelCfg ? null : { hostname: tunnelModeView.hostname ?? "", token: "", err: null }) }, t("modeNamed"))
|
|
2236
2245
|
),
|
|
2237
|
-
(0,
|
|
2246
|
+
(0, import_react2.createElement)(
|
|
2238
2247
|
"div",
|
|
2239
2248
|
{ style: { marginTop: 6 } },
|
|
2240
2249
|
// 刚保存固定域名但当前连接仍是随机域名:需关闭后重新开启才生效
|
|
2241
|
-
namedMode && /trycloudflare\.com/i.test(tunnelUrl ?? "") ? (0,
|
|
2250
|
+
namedMode && /trycloudflare\.com/i.test(tunnelUrl ?? "") ? (0, import_react2.createElement)("div", { style: { ...styles.warn } }, t("namedTakeEffect")) : null,
|
|
2242
2251
|
// 固定域名:已保存摘要 + 修改入口(非编辑态)
|
|
2243
|
-
namedMode && !tunnelCfg ? (0,
|
|
2252
|
+
namedMode && !tunnelCfg ? (0, import_react2.createElement)(
|
|
2244
2253
|
"div",
|
|
2245
2254
|
{ style: { ...styles.muted } },
|
|
2246
2255
|
fmt(t, "namedSummary", { host: tunnelModeView.hostname || "\u2014", token: tunnelModeView.tokenSet ? t("namedTokenSet") : t("namedTokenMissing") }),
|
|
2247
|
-
(0,
|
|
2248
|
-
(0,
|
|
2249
|
-
!tunnelModeView.tokenSet || !tunnelModeView.hostname ? (0,
|
|
2256
|
+
(0, import_react2.createElement)("button", { style: { ...styles.btn, height: 26, padding: "0 10px", fontSize: 12, marginLeft: 8 }, onClick: () => setTunnelCfg({ hostname: tunnelModeView.hostname ?? "", token: "", err: null }) }, t("namedEdit")),
|
|
2257
|
+
(0, import_react2.createElement)("div", { style: { ...styles.muted, marginTop: 4 } }, t("namedHow")),
|
|
2258
|
+
!tunnelModeView.tokenSet || !tunnelModeView.hostname ? (0, import_react2.createElement)("div", { style: { marginTop: 2, color: "var(--dsw-alias-state-error-primary,#dc2626)" } }, t("namedNeedCfg")) : null
|
|
2250
2259
|
) : null,
|
|
2251
2260
|
// 固定域名:编辑表单(域名 + Tunnel Token,Token 留空保持不变)
|
|
2252
|
-
tunnelCfg ? (0,
|
|
2261
|
+
tunnelCfg ? (0, import_react2.createElement)(
|
|
2253
2262
|
"div",
|
|
2254
2263
|
{ style: { marginTop: 6, fontSize: 12, color: "var(--dsw-alias-label-secondary,#6b7280)", lineHeight: 1.6 } },
|
|
2255
|
-
(0,
|
|
2264
|
+
(0, import_react2.createElement)(
|
|
2256
2265
|
"div",
|
|
2257
2266
|
null,
|
|
2258
2267
|
t("namedHostnameLabel"),
|
|
2259
|
-
(0,
|
|
2268
|
+
(0, import_react2.createElement)("input", {
|
|
2260
2269
|
style: { margin: "4px 0 0 6px", padding: "4px 8px", fontSize: 13, border: "1px solid var(--dsw-alias-border-l2,#d1d5db)", borderRadius: 6, outline: "none", width: 200 },
|
|
2261
2270
|
placeholder: "pocket.example.com",
|
|
2262
2271
|
value: tunnelCfg.hostname ?? "",
|
|
@@ -2268,11 +2277,11 @@ function PocketSettingsTab({ rpcCall, t }) {
|
|
|
2268
2277
|
}
|
|
2269
2278
|
})
|
|
2270
2279
|
),
|
|
2271
|
-
(0,
|
|
2280
|
+
(0, import_react2.createElement)(
|
|
2272
2281
|
"div",
|
|
2273
2282
|
{ style: { marginTop: 6 } },
|
|
2274
2283
|
t("namedTokenLabel"),
|
|
2275
|
-
(0,
|
|
2284
|
+
(0, import_react2.createElement)("input", {
|
|
2276
2285
|
style: { margin: "4px 0 0 6px", padding: "4px 8px", fontSize: 13, border: "1px solid var(--dsw-alias-border-l2,#d1d5db)", borderRadius: 6, outline: "none", width: 240, fontFamily: "ui-monospace,Menlo,monospace" },
|
|
2277
2286
|
type: "password",
|
|
2278
2287
|
value: tunnelCfg.token ?? "",
|
|
@@ -2283,121 +2292,121 @@ function PocketSettingsTab({ rpcCall, t }) {
|
|
|
2283
2292
|
}
|
|
2284
2293
|
})
|
|
2285
2294
|
),
|
|
2286
|
-
(0,
|
|
2295
|
+
(0, import_react2.createElement)(
|
|
2287
2296
|
"div",
|
|
2288
2297
|
{ style: { marginTop: 6, display: "flex", gap: 8 } },
|
|
2289
|
-
(0,
|
|
2290
|
-
(0,
|
|
2298
|
+
(0, import_react2.createElement)("button", { style: { ...styles.btn, height: 26, padding: "0 10px", fontSize: 12 }, onClick: saveNamedTunnel }, t("save")),
|
|
2299
|
+
(0, import_react2.createElement)("button", { style: { ...styles.btn, height: 26, padding: "0 10px", fontSize: 12 }, onClick: () => setTunnelCfg(null) }, t("cancel"))
|
|
2291
2300
|
),
|
|
2292
|
-
(0,
|
|
2293
|
-
(0,
|
|
2294
|
-
tunnelCfg.err ? (0,
|
|
2301
|
+
(0, import_react2.createElement)("div", { style: { ...styles.muted, marginTop: 6 } }, t("namedHow")),
|
|
2302
|
+
(0, import_react2.createElement)("div", { style: { marginTop: 2, fontSize: 11, color: "var(--dsw-alias-state-warn-primary,#b45309)", lineHeight: 1.5 } }, t("namedSecurity")),
|
|
2303
|
+
tunnelCfg.err ? (0, import_react2.createElement)("div", { style: { color: "var(--dsw-alias-state-error-primary,#dc2626)", marginTop: 4 } }, errText(tunnelCfg.err)) : null
|
|
2295
2304
|
) : null
|
|
2296
2305
|
)
|
|
2297
2306
|
),
|
|
2298
2307
|
// 访问密码行:值 + 自定义(自定义输入态整体替换)
|
|
2299
2308
|
status.accessToken ? row(
|
|
2300
2309
|
t("pinLabel"),
|
|
2301
|
-
customPin?.which === "public" ? null : (0,
|
|
2310
|
+
customPin?.which === "public" ? null : (0, import_react2.createElement)(
|
|
2302
2311
|
"span",
|
|
2303
2312
|
{ style: { display: "inline-flex", alignItems: "center", gap: 8 } },
|
|
2304
|
-
(0,
|
|
2313
|
+
(0, import_react2.createElement)("span", { style: { fontFamily: "ui-monospace,Menlo,monospace", fontSize: 13, letterSpacing: 1 } }, status.accessToken),
|
|
2305
2314
|
customBtn("public")
|
|
2306
2315
|
),
|
|
2307
|
-
(0,
|
|
2316
|
+
(0, import_react2.createElement)(
|
|
2308
2317
|
"div",
|
|
2309
2318
|
{ style: { marginTop: 6 } },
|
|
2310
2319
|
customPin?.which === "public" ? customPinRow("public") : null,
|
|
2311
|
-
status?.publicPinCustom ? (0,
|
|
2312
|
-
namedMode ? (0,
|
|
2320
|
+
status?.publicPinCustom ? (0, import_react2.createElement)("div", { style: { ...styles.warn } }, t("pinCustomHint")) : null,
|
|
2321
|
+
namedMode ? (0, import_react2.createElement)("div", { style: { ...styles.warn } }, t("namedSecurity")) : null
|
|
2313
2322
|
)
|
|
2314
2323
|
) : null
|
|
2315
2324
|
) : null
|
|
2316
2325
|
),
|
|
2317
|
-
error ? (0,
|
|
2326
|
+
error ? (0, import_react2.createElement)("div", { style: { color: "var(--dsw-alias-state-error-primary,#dc2626)", fontSize: 12, marginTop: 8 } }, `\u274C ${errText(error)}`) : null,
|
|
2318
2327
|
// 恢复出厂设置:设置出问题时的临时兜底(最底部,避免误触)
|
|
2319
|
-
(0,
|
|
2328
|
+
(0, import_react2.createElement)(
|
|
2320
2329
|
"div",
|
|
2321
2330
|
{ style: styles.block },
|
|
2322
|
-
(0,
|
|
2331
|
+
(0, import_react2.createElement)(
|
|
2323
2332
|
"div",
|
|
2324
2333
|
{ style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8 } },
|
|
2325
|
-
(0,
|
|
2326
|
-
(0,
|
|
2334
|
+
(0, import_react2.createElement)("span", { style: { fontWeight: 600, fontSize: 13 } }, t("resetFactory")),
|
|
2335
|
+
(0, import_react2.createElement)("button", { style: { ...styles.btn, height: 28, padding: "0 12px", fontSize: 12, color: "var(--dsw-alias-state-error-primary,#dc2626)" }, onClick: () => setResetOpen(true) }, t("resetGo"))
|
|
2327
2336
|
),
|
|
2328
|
-
(0,
|
|
2337
|
+
(0, import_react2.createElement)("div", { style: { ...styles.muted, marginTop: 6 } }, t("resetIntro"))
|
|
2329
2338
|
),
|
|
2330
2339
|
// 恢复出厂设置确认弹框
|
|
2331
|
-
resetOpen ? (0,
|
|
2340
|
+
resetOpen ? (0, import_react2.createElement)(
|
|
2332
2341
|
"div",
|
|
2333
2342
|
{ style: { position: "fixed", inset: 0, zIndex: 1e4, background: "rgba(0,0,0,.5)", display: "flex", alignItems: "center", justifyContent: "center", padding: 20 } },
|
|
2334
|
-
(0,
|
|
2343
|
+
(0, import_react2.createElement)(
|
|
2335
2344
|
"div",
|
|
2336
2345
|
{ style: { background: "var(--dsw-alias-bg-layer-1,#fff)", borderRadius: 12, maxWidth: 440, width: "100%", padding: "20px 22px", boxShadow: "0 8px 32px rgba(0,0,0,.18)" } },
|
|
2337
|
-
(0,
|
|
2338
|
-
(0,
|
|
2339
|
-
(0,
|
|
2346
|
+
(0, import_react2.createElement)("div", { style: { fontWeight: 600, fontSize: 15, color: "var(--dsw-alias-state-warn-primary,#b45309)", marginBottom: 10 } }, t("resetTitle")),
|
|
2347
|
+
(0, import_react2.createElement)("div", { style: { fontSize: 13, lineHeight: 1.7, color: "var(--dsw-alias-label-primary,inherit)", whiteSpace: "pre-line" } }, t("resetBody")),
|
|
2348
|
+
(0, import_react2.createElement)(
|
|
2340
2349
|
"div",
|
|
2341
2350
|
{ style: { display: "flex", gap: 8, marginTop: 16 } },
|
|
2342
|
-
(0,
|
|
2343
|
-
(0,
|
|
2351
|
+
(0, import_react2.createElement)("button", { style: { ...styles.btn, flex: 1 }, onClick: () => setResetOpen(false) }, t("cancel")),
|
|
2352
|
+
(0, import_react2.createElement)("button", { style: { ...styles.primary, flex: 1, background: "var(--dsw-alias-state-error-primary,#dc2626)" }, onClick: doFactoryReset }, t("resetConfirm"))
|
|
2344
2353
|
)
|
|
2345
2354
|
)
|
|
2346
2355
|
) : null,
|
|
2347
2356
|
// Toast:重置等操作的即时反馈(固定屏幕正中央,2.6s 自动消失)
|
|
2348
|
-
toast ? (0,
|
|
2357
|
+
toast ? (0, import_react2.createElement)("div", {
|
|
2349
2358
|
style: { position: "fixed", left: "50%", top: "50%", transform: "translate(-50%, -50%)", zIndex: 10001, width: "auto", maxWidth: 280, background: "rgba(17,24,39,.92)", color: "#fff", border: "none", borderRadius: 10, padding: "10px 16px", fontSize: 13, lineHeight: 1.5, textAlign: "center", boxShadow: "0 8px 24px rgba(0,0,0,.22)" }
|
|
2350
2359
|
}, toast) : null,
|
|
2351
2360
|
// 局域网访问开关确认弹框(关闭/打开时弹窗提醒)
|
|
2352
|
-
lanToggleOpen !== null ? (0,
|
|
2361
|
+
lanToggleOpen !== null ? (0, import_react2.createElement)(
|
|
2353
2362
|
"div",
|
|
2354
2363
|
{ style: { position: "fixed", inset: 0, zIndex: 1e4, background: "rgba(0,0,0,.5)", display: "flex", alignItems: "center", justifyContent: "center", padding: 20 } },
|
|
2355
|
-
(0,
|
|
2364
|
+
(0, import_react2.createElement)(
|
|
2356
2365
|
"div",
|
|
2357
2366
|
{ style: { background: "var(--dsw-alias-bg-layer-1,#fff)", borderRadius: 12, maxWidth: 420, width: "100%", padding: "20px 22px", boxShadow: "0 8px 32px rgba(0,0,0,.18)" } },
|
|
2358
|
-
(0,
|
|
2359
|
-
(0,
|
|
2360
|
-
(0,
|
|
2367
|
+
(0, import_react2.createElement)("div", { style: { fontWeight: 600, fontSize: 15, color: lanToggleOpen ? "var(--dsw-alias-brand-primary,#4f6ef7)" : "var(--dsw-alias-state-warn-primary,#b45309)", marginBottom: 10 } }, t(lanToggleOpen ? "lanToggleTitleOn" : "lanToggleTitleOff")),
|
|
2368
|
+
(0, import_react2.createElement)("div", { style: { fontSize: 13, lineHeight: 1.7, color: "var(--dsw-alias-label-primary,inherit)" } }, t(lanToggleOpen ? "lanToggleBodyOn" : "lanToggleBodyOff")),
|
|
2369
|
+
(0, import_react2.createElement)(
|
|
2361
2370
|
"div",
|
|
2362
2371
|
{ style: { display: "flex", gap: 8, marginTop: 16 } },
|
|
2363
|
-
(0,
|
|
2364
|
-
(0,
|
|
2372
|
+
(0, import_react2.createElement)("button", { style: { ...styles.btn, flex: 1 }, onClick: () => setLanToggleOpen(null) }, t("cancel")),
|
|
2373
|
+
(0, import_react2.createElement)("button", { style: { ...styles.primary, flex: 1 }, onClick: confirmLanToggle }, t("confirm"))
|
|
2365
2374
|
)
|
|
2366
2375
|
)
|
|
2367
2376
|
) : null,
|
|
2368
2377
|
// 安全免责声明弹框(issue #31):每次开启公网访问前确认
|
|
2369
|
-
disclaimerOpen ? (0,
|
|
2378
|
+
disclaimerOpen ? (0, import_react2.createElement)(
|
|
2370
2379
|
"div",
|
|
2371
2380
|
{ style: { position: "fixed", inset: 0, zIndex: 1e4, background: "rgba(0,0,0,.5)", display: "flex", alignItems: "center", justifyContent: "center", padding: 20 } },
|
|
2372
|
-
(0,
|
|
2381
|
+
(0, import_react2.createElement)(
|
|
2373
2382
|
"div",
|
|
2374
2383
|
{ style: { background: "var(--dsw-alias-bg-layer-1,#fff)", borderRadius: 12, maxWidth: 420, width: "100%", padding: "20px 22px", boxShadow: "0 8px 32px rgba(0,0,0,.18)" } },
|
|
2375
|
-
(0,
|
|
2376
|
-
(0,
|
|
2377
|
-
(0,
|
|
2384
|
+
(0, import_react2.createElement)("div", { style: { fontWeight: 600, fontSize: 15, color: "var(--dsw-alias-state-warn-primary,#b45309)", marginBottom: 10 } }, t("disclaimerTitle")),
|
|
2385
|
+
(0, import_react2.createElement)("div", { style: { fontSize: 13, lineHeight: 1.7, color: "var(--dsw-alias-label-primary,inherit)" } }, t("disclaimerBody")),
|
|
2386
|
+
(0, import_react2.createElement)(
|
|
2378
2387
|
"label",
|
|
2379
2388
|
{ style: { display: "flex", alignItems: "center", gap: 8, marginTop: 14, fontSize: 13, cursor: "pointer" } },
|
|
2380
|
-
(0,
|
|
2389
|
+
(0, import_react2.createElement)("input", { type: "checkbox", checked: disclaimerChecked, onChange: (e) => setDisclaimerChecked(e.target.checked), style: { width: 16, height: 16 } }),
|
|
2381
2390
|
t("disclaimerAgree")
|
|
2382
2391
|
),
|
|
2383
|
-
(0,
|
|
2392
|
+
(0, import_react2.createElement)(
|
|
2384
2393
|
"div",
|
|
2385
2394
|
{ style: { display: "flex", gap: 8, marginTop: 16 } },
|
|
2386
|
-
(0,
|
|
2387
|
-
(0,
|
|
2395
|
+
(0, import_react2.createElement)("button", { style: { ...styles.btn, flex: 1 }, onClick: () => setDisclaimerOpen(false) }, t("cancel")),
|
|
2396
|
+
(0, import_react2.createElement)("button", {
|
|
2388
2397
|
style: { ...styles.primary, flex: 1, opacity: disclaimerChecked ? 1 : 0.5 },
|
|
2389
2398
|
disabled: !disclaimerChecked,
|
|
2390
2399
|
onClick: confirmDisclaimer
|
|
2391
2400
|
}, t("disclaimerAgree"))
|
|
2392
2401
|
),
|
|
2393
|
-
!disclaimerChecked ? (0,
|
|
2402
|
+
!disclaimerChecked ? (0, import_react2.createElement)("div", { style: { marginTop: 8, fontSize: 12, color: "var(--dsw-alias-state-error-primary,#dc2626)" } }, t("disclaimerHint")) : null
|
|
2394
2403
|
)
|
|
2395
2404
|
) : null,
|
|
2396
2405
|
// 页面最底部:反馈入口
|
|
2397
|
-
(0,
|
|
2406
|
+
(0, import_react2.createElement)(
|
|
2398
2407
|
"div",
|
|
2399
2408
|
{ style: { ...styles.block, textAlign: "center" } },
|
|
2400
|
-
(0,
|
|
2409
|
+
(0, import_react2.createElement)(
|
|
2401
2410
|
"a",
|
|
2402
2411
|
{ href: "https://github.com/shaobeichen/dsh-pocket/issues", target: "_blank", rel: "noreferrer", style: { fontSize: 12, color: "var(--dsw-alias-label-secondary,#6b7280)", textDecoration: "none" } },
|
|
2403
2412
|
t("feedback")
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// 移动端文件守卫(issue #17 修正):dsh-web 在手机上点「文件链接」会触发桌面端
|
|
2
|
+
// workspaces.openPath(open <path>),既打不开(文件在电脑上),又会抛
|
|
3
|
+
// "path open failed: ...". 这里在捕获阶段拦截这类激活(点击 / 键盘),改为弹一个
|
|
4
|
+
// 提示;并隐藏「添加工作区」入口(手机上配工作区无意义)。
|
|
5
|
+
// 移植自 dsh-web-mobile(MIT)。
|
|
6
|
+
//
|
|
7
|
+
// 识别方式:不依赖 dsh-web 的 hash 类名(每次构建都变),只认「文本像文件路径的
|
|
8
|
+
// <button>/<a>」——文件链接按钮的文案就是路径(如 lib/proxy.mjs / /Users/.../x.ts)。
|
|
9
|
+
|
|
10
|
+
/** 手机上点击文件时弹出的提示。 */
|
|
11
|
+
const GUARD_MSG = '手机上无法直接打开电脑上的文件'
|
|
12
|
+
/** 「添加工作区」入口的文案(随语言变化),两种都覆盖。 */
|
|
13
|
+
const WS_LABELS = ['添加工作区', '添加工作区…', 'Add workspace', 'Add workspace…']
|
|
14
|
+
|
|
15
|
+
/** 文本是否像文件路径:绝对路径 / 相对路径 / 带扩展名的目录路径。 */
|
|
16
|
+
function looksLikeFilePath(text: string | null): boolean {
|
|
17
|
+
const t = (text ?? '').trim()
|
|
18
|
+
if (t.length < 3 || t.length > 320) return false
|
|
19
|
+
if (/^(\/|~\/|\.\.?\/|[A-Za-z]:\\)/.test(t)) return true
|
|
20
|
+
if (/\/[\w.\-]+\.\w{1,12}$/.test(t)) return true
|
|
21
|
+
if (/[\w.\-]+\/[\w.\-]+\.\w{1,12}/.test(t)) return true
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** 节点是否落在 dsh-pocket 自身面板内(不拦截面板内的交互)。 */
|
|
26
|
+
function isInsidePocket(el: Element | null): boolean {
|
|
27
|
+
return el !== null && el.closest('[data-mobile-nav="frame"]') !== null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function startFileGuard(): () => void {
|
|
31
|
+
// 轻量 toast:自包含,不依赖 dsh-pocket 面板的 React 状态。
|
|
32
|
+
let toastEl: HTMLElement | null = null
|
|
33
|
+
let toastTimer: number | null = null
|
|
34
|
+
const showToast = (text: string): void => {
|
|
35
|
+
if (toastEl === null) {
|
|
36
|
+
toastEl = document.createElement('div')
|
|
37
|
+
toastEl.setAttribute('data-mobile-nav', 'file-guard-toast')
|
|
38
|
+
Object.assign(toastEl.style, {
|
|
39
|
+
position: 'fixed',
|
|
40
|
+
left: '50%',
|
|
41
|
+
bottom: '64px',
|
|
42
|
+
transform: 'translateX(-50%)',
|
|
43
|
+
maxWidth: '84vw',
|
|
44
|
+
zIndex: '9999',
|
|
45
|
+
padding: '10px 14px',
|
|
46
|
+
borderRadius: '10px',
|
|
47
|
+
background: 'rgba(20,22,28,.92)',
|
|
48
|
+
color: '#fff',
|
|
49
|
+
fontSize: '13px',
|
|
50
|
+
lineHeight: '1.4',
|
|
51
|
+
textAlign: 'center',
|
|
52
|
+
fontFamily: 'inherit',
|
|
53
|
+
boxShadow: '0 4px 16px rgba(0,0,0,.28)',
|
|
54
|
+
pointerEvents: 'none',
|
|
55
|
+
opacity: '0',
|
|
56
|
+
transition: 'opacity .18s ease',
|
|
57
|
+
} as CSSStyleDeclaration)
|
|
58
|
+
document.body.appendChild(toastEl)
|
|
59
|
+
}
|
|
60
|
+
toastEl.textContent = text
|
|
61
|
+
requestAnimationFrame(() => {
|
|
62
|
+
if (toastEl !== null) toastEl.style.opacity = '1'
|
|
63
|
+
})
|
|
64
|
+
if (toastTimer !== null) window.clearTimeout(toastTimer)
|
|
65
|
+
toastTimer = window.setTimeout(() => {
|
|
66
|
+
if (toastEl !== null) toastEl.style.opacity = '0'
|
|
67
|
+
}, 2600)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 捕获阶段拦截文件链接的激活。按钮的键盘激活(Enter/Space)会派发 click,
|
|
71
|
+
// 因此只拦 click 即可同时覆盖鼠标与键盘,避免重复处理。
|
|
72
|
+
const onClick = (event: MouseEvent): void => {
|
|
73
|
+
const target = event.target as HTMLElement | null
|
|
74
|
+
if (target === null || isInsidePocket(target)) return
|
|
75
|
+
const el = target.closest('button, a') as HTMLElement | null
|
|
76
|
+
if (el === null) return
|
|
77
|
+
if (!looksLikeFilePath(el.textContent)) return
|
|
78
|
+
event.preventDefault()
|
|
79
|
+
event.stopImmediatePropagation()
|
|
80
|
+
showToast(GUARD_MSG)
|
|
81
|
+
}
|
|
82
|
+
document.addEventListener('click', onClick, true)
|
|
83
|
+
|
|
84
|
+
// 隐藏「添加工作区」入口:图标按钮由 mobile.css.ts 按 aria-label 隐藏;
|
|
85
|
+
// 下拉菜单里的文本项 CSS 选不到,这里按文案兜底(只在新增节点时检查,省开销)。
|
|
86
|
+
const hideWsEntries = (): (() => void) => {
|
|
87
|
+
const checkOne = (node: Node): void => {
|
|
88
|
+
if (node.nodeType !== 1) return
|
|
89
|
+
const el = node as HTMLElement
|
|
90
|
+
const txt = (el.getAttribute('aria-label') ?? el.textContent ?? '').trim()
|
|
91
|
+
if (WS_LABELS.includes(txt)) {
|
|
92
|
+
el.style.display = 'none'
|
|
93
|
+
el.setAttribute('data-mobile-nav-hide', 'add-workspace')
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const sel = '[role="menuitem"],[role="option"],li,button,a'
|
|
97
|
+
document.querySelectorAll(sel).forEach(checkOne)
|
|
98
|
+
const observer = new MutationObserver((mutations) => {
|
|
99
|
+
for (const m of mutations) {
|
|
100
|
+
m.addedNodes.forEach((n) => {
|
|
101
|
+
if (n.nodeType !== 1) return
|
|
102
|
+
checkOne(n)
|
|
103
|
+
;(n as HTMLElement).querySelectorAll?.(sel).forEach(checkOne)
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
observer.observe(document.body, { childList: true, subtree: true })
|
|
108
|
+
return () => observer.disconnect()
|
|
109
|
+
}
|
|
110
|
+
const disconnectWs = hideWsEntries()
|
|
111
|
+
|
|
112
|
+
return () => {
|
|
113
|
+
document.removeEventListener('click', onClick, true)
|
|
114
|
+
disconnectWs()
|
|
115
|
+
if (toastTimer !== null) window.clearTimeout(toastTimer)
|
|
116
|
+
toastEl?.remove()
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -3,7 +3,7 @@ import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
|
|
|
3
3
|
import { MobileNavToggle } from './MobileNavToggle.tsx'
|
|
4
4
|
import { MobileNavOverlay } from './MobileNavOverlay.tsx'
|
|
5
5
|
import { MobileDrawerFooter } from './MobileDrawerFooter.tsx'
|
|
6
|
-
import {
|
|
6
|
+
import { startFileGuard } from './fileGuard.ts'
|
|
7
7
|
import { MOBILE_CSS } from './mobile.css.ts'
|
|
8
8
|
import { NS, en, zh } from './locales.ts'
|
|
9
9
|
import type { MobileNavKey } from './locales.ts'
|
|
@@ -263,6 +263,15 @@ export function mobileApply(ctx): void {
|
|
|
263
263
|
}
|
|
264
264
|
}, 'dsh-mobile-nav: sheet rise animation replay')
|
|
265
265
|
|
|
266
|
+
// 移动端文件守卫(issue #17 修正):手机上点 dsh-web 渲染的文件链接会触发桌面
|
|
267
|
+
// 端 workspaces.openPath(open ...) —— 既打不开(路径在电脑上),又会抛
|
|
268
|
+
// "path open failed"。这里在捕获阶段拦截这类点击 / 键盘激活,改为弹一个提示,
|
|
269
|
+
// 并隐藏「添加工作区」入口(手机上配工作区无意义)。只挂窄屏。
|
|
270
|
+
ctx.effect(() => {
|
|
271
|
+
if (!narrow.matches) return () => {}
|
|
272
|
+
return startFileGuard()
|
|
273
|
+
}, 'dsh-mobile-nav: file open guard + hide add-workspace (issue #17)')
|
|
274
|
+
|
|
266
275
|
ctx.slots.inject('conversation.session.header.actions', () => ctx.slots.register({
|
|
267
276
|
name: 'conversation.session.header.actions',
|
|
268
277
|
id: 'mobile-nav-toggle',
|
|
@@ -273,16 +282,6 @@ export function mobileApply(ctx): void {
|
|
|
273
282
|
}),
|
|
274
283
|
}, MobileNavToggle))
|
|
275
284
|
|
|
276
|
-
// 「⛶ 放大输入」按钮(issue #23):注册到 conversation.input.right(发送键旁)
|
|
277
|
-
// ——桌面端由 mobile.css.ts 隐藏(min-width:1024px)。点按切换 body 上的
|
|
278
|
-
// data-dsh-pocket-composer-fullscreen 标记,由 CSS 把 composer 卡片全屏化。
|
|
279
|
-
ctx.slots.inject('conversation.input.right', () => ctx.slots.register({
|
|
280
|
-
name: 'conversation.input.right',
|
|
281
|
-
id: 'mobile-composer-fullscreen',
|
|
282
|
-
order: 100,
|
|
283
|
-
locale: NS,
|
|
284
|
-
}, MobileComposerFullscreen))
|
|
285
|
-
|
|
286
285
|
ctx.slots.inject('shell.overlay', () => ctx.slots.register({
|
|
287
286
|
name: 'shell.overlay',
|
|
288
287
|
id: 'mobile-nav-overlay',
|
|
@@ -872,6 +872,17 @@ export const MOBILE_CSS = `
|
|
|
872
872
|
[data-phase="hero"] [class$="_stack"] {
|
|
873
873
|
gap: 0 !important;
|
|
874
874
|
}
|
|
875
|
+
|
|
876
|
+
/* ---------- 隐藏「添加工作区」入口(手机上配工作区无意义,issue #17 修正) ----------
|
|
877
|
+
图标按钮的 aria-label 随语言变化(zh「添加工作区」/ en「Add workspace」),
|
|
878
|
+
两种都覆盖;下拉菜单里的「添加工作区…」项由 fileGuard.ts 的 MutationObserver
|
|
879
|
+
按文案兜底隐藏(CSS 选不到纯文本节点)。只在窄屏生效——桌面端照常保留。 */
|
|
880
|
+
button[aria-label="添加工作区"],
|
|
881
|
+
button[aria-label="添加工作区…"],
|
|
882
|
+
button[aria-label="Add workspace"],
|
|
883
|
+
button[aria-label="Add workspace…"] {
|
|
884
|
+
display: none !important;
|
|
885
|
+
}
|
|
875
886
|
}
|
|
876
887
|
|
|
877
888
|
/* ---------- desktop: the mobile controls must never appear ---------- */
|
|
@@ -886,50 +897,6 @@ export const MOBILE_CSS = `
|
|
|
886
897
|
[data-mobile-nav="drawer-actions"] {
|
|
887
898
|
display: none !important;
|
|
888
899
|
}
|
|
889
|
-
/* 桌面端永远不需要「⛶ 放大输入」按钮(composer 已经有完整工具行) */
|
|
890
|
-
[data-mobile-nav="composer-fullscreen"] {
|
|
891
|
-
display: none !important;
|
|
892
|
-
}
|
|
893
900
|
}
|
|
894
901
|
|
|
895
|
-
/* ---------- 放大输入(issue #23) ----------
|
|
896
|
-
窄屏默认隐藏插件注册到 conversation.input.* slot 的 UI(仅留官方 resident
|
|
897
|
-
chrome),点「⛶」按钮把 composer 卡片固定到全屏后所有插件 UI 恢复显示——
|
|
898
|
-
新插件零适配。
|
|
899
|
-
实现说明:当前 dsh 0.1.1 没把 input slot 渲染成 [data-slot] 包装(0.1.2+ 才有),
|
|
900
|
-
所以这里用「白名单」反选:mobileApply 注册的 mobile 自身节点加
|
|
901
|
-
data-mobile-nav="composer-fullscreen" 不被隐藏;其它插件 UI 隐藏。 */
|
|
902
|
-
@media (max-width: 1023px) {
|
|
903
|
-
/* 默认(非全屏):隐藏 input slot 容器下的所有直接子元素(包含插件和官方)
|
|
904
|
-
——官方自己如果想在窄屏也露出,注 input slot 时加 data-mobile-nav-keep="1" */
|
|
905
|
-
[data-dsh-pocket-composer-fullscreen]:not([data-dsh-pocket-composer-fullscreen="1"])
|
|
906
|
-
[class$="_card"]:has(textarea) [data-slot^="conversation.input."] > :not([data-mobile-nav-keep]),
|
|
907
|
-
[data-dsh-pocket-composer-fullscreen]:not([data-dsh-pocket-composer-fullscreen="1"])
|
|
908
|
-
[class$="_card"]:has(textarea) [data-slot^="conversation.input."]:empty {
|
|
909
|
-
display: none !important;
|
|
910
|
-
}
|
|
911
|
-
/* 放大按钮:常驻在 conversation.input.right slot 旁,桌面端已被上面规则隐藏 */
|
|
912
|
-
[data-mobile-nav="composer-fullscreen"] {
|
|
913
|
-
display: inline-flex;
|
|
914
|
-
}
|
|
915
|
-
/* 全屏模式:composer 卡片固定到视口,textarea 拉高,工具行可换行 */
|
|
916
|
-
[data-dsh-pocket-composer-fullscreen="1"] [class$="_card"]:has(textarea) {
|
|
917
|
-
position: fixed !important;
|
|
918
|
-
inset: 0 !important;
|
|
919
|
-
z-index: 9999 !important;
|
|
920
|
-
border-radius: 0 !important;
|
|
921
|
-
margin: 0 !important;
|
|
922
|
-
height: 100dvh !important;
|
|
923
|
-
display: flex !important;
|
|
924
|
-
flex-direction: column !important;
|
|
925
|
-
}
|
|
926
|
-
[data-dsh-pocket-composer-fullscreen="1"] [class$="_card"]:has(textarea) textarea {
|
|
927
|
-
flex: 1 1 auto !important;
|
|
928
|
-
min-height: 50dvh !important;
|
|
929
|
-
max-height: none !important;
|
|
930
|
-
}
|
|
931
|
-
[data-dsh-pocket-composer-fullscreen="1"] [class$="_card"]:has(textarea) [data-slot^="conversation.input."] {
|
|
932
|
-
flex-wrap: wrap !important;
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
902
|
`
|
package/package.json
CHANGED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
// 手机端「⛶ 放大输入」开关(issue #23)
|
|
2
|
-
//
|
|
3
|
-
// 行为:
|
|
4
|
-
// - 点击切换 body 属性 `data-dsh-pocket-composer-fullscreen`;
|
|
5
|
-
// - mobile.css.ts 监听这个属性,把 composer 卡片固定到全屏(textarea 拉高、工具行
|
|
6
|
-
// 全部展开),插件 UI 注册到 conversation.input.* slot 的按钮全部恢复显示;
|
|
7
|
-
// - 默认(无 fullscreen 属性)下,CSS 把这些 slot 全部隐藏,只保留官方 resident
|
|
8
|
-
// chrome(权限/plan/模型/发送)。新插件只要注册到 input slot 就自动遵守此规则,
|
|
9
|
-
// 无需逐个适配。
|
|
10
|
-
//
|
|
11
|
-
// 实现细节:
|
|
12
|
-
// - 不依赖 dsh web 把 data-slot 包装暴露给选择器(0.1.1 没,0.1.2+ 才有)。我们用
|
|
13
|
-
// `:scope > :not([data-mobile-nav-keep])` 这样的「保留」标记:所有插件如果
|
|
14
|
-
// 想在手机未放大时也露出,加 data-mobile-nav-keep="1" 即可。默认隐藏的是
|
|
15
|
-
// slot 容器下的所有直接子元素,**包含 dsh web 官方自己注入的**(这些是
|
|
16
|
-
// 我们想隐藏的"插件 UI"——只要不冲突)。所以需要 dsh web 在官方自己的 UI 上
|
|
17
|
-
// 加 `data-mobile-nav-keep` 标记——等 dsh 0.1.2+ data-slot 上线后改用那个
|
|
18
|
-
// 更优雅(CSS 选择器变成 `[data-slot=...] > :not([data-mobile-nav-keep])`)。
|
|
19
|
-
import { createElement as h, useEffect, useState } from 'react';
|
|
20
|
-
|
|
21
|
-
const FULLSCREEN_ATTR = 'data-dsh-pocket-composer-fullscreen';
|
|
22
|
-
|
|
23
|
-
function isFullscreen() {
|
|
24
|
-
return typeof document !== 'undefined' && document.body?.getAttribute(FULLSCREEN_ATTR) === '1';
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function setFullscreen(on) {
|
|
28
|
-
if (typeof document === 'undefined') return;
|
|
29
|
-
if (on) document.body.setAttribute(FULLSCREEN_ATTR, '1');
|
|
30
|
-
else document.body.removeAttribute(FULLSCREEN_ATTR);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* 放大输入按钮:注册到 `conversation.input.right`,id `mobile-composer-fullscreen`。
|
|
35
|
-
* 仅在窄屏(max-width:1023px)显示,桌面端由 CSS 隐藏。
|
|
36
|
-
*/
|
|
37
|
-
export function MobileComposerFullscreen() {
|
|
38
|
-
const [on, setOn] = useState(isFullscreen);
|
|
39
|
-
useEffect(() => {
|
|
40
|
-
const update = () => setOn(isFullscreen());
|
|
41
|
-
// 与其它 effect 协调:监听任何对 body 属性的修改即可(设置项自己改也算)
|
|
42
|
-
const observer = new MutationObserver(update);
|
|
43
|
-
observer.observe(document.body, { attributes: true, attributeFilter: [FULLSCREEN_ATTR] });
|
|
44
|
-
return () => observer.disconnect();
|
|
45
|
-
}, []);
|
|
46
|
-
return h('button', {
|
|
47
|
-
type: 'button',
|
|
48
|
-
'aria-label': on ? '收起输入' : '放大输入',
|
|
49
|
-
'aria-pressed': on,
|
|
50
|
-
title: on ? '收起输入' : '放大输入',
|
|
51
|
-
'data-mobile-nav': 'composer-fullscreen',
|
|
52
|
-
'data-mobile-nav-keep': '1', // 自己的按钮不该被 #23 的 slot 通用隐藏规则误伤
|
|
53
|
-
onClick: () => {
|
|
54
|
-
const next = !isFullscreen();
|
|
55
|
-
setFullscreen(next);
|
|
56
|
-
setOn(next);
|
|
57
|
-
},
|
|
58
|
-
style: {
|
|
59
|
-
appearance: 'none',
|
|
60
|
-
WebkitAppearance: 'none',
|
|
61
|
-
border: 'none',
|
|
62
|
-
background: 'transparent',
|
|
63
|
-
color: 'inherit',
|
|
64
|
-
cursor: 'pointer',
|
|
65
|
-
padding: '0 8px',
|
|
66
|
-
height: 32,
|
|
67
|
-
minWidth: 32,
|
|
68
|
-
borderRadius: 6,
|
|
69
|
-
display: 'inline-flex',
|
|
70
|
-
alignItems: 'center',
|
|
71
|
-
justifyContent: 'center',
|
|
72
|
-
font: 'inherit',
|
|
73
|
-
fontSize: 16,
|
|
74
|
-
lineHeight: 1,
|
|
75
|
-
},
|
|
76
|
-
}, on ? '⤡' : '⤢');
|
|
77
|
-
}
|