@michengai/dsh-codex-ui 0.2.71 → 0.2.73
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/dist/client.js +211 -264
- package/dist/index.mjs +127 -44
- package/package.json +7 -6
package/dist/client.js
CHANGED
|
@@ -18,6 +18,7 @@ window.__ModuleLoader__.load({
|
|
|
18
18
|
if (match !== void 0) return match;
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
+
let cancelPendingNavigation;
|
|
21
22
|
function openSettingsSection(root, label, onMissing) {
|
|
22
23
|
const labels = typeof label === "string" ? [label] : label;
|
|
23
24
|
const trigger = root?.querySelector("[aria-haspopup=\"dialog\"]");
|
|
@@ -25,18 +26,48 @@ window.__ModuleLoader__.load({
|
|
|
25
26
|
onMissing?.();
|
|
26
27
|
return;
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
29
|
+
const opening = cancelPendingNavigation !== void 0;
|
|
30
|
+
cancelPendingNavigation?.();
|
|
31
|
+
if (!opening && document.querySelector("[role=\"dialog\"]") === null) trigger.click();
|
|
32
|
+
let frame;
|
|
33
|
+
let finished = false;
|
|
34
|
+
const observer = new MutationObserver(() => {
|
|
35
|
+
schedule();
|
|
36
|
+
});
|
|
37
|
+
const cleanup = () => {
|
|
38
|
+
if (finished) return;
|
|
39
|
+
finished = true;
|
|
40
|
+
observer.disconnect();
|
|
41
|
+
window.clearTimeout(timeout);
|
|
42
|
+
if (frame !== void 0) window.cancelAnimationFrame(frame);
|
|
43
|
+
if (cancelPendingNavigation === cleanup) cancelPendingNavigation = void 0;
|
|
44
|
+
};
|
|
45
|
+
const select = () => {
|
|
46
|
+
const target = pickSettingsSectionButton([...document.querySelectorAll("[role=\"dialog\"] nav button")], labels);
|
|
47
|
+
if (target === void 0) return false;
|
|
48
|
+
cleanup();
|
|
49
|
+
target.click();
|
|
50
|
+
return true;
|
|
51
|
+
};
|
|
52
|
+
const schedule = () => {
|
|
53
|
+
if (finished || frame !== void 0) return;
|
|
54
|
+
frame = window.requestAnimationFrame(() => {
|
|
55
|
+
frame = void 0;
|
|
56
|
+
select();
|
|
38
57
|
});
|
|
58
|
+
};
|
|
59
|
+
const timeout = window.setTimeout(() => {
|
|
60
|
+
if (select()) return;
|
|
61
|
+
cleanup();
|
|
62
|
+
console.warn(`[michengai-codex-ui] 未找到设置分区:${labels.join(" / ")}`);
|
|
63
|
+
onMissing?.();
|
|
64
|
+
}, 1500);
|
|
65
|
+
observer.observe(document.body, {
|
|
66
|
+
childList: true,
|
|
67
|
+
subtree: true
|
|
39
68
|
});
|
|
69
|
+
cancelPendingNavigation = cleanup;
|
|
70
|
+
schedule();
|
|
40
71
|
}
|
|
41
72
|
//#endregion
|
|
42
73
|
//#region src/client/sidebar-search.ts
|
|
@@ -52,6 +83,7 @@ window.__ModuleLoader__.load({
|
|
|
52
83
|
channels: false,
|
|
53
84
|
schedule: false
|
|
54
85
|
};
|
|
86
|
+
const reportedSlotErrors = /* @__PURE__ */ new WeakSet();
|
|
55
87
|
/** 插槽声明本身不算占用;只有其他插件 register 后才视为已安装。 */
|
|
56
88
|
function readSlotEntries(slots, name) {
|
|
57
89
|
if (slots === void 0) return [];
|
|
@@ -59,7 +91,11 @@ window.__ModuleLoader__.load({
|
|
|
59
91
|
if (typeof read !== "function") return [];
|
|
60
92
|
try {
|
|
61
93
|
return read.call(slots, name) ?? [];
|
|
62
|
-
} catch {
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (!reportedSlotErrors.has(slots)) {
|
|
96
|
+
reportedSlotErrors.add(slots);
|
|
97
|
+
console.warn("[michengai-codex-ui] 无法读取配套插件插槽。", error);
|
|
98
|
+
}
|
|
63
99
|
return [];
|
|
64
100
|
}
|
|
65
101
|
}
|
|
@@ -251,18 +287,25 @@ window.__ModuleLoader__.load({
|
|
|
251
287
|
});
|
|
252
288
|
}
|
|
253
289
|
/** 读取 IM 频道分组;失败时交给界面显示空态或错误。 */
|
|
254
|
-
async function loadChannelGroups() {
|
|
255
|
-
const response = await fetch(CHANNELS_ENDPOINT, {
|
|
256
|
-
|
|
257
|
-
|
|
290
|
+
async function loadChannelGroups(signal) {
|
|
291
|
+
const response = await fetch(CHANNELS_ENDPOINT, {
|
|
292
|
+
cache: "no-store",
|
|
293
|
+
signal
|
|
294
|
+
});
|
|
295
|
+
let payload;
|
|
296
|
+
try {
|
|
297
|
+
payload = await response.json();
|
|
298
|
+
} catch {
|
|
299
|
+
throw new Error(response.ok ? "频道会话数据格式无效" : "无法读取频道会话");
|
|
300
|
+
}
|
|
258
301
|
const root = payload !== null && typeof payload === "object" ? payload : {};
|
|
302
|
+
if (!response.ok) throw new Error(text(root.error, "无法读取频道会话"));
|
|
259
303
|
if (root.ok === false) throw new Error(text(root.error, "无法读取频道会话"));
|
|
260
304
|
return parseChannelGroups(payload);
|
|
261
305
|
}
|
|
262
306
|
//#endregion
|
|
263
307
|
//#region src/client/session-manager.ts
|
|
264
308
|
const SESSION_PINS_STORAGE_KEY = "dsh.session-pins.v1";
|
|
265
|
-
const SESSION_SECTION_PINS_STORAGE_KEY = "dsh-codex-ui.pinned-section-sessions.v1";
|
|
266
309
|
const SESSION_UNREAD_STORAGE_KEY = "dsh.session-unread.v1";
|
|
267
310
|
function normalizeSessionIds(ids) {
|
|
268
311
|
return [...new Set(ids.filter((id) => id.trim() !== ""))];
|
|
@@ -295,15 +338,6 @@ window.__ModuleLoader__.load({
|
|
|
295
338
|
url.searchParams.set("session", sessionId);
|
|
296
339
|
return url.toString();
|
|
297
340
|
}
|
|
298
|
-
/** 把会话插入置顶列表的指定位置;省略锚点时追加到末尾。 */
|
|
299
|
-
function insertSessionId(ids, id, beforeId) {
|
|
300
|
-
const next = ids.filter((item) => item !== id);
|
|
301
|
-
if (beforeId === void 0) return [...next, id];
|
|
302
|
-
if (beforeId === id) return ids.includes(id) ? [...ids] : [...next, id];
|
|
303
|
-
const index = next.indexOf(beforeId);
|
|
304
|
-
next.splice(index < 0 ? next.length : index, 0, id);
|
|
305
|
-
return next;
|
|
306
|
-
}
|
|
307
341
|
//#endregion
|
|
308
342
|
//#region src/client/session-tree.tsx
|
|
309
343
|
function PinIcon() {
|
|
@@ -689,7 +723,6 @@ window.__ModuleLoader__.load({
|
|
|
689
723
|
card.append(sub);
|
|
690
724
|
}
|
|
691
725
|
card.dataset.dcuUserText = text;
|
|
692
|
-
card.title = text;
|
|
693
726
|
}
|
|
694
727
|
}
|
|
695
728
|
function ensureUserBubbleStyle(doc) {
|
|
@@ -800,19 +833,25 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
800
833
|
slider.style.transform = `translateX(${Math.max(0, tabBox.left - listBox.left)}px)`;
|
|
801
834
|
}
|
|
802
835
|
function watchTabSelection(tabs) {
|
|
803
|
-
if (tabs.dataset.dcuTabWatch === "") return;
|
|
804
836
|
tabs.dataset.dcuTabWatch = "";
|
|
805
837
|
const sync = () => {
|
|
806
838
|
syncTabSlider(tabs);
|
|
807
839
|
};
|
|
808
|
-
|
|
840
|
+
const onClick = () => {
|
|
841
|
+
window.requestAnimationFrame(sync);
|
|
842
|
+
};
|
|
843
|
+
const observer = new MutationObserver(sync);
|
|
844
|
+
observer.observe(tabs, {
|
|
809
845
|
attributes: true,
|
|
810
846
|
subtree: true,
|
|
811
847
|
attributeFilter: ["aria-selected", "data-state"]
|
|
812
848
|
});
|
|
813
|
-
tabs.addEventListener("click",
|
|
814
|
-
|
|
815
|
-
|
|
849
|
+
tabs.addEventListener("click", onClick);
|
|
850
|
+
return () => {
|
|
851
|
+
observer.disconnect();
|
|
852
|
+
tabs.removeEventListener("click", onClick);
|
|
853
|
+
delete tabs.dataset.dcuTabWatch;
|
|
854
|
+
};
|
|
816
855
|
}
|
|
817
856
|
function ensureStyle(doc) {
|
|
818
857
|
if (doc.getElementById("dcu-conversation-header-style") !== null) return;
|
|
@@ -828,6 +867,8 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
828
867
|
ensureUserBubbleStyle(doc);
|
|
829
868
|
let applying = false;
|
|
830
869
|
let frame;
|
|
870
|
+
let watchedTabs;
|
|
871
|
+
let stopWatchingTabs;
|
|
831
872
|
const run = () => {
|
|
832
873
|
frame = void 0;
|
|
833
874
|
if (applying) return;
|
|
@@ -835,7 +876,11 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
835
876
|
try {
|
|
836
877
|
placeConversationTabs(doc);
|
|
837
878
|
const tabs = findConversationTablist(doc);
|
|
838
|
-
if (tabs !==
|
|
879
|
+
if (tabs !== watchedTabs) {
|
|
880
|
+
stopWatchingTabs?.();
|
|
881
|
+
watchedTabs = tabs;
|
|
882
|
+
stopWatchingTabs = tabs === void 0 ? void 0 : watchTabSelection(tabs);
|
|
883
|
+
}
|
|
839
884
|
syncTabSlider(doc);
|
|
840
885
|
decorateConversationTitle(doc);
|
|
841
886
|
decorateUserBubbles(doc);
|
|
@@ -855,6 +900,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
855
900
|
});
|
|
856
901
|
return () => {
|
|
857
902
|
observer.disconnect();
|
|
903
|
+
stopWatchingTabs?.();
|
|
858
904
|
if (frame !== void 0) window.cancelAnimationFrame(frame);
|
|
859
905
|
};
|
|
860
906
|
}
|
|
@@ -913,6 +959,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
913
959
|
window.clearTimeout(showTipTimer.current);
|
|
914
960
|
showTipTimer.current = void 0;
|
|
915
961
|
}
|
|
962
|
+
if (hideTipTimer.current !== void 0) window.clearTimeout(hideTipTimer.current);
|
|
916
963
|
hideTipTimer.current = window.setTimeout(() => {
|
|
917
964
|
setHoverTip(void 0);
|
|
918
965
|
}, 120);
|
|
@@ -1121,22 +1168,10 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
1121
1168
|
return item === void 0 ? [] : [item];
|
|
1122
1169
|
});
|
|
1123
1170
|
}
|
|
1124
|
-
/** 置顶区的会话按拖入顺序展示;不因父项目已置顶而隐藏。 */
|
|
1125
|
-
function standalonePinnedSessionIds(pinnedSessionIds) {
|
|
1126
|
-
return [...pinnedSessionIds];
|
|
1127
|
-
}
|
|
1128
|
-
/** 置顶区已有会话时,不再同时展示任何项目文件夹,避免旧置顶项目叠成两条。 */
|
|
1129
|
-
function workspaceIdsHiddenByPinnedSessions(workspaces, pinnedSessionIds) {
|
|
1130
|
-
if (pinnedSessionIds.length === 0) return [];
|
|
1131
|
-
return workspaces.map((workspace) => String(workspace.workspaceId));
|
|
1132
|
-
}
|
|
1133
1171
|
const SESSION_DRAG_TYPE = "application/x-dcu-session";
|
|
1134
1172
|
const WORKSPACE_DRAG_TYPE = "application/x-dcu-workspace";
|
|
1135
1173
|
const SESSION_DRAG_PREFIX = "dcu-session:";
|
|
1136
1174
|
const WORKSPACE_DRAG_PREFIX = "dcu-workspace:";
|
|
1137
|
-
function dragTypes(data) {
|
|
1138
|
-
return data === void 0 ? [] : Array.from(data.types);
|
|
1139
|
-
}
|
|
1140
1175
|
function textPayload(data) {
|
|
1141
1176
|
try {
|
|
1142
1177
|
return data?.getData("text/plain") ?? "";
|
|
@@ -1144,11 +1179,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
1144
1179
|
return "";
|
|
1145
1180
|
}
|
|
1146
1181
|
}
|
|
1147
|
-
/** 拖过置顶区时用来决定是否 preventDefault;只看 types,不读数据。 */
|
|
1148
|
-
function isSidebarItemDrag(data) {
|
|
1149
|
-
const types = dragTypes(data);
|
|
1150
|
-
return types.includes("application/x-dcu-session") || types.includes("application/x-dcu-workspace") || types.includes("text/plain");
|
|
1151
|
-
}
|
|
1152
1182
|
/** 写入会话拖拽载荷;drop 时以这个为准,不依赖尚未刷新的 React 状态。 */
|
|
1153
1183
|
function writeSessionDrag(data, sessionId, title) {
|
|
1154
1184
|
data.effectAllowed = "move";
|
|
@@ -1190,11 +1220,11 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
1190
1220
|
.dcu-wb-section-head{position:relative;display:flex;align-items:center;min-height:24px;padding:0 4px;border-radius:6px}.dcu-wb-section-head:hover .dcu-wb-actions{display:flex}.dcu-wb-section-label{display:flex;align-items:center;gap:4px;flex:1;min-width:0;min-height:24px;border:0;padding:2px 4px;background:transparent;color:var(--dcu-sidebar-tertiary);font:13px/20px var(--dcu-font,inherit);font-weight:400;letter-spacing:.02em;text-align:left;cursor:pointer}.dcu-wb-section-caret{display:grid;place-items:center;flex:none;width:12px;height:12px;color:currentColor;opacity:0;transform:rotate(0deg);transform-origin:50% 50%;transition:opacity 140ms ease,transform 180ms ease}.dcu-wb-section-caret svg{display:block}.dcu-wb-section-head:hover .dcu-wb-section-caret,.dcu-wb-section-label:focus-visible .dcu-wb-section-caret{opacity:.78}.dcu-wb-section-label[aria-expanded=true] .dcu-wb-section-caret{transform:rotate(90deg)}.dcu-wb-section-body{display:grid;grid-template-rows:0fr;transition:grid-template-rows 220ms ease}.dcu-wb-section-body[data-open=true]{grid-template-rows:1fr}.dcu-wb-section-body>div{overflow:hidden;min-height:0}@media (prefers-reduced-motion:reduce){.dcu-wb-section-caret,.dcu-wb-section-body{transition:none}}
|
|
1191
1221
|
.dcu-wb-project{position:relative}
|
|
1192
1222
|
.dcu-wb-project-head,.dcu-wb-session{position:relative;display:flex;align-items:center;gap:6px;width:100%;border-radius:8px;padding:0 8px;color:var(--dcu-sidebar-primary);cursor:pointer}
|
|
1193
|
-
.dcu-wb-project-head{height:
|
|
1194
|
-
.dcu-wb-project-head:hover,.dcu-wb-project-head.dcu-wb-menu-open,.dcu-wb-session:hover,.dcu-wb-session.dcu-wb-selected,.dcu-wb-session.dcu-wb-menu-open{background:var(--dcu-sidebar-hover)}.dcu-wb-project-head+.dcu-wb-session,.dcu-wb-project-head+.dcu-wb-nochat{margin-top:
|
|
1223
|
+
.dcu-wb-project-head{height:28px;background:transparent;font:inherit;text-align:left}
|
|
1224
|
+
.dcu-wb-project-head:hover,.dcu-wb-project-head.dcu-wb-menu-open,.dcu-wb-session:hover,.dcu-wb-session.dcu-wb-selected,.dcu-wb-session.dcu-wb-menu-open{background:var(--dcu-sidebar-hover)}.dcu-wb-project-head+.dcu-wb-session,.dcu-wb-project-head+.dcu-wb-nochat{margin-top:0}.dcu-wb-session+.dcu-wb-session{margin-top:2px}
|
|
1195
1225
|
.dcu-wb-project-head[draggable=true],.dcu-wb-session[draggable=true]{cursor:grab}
|
|
1196
1226
|
.dcu-wb-project-head[draggable=true]:active,.dcu-wb-session[draggable=true]:active{cursor:grabbing}
|
|
1197
|
-
.dcu-wb-section,.dcu-wb-section:focus,.dcu-wb-section:focus-visible,.dcu-wb-project-head:focus,.dcu-wb-session:focus{outline:0}.dcu-wb-pin-end,.dcu-wb-pin-start{position:relative;height:8px}.dcu-wb-project-head.dcu-wb-drop::before,.dcu-wb-session.dcu-wb-drop::before,.dcu-wb-pin-end.dcu-wb-drop::before,.dcu-wb-pin-start.dcu-wb-drop::before{content:"";position:absolute;left:8px;right:8px;top:-4px;height:8px;pointer-events:none;background:radial-gradient(circle at 4px 50%,var(--dsw-alias-state-business-primary) 3.25px,transparent 3.45px),linear-gradient(var(--dsw-alias-state-business-primary),var(--dsw-alias-state-business-primary)) 10px 50%/calc(100% - 10px) 2px no-repeat}.dcu-wb-pin-start.dcu-wb-drop::before{top:0}.dcu-wb-project-head.dcu-wb-drop-after::before,.dcu-wb-session.dcu-wb-drop-after::before{top:auto;bottom
|
|
1227
|
+
.dcu-wb-section,.dcu-wb-section:focus,.dcu-wb-section:focus-visible,.dcu-wb-project-head:focus,.dcu-wb-session:focus{outline:0}.dcu-wb-pin-end,.dcu-wb-pin-start{position:relative;height:8px}.dcu-wb-project-head.dcu-wb-drop::before,.dcu-wb-session.dcu-wb-drop::before,.dcu-wb-pin-end.dcu-wb-drop::before,.dcu-wb-pin-start.dcu-wb-drop::before{content:"";position:absolute;left:8px;right:8px;top:-4px;height:8px;pointer-events:none;background:radial-gradient(circle at 4px 50%,var(--dsw-alias-state-business-primary) 3.25px,transparent 3.45px),linear-gradient(var(--dsw-alias-state-business-primary),var(--dsw-alias-state-business-primary)) 10px 50%/calc(100% - 10px) 2px no-repeat}.dcu-wb-pin-start.dcu-wb-drop::before,.dcu-wb-project-head.dcu-wb-drop::before,.dcu-wb-session.dcu-wb-drop::before{top:0}.dcu-wb-project-head.dcu-wb-drop-after::before,.dcu-wb-session.dcu-wb-drop-after::before{top:auto;bottom:0}.dcu-wb-dragging{opacity:.28}.dcu-wb-drag-ghost{position:fixed;top:-120px;left:-240px;z-index:10040;max-width:220px;height:32px;padding:0 10px;border:1px solid var(--dcu-sidebar-border);border-radius:8px;background:var(--dcu-sidebar-hover);color:var(--dcu-sidebar-primary);font:13px/32px var(--dcu-font,inherit);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;pointer-events:none;opacity:0}
|
|
1198
1228
|
.dcu-wb-folder{display:grid;place-items:center;flex:none;width:16px;height:20px;color:var(--dcu-sidebar-icon)}.dcu-wb-brand{display:block;width:16px;height:16px}
|
|
1199
1229
|
.dcu-wb-project-current .dcu-wb-folder{color:var(--dsw-alias-state-business-primary)}
|
|
1200
1230
|
.dcu-wb-project-title,.dcu-wb-session-title{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:14px;line-height:20px}
|
|
@@ -1222,7 +1252,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
1222
1252
|
.dcu-wb-rename-input:focus{border-color:var(--dsw-alias-button-info-fill)}
|
|
1223
1253
|
`;
|
|
1224
1254
|
const runningStyles = `.dcu-wb-running{flex:none;width:12px;height:12px;border:2px solid color-mix(in srgb,var(--dcu-sidebar-secondary) 25%,transparent);border-top-color:var(--dcu-sidebar-secondary);border-radius:50%;animation:dcu-wb-spin .8s linear infinite}@keyframes dcu-wb-spin{to{transform:rotate(360deg)}}@media (prefers-reduced-motion:reduce){.dcu-wb-running{animation:none}}`;
|
|
1225
|
-
const typographyStyles = `.dcu-wb{font:14px/20px var(--dcu-font,var(--dsw-font-family))}.dcu-wb-section-label{color:var(--dcu-sidebar-tertiary);font-size:13px;font-weight:400;letter-spacing:.02em}.dcu-wb-project-title{font-size:14px;line-height:20px;font-weight:400;color:var(--dcu-sidebar-primary)}.dcu-wb-session-title{font-size:14px;line-height:20px;font-weight:400;color:var(--dcu-sidebar-secondary)}.dcu-wb-session.dcu-wb-selected .dcu-wb-session-title,.dcu-wb-session:hover .dcu-wb-session-title{color:var(--dcu-sidebar-primary)}.dcu-wb-empty{color:var(--dcu-sidebar-tertiary);font-size:13px;line-height:18px}.dcu-wb-nochat{padding:
|
|
1255
|
+
const typographyStyles = `.dcu-wb{font:14px/20px var(--dcu-font,var(--dsw-font-family))}.dcu-wb-section-label{color:var(--dcu-sidebar-tertiary);font-size:13px;font-weight:400;letter-spacing:.02em}.dcu-wb-project-title{font-size:14px;line-height:20px;font-weight:400;color:var(--dcu-sidebar-primary)}.dcu-wb-session-title{font-size:14px;line-height:20px;font-weight:400;color:var(--dcu-sidebar-secondary)}.dcu-wb-session.dcu-wb-selected .dcu-wb-session-title,.dcu-wb-session:hover .dcu-wb-session-title{color:var(--dcu-sidebar-primary)}.dcu-wb-empty{color:var(--dcu-sidebar-tertiary);font-size:13px;line-height:18px}.dcu-wb-nochat{padding:0 8px 4px 28px;color:var(--dcu-sidebar-tertiary);font-size:14px;line-height:20px}`;
|
|
1226
1256
|
const WORKSPACE_TREE_STYLE = stylesheet$4 + runningStyles + typographyStyles;
|
|
1227
1257
|
function storage$1() {
|
|
1228
1258
|
return typeof window === "undefined" ? void 0 : window.localStorage;
|
|
@@ -1245,7 +1275,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
1245
1275
|
const [expanded, setExpanded] = (0, react.useState)({});
|
|
1246
1276
|
const [pinnedWorkspaceIds, setPinnedWorkspaceIds] = (0, react.useState)(() => readPinnedWorkspaceIds(storage$1()));
|
|
1247
1277
|
const [pinnedSessionIds, setPinnedSessionIds] = (0, react.useState)(() => readSessionIds(storage$1(), SESSION_PINS_STORAGE_KEY));
|
|
1248
|
-
const [sectionSessionIds, setSectionSessionIds] = (0, react.useState)(() => readSessionIds(storage$1(), SESSION_SECTION_PINS_STORAGE_KEY));
|
|
1249
1278
|
const [unreadSessionIds, setUnreadSessionIds] = (0, react.useState)(() => readSessionIds(storage$1(), SESSION_UNREAD_STORAGE_KEY));
|
|
1250
1279
|
const [menu, setMenu] = (0, react.useState)();
|
|
1251
1280
|
const [renameTarget, setRenameTarget] = (0, react.useState)();
|
|
@@ -1257,6 +1286,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
1257
1286
|
};
|
|
1258
1287
|
const [renameDraft, setRenameDraft] = (0, react.useState)("");
|
|
1259
1288
|
const [busy, setBusy] = (0, react.useState)();
|
|
1289
|
+
const busyRef = (0, react.useRef)();
|
|
1260
1290
|
const [error, setError] = (0, react.useState)();
|
|
1261
1291
|
const [workspaceDragId, setWorkspaceDragId] = (0, react.useState)();
|
|
1262
1292
|
const [workspaceDropId, setWorkspaceDropId] = (0, react.useState)();
|
|
@@ -1274,13 +1304,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
1274
1304
|
(0, react.useEffect)(() => {
|
|
1275
1305
|
writeSessionIds(storage$1(), SESSION_PINS_STORAGE_KEY, pinnedSessionIds);
|
|
1276
1306
|
}, [pinnedSessionIds]);
|
|
1277
|
-
(0, react.useEffect)(() => {
|
|
1278
|
-
writeSessionIds(storage$1(), SESSION_SECTION_PINS_STORAGE_KEY, sectionSessionIds);
|
|
1279
|
-
}, [sectionSessionIds]);
|
|
1280
|
-
(0, react.useEffect)(() => {
|
|
1281
|
-
if (sectionSessionIds.length === 0) return;
|
|
1282
|
-
setPinnedWorkspaceIds((ids) => ids.length === 0 ? ids : []);
|
|
1283
|
-
}, [sectionSessionIds]);
|
|
1284
1307
|
(0, react.useEffect)(() => {
|
|
1285
1308
|
writeSessionIds(storage$1(), SESSION_UNREAD_STORAGE_KEY, unreadSessionIds);
|
|
1286
1309
|
}, [unreadSessionIds]);
|
|
@@ -1367,15 +1390,15 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
1367
1390
|
sessions.current,
|
|
1368
1391
|
menu
|
|
1369
1392
|
]);
|
|
1370
|
-
const
|
|
1371
|
-
const
|
|
1372
|
-
const projectPinned = (id) => pinnedWorkspaceIds.includes(String(id)) && !hiddenPinnedWorkspaceIds.has(String(id));
|
|
1373
|
-
const pinnedGroups = orderByIds(groups.items, pinnedWorkspaceIds, (workspace) => String(workspace.workspaceId)).filter((workspace) => !hiddenPinnedWorkspaceIds.has(String(workspace.workspaceId)));
|
|
1393
|
+
const projectPinned = (id) => pinnedWorkspaceIds.includes(String(id));
|
|
1394
|
+
const pinnedGroups = orderByIds(groups.items, pinnedWorkspaceIds, (workspace) => String(workspace.workspaceId));
|
|
1374
1395
|
const regularGroups = groups.items.filter((workspace) => !projectPinned(workspace.workspaceId));
|
|
1375
|
-
const pinDragActive = workspaceDragId !== void 0
|
|
1396
|
+
const pinDragActive = workspaceDragId !== void 0;
|
|
1376
1397
|
const assignedIds = workspaces.items.flatMap((workspace) => workspace.sessionIds.map((id) => String(id)));
|
|
1377
|
-
const recentIds = ungroupedSessionIds(sessions.ids ?? Object.keys(sessions.byId), sessions.byId, assignedIds, workspaces.archivedSessionIds).
|
|
1398
|
+
const recentIds = ungroupedSessionIds(sessions.ids ?? Object.keys(sessions.byId), sessions.byId, assignedIds, workspaces.archivedSessionIds).sort((left, right) => (sessions.byId[right]?.updatedAt ?? 0) - (sessions.byId[left]?.updatedAt ?? 0));
|
|
1378
1399
|
const run = async (key, action) => {
|
|
1400
|
+
if (busyRef.current !== void 0) return;
|
|
1401
|
+
busyRef.current = key;
|
|
1379
1402
|
setBusy(key);
|
|
1380
1403
|
setError(void 0);
|
|
1381
1404
|
try {
|
|
@@ -1384,6 +1407,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
1384
1407
|
} catch (reason) {
|
|
1385
1408
|
setError(reason instanceof Error ? reason.message : String(reason));
|
|
1386
1409
|
} finally {
|
|
1410
|
+
busyRef.current = void 0;
|
|
1387
1411
|
setBusy(void 0);
|
|
1388
1412
|
}
|
|
1389
1413
|
};
|
|
@@ -1412,7 +1436,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
1412
1436
|
if (target.kind === "session") {
|
|
1413
1437
|
await deleteSession(target.id);
|
|
1414
1438
|
setPinnedSessionIds((ids) => ids.filter((id) => id !== target.id));
|
|
1415
|
-
setSectionSessionIds((ids) => ids.filter((id) => id !== target.id));
|
|
1416
1439
|
setUnreadSessionIds((ids) => ids.filter((id) => id !== target.id));
|
|
1417
1440
|
} else {
|
|
1418
1441
|
await deleteWorkspace(target.id);
|
|
@@ -1481,14 +1504,10 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
1481
1504
|
const pinWorkspaceAt = (id, beforeId) => {
|
|
1482
1505
|
setPinnedWorkspaceIds((ids) => insertPinnedWorkspace(ids, id, beforeId));
|
|
1483
1506
|
};
|
|
1484
|
-
const pinSessionAt = (id, beforeId) => {
|
|
1485
|
-
setSectionSessionIds((ids) => insertSessionId(ids, id, beforeId));
|
|
1486
|
-
setPinnedWorkspaceIds((ids) => ids.length === 0 ? ids : []);
|
|
1487
|
-
};
|
|
1488
1507
|
const renderGroup = (workspace, zone) => {
|
|
1489
1508
|
const expandKey = zone === "pinned" ? `pin:${workspace.workspaceId}` : String(workspace.workspaceId);
|
|
1490
1509
|
const isExpanded = expanded[expandKey] ?? true;
|
|
1491
|
-
const shownIds = workspace.visibleIds
|
|
1510
|
+
const shownIds = workspace.visibleIds;
|
|
1492
1511
|
const menuOpen = menu?.type === "workspace" && menu.id === workspace.workspaceId;
|
|
1493
1512
|
const menuAt = menuOpen && menu.x !== void 0 && menu.y !== void 0 ? {
|
|
1494
1513
|
x: menu.x,
|
|
@@ -1498,7 +1517,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
1498
1517
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1499
1518
|
className: `dcu-wb-project${isCurrentWorkspace ? " dcu-wb-project-current" : ""}`,
|
|
1500
1519
|
onDragOver: (event) => {
|
|
1501
|
-
if (workspaceDragId === void 0
|
|
1520
|
+
if (workspaceDragId === void 0) return;
|
|
1502
1521
|
event.preventDefault();
|
|
1503
1522
|
event.stopPropagation();
|
|
1504
1523
|
event.dataTransfer.dropEffect = "move";
|
|
@@ -1519,17 +1538,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
1519
1538
|
onDrop: (event) => {
|
|
1520
1539
|
event.preventDefault();
|
|
1521
1540
|
event.stopPropagation();
|
|
1522
|
-
const droppedSession = readSessionDrag(event.dataTransfer, sessionDrag?.sessionId);
|
|
1523
|
-
if (zone === "pinned" && droppedSession !== void 0) {
|
|
1524
|
-
setSessionDrag(void 0);
|
|
1525
|
-
setSessionDropId(void 0);
|
|
1526
|
-
setWorkspaceDragId(void 0);
|
|
1527
|
-
setWorkspaceDropId(void 0);
|
|
1528
|
-
setWorkspaceDropAfter(false);
|
|
1529
|
-
setPinSlot(void 0);
|
|
1530
|
-
pinSessionAt(droppedSession);
|
|
1531
|
-
return;
|
|
1532
|
-
}
|
|
1533
1541
|
const dragged = readWorkspaceDrag(event.dataTransfer, workspaceDragId);
|
|
1534
1542
|
const after = event.clientY > event.currentTarget.getBoundingClientRect().top + event.currentTarget.getBoundingClientRect().height / 2;
|
|
1535
1543
|
setWorkspaceDragId(void 0);
|
|
@@ -1918,27 +1926,20 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
1918
1926
|
className: "dcu-wb-section",
|
|
1919
1927
|
"aria-label": t("workspace.pinned"),
|
|
1920
1928
|
onDragOver: (event) => {
|
|
1921
|
-
if (!pinDragActive
|
|
1929
|
+
if (!pinDragActive) return;
|
|
1922
1930
|
event.preventDefault();
|
|
1923
1931
|
event.dataTransfer.dropEffect = "move";
|
|
1924
1932
|
if (event.target === event.currentTarget || event.target instanceof Element && event.target.closest(".dcu-wb-section-head") !== null) setPinSlot("header");
|
|
1925
1933
|
},
|
|
1926
1934
|
onDrop: (event) => {
|
|
1927
1935
|
event.preventDefault();
|
|
1928
|
-
const draggedSession = readSessionDrag(event.dataTransfer, sessionDrag?.sessionId);
|
|
1929
1936
|
const draggedWorkspace = readWorkspaceDrag(event.dataTransfer, workspaceDragId);
|
|
1930
1937
|
const slot = pinSlot;
|
|
1931
1938
|
setWorkspaceDragId(void 0);
|
|
1932
1939
|
setWorkspaceDropId(void 0);
|
|
1933
1940
|
setWorkspaceDropAfter(false);
|
|
1934
|
-
setSessionDrag(void 0);
|
|
1935
|
-
setSessionDropId(void 0);
|
|
1936
1941
|
setPinSlot(void 0);
|
|
1937
|
-
if (
|
|
1938
|
-
pinSessionAt(draggedSession, slot === "header" ? pinSectionSessions[0] : slot === "end" || slot === void 0 || !pinSectionSessions.includes(slot) ? void 0 : slot);
|
|
1939
|
-
return;
|
|
1940
|
-
}
|
|
1941
|
-
if (draggedWorkspace !== void 0) pinWorkspaceAt(draggedWorkspace, slot === "header" ? pinnedGroups[0] === void 0 ? void 0 : String(pinnedGroups[0].workspaceId) : slot === "end" || slot === void 0 || pinSectionSessions.includes(slot) ? void 0 : slot);
|
|
1942
|
+
if (draggedWorkspace !== void 0) pinWorkspaceAt(draggedWorkspace, slot === "header" ? pinnedGroups[0] === void 0 ? void 0 : String(pinnedGroups[0].workspaceId) : slot === "end" || slot === void 0 ? void 0 : slot);
|
|
1942
1943
|
},
|
|
1943
1944
|
onDragLeave: (event) => {
|
|
1944
1945
|
if (event.currentTarget.contains(event.relatedTarget)) return;
|
|
@@ -1978,7 +1979,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
1978
1979
|
(pinDragActive || pinSlot === "header") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1979
1980
|
className: `dcu-wb-pin-start${pinSlot === "header" ? " dcu-wb-drop" : ""}`,
|
|
1980
1981
|
onDragOver: (event) => {
|
|
1981
|
-
if (!pinDragActive
|
|
1982
|
+
if (!pinDragActive) return;
|
|
1982
1983
|
event.preventDefault();
|
|
1983
1984
|
event.stopPropagation();
|
|
1984
1985
|
event.dataTransfer.dropEffect = "move";
|
|
@@ -1987,149 +1988,15 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
1987
1988
|
setWorkspaceDropAfter(false);
|
|
1988
1989
|
}
|
|
1989
1990
|
}),
|
|
1990
|
-
|
|
1991
|
+
pinnedGroups.length === 0 && pinSlot !== "header" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1991
1992
|
className: "dcu-wb-empty",
|
|
1992
1993
|
children: t("workspace.pinnedEmpty")
|
|
1993
1994
|
}),
|
|
1994
|
-
pinSectionSessions.map((id) => {
|
|
1995
|
-
const session = sessions.byId[id];
|
|
1996
|
-
if (session === void 0) return null;
|
|
1997
|
-
const title = session.displayTitle;
|
|
1998
|
-
const workspace = groups.items.find((item) => item.visibleIds.includes(id));
|
|
1999
|
-
const path = session.cwd ?? workspace?.path;
|
|
2000
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionRow, {
|
|
2001
|
-
id,
|
|
2002
|
-
title,
|
|
2003
|
-
flat: true,
|
|
2004
|
-
selected: sessions.current === id,
|
|
2005
|
-
menuOpen: menu?.type === "session" && menu.id === id,
|
|
2006
|
-
pinned: true,
|
|
2007
|
-
unread: unreadSessionIds.includes(id),
|
|
2008
|
-
running: session.running === true,
|
|
2009
|
-
t,
|
|
2010
|
-
menuItems: sessionMenu(id, title, path),
|
|
2011
|
-
menuPoint: menu?.type === "session" && menu.id === id && menu.x !== void 0 && menu.y !== void 0 ? {
|
|
2012
|
-
x: menu.x,
|
|
2013
|
-
y: menu.y
|
|
2014
|
-
} : void 0,
|
|
2015
|
-
draggable: true,
|
|
2016
|
-
dropActive: sessionDropId === id,
|
|
2017
|
-
onDragStart: (event) => {
|
|
2018
|
-
event.stopPropagation();
|
|
2019
|
-
setWorkspaceDragId(void 0);
|
|
2020
|
-
writeSessionDrag(event.dataTransfer, id, title);
|
|
2021
|
-
const preview = document.createElement("div");
|
|
2022
|
-
preview.className = "dcu-wb-drag-ghost";
|
|
2023
|
-
preview.textContent = title;
|
|
2024
|
-
document.body.appendChild(preview);
|
|
2025
|
-
event.dataTransfer.setDragImage(preview, 16, 18);
|
|
2026
|
-
window.requestAnimationFrame(() => {
|
|
2027
|
-
preview.remove();
|
|
2028
|
-
});
|
|
2029
|
-
setSessionDrag({
|
|
2030
|
-
sessionId: id,
|
|
2031
|
-
workspaceId: workspace?.workspaceId ?? ""
|
|
2032
|
-
});
|
|
2033
|
-
},
|
|
2034
|
-
onDragEnd: () => {
|
|
2035
|
-
setSessionDrag(void 0);
|
|
2036
|
-
setSessionDropId(void 0);
|
|
2037
|
-
setPinSlot(void 0);
|
|
2038
|
-
},
|
|
2039
|
-
onDragOver: (event) => {
|
|
2040
|
-
if (sessionDrag === void 0) return;
|
|
2041
|
-
event.preventDefault();
|
|
2042
|
-
event.stopPropagation();
|
|
2043
|
-
setSessionDropId(id);
|
|
2044
|
-
setPinSlot(id);
|
|
2045
|
-
},
|
|
2046
|
-
onDrop: (event) => {
|
|
2047
|
-
event.preventDefault();
|
|
2048
|
-
event.stopPropagation();
|
|
2049
|
-
const draggedSession = readSessionDrag(event.dataTransfer, sessionDrag?.sessionId);
|
|
2050
|
-
const after = event.clientY > event.currentTarget.getBoundingClientRect().top + event.currentTarget.getBoundingClientRect().height / 2;
|
|
2051
|
-
setSessionDrag(void 0);
|
|
2052
|
-
setSessionDropId(void 0);
|
|
2053
|
-
setPinSlot(void 0);
|
|
2054
|
-
if (draggedSession === void 0) return;
|
|
2055
|
-
pinSessionAt(draggedSession, dropBeforeId(pinSectionSessions, id, after));
|
|
2056
|
-
},
|
|
2057
|
-
onOpen: () => {
|
|
2058
|
-
setUnreadSessionIds((ids) => ids.filter((item) => item !== id));
|
|
2059
|
-
openSession(id);
|
|
2060
|
-
},
|
|
2061
|
-
onMenuChange: (open) => {
|
|
2062
|
-
setMenu(open ? {
|
|
2063
|
-
id,
|
|
2064
|
-
type: "session"
|
|
2065
|
-
} : void 0);
|
|
2066
|
-
},
|
|
2067
|
-
onPin: () => {
|
|
2068
|
-
setSectionSessionIds((ids) => ids.filter((item) => item !== id));
|
|
2069
|
-
},
|
|
2070
|
-
onArchive: () => {
|
|
2071
|
-
run("archive", () => archiveSession(id));
|
|
2072
|
-
},
|
|
2073
|
-
onHover: (event) => {
|
|
2074
|
-
const box = hoverCardAnchor(event.currentTarget.getBoundingClientRect());
|
|
2075
|
-
showTip({
|
|
2076
|
-
kind: "session",
|
|
2077
|
-
id,
|
|
2078
|
-
title,
|
|
2079
|
-
project: workspace?.title,
|
|
2080
|
-
path,
|
|
2081
|
-
time: formatHoverTime(session.updatedAt),
|
|
2082
|
-
left: box.left,
|
|
2083
|
-
top: box.top
|
|
2084
|
-
});
|
|
2085
|
-
},
|
|
2086
|
-
onLeave: hideTip,
|
|
2087
|
-
onContextMenu: (event) => {
|
|
2088
|
-
event.preventDefault();
|
|
2089
|
-
event.stopPropagation();
|
|
2090
|
-
dismissTip();
|
|
2091
|
-
setMenu({
|
|
2092
|
-
id,
|
|
2093
|
-
type: "session",
|
|
2094
|
-
x: event.clientX,
|
|
2095
|
-
y: event.clientY
|
|
2096
|
-
});
|
|
2097
|
-
},
|
|
2098
|
-
onSelectAction: (action) => {
|
|
2099
|
-
if (busy !== void 0) return;
|
|
2100
|
-
if (action === "rename") beginRename("session", id, title);
|
|
2101
|
-
if (action === "pin") {
|
|
2102
|
-
setSectionSessionIds((ids) => ids.filter((item) => item !== id));
|
|
2103
|
-
setMenu(void 0);
|
|
2104
|
-
}
|
|
2105
|
-
if (action === "unread") {
|
|
2106
|
-
setUnreadSessionIds((ids) => toggleSessionId(ids, id));
|
|
2107
|
-
setMenu(void 0);
|
|
2108
|
-
}
|
|
2109
|
-
if (action === "archive") run("archive", () => archiveSession(id));
|
|
2110
|
-
if (action === "delete") {
|
|
2111
|
-
setDeleteTarget({
|
|
2112
|
-
id,
|
|
2113
|
-
kind: "session",
|
|
2114
|
-
title
|
|
2115
|
-
});
|
|
2116
|
-
setError(void 0);
|
|
2117
|
-
setMenu(void 0);
|
|
2118
|
-
}
|
|
2119
|
-
if (action === "fork") run("fork", () => forkSession(id));
|
|
2120
|
-
if (action === "openPath" && path !== void 0) run("open-path", () => openPath(path));
|
|
2121
|
-
if (action === "copyPath") copy(path);
|
|
2122
|
-
if (action === "copyTitle") copy(title);
|
|
2123
|
-
if (action === "copyId") copy(id);
|
|
2124
|
-
if (action === "copyLink") copy(sessionDeepLink(browserBase(), id));
|
|
2125
|
-
}
|
|
2126
|
-
}, id);
|
|
2127
|
-
}),
|
|
2128
1995
|
pinnedGroups.map((workspace) => renderGroup(workspace, "pinned")),
|
|
2129
1996
|
pinDragActive && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2130
1997
|
className: `dcu-wb-pin-end${pinSlot === "end" ? " dcu-wb-drop" : ""}`,
|
|
2131
1998
|
onDragOver: (event) => {
|
|
2132
|
-
if (!pinDragActive
|
|
1999
|
+
if (!pinDragActive) return;
|
|
2133
2000
|
event.preventDefault();
|
|
2134
2001
|
event.stopPropagation();
|
|
2135
2002
|
event.dataTransfer.dropEffect = "move";
|
|
@@ -2524,7 +2391,10 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
2524
2391
|
function useBusyAction(onSuccess) {
|
|
2525
2392
|
const [busy, setBusy] = (0, react.useState)();
|
|
2526
2393
|
const [error, setError] = (0, react.useState)();
|
|
2394
|
+
const busyRef = (0, react.useRef)();
|
|
2527
2395
|
const run = async (key, action) => {
|
|
2396
|
+
if (busyRef.current !== void 0) return;
|
|
2397
|
+
busyRef.current = key;
|
|
2528
2398
|
setBusy(key);
|
|
2529
2399
|
setError(void 0);
|
|
2530
2400
|
try {
|
|
@@ -2533,6 +2403,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
2533
2403
|
} catch (reason) {
|
|
2534
2404
|
setError(reason instanceof Error ? reason.message : String(reason));
|
|
2535
2405
|
} finally {
|
|
2406
|
+
busyRef.current = void 0;
|
|
2536
2407
|
setBusy(void 0);
|
|
2537
2408
|
}
|
|
2538
2409
|
};
|
|
@@ -2751,11 +2622,15 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
2751
2622
|
});
|
|
2752
2623
|
(0, react.useEffect)(() => {
|
|
2753
2624
|
let disposed = false;
|
|
2754
|
-
let
|
|
2625
|
+
let active;
|
|
2755
2626
|
const load = () => {
|
|
2756
|
-
if (
|
|
2757
|
-
|
|
2758
|
-
|
|
2627
|
+
if (active !== void 0 || document.visibilityState === "hidden") return;
|
|
2628
|
+
const controller = new AbortController();
|
|
2629
|
+
active = controller;
|
|
2630
|
+
const timeout = window.setTimeout(() => {
|
|
2631
|
+
controller.abort();
|
|
2632
|
+
}, 8e3);
|
|
2633
|
+
loadChannelGroups(controller.signal).then((next) => {
|
|
2759
2634
|
if (!disposed) {
|
|
2760
2635
|
setGroups(next);
|
|
2761
2636
|
setPollError(void 0);
|
|
@@ -2763,16 +2638,23 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
2763
2638
|
}).catch(() => {
|
|
2764
2639
|
if (!disposed) setPollError(t("channels.loadError"));
|
|
2765
2640
|
}).finally(() => {
|
|
2766
|
-
|
|
2641
|
+
window.clearTimeout(timeout);
|
|
2642
|
+
if (active === controller) active = void 0;
|
|
2767
2643
|
});
|
|
2768
2644
|
};
|
|
2769
2645
|
load();
|
|
2770
2646
|
const timer = window.setInterval(load, 4e3);
|
|
2647
|
+
const onVisibilityChange = () => {
|
|
2648
|
+
if (document.visibilityState === "visible") load();
|
|
2649
|
+
};
|
|
2650
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
2771
2651
|
return () => {
|
|
2772
2652
|
disposed = true;
|
|
2653
|
+
active?.abort();
|
|
2773
2654
|
window.clearInterval(timer);
|
|
2655
|
+
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
2774
2656
|
};
|
|
2775
|
-
}, []);
|
|
2657
|
+
}, [t]);
|
|
2776
2658
|
const banner = pollError ?? error;
|
|
2777
2659
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2778
2660
|
className: "dcu-wb",
|
|
@@ -3069,6 +2951,8 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
3069
2951
|
}
|
|
3070
2952
|
//#endregion
|
|
3071
2953
|
//#region src/client/CodexSidebar.tsx
|
|
2954
|
+
const subscribeEmptyCompanionTabs = () => () => {};
|
|
2955
|
+
const getEmptyCompanionTabs = () => EMPTY_COMPANION_TABS;
|
|
3072
2956
|
const stylesheet$3 = `
|
|
3073
2957
|
.dcu-root{--dcu-font:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI","Microsoft YaHei UI",sans-serif;--dcu-sidebar-primary:#393d3e;--dcu-sidebar-secondary:#676b6c;--dcu-sidebar-tertiary:#9a9f9f;--dcu-sidebar-navigation:#4e5253;--dcu-sidebar-icon:#4e5253;--dcu-sidebar-hover:#dfe8e5;--dcu-sidebar-border:rgba(37,46,41,.10);--dcu-tip-bg:#ffffff;--dcu-tip-shadow:0 10px 32px rgba(31,39,36,.22);height:100%;min-width:0;box-sizing:border-box;display:flex;flex-direction:column;background:#eef7f5;color:var(--dcu-sidebar-primary);font:14px/20px var(--dcu-font)}body[data-ds-dark-theme] .dcu-root{background:#1d2120;--dcu-sidebar-primary:#b9bab9;--dcu-sidebar-secondary:#909191;--dcu-sidebar-tertiary:#666867;--dcu-sidebar-navigation:#b9bab9;--dcu-sidebar-icon:#afafaf;--dcu-sidebar-hover:#303432;--dcu-sidebar-border:rgba(255,255,255,.08);--dcu-tip-bg:#2a2e2c;--dcu-tip-shadow:0 10px 30px rgba(0,0,0,.28)}
|
|
3074
2958
|
.dcu-root *{box-sizing:border-box}.dcu-head{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;column-gap:8px;height:60px;padding:8px 8px 8px 12px}.dcu-brand{border:0;background:transparent;color:inherit;padding:0;display:flex;align-items:center;min-width:0;overflow:hidden}.dcu-brand svg{display:block;width:auto;max-width:100%;height:24px;min-width:0}.dcu-head-actions{display:grid;grid-auto-flow:column;grid-auto-columns:28px;align-items:center;column-gap:8px;height:28px}
|
|
@@ -3126,7 +3010,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
3126
3010
|
const [searchQuery, setSearchQuery] = (0, react.useState)("");
|
|
3127
3011
|
const [activeSearchIndex, setActiveSearchIndex] = (0, react.useState)(0);
|
|
3128
3012
|
const [imTab, setImTab] = (0, react.useState)("tasks");
|
|
3129
|
-
const companionTabs = (0, react.useSyncExternalStore)(companionSlots?.subscribe ??
|
|
3013
|
+
const companionTabs = (0, react.useSyncExternalStore)(companionSlots?.subscribe ?? subscribeEmptyCompanionTabs, companionSlots?.getSnapshot ?? getEmptyCompanionTabs, companionSlots?.getSnapshot ?? getEmptyCompanionTabs);
|
|
3130
3014
|
const showChannels = companionTabs.channels;
|
|
3131
3015
|
const showSchedule = companionTabs.schedule;
|
|
3132
3016
|
const showCompanionTabs = showChannels || showSchedule;
|
|
@@ -3320,14 +3204,17 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
3320
3204
|
(0, react.useEffect)(() => {
|
|
3321
3205
|
let startX = 0;
|
|
3322
3206
|
let dragging = false;
|
|
3207
|
+
let pointerId;
|
|
3323
3208
|
const onDown = (event) => {
|
|
3324
|
-
if (!isSidebarDragHandle(event.target)) return;
|
|
3209
|
+
if (dragging || !isSidebarDragHandle(event.target)) return;
|
|
3325
3210
|
dragging = true;
|
|
3211
|
+
pointerId = event.pointerId;
|
|
3326
3212
|
startX = event.clientX;
|
|
3327
3213
|
};
|
|
3328
3214
|
const onUp = (event) => {
|
|
3329
|
-
if (!dragging) return;
|
|
3215
|
+
if (!dragging || event.pointerId !== pointerId) return;
|
|
3330
3216
|
dragging = false;
|
|
3217
|
+
pointerId = void 0;
|
|
3331
3218
|
if (!shouldCollapseOnSidebarDrag(startX, event.clientX)) return;
|
|
3332
3219
|
window.requestAnimationFrame(() => {
|
|
3333
3220
|
window.requestAnimationFrame(() => {
|
|
@@ -3335,11 +3222,18 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
3335
3222
|
});
|
|
3336
3223
|
});
|
|
3337
3224
|
};
|
|
3225
|
+
const onCancel = (event) => {
|
|
3226
|
+
if (event.pointerId !== pointerId) return;
|
|
3227
|
+
dragging = false;
|
|
3228
|
+
pointerId = void 0;
|
|
3229
|
+
};
|
|
3338
3230
|
window.addEventListener("pointerdown", onDown, true);
|
|
3339
3231
|
window.addEventListener("pointerup", onUp, true);
|
|
3232
|
+
window.addEventListener("pointercancel", onCancel, true);
|
|
3340
3233
|
return () => {
|
|
3341
3234
|
window.removeEventListener("pointerdown", onDown, true);
|
|
3342
3235
|
window.removeEventListener("pointerup", onUp, true);
|
|
3236
|
+
window.removeEventListener("pointercancel", onCancel, true);
|
|
3343
3237
|
};
|
|
3344
3238
|
}, [toggleSidebar]);
|
|
3345
3239
|
if (collapsed || width < 80) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
|
|
@@ -3801,9 +3695,13 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
3801
3695
|
const root = (0, react.useRef)(null);
|
|
3802
3696
|
const stateRef = (0, react.useRef)("loading");
|
|
3803
3697
|
const requestId = (0, react.useRef)(0);
|
|
3698
|
+
const installingRef = (0, react.useRef)();
|
|
3804
3699
|
stateRef.current = state;
|
|
3805
|
-
(0, react.useEffect)(() =>
|
|
3806
|
-
alive.current =
|
|
3700
|
+
(0, react.useEffect)(() => {
|
|
3701
|
+
alive.current = true;
|
|
3702
|
+
return () => {
|
|
3703
|
+
alive.current = false;
|
|
3704
|
+
};
|
|
3807
3705
|
}, []);
|
|
3808
3706
|
const load = (0, react.useCallback)(async (signal) => {
|
|
3809
3707
|
const currentRequest = ++requestId.current;
|
|
@@ -3833,8 +3731,17 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
3833
3731
|
if (node === null || typeof IntersectionObserver === "undefined") return () => {
|
|
3834
3732
|
controller.abort();
|
|
3835
3733
|
};
|
|
3734
|
+
let initialized = false;
|
|
3735
|
+
let wasVisible = false;
|
|
3836
3736
|
const observer = new IntersectionObserver((entries) => {
|
|
3837
|
-
|
|
3737
|
+
const visible = entries.some((entry) => entry.isIntersecting);
|
|
3738
|
+
if (!initialized) {
|
|
3739
|
+
initialized = true;
|
|
3740
|
+
wasVisible = visible;
|
|
3741
|
+
return;
|
|
3742
|
+
}
|
|
3743
|
+
if (visible && !wasVisible) refresh();
|
|
3744
|
+
wasVisible = visible;
|
|
3838
3745
|
}, { threshold: .2 });
|
|
3839
3746
|
observer.observe(node);
|
|
3840
3747
|
return () => {
|
|
@@ -3843,6 +3750,8 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
3843
3750
|
};
|
|
3844
3751
|
}, [load]);
|
|
3845
3752
|
const install = async (id) => {
|
|
3753
|
+
if (installingRef.current !== void 0) return;
|
|
3754
|
+
installingRef.current = id;
|
|
3846
3755
|
setInstalling(id);
|
|
3847
3756
|
setMessage(void 0);
|
|
3848
3757
|
try {
|
|
@@ -3863,6 +3772,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
3863
3772
|
text: error instanceof Error ? error.message : t("about.installFailed")
|
|
3864
3773
|
});
|
|
3865
3774
|
} finally {
|
|
3775
|
+
installingRef.current = void 0;
|
|
3866
3776
|
if (alive.current) setInstalling(void 0);
|
|
3867
3777
|
}
|
|
3868
3778
|
};
|
|
@@ -3930,7 +3840,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
3930
3840
|
(!dependency.installed || dependency.updateAvailable) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3931
3841
|
className: "dcu-about-install",
|
|
3932
3842
|
type: "button",
|
|
3933
|
-
disabled: installing
|
|
3843
|
+
disabled: installing !== void 0,
|
|
3934
3844
|
onClick: () => {
|
|
3935
3845
|
install(dependency.id);
|
|
3936
3846
|
},
|
|
@@ -4066,7 +3976,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
4066
3976
|
"search.actions": "快捷操作",
|
|
4067
3977
|
"workspace.label": "工作区会话",
|
|
4068
3978
|
"workspace.pinned": "置顶",
|
|
4069
|
-
"workspace.pinnedEmpty": "
|
|
3979
|
+
"workspace.pinnedEmpty": "拖动项目到此处置顶",
|
|
4070
3980
|
"workspace.projects": "项目",
|
|
4071
3981
|
"workspace.recent": "最近",
|
|
4072
3982
|
"workspace.recentEmpty": "无聊天",
|
|
@@ -4194,7 +4104,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
4194
4104
|
"workspace.pin": "Pin project",
|
|
4195
4105
|
"workspace.unpin": "Unpin",
|
|
4196
4106
|
"workspace.pinned": "Pinned",
|
|
4197
|
-
"workspace.pinnedEmpty": "Drag a project
|
|
4107
|
+
"workspace.pinnedEmpty": "Drag a project here to pin it",
|
|
4198
4108
|
"workspace.openPath": "Open in file explorer",
|
|
4199
4109
|
"workspace.delete": "Delete project",
|
|
4200
4110
|
"workspace.deleteDescription": "This removes “{name}” from the project list. The folder and conversation records remain.",
|
|
@@ -4384,11 +4294,27 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
4384
4294
|
let applying = false;
|
|
4385
4295
|
let frame;
|
|
4386
4296
|
let pending;
|
|
4297
|
+
let frameObserver;
|
|
4298
|
+
const watchFrame = (next) => {
|
|
4299
|
+
if (frame === next) return;
|
|
4300
|
+
frameObserver?.disconnect();
|
|
4301
|
+
frame = next;
|
|
4302
|
+
if (frame === void 0) return;
|
|
4303
|
+
frameObserver = new MutationObserver(schedule);
|
|
4304
|
+
frameObserver.observe(frame, {
|
|
4305
|
+
attributes: true,
|
|
4306
|
+
attributeFilter: [
|
|
4307
|
+
"style",
|
|
4308
|
+
"data-sidebar-collapsed",
|
|
4309
|
+
"data-dragging"
|
|
4310
|
+
]
|
|
4311
|
+
});
|
|
4312
|
+
};
|
|
4387
4313
|
const apply = () => {
|
|
4388
4314
|
if (applying) return;
|
|
4389
4315
|
applying = true;
|
|
4390
4316
|
try {
|
|
4391
|
-
if (frame === void 0 || !frame.isConnected)
|
|
4317
|
+
if (frame === void 0 || !frame.isConnected) watchFrame(findSidebarFrame(document));
|
|
4392
4318
|
if (frame !== void 0) applySlimSidebar(frame);
|
|
4393
4319
|
} finally {
|
|
4394
4320
|
applying = false;
|
|
@@ -4402,19 +4328,16 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
4402
4328
|
});
|
|
4403
4329
|
};
|
|
4404
4330
|
apply();
|
|
4405
|
-
const observer = new MutationObserver(
|
|
4331
|
+
const observer = new MutationObserver(() => {
|
|
4332
|
+
if (frame === void 0 || !frame.isConnected) schedule();
|
|
4333
|
+
});
|
|
4406
4334
|
observer.observe(document.body, {
|
|
4407
|
-
attributes: true,
|
|
4408
|
-
attributeFilter: [
|
|
4409
|
-
"style",
|
|
4410
|
-
"data-sidebar-collapsed",
|
|
4411
|
-
"data-dragging"
|
|
4412
|
-
],
|
|
4413
4335
|
childList: true,
|
|
4414
4336
|
subtree: true
|
|
4415
4337
|
});
|
|
4416
4338
|
return () => {
|
|
4417
4339
|
observer.disconnect();
|
|
4340
|
+
frameObserver?.disconnect();
|
|
4418
4341
|
if (pending !== void 0) window.cancelAnimationFrame(pending);
|
|
4419
4342
|
};
|
|
4420
4343
|
}
|
|
@@ -4428,8 +4351,16 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
4428
4351
|
return document.querySelector("[data-conversation-scroll]");
|
|
4429
4352
|
}
|
|
4430
4353
|
function conversationAnchor(root, key) {
|
|
4431
|
-
|
|
4432
|
-
|
|
4354
|
+
return conversationAnchors(root).get(key) ?? null;
|
|
4355
|
+
}
|
|
4356
|
+
/** 一次扫描生成锚点索引,滚动帧内不得为每个轮次重复遍历 DOM。 */
|
|
4357
|
+
function conversationAnchors(root) {
|
|
4358
|
+
const anchors = /* @__PURE__ */ new Map();
|
|
4359
|
+
for (const anchor of root.querySelectorAll("[data-chat-anchor-key]")) {
|
|
4360
|
+
const key = anchor.dataset.chatAnchorKey;
|
|
4361
|
+
if (key !== void 0 && !anchors.has(key)) anchors.set(key, anchor);
|
|
4362
|
+
}
|
|
4363
|
+
return anchors;
|
|
4433
4364
|
}
|
|
4434
4365
|
//#endregion
|
|
4435
4366
|
//#region src/client/TurnNavigator.tsx
|
|
@@ -4531,27 +4462,44 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
4531
4462
|
};
|
|
4532
4463
|
}, []);
|
|
4533
4464
|
(0, react.useEffect)(() => {
|
|
4534
|
-
|
|
4535
|
-
|
|
4465
|
+
if (turns.length === 0) return;
|
|
4466
|
+
let host = null;
|
|
4536
4467
|
let frame = null;
|
|
4537
4468
|
const update = () => {
|
|
4538
4469
|
frame = null;
|
|
4470
|
+
if (host === null) return;
|
|
4539
4471
|
const threshold = host.getBoundingClientRect().top + Math.min(180, host.clientHeight * .35);
|
|
4472
|
+
const anchors = conversationAnchors(host);
|
|
4540
4473
|
let next = turns[0]?.key ?? null;
|
|
4541
4474
|
for (const turn of turns) {
|
|
4542
|
-
const anchor =
|
|
4543
|
-
if (anchor !==
|
|
4475
|
+
const anchor = anchors.get(turn.key);
|
|
4476
|
+
if (anchor !== void 0 && anchor.getBoundingClientRect().top <= threshold) next = turn.key;
|
|
4544
4477
|
}
|
|
4545
4478
|
setCurrent((previous) => previous === next ? previous : next);
|
|
4546
4479
|
};
|
|
4547
4480
|
const schedule = () => {
|
|
4548
4481
|
if (frame === null) frame = window.requestAnimationFrame(update);
|
|
4549
4482
|
};
|
|
4550
|
-
|
|
4483
|
+
const bindHost = () => {
|
|
4484
|
+
const next = conversationScrollRoot();
|
|
4485
|
+
if (next === host) return;
|
|
4486
|
+
host?.removeEventListener("scroll", schedule);
|
|
4487
|
+
host = next;
|
|
4488
|
+
host?.addEventListener("scroll", schedule, { passive: true });
|
|
4489
|
+
schedule();
|
|
4490
|
+
};
|
|
4491
|
+
const observer = new MutationObserver(() => {
|
|
4492
|
+
if (host === null || !host.isConnected) bindHost();
|
|
4493
|
+
});
|
|
4494
|
+
observer.observe(document.body, {
|
|
4495
|
+
childList: true,
|
|
4496
|
+
subtree: true
|
|
4497
|
+
});
|
|
4551
4498
|
window.addEventListener("resize", schedule);
|
|
4552
|
-
|
|
4499
|
+
bindHost();
|
|
4553
4500
|
return () => {
|
|
4554
|
-
|
|
4501
|
+
observer.disconnect();
|
|
4502
|
+
host?.removeEventListener("scroll", schedule);
|
|
4555
4503
|
window.removeEventListener("resize", schedule);
|
|
4556
4504
|
if (frame !== null) window.cancelAnimationFrame(frame);
|
|
4557
4505
|
};
|
|
@@ -4606,7 +4554,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
4606
4554
|
index: index + 1,
|
|
4607
4555
|
summary: turn.summary
|
|
4608
4556
|
}),
|
|
4609
|
-
title: turn.summary,
|
|
4610
4557
|
style: tickStyle,
|
|
4611
4558
|
onFocus: () => {
|
|
4612
4559
|
setHoverAt(index);
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { readFile, unlink, writeFile } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { resolve, sep } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -54,6 +54,7 @@ function managedDependency(id) {
|
|
|
54
54
|
//#region src/dependency-manager.ts
|
|
55
55
|
const PROFILE_PENDING_UPDATES_FILE = ".dsh-pending-updates.json";
|
|
56
56
|
const APPLY_PLUGIN_UPDATES_IPC = "apply-plugin-updates";
|
|
57
|
+
const PLUGIN_INSTALL_TIMEOUT_MS = 6e5;
|
|
57
58
|
function profileDirectory() {
|
|
58
59
|
if (process.env.DSH_PROFILE_DIR !== void 0) return process.env.DSH_PROFILE_DIR;
|
|
59
60
|
return resolve(homedir(), ".dsh", "profiles", "web");
|
|
@@ -173,7 +174,7 @@ function applyReleaseExclude(source, packageName, version) {
|
|
|
173
174
|
}
|
|
174
175
|
/** 将用户本次确认的精确版本加入 Profile 的 pnpm 发布时间保护例外。 */
|
|
175
176
|
async function ensureLatestReleaseAllowed(packageName, version) {
|
|
176
|
-
if (
|
|
177
|
+
if (parseSemver(version) === void 0) throw new Error("npm 返回了无法识别的最新版本。");
|
|
177
178
|
const path = resolve(profileDirectory(), "pnpm-workspace.yaml");
|
|
178
179
|
let source;
|
|
179
180
|
try {
|
|
@@ -185,26 +186,40 @@ async function ensureLatestReleaseAllowed(packageName, version) {
|
|
|
185
186
|
const next = applyReleaseExclude(source, packageName, version);
|
|
186
187
|
if (next !== source) await writeFile(path, next, "utf8");
|
|
187
188
|
}
|
|
188
|
-
function
|
|
189
|
-
const match = /^v?(\d+)\.(\d+)\.(\d+)
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
189
|
+
function parseSemver(version) {
|
|
190
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(version);
|
|
191
|
+
if (match === null) return void 0;
|
|
192
|
+
return {
|
|
193
|
+
core: [
|
|
194
|
+
Number(match[1]),
|
|
195
|
+
Number(match[2]),
|
|
196
|
+
Number(match[3])
|
|
197
|
+
],
|
|
198
|
+
prerelease: match[4] === void 0 ? [] : match[4].split(".")
|
|
199
|
+
};
|
|
195
200
|
}
|
|
196
|
-
function
|
|
197
|
-
|
|
198
|
-
|
|
201
|
+
function comparePrerelease(left, right) {
|
|
202
|
+
if (left.length === 0 || right.length === 0) return left.length === right.length ? 0 : left.length === 0 ? 1 : -1;
|
|
203
|
+
const length = Math.max(left.length, right.length);
|
|
204
|
+
for (let index = 0; index < length; index += 1) {
|
|
205
|
+
const a = left[index];
|
|
206
|
+
const b = right[index];
|
|
207
|
+
if (a === void 0 || b === void 0) return a === b ? 0 : a === void 0 ? -1 : 1;
|
|
208
|
+
if (a === b) continue;
|
|
209
|
+
const aNumeric = /^\d+$/.test(a);
|
|
210
|
+
const bNumeric = /^\d+$/.test(b);
|
|
211
|
+
if (aNumeric && bNumeric) return Number(a) > Number(b) ? 1 : -1;
|
|
212
|
+
if (aNumeric !== bNumeric) return aNumeric ? -1 : 1;
|
|
213
|
+
return a > b ? 1 : -1;
|
|
214
|
+
}
|
|
215
|
+
return 0;
|
|
199
216
|
}
|
|
200
217
|
function newerVersion(installed, latest) {
|
|
201
|
-
const current =
|
|
202
|
-
const candidate =
|
|
218
|
+
const current = parseSemver(installed);
|
|
219
|
+
const candidate = parseSemver(latest);
|
|
203
220
|
if (current === void 0 || candidate === void 0) return false;
|
|
204
|
-
if (candidate[
|
|
205
|
-
|
|
206
|
-
if (candidate[2] !== current[2]) return candidate[2] > current[2];
|
|
207
|
-
return prereleaseRank(latest) > prereleaseRank(installed);
|
|
221
|
+
for (let index = 0; index < current.core.length; index += 1) if (candidate.core[index] !== current.core[index]) return candidate.core[index] > current.core[index];
|
|
222
|
+
return comparePrerelease(candidate.prerelease, current.prerelease) > 0;
|
|
208
223
|
}
|
|
209
224
|
/** 返回 Web profile 中固定管理插件的实际安装版本与 npm latest 状态。 */
|
|
210
225
|
async function dependencyStatuses() {
|
|
@@ -272,9 +287,31 @@ async function recordPendingUpdate(packageName, version) {
|
|
|
272
287
|
});
|
|
273
288
|
await writeFile(pendingPath, `${JSON.stringify({ packages }, void 0, 2)}\n`, "utf8");
|
|
274
289
|
}
|
|
290
|
+
async function removePendingUpdate(packageName) {
|
|
291
|
+
const pendingPath = resolve(profileDirectory(), PROFILE_PENDING_UPDATES_FILE);
|
|
292
|
+
try {
|
|
293
|
+
const parsed = JSON.parse(await readFile(pendingPath, "utf8"));
|
|
294
|
+
if (!Array.isArray(parsed.packages)) return;
|
|
295
|
+
const packages = parsed.packages.filter((item) => item === null || typeof item !== "object" || item.packageName !== packageName);
|
|
296
|
+
if (packages.length === parsed.packages.length) return;
|
|
297
|
+
if (packages.length === 0) {
|
|
298
|
+
await unlink(pendingPath);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
await writeFile(pendingPath, `${JSON.stringify({ packages }, void 0, 2)}\n`, "utf8");
|
|
302
|
+
} catch (error) {
|
|
303
|
+
if (error.code !== "ENOENT") throw error;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
275
306
|
async function recordDeclaredVersion(packageName, version) {
|
|
276
307
|
const manifestPath = resolve(profileDirectory(), "package.json");
|
|
277
|
-
|
|
308
|
+
let manifest;
|
|
309
|
+
try {
|
|
310
|
+
manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
311
|
+
} catch (error) {
|
|
312
|
+
if (error.code !== "ENOENT") throw error;
|
|
313
|
+
manifest = {};
|
|
314
|
+
}
|
|
278
315
|
manifest.dependencies = {
|
|
279
316
|
...manifest.dependencies,
|
|
280
317
|
[packageName]: version
|
|
@@ -293,43 +330,87 @@ function pluginCommandError(stderr) {
|
|
|
293
330
|
* 复用启动当前服务的 DSH CLI:它会通过 pnpm 从 npm 安装或更新,并自动维护
|
|
294
331
|
* dsh.profile.bundles,避免浏览器端直接管理 profile 文件。
|
|
295
332
|
*/
|
|
296
|
-
function
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
"
|
|
305
|
-
|
|
333
|
+
function pluginExecArgv(args = process.execArgv) {
|
|
334
|
+
return args.filter((arg) => !/^--(?:inspect|inspect-brk|debug|debug-brk)(?:=|$)/.test(arg));
|
|
335
|
+
}
|
|
336
|
+
const activePluginChildren = /* @__PURE__ */ new Set();
|
|
337
|
+
function terminatePluginChild(child) {
|
|
338
|
+
if (child.exitCode !== null || child.killed) return;
|
|
339
|
+
if (process.platform === "win32" && child.pid !== void 0) {
|
|
340
|
+
const killer = spawn("taskkill", [
|
|
341
|
+
"/pid",
|
|
342
|
+
String(child.pid),
|
|
343
|
+
"/T",
|
|
344
|
+
"/F"
|
|
306
345
|
], {
|
|
307
|
-
cwd: process.cwd(),
|
|
308
|
-
env: {
|
|
309
|
-
...process.env,
|
|
310
|
-
CI: "true"
|
|
311
|
-
},
|
|
312
346
|
windowsHide: true,
|
|
313
|
-
stdio:
|
|
314
|
-
"ignore",
|
|
315
|
-
"pipe",
|
|
316
|
-
"pipe"
|
|
317
|
-
]
|
|
347
|
+
stdio: "ignore"
|
|
318
348
|
});
|
|
349
|
+
killer.once("error", () => {
|
|
350
|
+
child.kill("SIGTERM");
|
|
351
|
+
});
|
|
352
|
+
killer.unref();
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
child.kill("SIGTERM");
|
|
356
|
+
}
|
|
357
|
+
/** 插件停用时终止仍在运行的安装进程,避免热更新后遗留 pnpm。 */
|
|
358
|
+
function disposeDependencyInstaller() {
|
|
359
|
+
for (const child of activePluginChildren) terminatePluginChild(child);
|
|
360
|
+
}
|
|
361
|
+
function monitorPluginChild(child, timeoutMs = PLUGIN_INSTALL_TIMEOUT_MS) {
|
|
362
|
+
return new Promise((resolvePromise, reject) => {
|
|
363
|
+
activePluginChildren.add(child);
|
|
319
364
|
let output = "";
|
|
365
|
+
let settled = false;
|
|
320
366
|
const collect = (chunk) => {
|
|
321
|
-
output
|
|
367
|
+
output = `${output}${String(chunk)}`.slice(-65536);
|
|
368
|
+
};
|
|
369
|
+
const finish = (error) => {
|
|
370
|
+
if (settled) return;
|
|
371
|
+
settled = true;
|
|
372
|
+
clearTimeout(timeout);
|
|
373
|
+
activePluginChildren.delete(child);
|
|
374
|
+
error === void 0 ? resolvePromise() : reject(error);
|
|
322
375
|
};
|
|
323
376
|
child.stdout?.on("data", collect);
|
|
324
377
|
child.stderr?.on("data", collect);
|
|
325
378
|
child.once("error", () => {
|
|
326
|
-
|
|
379
|
+
finish(/* @__PURE__ */ new Error("无法启动 DSH 插件安装命令。请确认 Node.js 与 pnpm 可用后重试。"));
|
|
327
380
|
});
|
|
328
381
|
child.once("exit", (code) => {
|
|
329
|
-
code === 0 ?
|
|
382
|
+
finish(code === 0 ? void 0 : pluginCommandError(output));
|
|
330
383
|
});
|
|
384
|
+
const timeout = setTimeout(() => {
|
|
385
|
+
terminatePluginChild(child);
|
|
386
|
+
finish(/* @__PURE__ */ new Error("插件安装超时,已终止安装进程。请检查网络后重试。"));
|
|
387
|
+
}, timeoutMs);
|
|
388
|
+
timeout.unref?.();
|
|
331
389
|
});
|
|
332
390
|
}
|
|
391
|
+
function runDshPlugin(args, timeoutMs = PLUGIN_INSTALL_TIMEOUT_MS) {
|
|
392
|
+
const entry = resolveDshCliEntry();
|
|
393
|
+
return monitorPluginChild(spawn(process.execPath, [
|
|
394
|
+
...pluginExecArgv(),
|
|
395
|
+
entry,
|
|
396
|
+
"plugin",
|
|
397
|
+
"--profile",
|
|
398
|
+
"web",
|
|
399
|
+
...args
|
|
400
|
+
], {
|
|
401
|
+
cwd: process.cwd(),
|
|
402
|
+
env: {
|
|
403
|
+
...process.env,
|
|
404
|
+
CI: "true"
|
|
405
|
+
},
|
|
406
|
+
windowsHide: true,
|
|
407
|
+
stdio: [
|
|
408
|
+
"ignore",
|
|
409
|
+
"pipe",
|
|
410
|
+
"pipe"
|
|
411
|
+
]
|
|
412
|
+
}), timeoutMs);
|
|
413
|
+
}
|
|
333
414
|
/** 并发安装互斥:pnpm 锁文件竞争会触发 EPERM/EBUSY,同一时间只允许一个安装进程。 */
|
|
334
415
|
let installing = false;
|
|
335
416
|
/** 仅允许安装固定依赖,避免把浏览器输入转成任意命令。 */
|
|
@@ -352,10 +433,10 @@ async function installDependencyLocked(id, requestHotUpdate) {
|
|
|
352
433
|
const targetVersion = target === dependency.packageName ? latestVersion : await npmLatestVersion(target);
|
|
353
434
|
if (targetVersion === void 0) throw new Error("无法获取 npm 最新版本,请检查网络或 npm registry 后重试。");
|
|
354
435
|
const remove = pluginsToRemoveBeforeInstall(declared, target);
|
|
355
|
-
if (remove.length > 0) await runDshPlugin(["remove", ...remove]);
|
|
356
436
|
await ensureLatestReleaseAllowed(target, targetVersion);
|
|
357
437
|
if (!isOfficialRuntimePackage(target)) await recordDeclaredVersion(target, targetVersion);
|
|
358
438
|
await recordPendingUpdate(target, targetVersion);
|
|
439
|
+
if (remove.length > 0) await runDshPlugin(["remove", ...remove]);
|
|
359
440
|
if (requestHotUpdate()) return dependencyStatuses();
|
|
360
441
|
try {
|
|
361
442
|
await runDshPlugin([
|
|
@@ -368,6 +449,7 @@ async function installDependencyLocked(id, requestHotUpdate) {
|
|
|
368
449
|
throw error;
|
|
369
450
|
}
|
|
370
451
|
if (await installedPackageVersion(target) !== targetVersion) return dependencyStatuses();
|
|
452
|
+
await removePendingUpdate(target);
|
|
371
453
|
return dependencyStatuses();
|
|
372
454
|
}
|
|
373
455
|
//#endregion
|
|
@@ -419,7 +501,7 @@ function crossSiteRequest(request) {
|
|
|
419
501
|
/** 把安装错误收成可给浏览器看的文案:我们自己的中文说明保留,带本地路径的底层错误脱敏。 */
|
|
420
502
|
function publicDependencyError(error) {
|
|
421
503
|
const message = error instanceof Error ? error.message : "依赖管理暂不可用。";
|
|
422
|
-
if (/[A-Za-z]:[\\/]|\/(?:home|Users|var|tmp)\//.test(message)) return "依赖管理暂不可用,请查看服务端日志。";
|
|
504
|
+
if (/[A-Za-z]:[\\/]|\/(?:home|root|Users|var|tmp)\//.test(message)) return "依赖管理暂不可用,请查看服务端日志。";
|
|
423
505
|
return message;
|
|
424
506
|
}
|
|
425
507
|
const inject = [
|
|
@@ -528,6 +610,7 @@ function apply(ctx) {
|
|
|
528
610
|
}
|
|
529
611
|
});
|
|
530
612
|
return () => {
|
|
613
|
+
disposeDependencyInstaller();
|
|
531
614
|
disposeConnectors();
|
|
532
615
|
disposeDependencies();
|
|
533
616
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@michengai/dsh-codex-ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.73",
|
|
4
4
|
"description": "以 Codex 风格重构 DSH Web 侧栏的独立客户端插件",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"publishConfig": {
|
|
@@ -51,6 +51,11 @@
|
|
|
51
51
|
"platform": "web"
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsc --noEmit && tsdown",
|
|
56
|
+
"typecheck": "tsc --noEmit",
|
|
57
|
+
"test": "pnpm run typecheck && vitest run tests/client-runtime.integration.spec.ts && tsdown && node tests/run-assertions.mjs"
|
|
58
|
+
},
|
|
54
59
|
"peerDependencies": {
|
|
55
60
|
"@deepseek-ai/cordis": ">=4.0.1 <5.0.0",
|
|
56
61
|
"@deepseek-ai/dsh-client-locale": ">=0.1.0-rc.0 <0.2.0",
|
|
@@ -96,9 +101,5 @@
|
|
|
96
101
|
"tsx": "^4.22.4",
|
|
97
102
|
"typescript": "^6.0.3",
|
|
98
103
|
"vitest": "^4.1.8"
|
|
99
|
-
},
|
|
100
|
-
"scripts": {
|
|
101
|
-
"build": "tsc --noEmit && tsdown",
|
|
102
|
-
"test": "tsx tests/hover-tip.assert.ts && tsx tests/companion-slots.assert.ts && tsx tests/channel-api.assert.ts && tsx tests/schedule-sessions.assert.ts && tsx tests/session-tree.assert.ts && tsx tests/pinned-sessions.assert.ts && tsx tests/session-manager.assert.ts && tsx tests/workspace-browser.assert.ts && tsx tests/sidebar-search.assert.ts && tsx tests/settings-navigation.assert.ts && tsx tests/settings-nav-icons.assert.ts && tsx tests/locales.assert.ts && vitest run tests/client-runtime.integration.spec.ts && tsx tests/settings-integration.assert.ts && tsx tests/about-dependencies.assert.ts && tsx tests/dependency-manager.assert.ts && tsx tests/conversation-bubbles.assert.ts && tsx tests/conversation-header.assert.ts && tsx tests/conversation-visuals.assert.ts && tsdown && tsx tests/client-bundle.assert.ts && tsx tests/codex-suite.assert.ts"
|
|
103
104
|
}
|
|
104
|
-
}
|
|
105
|
+
}
|