@anionex/dsh-pinned-sessions 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/README.md +79 -20
- package/README.zh.md +81 -22
- package/assets/hero.png +0 -0
- package/assets/pinned-session-menu.png +0 -0
- package/assets/social-preview.png +0 -0
- package/lib/client.js +433 -42
- package/lib/client.js.map +1 -1
- package/lib/types/client/index.d.ts +33 -2
- package/package.json +5 -2
- package/src/client/dsh-primitives.d.ts +50 -0
- package/src/client/index.tsx +475 -47
package/lib/client.js
CHANGED
|
@@ -28,6 +28,7 @@ __export(index_exports, {
|
|
|
28
28
|
});
|
|
29
29
|
module.exports = __toCommonJS(index_exports);
|
|
30
30
|
var import_react3 = require("react");
|
|
31
|
+
var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
31
32
|
|
|
32
33
|
// node_modules/lucide-react/dist/esm/createLucideIcon.js
|
|
33
34
|
var import_react2 = require("react");
|
|
@@ -100,6 +101,20 @@ var createLucideIcon = (iconName, iconNode) => {
|
|
|
100
101
|
return Component;
|
|
101
102
|
};
|
|
102
103
|
|
|
104
|
+
// node_modules/lucide-react/dist/esm/icons/pin-off.js
|
|
105
|
+
var PinOff = createLucideIcon("PinOff", [
|
|
106
|
+
["path", { d: "M12 17v5", key: "bb1du9" }],
|
|
107
|
+
["path", { d: "M15 9.34V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H7.89", key: "znwnzq" }],
|
|
108
|
+
["path", { d: "m2 2 20 20", key: "1ooewy" }],
|
|
109
|
+
[
|
|
110
|
+
"path",
|
|
111
|
+
{
|
|
112
|
+
d: "M9 9v1.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h11",
|
|
113
|
+
key: "c9qhm2"
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
]);
|
|
117
|
+
|
|
103
118
|
// node_modules/lucide-react/dist/esm/icons/pin.js
|
|
104
119
|
var Pin = createLucideIcon("Pin", [
|
|
105
120
|
["path", { d: "M12 17v5", key: "bb1du9" }],
|
|
@@ -112,12 +127,6 @@ var Pin = createLucideIcon("Pin", [
|
|
|
112
127
|
]
|
|
113
128
|
]);
|
|
114
129
|
|
|
115
|
-
// node_modules/lucide-react/dist/esm/icons/x.js
|
|
116
|
-
var X = createLucideIcon("X", [
|
|
117
|
-
["path", { d: "M18 6 6 18", key: "1bl5f8" }],
|
|
118
|
-
["path", { d: "m6 6 12 12", key: "d8bk6v" }]
|
|
119
|
-
]);
|
|
120
|
-
|
|
121
130
|
// src/client/index.tsx
|
|
122
131
|
var import_react_dom = require("react-dom");
|
|
123
132
|
|
|
@@ -135,7 +144,7 @@ var defaultAttributes2 = {
|
|
|
135
144
|
};
|
|
136
145
|
|
|
137
146
|
// node_modules/lucide/dist/esm/icons/pin-off.js
|
|
138
|
-
var
|
|
147
|
+
var PinOff2 = [
|
|
139
148
|
"svg",
|
|
140
149
|
defaultAttributes2,
|
|
141
150
|
[
|
|
@@ -271,7 +280,7 @@ function updateSessionMenuItem(target, pinned, label) {
|
|
|
271
280
|
target.label.textContent = label;
|
|
272
281
|
target.icon.replaceChildren(createLucideIcon2(
|
|
273
282
|
target.menu.ownerDocument,
|
|
274
|
-
pinned ?
|
|
283
|
+
pinned ? PinOff2 : Pin2,
|
|
275
284
|
pinned ? "pin-off" : "pin"
|
|
276
285
|
));
|
|
277
286
|
}
|
|
@@ -429,23 +438,69 @@ function sessionDisplayTitle(session, workspaceTitle) {
|
|
|
429
438
|
|
|
430
439
|
// src/client/index.tsx
|
|
431
440
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
441
|
+
var BooleanStore = class {
|
|
442
|
+
value = false;
|
|
443
|
+
listeners = /* @__PURE__ */ new Set();
|
|
444
|
+
getSnapshot = () => this.value;
|
|
445
|
+
subscribe = (listener) => {
|
|
446
|
+
this.listeners.add(listener);
|
|
447
|
+
return () => {
|
|
448
|
+
this.listeners.delete(listener);
|
|
449
|
+
};
|
|
450
|
+
};
|
|
451
|
+
set(value) {
|
|
452
|
+
if (value === this.value) return;
|
|
453
|
+
this.value = value;
|
|
454
|
+
for (const listener of this.listeners) listener();
|
|
455
|
+
}
|
|
456
|
+
};
|
|
432
457
|
var NS = "pinnedSessions";
|
|
433
458
|
var STYLE_ID = "@anionex/dsh-pinned-sessions";
|
|
434
459
|
var MENU_ASSOCIATION_MS = 2e3;
|
|
435
460
|
var zh = {
|
|
461
|
+
"menu.rename": "\u91CD\u547D\u540D",
|
|
462
|
+
"menu.fork": "\u5206\u53C9\u4F1A\u8BDD",
|
|
436
463
|
"menu.pin": "\u7F6E\u9876\u4F1A\u8BDD",
|
|
437
464
|
"menu.unpin": "\u53D6\u6D88\u7F6E\u9876",
|
|
465
|
+
"menu.archive": "\u5F52\u6863\u4F1A\u8BDD",
|
|
466
|
+
"menu.delete": "\u5220\u9664\u4F1A\u8BDD",
|
|
438
467
|
"section.label": "\u7F6E\u9876\u4F1A\u8BDD",
|
|
439
468
|
"session.open": "\u6253\u5F00\u7F6E\u9876\u4F1A\u8BDD\u201C{name}\u201D",
|
|
469
|
+
"session.actions": "\u7F6E\u9876\u4F1A\u8BDD\u201C{name}\u201D\u7684\u64CD\u4F5C",
|
|
470
|
+
"session.archiveFailed": "\u5F52\u6863\u4F1A\u8BDD\u5931\u8D25\uFF1A{detail}",
|
|
471
|
+
"session.forkFailed": "\u5206\u53C9\u4F1A\u8BDD\u5931\u8D25\uFF1A{detail}",
|
|
440
472
|
"session.unpin": "\u53D6\u6D88\u7F6E\u9876\u201C{name}\u201D",
|
|
473
|
+
"dialog.close": "\u5173\u95ED",
|
|
474
|
+
"dialog.cancel": "\u53D6\u6D88",
|
|
475
|
+
"rename.title": "\u91CD\u547D\u540D\u4F1A\u8BDD",
|
|
476
|
+
"rename.field": "\u4F1A\u8BDD\u540D\u79F0",
|
|
477
|
+
"rename.confirm": "\u91CD\u547D\u540D",
|
|
478
|
+
"delete.title": "\u5220\u9664\u4F1A\u8BDD",
|
|
479
|
+
"delete.description": "\u5C06\u6C38\u4E45\u5220\u9664\u4F1A\u8BDD\u201C{name}\u201D\u53CA\u5176\u5B50\u4EE3\u7406\uFF08\u542B\u6B63\u5728\u8FD0\u884C\u7684\uFF09\u548C\u5168\u90E8\u8BB0\u5F55\uFF08\u5BF9\u8BDD\u5185\u5BB9\u3001\u7EDF\u8BA1\u3001\u7F13\u5B58\uFF09\uFF0C\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002",
|
|
480
|
+
"delete.pending": "\u6B63\u5728\u5220\u9664\u4F1A\u8BDD\u2026",
|
|
441
481
|
"workspace.ungrouped": "\u672A\u5206\u7EC4"
|
|
442
482
|
};
|
|
443
483
|
var en = {
|
|
484
|
+
"menu.rename": "Rename",
|
|
485
|
+
"menu.fork": "Fork session",
|
|
444
486
|
"menu.pin": "Pin session",
|
|
445
487
|
"menu.unpin": "Unpin session",
|
|
488
|
+
"menu.archive": "Archive session",
|
|
489
|
+
"menu.delete": "Delete session",
|
|
446
490
|
"section.label": "Pinned sessions",
|
|
447
491
|
"session.open": "Open pinned session \u201C{name}\u201D",
|
|
492
|
+
"session.actions": "Actions for pinned session \u201C{name}\u201D",
|
|
493
|
+
"session.archiveFailed": "Could not archive session: {detail}",
|
|
494
|
+
"session.forkFailed": "Could not fork session: {detail}",
|
|
448
495
|
"session.unpin": "Unpin \u201C{name}\u201D",
|
|
496
|
+
"dialog.close": "Close",
|
|
497
|
+
"dialog.cancel": "Cancel",
|
|
498
|
+
"rename.title": "Rename session",
|
|
499
|
+
"rename.field": "Session name",
|
|
500
|
+
"rename.confirm": "Rename",
|
|
501
|
+
"delete.title": "Delete session",
|
|
502
|
+
"delete.description": "This permanently deletes session \u201C{name}\u201D, its child agents (including any that are still running), and all of its records (conversation, stats, cache). This cannot be undone.",
|
|
503
|
+
"delete.pending": "Deleting session\u2026",
|
|
449
504
|
"workspace.ungrouped": "Ungrouped"
|
|
450
505
|
};
|
|
451
506
|
var styles = `
|
|
@@ -455,19 +510,58 @@ var styles = `
|
|
|
455
510
|
.dsh-pins-header svg{flex:none;color:var(--dsw-alias-state-business-primary)}
|
|
456
511
|
.dsh-pins-list{box-sizing:border-box;max-height:192px;min-width:0;margin:0;padding:0;overflow-y:auto;overscroll-behavior:contain;list-style:none}
|
|
457
512
|
.dsh-pins-row{box-sizing:border-box;display:flex;align-items:center;gap:0;height:32px;min-width:0;border-radius:6px;color:var(--dsw-alias-label-primary)}
|
|
458
|
-
.dsh-pins-row:hover,.dsh-pins-row[data-current="true"]{background:var(--dsw-alias-interactive-bg-hover)}
|
|
513
|
+
.dsh-pins-row:hover,.dsh-pins-row[data-current="true"],.dsh-pins-row[data-menu-open="true"]{background:var(--dsw-alias-interactive-bg-hover)}
|
|
459
514
|
.dsh-pins-open{box-sizing:border-box;display:flex;align-items:center;gap:6px;min-width:0;height:32px;padding:0 4px 0 8px;border:0;background:transparent;color:inherit;cursor:pointer;flex:1;text-align:left}
|
|
460
|
-
.dsh-pins-open:focus-visible,.dsh-pins-
|
|
515
|
+
.dsh-pins-open:focus-visible,.dsh-pins-actions:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}
|
|
461
516
|
.dsh-pins-open>svg{flex:none;color:var(--dsw-alias-state-business-primary)}
|
|
462
517
|
.dsh-pins-title{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:14px;line-height:20px;flex:1}
|
|
463
518
|
.dsh-pins-workspace{max-width:34%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:18px;flex:none}
|
|
464
|
-
.dsh-pins-
|
|
465
|
-
.dsh-pins-row:hover .dsh-pins-
|
|
466
|
-
.dsh-pins-
|
|
467
|
-
.dsh-pins-menu
|
|
468
|
-
|
|
519
|
+
.dsh-pins-actions{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;margin-right:3px;padding:0;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-tertiary);cursor:pointer;opacity:0;pointer-events:none;flex:none}
|
|
520
|
+
.dsh-pins-row:hover .dsh-pins-actions,.dsh-pins-row:focus-within .dsh-pins-actions,.dsh-pins-row[data-menu-open="true"] .dsh-pins-actions{opacity:1;pointer-events:auto}
|
|
521
|
+
.dsh-pins-actions:hover,.dsh-pins-row[data-menu-open="true"] .dsh-pins-actions{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
522
|
+
.dsh-pins-action-menu{display:inline-flex;flex:none}
|
|
523
|
+
.dsh-pins-rename-input{box-sizing:border-box;width:100%;height:36px;padding:0 11px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:0;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit}
|
|
524
|
+
.dsh-pins-rename-input:focus{border-color:var(--dsw-alias-state-business-primary)}
|
|
525
|
+
.dsh-pins-dialog-error{margin-top:8px;color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}
|
|
526
|
+
.dsh-pins-dialog-status{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
|
|
527
|
+
.dsh-pins-delete-action{color:var(--dsw-alias-state-error-primary)!important;border-color:var(--dsw-alias-state-error-primary)!important}
|
|
528
|
+
@media (max-width:767px){.dsh-pins-workspace{display:none}}
|
|
529
|
+
@media (hover:none),(max-width:767px){.dsh-pins-actions{opacity:1;pointer-events:auto}}
|
|
469
530
|
`;
|
|
470
|
-
var inject = ["slots", "sessions", "locale"];
|
|
531
|
+
var inject = ["slots", "sessions", "workspaces", "locale"];
|
|
532
|
+
function deleteSessionService(ctx) {
|
|
533
|
+
try {
|
|
534
|
+
const service = ctx.get("remote.workspaceRegistry");
|
|
535
|
+
return typeof service?.deleteSession === "function" ? service : null;
|
|
536
|
+
} catch {
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
function makePinnedSessionActions(ctx, deleteAvailability) {
|
|
541
|
+
const workspaces = ctx.get("workspaces");
|
|
542
|
+
return {
|
|
543
|
+
renameSession: async (sessionId, title) => {
|
|
544
|
+
const session = ctx.sessions.binding(sessionId)?.session;
|
|
545
|
+
if (session === void 0) throw new Error(`unknown session "${sessionId}"`);
|
|
546
|
+
const result = await session.rename(title);
|
|
547
|
+
if (!result.ok) throw new Error(result.error?.message ?? "Session rename failed");
|
|
548
|
+
},
|
|
549
|
+
forkSession: async (sessionId) => {
|
|
550
|
+
const childId = await ctx.sessions.fork({ sessionId, increaseTitle: true });
|
|
551
|
+
ctx.sessions.open(childId);
|
|
552
|
+
},
|
|
553
|
+
archiveSession: async (sessionId) => {
|
|
554
|
+
await workspaces.archiveSession(sessionId);
|
|
555
|
+
},
|
|
556
|
+
deleteAvailability,
|
|
557
|
+
deleteSession: async (sessionId) => {
|
|
558
|
+
const service = deleteSessionService(ctx);
|
|
559
|
+
if (service === null) throw new Error("Session deletion is unavailable");
|
|
560
|
+
const result = await service.deleteSession(sessionId);
|
|
561
|
+
if (!result.ok) throw new Error(result.error?.message ?? "Session deletion failed");
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
}
|
|
471
565
|
function apply(ctx) {
|
|
472
566
|
let storage;
|
|
473
567
|
try {
|
|
@@ -476,6 +570,15 @@ function apply(ctx) {
|
|
|
476
570
|
storage = void 0;
|
|
477
571
|
}
|
|
478
572
|
const store = new PinStore(storage);
|
|
573
|
+
const deleteAvailability = new BooleanStore();
|
|
574
|
+
const actions = makePinnedSessionActions(ctx, deleteAvailability);
|
|
575
|
+
ctx.inject(["remote.workspaceRegistry"], (serviceCtx) => {
|
|
576
|
+
const available = deleteSessionService(serviceCtx) !== null;
|
|
577
|
+
deleteAvailability.set(available);
|
|
578
|
+
serviceCtx.effect(() => () => {
|
|
579
|
+
deleteAvailability.set(false);
|
|
580
|
+
}, "pinned-sessions: delete capability");
|
|
581
|
+
});
|
|
479
582
|
ctx.effect(() => {
|
|
480
583
|
const onStorage = (event) => {
|
|
481
584
|
if (event.key === PIN_STORAGE_KEY || event.key === null) store.reload();
|
|
@@ -503,10 +606,10 @@ function apply(ctx) {
|
|
|
503
606
|
id: "pinned-sessions-bridge",
|
|
504
607
|
order: 40,
|
|
505
608
|
locale: NS,
|
|
506
|
-
inject: () => ({ store, sessions: ctx.sessions })
|
|
609
|
+
inject: () => ({ store, sessions: ctx.sessions, actions })
|
|
507
610
|
}, PinnedSessionsBridge));
|
|
508
611
|
}
|
|
509
|
-
function PinnedSessionsBridge({ store, sessions, useSessions, useWorkspaces, t }) {
|
|
612
|
+
function PinnedSessionsBridge({ store, sessions, actions, useSessions, useWorkspaces, t }) {
|
|
510
613
|
const sessionSnapshot = useSessions((snapshot) => snapshot);
|
|
511
614
|
const workspaceSnapshot = useWorkspaces((snapshot) => snapshot);
|
|
512
615
|
const pins = (0, import_react3.useSyncExternalStore)(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
@@ -514,7 +617,7 @@ function PinnedSessionsBridge({ store, sessions, useSessions, useWorkspaces, t }
|
|
|
514
617
|
const [sidebarWide, setSidebarWide] = (0, import_react3.useState)(true);
|
|
515
618
|
const pending = (0, import_react3.useRef)(null);
|
|
516
619
|
const activeMenu = (0, import_react3.useRef)(null);
|
|
517
|
-
const
|
|
620
|
+
const focusAfterRemoval = (0, import_react3.useRef)(null);
|
|
518
621
|
(0, import_react3.useLayoutEffect)(() => {
|
|
519
622
|
let active = true;
|
|
520
623
|
let queued = false;
|
|
@@ -684,22 +787,29 @@ function PinnedSessionsBridge({ store, sessions, useSessions, useWorkspaces, t }
|
|
|
684
787
|
});
|
|
685
788
|
}, [pins, sessionSnapshot.byId, t, workspaceSnapshot.items]);
|
|
686
789
|
(0, import_react3.useLayoutEffect)(() => {
|
|
687
|
-
const
|
|
688
|
-
if (
|
|
689
|
-
|
|
690
|
-
focusAfterPinnedRemoval(document, sidebarHost,
|
|
790
|
+
const pendingRemoval = focusAfterRemoval.current;
|
|
791
|
+
if (pendingRemoval === null || rows.some((row) => row.session.id === pendingRemoval.sessionId)) return;
|
|
792
|
+
focusAfterRemoval.current = null;
|
|
793
|
+
focusAfterPinnedRemoval(document, sidebarHost, pendingRemoval.index);
|
|
691
794
|
}, [rows, sidebarHost]);
|
|
692
795
|
return sidebarHost !== null && sidebarHost.isConnected && sidebarWide && rows.length > 0 ? (0, import_react_dom.createPortal)(
|
|
693
796
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
694
797
|
PinnedSessionSection,
|
|
695
798
|
{
|
|
799
|
+
actions,
|
|
800
|
+
cancelRemoval: (sessionId) => {
|
|
801
|
+
if (focusAfterRemoval.current?.sessionId === sessionId) focusAfterRemoval.current = null;
|
|
802
|
+
},
|
|
696
803
|
currentId: sessionSnapshot.current,
|
|
697
804
|
rows,
|
|
698
805
|
open: (sessionId) => {
|
|
699
806
|
sessions.open(sessionId);
|
|
700
807
|
},
|
|
808
|
+
prepareRemoval: (sessionId, index) => {
|
|
809
|
+
focusAfterRemoval.current = { sessionId, index };
|
|
810
|
+
},
|
|
701
811
|
unpin: (sessionId, index) => {
|
|
702
|
-
|
|
812
|
+
focusAfterRemoval.current = { sessionId, index };
|
|
703
813
|
store.toggle(sessionId);
|
|
704
814
|
},
|
|
705
815
|
t
|
|
@@ -708,24 +818,213 @@ function PinnedSessionsBridge({ store, sessions, useSessions, useWorkspaces, t }
|
|
|
708
818
|
sidebarHost
|
|
709
819
|
) : null;
|
|
710
820
|
}
|
|
711
|
-
function PinnedSessionSection({ currentId, rows, open, unpin, t }) {
|
|
821
|
+
function PinnedSessionSection({ actions, cancelRemoval, currentId, rows, open, prepareRemoval, unpin, t }) {
|
|
712
822
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "dsh-pins-section", "aria-label": t("section.label"), children: [
|
|
713
823
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-pins-header", children: [
|
|
714
824
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PinIcon, { size: 13 }),
|
|
715
825
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("section.label") })
|
|
716
826
|
] }),
|
|
717
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "dsh-pins-list", children: rows.map(({ session, title, workspace }, index) => /* @__PURE__ */ (0, import_jsx_runtime.
|
|
827
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "dsh-pins-list", children: rows.map(({ session, title, workspace }, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
828
|
+
PinnedSessionRow,
|
|
829
|
+
{
|
|
830
|
+
actions,
|
|
831
|
+
cancelRemoval,
|
|
832
|
+
current: session.id === currentId,
|
|
833
|
+
index,
|
|
834
|
+
open,
|
|
835
|
+
prepareRemoval,
|
|
836
|
+
session,
|
|
837
|
+
t,
|
|
838
|
+
title,
|
|
839
|
+
unpin,
|
|
840
|
+
workspace
|
|
841
|
+
},
|
|
842
|
+
session.id
|
|
843
|
+
)) })
|
|
844
|
+
] });
|
|
845
|
+
}
|
|
846
|
+
function PinnedSessionRow({ actions, cancelRemoval, current, index, open, prepareRemoval, session, t, title, unpin, workspace }) {
|
|
847
|
+
const [menuOpen, setMenuOpen] = (0, import_react3.useState)(false);
|
|
848
|
+
const [renameOpen, setRenameOpen] = (0, import_react3.useState)(false);
|
|
849
|
+
const [renameDraft, setRenameDraft] = (0, import_react3.useState)("");
|
|
850
|
+
const [renaming, setRenaming] = (0, import_react3.useState)(false);
|
|
851
|
+
const [renameError, setRenameError] = (0, import_react3.useState)(null);
|
|
852
|
+
const [deleteOpen, setDeleteOpen] = (0, import_react3.useState)(false);
|
|
853
|
+
const [deleting, setDeleting] = (0, import_react3.useState)(false);
|
|
854
|
+
const [deleteError, setDeleteError] = (0, import_react3.useState)(null);
|
|
855
|
+
const [actionToast, setActionToast] = (0, import_react3.useState)(null);
|
|
856
|
+
const toastSeq = (0, import_react3.useRef)(0);
|
|
857
|
+
const composing = (0, import_react3.useRef)(false);
|
|
858
|
+
const trigger = (0, import_react3.useRef)(null);
|
|
859
|
+
const menusBeforeOpen = (0, import_react3.useRef)(/* @__PURE__ */ new Set());
|
|
860
|
+
const focusMenuEdge = (0, import_react3.useRef)(null);
|
|
861
|
+
const actionLabel = t("session.actions", { name: title });
|
|
862
|
+
const renameTrimmed = renameDraft.trim();
|
|
863
|
+
const renameBlocked = renaming || renameTrimmed === "";
|
|
864
|
+
const canDelete = (0, import_react3.useSyncExternalStore)(
|
|
865
|
+
actions.deleteAvailability.subscribe,
|
|
866
|
+
actions.deleteAvailability.getSnapshot,
|
|
867
|
+
actions.deleteAvailability.getSnapshot
|
|
868
|
+
);
|
|
869
|
+
const showDesktopActionError = (key, reason) => {
|
|
870
|
+
if (!canDelete) return;
|
|
871
|
+
toastSeq.current += 1;
|
|
872
|
+
setActionToast({
|
|
873
|
+
text: t(key, { detail: reason instanceof Error ? reason.message : String(reason) }),
|
|
874
|
+
seq: toastSeq.current
|
|
875
|
+
});
|
|
876
|
+
};
|
|
877
|
+
const restoreTriggerFocus = () => {
|
|
878
|
+
queueMicrotask(() => {
|
|
879
|
+
trigger.current?.focus({ preventScroll: true });
|
|
880
|
+
});
|
|
881
|
+
};
|
|
882
|
+
const openActionMenu = (focusEdge) => {
|
|
883
|
+
menusBeforeOpen.current = new Set(document.querySelectorAll('[role="menu"]'));
|
|
884
|
+
focusMenuEdge.current = focusEdge;
|
|
885
|
+
setMenuOpen(true);
|
|
886
|
+
};
|
|
887
|
+
(0, import_react3.useLayoutEffect)(() => {
|
|
888
|
+
if (!menuOpen) return;
|
|
889
|
+
let menu;
|
|
890
|
+
let observer;
|
|
891
|
+
let disposed = false;
|
|
892
|
+
const items = () => menu === void 0 ? [] : [...menu.querySelectorAll('[role="menuitem"]:not(:disabled)')];
|
|
893
|
+
const onMenuKeyDown = (event) => {
|
|
894
|
+
if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) return;
|
|
895
|
+
const available = items();
|
|
896
|
+
if (available.length === 0) return;
|
|
897
|
+
const currentIndex = available.indexOf(document.activeElement);
|
|
898
|
+
let nextIndex;
|
|
899
|
+
if (event.key === "Home") nextIndex = 0;
|
|
900
|
+
else if (event.key === "End") nextIndex = available.length - 1;
|
|
901
|
+
else if (event.key === "ArrowDown") nextIndex = currentIndex < 0 ? 0 : (currentIndex + 1) % available.length;
|
|
902
|
+
else nextIndex = currentIndex <= 0 ? available.length - 1 : currentIndex - 1;
|
|
903
|
+
event.preventDefault();
|
|
904
|
+
available[nextIndex]?.focus({ preventScroll: true });
|
|
905
|
+
};
|
|
906
|
+
const onDocumentKeyDown = (event) => {
|
|
907
|
+
if (event.key === "Escape") restoreTriggerFocus();
|
|
908
|
+
};
|
|
909
|
+
const install = () => {
|
|
910
|
+
if (disposed) return;
|
|
911
|
+
menu = [...document.querySelectorAll('[role="menu"]')].find((candidate) => !menusBeforeOpen.current.has(candidate));
|
|
912
|
+
if (menu === void 0) return;
|
|
913
|
+
observer?.disconnect();
|
|
914
|
+
menu.addEventListener("keydown", onMenuKeyDown);
|
|
915
|
+
const edge = focusMenuEdge.current;
|
|
916
|
+
focusMenuEdge.current = null;
|
|
917
|
+
if (edge === null) return;
|
|
918
|
+
const available = items();
|
|
919
|
+
const target = edge === "first" ? available[0] : available.at(-1);
|
|
920
|
+
queueMicrotask(() => {
|
|
921
|
+
if (!disposed && target?.isConnected === true) target.focus({ preventScroll: true });
|
|
922
|
+
});
|
|
923
|
+
};
|
|
924
|
+
document.addEventListener("keydown", onDocumentKeyDown, true);
|
|
925
|
+
install();
|
|
926
|
+
if (menu === void 0) {
|
|
927
|
+
observer = new MutationObserver(install);
|
|
928
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
929
|
+
install();
|
|
930
|
+
}
|
|
931
|
+
return () => {
|
|
932
|
+
disposed = true;
|
|
933
|
+
observer?.disconnect();
|
|
934
|
+
menu?.removeEventListener("keydown", onMenuKeyDown);
|
|
935
|
+
document.removeEventListener("keydown", onDocumentKeyDown, true);
|
|
936
|
+
};
|
|
937
|
+
}, [menuOpen]);
|
|
938
|
+
const menuItems = [
|
|
939
|
+
{ id: "rename", label: t("menu.rename"), icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconEditOutline16, {}) },
|
|
940
|
+
{ id: "fork", label: t("menu.fork"), icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconBranchOutline16, {}) },
|
|
941
|
+
{ id: "unpin", label: t("menu.unpin"), icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PinOff, { size: 16, strokeWidth: 1.8, "aria-hidden": "true" }) },
|
|
942
|
+
{ id: "archive", label: t("menu.archive"), icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconArchiveOutline20, { size: 16 }) },
|
|
943
|
+
...canDelete ? [{ id: "delete", label: t("menu.delete"), icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconTrashOutline16, {}), danger: true }] : []
|
|
944
|
+
];
|
|
945
|
+
const closeRename = () => {
|
|
946
|
+
if (renaming) return;
|
|
947
|
+
setRenameOpen(false);
|
|
948
|
+
setRenameError(null);
|
|
949
|
+
restoreTriggerFocus();
|
|
950
|
+
};
|
|
951
|
+
const confirmRename = () => {
|
|
952
|
+
if (renameBlocked) return;
|
|
953
|
+
setRenaming(true);
|
|
954
|
+
setRenameError(null);
|
|
955
|
+
void actions.renameSession(session.id, renameTrimmed).then(() => {
|
|
956
|
+
setRenaming(false);
|
|
957
|
+
setRenameOpen(false);
|
|
958
|
+
restoreTriggerFocus();
|
|
959
|
+
}).catch((reason) => {
|
|
960
|
+
setRenaming(false);
|
|
961
|
+
setRenameError(reason instanceof Error ? reason.message : String(reason));
|
|
962
|
+
});
|
|
963
|
+
};
|
|
964
|
+
const closeDelete = () => {
|
|
965
|
+
if (deleting) return;
|
|
966
|
+
setDeleteOpen(false);
|
|
967
|
+
setDeleteError(null);
|
|
968
|
+
restoreTriggerFocus();
|
|
969
|
+
};
|
|
970
|
+
const confirmDelete = () => {
|
|
971
|
+
if (deleting) return;
|
|
972
|
+
prepareRemoval(session.id, index);
|
|
973
|
+
setDeleting(true);
|
|
974
|
+
setDeleteError(null);
|
|
975
|
+
void actions.deleteSession(session.id).then(() => {
|
|
976
|
+
setDeleting(false);
|
|
977
|
+
setDeleteOpen(false);
|
|
978
|
+
restoreTriggerFocus();
|
|
979
|
+
}).catch((reason) => {
|
|
980
|
+
cancelRemoval(session.id);
|
|
981
|
+
setDeleting(false);
|
|
982
|
+
setDeleteError(reason instanceof Error ? reason.message : String(reason));
|
|
983
|
+
});
|
|
984
|
+
};
|
|
985
|
+
const onSelect = (id) => {
|
|
986
|
+
setMenuOpen(false);
|
|
987
|
+
if (id === "rename") {
|
|
988
|
+
setRenameDraft(session.title ?? title);
|
|
989
|
+
setRenameError(null);
|
|
990
|
+
setRenameOpen(true);
|
|
991
|
+
}
|
|
992
|
+
if (id === "fork") {
|
|
993
|
+
restoreTriggerFocus();
|
|
994
|
+
void actions.forkSession(session.id).catch((reason) => {
|
|
995
|
+
showDesktopActionError("session.forkFailed", reason);
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
if (id === "unpin") unpin(session.id, index);
|
|
999
|
+
if (id === "archive") {
|
|
1000
|
+
prepareRemoval(session.id, index);
|
|
1001
|
+
restoreTriggerFocus();
|
|
1002
|
+
void actions.archiveSession(session.id).catch((reason) => {
|
|
1003
|
+
cancelRemoval(session.id);
|
|
1004
|
+
restoreTriggerFocus();
|
|
1005
|
+
showDesktopActionError("session.archiveFailed", reason);
|
|
1006
|
+
if (!canDelete) console.warn("session archive rejected:", reason);
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
if (id === "delete" && canDelete) {
|
|
1010
|
+
setDeleteError(null);
|
|
1011
|
+
setDeleteOpen(true);
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1015
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
718
1016
|
"li",
|
|
719
1017
|
{
|
|
720
1018
|
className: "dsh-pins-row",
|
|
721
|
-
"data-current":
|
|
1019
|
+
"data-current": current ? "true" : "false",
|
|
1020
|
+
"data-menu-open": menuOpen ? "true" : void 0,
|
|
722
1021
|
children: [
|
|
723
1022
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
724
1023
|
"button",
|
|
725
1024
|
{
|
|
726
1025
|
type: "button",
|
|
727
1026
|
className: "dsh-pins-open",
|
|
728
|
-
"aria-current":
|
|
1027
|
+
"aria-current": current ? "page" : void 0,
|
|
729
1028
|
"aria-label": t("session.open", { name: title }),
|
|
730
1029
|
onClick: () => {
|
|
731
1030
|
open(session.id);
|
|
@@ -738,38 +1037,130 @@ function PinnedSessionSection({ currentId, rows, open, unpin, t }) {
|
|
|
738
1037
|
}
|
|
739
1038
|
),
|
|
740
1039
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
741
|
-
|
|
1040
|
+
import_dsh_client_ui_primitives.Menu,
|
|
742
1041
|
{
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
onClick: () => {
|
|
748
|
-
unpin(session.id, index);
|
|
1042
|
+
className: "dsh-pins-action-menu",
|
|
1043
|
+
open: menuOpen,
|
|
1044
|
+
onClose: () => {
|
|
1045
|
+
setMenuOpen(false);
|
|
749
1046
|
},
|
|
750
|
-
|
|
1047
|
+
items: menuItems,
|
|
1048
|
+
onSelect,
|
|
1049
|
+
portal: true,
|
|
1050
|
+
closeOnPointerLeave: true,
|
|
1051
|
+
anchor: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1052
|
+
"button",
|
|
1053
|
+
{
|
|
1054
|
+
ref: trigger,
|
|
1055
|
+
type: "button",
|
|
1056
|
+
className: "dsh-pins-actions",
|
|
1057
|
+
"aria-expanded": menuOpen,
|
|
1058
|
+
"aria-haspopup": "menu",
|
|
1059
|
+
"aria-label": actionLabel,
|
|
1060
|
+
title: actionLabel,
|
|
1061
|
+
onClick: (event) => {
|
|
1062
|
+
event.stopPropagation();
|
|
1063
|
+
if (menuOpen) setMenuOpen(false);
|
|
1064
|
+
else openActionMenu(null);
|
|
1065
|
+
},
|
|
1066
|
+
onKeyDown: (event) => {
|
|
1067
|
+
if (event.key !== "ArrowDown" && event.key !== "ArrowUp") return;
|
|
1068
|
+
event.preventDefault();
|
|
1069
|
+
openActionMenu(event.key === "ArrowDown" ? "first" : "last");
|
|
1070
|
+
},
|
|
1071
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconEllipsisOutline16, {})
|
|
1072
|
+
}
|
|
1073
|
+
)
|
|
751
1074
|
}
|
|
752
1075
|
)
|
|
753
1076
|
]
|
|
1077
|
+
}
|
|
1078
|
+
),
|
|
1079
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1080
|
+
import_dsh_client_ui_primitives.Modal,
|
|
1081
|
+
{
|
|
1082
|
+
open: renameOpen,
|
|
1083
|
+
onClose: closeRename,
|
|
1084
|
+
closeLabel: t("dialog.close"),
|
|
1085
|
+
title: t("rename.title"),
|
|
1086
|
+
footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1087
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", disabled: renaming, onClick: closeRename, children: t("dialog.cancel") }),
|
|
1088
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "primary", disabled: renameBlocked, onClick: confirmRename, children: t("rename.confirm") })
|
|
1089
|
+
] }),
|
|
1090
|
+
children: [
|
|
1091
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1092
|
+
"input",
|
|
1093
|
+
{
|
|
1094
|
+
className: "dsh-pins-rename-input",
|
|
1095
|
+
value: renameDraft,
|
|
1096
|
+
"aria-label": t("rename.field"),
|
|
1097
|
+
autoFocus: true,
|
|
1098
|
+
disabled: renaming,
|
|
1099
|
+
onFocus: (event) => {
|
|
1100
|
+
event.currentTarget.select();
|
|
1101
|
+
},
|
|
1102
|
+
onChange: (event) => {
|
|
1103
|
+
setRenameDraft(event.currentTarget.value);
|
|
1104
|
+
setRenameError(null);
|
|
1105
|
+
},
|
|
1106
|
+
onCompositionStart: () => {
|
|
1107
|
+
composing.current = true;
|
|
1108
|
+
},
|
|
1109
|
+
onCompositionEnd: () => {
|
|
1110
|
+
composing.current = false;
|
|
1111
|
+
},
|
|
1112
|
+
onKeyDown: (event) => {
|
|
1113
|
+
if (event.key !== "Enter" || composing.current) return;
|
|
1114
|
+
event.preventDefault();
|
|
1115
|
+
confirmRename();
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
),
|
|
1119
|
+
renameError !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-pins-dialog-error", role: "alert", children: renameError })
|
|
1120
|
+
]
|
|
1121
|
+
}
|
|
1122
|
+
),
|
|
1123
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1124
|
+
import_dsh_client_ui_primitives.Modal,
|
|
1125
|
+
{
|
|
1126
|
+
open: deleteOpen,
|
|
1127
|
+
onClose: closeDelete,
|
|
1128
|
+
closeLabel: t("dialog.close"),
|
|
1129
|
+
title: t("delete.title"),
|
|
1130
|
+
description: t("delete.description", { name: title }),
|
|
1131
|
+
footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1132
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", autoFocus: true, disabled: deleting, onClick: closeDelete, children: t("dialog.cancel") }),
|
|
1133
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", className: "dsh-pins-delete-action", disabled: deleting, onClick: confirmDelete, children: t("delete.title") })
|
|
1134
|
+
] }),
|
|
1135
|
+
children: [
|
|
1136
|
+
deleting && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-pins-dialog-status", role: "status", children: t("delete.pending") }),
|
|
1137
|
+
deleteError !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-pins-dialog-error", role: "alert", children: deleteError })
|
|
1138
|
+
]
|
|
1139
|
+
}
|
|
1140
|
+
),
|
|
1141
|
+
actionToast !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1142
|
+
import_dsh_client_ui_primitives.Toast,
|
|
1143
|
+
{
|
|
1144
|
+
text: actionToast.text,
|
|
1145
|
+
onDone: () => {
|
|
1146
|
+
setActionToast(null);
|
|
1147
|
+
}
|
|
754
1148
|
},
|
|
755
|
-
|
|
756
|
-
)
|
|
1149
|
+
actionToast.seq
|
|
1150
|
+
)
|
|
757
1151
|
] });
|
|
758
1152
|
}
|
|
759
1153
|
function PinIcon({ size = 16 }) {
|
|
760
1154
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pin, { size, strokeWidth: 1.8, "aria-hidden": "true" });
|
|
761
1155
|
}
|
|
762
|
-
function CloseIcon() {
|
|
763
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { size: 14, strokeWidth: 2, "aria-hidden": "true" });
|
|
764
|
-
}
|
|
765
1156
|
/*! Bundled license information:
|
|
766
1157
|
|
|
767
1158
|
lucide-react/dist/esm/shared/src/utils.js:
|
|
768
1159
|
lucide-react/dist/esm/defaultAttributes.js:
|
|
769
1160
|
lucide-react/dist/esm/Icon.js:
|
|
770
1161
|
lucide-react/dist/esm/createLucideIcon.js:
|
|
1162
|
+
lucide-react/dist/esm/icons/pin-off.js:
|
|
771
1163
|
lucide-react/dist/esm/icons/pin.js:
|
|
772
|
-
lucide-react/dist/esm/icons/x.js:
|
|
773
1164
|
lucide-react/dist/esm/lucide-react.js:
|
|
774
1165
|
(**
|
|
775
1166
|
* @license lucide-react v0.468.0 - ISC
|