dsh-mobile 0.4.6 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/CONTRIBUTORS.md +10 -1
- package/README.en.md +25 -8
- package/README.md +25 -8
- package/SECURITY.md +1 -1
- package/lib/client.js +326 -13
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +8 -3
- package/lib/index.mjs +293 -75
- package/lib/index.mjs.map +1 -1
- package/lib/mobile-compat.js.map +1 -1
- package/lib/mobile-layout.js +123 -21
- package/lib/mobile-layout.js.map +1 -1
- package/package.json +13 -13
package/lib/client.js
CHANGED
|
@@ -513,6 +513,30 @@ window.__ModuleLoader__.load({
|
|
|
513
513
|
wsPathsAnnounce: "{n} 条插件连接等待放行",
|
|
514
514
|
wsPathsAdvancedHint: "通常不需要手动填写:用上方按钮即可。只有当你确切知道所需路径时才填。"
|
|
515
515
|
});
|
|
516
|
+
Object.assign(MOBILE_CONTROL_MESSAGES.en, {
|
|
517
|
+
cpolarDownloadFailed: "Could not download cpolar from its official site. Check this computer’s network or proxy, then retry.",
|
|
518
|
+
cpolarDownloadTimeout: "The cpolar download timed out. Check this computer’s network or proxy, then retry.",
|
|
519
|
+
cpolarDownloadHttp: "The official cpolar download returned HTTP {status}. Retry later or check the official download page.",
|
|
520
|
+
cpolarIntegrityFailed: "The cpolar download did not match the pinned build, so it was not installed. Retry later or update DSH Mobile; do not bypass verification.",
|
|
521
|
+
cpolarExtractFailed: "The cpolar download completed, but extraction failed. Check system extraction tools and security software, then retry.",
|
|
522
|
+
cpolarStorageFailed: "Could not write the cpolar component in DSH Mobile’s private directory. Check disk space, permissions, and security software, then retry."
|
|
523
|
+
});
|
|
524
|
+
Object.assign(MOBILE_CONTROL_MESSAGES.it, {
|
|
525
|
+
cpolarDownloadFailed: "Impossibile scaricare cpolar dal sito ufficiale. Controlla rete o proxy del computer e riprova.",
|
|
526
|
+
cpolarDownloadTimeout: "Download di cpolar scaduto. Controlla rete o proxy del computer e riprova.",
|
|
527
|
+
cpolarDownloadHttp: "Il download ufficiale di cpolar ha restituito HTTP {status}. Riprova più tardi o controlla la pagina ufficiale.",
|
|
528
|
+
cpolarIntegrityFailed: "Il pacchetto cpolar non corrisponde alla versione verificata e non è stato installato. Riprova più tardi o aggiorna DSH Mobile; non aggirare la verifica.",
|
|
529
|
+
cpolarExtractFailed: "Download di cpolar completato, ma estrazione non riuscita. Controlla gli strumenti di estrazione e il software di sicurezza, poi riprova.",
|
|
530
|
+
cpolarStorageFailed: "Impossibile scrivere cpolar nella directory privata di DSH Mobile. Controlla spazio, permessi e software di sicurezza, poi riprova."
|
|
531
|
+
});
|
|
532
|
+
Object.assign(MOBILE_CONTROL_MESSAGES.zh, {
|
|
533
|
+
cpolarDownloadFailed: "无法从 cpolar 官网下载组件。请检查这台电脑的网络或代理,然后重试。",
|
|
534
|
+
cpolarDownloadTimeout: "下载 cpolar 组件超时。请检查这台电脑的网络或代理,然后重试。",
|
|
535
|
+
cpolarDownloadHttp: "cpolar 官网下载返回 HTTP {status}。请稍后重试,或检查官网下载页。",
|
|
536
|
+
cpolarIntegrityFailed: "下载的 cpolar 组件未通过固定版本校验,因此没有安装。请稍后重试或更新 DSH Mobile,不要跳过校验。",
|
|
537
|
+
cpolarExtractFailed: "cpolar 下载完成,但解包失败。请检查系统解压组件和安全软件,然后重试。",
|
|
538
|
+
cpolarStorageFailed: "无法把 cpolar 写入 DSH Mobile 私有目录。请检查磁盘空间、目录权限和安全软件,然后重试。"
|
|
539
|
+
});
|
|
516
540
|
Object.assign(MOBILE_CONTROL_MESSAGES.en, {
|
|
517
541
|
lanSetupTitle: "Finish local network setup",
|
|
518
542
|
lanSetupIntro: "Choose the network shared by this computer and your phone. DSH Mobile creates private certificates only after you confirm.",
|
|
@@ -1402,6 +1426,7 @@ window.__ModuleLoader__.load({
|
|
|
1402
1426
|
"remote-needs-login": ["Tailscale is waiting for login to finish.", "Return to Remote access and continue login."],
|
|
1403
1427
|
"remote-connecting": ["The {provider} remote channel is still connecting.", "Wait briefly, then check again."],
|
|
1404
1428
|
"remote-controller-error": ["The {provider} controller reported {controllerCode}.", "Return to Remote access and reconnect."],
|
|
1429
|
+
"competing-remote-channel": ["Another remote plugin is injecting its own request channel; the phone may show that plugin’s pairing screen.", "Turn off dsh-remote-web-ui remote access on the computer, refresh, and pair using the current DSH Mobile QR code. Do not mix pairing codes."],
|
|
1405
1430
|
"phone-network-unknown": ["The computer cannot determine whether the router isolates the phone.", "Confirm the phone and computer use the same network, then disable guest-network or AP isolation."]
|
|
1406
1431
|
},
|
|
1407
1432
|
it: {
|
|
@@ -1426,6 +1451,7 @@ window.__ModuleLoader__.load({
|
|
|
1426
1451
|
"remote-needs-login": ["Tailscale attende il completamento dell’accesso.", "Torna ad Accesso remoto e continua l’accesso."],
|
|
1427
1452
|
"remote-connecting": ["Il canale remoto {provider} è ancora in connessione.", "Attendi qualche istante e ripeti il controllo."],
|
|
1428
1453
|
"remote-controller-error": ["Il controller {provider} ha segnalato {controllerCode}.", "Torna ad Accesso remoto e riconnettiti."],
|
|
1454
|
+
"competing-remote-channel": ["Un altro plugin remoto sta inserendo il proprio canale di richieste; sul telefono potrebbe apparire la sua schermata di abbinamento.", "Disattiva l’accesso remoto di dsh-remote-web-ui sul computer, aggiorna la pagina e usa il codice QR corrente di DSH Mobile. Non mescolare i codici."],
|
|
1429
1455
|
"phone-network-unknown": ["Il computer non può stabilire se il router isola il telefono.", "Verifica che telefono e computer usino la stessa rete, poi disattiva rete ospiti o isolamento AP."]
|
|
1430
1456
|
},
|
|
1431
1457
|
zh: {
|
|
@@ -1450,6 +1476,7 @@ window.__ModuleLoader__.load({
|
|
|
1450
1476
|
"remote-needs-login": ["Tailscale 正在等待完成登录。", "返回远程访问并继续登录。"],
|
|
1451
1477
|
"remote-connecting": ["{provider} 远程通道仍在连接。", "等待片刻后重新检查。"],
|
|
1452
1478
|
"remote-controller-error": ["{provider} 控制器报告 {controllerCode}。", "返回远程访问并重新连接。"],
|
|
1479
|
+
"competing-remote-channel": ["检测到另一个远程插件注入自己的请求通道;手机可能显示它的配对页面。", "在电脑端关闭 dsh-remote-web-ui 的远程访问并刷新,再用 DSH Mobile 当前二维码配对;不要混用配对码。"],
|
|
1453
1480
|
"phone-network-unknown": ["电脑无法判断路由器是否隔离了手机。", "确认手机与电脑使用同一网络,并关闭访客网络或 AP 隔离。"]
|
|
1454
1481
|
}
|
|
1455
1482
|
};
|
|
@@ -2599,12 +2626,13 @@ window.__ModuleLoader__.load({
|
|
|
2599
2626
|
[data-dsh-mobile-center] { grid-column:2 !important; width:100vw !important; min-width:0 !important; }
|
|
2600
2627
|
[data-dsh-mobile-center] > * { min-width:0 !important; }
|
|
2601
2628
|
[data-dsh-mobile-header] { box-sizing:border-box !important; width:calc(100% - 16px) !important; margin:0 8px !important; min-width:0; padding-top:max(4px,env(safe-area-inset-top)) !important; padding-right:8px !important; padding-left:42px !important; }
|
|
2629
|
+
[data-dsh-mobile-header] :is([class*="_titleRow"],[class*="_headerLeading"]) { touch-action:pan-y; }
|
|
2602
2630
|
[data-dsh-mobile-header] [class*="_titleRow"] { box-sizing:border-box !important; display:flex !important; align-items:center !important; min-width:0; min-height:32px !important; height:32px !important; gap:6px !important; padding:0 6px !important; }
|
|
2603
2631
|
[data-dsh-mobile-header] [class*="_titleCluster"] { min-width:0; }
|
|
2604
2632
|
[data-dsh-mobile-header] [class*="_crumbs"] { min-width:0; overflow:hidden; }
|
|
2605
2633
|
[data-dsh-mobile-header] [class*="_crumb"] { max-width:46vw; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
2606
|
-
|
|
2607
|
-
[data-dsh-mobile-header] [class*="_headerActions"]
|
|
2634
|
+
/* Nested header actions (including the Jobs menu) must paint outside the row. */
|
|
2635
|
+
[data-dsh-mobile-header] [class*="_headerActions"] { flex:none; min-width:max-content; overflow:visible; }
|
|
2608
2636
|
[data-dsh-mobile-header] [class*="_headerUtilities"] { gap:2px !important; }
|
|
2609
2637
|
[data-dsh-mobile-header] [class*="_sessionLogButton"] { width:40px; min-width:40px; padding:0 !important; overflow:hidden; color:transparent; font-size:0 !important; }
|
|
2610
2638
|
[data-dsh-mobile-header] [class*="_sessionLogButton"] > * { display:none !important; }
|
|
@@ -2691,6 +2719,15 @@ window.__ModuleLoader__.load({
|
|
|
2691
2719
|
[data-dsh-mobile-center] [data-composer-card] ~ * [class*="_root"] [class*="_sep"] { margin:0 6px !important; }
|
|
2692
2720
|
/* Composer dock stats strip (turns/steps/tokens) reads small on phones. */
|
|
2693
2721
|
[data-dsh-mobile-center] [data-slot="conversation.composer.dock"] [class*="_root"] { font-size:10px !important; line-height:16px !important; }
|
|
2722
|
+
/* The context ring is a composer-metadata _root too, so the metadata rule
|
|
2723
|
+
above stretched it across the whole dock. It cannot shrink, which left the
|
|
2724
|
+
session statistics beside it a few pixels wide, every number clipped to an
|
|
2725
|
+
ellipsis. Put the numbers in a wrapping left column and the ring in a right
|
|
2726
|
+
column it cannot grow out of: a phone then shows every number in full, and
|
|
2727
|
+
a wide screen keeps the ring beside them on one line. */
|
|
2728
|
+
[data-dsh-mobile-center] [data-composer-card] ~ * [class*="_root"]:has(> [class*="_trigger"][aria-haspopup="dialog"]) { width:auto !important; max-width:none !important; flex:0 0 auto !important; }
|
|
2729
|
+
[data-dsh-mobile-center] [class*="_dock"]:has([data-composer-stats]) { display:grid !important; grid-template-columns:minmax(0,1fr) max-content !important; align-items:center !important; column-gap:12px !important; }
|
|
2730
|
+
[data-dsh-mobile-center] [data-composer-card] ~ * [class*="_root"][data-composer-stats] { box-sizing:border-box !important; width:auto !important; max-width:none !important; flex:0 1 auto !important; flex-wrap:wrap !important; justify-content:flex-start !important; row-gap:2px !important; }
|
|
2694
2731
|
/* Message runtime details are inline on desktop. Give the clock/runtime
|
|
2695
2732
|
label its own wrapping row on narrow screens so TTFT and throughput do
|
|
2696
2733
|
not push the action buttons or clip at the viewport edge. */
|
|
@@ -2739,7 +2776,6 @@ window.__ModuleLoader__.load({
|
|
|
2739
2776
|
@keyframes dsh-mobile-panel-in { from { opacity:.72; transform:translateY(6px); } }
|
|
2740
2777
|
@keyframes dsh-mobile-view-in { from { opacity:.58; transform:translateY(5px); } }
|
|
2741
2778
|
@media (max-width:420px) {
|
|
2742
|
-
[data-dsh-mobile-header] [class*="_headerActions"] { max-width:42vw; }
|
|
2743
2779
|
[data-dsh-mobile-settings-options] [data-slot="settings.general.item"] [class*="_selector"] { align-self:stretch !important; width:100% !important; }
|
|
2744
2780
|
[data-dsh-mobile-message-column] [data-context-fields] > * { grid-template-columns:1fr !important; }
|
|
2745
2781
|
}
|
|
@@ -2782,6 +2818,93 @@ window.__ModuleLoader__.load({
|
|
|
2782
2818
|
return marked;
|
|
2783
2819
|
}
|
|
2784
2820
|
const AUTO_HISTORY_THRESHOLD_PX = 64;
|
|
2821
|
+
/** Travel, in CSS pixels, that separates a header strip pan from a tap on a chip. */
|
|
2822
|
+
const STRIP_DRAG_THRESHOLD_PX = 8;
|
|
2823
|
+
/** Measure flowing title controls without counting an open absolute-positioned menu. */
|
|
2824
|
+
function measureHeaderStripOverflow(row) {
|
|
2825
|
+
const left = row.getBoundingClientRect().left;
|
|
2826
|
+
const controls = Array.from(row.children);
|
|
2827
|
+
const actions = row.querySelector(":scope > [class*=\"_titleCluster\"] [class*=\"_headerActions\"]");
|
|
2828
|
+
if (actions !== null) controls.push(actions);
|
|
2829
|
+
const right = Math.max(left, ...controls.map((control) => control.getBoundingClientRect().right));
|
|
2830
|
+
return Math.max(0, right - left - row.clientWidth);
|
|
2831
|
+
}
|
|
2832
|
+
/** Touch-only pan state; browser pointer cancellation does not interrupt the touch stream. */
|
|
2833
|
+
function createHeaderStripPanController(options) {
|
|
2834
|
+
let offset = 0;
|
|
2835
|
+
let drag;
|
|
2836
|
+
let suppressUntil = 0;
|
|
2837
|
+
const clamp = (value) => Math.min(Math.max(0, options.range()), Math.max(0, value));
|
|
2838
|
+
const setOffset = (value) => {
|
|
2839
|
+
const next = clamp(value);
|
|
2840
|
+
if (next === offset) return;
|
|
2841
|
+
offset = next;
|
|
2842
|
+
options.render(offset);
|
|
2843
|
+
};
|
|
2844
|
+
const end = () => {
|
|
2845
|
+
if (drag === void 0) return;
|
|
2846
|
+
if (drag.claimed) suppressUntil = options.now() + 400;
|
|
2847
|
+
drag = void 0;
|
|
2848
|
+
};
|
|
2849
|
+
const cancel = () => {
|
|
2850
|
+
if (drag?.claimed) suppressUntil = options.now() + 400;
|
|
2851
|
+
drag = void 0;
|
|
2852
|
+
};
|
|
2853
|
+
return {
|
|
2854
|
+
offset: () => offset,
|
|
2855
|
+
start: (x, y) => {
|
|
2856
|
+
drag = {
|
|
2857
|
+
x,
|
|
2858
|
+
y,
|
|
2859
|
+
offset,
|
|
2860
|
+
claimed: false
|
|
2861
|
+
};
|
|
2862
|
+
suppressUntil = 0;
|
|
2863
|
+
},
|
|
2864
|
+
move: (x, y) => {
|
|
2865
|
+
if (drag === void 0) return false;
|
|
2866
|
+
const travel = x - drag.x;
|
|
2867
|
+
if (!drag.claimed) {
|
|
2868
|
+
const drift = y - drag.y;
|
|
2869
|
+
if (Math.abs(travel) < STRIP_DRAG_THRESHOLD_PX && Math.abs(drift) < STRIP_DRAG_THRESHOLD_PX) return false;
|
|
2870
|
+
if (Math.abs(drift) > Math.abs(travel)) {
|
|
2871
|
+
drag = void 0;
|
|
2872
|
+
return false;
|
|
2873
|
+
}
|
|
2874
|
+
drag.claimed = true;
|
|
2875
|
+
}
|
|
2876
|
+
setOffset(drag.offset - travel);
|
|
2877
|
+
return true;
|
|
2878
|
+
},
|
|
2879
|
+
end,
|
|
2880
|
+
cancel,
|
|
2881
|
+
sync: () => {
|
|
2882
|
+
if (options.range() === 0) {
|
|
2883
|
+
drag = void 0;
|
|
2884
|
+
suppressUntil = 0;
|
|
2885
|
+
}
|
|
2886
|
+
setOffset(offset);
|
|
2887
|
+
},
|
|
2888
|
+
reveal: (left, right, visibleLeft, visibleRight) => {
|
|
2889
|
+
if (left < visibleLeft) setOffset(offset - (visibleLeft - left));
|
|
2890
|
+
else if (right > visibleRight) setOffset(offset + right - visibleRight);
|
|
2891
|
+
},
|
|
2892
|
+
resetClickSuppression: () => {
|
|
2893
|
+
suppressUntil = 0;
|
|
2894
|
+
},
|
|
2895
|
+
suppressClick: (inHeader, detail) => {
|
|
2896
|
+
if (suppressUntil === 0 || detail === 0) return false;
|
|
2897
|
+
const suppress = inHeader && options.now() <= suppressUntil;
|
|
2898
|
+
suppressUntil = 0;
|
|
2899
|
+
return suppress;
|
|
2900
|
+
},
|
|
2901
|
+
dispose: () => {
|
|
2902
|
+
drag = void 0;
|
|
2903
|
+
suppressUntil = 0;
|
|
2904
|
+
setOffset(0);
|
|
2905
|
+
}
|
|
2906
|
+
};
|
|
2907
|
+
}
|
|
2785
2908
|
function controlledFileDragEvent(type, files, initialDropEffect) {
|
|
2786
2909
|
const event = new Event(type, {
|
|
2787
2910
|
bubbles: true,
|
|
@@ -2839,6 +2962,48 @@ window.__ModuleLoader__.load({
|
|
|
2839
2962
|
function drawerScrimVisible(sidebarCollapsed, overlayActive) {
|
|
2840
2963
|
return !sidebarCollapsed && overlayActive;
|
|
2841
2964
|
}
|
|
2965
|
+
/** Only a known on-screen keyboard in an active App composer may change Enter into a line break. */
|
|
2966
|
+
function isSoftKeyboardEnterLineBreak(event, context) {
|
|
2967
|
+
if (context.nativeState?.imeVisible !== true || context.nativeState.noHardwareKeyboard !== true || !context.editable || !context.activeSession || context.commandMenuOpen || context.recentlyComposing) return false;
|
|
2968
|
+
if (!event.isTrusted || event.key !== "Enter" || event.isComposing || event.keyCode === 229 || event.repeat) return false;
|
|
2969
|
+
return !event.shiftKey && !event.altKey && !event.ctrlKey && !event.metaKey && !event.getModifierState("AltGraph");
|
|
2970
|
+
}
|
|
2971
|
+
/** Attach the App-only Enter translation while retaining DSH's original physical-key semantics. */
|
|
2972
|
+
function bindComposerSoftEnter(editor, context) {
|
|
2973
|
+
let composing = false;
|
|
2974
|
+
let composingUntil = 0;
|
|
2975
|
+
const onCompositionStart = () => {
|
|
2976
|
+
composing = true;
|
|
2977
|
+
};
|
|
2978
|
+
const onCompositionEnd = () => {
|
|
2979
|
+
composing = false;
|
|
2980
|
+
composingUntil = performance.now() + 10;
|
|
2981
|
+
};
|
|
2982
|
+
const onKeyDown = (event) => {
|
|
2983
|
+
if (!isSoftKeyboardEnterLineBreak(event, {
|
|
2984
|
+
...context(),
|
|
2985
|
+
recentlyComposing: composing || editor.hasAttribute("data-composer-composing") || performance.now() < composingUntil
|
|
2986
|
+
})) return;
|
|
2987
|
+
event.preventDefault();
|
|
2988
|
+
event.stopImmediatePropagation();
|
|
2989
|
+
editor.dispatchEvent(new KeyboardEvent("keydown", {
|
|
2990
|
+
key: "Enter",
|
|
2991
|
+
code: "Enter",
|
|
2992
|
+
shiftKey: true,
|
|
2993
|
+
bubbles: true,
|
|
2994
|
+
cancelable: true,
|
|
2995
|
+
composed: true
|
|
2996
|
+
}));
|
|
2997
|
+
};
|
|
2998
|
+
editor.addEventListener("compositionstart", onCompositionStart);
|
|
2999
|
+
editor.addEventListener("compositionend", onCompositionEnd);
|
|
3000
|
+
editor.addEventListener("keydown", onKeyDown, { capture: true });
|
|
3001
|
+
return () => {
|
|
3002
|
+
editor.removeEventListener("compositionstart", onCompositionStart);
|
|
3003
|
+
editor.removeEventListener("compositionend", onCompositionEnd);
|
|
3004
|
+
editor.removeEventListener("keydown", onKeyDown, { capture: true });
|
|
3005
|
+
};
|
|
3006
|
+
}
|
|
2842
3007
|
/** Check that an asynchronous picker result still belongs to its originating session and composer. */
|
|
2843
3008
|
function isComposerMediaOriginCurrent(origin, current) {
|
|
2844
3009
|
return !current.disposed && origin.generation === current.generation && origin.href === current.href && origin.composer !== null && current.composerConnected && origin.composer === current.composer && origin.sessionRoot !== null && origin.sessionRoot === current.sessionRoot && origin.sessionId !== null && origin.sessionId === current.sessionId;
|
|
@@ -2935,6 +3100,9 @@ window.__ModuleLoader__.load({
|
|
|
2935
3100
|
const canAcceptComposerDrop = () => preflightComposerImageDrop(document, [preflightFile]);
|
|
2936
3101
|
const mediaPickerAbortController = new AbortController();
|
|
2937
3102
|
const browserPickerCleanups = /* @__PURE__ */ new Set();
|
|
3103
|
+
let boundEnterEditor = null;
|
|
3104
|
+
let boundEnterCard = null;
|
|
3105
|
+
let disposeEnterBinding;
|
|
2938
3106
|
const sessionTokens = /* @__PURE__ */ new WeakMap();
|
|
2939
3107
|
let nextSessionToken = 0;
|
|
2940
3108
|
const currentSessionOrigin = () => {
|
|
@@ -3135,6 +3303,101 @@ window.__ModuleLoader__.load({
|
|
|
3135
3303
|
if (branch === null || branch.hasAttribute("disabled") || branch.getAttribute("aria-disabled") === "true") return;
|
|
3136
3304
|
window.setTimeout(showBranchToast, 80);
|
|
3137
3305
|
};
|
|
3306
|
+
let stripHeader;
|
|
3307
|
+
const stripParts = () => {
|
|
3308
|
+
const row = stripHeader?.querySelector("[class*=\"_titleRow\"]");
|
|
3309
|
+
if (row === void 0 || row === null) return void 0;
|
|
3310
|
+
return {
|
|
3311
|
+
row,
|
|
3312
|
+
lead: stripHeader?.querySelector("[class*=\"_headerLeading\"]") ?? void 0
|
|
3313
|
+
};
|
|
3314
|
+
};
|
|
3315
|
+
const stripRange = () => {
|
|
3316
|
+
const parts = stripParts();
|
|
3317
|
+
if (!overlayQuery.matches || parts === void 0) return 0;
|
|
3318
|
+
const overflow = measureHeaderStripOverflow(parts.row);
|
|
3319
|
+
return overflow > 1 ? overflow : 0;
|
|
3320
|
+
};
|
|
3321
|
+
const applyStrip = (offset) => {
|
|
3322
|
+
const parts = stripParts();
|
|
3323
|
+
if (parts === void 0) return;
|
|
3324
|
+
const value = offset === 0 ? "" : `translateX(${-offset}px)`;
|
|
3325
|
+
if (parts.row.style.transform !== value) parts.row.style.transform = value;
|
|
3326
|
+
if (parts.lead !== void 0 && parts.lead.style.transform !== value) parts.lead.style.transform = value;
|
|
3327
|
+
};
|
|
3328
|
+
const pan = createHeaderStripPanController({
|
|
3329
|
+
range: stripRange,
|
|
3330
|
+
render: applyStrip,
|
|
3331
|
+
now: () => performance.now()
|
|
3332
|
+
});
|
|
3333
|
+
const syncPanHeader = (header) => {
|
|
3334
|
+
if (header !== stripHeader) {
|
|
3335
|
+
pan.dispose();
|
|
3336
|
+
stripHeader = header;
|
|
3337
|
+
}
|
|
3338
|
+
pan.sync();
|
|
3339
|
+
applyStrip(pan.offset());
|
|
3340
|
+
};
|
|
3341
|
+
let activeTouchId;
|
|
3342
|
+
const onStripTouchStart = (event) => {
|
|
3343
|
+
if (activeTouchId !== void 0) pan.cancel();
|
|
3344
|
+
activeTouchId = void 0;
|
|
3345
|
+
pan.resetClickSuppression();
|
|
3346
|
+
if (event.touches.length !== 1) return;
|
|
3347
|
+
if (!overlayQuery.matches) return;
|
|
3348
|
+
if (!(event.target instanceof Element)) return;
|
|
3349
|
+
if (event.target.closest("[data-dsh-mobile-header]") !== stripHeader) return;
|
|
3350
|
+
if (event.target.closest("[data-dsh-mobile-header] [class*=\"_titleRow\"],[data-dsh-mobile-header] [class*=\"_headerLeading\"]") === null) return;
|
|
3351
|
+
if (stripRange() === 0) return;
|
|
3352
|
+
const touch = event.touches[0];
|
|
3353
|
+
if (touch === void 0) return;
|
|
3354
|
+
activeTouchId = touch.identifier;
|
|
3355
|
+
pan.start(touch.clientX, touch.clientY);
|
|
3356
|
+
};
|
|
3357
|
+
const onStripTouchMove = (event) => {
|
|
3358
|
+
if (activeTouchId === void 0) return;
|
|
3359
|
+
if (event.touches.length !== 1) {
|
|
3360
|
+
activeTouchId = void 0;
|
|
3361
|
+
pan.cancel();
|
|
3362
|
+
return;
|
|
3363
|
+
}
|
|
3364
|
+
const touch = event.touches[0];
|
|
3365
|
+
if (touch === void 0 || touch.identifier !== activeTouchId) return;
|
|
3366
|
+
if (pan.move(touch.clientX, touch.clientY)) event.preventDefault();
|
|
3367
|
+
};
|
|
3368
|
+
const onStripTouchEnd = (event) => {
|
|
3369
|
+
if (activeTouchId === void 0 || !Array.from(event.changedTouches).some((touch) => touch.identifier === activeTouchId)) return;
|
|
3370
|
+
activeTouchId = void 0;
|
|
3371
|
+
pan.end();
|
|
3372
|
+
};
|
|
3373
|
+
const onStripTouchCancel = (event) => {
|
|
3374
|
+
if (activeTouchId === void 0 || !Array.from(event.changedTouches).some((touch) => touch.identifier === activeTouchId)) return;
|
|
3375
|
+
activeTouchId = void 0;
|
|
3376
|
+
pan.cancel();
|
|
3377
|
+
};
|
|
3378
|
+
const onStripFocus = (event) => {
|
|
3379
|
+
if (!(event.target instanceof HTMLElement)) return;
|
|
3380
|
+
const header = event.target.closest("[data-dsh-mobile-header]");
|
|
3381
|
+
if (header === null || header !== stripHeader || stripRange() === 0 || event.target.closest("[class*=\"_titleRow\"],[class*=\"_headerLeading\"]") === null) return;
|
|
3382
|
+
const focused = event.target.getBoundingClientRect();
|
|
3383
|
+
const visible = header.getBoundingClientRect();
|
|
3384
|
+
pan.reveal(focused.left, focused.right, visible.left + 42, visible.right - 8);
|
|
3385
|
+
};
|
|
3386
|
+
const onStripClickCapture = (event) => {
|
|
3387
|
+
const inHeader = event.target instanceof Element && event.target.closest("[data-dsh-mobile-header]") !== null;
|
|
3388
|
+
if (!pan.suppressClick(inHeader, event.detail)) return;
|
|
3389
|
+
event.preventDefault();
|
|
3390
|
+
event.stopPropagation();
|
|
3391
|
+
};
|
|
3392
|
+
document.addEventListener("touchstart", onStripTouchStart, true);
|
|
3393
|
+
document.addEventListener("touchmove", onStripTouchMove, {
|
|
3394
|
+
capture: true,
|
|
3395
|
+
passive: false
|
|
3396
|
+
});
|
|
3397
|
+
document.addEventListener("touchend", onStripTouchEnd, true);
|
|
3398
|
+
document.addEventListener("touchcancel", onStripTouchCancel, true);
|
|
3399
|
+
document.addEventListener("focusin", onStripFocus, true);
|
|
3400
|
+
document.addEventListener("click", onStripClickCapture, true);
|
|
3138
3401
|
document.addEventListener("click", onBranchClick, true);
|
|
3139
3402
|
let frame;
|
|
3140
3403
|
let sidebar;
|
|
@@ -3200,6 +3463,19 @@ window.__ModuleLoader__.load({
|
|
|
3200
3463
|
});
|
|
3201
3464
|
};
|
|
3202
3465
|
document.addEventListener("click", animateNavigation);
|
|
3466
|
+
const syncComposerEnterNewline = (composerCard) => {
|
|
3467
|
+
const editor = composerCard?.querySelector("[data-composer-input][contenteditable=\"true\"]") ?? null;
|
|
3468
|
+
if (editor === boundEnterEditor && composerCard === boundEnterCard) return;
|
|
3469
|
+
disposeEnterBinding?.();
|
|
3470
|
+
boundEnterEditor = editor;
|
|
3471
|
+
boundEnterCard = composerCard;
|
|
3472
|
+
disposeEnterBinding = editor === null || composerCard === null ? void 0 : bindComposerSoftEnter(editor, () => ({
|
|
3473
|
+
nativeState: window.__DSH_MOBILE_NATIVE__ === void 0 ? null : window.__DSH_MOBILE_KEYBOARD_STATE__,
|
|
3474
|
+
editable: editor.isConnected && editor.getAttribute("contenteditable") === "true" && editor.getAttribute("aria-disabled") !== "true" && editor.getAttribute("aria-haspopup") !== "menu" && composerCard.getAttribute("aria-busy") !== "true",
|
|
3475
|
+
activeSession: currentSessionOrigin().sessionId !== null,
|
|
3476
|
+
commandMenuOpen: composerCard.querySelector("[data-trigger-menu],button[aria-haspopup=\"listbox\"][aria-expanded=\"true\"]") !== null
|
|
3477
|
+
}));
|
|
3478
|
+
};
|
|
3203
3479
|
const syncMediaBinding = (composer) => {
|
|
3204
3480
|
const previousComposer = boundComposer;
|
|
3205
3481
|
boundComposer = composer;
|
|
@@ -3222,6 +3498,8 @@ window.__ModuleLoader__.load({
|
|
|
3222
3498
|
const handle = frame === void 0 ? void 0 : firstByClassSuffix(frame, "_handle");
|
|
3223
3499
|
markNativeMobileSettings(document);
|
|
3224
3500
|
if (center === void 0) {
|
|
3501
|
+
syncPanHeader(void 0);
|
|
3502
|
+
syncComposerEnterNewline(null);
|
|
3225
3503
|
bindHistoryScroller(void 0);
|
|
3226
3504
|
syncMediaBinding(null);
|
|
3227
3505
|
setCameraActionDisabled(true, label("Apri prima una sessione", "Open a session first", "请先打开会话"));
|
|
@@ -3230,7 +3508,9 @@ window.__ModuleLoader__.load({
|
|
|
3230
3508
|
}
|
|
3231
3509
|
if (center !== void 0) {
|
|
3232
3510
|
center.dataset.dshMobileCenter = "true";
|
|
3233
|
-
center.querySelector("header")
|
|
3511
|
+
const header = center.querySelector("header") ?? void 0;
|
|
3512
|
+
header?.setAttribute("data-dsh-mobile-header", "true");
|
|
3513
|
+
syncPanHeader(header);
|
|
3234
3514
|
viewArea = firstByClassSuffix(center, "_viewArea");
|
|
3235
3515
|
if (viewArea !== void 0) viewArea.dataset.dshMobileView = "true";
|
|
3236
3516
|
const conversation = center.querySelector("[data-conversation-scroll]");
|
|
@@ -3262,6 +3542,7 @@ window.__ModuleLoader__.load({
|
|
|
3262
3542
|
const composerCard = center.querySelector("[data-composer-card]");
|
|
3263
3543
|
const composerRow = composerCard?.querySelector(":scope > [data-input-scroll]")?.nextElementSibling;
|
|
3264
3544
|
if (!(composerRow instanceof HTMLElement)) {
|
|
3545
|
+
syncComposerEnterNewline(null);
|
|
3265
3546
|
syncMediaBinding(null);
|
|
3266
3547
|
setCameraActionDisabled(true, label("Apri prima una sessione", "Open a session first", "请先打开会话"));
|
|
3267
3548
|
cameraButton.remove();
|
|
@@ -3274,6 +3555,7 @@ window.__ModuleLoader__.load({
|
|
|
3274
3555
|
if (composerTools !== void 0) {
|
|
3275
3556
|
composerTools.dataset.dshMobileComposerTools = "true";
|
|
3276
3557
|
syncMediaBinding(composerCard ?? null);
|
|
3558
|
+
syncComposerEnterNewline(composerCard ?? null);
|
|
3277
3559
|
const composerInput = composerCard?.querySelector("textarea");
|
|
3278
3560
|
const composerEditor = composerCard?.querySelector("[contenteditable=\"true\"],[contenteditable=\"plaintext-only\"]");
|
|
3279
3561
|
const composerBusy = composerCard?.getAttribute("aria-busy") === "true" || composerInput?.disabled === true || composerInput?.readOnly === true || composerEditor?.getAttribute("aria-disabled") === "true";
|
|
@@ -3284,7 +3566,7 @@ window.__ModuleLoader__.load({
|
|
|
3284
3566
|
const commandMenu = composerCard?.querySelector("[data-trigger-menu]") ?? null;
|
|
3285
3567
|
if (commandMenu !== null) placeCameraAction(commandMenu);
|
|
3286
3568
|
else cameraButton.remove();
|
|
3287
|
-
}
|
|
3569
|
+
} else syncComposerEnterNewline(null);
|
|
3288
3570
|
if (composerTrailing !== void 0 && composerTrailing !== composerTools) {
|
|
3289
3571
|
composerTrailing.dataset.dshMobileComposerTrailing = "true";
|
|
3290
3572
|
const modelTrigger = composerTrailing.querySelector("button[aria-label^=\"选择模型\"],button[aria-label^=\"Select model\"],button[aria-label^=\"Seleziona modello\"]");
|
|
@@ -3356,9 +3638,18 @@ window.__ModuleLoader__.load({
|
|
|
3356
3638
|
mediaPickerAbortController.abort();
|
|
3357
3639
|
restoreLanguageMarker();
|
|
3358
3640
|
for (const cleanup of [...browserPickerCleanups]) cleanup();
|
|
3641
|
+
disposeEnterBinding?.();
|
|
3359
3642
|
observer.disconnect();
|
|
3360
3643
|
overlayQuery.removeEventListener("change", schedule);
|
|
3361
3644
|
document.removeEventListener("click", onBranchClick, true);
|
|
3645
|
+
document.removeEventListener("touchstart", onStripTouchStart, true);
|
|
3646
|
+
document.removeEventListener("touchmove", onStripTouchMove, true);
|
|
3647
|
+
document.removeEventListener("touchend", onStripTouchEnd, true);
|
|
3648
|
+
document.removeEventListener("touchcancel", onStripTouchCancel, true);
|
|
3649
|
+
document.removeEventListener("focusin", onStripFocus, true);
|
|
3650
|
+
document.removeEventListener("click", onStripClickCapture, true);
|
|
3651
|
+
pan.dispose();
|
|
3652
|
+
applyStrip(0);
|
|
3362
3653
|
cameraButton.removeEventListener("pointerdown", quietMediaPointer);
|
|
3363
3654
|
cameraButton.removeEventListener("click", takePhoto);
|
|
3364
3655
|
if (branchToastTimer !== 0) window.clearTimeout(branchToastTimer);
|
|
@@ -3383,6 +3674,16 @@ window.__ModuleLoader__.load({
|
|
|
3383
3674
|
//#endregion
|
|
3384
3675
|
//#region src/local-admin-host.ts
|
|
3385
3676
|
/** Browser-safe desktop-admin Host checks. Do not import Node APIs here. */
|
|
3677
|
+
const DESKTOP_ADMIN_HEADER = "x-dsh-mobile-desktop-admin";
|
|
3678
|
+
const DESKTOP_ADMIN_MARKER = "dsh-app://app";
|
|
3679
|
+
/** Add the forwarded Desktop marker only to this plugin's mutating admin requests. */
|
|
3680
|
+
function localAdminRequestHeaders(init, location) {
|
|
3681
|
+
const headers = new Headers(init?.headers);
|
|
3682
|
+
if (!headers.has("content-type")) headers.set("content-type", "application/json");
|
|
3683
|
+
headers.delete(DESKTOP_ADMIN_HEADER);
|
|
3684
|
+
if (init?.method?.toUpperCase() === "POST" && location.protocol === "dsh-app:" && location.hostname === "app") headers.set(DESKTOP_ADMIN_HEADER, DESKTOP_ADMIN_MARKER);
|
|
3685
|
+
return headers;
|
|
3686
|
+
}
|
|
3386
3687
|
function unwrapBrackets(hostname) {
|
|
3387
3688
|
return hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
|
|
3388
3689
|
}
|
|
@@ -3423,11 +3724,12 @@ window.__ModuleLoader__.load({
|
|
|
3423
3724
|
/**
|
|
3424
3725
|
* Whether the current browser document should mount the desktop Mobile access
|
|
3425
3726
|
* control. Dedicated Mobile HTTPS (the phone surface) stays native even when
|
|
3426
|
-
* the Host is a private LAN address.
|
|
3727
|
+
* the Host is a private LAN address. DSH Desktop (dsh-app://app) has neither,
|
|
3728
|
+
* so its protocol gated on the fixed hostname "app" counts too.
|
|
3427
3729
|
*/
|
|
3428
|
-
function isDesktopAdminSurface(hostname, search = "", frontend) {
|
|
3730
|
+
function isDesktopAdminSurface(hostname, search = "", frontend, protocol = "") {
|
|
3429
3731
|
const query = search.startsWith("?") ? search.slice(1) : search;
|
|
3430
|
-
return isLocalAdminHostname(hostname) && frontend !== "dedicated" && !new URLSearchParams(query).has("dsh-mobile-preview");
|
|
3732
|
+
return (protocol === "dsh-app:" ? hostname === "app" : (protocol === "" || protocol === "http:" || protocol === "https:") && isLocalAdminHostname(hostname)) && frontend !== "dedicated" && !new URLSearchParams(query).has("dsh-mobile-preview");
|
|
3431
3733
|
}
|
|
3432
3734
|
//#endregion
|
|
3433
3735
|
//#region src/client.ts
|
|
@@ -3748,10 +4050,7 @@ window.__ModuleLoader__.load({
|
|
|
3748
4050
|
const response = await fetch(path, {
|
|
3749
4051
|
...init,
|
|
3750
4052
|
signal: controller.signal,
|
|
3751
|
-
headers:
|
|
3752
|
-
"content-type": "application/json",
|
|
3753
|
-
...init?.headers
|
|
3754
|
-
}
|
|
4053
|
+
headers: localAdminRequestHeaders(init, location)
|
|
3755
4054
|
});
|
|
3756
4055
|
const body = await response.json();
|
|
3757
4056
|
if (!response.ok) throw new Error(typeof body.error === "string" ? body.error : `HTTP ${String(response.status)}`);
|
|
@@ -3851,6 +4150,18 @@ window.__ModuleLoader__.load({
|
|
|
3851
4150
|
control_channel_failed: "controlChannelFailed",
|
|
3852
4151
|
cpolar_component_missing: "cpolarMissing",
|
|
3853
4152
|
cpolar_component_invalid: "cpolarInvalid",
|
|
4153
|
+
cpolar_download_failed: "cpolarDownloadFailed",
|
|
4154
|
+
cpolar_download_timeout: "cpolarDownloadTimeout",
|
|
4155
|
+
cpolar_download_size_mismatch: "cpolarIntegrityFailed",
|
|
4156
|
+
cpolar_download_hash_mismatch: "cpolarIntegrityFailed",
|
|
4157
|
+
cpolar_executable_hash_mismatch: "cpolarIntegrityFailed",
|
|
4158
|
+
cpolar_download_redirect_missing: "cpolarDownloadFailed",
|
|
4159
|
+
cpolar_download_redirect_invalid: "cpolarDownloadFailed",
|
|
4160
|
+
cpolar_download_redirect_rejected: "cpolarDownloadFailed",
|
|
4161
|
+
cpolar_installer_missing: "cpolarExtractFailed",
|
|
4162
|
+
cpolar_executable_missing: "cpolarExtractFailed",
|
|
4163
|
+
cpolar_extract_failed: "cpolarExtractFailed",
|
|
4164
|
+
cpolar_storage_failed: "cpolarStorageFailed",
|
|
3854
4165
|
cpolar_config_missing: "cpolarConfigMissing",
|
|
3855
4166
|
cpolar_config_invalid: "cpolarConfigInvalid",
|
|
3856
4167
|
cpolar_port_unavailable: "cpolarPortUnavailable",
|
|
@@ -3986,6 +4297,8 @@ window.__ModuleLoader__.load({
|
|
|
3986
4297
|
*/
|
|
3987
4298
|
const remoteFailureTextFor = (error, fallbackKey) => {
|
|
3988
4299
|
const message = error instanceof Error ? error.message : String(error);
|
|
4300
|
+
const cpolarHttpStatus = /^cpolar_download_http_(\d{3})$/u.exec(message)?.[1];
|
|
4301
|
+
if (cpolarHttpStatus !== void 0) return t("cpolarDownloadHttp", { status: cpolarHttpStatus });
|
|
3989
4302
|
const key = REMOTE_ERROR_MESSAGE_KEYS[message];
|
|
3990
4303
|
return key === void 0 ? t(fallbackKey, { error: message }) : t(key);
|
|
3991
4304
|
};
|
|
@@ -8224,7 +8537,7 @@ window.__ModuleLoader__.load({
|
|
|
8224
8537
|
return trustAuthenticatedGatewayConnection(ctx.get("connection"));
|
|
8225
8538
|
}, "dsh-mobile: authenticated gateway client trust");
|
|
8226
8539
|
ctx.effect(() => {
|
|
8227
|
-
const desktopAdmin = isDesktopAdminSurface(location.hostname, location.search, window.__DSH_MOBILE_FRONTEND__);
|
|
8540
|
+
const desktopAdmin = isDesktopAdminSurface(location.hostname, location.search, window.__DSH_MOBILE_FRONTEND__, location.protocol);
|
|
8228
8541
|
const style = element("style");
|
|
8229
8542
|
style.dataset.plugin = "dsh-mobile";
|
|
8230
8543
|
style.textContent = desktopAdmin ? CONTROL_STYLES : NATIVE_MOBILE_STYLES;
|