@dungsil/dsh-plugin-ui-worktree 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -12
- package/dist/client.js +662 -391
- package/dist/index.js +212 -70
- package/locale/en.json +1 -1
- package/locale/ko.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +10 -9
package/dist/client.js
CHANGED
|
@@ -3,9 +3,160 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
3
3
|
let react = require("react");
|
|
4
4
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
5
5
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
6
|
+
// ../../tools/ui/src/client.ts
|
|
7
|
+
function useLocaleSnapshot(locale) {
|
|
8
|
+
const subscribe = (0, react.useCallback)((listener) => locale.subscribe(listener), [locale]);
|
|
9
|
+
const getSnapshot = (0, react.useCallback)(() => locale.getSnapshot(), [locale]);
|
|
10
|
+
return (0, react.useSyncExternalStore)(subscribe, getSnapshot);
|
|
11
|
+
}
|
|
12
|
+
/** Each effect owns its sheet, even when another plugin installs the same defaults. */
|
|
13
|
+
function installStyles(css) {
|
|
14
|
+
const style = document.createElement("style");
|
|
15
|
+
style.textContent = css;
|
|
16
|
+
document.head.append(style);
|
|
17
|
+
return () => style.remove();
|
|
18
|
+
}
|
|
19
|
+
// ../../tools/ui/src/icons/git-branch.svg
|
|
20
|
+
var git_branch_default = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"6\" cy=\"5\" r=\"3\"/>\n <circle cx=\"6\" cy=\"19\" r=\"3\"/>\n <circle cx=\"18\" cy=\"6\" r=\"3\"/>\n <path d=\"M6 8v8M6 14h7a5 5 0 0 0 5-5\"/>\n</svg>\n";
|
|
21
|
+
// ../../tools/ui/src/composer.tsx
|
|
22
|
+
const composerStyles = `
|
|
23
|
+
:where(.dsh-plugin-composer-trigger) { display: flex; align-items: center; gap: 4px; min-width: 0; height: 28px; padding: 0 4px 0 8px; border: 0; border-radius: var(--dsw-radius-sm); background: transparent; color: var(--dsw-alias-label-secondary); font: inherit; font-size: 13px; line-height: 20px; cursor: pointer; }
|
|
24
|
+
:where(.dsh-plugin-composer-trigger):disabled { color: var(--dsw-alias-label-dimmed); cursor: default; }
|
|
25
|
+
.dsh-plugin-composer-icon, .dsh-plugin-composer-chevron { display: inline-flex; flex: none; }
|
|
26
|
+
.dsh-plugin-composer-label, .dsh-plugin-composer-detail { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
27
|
+
.dsh-plugin-composer-chevron, .dsh-plugin-composer-detail { color: var(--dsw-alias-label-caption); }
|
|
28
|
+
.dsh-plugin-composer-trigger[aria-expanded=true] .dsh-plugin-composer-chevron { transform: rotate(180deg); }
|
|
29
|
+
`;
|
|
30
|
+
const ComposerTrigger = (0, react.forwardRef)(function ComposerTrigger({ className = "", icon, label, detail, ...props }, ref) {
|
|
31
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
32
|
+
...props,
|
|
33
|
+
ref,
|
|
34
|
+
type: "button",
|
|
35
|
+
className: `dsh-plugin-composer-trigger ${className}`,
|
|
36
|
+
children: [
|
|
37
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
38
|
+
className: "dsh-plugin-composer-icon",
|
|
39
|
+
"aria-hidden": "true",
|
|
40
|
+
children: icon
|
|
41
|
+
}),
|
|
42
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
43
|
+
className: "dsh-plugin-composer-label",
|
|
44
|
+
children: label
|
|
45
|
+
}),
|
|
46
|
+
detail && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
47
|
+
className: "dsh-plugin-composer-detail",
|
|
48
|
+
children: detail
|
|
49
|
+
}),
|
|
50
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
51
|
+
className: "dsh-plugin-composer-chevron",
|
|
52
|
+
"aria-hidden": "true",
|
|
53
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {})
|
|
54
|
+
})
|
|
55
|
+
]
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
// ../../tools/ui/src/settings.tsx
|
|
59
|
+
/** The rc.5 settings shell has no icon slot, so decorate only the matching navigation rows. */
|
|
60
|
+
function installSettingsIcon(id, labels, svg) {
|
|
61
|
+
const attribute = "data-dsh-settings-icon";
|
|
62
|
+
const selector = `[${attribute}="${id}"]`;
|
|
63
|
+
const removeStyles = installStyles(`
|
|
64
|
+
${selector} > svg { display: none; }
|
|
65
|
+
${selector}::before { content: ''; display: block; flex: none; width: 16px; height: 16px; background: currentColor; mask: url("data:image/svg+xml,${encodeURIComponent(svg)}") center / contain no-repeat; }
|
|
66
|
+
`);
|
|
67
|
+
const marked = /* @__PURE__ */ new Set();
|
|
68
|
+
const refresh = () => {
|
|
69
|
+
const rows = new Set(document.querySelectorAll("[data-shortcut-modal=\"settings\"] nav button"));
|
|
70
|
+
for (const row of marked) if (!rows.has(row) || !labels.includes(row.textContent?.trim() ?? "")) {
|
|
71
|
+
row.removeAttribute(attribute);
|
|
72
|
+
marked.delete(row);
|
|
73
|
+
}
|
|
74
|
+
for (const row of rows) if (labels.includes(row.textContent?.trim() ?? "")) {
|
|
75
|
+
row.setAttribute(attribute, id);
|
|
76
|
+
marked.add(row);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const observer = new MutationObserver(refresh);
|
|
80
|
+
observer.observe(document.body, {
|
|
81
|
+
childList: true,
|
|
82
|
+
subtree: true,
|
|
83
|
+
characterData: true
|
|
84
|
+
});
|
|
85
|
+
refresh();
|
|
86
|
+
return () => {
|
|
87
|
+
observer.disconnect();
|
|
88
|
+
for (const row of marked) row.removeAttribute(attribute);
|
|
89
|
+
removeStyles();
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const settingsStyles = `
|
|
93
|
+
.dsh-plugin-settings-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 16px 0; border-bottom: .5px solid var(--dsw-alias-border-l2); }
|
|
94
|
+
.dsh-plugin-settings-text { flex: 1; min-width: 0; }
|
|
95
|
+
.dsh-plugin-settings-picker { min-width: 0; }
|
|
96
|
+
.dsh-plugin-settings-selector { box-sizing: border-box; display: inline-flex; align-items: center; gap: 12px; max-width: 100%; height: 36px; padding: 0 14px; border: none; border-radius: var(--dsw-radius-md); background: var(--dsw-alias-bg-module-platform); color: inherit; font: inherit; cursor: pointer; }
|
|
97
|
+
.dsh-plugin-settings-value { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
98
|
+
.dsh-plugin-settings-selector svg { flex: none; }
|
|
99
|
+
.dsh-plugin-settings-selector:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
|
|
100
|
+
.dsh-plugin-settings-selector:disabled { opacity: .5; cursor: default; }
|
|
101
|
+
.dsh-plugin-settings-selector:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary-new-colorprimary-new-color); outline-offset: 2px; }
|
|
102
|
+
`;
|
|
103
|
+
function SettingsRow({ label, children }) {
|
|
104
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
105
|
+
className: "dsh-plugin-settings-row",
|
|
106
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
107
|
+
className: "dsh-plugin-settings-text",
|
|
108
|
+
children: label
|
|
109
|
+
}), children]
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function SettingsPicker({ className = "", value, selectedId, items, onSelect, disabled = false, ...buttonProps }) {
|
|
113
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
114
|
+
(0, react.useEffect)(() => {
|
|
115
|
+
if (disabled) setOpen(false);
|
|
116
|
+
}, [disabled]);
|
|
117
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
118
|
+
className: `dsh-plugin-settings-picker ${className}`,
|
|
119
|
+
open: open && !disabled,
|
|
120
|
+
onClose: () => setOpen(false),
|
|
121
|
+
items,
|
|
122
|
+
selectedId,
|
|
123
|
+
onSelect: (id) => {
|
|
124
|
+
setOpen(false);
|
|
125
|
+
if (!disabled) onSelect(id);
|
|
126
|
+
},
|
|
127
|
+
align: "end",
|
|
128
|
+
portal: true,
|
|
129
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
130
|
+
...buttonProps,
|
|
131
|
+
type: "button",
|
|
132
|
+
className: "dsh-plugin-settings-selector",
|
|
133
|
+
"aria-haspopup": "menu",
|
|
134
|
+
"aria-expanded": open && !disabled,
|
|
135
|
+
disabled,
|
|
136
|
+
onClick: () => setOpen((current) => !current),
|
|
137
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
138
|
+
className: "dsh-plugin-settings-value",
|
|
139
|
+
children: value
|
|
140
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {
|
|
141
|
+
size: 16,
|
|
142
|
+
"aria-hidden": "true"
|
|
143
|
+
})]
|
|
144
|
+
})
|
|
145
|
+
});
|
|
146
|
+
}
|
|
6
147
|
// src/protocol.ts
|
|
7
148
|
const channel = "/api";
|
|
8
149
|
const endpointPrefix = "dungsil-worktree/";
|
|
150
|
+
function isWorktreeProgress(value) {
|
|
151
|
+
if (!value || typeof value !== "object") return false;
|
|
152
|
+
const progress = value;
|
|
153
|
+
return typeof progress.id === "string" && Number.isInteger(progress.step) && progress.step >= 0 && progress.step < 4 && [
|
|
154
|
+
"running",
|
|
155
|
+
"completed",
|
|
156
|
+
"failed",
|
|
157
|
+
"cancelled"
|
|
158
|
+
].includes(progress.status) && Number.isFinite(progress.updatedAt) && Array.isArray(progress.durations) && progress.durations.length === 4 && progress.durations.every((duration) => Number.isFinite(duration) && duration >= 0) && (progress.branch === void 0 || typeof progress.branch === "string");
|
|
159
|
+
}
|
|
9
160
|
function comparablePath(path) {
|
|
10
161
|
return (/^[a-z]:[\\/]/i.test(path) || path.startsWith("\\\\") || path.startsWith("//") ? path.replaceAll("\\", "/").replace(/^[A-Z]:/, (drive) => drive.toLowerCase()) : path).replace(/\/+$/, "");
|
|
11
162
|
}
|
|
@@ -20,7 +171,7 @@ function currentWorktree(view) {
|
|
|
20
171
|
function isWorktreeView(value) {
|
|
21
172
|
if (!value || typeof value !== "object") return false;
|
|
22
173
|
const view = value;
|
|
23
|
-
return typeof view.cwd === "string" && Array.isArray(view.worktrees) && view.worktrees.every((item) => item && typeof item.path === "string" && (item.branch === null || typeof item.branch === "string"));
|
|
174
|
+
return typeof view.cwd === "string" && (view.pendingCreation === void 0 || view.pendingCreation !== null && typeof view.pendingCreation.name === "string" && typeof view.pendingCreation.failed === "boolean") && Array.isArray(view.worktrees) && view.worktrees.every((item) => item && typeof item.path === "string" && (item.branch === null || typeof item.branch === "string"));
|
|
24
175
|
}
|
|
25
176
|
// src/errors.ts
|
|
26
177
|
const failureCodes = [
|
|
@@ -54,16 +205,55 @@ function failureCode(error) {
|
|
|
54
205
|
}
|
|
55
206
|
// src/requests.ts
|
|
56
207
|
const copy$1 = {
|
|
208
|
+
zh: {
|
|
209
|
+
title: "Git 工作树",
|
|
210
|
+
detached: "分离的 HEAD",
|
|
211
|
+
unknown: "无法获取分支",
|
|
212
|
+
create: "新工作树",
|
|
213
|
+
reservationHint: "发送下一条提示后,将先创建并切换到新工作树再开始任务。选择现有工作树可取消。",
|
|
214
|
+
creationError: "无法确认创建结果,尚未开始任务。请刷新列表,选择工作树或重新预约,然后重新发送提示。",
|
|
215
|
+
close: "关闭",
|
|
216
|
+
refresh: "刷新工作树",
|
|
217
|
+
loading: "正在加载工作树…",
|
|
218
|
+
busy: "正在切换工作树…",
|
|
219
|
+
changed: "已切换工作树。",
|
|
220
|
+
empty: "未找到 Git 工作树。",
|
|
221
|
+
wait: "请等待会话准备就绪且当前任务结束。",
|
|
222
|
+
error: "无法确认当前状态。请刷新工作树以检查状态后再重试。",
|
|
223
|
+
lookupError: "查询失败",
|
|
224
|
+
mutationError: "需确认工作树状态",
|
|
225
|
+
errorTitle: "工作树错误详情",
|
|
226
|
+
readError: "无法加载工作树列表。请重新查询以确认当前分支。",
|
|
227
|
+
errorCode: "错误代码",
|
|
228
|
+
errorOperation: "请求",
|
|
229
|
+
operations: {
|
|
230
|
+
describe: "查询工作树",
|
|
231
|
+
create: "创建工作树",
|
|
232
|
+
switch: "切换工作树"
|
|
233
|
+
},
|
|
234
|
+
reasons: {
|
|
235
|
+
busy: "会话中有正在执行的任务。请在任务结束后重新查询。",
|
|
236
|
+
conflict: "当前工作目录已更改。请刷新列表后再进行更改。",
|
|
237
|
+
invalid: "请求无效。请刷新列表并检查分支名称或工作树。",
|
|
238
|
+
operation_failed: "主机无法完成操作。请检查 Git 和工作树配置。",
|
|
239
|
+
session_not_found: "会话不可用。请重新打开会话后重试。",
|
|
240
|
+
forbidden: "此操作需要所有者连接。请以所有者身份重新打开应用。",
|
|
241
|
+
auth_required: "连接需要身份验证。请重新打开应用后重试。",
|
|
242
|
+
endpoint_missing: "主机没有工作树接口。请确认插件已启用并重启应用。",
|
|
243
|
+
client_unavailable: "连接缺少工作树 RPC 客户端。请刷新页面或更新应用。",
|
|
244
|
+
invalid_response: "主机返回的工作树列表无效。请检查应用和插件版本。",
|
|
245
|
+
connection_error: "收到有效响应前连接失败。请检查连接后重新查询。",
|
|
246
|
+
timeout: "请求超时。请在当前任务结束后重新查询。"
|
|
247
|
+
}
|
|
248
|
+
},
|
|
57
249
|
en: {
|
|
58
250
|
title: "Git worktree",
|
|
59
251
|
detached: "Detached HEAD",
|
|
60
252
|
unknown: "Branch unavailable",
|
|
61
|
-
create: "New worktree
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
hint: "Leave the name empty for model naming. Uncommitted changes stay in the source checkout.",
|
|
253
|
+
create: "New worktree",
|
|
254
|
+
reservationHint: "The next prompt will create and enter a new worktree before starting work. Select an existing worktree to cancel.",
|
|
255
|
+
creationError: "Creation could not be confirmed, so work did not start. Refresh the list, select a worktree or schedule a new one, then resend your prompt.",
|
|
65
256
|
close: "Close",
|
|
66
|
-
submit: "Create",
|
|
67
257
|
refresh: "Refresh worktrees",
|
|
68
258
|
loading: "Loading worktrees…",
|
|
69
259
|
busy: "Changing worktree…",
|
|
@@ -101,12 +291,10 @@ const copy$1 = {
|
|
|
101
291
|
title: "Git worktree",
|
|
102
292
|
detached: "분리된 HEAD",
|
|
103
293
|
unknown: "브랜치 확인 불가",
|
|
104
|
-
create: "
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
hint: "이름을 비우면 모델이 이름을 정합니다. 커밋하지 않은 변경은 원래 체크아웃에 남습니다.",
|
|
294
|
+
create: "새 워크트리",
|
|
295
|
+
reservationHint: "다음 프롬프트를 전송하면 새 워크트리를 생성·전환한 뒤 작업을 시작합니다. 기존 워크트리를 선택하면 예약을 취소합니다.",
|
|
296
|
+
creationError: "생성 결과를 확인하지 못해 작업을 시작하지 않았습니다. 목록을 새로고침하고 워크트리를 선택하거나 다시 예약한 뒤 프롬프트를 다시 전송하세요.",
|
|
108
297
|
close: "닫기",
|
|
109
|
-
submit: "생성",
|
|
110
298
|
refresh: "워크트리 새로고침",
|
|
111
299
|
loading: "워크트리를 불러오는 중…",
|
|
112
300
|
busy: "워크트리 변경 중…",
|
|
@@ -145,89 +333,107 @@ const copy$1 = {
|
|
|
145
333
|
const copy = {
|
|
146
334
|
ko: {
|
|
147
335
|
nav: "워크트리",
|
|
336
|
+
defaultSelection: "기본 선택",
|
|
337
|
+
current: "현재 체크아웃",
|
|
338
|
+
new: "새 워크트리",
|
|
148
339
|
directory: "워크트리 풀 디렉터리",
|
|
149
|
-
directoryHint: "원본 저장소 루트 기준 상대 경로 또는 절대 경로를 입력하세요.",
|
|
150
340
|
remote: "추적 중인 원격 브랜치 사용",
|
|
151
|
-
remoteHint: "로컬에 가져온 원격 참조를 사용합니다. 자동으로 fetch하지 않습니다.",
|
|
152
341
|
naming: "자동 브랜치 이름 형식",
|
|
153
|
-
namingHint: "워크트리 생성 시 브랜치 입력을 비운 경우에만 적용합니다.",
|
|
154
342
|
semantic: "시멘틱 (feat/… 또는 fix/…)",
|
|
155
343
|
dsh: "DSH (dsh/*)",
|
|
156
344
|
custom: "사용자 지정 접두사",
|
|
157
345
|
prefix: "브랜치 접두사",
|
|
158
|
-
prefixHint: "영문·숫자·하이픈·밑줄과 /를 사용하세요. 예: team/, task-",
|
|
159
|
-
save: "저장",
|
|
160
|
-
refresh: "설정 새로고침",
|
|
161
346
|
loading: "설정을 불러오는 중입니다…",
|
|
162
347
|
saving: "저장 중…",
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
348
|
+
error: "변경 결과를 확인하지 못했습니다. 저장된 값을 확인한 뒤 다시 변경하세요.",
|
|
349
|
+
conflict: "다른 곳에서 설정을 변경했습니다. 저장된 값을 확인한 뒤 다시 변경하세요.",
|
|
350
|
+
unavailable: "설정을 불러오지 못했습니다. 연결을 확인한 뒤 설정 화면을 다시 여세요.",
|
|
351
|
+
invalidDirectory: "공백이 아닌 경로를 4096자 이내로 입력하세요. 줄바꿈은 사용할 수 없습니다.",
|
|
352
|
+
invalidPrefix: "올바른 접두사를 100자 이내로 입력하세요. 사용자 지정 접두사는 비워 둘 수 없습니다."
|
|
353
|
+
},
|
|
354
|
+
zh: {
|
|
355
|
+
nav: "工作树",
|
|
356
|
+
defaultSelection: "默认选择",
|
|
357
|
+
current: "当前检出",
|
|
358
|
+
new: "新工作树",
|
|
359
|
+
directory: "工作树池目录",
|
|
360
|
+
remote: "使用跟踪的远程分支",
|
|
361
|
+
naming: "自动分支命名格式",
|
|
362
|
+
semantic: "语义化(feat/… 或 fix/…)",
|
|
363
|
+
dsh: "DSH(dsh/*)",
|
|
364
|
+
custom: "自定义前缀",
|
|
365
|
+
prefix: "分支前缀",
|
|
366
|
+
loading: "正在加载设置…",
|
|
367
|
+
saving: "正在保存…",
|
|
368
|
+
error: "无法确认更改结果,请检查已保存的值后再进行更改。",
|
|
369
|
+
conflict: "设置已在其他位置更改,请检查已保存的值后再进行更改。",
|
|
370
|
+
unavailable: "无法加载设置,请检查连接后重新打开此设置页面。",
|
|
371
|
+
invalidDirectory: "请输入不超过 4096 个字符的非空路径,不能包含换行符。",
|
|
372
|
+
invalidPrefix: "请输入不超过 100 个字符的有效前缀,自定义前缀不能为空。"
|
|
166
373
|
},
|
|
167
374
|
en: {
|
|
168
375
|
nav: "Worktrees",
|
|
376
|
+
defaultSelection: "Default selection",
|
|
377
|
+
current: "Current checkout",
|
|
378
|
+
new: "New worktree",
|
|
169
379
|
directory: "Worktree pool directory",
|
|
170
|
-
directoryHint: "Enter a path relative to the source repository root, or an absolute path.",
|
|
171
380
|
remote: "Use tracked remote branch",
|
|
172
|
-
remoteHint: "Uses the locally fetched remote reference. Does not fetch automatically.",
|
|
173
381
|
naming: "Automatic branch naming",
|
|
174
|
-
namingHint: "Applies only when the branch input is left empty when creating a worktree.",
|
|
175
382
|
semantic: "Semantic (feat/… or fix/…)",
|
|
176
383
|
dsh: "DSH (dsh/*)",
|
|
177
384
|
custom: "Custom prefix",
|
|
178
385
|
prefix: "Branch prefix",
|
|
179
|
-
prefixHint: "Use letters, digits, hyphens, underscores and /. For example: team/, task-",
|
|
180
|
-
save: "Save",
|
|
181
|
-
refresh: "Reload settings",
|
|
182
386
|
loading: "Loading settings…",
|
|
183
387
|
saving: "Saving…",
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
388
|
+
error: "Could not confirm the change. Check the saved values before you change them again.",
|
|
389
|
+
conflict: "Settings changed elsewhere. Check the saved values before you change them again.",
|
|
390
|
+
unavailable: "Could not load settings. Check the connection and reopen this settings page.",
|
|
391
|
+
invalidDirectory: "Enter a nonblank path of at most 4096 characters without line breaks.",
|
|
392
|
+
invalidPrefix: "Enter a valid prefix of at most 100 characters. A custom prefix cannot be empty."
|
|
187
393
|
}
|
|
188
394
|
};
|
|
189
|
-
const
|
|
395
|
+
const languageKey = (language) => language.startsWith("ko") ? "ko" : language.startsWith("zh") ? "zh" : "en";
|
|
396
|
+
const settingsLabel = (language) => copy[languageKey(language)].nav;
|
|
397
|
+
const validDirectory = (value) => value.trim().length > 0 && value.length <= 4096 && !/[\0\r\n]/.test(value);
|
|
398
|
+
const validPrefix = (value) => value.length <= 100 && (value === "" || /^[a-zA-Z0-9][a-zA-Z0-9_-]*(\/[a-zA-Z0-9][a-zA-Z0-9_-]*)*\/?$/.test(value));
|
|
399
|
+
const validSettings = (settings) => validDirectory(settings.directory) && validPrefix(settings.prefix) && (settings.naming !== "custom" || !!settings.prefix);
|
|
400
|
+
const sameSettings = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
190
401
|
function WorktreeSettingsSection({ ctx }) {
|
|
191
|
-
const
|
|
192
|
-
const text = copy[language
|
|
402
|
+
const { active: language } = useLocaleSnapshot(ctx.locale);
|
|
403
|
+
const text = copy[languageKey(language)];
|
|
193
404
|
const id = (0, react.useId)();
|
|
194
405
|
const connection = ctx.get("connection");
|
|
195
|
-
const [view, setView] = (0, react.useState)();
|
|
196
406
|
const [draft, setDraft] = (0, react.useState)();
|
|
197
407
|
const [operation, setOperation] = (0, react.useState)("loading");
|
|
198
408
|
const [notice, setNotice] = (0, react.useState)();
|
|
199
|
-
const
|
|
409
|
+
const saved = (0, react.useRef)(void 0);
|
|
410
|
+
const latest = (0, react.useRef)(void 0);
|
|
200
411
|
const pending = (0, react.useRef)(false);
|
|
201
|
-
const
|
|
412
|
+
const blocked = (0, react.useRef)(true);
|
|
202
413
|
const lifetime = (0, react.useRef)(null);
|
|
203
|
-
const
|
|
414
|
+
const read = async (controller) => {
|
|
415
|
+
const result = await connection.rpc.call(channel, `${endpointPrefix}settings`, {}, controller.signal);
|
|
416
|
+
if (controller.signal.aborted) return;
|
|
417
|
+
if (!result.ok) throw new Error("settings failed");
|
|
418
|
+
const next = result.value;
|
|
419
|
+
saved.current = next;
|
|
420
|
+
latest.current = { ...next.settings };
|
|
421
|
+
setDraft(latest.current);
|
|
422
|
+
blocked.current = false;
|
|
423
|
+
};
|
|
424
|
+
const load = async () => {
|
|
204
425
|
const controller = lifetime.current;
|
|
205
426
|
if (!controller || controller.signal.aborted || pending.current) return;
|
|
206
|
-
if (save && (!view || !draft || needsReload.current)) return;
|
|
207
427
|
pending.current = true;
|
|
208
|
-
|
|
209
|
-
setOperation(
|
|
210
|
-
setNotice(void 0);
|
|
428
|
+
blocked.current = true;
|
|
429
|
+
setOperation("loading");
|
|
211
430
|
try {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
settings: { ...draft }
|
|
215
|
-
} : {}, controller.signal);
|
|
216
|
-
if (controller.signal.aborted) return;
|
|
217
|
-
if (!result.ok) {
|
|
218
|
-
needsReload.current = true;
|
|
219
|
-
setNotice(result.error.code === "conflict" ? "conflict" : "error");
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
const next = result.value;
|
|
223
|
-
setView(next);
|
|
224
|
-
setDraft({ ...next.settings });
|
|
225
|
-
needsReload.current = false;
|
|
226
|
-
if (save) setNotice("saved");
|
|
431
|
+
await read(controller);
|
|
432
|
+
if (!controller.signal.aborted) setNotice(void 0);
|
|
227
433
|
} catch {
|
|
228
434
|
if (!controller.signal.aborted) {
|
|
229
|
-
|
|
230
|
-
setNotice("
|
|
435
|
+
blocked.current = true;
|
|
436
|
+
setNotice("unavailable");
|
|
231
437
|
}
|
|
232
438
|
} finally {
|
|
233
439
|
if (!controller.signal.aborted) {
|
|
@@ -236,40 +442,125 @@ function WorktreeSettingsSection({ ctx }) {
|
|
|
236
442
|
}
|
|
237
443
|
}
|
|
238
444
|
};
|
|
445
|
+
const save = async () => {
|
|
446
|
+
const controller = lifetime.current;
|
|
447
|
+
if (!controller || controller.signal.aborted || pending.current || blocked.current || !saved.current) return;
|
|
448
|
+
pending.current = true;
|
|
449
|
+
setNotice(void 0);
|
|
450
|
+
try {
|
|
451
|
+
while (latest.current && validSettings(latest.current) && !sameSettings(latest.current, saved.current.settings)) {
|
|
452
|
+
const submitted = { ...latest.current };
|
|
453
|
+
setOperation("saving");
|
|
454
|
+
const result = await connection.rpc.call(channel, `${endpointPrefix}save-settings`, {
|
|
455
|
+
revision: saved.current.revision,
|
|
456
|
+
settings: submitted
|
|
457
|
+
}, controller.signal);
|
|
458
|
+
if (controller.signal.aborted) return;
|
|
459
|
+
if (!result.ok) {
|
|
460
|
+
setNotice(result.error.code === "conflict" ? "conflict" : "error");
|
|
461
|
+
blocked.current = true;
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
const next = result.value;
|
|
465
|
+
saved.current = next;
|
|
466
|
+
if (sameSettings(latest.current, submitted)) {
|
|
467
|
+
latest.current = { ...next.settings };
|
|
468
|
+
setDraft(latest.current);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
} catch {
|
|
472
|
+
if (controller.signal.aborted) return;
|
|
473
|
+
blocked.current = true;
|
|
474
|
+
setNotice("error");
|
|
475
|
+
} finally {
|
|
476
|
+
if (!controller.signal.aborted) {
|
|
477
|
+
if (blocked.current) {
|
|
478
|
+
setOperation("loading");
|
|
479
|
+
try {
|
|
480
|
+
await read(controller);
|
|
481
|
+
} catch {
|
|
482
|
+
if (!controller.signal.aborted) setNotice("unavailable");
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
if (!controller.signal.aborted) {
|
|
486
|
+
pending.current = false;
|
|
487
|
+
setOperation(void 0);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
const change = (patch) => {
|
|
493
|
+
if (!latest.current || blocked.current) return;
|
|
494
|
+
latest.current = {
|
|
495
|
+
...latest.current,
|
|
496
|
+
...patch
|
|
497
|
+
};
|
|
498
|
+
if (patch.naming && patch.naming !== "custom" && !validPrefix(latest.current.prefix)) latest.current.prefix = "";
|
|
499
|
+
setDraft(latest.current);
|
|
500
|
+
setNotice(void 0);
|
|
501
|
+
if (validSettings(latest.current)) save();
|
|
502
|
+
};
|
|
239
503
|
(0, react.useEffect)(() => {
|
|
240
504
|
const controller = new AbortController();
|
|
241
505
|
lifetime.current = controller;
|
|
242
506
|
pending.current = false;
|
|
243
|
-
|
|
244
|
-
|
|
507
|
+
blocked.current = true;
|
|
508
|
+
load();
|
|
509
|
+
const refresh = () => {
|
|
510
|
+
if (blocked.current || sameSettings(latest.current, saved.current?.settings)) load();
|
|
511
|
+
};
|
|
512
|
+
window.addEventListener("focus", refresh);
|
|
513
|
+
window.addEventListener("online", refresh);
|
|
514
|
+
return () => {
|
|
515
|
+
controller.abort();
|
|
516
|
+
window.removeEventListener("focus", refresh);
|
|
517
|
+
window.removeEventListener("online", refresh);
|
|
518
|
+
};
|
|
245
519
|
}, []);
|
|
246
|
-
const
|
|
247
|
-
const
|
|
520
|
+
const disabled = operation === "loading" || blocked.current;
|
|
521
|
+
const invalidDirectory = !!draft && !validDirectory(draft.directory);
|
|
522
|
+
const invalidPrefix = !!draft && (!validPrefix(draft.prefix) || draft.naming === "custom" && !draft.prefix);
|
|
248
523
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
249
524
|
className: "dsh-worktree-settings",
|
|
250
525
|
"aria-label": text.nav,
|
|
251
|
-
"aria-busy":
|
|
526
|
+
"aria-busy": operation !== void 0,
|
|
252
527
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
|
|
253
|
-
onSubmit: (event) =>
|
|
254
|
-
event.preventDefault();
|
|
255
|
-
request(true);
|
|
256
|
-
},
|
|
528
|
+
onSubmit: (event) => event.preventDefault(),
|
|
257
529
|
children: [
|
|
258
530
|
draft && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
|
|
259
|
-
disabled
|
|
531
|
+
disabled,
|
|
260
532
|
children: [
|
|
261
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
className: "dsh-worktree-settings-
|
|
265
|
-
children:
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
533
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsRow, {
|
|
534
|
+
label: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
535
|
+
id: `${id}-default-selection`,
|
|
536
|
+
className: "dsh-worktree-settings-title",
|
|
537
|
+
children: text.defaultSelection
|
|
538
|
+
}),
|
|
539
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsPicker, {
|
|
540
|
+
className: "dsh-worktree-settings-picker",
|
|
541
|
+
disabled,
|
|
542
|
+
"aria-labelledby": `${id}-default-selection`,
|
|
543
|
+
value: text[draft.defaultSelection],
|
|
544
|
+
selectedId: draft.defaultSelection,
|
|
545
|
+
items: ["current", "new"].map((id) => ({
|
|
546
|
+
id,
|
|
547
|
+
label: text[id]
|
|
548
|
+
})),
|
|
549
|
+
onSelect: (defaultSelection) => {
|
|
550
|
+
change({ defaultSelection });
|
|
551
|
+
}
|
|
552
|
+
})
|
|
553
|
+
}),
|
|
554
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsRow, {
|
|
555
|
+
label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
556
|
+
htmlFor: `${id}-directory`,
|
|
557
|
+
children: text.directory
|
|
558
|
+
}), invalidDirectory && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
559
|
+
id: `${id}-directory-error`,
|
|
560
|
+
role: "alert",
|
|
561
|
+
children: text.invalidDirectory
|
|
562
|
+
})] }),
|
|
563
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
273
564
|
className: "dsh-worktree-settings-input",
|
|
274
565
|
id: `${id}-directory`,
|
|
275
566
|
type: "text",
|
|
@@ -278,47 +569,34 @@ function WorktreeSettingsSection({ ctx }) {
|
|
|
278
569
|
maxLength: 4096,
|
|
279
570
|
autoComplete: "off",
|
|
280
571
|
spellCheck: false,
|
|
281
|
-
"aria-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
})
|
|
286
|
-
})]
|
|
572
|
+
"aria-invalid": invalidDirectory || void 0,
|
|
573
|
+
"aria-describedby": invalidDirectory ? `${id}-directory-error` : void 0,
|
|
574
|
+
onChange: (event) => change({ directory: event.target.value })
|
|
575
|
+
})
|
|
287
576
|
}),
|
|
288
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
children: text.remote
|
|
295
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: text.remoteHint })]
|
|
296
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
577
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsRow, {
|
|
578
|
+
label: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
579
|
+
className: "dsh-worktree-settings-title",
|
|
580
|
+
children: text.remote
|
|
581
|
+
}),
|
|
582
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
297
583
|
label: text.remote,
|
|
298
584
|
checked: draft.remote,
|
|
299
|
-
disabled
|
|
300
|
-
onChange: (remote) =>
|
|
301
|
-
|
|
302
|
-
remote
|
|
303
|
-
})
|
|
304
|
-
})]
|
|
585
|
+
disabled,
|
|
586
|
+
onChange: (remote) => change({ remote })
|
|
587
|
+
})
|
|
305
588
|
}),
|
|
306
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
className: "dsh-worktree-settings-
|
|
310
|
-
children:
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
})]
|
|
318
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
319
|
-
open: namingOpen,
|
|
320
|
-
align: "end",
|
|
321
|
-
portal: true,
|
|
589
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsRow, {
|
|
590
|
+
label: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
591
|
+
id: `${id}-naming`,
|
|
592
|
+
className: "dsh-worktree-settings-title",
|
|
593
|
+
children: text.naming
|
|
594
|
+
}),
|
|
595
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsPicker, {
|
|
596
|
+
className: "dsh-worktree-settings-picker",
|
|
597
|
+
disabled,
|
|
598
|
+
"aria-labelledby": `${id}-naming`,
|
|
599
|
+
value: text[draft.naming],
|
|
322
600
|
selectedId: draft.naming,
|
|
323
601
|
items: [
|
|
324
602
|
"semantic",
|
|
@@ -328,42 +606,21 @@ function WorktreeSettingsSection({ ctx }) {
|
|
|
328
606
|
id,
|
|
329
607
|
label: text[id]
|
|
330
608
|
})),
|
|
331
|
-
onClose: () => setNamingOpen(false),
|
|
332
609
|
onSelect: (naming) => {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
naming
|
|
337
|
-
});
|
|
338
|
-
},
|
|
339
|
-
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
340
|
-
type: "button",
|
|
341
|
-
className: "dsh-worktree-settings-selector",
|
|
342
|
-
disabled: busy,
|
|
343
|
-
"aria-labelledby": `${id}-naming`,
|
|
344
|
-
"aria-describedby": `${id}-naming-hint`,
|
|
345
|
-
"aria-haspopup": "menu",
|
|
346
|
-
"aria-expanded": namingOpen,
|
|
347
|
-
onClick: () => setNamingOpen((open) => !open),
|
|
348
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text[draft.naming] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {
|
|
349
|
-
size: 16,
|
|
350
|
-
"aria-hidden": "true"
|
|
351
|
-
})]
|
|
352
|
-
})
|
|
353
|
-
})]
|
|
610
|
+
change({ naming });
|
|
611
|
+
}
|
|
612
|
+
})
|
|
354
613
|
}),
|
|
355
|
-
draft.naming === "custom" && /* @__PURE__ */ (0, react_jsx_runtime.
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
})]
|
|
366
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
614
|
+
draft.naming === "custom" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsRow, {
|
|
615
|
+
label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
616
|
+
htmlFor: `${id}-prefix`,
|
|
617
|
+
children: text.prefix
|
|
618
|
+
}), invalidPrefix && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
619
|
+
id: `${id}-prefix-error`,
|
|
620
|
+
role: "alert",
|
|
621
|
+
children: text.invalidPrefix
|
|
622
|
+
})] }),
|
|
623
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
367
624
|
className: "dsh-worktree-settings-input",
|
|
368
625
|
id: `${id}-prefix`,
|
|
369
626
|
type: "text",
|
|
@@ -371,66 +628,44 @@ function WorktreeSettingsSection({ ctx }) {
|
|
|
371
628
|
required: true,
|
|
372
629
|
maxLength: 100,
|
|
373
630
|
pattern: "[a-zA-Z0-9][a-zA-Z0-9_\\-]*(/[a-zA-Z0-9][a-zA-Z0-9_\\-]*)*/?",
|
|
374
|
-
"aria-
|
|
631
|
+
"aria-invalid": invalidPrefix || void 0,
|
|
632
|
+
"aria-describedby": invalidPrefix ? `${id}-prefix-error` : void 0,
|
|
375
633
|
autoComplete: "off",
|
|
376
634
|
spellCheck: false,
|
|
377
|
-
onChange: (event) =>
|
|
378
|
-
|
|
379
|
-
prefix: event.target.value
|
|
380
|
-
})
|
|
381
|
-
})]
|
|
635
|
+
onChange: (event) => change({ prefix: event.target.value })
|
|
636
|
+
})
|
|
382
637
|
})
|
|
383
638
|
]
|
|
384
639
|
}),
|
|
385
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
640
|
+
operation && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
386
641
|
role: "status",
|
|
387
|
-
children:
|
|
642
|
+
children: text[operation]
|
|
388
643
|
}),
|
|
389
|
-
|
|
644
|
+
notice && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
390
645
|
role: "alert",
|
|
391
646
|
children: text[notice]
|
|
392
|
-
})
|
|
393
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
394
|
-
type: "button",
|
|
395
|
-
variant: "outline",
|
|
396
|
-
disabled: busy,
|
|
397
|
-
onClick: () => void request(),
|
|
398
|
-
children: text.refresh
|
|
399
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
400
|
-
type: "submit",
|
|
401
|
-
variant: "primary",
|
|
402
|
-
disabled: busy || !view || !dirty || needsReload.current,
|
|
403
|
-
children: operation === "saving" ? text.saving : text.save
|
|
404
|
-
})] })
|
|
647
|
+
})
|
|
405
648
|
]
|
|
406
649
|
})
|
|
407
650
|
});
|
|
408
651
|
}
|
|
409
652
|
// src/styles.ts
|
|
410
|
-
const styles =
|
|
653
|
+
const styles = `${settingsStyles}${composerStyles}
|
|
411
654
|
.dsh-worktree-sidebar-name { flex: 0 1 auto; min-width: 0; max-width: 88px; margin-inline: 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-caption); font-size: 10px; line-height: 16px; }
|
|
412
655
|
.dsh-worktree { display: inline-flex; flex-wrap: wrap; align-items: center; min-width: 0; max-width: 100%; color: var(--dsw-alias-label-secondary); font: inherit; }
|
|
413
|
-
.dsh-worktree-controls { display: inline-flex; flex-wrap: nowrap; align-items: center; min-width: 0; max-width: 100%; }
|
|
414
656
|
.dsh-worktree-picker { display: inline-flex; min-width: 0; max-width: 100%; }
|
|
415
|
-
.dsh-worktree-trigger { display: inline-flex;
|
|
416
|
-
.dsh-worktree-
|
|
417
|
-
.dsh-worktree-icon svg { width: 14px; height: 14px; }
|
|
418
|
-
.dsh-worktree-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
419
|
-
.dsh-worktree-chevron { color: var(--dsw-alias-label-caption); }
|
|
420
|
-
.dsh-worktree-trigger[aria-expanded=true] .dsh-worktree-chevron { transform: rotate(180deg); }
|
|
421
|
-
.dsh-worktree-trigger:disabled { color: var(--dsw-alias-label-dimmed); cursor: default; }
|
|
657
|
+
.dsh-worktree-trigger { display: inline-flex; max-width: 220px; font-weight: 500; }
|
|
658
|
+
.dsh-worktree-trigger .dsh-plugin-composer-icon svg { width: 14px; height: 14px; }
|
|
422
659
|
.dsh-worktree-trigger:focus-visible { outline: 2px solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); outline-offset: 0; }
|
|
423
660
|
@container (width <= 460px) {
|
|
424
|
-
.dsh-worktree-
|
|
661
|
+
.dsh-worktree-trigger .dsh-plugin-composer-label { display: none; }
|
|
425
662
|
}
|
|
426
663
|
@media (prefers-reduced-motion: no-preference) {
|
|
427
|
-
.dsh-worktree-chevron { transition: transform .12s; }
|
|
664
|
+
.dsh-worktree-trigger .dsh-plugin-composer-chevron { transition: transform .12s; }
|
|
428
665
|
}
|
|
429
666
|
@media (hover: hover) {
|
|
430
|
-
.dsh-worktree-trigger:hover:not(:disabled), .dsh-worktree-
|
|
667
|
+
.dsh-worktree-trigger:hover:not(:disabled), .dsh-worktree-dialog button:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
|
|
431
668
|
}
|
|
432
|
-
.dsh-worktree-error-trigger { flex: 0 0 auto; min-height: 28px; padding: 2px 6px; border: 0; border-radius: var(--dsw-radius-sm); background: transparent; color: var(--dsw-alias-label-secondary); font: inherit; font-size: 12px; line-height: 20px; white-space: nowrap; text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
|
|
433
|
-
.dsh-worktree-error-trigger:focus-visible { outline: 2px solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); outline-offset: 0; }
|
|
434
669
|
.dsh-worktree-dialog.dsh-worktree-error-dialog { inset: 0; margin: auto; }
|
|
435
670
|
.dsh-worktree-error-dialog dl { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 8px 12px; }
|
|
436
671
|
.dsh-worktree-error-dialog dt { color: var(--dsw-alias-label-secondary); }
|
|
@@ -442,36 +677,39 @@ const styles = `
|
|
|
442
677
|
.dsh-worktree-dialog p { margin: 10px 0; color: var(--dsw-alias-label-secondary); }
|
|
443
678
|
.dsh-worktree-dialog button { border: 1px solid var(--dsw-alias-border-l2); border-radius: var(--dsw-radius-sm); background: transparent; color: inherit; padding: 7px 10px; cursor: pointer; font: inherit; }
|
|
444
679
|
.dsh-worktree-dialog header button { display: inline-flex; align-items: center; justify-content: center; padding: 0; width: 28px; height: 28px; border: 0; }
|
|
445
|
-
.dsh-worktree-dialog form > label { display: block; font-weight: 500; margin: 14px 0 6px; }
|
|
446
|
-
.dsh-worktree-dialog input[type=text] { box-sizing: border-box; width: 100%; padding: 9px 10px; border: 1px solid var(--dsw-alias-border-l2); border-radius: var(--dsw-radius-sm); background: var(--dsw-alias-bg-layer-1); color: inherit; font: inherit; }
|
|
447
|
-
.dsh-worktree-dialog .dsh-worktree-hint { font-size: 12px; }
|
|
448
680
|
.dsh-worktree-dialog footer { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; margin-top: 16px; }
|
|
449
|
-
.dsh-worktree-dialog .dsh-worktree-primary, .dsh-worktree-dialog .dsh-worktree-primary:hover:not(:disabled) { background: var(--dsw-alias-button-primary-fill); border-color: transparent; color: var(--dsw-alias-label-primary-foreground); }
|
|
450
|
-
.dsh-worktree-dialog [role=alert] { color: var(--dsw-alias-state-error-primary); }
|
|
451
681
|
.dsh-worktree-dialog button:disabled { opacity: .45; cursor: not-allowed; }
|
|
452
|
-
.dsh-worktree-dialog button:focus-visible
|
|
682
|
+
.dsh-worktree-dialog button:focus-visible { outline: 2px solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); outline-offset: 2px; }
|
|
453
683
|
.dsh-worktree-sr { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
|
|
684
|
+
.dsh-worktree-progress { margin: 12px 0 20px; color: var(--dsw-alias-label-secondary); font: inherit; font-size: var(--dsh-content-font-size-secondary, 13px); line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px)); }
|
|
685
|
+
.dsh-worktree-progress > p { margin: 0 0 8px; overflow-wrap: anywhere; }
|
|
686
|
+
.dsh-worktree-progress ol { list-style: none; margin: 0; padding: 8px 0 0; border-top: 1px solid var(--dsw-alias-border-l2); }
|
|
687
|
+
.dsh-worktree-progress li { display: flex; align-items: center; gap: 10px; padding: 3px 4px; }
|
|
688
|
+
.dsh-worktree-progress li[data-state=pending] { opacity: .4; }
|
|
689
|
+
.dsh-worktree-progress-mark { display: inline-flex; flex: none; align-items: center; justify-content: center; width: calc(16px + var(--dsh-content-font-delta, 0px)); height: calc(16px + var(--dsh-content-font-delta, 0px)); }
|
|
690
|
+
.dsh-worktree-progress-mark svg { width: calc(14px + var(--dsh-content-font-delta, 0px)); height: calc(14px + var(--dsh-content-font-delta, 0px)); }
|
|
691
|
+
.dsh-worktree-progress li[data-state=failed], .dsh-worktree-progress [role=alert] { color: var(--dsw-alias-state-error-primary); }
|
|
692
|
+
.dsh-worktree-progress [role=alert] { margin-top: 10px; font: inherit; }
|
|
693
|
+
.dsh-worktree-progress-time { margin-inline-start: auto; padding-inline-start: 8px; font-size: calc(var(--dsh-content-font-size-secondary, 13px) - 1px); font-variant-numeric: tabular-nums; }
|
|
694
|
+
@keyframes dsh-worktree-spin { to { transform: rotate(360deg); } }
|
|
695
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
696
|
+
.dsh-worktree-progress li[data-state=running] .dsh-worktree-progress-mark { animation: dsh-worktree-spin .8s linear infinite; }
|
|
697
|
+
}
|
|
454
698
|
.dsh-worktree-settings { width: 100%; color: var(--dsw-alias-label-primary); font: inherit; }
|
|
455
699
|
.dsh-worktree-settings fieldset { min-width: 0; padding: 0; margin: 0; border: 0; }
|
|
456
|
-
.dsh-worktree-settings-row {
|
|
457
|
-
.dsh-worktree-settings-row:last-child { border-bottom: 0; }
|
|
458
|
-
.dsh-worktree-settings-text { flex: 1; min-width: 0; }
|
|
700
|
+
.dsh-worktree-settings .dsh-plugin-settings-row { gap: 24px; }
|
|
701
|
+
.dsh-worktree-settings .dsh-plugin-settings-row:last-child { border-bottom: 0; }
|
|
459
702
|
.dsh-worktree-settings label, .dsh-worktree-settings-title { display: block; font-size: 14px; font-weight: 400; line-height: 20px; }
|
|
460
|
-
.dsh-worktree-settings-text p { margin: 4px 0 0; color: var(--dsw-alias-label-secondary); font-size: 12px; line-height: 18px; overflow-wrap: anywhere; }
|
|
703
|
+
.dsh-worktree-settings .dsh-plugin-settings-text p { margin: 4px 0 0; color: var(--dsw-alias-label-secondary); font-size: 12px; line-height: 18px; overflow-wrap: anywhere; }
|
|
461
704
|
.dsh-worktree-settings-input { box-sizing: border-box; width: 220px; max-width: 100%; flex: 0 1 220px; min-width: 0; }
|
|
462
|
-
.dsh-worktree-settings-
|
|
463
|
-
.dsh-worktree-settings-selector
|
|
464
|
-
.dsh-worktree-settings-selector svg { flex: none; }
|
|
465
|
-
.dsh-worktree-settings-selector:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
|
|
466
|
-
.dsh-worktree-settings-selector:disabled { opacity: .5; cursor: default; }
|
|
467
|
-
.dsh-worktree-settings-selector:focus-visible { outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); outline-offset: 2px; }
|
|
705
|
+
.dsh-worktree-settings-picker .dsh-plugin-settings-selector { max-width: 220px; color: var(--dsw-alias-label-primary); font-size: 14px; line-height: 22px; }
|
|
706
|
+
.dsh-worktree-settings-picker .dsh-plugin-settings-selector:focus-visible { outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); }
|
|
468
707
|
.dsh-worktree-settings [role=status], .dsh-worktree-settings [role=alert] { margin: 12px 0; font-size: 12px; line-height: 18px; }
|
|
469
708
|
.dsh-worktree-settings [role=status]:empty { margin: 0; }
|
|
470
|
-
.dsh-worktree-settings footer { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; margin-top: 16px; }
|
|
471
709
|
.dsh-worktree-settings [role=alert] { color: var(--dsw-alias-state-error-primary); }
|
|
472
710
|
@media (max-width: 600px) {
|
|
473
|
-
.dsh-worktree-settings-row { flex-wrap: wrap; gap: 12px; }
|
|
474
|
-
.dsh-worktree-settings-text { flex-basis: 160px; }
|
|
711
|
+
.dsh-worktree-settings .dsh-plugin-settings-row { flex-wrap: wrap; gap: 12px; }
|
|
712
|
+
.dsh-worktree-settings .dsh-plugin-settings-text { flex-basis: 160px; }
|
|
475
713
|
.dsh-worktree-settings-input { flex-grow: 1; }
|
|
476
714
|
}
|
|
477
715
|
`;
|
|
@@ -512,22 +750,167 @@ function installSidebarLabel(sessionId, name, path) {
|
|
|
512
750
|
for (const label of labels.values()) label.remove();
|
|
513
751
|
};
|
|
514
752
|
}
|
|
753
|
+
// src/progress.tsx
|
|
754
|
+
const labels = {
|
|
755
|
+
ko: {
|
|
756
|
+
running: "워크트리 준비 중…",
|
|
757
|
+
completed: "워크트리 준비 완료",
|
|
758
|
+
failed: "워크트리 생성 실패",
|
|
759
|
+
cancelled: "워크트리 준비 중단",
|
|
760
|
+
steps: [
|
|
761
|
+
"브랜치 이름 정하기",
|
|
762
|
+
"기준 커밋 및 경로 확인",
|
|
763
|
+
"파일 체크아웃",
|
|
764
|
+
"작업 경로 전환"
|
|
765
|
+
],
|
|
766
|
+
failure: "작업을 시작하지 않았습니다. 워크트리를 선택한 뒤 프롬프트를 다시 전송하세요."
|
|
767
|
+
},
|
|
768
|
+
en: {
|
|
769
|
+
running: "Setting up worktree…",
|
|
770
|
+
completed: "Worktree ready",
|
|
771
|
+
failed: "Worktree creation failed",
|
|
772
|
+
cancelled: "Worktree setup stopped",
|
|
773
|
+
steps: [
|
|
774
|
+
"Name branch",
|
|
775
|
+
"Check base commit and path",
|
|
776
|
+
"Check out files",
|
|
777
|
+
"Switch working directory"
|
|
778
|
+
],
|
|
779
|
+
failure: "Work did not start. Select a worktree, then resend your prompt."
|
|
780
|
+
},
|
|
781
|
+
zh: {
|
|
782
|
+
running: "正在准备工作树…",
|
|
783
|
+
completed: "工作树已就绪",
|
|
784
|
+
failed: "工作树创建失败",
|
|
785
|
+
cancelled: "工作树准备已停止",
|
|
786
|
+
steps: [
|
|
787
|
+
"生成分支名称",
|
|
788
|
+
"检查基准提交和路径",
|
|
789
|
+
"检出文件",
|
|
790
|
+
"切换工作目录"
|
|
791
|
+
],
|
|
792
|
+
failure: "尚未开始任务。请选择工作树,然后重新发送提示。"
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
const progressDefinition = {
|
|
796
|
+
kind: "dungsil-worktree-progress",
|
|
797
|
+
target: "chat",
|
|
798
|
+
match: (event) => {
|
|
799
|
+
const record = event;
|
|
800
|
+
if (record.type === "plugin:dungsil/ui-worktree/progress" && isWorktreeProgress(record.data)) return {
|
|
801
|
+
id: record.data.id,
|
|
802
|
+
role: "start"
|
|
803
|
+
};
|
|
804
|
+
if (event.type === "user/message") return {
|
|
805
|
+
id: event.data.id,
|
|
806
|
+
role: "update"
|
|
807
|
+
};
|
|
808
|
+
return null;
|
|
809
|
+
},
|
|
810
|
+
start: (_context, match) => ({
|
|
811
|
+
progress: match.event.data,
|
|
812
|
+
anchorSeq: match.event.seq
|
|
813
|
+
}),
|
|
814
|
+
update: (context, match) => match.event.type === "user/message" ? {
|
|
815
|
+
...context.state,
|
|
816
|
+
anchorSeq: match.event.seq + .1
|
|
817
|
+
} : {
|
|
818
|
+
...context.state,
|
|
819
|
+
progress: match.event.data
|
|
820
|
+
},
|
|
821
|
+
buildViewNode: (context) => {
|
|
822
|
+
if (!context.state || !context.start) return null;
|
|
823
|
+
return {
|
|
824
|
+
key: context.key,
|
|
825
|
+
id: context.id,
|
|
826
|
+
kind: "dungsil-worktree-progress",
|
|
827
|
+
target: "chat",
|
|
828
|
+
anchorSeq: context.state.anchorSeq,
|
|
829
|
+
location: context.start.location,
|
|
830
|
+
visibility: "visible",
|
|
831
|
+
groupTree: context.state.progress.status === "completed" ? void 0 : "turn",
|
|
832
|
+
data: context.state.progress
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
function installProgress(ctx) {
|
|
837
|
+
ctx.uiConversation.events.register(progressDefinition);
|
|
838
|
+
ctx.slots.inject("conversation.chat.node", () => ctx.slots.register({
|
|
839
|
+
name: "conversation.chat.node",
|
|
840
|
+
key: "dungsil-worktree-progress",
|
|
841
|
+
inject: () => ({})
|
|
842
|
+
}, function Progress({ node }) {
|
|
843
|
+
const { active: language } = useLocaleSnapshot(ctx.locale);
|
|
844
|
+
const text = labels[language.startsWith("ko") ? "ko" : language.startsWith("zh") ? "zh" : "en"];
|
|
845
|
+
const progress = node.data;
|
|
846
|
+
const ended = node.location.kind === "turn" || node.location.kind === "step" ? node.location.turn.status === "closed" : false;
|
|
847
|
+
const status = progress.status === "running" && ended ? "cancelled" : progress.status;
|
|
848
|
+
const [now, setNow] = (0, react.useState)(Date.now);
|
|
849
|
+
(0, react.useEffect)(() => {
|
|
850
|
+
if (status !== "running") return;
|
|
851
|
+
const timer = window.setInterval(() => setNow(Date.now()), 100);
|
|
852
|
+
return () => window.clearInterval(timer);
|
|
853
|
+
}, [status]);
|
|
854
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
855
|
+
className: "dsh-worktree-progress",
|
|
856
|
+
"aria-label": text[status],
|
|
857
|
+
"aria-busy": status === "running",
|
|
858
|
+
children: [
|
|
859
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
860
|
+
role: "status",
|
|
861
|
+
children: [text[status], status === "completed" && progress.branch ? ` · ${progress.branch}` : ""]
|
|
862
|
+
}),
|
|
863
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", { children: text.steps.map((label, index) => {
|
|
864
|
+
const state = index < progress.step || status === "completed" ? "completed" : index === progress.step ? status : "pending";
|
|
865
|
+
const duration = progress.durations[index] + (state === "running" ? Math.max(0, now - progress.updatedAt) : 0);
|
|
866
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
867
|
+
"data-state": state,
|
|
868
|
+
"aria-current": state === "running" ? "step" : void 0,
|
|
869
|
+
children: [
|
|
870
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
871
|
+
className: "dsh-worktree-progress-mark",
|
|
872
|
+
"aria-hidden": "true",
|
|
873
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(state === "completed" ? _deepseek_ai_dsh_client_ui_primitives.IconCheckCircleOutlineRegular : state === "failed" ? _deepseek_ai_dsh_client_ui_primitives.IconCloseOutlineRegular : state === "cancelled" ? _deepseek_ai_dsh_client_ui_primitives.IconStopFillRegular : state === "running" ? _deepseek_ai_dsh_client_ui_primitives.IconLoadingOutlineRegular : _deepseek_ai_dsh_client_ui_primitives.IconClockOutlineRegular, { size: 14 })
|
|
874
|
+
}),
|
|
875
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }),
|
|
876
|
+
state !== "pending" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
877
|
+
className: "dsh-worktree-progress-time",
|
|
878
|
+
children: duration < 1e3 ? `${Math.floor(duration)}ms` : `${(duration / 1e3).toFixed(1)}s`
|
|
879
|
+
})
|
|
880
|
+
]
|
|
881
|
+
}, label);
|
|
882
|
+
}) }),
|
|
883
|
+
(status === "failed" || status === "cancelled") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
884
|
+
role: "alert",
|
|
885
|
+
children: text.failure
|
|
886
|
+
})
|
|
887
|
+
]
|
|
888
|
+
});
|
|
889
|
+
}));
|
|
890
|
+
}
|
|
515
891
|
// src/client.tsx
|
|
516
892
|
const name = "dungsil-ui-worktree";
|
|
517
893
|
const inject = [
|
|
518
894
|
"slots",
|
|
519
895
|
"locale",
|
|
520
|
-
"connection"
|
|
896
|
+
"connection",
|
|
897
|
+
"uiConversation"
|
|
521
898
|
];
|
|
522
899
|
function apply(ctx) {
|
|
900
|
+
ctx.effect(() => installSettingsIcon(name, [
|
|
901
|
+
"en",
|
|
902
|
+
"ko",
|
|
903
|
+
"zh"
|
|
904
|
+
].map(settingsLabel), git_branch_default));
|
|
523
905
|
const connection = ctx.get("connection");
|
|
524
|
-
|
|
906
|
+
installProgress(ctx);
|
|
525
907
|
function Picker({ sessionId, useSession }) {
|
|
526
|
-
const language = (
|
|
527
|
-
const text = copy$1[language.startsWith("ko") ? "ko" : "en"];
|
|
908
|
+
const { active: language } = useLocaleSnapshot(ctx.locale);
|
|
909
|
+
const text = copy$1[language.startsWith("ko") ? "ko" : language.startsWith("zh") ? "zh" : "en"];
|
|
528
910
|
const unavailable = useSession((s) => s.running || s.removed || s.openState !== "open" || s.pendingSubmissions.length > 0);
|
|
911
|
+
const running = useSession((s) => s.running);
|
|
912
|
+
const wasRunning = (0, react.useRef)(running);
|
|
529
913
|
const [view, setView] = (0, react.useState)();
|
|
530
|
-
const [value, setValue] = (0, react.useState)("");
|
|
531
914
|
const [loading, setLoading] = (0, react.useState)(true);
|
|
532
915
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
533
916
|
const [error, setError] = (0, react.useState)();
|
|
@@ -537,16 +920,18 @@ function apply(ctx) {
|
|
|
537
920
|
const needsRefresh = (0, react.useRef)(false);
|
|
538
921
|
const lifetime = (0, react.useRef)(null);
|
|
539
922
|
const trigger = (0, react.useRef)(null);
|
|
540
|
-
const dialog = (0, react.useRef)(null);
|
|
541
923
|
const errorDialog = (0, react.useRef)(null);
|
|
542
|
-
const errorTrigger = (0, react.useRef)(null);
|
|
543
|
-
const title = (0, react.useId)();
|
|
544
924
|
const errorTitle = (0, react.useId)();
|
|
545
925
|
const errorDescription = (0, react.useId)();
|
|
546
|
-
const field = (0, react.useId)();
|
|
547
926
|
const status = (0, react.useId)();
|
|
548
927
|
const disabled = unavailable || loading || busy || !!error || !view;
|
|
549
|
-
const
|
|
928
|
+
const creation = view?.pendingCreation;
|
|
929
|
+
const detailError = error ?? (creation?.failed ? {
|
|
930
|
+
operation: "create",
|
|
931
|
+
code: "operation_failed",
|
|
932
|
+
uncertain: true
|
|
933
|
+
} : void 0);
|
|
934
|
+
const errorLabel = detailError?.uncertain ? text.mutationError : text.lookupError;
|
|
550
935
|
const recordFailure = (operation, cause) => {
|
|
551
936
|
const code = failureCode(cause);
|
|
552
937
|
needsRefresh.current = true;
|
|
@@ -598,7 +983,6 @@ function apply(ctx) {
|
|
|
598
983
|
};
|
|
599
984
|
const resize = () => {
|
|
600
985
|
setOpen(false);
|
|
601
|
-
dialog.current?.close();
|
|
602
986
|
};
|
|
603
987
|
window.addEventListener("focus", focus);
|
|
604
988
|
window.addEventListener("resize", resize);
|
|
@@ -608,19 +992,13 @@ function apply(ctx) {
|
|
|
608
992
|
window.removeEventListener("resize", resize);
|
|
609
993
|
};
|
|
610
994
|
}, []);
|
|
995
|
+
(0, react.useEffect)(() => {
|
|
996
|
+
if (wasRunning.current && !running) load();
|
|
997
|
+
wasRunning.current = running;
|
|
998
|
+
}, [running]);
|
|
611
999
|
(0, react.useEffect)(() => {
|
|
612
1000
|
if (disabled) setOpen(false);
|
|
613
1001
|
}, [disabled]);
|
|
614
|
-
const show = () => {
|
|
615
|
-
const element = dialog.current;
|
|
616
|
-
const button = trigger.current;
|
|
617
|
-
if (disabled || pending.current || !element || !button) return;
|
|
618
|
-
element.showModal();
|
|
619
|
-
const anchor = button.getBoundingClientRect();
|
|
620
|
-
const box = element.getBoundingClientRect();
|
|
621
|
-
element.style.left = `${Math.max(8, Math.min(anchor.left, window.innerWidth - box.width - 8))}px`;
|
|
622
|
-
element.style.top = `${Math.max(8, anchor.top - box.height - 8)}px`;
|
|
623
|
-
};
|
|
624
1002
|
const change = async (endpoint, path) => {
|
|
625
1003
|
const controller = lifetime.current;
|
|
626
1004
|
if (disabled || pending.current || needsRefresh.current || !view || !controller || controller.signal.aborted) return;
|
|
@@ -630,8 +1008,7 @@ function apply(ctx) {
|
|
|
630
1008
|
try {
|
|
631
1009
|
const payload = endpoint === "create" ? {
|
|
632
1010
|
sessionId,
|
|
633
|
-
cwd: view.cwd
|
|
634
|
-
...value.trim() ? { name: value.trim() } : {}
|
|
1011
|
+
cwd: view.cwd
|
|
635
1012
|
} : {
|
|
636
1013
|
sessionId,
|
|
637
1014
|
cwd: view.cwd,
|
|
@@ -641,10 +1018,6 @@ function apply(ctx) {
|
|
|
641
1018
|
if (controller.signal.aborted) return;
|
|
642
1019
|
setView(nextView);
|
|
643
1020
|
setChanged(true);
|
|
644
|
-
if (endpoint === "create") {
|
|
645
|
-
setValue("");
|
|
646
|
-
dialog.current?.close();
|
|
647
|
-
}
|
|
648
1021
|
pending.current = false;
|
|
649
1022
|
await load();
|
|
650
1023
|
} catch (cause) {
|
|
@@ -657,13 +1030,13 @@ function apply(ctx) {
|
|
|
657
1030
|
}
|
|
658
1031
|
};
|
|
659
1032
|
(0, react.useEffect)(() => {
|
|
660
|
-
if (!busy && changed
|
|
1033
|
+
if (!busy && changed) trigger.current?.focus();
|
|
661
1034
|
}, [busy, changed]);
|
|
662
1035
|
(0, react.useEffect)(() => {
|
|
663
|
-
if (!
|
|
664
|
-
}, [error]);
|
|
1036
|
+
if (!detailError && errorDialog.current?.open) errorDialog.current.close();
|
|
1037
|
+
}, [error, creation?.failed]);
|
|
665
1038
|
const restoreErrorFocus = () => {
|
|
666
|
-
|
|
1039
|
+
trigger.current?.focus();
|
|
667
1040
|
};
|
|
668
1041
|
const current = currentWorktree(view);
|
|
669
1042
|
(0, react.useEffect)(() => {
|
|
@@ -674,79 +1047,58 @@ function apply(ctx) {
|
|
|
674
1047
|
current,
|
|
675
1048
|
view
|
|
676
1049
|
]);
|
|
677
|
-
const branch = current ? current.branch ?? text.detached : text.unknown;
|
|
678
|
-
const
|
|
1050
|
+
const branch = creation ? creation.failed ? text.mutationError : text.create : current ? current.branch ?? text.detached : text.unknown;
|
|
1051
|
+
const label = detailError ? errorLabel : view ? branch : loading ? text.loading : text.title;
|
|
1052
|
+
const notice = loading ? text.loading : busy ? text.busy : error ? errorLabel : unavailable ? text.wait : creation ? creation.failed ? text.creationError : text.reservationHint : changed ? text.changed : view?.worktrees.length === 0 ? text.empty : "";
|
|
679
1053
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
680
1054
|
className: "dsh-worktree",
|
|
681
1055
|
children: [
|
|
682
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
683
|
-
className: "dsh-worktree-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
if (
|
|
703
|
-
|
|
704
|
-
|
|
1056
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
1057
|
+
className: "dsh-worktree-picker",
|
|
1058
|
+
open,
|
|
1059
|
+
side: "top",
|
|
1060
|
+
portal: true,
|
|
1061
|
+
selectedId: creation ? "create" : current ? `path:${current.path}` : void 0,
|
|
1062
|
+
items: [{
|
|
1063
|
+
id: "create",
|
|
1064
|
+
label: text.create,
|
|
1065
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutlineRegular, {})
|
|
1066
|
+
}, ...view?.worktrees.map((item) => ({
|
|
1067
|
+
id: `path:${item.path}`,
|
|
1068
|
+
label: item.branch ?? text.detached,
|
|
1069
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutlineRegular, {})
|
|
1070
|
+
})) ?? []],
|
|
1071
|
+
onClose: () => setOpen(false),
|
|
1072
|
+
onSelect: (id) => {
|
|
1073
|
+
setOpen(false);
|
|
1074
|
+
if (disabled || pending.current) return;
|
|
1075
|
+
if (id === "create") {
|
|
1076
|
+
if (!creation || creation.failed) change("create");
|
|
1077
|
+
} else if (id.startsWith("path:") && (creation || id !== `path:${current?.path}`)) change("switch", id.slice(5));
|
|
1078
|
+
},
|
|
1079
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ComposerTrigger, {
|
|
1080
|
+
ref: trigger,
|
|
1081
|
+
className: "dsh-worktree-trigger",
|
|
1082
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutlineRegular, {}),
|
|
1083
|
+
label,
|
|
1084
|
+
disabled: detailError ? loading || busy : disabled,
|
|
1085
|
+
"aria-label": `${text.title}: ${label}`,
|
|
1086
|
+
"aria-haspopup": detailError ? "dialog" : "menu",
|
|
1087
|
+
"aria-expanded": detailError ? void 0 : open,
|
|
1088
|
+
"aria-describedby": status,
|
|
1089
|
+
"aria-busy": loading || busy,
|
|
1090
|
+
title: label,
|
|
1091
|
+
onClick: () => {
|
|
1092
|
+
if (detailError) errorDialog.current?.showModal();
|
|
1093
|
+
else setOpen((value) => !value);
|
|
705
1094
|
},
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
"aria-expanded": open,
|
|
714
|
-
"aria-describedby": status,
|
|
715
|
-
"aria-busy": loading || busy,
|
|
716
|
-
title: view ? branch : text.title,
|
|
717
|
-
onClick: () => setOpen((value) => !value),
|
|
718
|
-
onKeyDown: (event) => {
|
|
719
|
-
if (!open && (event.key === "ArrowDown" || event.key === "ArrowUp")) {
|
|
720
|
-
event.preventDefault();
|
|
721
|
-
setOpen(true);
|
|
722
|
-
}
|
|
723
|
-
},
|
|
724
|
-
children: [
|
|
725
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
726
|
-
className: "dsh-worktree-icon",
|
|
727
|
-
"aria-hidden": "true",
|
|
728
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutlineRegular, { className: "dsh-worktree-branch" })
|
|
729
|
-
}),
|
|
730
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
731
|
-
className: "dsh-worktree-name",
|
|
732
|
-
children: view ? branch : loading ? text.loading : text.title
|
|
733
|
-
}),
|
|
734
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
735
|
-
className: "dsh-worktree-chevron",
|
|
736
|
-
"aria-hidden": "true",
|
|
737
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {})
|
|
738
|
-
})
|
|
739
|
-
]
|
|
740
|
-
})
|
|
741
|
-
}), error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
742
|
-
ref: errorTrigger,
|
|
743
|
-
type: "button",
|
|
744
|
-
className: "dsh-worktree-error-trigger",
|
|
745
|
-
"aria-haspopup": "dialog",
|
|
746
|
-
"aria-label": `${errorLabel}: ${text.errorTitle}`,
|
|
747
|
-
onClick: () => errorDialog.current?.showModal(),
|
|
748
|
-
children: errorLabel
|
|
749
|
-
})]
|
|
1095
|
+
onKeyDown: (event) => {
|
|
1096
|
+
if (!detailError && !disabled && !open && (event.key === "ArrowDown" || event.key === "ArrowUp")) {
|
|
1097
|
+
event.preventDefault();
|
|
1098
|
+
setOpen(true);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
})
|
|
750
1102
|
}),
|
|
751
1103
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
752
1104
|
id: status,
|
|
@@ -754,82 +1106,6 @@ function apply(ctx) {
|
|
|
754
1106
|
role: "status",
|
|
755
1107
|
children: notice
|
|
756
1108
|
}),
|
|
757
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dialog", {
|
|
758
|
-
ref: dialog,
|
|
759
|
-
className: "dsh-worktree-dialog",
|
|
760
|
-
"aria-labelledby": title,
|
|
761
|
-
onClose: () => trigger.current?.focus(),
|
|
762
|
-
onCancel: (event) => {
|
|
763
|
-
if (pending.current) event.preventDefault();
|
|
764
|
-
},
|
|
765
|
-
onClick: (event) => {
|
|
766
|
-
const box = event.currentTarget.getBoundingClientRect();
|
|
767
|
-
const outside = event.clientX < box.left || event.clientX > box.right || event.clientY < box.top || event.clientY > box.bottom;
|
|
768
|
-
if (event.target === event.currentTarget && outside && !pending.current) event.currentTarget.close();
|
|
769
|
-
},
|
|
770
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
771
|
-
id: title,
|
|
772
|
-
children: text.createTitle
|
|
773
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
774
|
-
type: "button",
|
|
775
|
-
"aria-label": text.close,
|
|
776
|
-
disabled: busy,
|
|
777
|
-
onClick: () => dialog.current?.close(),
|
|
778
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutlineRegular, { "aria-hidden": "true" })
|
|
779
|
-
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
|
|
780
|
-
onSubmit: (event) => {
|
|
781
|
-
event.preventDefault();
|
|
782
|
-
change("create");
|
|
783
|
-
},
|
|
784
|
-
children: [
|
|
785
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
786
|
-
htmlFor: field,
|
|
787
|
-
children: text.branch
|
|
788
|
-
}),
|
|
789
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
790
|
-
id: field,
|
|
791
|
-
type: "text",
|
|
792
|
-
value,
|
|
793
|
-
disabled: busy,
|
|
794
|
-
maxLength: 200,
|
|
795
|
-
autoComplete: "off",
|
|
796
|
-
spellCheck: false,
|
|
797
|
-
placeholder: "fix-login",
|
|
798
|
-
onChange: (event) => setValue(event.target.value),
|
|
799
|
-
onKeyDown: (event) => {
|
|
800
|
-
if (event.key === "Enter" && (event.nativeEvent.isComposing || event.keyCode === 229)) event.preventDefault();
|
|
801
|
-
}
|
|
802
|
-
}),
|
|
803
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
804
|
-
className: "dsh-worktree-hint",
|
|
805
|
-
children: text.hint
|
|
806
|
-
}),
|
|
807
|
-
error && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
808
|
-
role: "alert",
|
|
809
|
-
children: [
|
|
810
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: error.uncertain ? text.error : text.readError }),
|
|
811
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: text.reasons[error.code] }),
|
|
812
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: error.code })
|
|
813
|
-
]
|
|
814
|
-
}),
|
|
815
|
-
unavailable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
816
|
-
role: "status",
|
|
817
|
-
children: text.wait
|
|
818
|
-
}),
|
|
819
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
820
|
-
type: "button",
|
|
821
|
-
disabled: loading || busy,
|
|
822
|
-
onClick: () => void load(true),
|
|
823
|
-
children: text.refresh
|
|
824
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
825
|
-
type: "submit",
|
|
826
|
-
className: "dsh-worktree-primary",
|
|
827
|
-
disabled,
|
|
828
|
-
children: busy ? text.busy : text.submit
|
|
829
|
-
})] })
|
|
830
|
-
]
|
|
831
|
-
})]
|
|
832
|
-
}),
|
|
833
1109
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dialog", {
|
|
834
1110
|
ref: errorDialog,
|
|
835
1111
|
className: "dsh-worktree-dialog dsh-worktree-error-dialog",
|
|
@@ -848,13 +1124,13 @@ function apply(ctx) {
|
|
|
848
1124
|
})] }),
|
|
849
1125
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
850
1126
|
id: errorDescription,
|
|
851
|
-
children: error?.uncertain ? text.error : text.readError
|
|
1127
|
+
children: creation?.failed ? text.creationError : error?.uncertain ? text.error : text.readError
|
|
852
1128
|
}),
|
|
853
|
-
|
|
1129
|
+
detailError && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: text.reasons[detailError.code] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", { children: [
|
|
854
1130
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: text.errorOperation }),
|
|
855
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: text.operations[
|
|
1131
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: text.operations[detailError.operation] }),
|
|
856
1132
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: text.errorCode }),
|
|
857
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children:
|
|
1133
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: detailError.code }) })
|
|
858
1134
|
] })] }),
|
|
859
1135
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("footer", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
860
1136
|
type: "button",
|
|
@@ -880,12 +1156,7 @@ function apply(ctx) {
|
|
|
880
1156
|
label: () => settingsLabel(ctx.locale.getSnapshot().active),
|
|
881
1157
|
inject: () => ({ ctx })
|
|
882
1158
|
}, WorktreeSettingsSection));
|
|
883
|
-
ctx.effect(() =>
|
|
884
|
-
const style = document.createElement("style");
|
|
885
|
-
style.textContent = styles;
|
|
886
|
-
document.head.append(style);
|
|
887
|
-
return () => style.remove();
|
|
888
|
-
}, "ui-worktree: composer styles");
|
|
1159
|
+
ctx.effect(() => installStyles(styles), "ui-worktree: composer styles");
|
|
889
1160
|
}
|
|
890
1161
|
exports.apply = apply;
|
|
891
1162
|
exports.inject = inject;
|