dsh-pocket 0.1.0 → 1.0.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/README.md +23 -0
- package/bin/dsh-pocket.mjs +14 -4
- package/client/build.mjs +1 -1
- package/client/client.js +1215 -28
- package/client/index.jsx +9 -4
- package/client/mobile/LICENSE.dsh-web-mobile +4 -0
- package/client/mobile/MobileDrawerFooter.tsx +56 -0
- package/client/mobile/MobileNavOverlay.tsx +155 -0
- package/client/mobile/MobileNavToggle.tsx +52 -0
- package/client/mobile/locales.ts +23 -0
- package/client/mobile/mobile-apply.tsx +272 -0
- package/client/mobile/mobile.css.ts +832 -0
- package/lib/proxy.mjs +41 -3
- package/lib/tunnel.mjs +25 -10
- package/lib/web-rpc.js +7 -1
- package/package.json +6 -3
package/client/client.js
CHANGED
|
@@ -30,7 +30,7 @@ __export(index_exports, {
|
|
|
30
30
|
redactStatus: () => redactStatus
|
|
31
31
|
});
|
|
32
32
|
module.exports = __toCommonJS(index_exports);
|
|
33
|
-
var
|
|
33
|
+
var import_react2 = require("react");
|
|
34
34
|
|
|
35
35
|
// client/api.js
|
|
36
36
|
var POCKET_RPC_CHANNEL = "/dsh-pocket";
|
|
@@ -52,9 +52,1195 @@ function redactStatus(s) {
|
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// client/mobile/MobileNavToggle.tsx
|
|
56
|
+
var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
57
|
+
function MobileNavToggle({ toggleSidebar, t }) {
|
|
58
|
+
const toggleExplorer = () => {
|
|
59
|
+
const frame = document.querySelector('[data-mobile-nav="frame"]');
|
|
60
|
+
if (frame === null) return;
|
|
61
|
+
if (frame.hasAttribute("data-aionui-explorer-open")) {
|
|
62
|
+
frame.removeAttribute("data-aionui-explorer-open");
|
|
63
|
+
} else {
|
|
64
|
+
frame.setAttribute("data-aionui-explorer-open", "");
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
|
|
68
|
+
"button",
|
|
69
|
+
{
|
|
70
|
+
type: "button",
|
|
71
|
+
"data-mobile-nav": "toggle",
|
|
72
|
+
"aria-label": t("open"),
|
|
73
|
+
title: t("open"),
|
|
74
|
+
onClick: () => toggleSidebar()
|
|
75
|
+
},
|
|
76
|
+
/* @__PURE__ */ React.createElement(import_dsh_client_ui_primitives.IconPanelLeftOutline16, { size: 16 })
|
|
77
|
+
), /* @__PURE__ */ React.createElement(
|
|
78
|
+
"button",
|
|
79
|
+
{
|
|
80
|
+
type: "button",
|
|
81
|
+
"data-mobile-nav": "files",
|
|
82
|
+
"aria-label": t("files"),
|
|
83
|
+
title: t("files"),
|
|
84
|
+
onClick: toggleExplorer
|
|
85
|
+
},
|
|
86
|
+
/* @__PURE__ */ React.createElement(import_dsh_client_ui_primitives.IconFolderOpenOutline16, { size: 16 })
|
|
87
|
+
));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// client/mobile/MobileNavOverlay.tsx
|
|
91
|
+
var import_react = require("react");
|
|
92
|
+
var import_dsh_client_ui_primitives2 = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
93
|
+
var MOBILE_QUERY = "(max-width: 1023px)";
|
|
94
|
+
function useMobile() {
|
|
95
|
+
const [mobile, setMobile] = (0, import_react.useState)(() => window.matchMedia(MOBILE_QUERY).matches);
|
|
96
|
+
(0, import_react.useEffect)(() => {
|
|
97
|
+
const query = window.matchMedia(MOBILE_QUERY);
|
|
98
|
+
const onChange = (event) => setMobile(event.matches);
|
|
99
|
+
query.addEventListener("change", onChange);
|
|
100
|
+
return () => query.removeEventListener("change", onChange);
|
|
101
|
+
}, []);
|
|
102
|
+
return mobile;
|
|
103
|
+
}
|
|
104
|
+
function findFrame() {
|
|
105
|
+
return document.querySelector("[data-shell-overlay]")?.parentElement ?? null;
|
|
106
|
+
}
|
|
107
|
+
function MobileNavOverlay({ toggleSidebar, t }) {
|
|
108
|
+
const mobile = useMobile();
|
|
109
|
+
const [open, setOpen] = (0, import_react.useState)(false);
|
|
110
|
+
const [fabVisible, setFabVisible] = (0, import_react.useState)(false);
|
|
111
|
+
(0, import_react.useLayoutEffect)(() => {
|
|
112
|
+
if (!mobile) {
|
|
113
|
+
setOpen(false);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const frame = findFrame();
|
|
117
|
+
if (frame === null) return;
|
|
118
|
+
frame.setAttribute("data-mobile-nav", "frame");
|
|
119
|
+
const sync = () => setOpen(!frame.hasAttribute("data-sidebar-collapsed"));
|
|
120
|
+
sync();
|
|
121
|
+
const observer = new MutationObserver(sync);
|
|
122
|
+
observer.observe(frame, { attributes: true, attributeFilter: ["data-sidebar-collapsed"] });
|
|
123
|
+
return () => {
|
|
124
|
+
observer.disconnect();
|
|
125
|
+
frame.removeAttribute("data-mobile-nav");
|
|
126
|
+
};
|
|
127
|
+
}, [mobile]);
|
|
128
|
+
(0, import_react.useEffect)(() => {
|
|
129
|
+
if (!mobile) {
|
|
130
|
+
setFabVisible(false);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const sync = () => setFabVisible(document.querySelector('[data-phase="active"]') === null);
|
|
134
|
+
sync();
|
|
135
|
+
const observer = new MutationObserver(sync);
|
|
136
|
+
observer.observe(document.documentElement, {
|
|
137
|
+
subtree: true,
|
|
138
|
+
childList: true,
|
|
139
|
+
attributes: true,
|
|
140
|
+
attributeFilter: ["data-phase"]
|
|
141
|
+
});
|
|
142
|
+
return () => observer.disconnect();
|
|
143
|
+
}, [mobile]);
|
|
144
|
+
(0, import_react.useEffect)(() => {
|
|
145
|
+
if (!mobile || !open) return;
|
|
146
|
+
const onKeyDown = (event) => {
|
|
147
|
+
if (event.key === "Escape" && document.querySelector('[aria-modal="true"]') === null) toggleSidebar();
|
|
148
|
+
};
|
|
149
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
150
|
+
return () => document.removeEventListener("keydown", onKeyDown, true);
|
|
151
|
+
}, [mobile, open, toggleSidebar]);
|
|
152
|
+
(0, import_react.useEffect)(() => {
|
|
153
|
+
if (!mobile || !open) return;
|
|
154
|
+
const onDrawerClick = (event) => {
|
|
155
|
+
if (document.querySelector('[aria-modal="true"]') !== null) return;
|
|
156
|
+
const target = event.target;
|
|
157
|
+
if (target === null) return;
|
|
158
|
+
const drawer = document.querySelector('[data-mobile-nav="frame"] > :first-child');
|
|
159
|
+
if (drawer === null || !drawer.contains(target)) return;
|
|
160
|
+
if (target.closest('[class*="sessionRow"] button') !== null) return;
|
|
161
|
+
const navigates = target.closest(
|
|
162
|
+
'button[data-dsh-taskboard-entry], button[data-dsh-ssh-entry], [class*="newSession"], [class*="sessionRow"], [class*="searchResultRow"], [class*="searchResultWorkspace"]'
|
|
163
|
+
);
|
|
164
|
+
if (navigates !== null) toggleSidebar();
|
|
165
|
+
};
|
|
166
|
+
document.addEventListener("click", onDrawerClick, true);
|
|
167
|
+
return () => document.removeEventListener("click", onDrawerClick, true);
|
|
168
|
+
}, [mobile, open, toggleSidebar]);
|
|
169
|
+
if (!mobile) return null;
|
|
170
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, open && /* @__PURE__ */ React.createElement(
|
|
171
|
+
"div",
|
|
172
|
+
{
|
|
173
|
+
"data-mobile-nav": "backdrop",
|
|
174
|
+
role: "button",
|
|
175
|
+
"aria-label": t("backdrop"),
|
|
176
|
+
onClick: () => toggleSidebar()
|
|
177
|
+
}
|
|
178
|
+
), fabVisible && !open && /* @__PURE__ */ React.createElement(
|
|
179
|
+
"button",
|
|
180
|
+
{
|
|
181
|
+
type: "button",
|
|
182
|
+
"data-mobile-nav": "fab",
|
|
183
|
+
"aria-label": t("open"),
|
|
184
|
+
title: t("open"),
|
|
185
|
+
onClick: () => toggleSidebar()
|
|
186
|
+
},
|
|
187
|
+
/* @__PURE__ */ React.createElement(import_dsh_client_ui_primitives2.IconPanelLeftOutline16, { size: 18 })
|
|
188
|
+
));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// client/mobile/MobileDrawerFooter.tsx
|
|
192
|
+
var import_dsh_client_ui_primitives3 = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
193
|
+
function MobileDrawerFooter({ useSessions, downloadSessionLog, toggleSidebar, t }) {
|
|
194
|
+
const sessionId = useSessions((state) => state.current);
|
|
195
|
+
const openExplorer = () => {
|
|
196
|
+
document.querySelector('[data-mobile-nav="frame"]')?.setAttribute("data-aionui-explorer-open", "");
|
|
197
|
+
toggleSidebar();
|
|
198
|
+
};
|
|
199
|
+
return /* @__PURE__ */ React.createElement("div", { "data-mobile-nav": "drawer-actions" }, /* @__PURE__ */ React.createElement(
|
|
200
|
+
"button",
|
|
201
|
+
{
|
|
202
|
+
type: "button",
|
|
203
|
+
"data-mobile-nav": "explorer",
|
|
204
|
+
"aria-label": t("files"),
|
|
205
|
+
title: t("files"),
|
|
206
|
+
onClick: openExplorer
|
|
207
|
+
},
|
|
208
|
+
/* @__PURE__ */ React.createElement(import_dsh_client_ui_primitives3.IconPanelLeftOutline16, { size: 14 }),
|
|
209
|
+
/* @__PURE__ */ React.createElement("span", null, t("files"))
|
|
210
|
+
), /* @__PURE__ */ React.createElement(
|
|
211
|
+
"button",
|
|
212
|
+
{
|
|
213
|
+
type: "button",
|
|
214
|
+
"data-mobile-nav": "session-log",
|
|
215
|
+
"aria-label": t("sessionLog"),
|
|
216
|
+
title: t("sessionLog"),
|
|
217
|
+
disabled: sessionId === void 0,
|
|
218
|
+
onClick: () => {
|
|
219
|
+
if (sessionId !== void 0) downloadSessionLog(sessionId);
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
/* @__PURE__ */ React.createElement(import_dsh_client_ui_primitives3.IconDownloadOutline16, { size: 14 }),
|
|
223
|
+
/* @__PURE__ */ React.createElement("span", null, t("sessionLog"))
|
|
224
|
+
));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// client/mobile/mobile.css.ts
|
|
228
|
+
var MOBILE_CSS = `
|
|
229
|
+
/* ---------- base control styles (rendered at any width, hidden where unused) ---------- */
|
|
230
|
+
|
|
231
|
+
[data-mobile-nav="toggle"],
|
|
232
|
+
[data-mobile-nav="files"] {
|
|
233
|
+
display: inline-flex;
|
|
234
|
+
align-items: center;
|
|
235
|
+
justify-content: center;
|
|
236
|
+
width: 28px;
|
|
237
|
+
height: 28px;
|
|
238
|
+
flex: none;
|
|
239
|
+
padding: 0;
|
|
240
|
+
border: none;
|
|
241
|
+
border-radius: 50%;
|
|
242
|
+
background: transparent;
|
|
243
|
+
color: var(--dsw-alias-label-secondary, inherit);
|
|
244
|
+
cursor: pointer;
|
|
245
|
+
-webkit-tap-highlight-color: transparent;
|
|
246
|
+
}
|
|
247
|
+
[data-mobile-nav="toggle"]:hover,
|
|
248
|
+
[data-mobile-nav="files"]:hover {
|
|
249
|
+
background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));
|
|
250
|
+
}
|
|
251
|
+
[data-mobile-nav="toggle"]:focus-visible,
|
|
252
|
+
[data-mobile-nav="files"]:focus-visible {
|
|
253
|
+
outline: 2px solid var(--dsw-alias-state-business-primary, #4f6ef7);
|
|
254
|
+
outline-offset: 1px;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/* Drawer footer actions: the relocated Session log download plus the Files
|
|
258
|
+
action that opens the dsh-web-ui explorer sheet. */
|
|
259
|
+
[data-mobile-nav="drawer-actions"] {
|
|
260
|
+
display: inline-flex;
|
|
261
|
+
align-items: center;
|
|
262
|
+
gap: 8px;
|
|
263
|
+
}
|
|
264
|
+
[data-mobile-nav="session-log"],
|
|
265
|
+
[data-mobile-nav="explorer"] {
|
|
266
|
+
display: inline-flex;
|
|
267
|
+
align-items: center;
|
|
268
|
+
justify-content: center;
|
|
269
|
+
gap: 6px;
|
|
270
|
+
height: 34px;
|
|
271
|
+
padding: 0 12px;
|
|
272
|
+
border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, .12));
|
|
273
|
+
border-radius: 12px;
|
|
274
|
+
background: transparent;
|
|
275
|
+
color: var(--dsw-alias-label-primary, inherit);
|
|
276
|
+
font-family: inherit;
|
|
277
|
+
font-size: 13px;
|
|
278
|
+
line-height: 20px;
|
|
279
|
+
cursor: pointer;
|
|
280
|
+
-webkit-tap-highlight-color: transparent;
|
|
281
|
+
}
|
|
282
|
+
[data-mobile-nav="session-log"]:hover:not(:disabled),
|
|
283
|
+
[data-mobile-nav="explorer"]:hover {
|
|
284
|
+
background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06));
|
|
285
|
+
}
|
|
286
|
+
[data-mobile-nav="session-log"]:disabled {
|
|
287
|
+
color: var(--dsw-alias-label-dimmed, rgba(0, 0, 0, .35));
|
|
288
|
+
cursor: default;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/* Floating fallback button (hero / blank phases without a session header).
|
|
292
|
+
The top clears the camera band below the status bar; when the client has
|
|
293
|
+
set viewport-fit=cover the safe-area inset moves it below the notch too. */
|
|
294
|
+
[data-mobile-nav="fab"] {
|
|
295
|
+
position: absolute;
|
|
296
|
+
top: calc(env(safe-area-inset-top, 0px) + 72px);
|
|
297
|
+
left: 10px;
|
|
298
|
+
z-index: 21;
|
|
299
|
+
display: inline-flex;
|
|
300
|
+
align-items: center;
|
|
301
|
+
justify-content: center;
|
|
302
|
+
width: 38px;
|
|
303
|
+
height: 38px;
|
|
304
|
+
padding: 0;
|
|
305
|
+
border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, .12));
|
|
306
|
+
border-radius: 50%;
|
|
307
|
+
background: var(--dsw-alias-button-floating-fill, #ffffff);
|
|
308
|
+
color: var(--dsw-alias-label-primary, inherit);
|
|
309
|
+
cursor: pointer;
|
|
310
|
+
box-shadow: 0 2px 12px rgba(0, 0, 0, .18);
|
|
311
|
+
-webkit-tap-highlight-color: transparent;
|
|
312
|
+
}
|
|
313
|
+
[data-mobile-nav="fab"]:hover {
|
|
314
|
+
background: var(--dsw-alias-button-floating-hover, rgba(0, 0, 0, .08));
|
|
315
|
+
}
|
|
316
|
+
[data-mobile-nav="fab"]:focus-visible {
|
|
317
|
+
outline: 2px solid var(--dsw-alias-state-business-primary, #4f6ef7);
|
|
318
|
+
outline-offset: 2px;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/* Dimmed backdrop under the open drawer; above every column, below the drawer. */
|
|
322
|
+
[data-mobile-nav="backdrop"] {
|
|
323
|
+
position: absolute;
|
|
324
|
+
inset: 0;
|
|
325
|
+
z-index: 30;
|
|
326
|
+
background: rgba(0, 0, 0, .45);
|
|
327
|
+
cursor: pointer;
|
|
328
|
+
animation: dsh-mobile-nav-fade .2s var(--ds-ease-in-out, ease-in-out);
|
|
329
|
+
-webkit-tap-highlight-color: transparent;
|
|
330
|
+
}
|
|
331
|
+
@keyframes dsh-mobile-nav-fade {
|
|
332
|
+
from { opacity: 0; }
|
|
333
|
+
to { opacity: 1; }
|
|
334
|
+
}
|
|
335
|
+
/* Settings sheet entrance: the official dialog mounts with no animation at
|
|
336
|
+
all, so it snaps in. Fade + slight rise/scale reads as a proper sheet. */
|
|
337
|
+
@keyframes dsh-mobile-nav-sheet-in {
|
|
338
|
+
from {
|
|
339
|
+
opacity: 0;
|
|
340
|
+
transform: translateY(14px) scale(.98);
|
|
341
|
+
}
|
|
342
|
+
to {
|
|
343
|
+
opacity: 1;
|
|
344
|
+
transform: none;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/* Preview sheet rise: the aionui preview column opens as a bottom sheet. */
|
|
348
|
+
@keyframes dsh-mobile-nav-sheet-up {
|
|
349
|
+
from {
|
|
350
|
+
opacity: 0;
|
|
351
|
+
transform: translateY(28px);
|
|
352
|
+
}
|
|
353
|
+
to {
|
|
354
|
+
opacity: 1;
|
|
355
|
+
transform: none;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/* ---------- mobile-only layout ---------- */
|
|
360
|
+
|
|
361
|
+
@media (max-width: 1023px) {
|
|
362
|
+
/* --- Phone chrome ---
|
|
363
|
+
The system status bar stays visible (no fullscreen). Two adjustments
|
|
364
|
+
make it behave:
|
|
365
|
+
- touch-action: manipulation kills double-tap-to-zoom (and the 300ms
|
|
366
|
+
tap delay) while keeping pan and pinch zoom; the client also
|
|
367
|
+
suppresses legacy-iOS gesturestart as a fallback.
|
|
368
|
+
- With the client's viewport-fit=cover, env(safe-area-inset-top) is the
|
|
369
|
+
status bar / notch height; the rules below push the app content below
|
|
370
|
+
it so the status bar never covers anything. Off notched phones (or in
|
|
371
|
+
a normal browser tab where the layout viewport already sits below the
|
|
372
|
+
status bar) the inset is 0 and nothing shifts. */
|
|
373
|
+
html,
|
|
374
|
+
body {
|
|
375
|
+
touch-action: manipulation !important;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/* AppFrame: the drawer takes the sidebar column out of grid flow, so the
|
|
379
|
+
remaining in-flow items (center, details) land in tracks 1..2: give the
|
|
380
|
+
center every pixel and keep the details track at zero. The top padding
|
|
381
|
+
clears the status bar / notch for every in-flow surface (session header,
|
|
382
|
+
messages, composer); the absolutely-positioned drawer is unaffected (its
|
|
383
|
+
containing block is the frame's padding box, i.e. still the frame top). */
|
|
384
|
+
[data-mobile-nav="frame"] {
|
|
385
|
+
position: relative !important;
|
|
386
|
+
grid-template-columns: minmax(0, 1fr) 0 0 !important;
|
|
387
|
+
padding-top: env(safe-area-inset-top, 0px) !important;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/* The sidebar column (first grid child) becomes a left drawer. The drawer
|
|
391
|
+
hugs the sidebar content exactly (the wide sidebar carries an inline
|
|
392
|
+
width, ~280px): a fixed 92vw box would leave a white strip where the
|
|
393
|
+
container background shows beside the content.
|
|
394
|
+
Closed state: translateX(-110%) \u2014 more than -100% of the max-content
|
|
395
|
+
width \u2014 guarantees the whole drawer (and its shadow, had it one) leaves
|
|
396
|
+
the viewport. A mere -100% leaves a sliver on screen; -105% (as used
|
|
397
|
+
before) left 14px of the drawer plus a long 32px-blur shadow gradient
|
|
398
|
+
visible along the left edge of the main UI. No box-shadow at all: the
|
|
399
|
+
dimmed backdrop already separates drawer from content. */
|
|
400
|
+
[data-mobile-nav="frame"] > :first-child {
|
|
401
|
+
position: absolute !important;
|
|
402
|
+
inset: 0 auto 0 0 !important;
|
|
403
|
+
width: max-content !important;
|
|
404
|
+
max-width: 92vw !important;
|
|
405
|
+
z-index: 40 !important;
|
|
406
|
+
transform: translateX(-110%);
|
|
407
|
+
transition: transform .28s var(--ds-ease-in-out, ease-in-out);
|
|
408
|
+
background: var(--dsw-alias-bg-base, #ffffff);
|
|
409
|
+
/* Keep the drawer's own content below the status bar / notch: the drawer
|
|
410
|
+
spans the full frame height (its absolute containing block is the
|
|
411
|
+
frame's padding box, so the frame's own safe-area padding does NOT
|
|
412
|
+
reach it). The drawer background paints the status-bar strip, which
|
|
413
|
+
the client's theme-color meta matches, so the strip reads seamless. */
|
|
414
|
+
padding-top: env(safe-area-inset-top, 0px) !important;
|
|
415
|
+
/* Kill the official sidebarCol right border: with the backdrop the edge
|
|
416
|
+
reads cleanly, and the settings dialog (width:100% of this box) stays
|
|
417
|
+
pixel-flush with the drawer. */
|
|
418
|
+
border-right: none !important;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/* Expanded state (frame without data-sidebar-collapsed) slides the drawer in.
|
|
422
|
+
The open state must be transform:none \u2014 NOT translateX(0): an identity
|
|
423
|
+
transform still makes the drawer the containing block for fixed-position
|
|
424
|
+
descendants (the settings dialog's .VOzbGW_overlay is portaled into the
|
|
425
|
+
sidebar DOM). With the identity transform the wide settings sheet
|
|
426
|
+
(100vw-16) overflows the 280px drawer, the dialog's focus scrolls the
|
|
427
|
+
overflow:hidden drawer to scrollLeft=102, and every static child (plus the
|
|
428
|
+
fixed overlay) shifts 102px off-screen. With transform:none the overlay is
|
|
429
|
+
viewport-anchored: it dims the full screen and the sheet sits at left:8. */
|
|
430
|
+
[data-mobile-nav="frame"]:not([data-sidebar-collapsed]) > :first-child {
|
|
431
|
+
transform: none !important;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/* Drag handles are useless on touch and would float over the drawer. */
|
|
435
|
+
[data-side="sidebar"],
|
|
436
|
+
[data-side="details"] {
|
|
437
|
+
display: none !important;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/* --- Conversation text on mobile ---
|
|
441
|
+
The official message flow keeps desktop's 32px side gutters and 16px
|
|
442
|
+
type. On a phone: shrink the type a notch and widen the lines by
|
|
443
|
+
trimming the gutters (the sidebar drawer list keeps its size). The
|
|
444
|
+
flow's scroll container is the only _scroll element holding markdown
|
|
445
|
+
<p> paragraphs \u2014 the composer's own scroll (textarea) is excluded
|
|
446
|
+
via :has(p). */
|
|
447
|
+
/* The official main scroll body reserves scrollbar-gutter for desktop
|
|
448
|
+
scrollbars (8px), which shoves every column off-center on a phone.
|
|
449
|
+
Classic desktop scrollbars (Edge/Chrome) also occupy ~8-17px in a
|
|
450
|
+
phone-sized viewport, shifting the column further. Mobile scrolling
|
|
451
|
+
is touch/wheel, so remove the scrollbar entirely on phones: the
|
|
452
|
+
column is then exactly centered in every browser. */
|
|
453
|
+
[data-phase] [class$="_scrollBody"] {
|
|
454
|
+
scrollbar-gutter: auto !important;
|
|
455
|
+
scrollbar-width: none !important;
|
|
456
|
+
}
|
|
457
|
+
[data-phase] [class$="_scrollBody"]::-webkit-scrollbar {
|
|
458
|
+
display: none !important;
|
|
459
|
+
width: 0 !important;
|
|
460
|
+
height: 0 !important;
|
|
461
|
+
}
|
|
462
|
+
/* Message action rows (copy / run-time badges) can overflow the right
|
|
463
|
+
edge on narrow screens \u2014 keep them inside the message width. */
|
|
464
|
+
[data-phase] [class$="_actions"] {
|
|
465
|
+
overflow: hidden !important;
|
|
466
|
+
}
|
|
467
|
+
[data-phase] [class$="_actions"] [class$="_timeEnd"] {
|
|
468
|
+
flex: 0 1 auto !important;
|
|
469
|
+
min-width: 0 !important;
|
|
470
|
+
overflow: hidden !important;
|
|
471
|
+
text-overflow: ellipsis !important;
|
|
472
|
+
white-space: nowrap !important;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
[data-phase] [class$="_scroll"]:has(p) {
|
|
476
|
+
padding-left: 20px !important;
|
|
477
|
+
padding-right: 20px !important;
|
|
478
|
+
font-size: 15px !important;
|
|
479
|
+
}
|
|
480
|
+
/* The official markdown styles set an explicit 16px on paragraphs and
|
|
481
|
+
list items, so the container's inherited 15px is not enough. User
|
|
482
|
+
messages render their text in a div whose class carries _text_
|
|
483
|
+
(16px too) \u2014 cover it as well. */
|
|
484
|
+
[data-phase] [class$="_scroll"]:has(p) p,
|
|
485
|
+
[data-phase] [class$="_scroll"]:has(p) li,
|
|
486
|
+
[data-phase] [class$="_scroll"]:has(p) [class*="_text_"] {
|
|
487
|
+
font-size: 15px !important;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/* --- Composer bottom row on mobile ---
|
|
491
|
+
The official row gives the model pill (trailing) flex:0 0 auto, which
|
|
492
|
+
squeezes the agent-permission pill (modes) down to 15px: the pill's
|
|
493
|
+
chevron then overflows on top of the model name. Let the permission
|
|
494
|
+
pill keep its natural width and let the model pill shrink instead.
|
|
495
|
+
Anchored by the composer card (:has(textarea)): row = last child,
|
|
496
|
+
tools = first child, permission pill = its 2nd child, model pill =
|
|
497
|
+
row's last child. */
|
|
498
|
+
[data-phase] [class*="_card"]:has(textarea) > :last-child {
|
|
499
|
+
gap: 8px !important;
|
|
500
|
+
}
|
|
501
|
+
[data-phase] [class*="_card"]:has(textarea) > :last-child > :first-child {
|
|
502
|
+
gap: 8px !important;
|
|
503
|
+
}
|
|
504
|
+
[data-phase] [class*="_card"]:has(textarea) > :last-child > :first-child > :nth-child(2) {
|
|
505
|
+
flex: 0 0 auto !important;
|
|
506
|
+
}
|
|
507
|
+
[data-phase] [class*="_card"]:has(textarea) > :last-child > :last-child {
|
|
508
|
+
flex: 1 1 auto !important;
|
|
509
|
+
min-width: 0 !important;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/* --- Session header on mobile ---
|
|
513
|
+
Layout goal: [toggle] [session title] [mode badge] in a row, with the
|
|
514
|
+
Session log capsule removed from the header (relocated to the drawer
|
|
515
|
+
footer). Stable structural hooks only:
|
|
516
|
+
[data-phase] header the session header element
|
|
517
|
+
header > :first-child titleRow (titleCluster + utilities)
|
|
518
|
+
header > :first-child > :last-child headerUtilities (Session log seat) */
|
|
519
|
+
[data-phase] header {
|
|
520
|
+
padding-right: 12px !important;
|
|
521
|
+
}
|
|
522
|
+
/* Give the title row a lane clear of the absolutely-placed toggle, then
|
|
523
|
+
balance the header: with header padding-right 12px, a 20px left
|
|
524
|
+
padding puts the title's geometric center exactly on the viewport
|
|
525
|
+
center (measured 195/195 at 390px). */
|
|
526
|
+
[data-phase] header > :first-child {
|
|
527
|
+
padding-left: 20px !important;
|
|
528
|
+
}
|
|
529
|
+
/* The directory toggle sits at the far left of the header (the header
|
|
530
|
+
is position:relative; the data-slot wrappers are display:contents). */
|
|
531
|
+
[data-mobile-nav="toggle"] {
|
|
532
|
+
position: absolute !important;
|
|
533
|
+
left: 8px !important;
|
|
534
|
+
top: 12px !important;
|
|
535
|
+
z-index: 2 !important;
|
|
536
|
+
}
|
|
537
|
+
/* The Files action sits at the FAR RIGHT of the header so it reads as a
|
|
538
|
+
distinct control from the directory toggle on the left (which opens
|
|
539
|
+
the history sidebar). */
|
|
540
|
+
[data-mobile-nav="files"] {
|
|
541
|
+
position: absolute !important;
|
|
542
|
+
left: auto !important;
|
|
543
|
+
right: 8px !important;
|
|
544
|
+
top: 12px !important;
|
|
545
|
+
z-index: 2 !important;
|
|
546
|
+
}
|
|
547
|
+
/* Session log download: gone from the header row on mobile (the utilities
|
|
548
|
+
seat holds only the session-log-export capsule). */
|
|
549
|
+
[data-phase] header > :first-child > :last-child {
|
|
550
|
+
display: none !important;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/* --- Settings dialog on mobile ---
|
|
554
|
+
Desktop: 800px two-column flex (188px nav + content). Mobile: a
|
|
555
|
+
near-full-width sheet \u2014 nav tabs wrap into rows on top, option rows
|
|
556
|
+
stay horizontal (title+description left, control right). Structural
|
|
557
|
+
selectors are scoped to the unique aria-modal dialog; every
|
|
558
|
+
settings-specific rule is gated with
|
|
559
|
+
:has(> :first-child > :last-child > button) \u2014 the settings nav tab
|
|
560
|
+
list holds <button> tabs, so the transient export dialog (the same
|
|
561
|
+
primitives Modal, header(title+close)+description+body) keeps its
|
|
562
|
+
official centered card layout. Requires :has() support
|
|
563
|
+
(Chromium 105+, 2022). */
|
|
564
|
+
[aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])) {
|
|
565
|
+
position: absolute !important;
|
|
566
|
+
left: 8px !important;
|
|
567
|
+
/* Fixed top (no translateY): a transform on the panel combined with the
|
|
568
|
+
panel overflowing the max-content drawer shifts the fixed overlay's
|
|
569
|
+
coordinate frame, dragging the whole sidebar content off-screen. The
|
|
570
|
+
safe-area inset keeps the sheet below the status bar / notch. */
|
|
571
|
+
top: calc(env(safe-area-inset-top, 0px) + 12px) !important;
|
|
572
|
+
width: calc(100vw - 16px) !important;
|
|
573
|
+
max-width: calc(100vw - 16px) !important;
|
|
574
|
+
/* Height follows the content (no dead space under a short page); it
|
|
575
|
+
caps at 100dvh-24 (less the safe-area top) and the options area
|
|
576
|
+
scrolls only then. */
|
|
577
|
+
height: auto !important;
|
|
578
|
+
max-height: min(800px, calc(100vh - 24px - env(safe-area-inset-top, 0px))) !important;
|
|
579
|
+
max-height: min(800px, calc(100dvh - 24px - env(safe-area-inset-top, 0px))) !important;
|
|
580
|
+
flex-direction: column !important;
|
|
581
|
+
border-radius: 14px !important;
|
|
582
|
+
animation: dsh-mobile-nav-sheet-in .22s var(--ds-ease-out, ease-in-out);
|
|
583
|
+
}
|
|
584
|
+
/* The settings sheet's dimmed mask fades in with the panel (the mask is
|
|
585
|
+
the first child of the overlay that directly contains the sheet). */
|
|
586
|
+
:has(> [aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"]))) > :first-child {
|
|
587
|
+
animation: dsh-mobile-nav-fade .18s var(--ds-ease-out, ease-in-out);
|
|
588
|
+
}
|
|
589
|
+
@media (prefers-reduced-motion: reduce) {
|
|
590
|
+
[aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])),
|
|
591
|
+
:has(> [aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"]))) > :first-child {
|
|
592
|
+
animation: none !important;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
/* The export dialog (not the settings sheet) must never overflow the
|
|
596
|
+
viewport: the official centered card can be wider than 390px. */
|
|
597
|
+
[aria-modal="true"]:not(:has(> :first-child > :last-child > button)) {
|
|
598
|
+
max-width: calc(100vw - 32px) !important;
|
|
599
|
+
}
|
|
600
|
+
/* Nav bar: hide the "Settings" caption (redundant on a full-width sheet)
|
|
601
|
+
and wrap the tab list so every tab is visible \u2014 a horizontal scroll cut
|
|
602
|
+
the last tab ("Plugins") off with no affordance to scroll. */
|
|
603
|
+
[aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])) > :first-child {
|
|
604
|
+
width: 100% !important;
|
|
605
|
+
flex-direction: row !important;
|
|
606
|
+
align-items: center !important;
|
|
607
|
+
gap: 6px !important;
|
|
608
|
+
padding: 10px 12px 8px !important;
|
|
609
|
+
}
|
|
610
|
+
[aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])) > :first-child > :first-child {
|
|
611
|
+
display: none !important;
|
|
612
|
+
}
|
|
613
|
+
[aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])) > :first-child > :last-child {
|
|
614
|
+
flex-direction: row !important;
|
|
615
|
+
flex-wrap: wrap !important;
|
|
616
|
+
width: 100% !important;
|
|
617
|
+
gap: 6px !important;
|
|
618
|
+
overflow: visible !important;
|
|
619
|
+
}
|
|
620
|
+
/* Content toolbar (Open configuration file + close): spread to the edges
|
|
621
|
+
instead of clustering right with a dead zone on the left. The toolbar
|
|
622
|
+
children carry official auto-margins that would defeat space-between,
|
|
623
|
+
so neutralize them. The close button gets a round tappable base so it
|
|
624
|
+
reads as its own control, not part of the outline button. */
|
|
625
|
+
[aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])) > :last-child > :first-child {
|
|
626
|
+
justify-content: space-between !important;
|
|
627
|
+
align-items: center !important;
|
|
628
|
+
padding: 0 12px !important;
|
|
629
|
+
min-height: 40px !important;
|
|
630
|
+
}
|
|
631
|
+
[aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])) > :last-child > :first-child > * {
|
|
632
|
+
margin-left: 0 !important;
|
|
633
|
+
margin-right: 0 !important;
|
|
634
|
+
}
|
|
635
|
+
[aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])) > :last-child > :first-child > :last-child {
|
|
636
|
+
width: 32px !important;
|
|
637
|
+
height: 32px !important;
|
|
638
|
+
border-radius: 50% !important;
|
|
639
|
+
display: inline-flex !important;
|
|
640
|
+
align-items: center !important;
|
|
641
|
+
justify-content: center !important;
|
|
642
|
+
background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .06)) !important;
|
|
643
|
+
}
|
|
644
|
+
/* Appearance mode cards: the official cube row renders three tall
|
|
645
|
+
vertical cards (~268px) that eat half the sheet. Turn them into a
|
|
646
|
+
compact horizontal trio (icon + label inline, equal widths).
|
|
647
|
+
Relies on the official cube-row class name of this version. */
|
|
648
|
+
[aria-modal="true"] [class$="_cubeRow"] {
|
|
649
|
+
gap: 6px !important;
|
|
650
|
+
}
|
|
651
|
+
[aria-modal="true"] [class$="_cubeRow"] > * {
|
|
652
|
+
flex: 1 1 0 !important;
|
|
653
|
+
flex-direction: row !important;
|
|
654
|
+
align-items: center !important;
|
|
655
|
+
justify-content: center !important;
|
|
656
|
+
gap: 6px !important;
|
|
657
|
+
padding: 10px 8px !important;
|
|
658
|
+
min-height: 0 !important;
|
|
659
|
+
}
|
|
660
|
+
/* Content: the options scroll area gets bottom breathing room so the last
|
|
661
|
+
row never sits flush against the sheet's rounded corner. */
|
|
662
|
+
[aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])) > :last-child {
|
|
663
|
+
flex: 1 1 auto !important;
|
|
664
|
+
min-height: 0 !important;
|
|
665
|
+
}
|
|
666
|
+
[aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])) > :last-child > :last-child {
|
|
667
|
+
padding: 0 12px 24px !important;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/* ---------- dsh-web-ui family compatibility ----------
|
|
671
|
+
The linxin666 plugin suite extends the shell frame directly:
|
|
672
|
+
- aionui-panel appends two trailing grid columns (explorer / preview)
|
|
673
|
+
plus absolute drag handles to [data-dsh-frame]; its 5-track inline
|
|
674
|
+
grid is already overridden above, but the handles and columns would
|
|
675
|
+
still float over the main UI. On mobile the columns leave the grid
|
|
676
|
+
as floating bottom sheets and keep their own visibility state \u2014
|
|
677
|
+
the suite's collapse chevron / preview tabs still work, so no
|
|
678
|
+
feature is lost. The task-board / ssh plugins inject sidebar
|
|
679
|
+
entries and center-column takeover panels; the entries need
|
|
680
|
+
spacing and the kanban needs scrollable columns. */
|
|
681
|
+
|
|
682
|
+
/* Touch devices: the drag handles are useless \u2014 the floating expand
|
|
683
|
+
button is the opener. */
|
|
684
|
+
.aionui-explorer-handle,
|
|
685
|
+
.aionui-preview-handle {
|
|
686
|
+
display: none !important;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/* Shared base: both columns leave the grid as floating panels. The
|
|
690
|
+
explorer is gated shut by default (its own persisted expanded state
|
|
691
|
+
must never cover the mobile UI on load); the header Files action opens
|
|
692
|
+
it via the frame marker below, and the sheet's own collapse chevron
|
|
693
|
+
clears it. Preview stays owned by the suite (hidden while no tab is
|
|
694
|
+
open). The per-column rules below override the geometry. */
|
|
695
|
+
[data-aionui-explorer-col],
|
|
696
|
+
[data-aionui-preview-col] {
|
|
697
|
+
position: fixed !important;
|
|
698
|
+
z-index: 55 !important;
|
|
699
|
+
background: var(--aion-bg-base, #ffffff) !important;
|
|
700
|
+
border-left: none !important;
|
|
701
|
+
}
|
|
702
|
+
/* Explorer (file tree) bottom sheet: bottom edge aligned exactly with
|
|
703
|
+
the composer card's bottom line \u2014 the card sits 36px above the
|
|
704
|
+
viewport bottom (8px composer padding + the 28px stats strip below
|
|
705
|
+
the card), so the sheet uses the same 36px bottom offset. */
|
|
706
|
+
[data-aionui-explorer-col] {
|
|
707
|
+
visibility: hidden !important;
|
|
708
|
+
left: 8px !important;
|
|
709
|
+
right: 8px !important;
|
|
710
|
+
top: auto !important;
|
|
711
|
+
bottom: 36px !important;
|
|
712
|
+
width: auto !important;
|
|
713
|
+
height: min(55dvh, 460px) !important;
|
|
714
|
+
max-height: calc(100dvh - 44px) !important;
|
|
715
|
+
border-radius: 14px !important;
|
|
716
|
+
overflow: hidden !important;
|
|
717
|
+
box-shadow: 0 -4px 28px rgba(0, 0, 0, .18) !important;
|
|
718
|
+
animation: dsh-mobile-nav-sheet-up .24s var(--ds-ease-out, ease-in-out) !important;
|
|
719
|
+
}
|
|
720
|
+
/* Preview (file content) bottom sheet. Gated shut by default: the suite
|
|
721
|
+
persists open preview tabs in localStorage and restores them on load,
|
|
722
|
+
which would pop the sheet over the fresh UI. The client only sets the
|
|
723
|
+
frame marker after the user taps a file row in the explorer; the
|
|
724
|
+
suite's own collapse chevron clears it via the visibility watcher. */
|
|
725
|
+
[data-aionui-preview-col] {
|
|
726
|
+
visibility: hidden !important;
|
|
727
|
+
position: fixed !important;
|
|
728
|
+
left: 8px !important;
|
|
729
|
+
right: 8px !important;
|
|
730
|
+
top: auto !important;
|
|
731
|
+
bottom: 40px !important;
|
|
732
|
+
width: auto !important;
|
|
733
|
+
height: min(50dvh, 420px) !important;
|
|
734
|
+
max-height: calc(100dvh - 48px) !important;
|
|
735
|
+
border-radius: 14px !important;
|
|
736
|
+
overflow: hidden !important;
|
|
737
|
+
box-shadow: 0 -4px 28px rgba(0, 0, 0, .18) !important;
|
|
738
|
+
z-index: 56 !important;
|
|
739
|
+
animation: dsh-mobile-nav-sheet-up .24s var(--ds-ease-out, ease-in-out) !important;
|
|
740
|
+
}
|
|
741
|
+
/* User-opened preview sheet (frame marker, set on file-row tap). */
|
|
742
|
+
[data-mobile-nav="frame"][data-aionui-preview-open] [data-aionui-preview-col] {
|
|
743
|
+
visibility: visible !important;
|
|
744
|
+
}
|
|
745
|
+
/* The Files action opens the explorer sheet (frame marker). */
|
|
746
|
+
[data-mobile-nav="frame"][data-aionui-explorer-open] [data-aionui-explorer-col] {
|
|
747
|
+
visibility: visible !important;
|
|
748
|
+
}
|
|
749
|
+
/* The open drawer must never sit under a sheet: while the frame is in the
|
|
750
|
+
narrow-expanded state both sheets yield (later in the file than the
|
|
751
|
+
open marker rule, so it wins at equal specificity). */
|
|
752
|
+
[data-mobile-nav="frame"]:not([data-sidebar-collapsed]) [data-aionui-explorer-col],
|
|
753
|
+
[data-mobile-nav="frame"]:not([data-sidebar-collapsed]) [data-aionui-preview-col] {
|
|
754
|
+
visibility: hidden !important;
|
|
755
|
+
}
|
|
756
|
+
/* The suite's own expand button reads the store state we bypass on
|
|
757
|
+
mobile \u2014 hide it; the header Files action is the opener. */
|
|
758
|
+
.aionui-floating-expand {
|
|
759
|
+
display: none !important;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/* dsh-web-ui sidebar entries (task board / ssh) sit flush against each
|
|
763
|
+
other \u2014 give the injected rows breathing room. */
|
|
764
|
+
button[data-dsh-taskboard-entry],
|
|
765
|
+
button[data-dsh-ssh-entry] {
|
|
766
|
+
margin-bottom: 8px !important;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/* Task board: five kanban columns at minmax(0,1fr) crush into ~78px phone
|
|
770
|
+
strips. Give every column a usable minimum and let the row scroll. */
|
|
771
|
+
[data-dsh-taskboard-board] > [class$="_columns"] {
|
|
772
|
+
grid-template-columns: repeat(5, minmax(240px, 1fr)) !important;
|
|
773
|
+
overflow-x: auto !important;
|
|
774
|
+
}
|
|
775
|
+
/* The floating button must not float over a takeover panel (task board /
|
|
776
|
+
ssh own the center column while active). */
|
|
777
|
+
html[data-dsh-taskboard-active] [data-mobile-nav="fab"],
|
|
778
|
+
html[data-dsh-ssh-active] [data-mobile-nav="fab"],
|
|
779
|
+
html[data-dsh-taskboard-active] [data-mobile-nav="backdrop"],
|
|
780
|
+
html[data-dsh-ssh-active] [data-mobile-nav="backdrop"] {
|
|
781
|
+
display: none !important;
|
|
782
|
+
}
|
|
783
|
+
/* Board header: let the search field take the slack instead of squeezing
|
|
784
|
+
the action buttons. */
|
|
785
|
+
[data-dsh-taskboard-board] > [class$="_boardHeader"] [class$="_search"] {
|
|
786
|
+
flex: 1 1 auto !important;
|
|
787
|
+
min-width: 80px !important;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/* ---------- dsh-web-ui polish: plugin market search ----------
|
|
791
|
+
The market tab row (Discover / Themes / Installed + the plugin search
|
|
792
|
+
box) is a no-wrap flex: at 390px the tabs plus the ~218px search box
|
|
793
|
+
(~475px total) overflow the ~334px sheet and the search box runs off
|
|
794
|
+
the right edge of the screen (it also forces a horizontal scrollbar on
|
|
795
|
+
the sheet's options area). Let the row wrap: the tabs keep the first
|
|
796
|
+
line and the search box gets its own full-width second line. */
|
|
797
|
+
|
|
798
|
+
[aria-modal="true"] [class$="_tabs"] {
|
|
799
|
+
flex-wrap: wrap !important;
|
|
800
|
+
row-gap: 8px !important;
|
|
801
|
+
}
|
|
802
|
+
[aria-modal="true"] [class$="_searchInline"] {
|
|
803
|
+
flex: 1 1 100% !important;
|
|
804
|
+
width: 100% !important;
|
|
805
|
+
max-width: 100% !important;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/* ---------- dsh-usage-stats polish: usage & balance panel ----------
|
|
809
|
+
The panel's stats row shows three token counters side by side
|
|
810
|
+
(today / month / total). The counters use tabular nowrap figures whose
|
|
811
|
+
min-content width overflows the ~336px panel body on a phone: figures
|
|
812
|
+
clip at the row's edges and the panel grows a horizontal scrollbar.
|
|
813
|
+
Stack the three counters vertically \u2014 full-width rows, so the figures
|
|
814
|
+
always fit. */
|
|
815
|
+
|
|
816
|
+
[class*="usg_"][class$="_statsRow"] {
|
|
817
|
+
flex-direction: column !important;
|
|
818
|
+
}
|
|
819
|
+
[class*="usg_"][class$="_stat"] {
|
|
820
|
+
flex: 0 0 auto !important;
|
|
821
|
+
width: 100% !important;
|
|
822
|
+
min-width: 0 !important;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/* ---------- dsh-web-ui polish: settings sheet ----------
|
|
826
|
+
The official dialog is a desktop two-column form; on a phone the
|
|
827
|
+
label/control split leaves a huge dead gap and long descriptions wrap
|
|
828
|
+
into tall stacks. Stack each row (text above, control full-width) and
|
|
829
|
+
compact the nav tabs into an even wrap. */
|
|
830
|
+
|
|
831
|
+
/* Nav tabs: a stable 3-per-row grid (two clean rows instead of a ragged
|
|
832
|
+
wrap) with tighter cells. */
|
|
833
|
+
[aria-modal="true"]:has(> :first-child > :last-child > button):not(:has([role="navigation"])) > :first-child > :last-child {
|
|
834
|
+
display: grid !important;
|
|
835
|
+
grid-template-columns: repeat(3, 1fr) !important;
|
|
836
|
+
gap: 6px !important;
|
|
837
|
+
}
|
|
838
|
+
[aria-modal="true"] [class$="_navCell"] {
|
|
839
|
+
padding: 6px 8px !important;
|
|
840
|
+
gap: 6px !important;
|
|
841
|
+
font-size: 13px !important;
|
|
842
|
+
justify-content: flex-start !important;
|
|
843
|
+
}
|
|
844
|
+
[aria-modal="true"] [class$="_navCell"] svg {
|
|
845
|
+
width: 14px !important;
|
|
846
|
+
height: 14px !important;
|
|
847
|
+
flex: none !important;
|
|
848
|
+
}
|
|
849
|
+
/* Setting rows: text on top, control below at full width. */
|
|
850
|
+
[aria-modal="true"] [class$="_section"] [class$="_row"] {
|
|
851
|
+
flex-direction: column !important;
|
|
852
|
+
align-items: stretch !important;
|
|
853
|
+
gap: 8px !important;
|
|
854
|
+
}
|
|
855
|
+
[aria-modal="true"] [class$="_section"] [class$="_row"] > :first-child {
|
|
856
|
+
width: 100% !important;
|
|
857
|
+
max-width: none !important;
|
|
858
|
+
}
|
|
859
|
+
[aria-modal="true"] [class$="_section"] [class$="_row"] > :last-child {
|
|
860
|
+
width: 100% !important;
|
|
861
|
+
max-width: none !important;
|
|
862
|
+
}
|
|
863
|
+
/* Appearance mode group: give the cube row a consistent bordered
|
|
864
|
+
segmented look (the official borders differ per state). */
|
|
865
|
+
[aria-modal="true"] [class$="_cubeRow"] > * {
|
|
866
|
+
border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, .12)) !important;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/* ---------- dsh-web-ui polish: explorer sheet ----------
|
|
870
|
+
The aionui explorer was designed for a desktop side column: compact the
|
|
871
|
+
header, search box and tree rows so a phone shows more entries, and pad
|
|
872
|
+
the scroll bottom so the last row never sits flush on the edge. */
|
|
873
|
+
|
|
874
|
+
[data-aionui-explorer-col] [class$="_tabBar"] {
|
|
875
|
+
height: 36px !important;
|
|
876
|
+
}
|
|
877
|
+
[data-aionui-explorer-col] [class$="_tabBtn"],
|
|
878
|
+
[data-aionui-explorer-col] [class$="_tabBtnActive"] {
|
|
879
|
+
padding: 0 12px !important;
|
|
880
|
+
font-size: 13px !important;
|
|
881
|
+
}
|
|
882
|
+
[data-aionui-explorer-col] [class$="_searchBox"] {
|
|
883
|
+
height: 32px !important;
|
|
884
|
+
font-size: 13px !important;
|
|
885
|
+
}
|
|
886
|
+
[data-aionui-explorer-col] [class$="_treeRow"] {
|
|
887
|
+
height: 30px !important;
|
|
888
|
+
font-size: 13px !important;
|
|
889
|
+
}
|
|
890
|
+
[data-aionui-explorer-col] [class$="_treeRow"] svg {
|
|
891
|
+
width: 14px !important;
|
|
892
|
+
height: 14px !important;
|
|
893
|
+
}
|
|
894
|
+
[data-aionui-explorer-col] [class$="_scrollArea"] {
|
|
895
|
+
padding-bottom: 28px !important;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/* ---------- dsh-web-ui polish: drawer footer ----------
|
|
899
|
+
The injected footer actions (Files + Session log) become two equal pill
|
|
900
|
+
buttons instead of text-width capsules. */
|
|
901
|
+
|
|
902
|
+
/* The official footerActions row also hosts the remote-web-ui entry
|
|
903
|
+
row (two icon buttons); without wrapping the two groups squeeze each
|
|
904
|
+
other on one line. Wrap so each group gets its own full-width row. */
|
|
905
|
+
[data-mobile-nav="frame"] [class$="_footerActions"] {
|
|
906
|
+
flex-wrap: wrap !important;
|
|
907
|
+
gap: 6px !important;
|
|
908
|
+
}
|
|
909
|
+
[data-mobile-nav="drawer-actions"] {
|
|
910
|
+
width: 100% !important;
|
|
911
|
+
}
|
|
912
|
+
[data-mobile-nav="drawer-actions"] > button {
|
|
913
|
+
flex: 1 1 0 !important;
|
|
914
|
+
padding: 0 8px !important;
|
|
915
|
+
white-space: nowrap !important;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/* ---------- dsh-web-ui polish: floating pet ----------
|
|
919
|
+
The whale-girl pet (dsh-pet) floats at the viewport corner with a
|
|
920
|
+
persisted, draggable position. On phones the pet is scaled down so
|
|
921
|
+
it does not dominate the screen; the plugin's own drag + persist
|
|
922
|
+
still work (the position itself is left alone \u2014 the mobile default
|
|
923
|
+
position is seeded via the pet API to just above the composer). */
|
|
924
|
+
|
|
925
|
+
body > [class$="_float"]:has([class$="_sprite"][role="button"]) {
|
|
926
|
+
transform: scale(.66);
|
|
927
|
+
transform-origin: bottom right;
|
|
928
|
+
}
|
|
929
|
+
/* While a modal dialog (settings sheet / export) owns the screen the pet
|
|
930
|
+
floats ABOVE it and covers the dialog content; modal semantics say the
|
|
931
|
+
background is inert, so hide the pet for the modal's lifetime. */
|
|
932
|
+
body:has([aria-modal="true"]) > [class$="_float"]:has([class$="_sprite"][role="button"]) {
|
|
933
|
+
display: none !important;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/* ---------- dsh-web-ui polish: conversation stats line ----------
|
|
937
|
+
The official session-status row (turns / steps / LLM time / TTFT /
|
|
938
|
+
cache) is long. The client marks the exact row with
|
|
939
|
+
[data-mobile-nav="stats"] (text-anchored, hashed classes can't be
|
|
940
|
+
targeted). Layout: ONE fixed-height (28px) flex strip that scrolls
|
|
941
|
+
horizontally \u2014 the full metrics stream stays reachable by swiping,
|
|
942
|
+
the row never grows vertically, no ellipsis or fade, 12px gaps
|
|
943
|
+
between metric groups, a 2px scrollbar as the swipe affordance. */
|
|
944
|
+
|
|
945
|
+
[data-mobile-nav="stats"] {
|
|
946
|
+
display: flex !important;
|
|
947
|
+
flex-flow: row nowrap !important;
|
|
948
|
+
align-items: center !important;
|
|
949
|
+
width: 100% !important;
|
|
950
|
+
max-width: 100% !important;
|
|
951
|
+
min-width: 0 !important;
|
|
952
|
+
height: 28px !important;
|
|
953
|
+
min-height: 28px !important;
|
|
954
|
+
max-height: 28px !important;
|
|
955
|
+
box-sizing: border-box !important;
|
|
956
|
+
white-space: nowrap !important;
|
|
957
|
+
overflow-x: auto !important;
|
|
958
|
+
overflow-y: hidden !important;
|
|
959
|
+
-webkit-overflow-scrolling: touch;
|
|
960
|
+
overscroll-behavior-x: contain;
|
|
961
|
+
scrollbar-width: thin !important;
|
|
962
|
+
scrollbar-color: var(--dsw-alias-border-l1, rgba(0, 0, 0, .28)) transparent !important;
|
|
963
|
+
padding: 0 0 4px !important;
|
|
964
|
+
line-height: 20px !important;
|
|
965
|
+
font-size: 12px !important;
|
|
966
|
+
}
|
|
967
|
+
[data-mobile-nav="stats"]::-webkit-scrollbar {
|
|
968
|
+
height: 2px !important;
|
|
969
|
+
}
|
|
970
|
+
[data-mobile-nav="stats"]::-webkit-scrollbar-thumb {
|
|
971
|
+
background: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, .3)) !important;
|
|
972
|
+
border-radius: 2px !important;
|
|
973
|
+
}
|
|
974
|
+
[data-mobile-nav="stats"]::-webkit-scrollbar-track {
|
|
975
|
+
background: transparent !important;
|
|
976
|
+
}
|
|
977
|
+
[data-mobile-nav="stats"] > * {
|
|
978
|
+
display: flex !important;
|
|
979
|
+
flex: 0 0 auto !important;
|
|
980
|
+
flex-flow: row nowrap !important;
|
|
981
|
+
align-items: center !important;
|
|
982
|
+
width: max-content !important;
|
|
983
|
+
min-width: max-content !important;
|
|
984
|
+
max-width: none !important;
|
|
985
|
+
white-space: nowrap !important;
|
|
986
|
+
margin-right: 12px !important;
|
|
987
|
+
padding: 0 !important;
|
|
988
|
+
}
|
|
989
|
+
[data-mobile-nav="stats"] > *:last-child {
|
|
990
|
+
margin-right: 0 !important;
|
|
991
|
+
}
|
|
992
|
+
[data-mobile-nav="stats"] * {
|
|
993
|
+
white-space: nowrap !important;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
/* ---------- hero composer on mobile ----------
|
|
997
|
+
The official hero card carries a 2-line textarea plus a tall tool row,
|
|
998
|
+
which reads oversized on a phone. Tighten the empty-state rhythm: keep
|
|
999
|
+
the official centered hero, shrink the textarea line box, slim the card
|
|
1000
|
+
padding and the tool row, and close the gap under the headline. */
|
|
1001
|
+
|
|
1002
|
+
[data-phase="hero"] [class$="_card"]:has(textarea) {
|
|
1003
|
+
padding-top: 6px !important;
|
|
1004
|
+
gap: 8px !important;
|
|
1005
|
+
}
|
|
1006
|
+
/* The official composer autosizes the textarea and writes an inline
|
|
1007
|
+
height (2 lines on the hero empty state) on the textarea's scroll/grow
|
|
1008
|
+
wrappers. :placeholder-shown lets us collapse the EMPTY state to one
|
|
1009
|
+
line with !important; as soon as the user types, the pseudo-class no
|
|
1010
|
+
longer matches and the autosizer's inline height takes over again \u2014 so
|
|
1011
|
+
multi-line growth keeps working. */
|
|
1012
|
+
[data-phase="hero"] textarea:placeholder-shown {
|
|
1013
|
+
height: 28px !important;
|
|
1014
|
+
}
|
|
1015
|
+
[data-phase="hero"] [class$="_card"]:has(textarea:placeholder-shown) > [class$="_scroll"],
|
|
1016
|
+
[data-phase="hero"] [class$="_card"]:has(textarea:placeholder-shown) [class$="_grow"] {
|
|
1017
|
+
height: 28px !important;
|
|
1018
|
+
}
|
|
1019
|
+
[data-phase="hero"] [class$="_card"]:has(textarea) > [class$="_row"] {
|
|
1020
|
+
padding-top: 2px !important;
|
|
1021
|
+
}
|
|
1022
|
+
[data-phase="hero"] [class$="_headline"] {
|
|
1023
|
+
line-height: 1.15 !important;
|
|
1024
|
+
margin-bottom: 0 !important;
|
|
1025
|
+
}
|
|
1026
|
+
[data-phase="hero"] [class$="_stack"] {
|
|
1027
|
+
gap: 0 !important;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
/* ---------- desktop: the mobile controls must never appear ---------- */
|
|
1032
|
+
|
|
1033
|
+
@media (min-width: 1024px) {
|
|
1034
|
+
[data-mobile-nav="toggle"],
|
|
1035
|
+
[data-mobile-nav="files"],
|
|
1036
|
+
[data-mobile-nav="fab"],
|
|
1037
|
+
[data-mobile-nav="backdrop"],
|
|
1038
|
+
[data-mobile-nav="session-log"],
|
|
1039
|
+
[data-mobile-nav="explorer"],
|
|
1040
|
+
[data-mobile-nav="drawer-actions"] {
|
|
1041
|
+
display: none !important;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
`;
|
|
1045
|
+
|
|
1046
|
+
// client/mobile/locales.ts
|
|
1047
|
+
var NS = "mobileNav";
|
|
1048
|
+
var zh = {
|
|
1049
|
+
"open": "\u6253\u5F00\u76EE\u5F55",
|
|
1050
|
+
"close": "\u6536\u8D77\u76EE\u5F55",
|
|
1051
|
+
"backdrop": "\u70B9\u51FB\u5173\u95ED\u76EE\u5F55",
|
|
1052
|
+
"sessionLog": "\u5BFC\u51FA\u4F1A\u8BDD\u65E5\u5FD7",
|
|
1053
|
+
"files": "\u6587\u4EF6\u6D4F\u89C8"
|
|
1054
|
+
};
|
|
1055
|
+
var en = {
|
|
1056
|
+
"open": "Open directory",
|
|
1057
|
+
"close": "Close directory",
|
|
1058
|
+
"backdrop": "Click to close directory",
|
|
1059
|
+
"sessionLog": "Session log",
|
|
1060
|
+
"files": "Files"
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1063
|
+
// client/mobile/mobile-apply.tsx
|
|
1064
|
+
function mobileApply(ctx) {
|
|
1065
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-mobile-nav: dictionaries");
|
|
1066
|
+
ctx.effect(() => {
|
|
1067
|
+
const tag = document.createElement("style");
|
|
1068
|
+
tag.dataset.plugin = "@dsh-external/dsh-mobile-nav";
|
|
1069
|
+
tag.dataset.pluginCss = "@dsh-external/dsh-mobile-nav/mobile.css";
|
|
1070
|
+
tag.textContent = MOBILE_CSS;
|
|
1071
|
+
document.head.appendChild(tag);
|
|
1072
|
+
return () => {
|
|
1073
|
+
tag.remove();
|
|
1074
|
+
};
|
|
1075
|
+
}, "dsh-mobile-nav: styles");
|
|
1076
|
+
ctx.effect(() => {
|
|
1077
|
+
const narrow = window.matchMedia("(max-width: 1023px)");
|
|
1078
|
+
const viewport = document.querySelector('meta[name="viewport"]');
|
|
1079
|
+
const originalViewport = viewport?.content ?? "";
|
|
1080
|
+
const themeMeta = document.createElement("meta");
|
|
1081
|
+
themeMeta.name = "theme-color";
|
|
1082
|
+
const bodyBg = () => getComputedStyle(document.body).backgroundColor;
|
|
1083
|
+
const sync = () => {
|
|
1084
|
+
if (viewport !== null) viewport.content = "width=device-width, initial-scale=1, viewport-fit=cover";
|
|
1085
|
+
themeMeta.content = bodyBg();
|
|
1086
|
+
if (themeMeta.parentElement === null) document.head.appendChild(themeMeta);
|
|
1087
|
+
};
|
|
1088
|
+
const restore = () => {
|
|
1089
|
+
if (viewport !== null) viewport.content = originalViewport;
|
|
1090
|
+
themeMeta.remove();
|
|
1091
|
+
};
|
|
1092
|
+
const onGestureStart = (event) => event.preventDefault();
|
|
1093
|
+
if (narrow.matches) sync();
|
|
1094
|
+
const onChange = (event) => event.matches ? sync() : restore();
|
|
1095
|
+
narrow.addEventListener("change", onChange);
|
|
1096
|
+
const observer = new MutationObserver(() => {
|
|
1097
|
+
if (narrow.matches) themeMeta.content = bodyBg();
|
|
1098
|
+
});
|
|
1099
|
+
observer.observe(document.body, { attributes: true, attributeFilter: ["data-ds-dark-theme"] });
|
|
1100
|
+
document.addEventListener("gesturestart", onGestureStart);
|
|
1101
|
+
return () => {
|
|
1102
|
+
narrow.removeEventListener("change", onChange);
|
|
1103
|
+
observer.disconnect();
|
|
1104
|
+
document.removeEventListener("gesturestart", onGestureStart);
|
|
1105
|
+
restore();
|
|
1106
|
+
};
|
|
1107
|
+
}, "dsh-mobile-nav: status bar theme + viewport + zoom guard");
|
|
1108
|
+
ctx.effect(() => {
|
|
1109
|
+
const narrow = window.matchMedia("(max-width: 1023px)");
|
|
1110
|
+
if (!narrow.matches) return () => {
|
|
1111
|
+
};
|
|
1112
|
+
const onChevronClick = (event) => {
|
|
1113
|
+
const target = event.target;
|
|
1114
|
+
if (target === null || !target.closest(".aionui-collapse-chevron")) return;
|
|
1115
|
+
document.querySelector('[data-mobile-nav="frame"]')?.removeAttribute("data-aionui-explorer-open");
|
|
1116
|
+
};
|
|
1117
|
+
document.addEventListener("click", onChevronClick, true);
|
|
1118
|
+
return () => document.removeEventListener("click", onChevronClick, true);
|
|
1119
|
+
}, "dsh-mobile-nav: aionui explorer close marker");
|
|
1120
|
+
ctx.effect(() => {
|
|
1121
|
+
const narrow = window.matchMedia("(max-width: 1023px)");
|
|
1122
|
+
if (!narrow.matches) return () => {
|
|
1123
|
+
};
|
|
1124
|
+
const frame = () => document.querySelector('[data-mobile-nav="frame"]');
|
|
1125
|
+
const onTap = (event) => {
|
|
1126
|
+
const target = event.target;
|
|
1127
|
+
if (target === null) return;
|
|
1128
|
+
if (target.closest('[data-aionui-explorer-col] [class$="_treeRow"]') === null) return;
|
|
1129
|
+
frame()?.setAttribute("data-aionui-preview-open", "");
|
|
1130
|
+
};
|
|
1131
|
+
const sync = () => {
|
|
1132
|
+
const pv = document.querySelector("[data-aionui-preview-col]");
|
|
1133
|
+
if (pv === null) return;
|
|
1134
|
+
if (getComputedStyle(pv).visibility === "hidden") frame()?.removeAttribute("data-aionui-preview-open");
|
|
1135
|
+
};
|
|
1136
|
+
document.addEventListener("click", onTap, true);
|
|
1137
|
+
const observer = new MutationObserver(sync);
|
|
1138
|
+
observer.observe(document.body, { attributes: true, subtree: true, attributeFilter: ["style"] });
|
|
1139
|
+
sync();
|
|
1140
|
+
return () => {
|
|
1141
|
+
document.removeEventListener("click", onTap, true);
|
|
1142
|
+
observer.disconnect();
|
|
1143
|
+
};
|
|
1144
|
+
}, "dsh-mobile-nav: preview sheet open marker");
|
|
1145
|
+
ctx.effect(() => {
|
|
1146
|
+
const narrow = window.matchMedia("(max-width: 1023px)");
|
|
1147
|
+
if (!narrow.matches) return () => {
|
|
1148
|
+
};
|
|
1149
|
+
const moveTps = (stats) => {
|
|
1150
|
+
if ([...stats.children].some((c) => /^TPS\s+\d/.test((c.textContent ?? "").trim()))) return;
|
|
1151
|
+
const stack = stats.closest('[class$="_composerStack"]');
|
|
1152
|
+
if (stack === null) return;
|
|
1153
|
+
for (const el of stack.querySelectorAll("div")) {
|
|
1154
|
+
const text = (el.textContent ?? "").trim();
|
|
1155
|
+
if (!/^TPS\s+\d/.test(text)) continue;
|
|
1156
|
+
if (el.children.length > 0) continue;
|
|
1157
|
+
stats.appendChild(el);
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
};
|
|
1161
|
+
const mark = () => {
|
|
1162
|
+
for (const root of document.querySelectorAll('[data-phase] [class$="_root"]')) {
|
|
1163
|
+
if (root.closest('[class$="_composerStack"]') === null) continue;
|
|
1164
|
+
const text = root.textContent ?? "";
|
|
1165
|
+
if (!/(turns|steps|\bLLM\b|轮|步)/.test(text)) continue;
|
|
1166
|
+
if (root.querySelector("textarea") !== null) continue;
|
|
1167
|
+
root.setAttribute("data-mobile-nav", "stats");
|
|
1168
|
+
moveTps(root);
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
};
|
|
1172
|
+
const observer = new MutationObserver(mark);
|
|
1173
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
1174
|
+
mark();
|
|
1175
|
+
return () => {
|
|
1176
|
+
observer.disconnect();
|
|
1177
|
+
};
|
|
1178
|
+
}, "dsh-mobile-nav: stats line marker");
|
|
1179
|
+
ctx.effect(() => {
|
|
1180
|
+
const narrow = window.matchMedia("(max-width: 1023px)");
|
|
1181
|
+
if (!narrow.matches) return () => {
|
|
1182
|
+
};
|
|
1183
|
+
const cols = ["[data-aionui-explorer-col]", "[data-aionui-preview-col]"];
|
|
1184
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1185
|
+
const play = (el) => {
|
|
1186
|
+
el.animate(
|
|
1187
|
+
[
|
|
1188
|
+
{ opacity: 0, transform: "translateY(28px)" },
|
|
1189
|
+
{ opacity: 1, transform: "none" }
|
|
1190
|
+
],
|
|
1191
|
+
{ duration: 280, easing: "cubic-bezier(.16, 1, .3, 1)", fill: "backwards" }
|
|
1192
|
+
);
|
|
1193
|
+
};
|
|
1194
|
+
const check = () => {
|
|
1195
|
+
for (const sel of cols) {
|
|
1196
|
+
const el = document.querySelector(sel);
|
|
1197
|
+
if (el === null) continue;
|
|
1198
|
+
const visible = getComputedStyle(el).visibility === "visible";
|
|
1199
|
+
const prev = seen.get(sel) ?? false;
|
|
1200
|
+
if (visible && !prev) play(el);
|
|
1201
|
+
seen.set(sel, visible);
|
|
1202
|
+
}
|
|
1203
|
+
};
|
|
1204
|
+
const observer = new MutationObserver(check);
|
|
1205
|
+
observer.observe(document.body, { attributes: true, subtree: true, attributeFilter: ["style", "class", "data-aionui-explorer-open"] });
|
|
1206
|
+
check();
|
|
1207
|
+
return () => {
|
|
1208
|
+
observer.disconnect();
|
|
1209
|
+
};
|
|
1210
|
+
}, "dsh-mobile-nav: sheet rise animation replay");
|
|
1211
|
+
ctx.slots.inject("conversation.session.header.actions", () => ctx.slots.register({
|
|
1212
|
+
name: "conversation.session.header.actions",
|
|
1213
|
+
id: "mobile-nav-toggle",
|
|
1214
|
+
order: 10,
|
|
1215
|
+
locale: NS,
|
|
1216
|
+
inject: () => ({
|
|
1217
|
+
toggleSidebar: () => ctx.layout.toggleSidebar()
|
|
1218
|
+
})
|
|
1219
|
+
}, MobileNavToggle));
|
|
1220
|
+
ctx.slots.inject("shell.overlay", () => ctx.slots.register({
|
|
1221
|
+
name: "shell.overlay",
|
|
1222
|
+
id: "mobile-nav-overlay",
|
|
1223
|
+
order: 10,
|
|
1224
|
+
locale: NS,
|
|
1225
|
+
inject: () => ({
|
|
1226
|
+
toggleSidebar: () => ctx.layout.toggleSidebar()
|
|
1227
|
+
})
|
|
1228
|
+
}, MobileNavOverlay));
|
|
1229
|
+
ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({
|
|
1230
|
+
name: "sidebar.footer.action",
|
|
1231
|
+
id: "mobile-nav-session-log",
|
|
1232
|
+
order: 10,
|
|
1233
|
+
locale: NS,
|
|
1234
|
+
inject: () => ({
|
|
1235
|
+
downloadSessionLog: (sessionId) => ctx.sessionLogDownload.download(sessionId),
|
|
1236
|
+
toggleSidebar: () => ctx.layout.toggleSidebar()
|
|
1237
|
+
})
|
|
1238
|
+
}, MobileDrawerFooter));
|
|
1239
|
+
}
|
|
1240
|
+
|
|
55
1241
|
// client/index.jsx
|
|
56
1242
|
var name = "dsh-pocket";
|
|
57
|
-
var inject = ["slots", "connection"];
|
|
1243
|
+
var inject = ["slots", "connection", "layout", "locale", "sessionLogDownload"];
|
|
58
1244
|
var styles = {
|
|
59
1245
|
card: { background: "var(--dsw-alias-bg-layer-1,#fff)", border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)", borderRadius: 12, padding: "14px 16px", maxWidth: 480 },
|
|
60
1246
|
block: { borderTop: "1px solid var(--dsw-alias-border-l2,#e5e7eb)", marginTop: 12, paddingTop: 12 },
|
|
@@ -66,9 +1252,9 @@ var styles = {
|
|
|
66
1252
|
warn: { color: "var(--dsw-alias-state-warn-primary,#b45309)", fontSize: 12 }
|
|
67
1253
|
};
|
|
68
1254
|
function PocketSettingsTab({ rpcCall }) {
|
|
69
|
-
const [status, setStatus] = (0,
|
|
70
|
-
const [busy, setBusy] = (0,
|
|
71
|
-
const [error, setError] = (0,
|
|
1255
|
+
const [status, setStatus] = (0, import_react2.useState)(null);
|
|
1256
|
+
const [busy, setBusy] = (0, import_react2.useState)(false);
|
|
1257
|
+
const [error, setError] = (0, import_react2.useState)(null);
|
|
72
1258
|
const call = async (endpoint, payload) => {
|
|
73
1259
|
const res = await rpcCall(endpoint, payload);
|
|
74
1260
|
if (!res?.ok) throw new Error(res?.error?.message ?? "RPC failed");
|
|
@@ -80,7 +1266,7 @@ function PocketSettingsTab({ rpcCall }) {
|
|
|
80
1266
|
} catch {
|
|
81
1267
|
}
|
|
82
1268
|
};
|
|
83
|
-
(0,
|
|
1269
|
+
(0, import_react2.useEffect)(() => {
|
|
84
1270
|
load();
|
|
85
1271
|
const t = setInterval(load, 3e3);
|
|
86
1272
|
return () => clearInterval(t);
|
|
@@ -104,51 +1290,52 @@ function PocketSettingsTab({ rpcCall }) {
|
|
|
104
1290
|
};
|
|
105
1291
|
const lanUrl = status?.lanUrl;
|
|
106
1292
|
const tunnelUrl = status?.tunnelUrl;
|
|
107
|
-
return (0,
|
|
1293
|
+
return (0, import_react2.createElement)(
|
|
108
1294
|
"div",
|
|
109
1295
|
{ style: styles.card },
|
|
110
|
-
(0,
|
|
1296
|
+
(0, import_react2.createElement)(
|
|
111
1297
|
"div",
|
|
112
1298
|
null,
|
|
113
|
-
(0,
|
|
114
|
-
(0,
|
|
1299
|
+
(0, import_react2.createElement)("strong", null, "\u{1F4F1} \u624B\u673A\u8BBF\u95EE | Phone access"),
|
|
1300
|
+
(0, import_react2.createElement)("div", { style: styles.muted }, "\u624B\u673A\u626B\u7801\u6253\u5F00\u7684\u5C31\u662F\u7535\u8111\u4E0A\u7684\u8FD9\u4E2A\u754C\u9762\uFF0C\u5B9E\u65F6\u540C\u6B65 | the phone shows this exact screen, live")
|
|
115
1301
|
),
|
|
116
1302
|
// 局域网
|
|
117
|
-
(0,
|
|
1303
|
+
(0, import_react2.createElement)(
|
|
118
1304
|
"div",
|
|
119
1305
|
{ style: styles.block },
|
|
120
|
-
(0,
|
|
121
|
-
lanUrl ? (0,
|
|
1306
|
+
(0, import_react2.createElement)("div", { style: { fontWeight: 600, fontSize: 13 } }, "\u{1F4F6} \u5C40\u57DF\u7F51\uFF08\u540C\u4E00 WiFi\uFF09| LAN"),
|
|
1307
|
+
lanUrl ? (0, import_react2.createElement)(
|
|
122
1308
|
"div",
|
|
123
1309
|
null,
|
|
124
|
-
(0,
|
|
125
|
-
(0,
|
|
126
|
-
(0,
|
|
127
|
-
) : (0,
|
|
1310
|
+
(0, import_react2.createElement)("img", { src: status.lanQr, alt: "LAN QR", style: styles.qr }),
|
|
1311
|
+
(0, import_react2.createElement)("div", { style: styles.code }, lanUrl),
|
|
1312
|
+
(0, import_react2.createElement)("div", { style: styles.muted }, "\u624B\u673A\u8FDE\u63A5\u540C\u4E00 WiFi \u540E\u626B\u7801\u5373\u53EF\u6253\u5F00")
|
|
1313
|
+
) : (0, import_react2.createElement)("div", { style: styles.muted }, "\u4EE3\u7406\u672A\u5C31\u7EEA\u2026 | proxy starting\u2026")
|
|
128
1314
|
),
|
|
129
1315
|
// 公网
|
|
130
|
-
(0,
|
|
1316
|
+
(0, import_react2.createElement)(
|
|
131
1317
|
"div",
|
|
132
1318
|
{ style: styles.block },
|
|
133
|
-
(0,
|
|
134
|
-
tunnelUrl ? (0,
|
|
1319
|
+
(0, import_react2.createElement)("div", { style: { fontWeight: 600, fontSize: 13 } }, "\u{1F310} \u516C\u7F51\uFF08\u4EBA\u5728\u5916\u9762\uFF09| Anywhere"),
|
|
1320
|
+
tunnelUrl ? (0, import_react2.createElement)(
|
|
135
1321
|
"div",
|
|
136
1322
|
null,
|
|
137
|
-
(0,
|
|
138
|
-
(0,
|
|
139
|
-
(0,
|
|
140
|
-
(0,
|
|
141
|
-
) : (0,
|
|
1323
|
+
(0, import_react2.createElement)("img", { src: status.tunnelQr, alt: "Tunnel QR", style: styles.qr }),
|
|
1324
|
+
(0, import_react2.createElement)("div", { style: styles.code }, tunnelUrl),
|
|
1325
|
+
(0, import_react2.createElement)("div", { style: styles.muted }, "\u4EFB\u4F55\u7F51\u7EDC\u626B\u7801\u5373\u7528\uFF08URL \u6BCF\u6B21\u91CD\u542F\u4F1A\u53D8\uFF09"),
|
|
1326
|
+
(0, import_react2.createElement)("button", { style: styles.btn, onClick: stopTunnel }, "\u5173\u95ED\u516C\u7F51 | Stop")
|
|
1327
|
+
) : (0, import_react2.createElement)(
|
|
142
1328
|
"div",
|
|
143
1329
|
null,
|
|
144
|
-
(0,
|
|
145
|
-
(0,
|
|
1330
|
+
(0, import_react2.createElement)("button", { style: styles.primary, onClick: startTunnel, disabled: busy }, busy ? "\u5F00\u542F\u4E2D\u2026\uFF08\u9996\u6B21\u9700\u4E0B\u8F7D cloudflared\uFF09" : "\u5F00\u542F\u516C\u7F51\u8BBF\u95EE | Enable anywhere"),
|
|
1331
|
+
(0, import_react2.createElement)("div", { style: styles.warn, marginTop: 8 }, "\u26A0\uFE0F DSH \u80FD\u6267\u884C\u7535\u8111\u4EE3\u7801\uFF1A\u4E8C\u7EF4\u7801/URL \u5C31\u662F\u94A5\u5319\uFF0C\u8BF7\u52FF\u53D1\u7ED9\u522B\u4EBA")
|
|
146
1332
|
)
|
|
147
1333
|
),
|
|
148
|
-
error ? (0,
|
|
1334
|
+
error ? (0, import_react2.createElement)("div", { style: { color: "var(--dsw-alias-state-error-primary,#dc2626)", fontSize: 12, marginTop: 8 } }, `\u274C ${error}`) : null
|
|
149
1335
|
);
|
|
150
1336
|
}
|
|
151
1337
|
function apply(ctx) {
|
|
1338
|
+
mobileApply(ctx);
|
|
152
1339
|
const rpcCall = (endpoint, payload, signal) => ctx.connection.rpc.call(POCKET_RPC_CHANNEL, endpoint, payload, signal);
|
|
153
1340
|
ctx.slots.inject(
|
|
154
1341
|
"settings.plugins.tab",
|