@khorsheed/dsh-mobile 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.en.md +109 -0
- package/README.i18n.yaml +3 -3
- package/README.md +60 -83
- package/lib/client.js +244 -39
- package/lib/types/client/MobileChrome.js +1 -1
- package/lib/types/client/MobileLibrary.d.ts +2 -1
- package/lib/types/client/MobileLibrary.js +34 -15
- package/lib/types/client/MobileTools.d.ts +13 -3
- package/lib/types/client/MobileTools.js +7 -5
- package/lib/types/client/catalogReads.d.ts +10 -0
- package/lib/types/client/catalogReads.js +28 -0
- package/lib/types/client/commands.d.ts +18 -0
- package/lib/types/client/commands.js +6 -0
- package/lib/types/client/composerActions.d.ts +3 -3
- package/lib/types/client/composerActions.js +18 -9
- package/lib/types/client/index.js +8 -1
- package/lib/types/client/sheetGestures.d.ts +3 -0
- package/lib/types/client/sheetGestures.js +81 -0
- package/lib/types/client/styles.d.ts +2 -2
- package/lib/types/client/styles.js +26 -7
- package/lib/types/client/surface.js +7 -4
- package/package.json +4 -3
- package/README.zh.md +0 -132
package/lib/client.js
CHANGED
|
@@ -2929,8 +2929,12 @@ html[data-dsh-mobile] [data-slot="conversation.input.right"] { max-width: 100%;
|
|
|
2929
2929
|
[data-mobile-dialog] button[aria-pressed="true"] { color: #4d6bfe; background: #4d6bfe12; }
|
|
2930
2930
|
[data-mobile-dialog] [data-mobile-done] { display: block; width: 100%; margin-top: 24px; background: #4d6bfe; color: white; border-radius: 100px; }
|
|
2931
2931
|
|
|
2932
|
+
/* The host trigger menu is translucent; mobile overlays need an opaque face. */
|
|
2933
|
+
html[data-dsh-mobile] [data-trigger-menu] { background:var(--mobile-face,var(--dsw-alias-bg-base))!important; --dsw-specific-menu:var(--mobile-face,var(--dsw-alias-bg-base)); opacity:1; backdrop-filter:none; -webkit-backdrop-filter:none; border:1px solid var(--mobile-edge); padding:8px; border-radius:22px; box-shadow:var(--mobile-shadow); }
|
|
2934
|
+
html[data-dsh-mobile] [data-trigger-menu] [role=option] { min-height:48px; padding:12px; font-size:16px; line-height:1.45; }
|
|
2935
|
+
html[data-dsh-mobile] [data-trigger-menu] [role=listbox] { overscroll-behavior:contain; }
|
|
2932
2936
|
/* Hide an empty ordinary breadcrumb; populated catalogs keep their Host trigger. */
|
|
2933
|
-
html[data-dsh-mobile]:has([data-mobile-session-title]) [data-slot="main.conversation"] header:has([data-conversation-header-corner]) nav:has(> span:only-child > button:disabled + [data-slot="conversation.session.header.lineage"]:empty) { display:none; }
|
|
2937
|
+
html[data-dsh-mobile]:has([data-mobile-session-title]) [data-slot="main.conversation"] header:has([data-conversation-header-corner]) nav:has(> span:only-child > :is(button:disabled,span:not([data-slot])) + [data-slot="conversation.session.header.lineage"]:empty) { display:none; }
|
|
2934
2938
|
html[data-dsh-mobile] [data-mobile-lineage-count] { flex-shrink:0; white-space:nowrap; }
|
|
2935
2939
|
[data-mobile-restore] { position:fixed; right:16px; bottom:max(16px,env(safe-area-inset-bottom)); z-index:1200; pointer-events:auto; min-height:44px; padding:10px 18px; border:1px solid #4d6bfe40; border-radius:24px; background:var(--dsw-alias-bg-base,white); color:#4d6bfe; font:inherit; box-shadow:0 3px 18px #15244318; }
|
|
2936
2940
|
|
|
@@ -3067,17 +3071,17 @@ html[data-dsh-mobile]:has([data-mobile-session-title]) [data-mobile-header-hidde
|
|
|
3067
3071
|
[data-mobile-folded-action] { display:none!important; }
|
|
3068
3072
|
[data-mobile-tools-seat] { display:contents; }
|
|
3069
3073
|
html[data-dsh-mobile] [data-mobile-tools-open] { display:grid; place-items:center; width:44px; height:44px; min-width:44px; padding:0; border:0; border-radius:50%; corner-shape:round; color:var(--dsw-alias-label-primary); background:var(--mobile-soft); }
|
|
3070
|
-
[data-mobile-tools-dialog] { box-sizing:border-box; margin:auto auto 0; width:min(100%,520px); max-width:100%; max-height:75dvh; overflow-y:auto; padding:16px 20px max(20px,env(safe-area-inset-bottom)); border:1px solid var(--mobile-line); border-bottom:0; border-radius:26px 26px 0 0; background:var(--mobile-face); color:var(--dsw-alias-label-primary); box-shadow:0 -8px 40px #0002; }
|
|
3074
|
+
[data-mobile-tools-dialog] { box-sizing:border-box; margin:auto auto 0; width:min(100%,520px); max-width:100%; max-height:75dvh; overscroll-behavior:contain; overflow-y:auto; padding:16px 20px max(20px,env(safe-area-inset-bottom)); border:1px solid var(--mobile-line); border-bottom:0; border-radius:26px 26px 0 0; background:var(--mobile-face); color:var(--dsw-alias-label-primary); box-shadow:0 -8px 40px #0002; }
|
|
3071
3075
|
[data-mobile-tools-dialog]::backdrop { background:#0005; }
|
|
3072
|
-
[data-mobile-tools-dialog] [data-mobile-tools-handle] { width:34px; height:4px; margin:0 auto 12px; border-radius:4px; background:var(--dsw-alias-label-tertiary); opacity:.5; }
|
|
3073
|
-
[data-mobile-tools-dialog] header { display:flex; align-items:center; justify-content:space-between; margin-bottom:12px; }
|
|
3074
|
-
[data-mobile-tools-dialog] strong { font-size:
|
|
3076
|
+
[data-mobile-tools-dialog] [data-mobile-tools-handle] { width:34px; height:4px; margin:0 auto 12px; touch-action:pan-x; border-radius:4px; background:var(--dsw-alias-label-tertiary); opacity:.5; }
|
|
3077
|
+
[data-mobile-tools-dialog] header { touch-action:pan-x; display:flex; align-items:center; justify-content:space-between; margin-bottom:12px; }
|
|
3078
|
+
[data-mobile-tools-dialog] strong { font-size:20px; font-weight:600; }
|
|
3075
3079
|
[data-mobile-tools-dialog] button { font:inherit; color:inherit; cursor:pointer; border:0; background:transparent; -webkit-tap-highlight-color:transparent; }
|
|
3076
3080
|
[data-mobile-tools-dialog] button:disabled { opacity:.45; cursor:default; }
|
|
3077
3081
|
[data-mobile-tools-dialog] header button { width:44px; height:44px; display:grid; place-items:center; border-radius:50%; padding:0; }
|
|
3078
3082
|
[data-mobile-tools-grid] { display:grid; grid-template-columns:1fr 1fr; gap:12px; }
|
|
3079
|
-
[data-mobile-tools-grid] button { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:10px; min-height:
|
|
3080
|
-
[data-mobile-tools-permission] { display:flex; align-items:center; width:100%; gap:10px; margin-top:12px; min-height:64px; padding:12px 0; text-align:left; font-size:14px!important; }
|
|
3083
|
+
[data-mobile-tools-grid] button { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:10px; min-height:96px; padding:16px; border-radius:18px; background:var(--mobile-soft); font-size:14px; }
|
|
3084
|
+
[data-mobile-tools-permission] { display:flex; align-items:center; width:100%; gap:10px; margin-top:12px; border-top:1px solid var(--mobile-line)!important; min-height:64px; padding:12px 0; text-align:left; font-size:14px!important; }
|
|
3081
3085
|
[data-mobile-tools-permission] > span { flex-shrink:0; }
|
|
3082
3086
|
[data-mobile-tools-permission] > small { margin-left:auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:12px; color:var(--dsw-alias-label-secondary); }
|
|
3083
3087
|
[data-mobile-tools-dialog] svg { flex:none; }
|
|
@@ -3186,6 +3190,12 @@ html[data-dsh-mobile] [data-mobile-room-form] :is(input,textarea,select) { box-s
|
|
|
3186
3190
|
html[data-dsh-mobile] [data-mobile-room-form] textarea { min-height:94px; resize:vertical; }
|
|
3187
3191
|
html[data-dsh-mobile] [data-mobile-room-form] button { min-height:44px; min-width:44px; font-size:16px; border-radius:14px; }
|
|
3188
3192
|
html[data-dsh-mobile] [data-mobile-room-form] summary { min-height:44px; padding:10px 0; font-size:16px; color:var(--dsw-alias-label-primary); }
|
|
3193
|
+
/* Harness model controls are expandable summaries, not native selects. Keep the
|
|
3194
|
+
advanced-settings disclosure plain while aligning picker faces with form fields. */
|
|
3195
|
+
html[data-dsh-mobile] [data-mobile-room-form] details > summary[aria-expanded] { display:flex; align-items:center; box-sizing:border-box; width:100%; max-width:none; min-width:0; min-height:48px; gap:12px; padding:11px 12px; border:1px solid var(--mobile-edge); border-radius:14px; background:var(--mobile-soft); font-size:17px; line-height:1.5; color:var(--dsw-alias-label-primary); }
|
|
3196
|
+
html[data-dsh-mobile] [data-mobile-room-form] details > summary[aria-expanded] > span { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
3197
|
+
html[data-dsh-mobile] [data-mobile-room-form] details > summary[aria-expanded]::after { flex-shrink:0; margin-inline-start:auto; margin-inline-end:2px; }
|
|
3198
|
+
|
|
3189
3199
|
html[data-dsh-mobile] [data-mobile-room-form] > div:last-child { position:sticky; bottom:-16px; background:var(--mobile-face); padding:12px 0 0; margin-top:0; display:grid; grid-template-columns:1fr 1fr; gap:12px; }
|
|
3190
3200
|
html[data-dsh-mobile] [data-mobile-room-form] [role=alert] { font-size:15px; line-height:1.5; }
|
|
3191
3201
|
html[data-dsh-mobile] [data-member] { font-size:16px; }
|
|
@@ -3246,6 +3256,15 @@ html[data-dsh-mobile] [data-mobile-picker] { position:fixed!important; inset:aut
|
|
|
3246
3256
|
html[data-dsh-mobile] [data-mobile-picker] > [role=presentation] { max-height:none; overflow:visible; }
|
|
3247
3257
|
html[data-dsh-mobile] [data-mobile-picker] [role=menuitem] { min-height:52px; padding:12px; border-radius:14px; font-size:17px; }
|
|
3248
3258
|
html[data-dsh-mobile] [data-mobile-picker] [role=menuitem][hidden] { display:none; }
|
|
3259
|
+
/* Keep the workspace list inside its flex viewport: visible overflow used to
|
|
3260
|
+
* paint the last folder over the pinned add-action divider on short screens. */
|
|
3261
|
+
html[data-dsh-mobile] [data-mobile-picker=workspace] { overflow:hidden; }
|
|
3262
|
+
html[data-dsh-mobile] [data-mobile-picker=workspace] > [data-mobile-picker-chrome] { flex:none; }
|
|
3263
|
+
html[data-dsh-mobile] [data-mobile-picker=workspace] > [role=presentation]:nth-child(2) { flex:1 1 auto; min-height:0; overflow-y:auto; overscroll-behavior:contain; }
|
|
3264
|
+
html[data-dsh-mobile] [data-mobile-picker=workspace] > [role=presentation] > * { flex:none; }
|
|
3265
|
+
html[data-dsh-mobile] [data-mobile-picker=workspace] > [role=presentation]:nth-child(3):last-child { flex:none; margin-top:8px; padding-top:8px; border-top:1px solid var(--mobile-edge); }
|
|
3266
|
+
html[data-dsh-mobile] [data-mobile-picker=workspace] [role=menuitem] { box-sizing:border-box; min-height:52px; padding:12px; line-height:28px; }
|
|
3267
|
+
|
|
3249
3268
|
html[data-dsh-mobile] [data-mobile-picker=preset] [role=menuitem] span > span:last-child { font-size:14px; line-height:1.55; color:var(--dsw-alias-label-secondary); }
|
|
3250
3269
|
|
|
3251
3270
|
html[data-mobile-gesture] [data-mobile-library-pane][hidden] { display:block; }
|
|
@@ -3327,13 +3346,13 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
3327
3346
|
this.clearMarks();
|
|
3328
3347
|
const root = frame?.querySelector("[data-slot=\"main.conversation\"] > [data-phase]");
|
|
3329
3348
|
const row = root?.querySelector("header [data-conversation-header-corner]")?.parentElement;
|
|
3330
|
-
const ordinary = row?.querySelector("nav > span:only-child > button:disabled + [data-slot=\"conversation.session.header.lineage\"]:empty");
|
|
3349
|
+
const ordinary = row?.querySelector("nav > span:only-child > :is(button:disabled,span:not([data-slot])) + [data-slot=\"conversation.session.header.lineage\"]:empty");
|
|
3331
3350
|
let preset = "";
|
|
3332
3351
|
if (row) {
|
|
3333
3352
|
for (const button of row.querySelectorAll("[data-slot=\"conversation.session.header.actions\"] button[aria-label]")) if (/^(重命名会话|Rename session)$/.test(button.getAttribute("aria-label") ?? "")) this.mark(button, "data-mobile-original-rename");
|
|
3334
3353
|
}
|
|
3335
3354
|
if (root?.dataset.phase === "active" && row) {
|
|
3336
|
-
for (const title of row.querySelectorAll("nav > span > button:disabled")) {
|
|
3355
|
+
for (const title of row.querySelectorAll("nav > span > :is(button:disabled,span:not([data-slot]))")) {
|
|
3337
3356
|
const slot = title.nextElementSibling;
|
|
3338
3357
|
if (slot?.getAttribute("data-slot") !== "conversation.session.header.lineage") continue;
|
|
3339
3358
|
const count = slot.querySelector(":scope > div:only-child > button[aria-haspopup=\"tree\"]");
|
|
@@ -3343,17 +3362,18 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
3343
3362
|
this.mark(separator, "data-mobile-header-hidden");
|
|
3344
3363
|
this.mark(count, "data-mobile-lineage-count");
|
|
3345
3364
|
}
|
|
3346
|
-
const label = row.querySelector("[data-slot=\"conversation.session.header.actions\"] > span[title]:has(svg mask[id^=\"mask0_agent_preset\"])");
|
|
3365
|
+
const label = row.querySelector("[data-slot=\"conversation.session.header.actions\"] > span[title]:has(svg :is(mask[id^=\"mask0_agent_preset\"],path[d^=\"M6.51867 12.3282\"]))");
|
|
3347
3366
|
if (label) {
|
|
3348
3367
|
preset = label.textContent?.trim() ?? "";
|
|
3349
3368
|
this.mark(label, "data-mobile-header-hidden");
|
|
3350
3369
|
}
|
|
3370
|
+
for (const target of row.querySelectorAll("[data-slot=\"conversation.session.header.utilities\"] [data-open-target=\"directory\"]")) this.mark(target, "data-mobile-header-hidden");
|
|
3351
3371
|
for (const img of row.querySelectorAll("img[src^=\"/open-in-app/icon/\"]")) {
|
|
3352
3372
|
const split = img.closest("button")?.parentElement;
|
|
3353
3373
|
if (split && split.querySelectorAll("button").length === 2) this.mark(split, "data-mobile-header-hidden");
|
|
3354
3374
|
}
|
|
3355
3375
|
for (const button of row.querySelectorAll("[data-sidebar-right-expand], [data-slot=\"conversation.session.header.utilities\"] > span > button[aria-haspopup=\"menu\"][aria-busy]")) if (button.hasAttribute("data-sidebar-right-expand") || /^(更多操作|More actions)$/i.test(button.getAttribute("aria-label") ?? "")) this.mark(button, "data-mobile-header-hidden");
|
|
3356
|
-
if (this.doc.querySelector("[data-mobile-members-open]
|
|
3376
|
+
if (this.doc.querySelector("[data-mobile-members-open]:not([disabled])")) {
|
|
3357
3377
|
for (const button of row.querySelectorAll("[data-slot=\"conversation.session.header.actions\"] button")) if (/^(+|\+)?\s*(邀请 agent|Invite agent)$/i.test(button.textContent?.trim() ?? "")) this.mark(button, "data-mobile-header-hidden");
|
|
3358
3378
|
}
|
|
3359
3379
|
const content = row.cloneNode(true);
|
|
@@ -4092,6 +4112,19 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
4092
4112
|
return null;
|
|
4093
4113
|
}
|
|
4094
4114
|
//#endregion
|
|
4115
|
+
//#region src/client/commands.ts
|
|
4116
|
+
function openMobileCommands(controller, draft, span) {
|
|
4117
|
+
if (!controller || typeof controller.toggleSource !== "function") return false;
|
|
4118
|
+
controller.toggleSource("command", {
|
|
4119
|
+
trigger: "/",
|
|
4120
|
+
query: "",
|
|
4121
|
+
quoted: false,
|
|
4122
|
+
position: draft.slice(0, span.start).trim() === "" ? "leading" : "inline",
|
|
4123
|
+
span
|
|
4124
|
+
});
|
|
4125
|
+
return true;
|
|
4126
|
+
}
|
|
4127
|
+
//#endregion
|
|
4095
4128
|
//#region src/client/composerActions.ts
|
|
4096
4129
|
var ComposerActions = class {
|
|
4097
4130
|
seat;
|
|
@@ -4108,7 +4141,11 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
4108
4141
|
subtree: true,
|
|
4109
4142
|
characterData: true,
|
|
4110
4143
|
attributes: true,
|
|
4111
|
-
attributeFilter: [
|
|
4144
|
+
attributeFilter: [
|
|
4145
|
+
"disabled",
|
|
4146
|
+
"aria-label",
|
|
4147
|
+
"contenteditable"
|
|
4148
|
+
]
|
|
4112
4149
|
});
|
|
4113
4150
|
this.sync();
|
|
4114
4151
|
}
|
|
@@ -4120,21 +4157,25 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
4120
4157
|
};
|
|
4121
4158
|
};
|
|
4122
4159
|
sync() {
|
|
4123
|
-
const
|
|
4124
|
-
const
|
|
4125
|
-
const
|
|
4160
|
+
const card = this.seat.closest("[data-composer-card]");
|
|
4161
|
+
const file = card?.querySelector(":scope > div:last-child > div:first-child > input[type=file][hidden]");
|
|
4162
|
+
const previous = file?.previousElementSibling;
|
|
4163
|
+
const unified = previous instanceof HTMLButtonElement && previous.getAttribute("aria-haspopup") === "listbox";
|
|
4164
|
+
const attachments = unified ? file : previous;
|
|
4165
|
+
const commands = unified ? previous : previous?.previousElementSibling;
|
|
4126
4166
|
const next = {};
|
|
4127
|
-
if (commands instanceof HTMLButtonElement && commands.getAttribute("aria-haspopup") === "listbox" && attachments instanceof HTMLButtonElement) {
|
|
4167
|
+
if (commands instanceof HTMLButtonElement && commands.getAttribute("aria-haspopup") === "listbox" && (attachments instanceof HTMLButtonElement || attachments instanceof HTMLInputElement)) {
|
|
4128
4168
|
next.commands = this.target(commands);
|
|
4129
4169
|
next.attachments = this.target(attachments);
|
|
4130
|
-
|
|
4170
|
+
if (unified) next.attachments.disabled ||= commands.disabled || !card?.querySelector("[contenteditable=\"true\"]");
|
|
4171
|
+
const permission = (file?.nextElementSibling)?.querySelector("[data-slot=\"conversation.input.permission\"] button[aria-label], :scope > span > button[aria-label]");
|
|
4131
4172
|
if (permission?.querySelector(":scope > span:not([aria-hidden])") && permission.querySelector(":scope > span[aria-hidden] svg")) next.permissions = this.target(permission);
|
|
4132
4173
|
}
|
|
4133
4174
|
for (const button of this.marked) if (!Object.values(next).some((value) => value.button === button)) {
|
|
4134
4175
|
button.removeAttribute("data-mobile-folded-action");
|
|
4135
4176
|
this.marked.delete(button);
|
|
4136
4177
|
}
|
|
4137
|
-
for (const value of Object.values(next)) if (!this.marked.has(value.button)) {
|
|
4178
|
+
for (const value of Object.values(next)) if (value.button instanceof HTMLButtonElement && !this.marked.has(value.button)) {
|
|
4138
4179
|
value.button.setAttribute("data-mobile-folded-action", "");
|
|
4139
4180
|
this.marked.add(value.button);
|
|
4140
4181
|
}
|
|
@@ -4154,10 +4195,11 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
4154
4195
|
disabled: button.disabled
|
|
4155
4196
|
};
|
|
4156
4197
|
}
|
|
4157
|
-
invoke(action) {
|
|
4198
|
+
invoke(action, openCommands) {
|
|
4158
4199
|
this.sync();
|
|
4159
4200
|
const target = this.snapshot[action];
|
|
4160
4201
|
if (!target || target.disabled || !target.button.isConnected) return false;
|
|
4202
|
+
if (action === "commands" && openCommands?.()) return true;
|
|
4161
4203
|
if (action === "commands") this.seat.closest("[data-composer-card]")?.querySelector("[contenteditable=\"true\"]")?.focus({ preventScroll: true });
|
|
4162
4204
|
target.button.click();
|
|
4163
4205
|
return true;
|
|
@@ -4447,8 +4489,9 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
4447
4489
|
//#endregion
|
|
4448
4490
|
//#region src/client/MobileTools.tsx
|
|
4449
4491
|
/** Own only the launcher and grouped navigation; action targets stay host-owned. */
|
|
4450
|
-
function MobileTools({ t, useSession, rooms }) {
|
|
4492
|
+
function MobileTools({ t, useSession, useInput, inputActions, rooms, openCommands }) {
|
|
4451
4493
|
const sessionId = useSession((s) => s.sessionId);
|
|
4494
|
+
const draft = useInput((s) => s.draft);
|
|
4452
4495
|
const seat = (0, react.useRef)(null);
|
|
4453
4496
|
const [actions, setActions] = (0, react.useState)();
|
|
4454
4497
|
(0, react.useLayoutEffect)(() => {
|
|
@@ -4463,11 +4506,12 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
4463
4506
|
children: actions && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tools, {
|
|
4464
4507
|
actions,
|
|
4465
4508
|
t,
|
|
4509
|
+
openCommands: openCommands ? () => openCommands(sessionId, draft, inputActions.captureInsertion()) : void 0,
|
|
4466
4510
|
invite: rooms.available() ? () => openMobileMembers(sessionId, true) : void 0
|
|
4467
4511
|
})
|
|
4468
4512
|
});
|
|
4469
4513
|
}
|
|
4470
|
-
function Tools({ actions, t, invite }) {
|
|
4514
|
+
function Tools({ actions, t, invite, openCommands }) {
|
|
4471
4515
|
const targets = (0, react.useSyncExternalStore)(actions.subscribe, actions.getSnapshot);
|
|
4472
4516
|
const dialog = (0, react.useRef)(null), launcher = (0, react.useRef)(null);
|
|
4473
4517
|
const close = () => {
|
|
@@ -4480,7 +4524,7 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
4480
4524
|
if (!targets.commands || !targets.attachments) return null;
|
|
4481
4525
|
const invoke = (action) => {
|
|
4482
4526
|
close();
|
|
4483
|
-
actions.invoke(action);
|
|
4527
|
+
actions.invoke(action, openCommands);
|
|
4484
4528
|
};
|
|
4485
4529
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4486
4530
|
ref: launcher,
|
|
@@ -4489,6 +4533,8 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
4489
4533
|
"aria-haspopup": "dialog",
|
|
4490
4534
|
"aria-expanded": "false",
|
|
4491
4535
|
onClick: () => {
|
|
4536
|
+
if (document.activeElement instanceof HTMLElement) document.activeElement.blur();
|
|
4537
|
+
launcher.current?.focus({ preventScroll: true });
|
|
4492
4538
|
dialog.current?.showModal();
|
|
4493
4539
|
launcher.current?.setAttribute("aria-expanded", "true");
|
|
4494
4540
|
},
|
|
@@ -4772,6 +4818,42 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
4772
4818
|
}), document.body);
|
|
4773
4819
|
}
|
|
4774
4820
|
//#endregion
|
|
4821
|
+
//#region src/client/catalogReads.ts
|
|
4822
|
+
/** Bound optional catalog enrichment so cold log reads cannot fan out across the library. */
|
|
4823
|
+
var CatalogReads = class {
|
|
4824
|
+
limit;
|
|
4825
|
+
pending = [];
|
|
4826
|
+
running = 0;
|
|
4827
|
+
constructor(limit = 2) {
|
|
4828
|
+
this.limit = limit;
|
|
4829
|
+
}
|
|
4830
|
+
add(work, priority = 1) {
|
|
4831
|
+
const item = {
|
|
4832
|
+
cancelled: false,
|
|
4833
|
+
priority,
|
|
4834
|
+
work
|
|
4835
|
+
};
|
|
4836
|
+
this.pending.push(item);
|
|
4837
|
+
this.pending.sort((a, b) => a.priority - b.priority);
|
|
4838
|
+
this.pump();
|
|
4839
|
+
return () => {
|
|
4840
|
+
item.cancelled = true;
|
|
4841
|
+
this.pending = this.pending.filter((entry) => entry !== item);
|
|
4842
|
+
};
|
|
4843
|
+
}
|
|
4844
|
+
pump() {
|
|
4845
|
+
while (this.running < this.limit && this.pending.length) {
|
|
4846
|
+
const item = this.pending.shift();
|
|
4847
|
+
if (item.cancelled) continue;
|
|
4848
|
+
this.running++;
|
|
4849
|
+
Promise.resolve().then(() => item.cancelled ? void 0 : item.work(() => !item.cancelled)).catch(() => {}).finally(() => {
|
|
4850
|
+
this.running--;
|
|
4851
|
+
this.pump();
|
|
4852
|
+
});
|
|
4853
|
+
}
|
|
4854
|
+
}
|
|
4855
|
+
};
|
|
4856
|
+
//#endregion
|
|
4775
4857
|
//#region src/client/native.ts
|
|
4776
4858
|
/** Additive bridge features: older shells and ordinary browsers retain Web controls. */
|
|
4777
4859
|
function hasNativeAction(action) {
|
|
@@ -4793,7 +4875,8 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
4793
4875
|
//#endregion
|
|
4794
4876
|
//#region src/client/MobileLibrary.tsx
|
|
4795
4877
|
/** Browse official metadata; the official workspace service remains the only navigation writer. */
|
|
4796
|
-
function MobileLibrary({ rooms, navigation, onOpen, onBeforeOpen, t }) {
|
|
4878
|
+
function MobileLibrary({ rooms, navigation, onOpen, onBeforeOpen, visible = true, t }) {
|
|
4879
|
+
const reads = (0, react.useMemo)(() => new CatalogReads(), [navigation.sessions]);
|
|
4797
4880
|
const sessionFeed = navigation.sessions.list, workspaceFeed = navigation.workspaces.list;
|
|
4798
4881
|
const sessions = (0, react.useSyncExternalStore)((0, react.useCallback)((listener) => sessionFeed.subscribe(listener), [sessionFeed]), (0, react.useCallback)(() => sessionFeed.getSnapshot(), [sessionFeed]));
|
|
4799
4882
|
const workspaces = (0, react.useSyncExternalStore)((0, react.useCallback)((listener) => workspaceFeed.subscribe(listener), [workspaceFeed]), (0, react.useCallback)(() => workspaceFeed.getSnapshot(), [workspaceFeed]));
|
|
@@ -4891,8 +4974,10 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
4891
4974
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4892
4975
|
"data-mobile-session-copy": true,
|
|
4893
4976
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: row.title || (row.blank ? t("newSession") : row.displayTitle) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionMetadata, {
|
|
4894
|
-
|
|
4895
|
-
|
|
4977
|
+
row,
|
|
4978
|
+
sessions: navigation.sessions,
|
|
4979
|
+
reads,
|
|
4980
|
+
visible,
|
|
4896
4981
|
...rooms ? { rooms } : {},
|
|
4897
4982
|
t
|
|
4898
4983
|
})]
|
|
@@ -4950,29 +5035,56 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
4950
5035
|
}
|
|
4951
5036
|
const emptyRoomSubscribe = () => () => {};
|
|
4952
5037
|
const emptyRoomSnapshot = () => 0;
|
|
4953
|
-
function SessionMetadata({
|
|
5038
|
+
function SessionMetadata({ row, sessions, reads, visible, rooms, t }) {
|
|
4954
5039
|
const ref = (0, react.useRef)(null);
|
|
4955
5040
|
(0, react.useSyncExternalStore)(rooms?.subscribe ?? emptyRoomSubscribe, rooms?.getSnapshot ?? emptyRoomSnapshot);
|
|
4956
5041
|
(0, react.useEffect)(() => {
|
|
4957
|
-
if (!
|
|
5042
|
+
if (!visible || !ref.current) return;
|
|
5043
|
+
let cancel;
|
|
5044
|
+
const enter = () => {
|
|
5045
|
+
if (cancel) return;
|
|
5046
|
+
const cancelTitle = !row.title && !row.blank && typeof sessions.refreshProjections === "function" ? reads.add(async () => {
|
|
5047
|
+
await sessions.refreshProjections(row.id);
|
|
5048
|
+
}, 0) : void 0;
|
|
5049
|
+
const cancelRoom = rooms?.available() ? reads.add(async () => {
|
|
5050
|
+
await rooms.refresh(row.id);
|
|
5051
|
+
}) : void 0;
|
|
5052
|
+
cancel = () => {
|
|
5053
|
+
cancelTitle?.();
|
|
5054
|
+
cancelRoom?.();
|
|
5055
|
+
};
|
|
5056
|
+
};
|
|
5057
|
+
const leave = () => {
|
|
5058
|
+
cancel?.();
|
|
5059
|
+
cancel = void 0;
|
|
5060
|
+
};
|
|
4958
5061
|
if (typeof IntersectionObserver === "undefined") {
|
|
4959
|
-
|
|
4960
|
-
return;
|
|
5062
|
+
enter();
|
|
5063
|
+
return leave;
|
|
4961
5064
|
}
|
|
4962
5065
|
const observer = new IntersectionObserver((entries) => {
|
|
4963
|
-
if (entries.some((
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
}
|
|
4967
|
-
});
|
|
5066
|
+
if (entries.some((entry) => entry.isIntersecting)) enter();
|
|
5067
|
+
else leave();
|
|
5068
|
+
}, { root: ref.current.closest("[data-mobile-recents]") });
|
|
4968
5069
|
observer.observe(ref.current);
|
|
4969
|
-
return () =>
|
|
4970
|
-
|
|
4971
|
-
|
|
5070
|
+
return () => {
|
|
5071
|
+
observer.disconnect();
|
|
5072
|
+
leave();
|
|
5073
|
+
};
|
|
5074
|
+
}, [
|
|
5075
|
+
rooms,
|
|
5076
|
+
row.id,
|
|
5077
|
+
row.title,
|
|
5078
|
+
row.blank,
|
|
5079
|
+
sessions,
|
|
5080
|
+
reads,
|
|
5081
|
+
visible
|
|
5082
|
+
]);
|
|
5083
|
+
const state = rooms?.get(row.id), count = state ? state.members.length : state === null || !rooms?.available() ? 1 : "…";
|
|
4972
5084
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("small", {
|
|
4973
5085
|
ref,
|
|
4974
5086
|
children: [
|
|
4975
|
-
cwd.split(/[\\/]/).filter(Boolean).at(-1) || t("unassignedWorkspace"),
|
|
5087
|
+
(row.cwd ?? "").split(/[\\/]/).filter(Boolean).at(-1) || t("unassignedWorkspace"),
|
|
4976
5088
|
" · ",
|
|
4977
5089
|
count,
|
|
4978
5090
|
" ",
|
|
@@ -5186,6 +5298,7 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
5186
5298
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MobileLibrary, {
|
|
5187
5299
|
...rooms ? { rooms } : {},
|
|
5188
5300
|
navigation: available,
|
|
5301
|
+
visible: library || wide,
|
|
5189
5302
|
t,
|
|
5190
5303
|
onBeforeOpen: presentation.prepareNavigation,
|
|
5191
5304
|
onOpen: () => setLibrary(false)
|
|
@@ -5581,6 +5694,90 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
5581
5694
|
}
|
|
5582
5695
|
};
|
|
5583
5696
|
//#endregion
|
|
5697
|
+
//#region src/client/sheetGestures.ts
|
|
5698
|
+
/** Mobile dismissal delegates to native dialog.close or the Room owner's close handler. */
|
|
5699
|
+
function installSheetGestures(doc) {
|
|
5700
|
+
let drag;
|
|
5701
|
+
const reset = () => {
|
|
5702
|
+
if (drag) drag.sheet.style.transform = drag.transform;
|
|
5703
|
+
drag = void 0;
|
|
5704
|
+
};
|
|
5705
|
+
const close = (overlay) => {
|
|
5706
|
+
if (overlay.isConnected && doc.documentElement.hasAttribute("data-dsh-mobile")) {
|
|
5707
|
+
if (overlay instanceof HTMLDialogElement) {
|
|
5708
|
+
overlay.close();
|
|
5709
|
+
return;
|
|
5710
|
+
}
|
|
5711
|
+
overlay.dispatchEvent(new MouseEvent("mousedown", {
|
|
5712
|
+
bubbles: true,
|
|
5713
|
+
cancelable: true
|
|
5714
|
+
}));
|
|
5715
|
+
}
|
|
5716
|
+
};
|
|
5717
|
+
const down = (event) => {
|
|
5718
|
+
reset();
|
|
5719
|
+
if (!doc.documentElement.hasAttribute("data-dsh-mobile") || event.touches.length !== 1) return;
|
|
5720
|
+
const target = event.target;
|
|
5721
|
+
const native = target.closest("dialog[data-mobile-tools-dialog][open]");
|
|
5722
|
+
const overlay = native ?? target.closest("[data-mobile-room-overlay]");
|
|
5723
|
+
const sheet = native ?? overlay?.querySelector("[data-mobile-room-form]");
|
|
5724
|
+
if (!sheet || !overlay || !native && doc.querySelector("dialog[open]") || doc.getSelection()?.toString()) return;
|
|
5725
|
+
const point = event.touches[0], bounds = sheet.getBoundingClientRect();
|
|
5726
|
+
const outside = native ? target === native && (point.clientY < bounds.top || point.clientY > bounds.bottom || point.clientX < bounds.left || point.clientX > bounds.right) : target === overlay;
|
|
5727
|
+
if (!outside && (sheet.scrollTop > 0 || point.clientY > sheet.getBoundingClientRect().top + 96 || target.closest("button,input,textarea,select,details,a,[contenteditable],[role=menu]"))) return;
|
|
5728
|
+
drag = {
|
|
5729
|
+
sheet,
|
|
5730
|
+
overlay,
|
|
5731
|
+
x: point.clientX,
|
|
5732
|
+
y: point.clientY,
|
|
5733
|
+
outside,
|
|
5734
|
+
dragging: false,
|
|
5735
|
+
transform: sheet.style.transform
|
|
5736
|
+
};
|
|
5737
|
+
};
|
|
5738
|
+
const move = (event) => {
|
|
5739
|
+
if (!drag) return;
|
|
5740
|
+
if (event.touches.length !== 1 || !drag.sheet.isConnected || !doc.documentElement.hasAttribute("data-dsh-mobile")) {
|
|
5741
|
+
reset();
|
|
5742
|
+
return;
|
|
5743
|
+
}
|
|
5744
|
+
const dx = event.touches[0].clientX - drag.x, dy = event.touches[0].clientY - drag.y;
|
|
5745
|
+
if (drag.outside) {
|
|
5746
|
+
if (Math.hypot(dx, dy) > 10) reset();
|
|
5747
|
+
return;
|
|
5748
|
+
}
|
|
5749
|
+
if (!drag.dragging && (dy < -10 || Math.abs(dx) > 12 && Math.abs(dx) > Math.abs(dy))) {
|
|
5750
|
+
reset();
|
|
5751
|
+
return;
|
|
5752
|
+
}
|
|
5753
|
+
if (!drag.dragging && (dy < 12 || dy < Math.abs(dx) * 1.5)) return;
|
|
5754
|
+
drag.dragging = true;
|
|
5755
|
+
drag.sheet.style.transform = `translate3d(0,${Math.max(0, dy)}px,0)`;
|
|
5756
|
+
if (event.cancelable) event.preventDefault();
|
|
5757
|
+
};
|
|
5758
|
+
const up = (event) => {
|
|
5759
|
+
const from = drag, point = event.changedTouches[0];
|
|
5760
|
+
reset();
|
|
5761
|
+
if (!from || !point) return;
|
|
5762
|
+
const dx = point.clientX - from.x, dy = point.clientY - from.y;
|
|
5763
|
+
if (from.outside && Math.hypot(dx, dy) <= 10 || from.dragging && dy >= 72 && dy > Math.abs(dx) * 1.5) {
|
|
5764
|
+
if (event.cancelable) event.preventDefault();
|
|
5765
|
+
close(from.overlay);
|
|
5766
|
+
}
|
|
5767
|
+
};
|
|
5768
|
+
doc.addEventListener("touchstart", down, { passive: true });
|
|
5769
|
+
doc.addEventListener("touchmove", move, { passive: false });
|
|
5770
|
+
doc.addEventListener("touchend", up, { passive: false });
|
|
5771
|
+
doc.addEventListener("touchcancel", reset);
|
|
5772
|
+
return () => {
|
|
5773
|
+
reset();
|
|
5774
|
+
doc.removeEventListener("touchstart", down);
|
|
5775
|
+
doc.removeEventListener("touchmove", move);
|
|
5776
|
+
doc.removeEventListener("touchend", up);
|
|
5777
|
+
doc.removeEventListener("touchcancel", reset);
|
|
5778
|
+
};
|
|
5779
|
+
}
|
|
5780
|
+
//#endregion
|
|
5584
5781
|
//#region src/client/foreground.ts
|
|
5585
5782
|
/** Resume a stale official follow generation, preserving live reply baselines.
|
|
5586
5783
|
* Brief interruptions and duplicate native/browser signals leave healthy streams alone. */
|
|
@@ -5969,6 +6166,7 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
5969
6166
|
navigation
|
|
5970
6167
|
})
|
|
5971
6168
|
}, MobileDirectoryOverlay));
|
|
6169
|
+
ctx.effect(() => installSheetGestures(document), "mobile: sheet dismissal");
|
|
5972
6170
|
ctx.effect(() => () => {
|
|
5973
6171
|
presentation.dispose();
|
|
5974
6172
|
}, "mobile: presentation");
|
|
@@ -6067,7 +6265,14 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
6067
6265
|
id: "mobile-input-tools",
|
|
6068
6266
|
order: -100,
|
|
6069
6267
|
locale: NS,
|
|
6070
|
-
inject: () => ({
|
|
6268
|
+
inject: () => ({
|
|
6269
|
+
rooms,
|
|
6270
|
+
openCommands: (id, draft, span) => {
|
|
6271
|
+
const actx = ctx.get("sessions")?.scope(id);
|
|
6272
|
+
const service = ctx.get("inputTriggers");
|
|
6273
|
+
return actx !== void 0 && typeof service?.sessionOf === "function" && openMobileCommands(service.sessionOf(actx), draft, span);
|
|
6274
|
+
}
|
|
6275
|
+
})
|
|
6071
6276
|
}, MobileTools);
|
|
6072
6277
|
else {
|
|
6073
6278
|
remove?.();
|
|
@@ -119,6 +119,6 @@ export function MobileChrome({ presentation, toggleSidebar, connection, navigati
|
|
|
119
119
|
}, [state.drawer, settings, library, toggleSidebar]);
|
|
120
120
|
if (!state.active && !settings && state.mode !== 'desktop')
|
|
121
121
|
return null;
|
|
122
|
-
return _jsxs(_Fragment, { children: [!state.active && state.mode === 'desktop' && _jsx("button", { "data-mobile-restore": true, onClick: () => presentation.setMode('mobile'), children: t('restoreMobile') }), state.active && _jsxs(_Fragment, { children: [available && _jsxs("aside", { "data-mobile-library-pane": true, hidden: !library && !wide, children: [_jsxs("nav", { "data-mobile-library-toolbar": true, "aria-label": t('menu'), children: [_jsx("button", { "aria-label": t('settings'), onClick: openSettings, children: _jsx(MobileIcon, { name: "settings" }) }), _jsx("button", { "aria-label": t('newSession'), onClick: startSession, children: _jsx(MobileIcon, { name: "compose" }) })] }), _jsx(MobileLibrary, { ...(rooms ? { rooms } : {}), navigation: available, t: t, onBeforeOpen: presentation.prepareNavigation, onOpen: () => setLibrary(false) })] }), library && available && !wide && _jsx("button", { type: "button", "data-mobile-peek-close": true, "aria-label": t('returnToConversation'), onClick: () => { presentation.prepareNavigation(); setLibrary(false); } }), state.drawer && _jsx("button", { "data-mobile-shade": true, "aria-label": t('close'), onClick: toggleSidebar }), _jsxs("nav", { "data-mobile-toolbar": true, "data-library": false, "aria-label": t('menu'), children: [_jsx("button", { "aria-label": t('menu'), onClick: openLibrary, children: _jsx(MobileIcon, { name: "back" }) }), _jsxs("div", { "data-mobile-nav-title": true, children: [(available ? _jsx(ConversationTitle, { navigation: available, prepareNavigation: presentation.prepareNavigation, surface: presentation.surface, fallback: t('newSession'), t: t }) : _jsx("strong", { children: t('brand') })), wire !== 'connected' && !connectionExpanded && _jsx("small", { role: "status", children: t(wire ?? 'connecting') })] }), _jsx("button", { "data-mobile-new": !!available, "aria-label": available ? t('newSession') : t('settings'), onClick: available ? startSession : openSettings, children: _jsx(MobileIcon, { name: available ? "compose" : "settings" }) }), available && rooms && _jsx(MobileRoomNavigation, { rooms: rooms, navigation: available, prepareNavigation: presentation.prepareNavigation, t: t })] }), wire !== 'connected' && connectionExpanded && _jsxs("div", { "data-mobile-connection-status": true, role: "status", children: [_jsx("span", { children: t(wire ?? 'connecting') }), _jsx("button", { onClick: () => connection.reconnect(), children: t('reconnect') })] })] }), _jsxs("dialog", { ref: dialog, "data-mobile-dialog": true, onClose: () => setSettings(false), "aria-label": t('settings'), children: [_jsx("h2", { children: t('settings') }), available && _jsx("button", { "data-mobile-option": true, onClick: startSession, children: t('newSession') }), _jsx("button", { "data-mobile-option": true, onClick: () => { setSettings(false); setLibrary(false); if (!state.drawer)
|
|
122
|
+
return _jsxs(_Fragment, { children: [!state.active && state.mode === 'desktop' && _jsx("button", { "data-mobile-restore": true, onClick: () => presentation.setMode('mobile'), children: t('restoreMobile') }), state.active && _jsxs(_Fragment, { children: [available && _jsxs("aside", { "data-mobile-library-pane": true, hidden: !library && !wide, children: [_jsxs("nav", { "data-mobile-library-toolbar": true, "aria-label": t('menu'), children: [_jsx("button", { "aria-label": t('settings'), onClick: openSettings, children: _jsx(MobileIcon, { name: "settings" }) }), _jsx("button", { "aria-label": t('newSession'), onClick: startSession, children: _jsx(MobileIcon, { name: "compose" }) })] }), _jsx(MobileLibrary, { ...(rooms ? { rooms } : {}), navigation: available, visible: library || wide, t: t, onBeforeOpen: presentation.prepareNavigation, onOpen: () => setLibrary(false) })] }), library && available && !wide && _jsx("button", { type: "button", "data-mobile-peek-close": true, "aria-label": t('returnToConversation'), onClick: () => { presentation.prepareNavigation(); setLibrary(false); } }), state.drawer && _jsx("button", { "data-mobile-shade": true, "aria-label": t('close'), onClick: toggleSidebar }), _jsxs("nav", { "data-mobile-toolbar": true, "data-library": false, "aria-label": t('menu'), children: [_jsx("button", { "aria-label": t('menu'), onClick: openLibrary, children: _jsx(MobileIcon, { name: "back" }) }), _jsxs("div", { "data-mobile-nav-title": true, children: [(available ? _jsx(ConversationTitle, { navigation: available, prepareNavigation: presentation.prepareNavigation, surface: presentation.surface, fallback: t('newSession'), t: t }) : _jsx("strong", { children: t('brand') })), wire !== 'connected' && !connectionExpanded && _jsx("small", { role: "status", children: t(wire ?? 'connecting') })] }), _jsx("button", { "data-mobile-new": !!available, "aria-label": available ? t('newSession') : t('settings'), onClick: available ? startSession : openSettings, children: _jsx(MobileIcon, { name: available ? "compose" : "settings" }) }), available && rooms && _jsx(MobileRoomNavigation, { rooms: rooms, navigation: available, prepareNavigation: presentation.prepareNavigation, t: t })] }), wire !== 'connected' && connectionExpanded && _jsxs("div", { "data-mobile-connection-status": true, role: "status", children: [_jsx("span", { children: t(wire ?? 'connecting') }), _jsx("button", { onClick: () => connection.reconnect(), children: t('reconnect') })] })] }), _jsxs("dialog", { ref: dialog, "data-mobile-dialog": true, onClose: () => setSettings(false), "aria-label": t('settings'), children: [_jsx("h2", { children: t('settings') }), available && _jsx("button", { "data-mobile-option": true, onClick: startSession, children: t('newSession') }), _jsx("button", { "data-mobile-option": true, onClick: () => { setSettings(false); setLibrary(false); if (!state.drawer)
|
|
123
123
|
toggleSidebar(); }, children: t('workspaces') }), hasNativeAction('settings') ? _jsx("button", { "data-mobile-option": true, onClick: () => { setSettings(false); requestNativeAction('settings'); }, children: t('connectionSettings') }) : _jsxs(_Fragment, { children: [_jsx("h3", { children: t('connection') }), _jsx("p", { children: window.location.host }), _jsx("p", { children: t(wire ?? 'connecting') }), _jsx("button", { onClick: () => connection.reconnect(), children: t('reconnect') })] }), _jsx("h3", { children: t('display') }), _jsx("div", { role: "group", "aria-label": t('display'), children: ['auto', 'mobile', 'desktop'].map((mode) => _jsx("button", { "aria-pressed": state.mode === mode, onClick: () => presentation.setMode(mode), children: t(mode === 'auto' ? 'automatic' : mode) }, mode)) }), _jsx("p", { children: t('appearance') }), _jsx("button", { "data-mobile-done": true, onClick: () => setSettings(false), children: t('done') })] })] });
|
|
124
124
|
}
|
|
@@ -6,8 +6,9 @@ type Props = PropsLocale<'mobile'> & {
|
|
|
6
6
|
navigation: NavigationCapabilities;
|
|
7
7
|
onOpen: () => void;
|
|
8
8
|
onBeforeOpen?: () => void;
|
|
9
|
+
visible?: boolean;
|
|
9
10
|
};
|
|
10
11
|
/** Browse official metadata; the official workspace service remains the only navigation writer. */
|
|
11
|
-
export declare function MobileLibrary({ rooms, navigation, onOpen, onBeforeOpen, t }: Props): import("react").JSX.Element;
|
|
12
|
+
export declare function MobileLibrary({ rooms, navigation, onOpen, onBeforeOpen, visible, t }: Props): import("react").JSX.Element;
|
|
12
13
|
export {};
|
|
13
14
|
//# sourceMappingURL=MobileLibrary.d.ts.map
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
|
|
3
3
|
import { GROUPING_KEY, groupSessions, mainSessionId, recentSessions } from './navigation.js';
|
|
4
|
+
import { CatalogReads } from './catalogReads.js';
|
|
4
5
|
import { MobileIcon } from './MobileIcon.js';
|
|
5
6
|
import { hasNativeAction, requestNativeAction } from './native.js';
|
|
6
7
|
/** Browse official metadata; the official workspace service remains the only navigation writer. */
|
|
7
|
-
export function MobileLibrary({ rooms, navigation, onOpen, onBeforeOpen, t }) {
|
|
8
|
+
export function MobileLibrary({ rooms, navigation, onOpen, onBeforeOpen, visible = true, t }) {
|
|
9
|
+
const reads = useMemo(() => new CatalogReads(), [navigation.sessions]);
|
|
8
10
|
const sessionFeed = navigation.sessions.list, workspaceFeed = navigation.workspaces.list;
|
|
9
11
|
// Official feeds may expose prototype methods. Keep their receiver and stable subscriptions.
|
|
10
12
|
const sessions = useSyncExternalStore(useCallback(listener => sessionFeed.subscribe(listener), [sessionFeed]), useCallback(() => sessionFeed.getSnapshot(), [sessionFeed]));
|
|
@@ -46,28 +48,45 @@ export function MobileLibrary({ rooms, navigation, onOpen, onBeforeOpen, t }) {
|
|
|
46
48
|
catch {
|
|
47
49
|
setError(true);
|
|
48
50
|
}
|
|
49
|
-
}, children: [_jsxs("span", { "data-mobile-session-copy": true, children: [_jsx("strong", { children: row.title || (row.blank ? t('newSession') : row.displayTitle) }), _jsx(SessionMetadata, {
|
|
51
|
+
}, children: [_jsxs("span", { "data-mobile-session-copy": true, children: [_jsx("strong", { children: row.title || (row.blank ? t('newSession') : row.displayTitle) }), _jsx(SessionMetadata, { row: row, sessions: navigation.sessions, reads: reads, visible: visible, ...(rooms ? { rooms } : {}), t: t })] }), row.running ? _jsx("span", { "data-mobile-activity": true, role: "status", children: t('running') }) : _jsx("time", { dateTime: new Date(row.updatedAt).toISOString(), children: new Date(row.updatedAt).toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) })] }) }, row.id)) })] }, group.key);
|
|
50
52
|
})] }), _jsxs("footer", { "data-mobile-search-dock": true, children: [_jsxs("label", { "data-mobile-search": true, children: [_jsx(MobileIcon, { name: "search", size: 20 }), _jsx("input", { ref: input, type: "search", enterKeyHint: "search", autoComplete: "off", value: query, onFocus: () => setSearching(true), onChange: e => setQuery(e.target.value), placeholder: t('searchTitles'), "aria-label": t('searchTitles') })] }), searching ? _jsx("button", { "data-mobile-search-cancel": true, "aria-label": t('cancel'), onClick: () => { setQuery(''); setSearching(false); input.current?.blur(); }, children: _jsx(MobileIcon, { name: "close" }) }) : hasNativeAction('scan') && _jsx("button", { "data-mobile-scan": true, "aria-label": t('scan'), onClick: () => requestNativeAction('scan'), children: _jsx(MobileIcon, { name: "scan" }) })] })] });
|
|
51
53
|
}
|
|
52
54
|
const emptyRoomSubscribe = () => () => { };
|
|
53
55
|
const emptyRoomSnapshot = () => 0;
|
|
54
|
-
function SessionMetadata({
|
|
56
|
+
function SessionMetadata({ row, sessions, reads, visible, rooms, t }) {
|
|
55
57
|
const ref = useRef(null);
|
|
56
58
|
useSyncExternalStore(rooms?.subscribe ?? emptyRoomSubscribe, rooms?.getSnapshot ?? emptyRoomSnapshot);
|
|
57
59
|
useEffect(() => {
|
|
58
|
-
if (!
|
|
60
|
+
if (!visible || !ref.current)
|
|
59
61
|
return;
|
|
62
|
+
let cancel;
|
|
63
|
+
const enter = () => {
|
|
64
|
+
if (cancel)
|
|
65
|
+
return;
|
|
66
|
+
// Cold list hints may omit titles. Read the official projection store without
|
|
67
|
+
// retaining/opening a Session; queued title reads take priority over Room scans.
|
|
68
|
+
const cancelTitle = !row.title && !row.blank && typeof sessions.refreshProjections === 'function'
|
|
69
|
+
? reads.add(async () => { await sessions.refreshProjections(row.id); }, 0)
|
|
70
|
+
: undefined;
|
|
71
|
+
const cancelRoom = rooms?.available()
|
|
72
|
+
? reads.add(async () => { await rooms.refresh(row.id); })
|
|
73
|
+
: undefined;
|
|
74
|
+
cancel = () => { cancelTitle?.(); cancelRoom?.(); };
|
|
75
|
+
};
|
|
76
|
+
const leave = () => { cancel?.(); cancel = undefined; };
|
|
60
77
|
if (typeof IntersectionObserver === 'undefined') {
|
|
61
|
-
|
|
62
|
-
return;
|
|
78
|
+
enter();
|
|
79
|
+
return leave;
|
|
63
80
|
}
|
|
64
|
-
const observer = new IntersectionObserver(entries => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
81
|
+
const observer = new IntersectionObserver(entries => {
|
|
82
|
+
if (entries.some(entry => entry.isIntersecting))
|
|
83
|
+
enter();
|
|
84
|
+
else
|
|
85
|
+
leave();
|
|
86
|
+
}, { root: ref.current.closest('[data-mobile-recents]') });
|
|
68
87
|
observer.observe(ref.current);
|
|
69
|
-
return () => observer.disconnect();
|
|
70
|
-
}, [rooms, id]);
|
|
71
|
-
const state = rooms?.get(id), count = state ? state.members.length : state === null || !rooms?.available() ? 1 : '…';
|
|
72
|
-
return _jsxs("small", { ref: ref, children: [cwd.split(/[\\/]/).filter(Boolean).at(-1) || t('unassignedWorkspace'), " \u00B7 ", count, " ", t('memberUnit')] });
|
|
88
|
+
return () => { observer.disconnect(); leave(); };
|
|
89
|
+
}, [rooms, row.id, row.title, row.blank, sessions, reads, visible]);
|
|
90
|
+
const state = rooms?.get(row.id), count = state ? state.members.length : state === null || !rooms?.available() ? 1 : '…';
|
|
91
|
+
return _jsxs("small", { ref: ref, children: [(row.cwd ?? '').split(/[\\/]/).filter(Boolean).at(-1) || t('unassignedWorkspace'), " \u00B7 ", count, " ", t('memberUnit')] });
|
|
73
92
|
}
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import type { PropsRuntime, PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
|
+
import { ComposerActions } from './composerActions.ts';
|
|
2
3
|
import type { MobileRooms } from './rooms.ts';
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
import type { SessionSummary } from '@deepseek-ai/dsh-api-session-controller/client';
|
|
5
|
+
import type { TokenSpan } from '@deepseek-ai/dsh-client-ui-conversation/client';
|
|
6
|
+
export interface MobileToolsInjected {
|
|
5
7
|
rooms: MobileRooms;
|
|
6
|
-
|
|
8
|
+
openCommands: (id: SessionSummary['id'], draft: string, span: TokenSpan) => boolean;
|
|
9
|
+
}
|
|
10
|
+
/** Own only the launcher and grouped navigation; action targets stay host-owned. */
|
|
11
|
+
export declare function MobileTools({ t, useSession, useInput, inputActions, rooms, openCommands }: PropsLocale<'mobile'> & PropsRuntime<'conversation.input.left'> & MobileToolsInjected): import("react").JSX.Element;
|
|
12
|
+
export declare function Tools({ actions, t, invite, openCommands }: {
|
|
13
|
+
actions: ComposerActions;
|
|
14
|
+
openCommands?: (() => boolean) | undefined;
|
|
15
|
+
invite: (() => void) | undefined;
|
|
16
|
+
} & PropsLocale<'mobile'>): import("react").JSX.Element | null;
|
|
7
17
|
//# sourceMappingURL=MobileTools.d.ts.map
|