@civilization/dsh-workspace-files 0.2.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/lib/client.js ADDED
@@ -0,0 +1,1633 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@civilization/dsh-workspace-files",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ let react_jsx_runtime = require("react/jsx-runtime");
9
+ //#region src/client/icons.tsx
10
+ function IconFolder({ size = 15, open = false }) {
11
+ if (open) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
12
+ width: size,
13
+ height: size,
14
+ viewBox: "0 0 16 16",
15
+ fill: "none",
16
+ stroke: "currentColor",
17
+ strokeWidth: "1.2",
18
+ strokeLinecap: "round",
19
+ strokeLinejoin: "round",
20
+ style: {
21
+ display: "inline-block",
22
+ verticalAlign: "middle",
23
+ flexShrink: 0,
24
+ color: "var(--dsw-alias-brand-primary, #4b70e2)"
25
+ },
26
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
27
+ d: "M1.5 3.5A1.5 1.5 0 013 2h3l1.5 2H13a1.5 1.5 0 011.5 1.5v1H2.5l-1 7.5A1.5 1.5 0 003 15h10.5a1.5 1.5 0 001.5-1.5L14 6.5H2.5",
28
+ fill: "currentColor",
29
+ fillOpacity: "0.18"
30
+ })
31
+ });
32
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
33
+ width: size,
34
+ height: size,
35
+ viewBox: "0 0 16 16",
36
+ fill: "currentColor",
37
+ style: {
38
+ display: "inline-block",
39
+ verticalAlign: "middle",
40
+ flexShrink: 0,
41
+ color: "var(--dsw-alias-brand-primary, #4b70e2)"
42
+ },
43
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M1.75 1A1.75 1.75 0 000 2.75v10.5C0 14.216.784 15 1.75 15h12.5A1.75 1.75 0 0016 13.25v-8.5A1.75 1.75 0 0014.25 3H7.5a.25.25 0 01-.2-.1l-.9-1.2C6.07 1.26 5.55 1 5 1H1.75z" })
44
+ });
45
+ }
46
+ function IconSearch({ size = 13 }) {
47
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
48
+ width: size,
49
+ height: size,
50
+ viewBox: "0 0 16 16",
51
+ fill: "none",
52
+ stroke: "currentColor",
53
+ strokeWidth: "1.5",
54
+ strokeLinecap: "round",
55
+ strokeLinejoin: "round",
56
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
57
+ cx: "7",
58
+ cy: "7",
59
+ r: "5"
60
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
61
+ x1: "11",
62
+ y1: "11",
63
+ x2: "14.5",
64
+ y2: "14.5"
65
+ })]
66
+ });
67
+ }
68
+ function IconAt({ size = 13 }) {
69
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
70
+ width: size,
71
+ height: size,
72
+ viewBox: "0 0 16 16",
73
+ fill: "currentColor",
74
+ style: {
75
+ display: "inline-block",
76
+ verticalAlign: "middle",
77
+ flexShrink: 0
78
+ },
79
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
80
+ fillRule: "evenodd",
81
+ d: "M8 1a7 7 0 106.914 8.093.75.75 0 00-1.488-.186A5.5 5.5 0 1113.5 8c0 .64-.207 1.2-.6 1.597-.39.395-.94.653-1.65.653-.61 0-1.156-.25-1.5-.67-.344.42-.89.67-1.5.67A2.25 2.25 0 016 8a2.25 2.25 0 012.25-2.25c.61 0 1.156.25 1.5.67.344-.42.89-.67 1.5-.67.71 0 1.26.258 1.65.653.393.397.6 1.057.6 1.847A7.001 7.001 0 018 1zm0 5.75A1.25 1.25 0 108 9.25 1.25 1.25 0 008 6.75z"
82
+ })
83
+ });
84
+ }
85
+ function IconCopy({ size = 13 }) {
86
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
87
+ width: size,
88
+ height: size,
89
+ viewBox: "0 0 16 16",
90
+ fill: "currentColor",
91
+ style: {
92
+ display: "inline-block",
93
+ verticalAlign: "middle",
94
+ flexShrink: 0
95
+ },
96
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25v-7.5z" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0114.25 11h-7.5A1.75 1.75 0 015 9.25v-7.5zm1.75-.25a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25h-7.5z" })]
97
+ });
98
+ }
99
+ function IconExpandAll({ size = 13 }) {
100
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
101
+ width: size,
102
+ height: size,
103
+ viewBox: "0 0 16 16",
104
+ fill: "none",
105
+ stroke: "currentColor",
106
+ strokeWidth: "1.4",
107
+ strokeLinecap: "round",
108
+ strokeLinejoin: "round",
109
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("polyline", { points: "4 2 8 6 12 2" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("polyline", { points: "4 8 8 12 12 8" })]
110
+ });
111
+ }
112
+ function IconCollapseAll({ size = 13 }) {
113
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
114
+ width: size,
115
+ height: size,
116
+ viewBox: "0 0 16 16",
117
+ fill: "none",
118
+ stroke: "currentColor",
119
+ strokeWidth: "1.4",
120
+ strokeLinecap: "round",
121
+ strokeLinejoin: "round",
122
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("polyline", { points: "4 6 8 2 12 6" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("polyline", { points: "4 12 8 8 12 8" })]
123
+ });
124
+ }
125
+ function IconRefresh({ size = 13 }) {
126
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
127
+ width: size,
128
+ height: size,
129
+ viewBox: "0 0 16 16",
130
+ fill: "currentColor",
131
+ style: {
132
+ display: "inline-block",
133
+ verticalAlign: "middle",
134
+ flexShrink: 0
135
+ },
136
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
137
+ fillRule: "evenodd",
138
+ d: "M8 2.5a5.5 5.5 0 105.006 3.238.75.75 0 011.378-.592A7 7 0 118 1v1.5a.75.75 0 01-1.28.53L4.47 1.28a.75.75 0 010-1.06l2.25-1.75A.75.75 0 018-.75V1a7 7 0 010 1.5z"
139
+ })
140
+ });
141
+ }
142
+ function IconClose({ size = 12 }) {
143
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
144
+ width: size,
145
+ height: size,
146
+ viewBox: "0 0 16 16",
147
+ fill: "currentColor",
148
+ style: {
149
+ display: "inline-block",
150
+ verticalAlign: "middle",
151
+ flexShrink: 0
152
+ },
153
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M3.72 3.72a.75.75 0 011.06 0L8 6.94l3.22-3.22a.75.75 0 111.06 1.06L9.06 8l3.22 3.22a.75.75 0 11-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 01-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 010-1.06z" })
154
+ });
155
+ }
156
+ function IconCheck({ size = 12 }) {
157
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
158
+ width: size,
159
+ height: size,
160
+ viewBox: "0 0 16 16",
161
+ fill: "currentColor",
162
+ style: {
163
+ display: "inline-block",
164
+ verticalAlign: "middle",
165
+ flexShrink: 0
166
+ },
167
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
168
+ fillRule: "evenodd",
169
+ d: "M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"
170
+ })
171
+ });
172
+ }
173
+ function FileGlyphIcon({ filename, size = 14 }) {
174
+ const ext = filename.split(".").pop()?.toLowerCase() || "";
175
+ let color = "var(--dsw-alias-label-tertiary, #8c9ba5)";
176
+ let badge = "";
177
+ if (ext === "ts" || ext === "tsx") {
178
+ color = "#3178c6";
179
+ badge = ext === "tsx" ? "TSX" : "TS";
180
+ } else if (ext === "js" || ext === "jsx" || ext === "mjs" || ext === "cjs") {
181
+ color = "#f7df1e";
182
+ badge = "JS";
183
+ } else if (ext === "json") {
184
+ color = "#cbcb41";
185
+ badge = "{}";
186
+ } else if (ext === "md" || ext === "markdown") {
187
+ color = "#42a5f5";
188
+ badge = "M↓";
189
+ } else if (ext === "drawio" || ext === "dio") {
190
+ color = "#f08705";
191
+ badge = "D";
192
+ } else if (ext === "css" || ext === "scss" || ext === "less") {
193
+ color = "#42a5f5";
194
+ badge = "#";
195
+ } else if (ext === "html" || ext === "htm") {
196
+ color = "#e44d26";
197
+ badge = "<>";
198
+ } else if (ext === "py") {
199
+ color = "#3776ab";
200
+ badge = "PY";
201
+ } else if (ext === "png" || ext === "jpg" || ext === "jpeg" || ext === "svg" || ext === "gif" || ext === "webp") {
202
+ color = "#26a69a";
203
+ badge = "IMG";
204
+ } else if (ext === "yml" || ext === "yaml") {
205
+ color = "#cb171e";
206
+ badge = "Y";
207
+ }
208
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
209
+ style: {
210
+ display: "inline-flex",
211
+ alignItems: "center",
212
+ justifyContent: "center",
213
+ width: size,
214
+ height: size,
215
+ fontSize: 9,
216
+ fontWeight: 700,
217
+ fontFamily: "monospace",
218
+ color,
219
+ flexShrink: 0
220
+ },
221
+ title: filename,
222
+ children: badge || "•"
223
+ });
224
+ }
225
+ function IconList({ size = 13 }) {
226
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
227
+ width: size,
228
+ height: size,
229
+ viewBox: "0 0 16 16",
230
+ fill: "currentColor",
231
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
232
+ fillRule: "evenodd",
233
+ d: "M2.5 12a.5.5 0 01.5-.5h10a.5.5 0 010 1H3a.5.5 0 01-.5-.5zm0-4a.5.5 0 01.5-.5h10a.5.5 0 010 1H3a.5.5 0 01-.5-.5zm0-4a.5.5 0 01.5-.5h10a.5.5 0 010 1H3a.5.5 0 01-.5-.5z"
234
+ })
235
+ });
236
+ }
237
+ function IconTree({ size = 13 }) {
238
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
239
+ width: size,
240
+ height: size,
241
+ viewBox: "0 0 16 16",
242
+ fill: "none",
243
+ stroke: "currentColor",
244
+ strokeWidth: "1.3",
245
+ strokeLinecap: "round",
246
+ strokeLinejoin: "round",
247
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2 3h4v4H2zM10 9h4v4h-4zM4 7v6h6" })
248
+ });
249
+ }
250
+ //#endregion
251
+ //#region src/client/index.tsx
252
+ const inject = ["slots", "sidebarRightTabs"];
253
+ let globalCtx = null;
254
+ const OFFICIAL_FILES_TAB_ID = "@civilization/dsh-workspace-files";
255
+ function FolderGuideGlyph({ size = 20, className }) {
256
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
257
+ className,
258
+ style: {
259
+ display: "inline-flex",
260
+ alignItems: "center",
261
+ justifyContent: "center",
262
+ flexShrink: 0
263
+ },
264
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconFolder, { size })
265
+ });
266
+ }
267
+ function WorkspaceFilesTabTitle({ useTabInfo }) {
268
+ const title = (useTabInfo ? useTabInfo() : null)?.tab?.title || "文件";
269
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconFolder, { size: 15 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: title })] });
270
+ }
271
+ function WorkspaceFilesTabBody(props) {
272
+ const { sessionId, useSessions, useTabInfo } = props;
273
+ const tabActions = (useTabInfo ? useTabInfo() : null)?.tab?.actions;
274
+ const sessionCwd = useSessions ? useSessions((sessions) => sessions?.byId?.[sessionId]?.cwd) : void 0;
275
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceFilesView, {
276
+ sessionId,
277
+ cwd: sessionCwd,
278
+ onOpenFile: (filePath) => {
279
+ if (tabActions?.openResource && sessionId) {
280
+ const normalized = filePath.replace(/\\/g, "/").replace(/^\.?\//, "");
281
+ tabActions.openResource(`dsh-resource://file/session/${encodeURIComponent(sessionId)}/${encodeURIComponent(normalized)}`);
282
+ }
283
+ }
284
+ });
285
+ }
286
+ function apply(ctx) {
287
+ globalCtx = ctx;
288
+ const sidebarRightTabs = ctx.sidebarRightTabs;
289
+ const slots = ctx.slots;
290
+ if (sidebarRightTabs && slots) {
291
+ ctx.effect(() => sidebarRightTabs.register({
292
+ id: OFFICIAL_FILES_TAB_ID,
293
+ kind: "files",
294
+ priority: "extension",
295
+ title: () => "文件",
296
+ guide: [{
297
+ order: 10,
298
+ title: () => "文件",
299
+ description: () => "浏览、搜索与一键引用项目文件",
300
+ icon: FolderGuideGlyph
301
+ }]
302
+ }), "dsh-workspace-files: override official files tab");
303
+ ctx.effect(() => slots.inject("sidebar.right.pane.tab", () => slots.register({
304
+ name: "sidebar.right.pane.tab",
305
+ key: OFFICIAL_FILES_TAB_ID
306
+ }, WorkspaceFilesTabBody)), "dsh-workspace-files: official tab body");
307
+ ctx.effect(() => slots.inject("sidebar.right.pane.tab.title", () => slots.register({
308
+ name: "sidebar.right.pane.tab.title",
309
+ key: OFFICIAL_FILES_TAB_ID
310
+ }, WorkspaceFilesTabTitle)), "dsh-workspace-files: official tab title");
311
+ }
312
+ }
313
+ function WorkspaceFilesView({ sessionId, cwd, onOpenFile }) {
314
+ const [data, setData] = (0, react.useState)(null);
315
+ const [loading, setLoading] = (0, react.useState)(true);
316
+ const [error, setError] = (0, react.useState)(null);
317
+ const [query, setQuery] = (0, react.useState)("");
318
+ const [toast, setToast] = (0, react.useState)(null);
319
+ const [expandedPaths, setExpandedPaths] = (0, react.useState)(() => /* @__PURE__ */ new Set());
320
+ const [showHidden, setShowHidden] = (0, react.useState)(false);
321
+ const toastTimer = (0, react.useRef)(null);
322
+ const showToastMsg = (0, react.useCallback)((msg) => {
323
+ setToast(msg);
324
+ if (toastTimer.current) window.clearTimeout(toastTimer.current);
325
+ toastTimer.current = window.setTimeout(() => setToast(null), 2e3);
326
+ }, []);
327
+ const loadTree = (0, react.useCallback)(async (quiet = false) => {
328
+ if (!quiet) setLoading(true);
329
+ setError(null);
330
+ try {
331
+ const res = await call("files.tree", {
332
+ sessionId,
333
+ cwd,
334
+ showHidden
335
+ });
336
+ setData(res);
337
+ setExpandedPaths((prev) => {
338
+ if (prev.size > 0) return prev;
339
+ const stored = loadStoredExpandedPaths(res.root || cwd);
340
+ if (stored !== null) return stored;
341
+ return /* @__PURE__ */ new Set();
342
+ });
343
+ } catch (err) {
344
+ setError(err?.message || "无法加载文件列表");
345
+ } finally {
346
+ if (!quiet) setLoading(false);
347
+ }
348
+ }, [
349
+ sessionId,
350
+ cwd,
351
+ showHidden
352
+ ]);
353
+ (0, react.useEffect)(() => {
354
+ loadTree();
355
+ }, [loadTree]);
356
+ const handleToggleExpand = (path) => {
357
+ setExpandedPaths((prev) => {
358
+ const next = new Set(prev);
359
+ if (next.has(path)) next.delete(path);
360
+ else next.add(path);
361
+ saveStoredExpandedPaths(data?.root || cwd, next);
362
+ return next;
363
+ });
364
+ };
365
+ const handleExpandAll = () => {
366
+ if (!data) return;
367
+ const all = /* @__PURE__ */ new Set();
368
+ function collect(items) {
369
+ for (const it of items) if (it.isDirectory) {
370
+ all.add(it.path);
371
+ if (it.children) collect(it.children);
372
+ }
373
+ }
374
+ collect(data.items);
375
+ setExpandedPaths(all);
376
+ saveStoredExpandedPaths(data.root || cwd, all);
377
+ };
378
+ const handleCollapseAll = () => {
379
+ const empty = /* @__PURE__ */ new Set();
380
+ setExpandedPaths(empty);
381
+ saveStoredExpandedPaths(data?.root || cwd, empty);
382
+ };
383
+ const handleJumpToFolderInTree = (dirPath) => {
384
+ const parts = dirPath.split("/");
385
+ const toExpand = [];
386
+ let acc = "";
387
+ for (const p of parts) {
388
+ if (!p) continue;
389
+ acc = acc ? `${acc}/${p}` : p;
390
+ toExpand.push(acc);
391
+ }
392
+ setExpandedPaths((prev) => {
393
+ const next = new Set(prev);
394
+ for (const p of toExpand) next.add(p);
395
+ saveStoredExpandedPaths(data?.root || cwd, next);
396
+ return next;
397
+ });
398
+ setQuery("");
399
+ showToastMsg(`已定位并展开目录: ${dirPath}/`);
400
+ setTimeout(() => {
401
+ try {
402
+ const el = document.querySelector(`[data-tree-path="${dirPath}"]`);
403
+ if (el) el.scrollIntoView({
404
+ block: "center",
405
+ behavior: "smooth"
406
+ });
407
+ } catch {}
408
+ }, 60);
409
+ };
410
+ const handleCopyPath = (e, path) => {
411
+ e.stopPropagation();
412
+ navigator.clipboard.writeText(path).then(() => {
413
+ showToastMsg(`已复制路径: ${path}`);
414
+ });
415
+ };
416
+ const handleMention = (e, itemOrPath) => {
417
+ e.stopPropagation();
418
+ const itemObj = typeof itemOrPath === "string" ? {
419
+ name: itemOrPath.split("/").pop() || itemOrPath,
420
+ path: itemOrPath,
421
+ isDirectory: false
422
+ } : itemOrPath;
423
+ const mentionText = formatMentionText(itemObj.path, itemObj.isDirectory);
424
+ let inserted = false;
425
+ try {
426
+ inserted = tryInsertNativeReferenceChip(globalCtx, sessionId, itemObj);
427
+ } catch (err) {
428
+ console.warn("[dsh-workspace-files] 原生 Chip 插入失败:", err);
429
+ }
430
+ if (!inserted) try {
431
+ inserted = insertMentionToComposer(mentionText);
432
+ } catch (err) {
433
+ console.warn("[dsh-workspace-files] 降级纯文本插入失败:", err);
434
+ }
435
+ const displayName = itemObj.isDirectory ? `${itemObj.name}/` : itemObj.name;
436
+ navigator.clipboard.writeText(mentionText.trim()).then(() => {
437
+ if (inserted) showToastMsg(`已引用${itemObj.isDirectory ? "目录" : "文件"}: ${displayName}`);
438
+ else showToastMsg(`已复制引用: ${mentionText.trim()}`);
439
+ });
440
+ };
441
+ const [searchViewMode, setSearchViewMode] = (0, react.useState)("flat");
442
+ const [searchExpandedPaths, setSearchExpandedPaths] = (0, react.useState)(() => /* @__PURE__ */ new Set());
443
+ const { filteredTreeItems, flatSearchResults } = (0, react.useMemo)(() => {
444
+ if (!data) return {
445
+ filteredTreeItems: [],
446
+ flatSearchResults: []
447
+ };
448
+ const q = query.trim().toLowerCase();
449
+ if (!q) return {
450
+ filteredTreeItems: data.items,
451
+ flatSearchResults: []
452
+ };
453
+ const flat = [];
454
+ function traverseFlat(items, parentDir) {
455
+ for (const it of items) {
456
+ if (it.name.toLowerCase().includes(q) || it.path.toLowerCase().includes(q)) flat.push({
457
+ item: it,
458
+ parentDir
459
+ });
460
+ if (it.isDirectory && it.children) traverseFlat(it.children, it.path);
461
+ }
462
+ }
463
+ traverseFlat(data.items, "");
464
+ function filterNode(node) {
465
+ const selfMatch = node.name.toLowerCase().includes(q) || node.path.toLowerCase().includes(q);
466
+ if (node.isDirectory) {
467
+ const filteredChildren = [];
468
+ for (const child of node.children || []) {
469
+ const matchedChild = filterNode(child);
470
+ if (matchedChild) filteredChildren.push(matchedChild);
471
+ }
472
+ if (selfMatch || filteredChildren.length > 0) return {
473
+ ...node,
474
+ children: filteredChildren
475
+ };
476
+ return null;
477
+ }
478
+ return selfMatch ? node : null;
479
+ }
480
+ const tree = [];
481
+ for (const rootNode of data.items) {
482
+ const matched = filterNode(rootNode);
483
+ if (matched) tree.push(matched);
484
+ }
485
+ return {
486
+ filteredTreeItems: tree,
487
+ flatSearchResults: flat
488
+ };
489
+ }, [data, query]);
490
+ (0, react.useEffect)(() => {
491
+ if (!query.trim() || !data) return;
492
+ const q = query.trim().toLowerCase();
493
+ const autoExpand = /* @__PURE__ */ new Set();
494
+ function collect(items) {
495
+ for (const it of items) if (it.isDirectory) {
496
+ const selfMatch = it.name.toLowerCase().includes(q) || it.path.toLowerCase().includes(q);
497
+ const childMatch = it.children?.some((c) => c.name.toLowerCase().includes(q) || c.path.toLowerCase().includes(q));
498
+ if (selfMatch || childMatch) autoExpand.add(it.path);
499
+ if (it.children) collect(it.children);
500
+ }
501
+ }
502
+ collect(data.items);
503
+ setSearchExpandedPaths(autoExpand);
504
+ }, [query, data]);
505
+ const isSearchActive = Boolean(query.trim());
506
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
507
+ className: "dsh-files-root",
508
+ children: [
509
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GlobalFilesStyle, {}),
510
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("header", {
511
+ className: "dsh-files-header",
512
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
513
+ className: "dsh-files-header-info",
514
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
515
+ className: "dsh-files-title-row",
516
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
517
+ className: "dsh-files-title",
518
+ children: "文件"
519
+ }), data && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
520
+ className: "dsh-files-stats-badge",
521
+ children: [
522
+ data.totalFiles,
523
+ " 文件 · ",
524
+ data.totalDirs,
525
+ " 目录"
526
+ ]
527
+ })]
528
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
529
+ className: "dsh-files-root-path",
530
+ title: data?.root || cwd,
531
+ children: data?.root || cwd || "当前会话没有工作目录"
532
+ })]
533
+ })
534
+ }),
535
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
536
+ className: "dsh-files-toolbar",
537
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
538
+ className: "dsh-files-search-wrap",
539
+ children: [
540
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
541
+ className: "dsh-files-search-icon",
542
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconSearch, { size: 13 })
543
+ }),
544
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
545
+ type: "text",
546
+ className: "dsh-files-search-input",
547
+ placeholder: "搜索文件 (支持路径筛选)...",
548
+ value: query,
549
+ onChange: (e) => setQuery(e.target.value),
550
+ onKeyDown: (e) => {
551
+ if (e.key === "Escape") setQuery("");
552
+ }
553
+ }),
554
+ query && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
555
+ type: "button",
556
+ className: "dsh-files-clear-btn",
557
+ onClick: () => setQuery(""),
558
+ title: "清除搜索",
559
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconClose, { size: 12 })
560
+ })
561
+ ]
562
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
563
+ className: "dsh-files-toolbar-btns",
564
+ children: [
565
+ isSearchActive ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
566
+ type: "button",
567
+ className: "dsh-files-tool-btn is-active",
568
+ onClick: () => setSearchViewMode(searchViewMode === "flat" ? "tree" : "flat"),
569
+ title: searchViewMode === "flat" ? "当前为扁平列表,点击切换为树形视图" : "当前为树形视图,点击切换为扁平列表",
570
+ children: searchViewMode === "flat" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconTree, { size: 13 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconList, { size: 13 })
571
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
572
+ type: "button",
573
+ className: "dsh-files-tool-btn",
574
+ onClick: handleExpandAll,
575
+ title: "全部展开",
576
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconExpandAll, { size: 13 })
577
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
578
+ type: "button",
579
+ className: "dsh-files-tool-btn",
580
+ onClick: handleCollapseAll,
581
+ title: "全部折叠",
582
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCollapseAll, { size: 13 })
583
+ })] }),
584
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
585
+ type: "button",
586
+ className: `dsh-files-tool-btn ${showHidden ? "is-active" : ""}`,
587
+ onClick: () => setShowHidden(!showHidden),
588
+ title: showHidden ? "隐藏点号开头的文件" : "显示点号开头的文件",
589
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
590
+ style: {
591
+ fontSize: 11,
592
+ fontWeight: 700
593
+ },
594
+ children: ".*"
595
+ })
596
+ }),
597
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
598
+ type: "button",
599
+ className: "dsh-files-tool-btn",
600
+ onClick: () => void loadTree(true),
601
+ disabled: loading,
602
+ title: "刷新文件树",
603
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconRefresh, { size: 13 })
604
+ })
605
+ ]
606
+ })]
607
+ }),
608
+ error && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
609
+ className: "dsh-files-banner-error",
610
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: error }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
611
+ type: "button",
612
+ className: "dsh-files-banner-close",
613
+ onClick: () => setError(null),
614
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconClose, { size: 12 })
615
+ })]
616
+ }),
617
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
618
+ className: "dsh-files-body",
619
+ children: loading && !data ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
620
+ className: "dsh-files-centered",
621
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "dsh-files-spinner" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "加载文件…" })]
622
+ }) : isSearchActive && flatSearchResults.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
623
+ className: "dsh-files-empty",
624
+ children: [
625
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
626
+ className: "dsh-files-empty-icon",
627
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconFolder, { size: 26 })
628
+ }),
629
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
630
+ className: "dsh-files-empty-title",
631
+ children: "未搜索到匹配的文件"
632
+ }),
633
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
634
+ className: "dsh-files-empty-detail",
635
+ children: "请尝试更换搜索关键字,或按 Esc 清除搜索。"
636
+ })
637
+ ]
638
+ }) : !isSearchActive && (!data || data.items.length === 0) ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
639
+ className: "dsh-files-empty",
640
+ children: [
641
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
642
+ className: "dsh-files-empty-icon",
643
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconFolder, { size: 26 })
644
+ }),
645
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
646
+ className: "dsh-files-empty-title",
647
+ children: "工作区为空"
648
+ }),
649
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
650
+ className: "dsh-files-empty-detail",
651
+ children: "当前工作目录下暂无可显示的文件。"
652
+ })
653
+ ]
654
+ }) : isSearchActive && searchViewMode === "flat" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
655
+ className: "dsh-files-search-container",
656
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
657
+ className: "dsh-files-search-header",
658
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
659
+ className: "dsh-files-search-count",
660
+ children: [
661
+ "找到 ",
662
+ flatSearchResults.length,
663
+ " 个匹配项"
664
+ ]
665
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
666
+ type: "button",
667
+ className: "dsh-files-view-toggle-btn",
668
+ onClick: () => setSearchViewMode("tree"),
669
+ title: "切换为可折叠的树形视图",
670
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconTree, { size: 12 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "切为树形" })]
671
+ })]
672
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
673
+ className: "dsh-files-flat-list",
674
+ children: flatSearchResults.map(({ item, parentDir }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
675
+ className: `dsh-files-flat-row ${item.isDirectory ? "is-directory" : ""}`,
676
+ onClick: () => {
677
+ if (item.isDirectory) handleJumpToFolderInTree(item.path);
678
+ else onOpenFile(item.path);
679
+ },
680
+ title: item.isDirectory ? `点击退出搜索并定位展开目录: ${item.path}/` : `点击预览打开文件: ${item.path}`,
681
+ children: [
682
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
683
+ className: "dsh-files-flat-icon",
684
+ children: item.isDirectory ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconFolder, { size: 14 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FileGlyphIcon, {
685
+ filename: item.name,
686
+ size: 14
687
+ })
688
+ }),
689
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
690
+ className: "dsh-files-flat-info",
691
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
692
+ className: "dsh-files-flat-top",
693
+ children: [
694
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
695
+ className: "dsh-files-flat-name",
696
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(HighlightText, {
697
+ text: item.name,
698
+ query
699
+ }), item.isDirectory && "/"]
700
+ }),
701
+ item.size !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
702
+ className: "dsh-files-size",
703
+ children: formatFileSize(item.size)
704
+ }),
705
+ item.isDirectory && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
706
+ className: "dsh-files-jump-hint",
707
+ children: "跳回展开 ›"
708
+ })
709
+ ]
710
+ }), parentDir && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
711
+ className: "dsh-files-flat-parent",
712
+ title: parentDir,
713
+ children: parentDir
714
+ })]
715
+ }),
716
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
717
+ className: "dsh-files-row-actions",
718
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
719
+ type: "button",
720
+ className: "dsh-files-action-btn dsh-files-action-at",
721
+ onClick: (e) => handleMention(e, item),
722
+ title: `一键 @ 引用此${item.isDirectory ? "目录" : "文件"}到当前会话`,
723
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconAt, { size: 13 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
724
+ className: "dsh-files-action-at-text",
725
+ children: "@引用"
726
+ })]
727
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
728
+ type: "button",
729
+ className: "dsh-files-action-btn",
730
+ onClick: (e) => handleCopyPath(e, item.path),
731
+ title: "复制相对路径",
732
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCopy, { size: 12 })
733
+ })]
734
+ })
735
+ ]
736
+ }, item.path))
737
+ })]
738
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
739
+ className: "dsh-files-tree",
740
+ children: [isSearchActive && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
741
+ className: "dsh-files-search-header",
742
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
743
+ className: "dsh-files-search-count",
744
+ children: "树形匹配(自由折叠/展开)"
745
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
746
+ type: "button",
747
+ className: "dsh-files-view-toggle-btn",
748
+ onClick: () => setSearchViewMode("flat"),
749
+ title: "切换为扁平搜索结果列表",
750
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconList, { size: 12 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "切为列表" })]
751
+ })]
752
+ }), filteredTreeItems.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FileTreeNode, {
753
+ item,
754
+ depth: 0,
755
+ query,
756
+ isSearchMode: isSearchActive,
757
+ expandedPaths: isSearchActive ? searchExpandedPaths : expandedPaths,
758
+ onToggleExpand: (path) => {
759
+ if (isSearchActive) setSearchExpandedPaths((prev) => {
760
+ const next = new Set(prev);
761
+ if (next.has(path)) next.delete(path);
762
+ else next.add(path);
763
+ return next;
764
+ });
765
+ else handleToggleExpand(path);
766
+ },
767
+ onOpenFile,
768
+ onMention: handleMention,
769
+ onCopyPath: handleCopyPath
770
+ }, item.path))]
771
+ })
772
+ }),
773
+ toast && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
774
+ className: "dsh-files-toast",
775
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCheck, { size: 12 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: toast })]
776
+ })
777
+ ]
778
+ });
779
+ }
780
+ function FileTreeNode(props) {
781
+ const { item, depth, query, isSearchMode, expandedPaths, onToggleExpand, onOpenFile, onMention, onCopyPath } = props;
782
+ const isExpanded = expandedPaths.has(item.path);
783
+ if (item.isDirectory) {
784
+ const childCount = item.children?.length ?? 0;
785
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
786
+ className: "dsh-files-dir-block",
787
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
788
+ className: "dsh-files-dir-row",
789
+ "data-tree-path": item.path,
790
+ style: { paddingLeft: `${depth * 14 + 10}px` },
791
+ onClick: () => onToggleExpand(item.path),
792
+ children: [
793
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
794
+ className: "dsh-files-chevron",
795
+ "data-collapsed": !isExpanded ? "true" : void 0,
796
+ children: "▾"
797
+ }),
798
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
799
+ className: "dsh-files-folder-icon",
800
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconFolder, {
801
+ size: 14,
802
+ open: isExpanded
803
+ })
804
+ }),
805
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
806
+ className: "dsh-files-folder-name",
807
+ title: item.path,
808
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HighlightText, {
809
+ text: item.name,
810
+ query
811
+ })
812
+ }),
813
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
814
+ className: "dsh-files-count-badge",
815
+ children: childCount
816
+ }),
817
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
818
+ className: "dsh-files-row-actions",
819
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
820
+ type: "button",
821
+ className: "dsh-files-action-btn dsh-files-action-at",
822
+ onClick: (e) => onMention(e, item),
823
+ title: "一键 @ 引用此目录到当前会话",
824
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconAt, { size: 13 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
825
+ className: "dsh-files-action-at-text",
826
+ children: "@引用"
827
+ })]
828
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
829
+ type: "button",
830
+ className: "dsh-files-action-btn",
831
+ onClick: (e) => onCopyPath(e, item.path),
832
+ title: "复制目录路径",
833
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCopy, { size: 12 })
834
+ })]
835
+ })
836
+ ]
837
+ }), isExpanded && item.children && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
838
+ className: "dsh-files-children",
839
+ children: item.children.map((child) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FileTreeNode, {
840
+ item: child,
841
+ depth: depth + 1,
842
+ query,
843
+ isSearchMode,
844
+ expandedPaths,
845
+ onToggleExpand,
846
+ onOpenFile,
847
+ onMention,
848
+ onCopyPath
849
+ }, child.path))
850
+ })]
851
+ });
852
+ }
853
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
854
+ className: "dsh-files-row",
855
+ "data-tree-path": item.path,
856
+ style: { paddingLeft: `${depth * 14 + 10}px` },
857
+ onClick: () => onOpenFile(item.path),
858
+ title: `点击打开: ${item.path}`,
859
+ children: [
860
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FileGlyphIcon, {
861
+ filename: item.name,
862
+ size: 14
863
+ }),
864
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
865
+ className: "dsh-files-name",
866
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HighlightText, {
867
+ text: item.name,
868
+ query
869
+ })
870
+ }),
871
+ item.size !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
872
+ className: "dsh-files-size",
873
+ title: `大小: ${item.size} 字节`,
874
+ children: formatFileSize(item.size)
875
+ }),
876
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
877
+ className: "dsh-files-row-actions",
878
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
879
+ type: "button",
880
+ className: "dsh-files-action-btn dsh-files-action-at",
881
+ onClick: (e) => onMention(e, item),
882
+ title: "一键 @ 引用此文件到当前会话",
883
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconAt, { size: 13 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
884
+ className: "dsh-files-action-at-text",
885
+ children: "@引用"
886
+ })]
887
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
888
+ type: "button",
889
+ className: "dsh-files-action-btn",
890
+ onClick: (e) => onCopyPath(e, item.path),
891
+ title: "复制相对路径",
892
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCopy, { size: 12 })
893
+ })]
894
+ })
895
+ ]
896
+ });
897
+ }
898
+ function HighlightText({ text, query }) {
899
+ const q = query.trim().toLowerCase();
900
+ if (!q) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text });
901
+ const index = text.toLowerCase().indexOf(q);
902
+ if (index === -1) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text });
903
+ const before = text.slice(0, index);
904
+ const match = text.slice(index, index + q.length);
905
+ const after = text.slice(index + q.length);
906
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
907
+ before,
908
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("mark", {
909
+ className: "dsh-files-highlight",
910
+ children: match
911
+ }),
912
+ after
913
+ ] });
914
+ }
915
+ function formatFileSize(bytes) {
916
+ if (bytes < 1024) return `${bytes} B`;
917
+ const kb = bytes / 1024;
918
+ if (kb < 1024) return `${kb.toFixed(1)} KB`;
919
+ return `${(kb / 1024).toFixed(1)} MB`;
920
+ }
921
+ /**
922
+ * 按照 DSH 官方规范格式化引用路径:
923
+ * - 纯路径无空格:@path
924
+ * - 路径含空格:@"path"
925
+ * - 目录末尾保留斜杠:@folder/ 或 @"folder/"
926
+ */
927
+ function formatMentionText(path, isDirectory = false) {
928
+ const cleanPath = path.replace(/\\/g, "/").replace(/^\.?\//, "");
929
+ const finalPath = isDirectory ? cleanPath.endsWith("/") ? cleanPath : `${cleanPath}/` : cleanPath;
930
+ if (/\s/u.test(finalPath)) return `@"${finalPath}" `;
931
+ return `@${finalPath} `;
932
+ }
933
+ function getStorageKey(workspaceRoot) {
934
+ return `dsh_files_expanded_${(workspaceRoot || "default").replace(/[^a-zA-Z0-9_-]/g, "_")}`;
935
+ }
936
+ function loadStoredExpandedPaths(workspaceRoot) {
937
+ if (typeof window === "undefined" || !window.localStorage) return null;
938
+ try {
939
+ const raw = window.localStorage.getItem(getStorageKey(workspaceRoot));
940
+ if (!raw) return null;
941
+ const list = JSON.parse(raw);
942
+ if (Array.isArray(list)) return new Set(list);
943
+ } catch {}
944
+ return null;
945
+ }
946
+ function saveStoredExpandedPaths(workspaceRoot, paths) {
947
+ if (typeof window === "undefined" || !window.localStorage) return;
948
+ try {
949
+ window.localStorage.setItem(getStorageKey(workspaceRoot), JSON.stringify(Array.from(paths)));
950
+ } catch {}
951
+ }
952
+ /**
953
+ * 从当前 DOM 的 React Fiber 树或 Cordis RootContext 中提取当前活跃输入框的 SessionInputShell 实例
954
+ */
955
+ function findComposerShell(ctx, sessionId) {
956
+ try {
957
+ const composerDom = document.querySelector("[data-composer-input=\"true\"], [data-composer-input], [data-composer-card], div[role=\"textbox\"][contenteditable=\"true\"]");
958
+ if (composerDom) {
959
+ const fiberKey = Object.keys(composerDom).find((k) => k.startsWith("__reactFiber") || k.startsWith("__reactInternalInstance"));
960
+ if (fiberKey) {
961
+ let curr = composerDom[fiberKey];
962
+ let depth = 0;
963
+ while (curr && depth < 25) {
964
+ const p = curr.memoizedProps;
965
+ if (p) {
966
+ if (p.keyboard && typeof p.keyboard.insertReference === "function") return p.keyboard;
967
+ if (p.shell && typeof p.shell.insertReference === "function") return p.shell;
968
+ }
969
+ curr = curr.return;
970
+ depth++;
971
+ }
972
+ }
973
+ }
974
+ } catch (e) {
975
+ console.debug("[dsh-workspace-files] 从 Fiber 查找 keyboard 失败:", e);
976
+ }
977
+ if (ctx) try {
978
+ const root = ctx.root || ctx;
979
+ const inputService = (root.conversation || root.get?.("conversation") || window.__dsh_conversation)?.input || root["conversation.input"] || root.get?.("conversation.input");
980
+ if (inputService) {
981
+ if (sessionId) {
982
+ const s = inputService.shell?.(sessionId) || inputService.shells?.get?.(sessionId);
983
+ if (s) return s;
984
+ }
985
+ if (inputService.shells instanceof Map && inputService.shells.size > 0) {
986
+ const values = Array.from(inputService.shells.values());
987
+ return values[values.length - 1];
988
+ }
989
+ }
990
+ } catch (e) {
991
+ console.debug("[dsh-workspace-files] 从 RootContext 查找 shell 失败:", e);
992
+ }
993
+ return null;
994
+ }
995
+ /**
996
+ * 尝试向会话输入框插入原生的 ReferenceChipNode(官方胶囊 Tag 卡片)
997
+ */
998
+ function tryInsertNativeReferenceChip(ctx, sessionId, item) {
999
+ if (typeof document === "undefined") return false;
1000
+ try {
1001
+ const composerDom = document.querySelector("[data-composer-input=\"true\"], [data-composer-input], [data-composer-card] [contenteditable=\"true\"], div[role=\"textbox\"][contenteditable=\"true\"]");
1002
+ if (composerDom) composerDom.focus();
1003
+ const shell = findComposerShell(ctx, sessionId);
1004
+ if (!shell) return false;
1005
+ const cleanPath = item.path.replace(/\\/g, "/").replace(/^\.?\//, "");
1006
+ const mention = item.isDirectory ? cleanPath.endsWith("/") ? `@${cleanPath}` : `@${cleanPath}/` : `@${cleanPath}`;
1007
+ const reference = {
1008
+ source: "reference",
1009
+ ref: mention,
1010
+ label: item.isDirectory ? item.name.endsWith("/") ? item.name : `${item.name}/` : item.name,
1011
+ appearance: item.isDirectory ? "folder" : "file",
1012
+ clipboardText: mention
1013
+ };
1014
+ const text = shell.projection?.detectText || "";
1015
+ let caret = typeof shell.projection?.caret === "number" ? shell.projection.caret : text.length;
1016
+ let start = caret;
1017
+ const end = caret;
1018
+ if (start > 0 && text[start - 1] === "@") start = start - 1;
1019
+ const span = {
1020
+ draftRev: shell.rev,
1021
+ start,
1022
+ end
1023
+ };
1024
+ if (typeof shell.insertReference === "function") {
1025
+ if (shell.insertReference(reference, span)) return true;
1026
+ }
1027
+ const actx = shell.deps?.actx;
1028
+ if (actx && typeof actx.bail === "function") {
1029
+ if (actx.bail(actx, "slash/input-insert-reference", {
1030
+ reference,
1031
+ span
1032
+ }) === true) return true;
1033
+ }
1034
+ } catch (err) {
1035
+ console.debug("[dsh-workspace-files] 插入原生 Chip 异常:", err);
1036
+ }
1037
+ return false;
1038
+ }
1039
+ /**
1040
+ * 将引用文本注入到 DSH 当前会话的主输入框。
1041
+ * 兼容 DSH 官方基于 Lexical 的 ComposerContentEditable (div[data-composer-input])
1042
+ * 以及传统的 textarea / input,并自动消除用户手动输入的冗余 '@'。
1043
+ */
1044
+ function insertMentionToComposer(mentionText) {
1045
+ if (typeof document === "undefined") return false;
1046
+ const target = [
1047
+ document.querySelector("[data-composer-input=\"true\"]"),
1048
+ document.querySelector("[data-composer-input]"),
1049
+ document.querySelector("[data-composer-card] [contenteditable=\"true\"]"),
1050
+ document.querySelector("[contenteditable=\"true\"][role=\"textbox\"]"),
1051
+ document.querySelector("textarea:not([readonly]):not(.dsh-files-search-input)")
1052
+ ].filter(Boolean)[0];
1053
+ if (!target) return false;
1054
+ target.focus();
1055
+ if (target.getAttribute("contenteditable") === "true" || target.isContentEditable) {
1056
+ const sel = window.getSelection();
1057
+ if (sel) {
1058
+ let isInTarget = false;
1059
+ if (sel.rangeCount > 0) {
1060
+ const anchor = sel.anchorNode;
1061
+ if (anchor && (anchor === target || target.contains(anchor))) isInTarget = true;
1062
+ }
1063
+ if (!isInTarget) {
1064
+ const range = document.createRange();
1065
+ range.selectNodeContents(target);
1066
+ range.collapse(false);
1067
+ sel.removeAllRanges();
1068
+ sel.addRange(range);
1069
+ } else {
1070
+ const range = sel.getRangeAt(0);
1071
+ if (range.collapsed && range.startContainer.nodeType === Node.TEXT_NODE) {
1072
+ const nodeText = range.startContainer.textContent || "";
1073
+ const offset = range.startOffset;
1074
+ if (offset > 0 && nodeText[offset - 1] === "@") {
1075
+ range.setStart(range.startContainer, offset - 1);
1076
+ sel.removeAllRanges();
1077
+ sel.addRange(range);
1078
+ }
1079
+ }
1080
+ }
1081
+ }
1082
+ let success = false;
1083
+ try {
1084
+ success = document.execCommand("insertText", false, mentionText);
1085
+ } catch {}
1086
+ if (!success) try {
1087
+ const evt = new InputEvent("beforeinput", {
1088
+ bubbles: true,
1089
+ cancelable: true,
1090
+ inputType: "insertText",
1091
+ data: mentionText
1092
+ });
1093
+ target.dispatchEvent(evt);
1094
+ success = true;
1095
+ } catch {}
1096
+ return success;
1097
+ }
1098
+ if (target instanceof HTMLTextAreaElement || target instanceof HTMLInputElement) {
1099
+ let start = target.selectionStart ?? target.value.length;
1100
+ const end = target.selectionEnd ?? target.value.length;
1101
+ const prev = target.value;
1102
+ if (start > 0 && prev[start - 1] === "@") start = start - 1;
1103
+ let success = false;
1104
+ try {
1105
+ target.selectionStart = start;
1106
+ target.selectionEnd = end;
1107
+ success = document.execCommand("insertText", false, mentionText);
1108
+ } catch {}
1109
+ if (!success) {
1110
+ const next = prev.slice(0, start) + mentionText + prev.slice(end);
1111
+ const proto = target instanceof HTMLTextAreaElement ? window.HTMLTextAreaElement.prototype : window.HTMLInputElement.prototype;
1112
+ const setter = Object.getOwnPropertyDescriptor(proto, "value")?.set;
1113
+ if (setter) setter.call(target, next);
1114
+ else target.value = next;
1115
+ target.selectionStart = target.selectionEnd = start + mentionText.length;
1116
+ target.dispatchEvent(new InputEvent("input", {
1117
+ bubbles: true,
1118
+ inputType: "insertText",
1119
+ data: mentionText
1120
+ }));
1121
+ target.dispatchEvent(new Event("change", { bubbles: true }));
1122
+ success = true;
1123
+ }
1124
+ return success;
1125
+ }
1126
+ return false;
1127
+ }
1128
+ async function call(method, payload = {}) {
1129
+ let response = await fetch(`/dsh-workspace-files/api/${method}`, {
1130
+ method: "POST",
1131
+ headers: { "content-type": "application/json" },
1132
+ body: JSON.stringify(payload)
1133
+ }).catch(() => null);
1134
+ if (!response || !response.ok) response = await fetch(`/sidebar/api/${method}`, {
1135
+ method: "POST",
1136
+ headers: { "content-type": "application/json" },
1137
+ body: JSON.stringify(payload)
1138
+ }).catch(() => null);
1139
+ if (!response) throw new Error("网络请求失败:无法连接到文件服务");
1140
+ const envelope = await response.json().catch(() => null);
1141
+ if (!response.ok || envelope?.ok !== true || envelope.value === void 0) throw new Error(envelope?.error?.message ?? `HTTP ${response.status}`);
1142
+ return envelope.value;
1143
+ }
1144
+ function GlobalFilesStyle() {
1145
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: `
1146
+ .dsh-files-root {
1147
+ display: flex;
1148
+ flex-direction: column;
1149
+ height: 100%;
1150
+ min-height: 0;
1151
+ box-sizing: border-box;
1152
+ background: var(--dsw-alias-bg-base, #ffffff);
1153
+ color: var(--dsw-alias-label-primary, #17233c);
1154
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
1155
+ font-size: 12px;
1156
+ line-height: 1.4;
1157
+ overflow: hidden;
1158
+ position: relative;
1159
+ }
1160
+
1161
+ /* ── Header ── */
1162
+ .dsh-files-header {
1163
+ flex: none;
1164
+ display: flex;
1165
+ align-items: center;
1166
+ justify-content: space-between;
1167
+ gap: 8px;
1168
+ padding: 8px 12px;
1169
+ border-bottom: 1px solid var(--dsw-alias-border-l1, rgba(118,137,166,.18));
1170
+ }
1171
+ .dsh-files-header-info {
1172
+ flex: 1;
1173
+ min-width: 0;
1174
+ }
1175
+ .dsh-files-title-row {
1176
+ display: flex;
1177
+ align-items: center;
1178
+ gap: 8px;
1179
+ }
1180
+ .dsh-files-title {
1181
+ font-weight: 600;
1182
+ font-size: 13px;
1183
+ color: var(--dsw-alias-label-primary, inherit);
1184
+ }
1185
+ .dsh-files-stats-badge {
1186
+ font-size: 10px;
1187
+ font-weight: 500;
1188
+ color: var(--dsw-alias-label-tertiary, #8c9ba5);
1189
+ background: var(--dsw-alias-interactive-bg-hover, rgba(100,120,150,.1));
1190
+ padding: 1px 6px;
1191
+ border-radius: 99px;
1192
+ }
1193
+ .dsh-files-root-path {
1194
+ font-size: 11px;
1195
+ color: var(--dsw-alias-label-tertiary, #8c9ba5);
1196
+ overflow: hidden;
1197
+ text-overflow: ellipsis;
1198
+ white-space: nowrap;
1199
+ margin-top: 1px;
1200
+ }
1201
+
1202
+ /* ── Toolbar & Search ── */
1203
+ .dsh-files-toolbar {
1204
+ display: flex;
1205
+ align-items: center;
1206
+ gap: 6px;
1207
+ padding: 6px 8px 6px 12px;
1208
+ border-bottom: 1px solid var(--dsw-alias-border-l1, rgba(118,137,166,.15));
1209
+ background: var(--dsw-alias-bg-base, #ffffff);
1210
+ }
1211
+ .dsh-files-search-wrap {
1212
+ position: relative;
1213
+ flex: 1;
1214
+ min-width: 0;
1215
+ display: flex;
1216
+ align-items: center;
1217
+ }
1218
+ .dsh-files-search-icon {
1219
+ position: absolute;
1220
+ left: 7px;
1221
+ color: var(--dsw-alias-label-tertiary, #8c9ba5);
1222
+ pointer-events: none;
1223
+ display: flex;
1224
+ }
1225
+ .dsh-files-search-input {
1226
+ box-sizing: border-box;
1227
+ width: 100%;
1228
+ height: 25px;
1229
+ padding: 0 22px 0 26px;
1230
+ border: 1px solid var(--dsw-alias-border-l2, rgba(118,137,166,.25));
1231
+ border-radius: 4px;
1232
+ background: var(--dsw-alias-interactive-bg-hover, rgba(100,120,150,.06));
1233
+ color: inherit;
1234
+ font-size: 11.5px;
1235
+ outline: none;
1236
+ transition: all 0.15s ease;
1237
+ }
1238
+ .dsh-files-search-input:focus {
1239
+ background: var(--dsw-alias-bg-base, #ffffff);
1240
+ border-color: var(--dsw-alias-brand-primary, #4b70e2);
1241
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--dsw-alias-brand-primary, #4b70e2) 15%, transparent);
1242
+ }
1243
+ .dsh-files-clear-btn {
1244
+ position: absolute;
1245
+ right: 5px;
1246
+ border: none;
1247
+ background: transparent;
1248
+ color: var(--dsw-alias-label-tertiary, #8c9ba5);
1249
+ cursor: pointer;
1250
+ padding: 2px;
1251
+ display: flex;
1252
+ }
1253
+ .dsh-files-clear-btn:hover {
1254
+ color: var(--dsw-alias-label-primary, inherit);
1255
+ }
1256
+ .dsh-files-toolbar-btns {
1257
+ display: flex;
1258
+ align-items: center;
1259
+ gap: 2px;
1260
+ }
1261
+ .dsh-files-tool-btn {
1262
+ display: inline-flex;
1263
+ align-items: center;
1264
+ justify-content: center;
1265
+ width: 24px;
1266
+ height: 24px;
1267
+ border: none;
1268
+ border-radius: 4px;
1269
+ background: transparent;
1270
+ color: var(--dsw-alias-label-secondary, #6f7f9b);
1271
+ cursor: pointer;
1272
+ transition: all 0.12s ease;
1273
+ }
1274
+ .dsh-files-tool-btn:hover:not(:disabled) {
1275
+ color: var(--dsw-alias-label-primary, inherit);
1276
+ background: var(--dsw-alias-interactive-bg-hover, rgba(100,120,150,.1));
1277
+ }
1278
+ .dsh-files-tool-btn.is-active {
1279
+ color: var(--dsw-alias-brand-primary, #4b70e2);
1280
+ background: var(--dsw-alias-interactive-bg-active, rgba(75,112,226,.15));
1281
+ }
1282
+ .dsh-files-tool-btn:disabled {
1283
+ opacity: 0.4;
1284
+ cursor: not-allowed;
1285
+ }
1286
+
1287
+ /* ── Tree Body ── */
1288
+ .dsh-files-body {
1289
+ flex: 1;
1290
+ min-height: 0;
1291
+ overflow-y: auto;
1292
+ overflow-x: hidden;
1293
+ padding: 4px 0;
1294
+ }
1295
+ .dsh-files-dir-block {
1296
+ display: flex;
1297
+ flex-direction: column;
1298
+ }
1299
+ .dsh-files-dir-row {
1300
+ display: flex;
1301
+ align-items: center;
1302
+ gap: 6px;
1303
+ height: 26px;
1304
+ padding-right: 8px;
1305
+ cursor: pointer;
1306
+ user-select: none;
1307
+ position: relative;
1308
+ color: var(--dsw-alias-label-secondary, #6f7f9b);
1309
+ transition: background 0.12s ease;
1310
+ }
1311
+ .dsh-files-dir-row:hover {
1312
+ background: var(--dsw-alias-interactive-bg-hover, rgba(100,120,150,.08));
1313
+ color: var(--dsw-alias-label-primary, inherit);
1314
+ }
1315
+ .dsh-files-chevron {
1316
+ font-size: 10px;
1317
+ transition: transform 0.15s ease;
1318
+ display: inline-block;
1319
+ opacity: 0.8;
1320
+ }
1321
+ .dsh-files-chevron[data-collapsed='true'] {
1322
+ transform: rotate(-90deg);
1323
+ }
1324
+ .dsh-files-folder-icon {
1325
+ display: flex;
1326
+ }
1327
+ .dsh-files-folder-name {
1328
+ flex: 1;
1329
+ min-width: 0;
1330
+ font-weight: 500;
1331
+ font-size: 12px;
1332
+ overflow: hidden;
1333
+ text-overflow: ellipsis;
1334
+ white-space: nowrap;
1335
+ }
1336
+ .dsh-files-count-badge {
1337
+ font-size: 10px;
1338
+ font-weight: 600;
1339
+ padding: 0 5px;
1340
+ border-radius: 99px;
1341
+ background: var(--dsw-alias-interactive-bg-hover, rgba(100,120,150,.1));
1342
+ color: var(--dsw-alias-label-tertiary, #8c9ba5);
1343
+ }
1344
+
1345
+ /* ── File Row ── */
1346
+ .dsh-files-row {
1347
+ display: flex;
1348
+ align-items: center;
1349
+ gap: 6px;
1350
+ height: 26px;
1351
+ padding-right: 8px;
1352
+ cursor: pointer;
1353
+ user-select: none;
1354
+ position: relative;
1355
+ transition: background 0.12s ease;
1356
+ }
1357
+ .dsh-files-row:hover {
1358
+ background: var(--dsw-alias-interactive-bg-hover, rgba(100,120,150,.08));
1359
+ }
1360
+ .dsh-files-name {
1361
+ flex: 1;
1362
+ min-width: 0;
1363
+ font-size: 12px;
1364
+ color: var(--dsw-alias-label-primary, inherit);
1365
+ overflow: hidden;
1366
+ text-overflow: ellipsis;
1367
+ white-space: nowrap;
1368
+ }
1369
+ .dsh-files-size {
1370
+ font-size: 10px;
1371
+ color: var(--dsw-alias-label-tertiary, #8c9ba5);
1372
+ flex-shrink: 0;
1373
+ margin-right: 4px;
1374
+ }
1375
+
1376
+ /* ── Hover Actions on Rows ── */
1377
+ .dsh-files-row-actions {
1378
+ display: none;
1379
+ align-items: center;
1380
+ gap: 3px;
1381
+ flex-shrink: 0;
1382
+ }
1383
+ .dsh-files-row:hover .dsh-files-row-actions,
1384
+ .dsh-files-dir-row:hover .dsh-files-row-actions,
1385
+ .dsh-files-flat-row:hover .dsh-files-row-actions {
1386
+ display: flex;
1387
+ }
1388
+ .dsh-files-row:hover .dsh-files-size,
1389
+ .dsh-files-dir-row:hover .dsh-files-count-badge,
1390
+ .dsh-files-flat-row:hover .dsh-files-size {
1391
+ display: none;
1392
+ }
1393
+
1394
+ /* ── Flat Search Result List ── */
1395
+ .dsh-files-search-container {
1396
+ display: flex;
1397
+ flex-direction: column;
1398
+ }
1399
+ .dsh-files-search-header {
1400
+ display: flex;
1401
+ align-items: center;
1402
+ justify-content: space-between;
1403
+ padding: 6px 12px;
1404
+ font-size: 11px;
1405
+ color: var(--dsw-alias-label-tertiary, #8c9ba5);
1406
+ background: color-mix(in srgb, var(--dsw-alias-brand-primary, #4b70e2) 4%, transparent);
1407
+ border-bottom: 1px solid var(--dsw-alias-border-l1, rgba(118,137,166,.15));
1408
+ }
1409
+ .dsh-files-search-count {
1410
+ font-weight: 500;
1411
+ }
1412
+ .dsh-files-view-toggle-btn {
1413
+ display: inline-flex;
1414
+ align-items: center;
1415
+ gap: 4px;
1416
+ border: 1px solid var(--dsw-alias-border-l2, rgba(118,137,166,.25));
1417
+ background: var(--dsw-alias-container-bg, var(--dsw-alias-bg-base, #ffffff));
1418
+ color: var(--dsw-alias-brand-primary, #4b70e2);
1419
+ cursor: pointer;
1420
+ font-size: 10.5px;
1421
+ font-weight: 500;
1422
+ padding: 2px 7px;
1423
+ border-radius: 4px;
1424
+ transition: all 0.12s ease;
1425
+ }
1426
+ .dsh-files-view-toggle-btn:hover {
1427
+ background: var(--dsw-alias-brand-primary, #4b70e2);
1428
+ color: #ffffff;
1429
+ border-color: var(--dsw-alias-brand-primary, #4b70e2);
1430
+ }
1431
+ .dsh-files-flat-list {
1432
+ padding: 4px 0;
1433
+ }
1434
+ .dsh-files-flat-row {
1435
+ display: flex;
1436
+ align-items: center;
1437
+ gap: 8px;
1438
+ padding: 4px 12px;
1439
+ cursor: pointer;
1440
+ user-select: none;
1441
+ position: relative;
1442
+ transition: background 0.12s ease;
1443
+ min-height: 32px;
1444
+ }
1445
+ .dsh-files-flat-row:hover {
1446
+ background: var(--dsw-alias-interactive-bg-hover, rgba(100,120,150,.08));
1447
+ }
1448
+ .dsh-files-flat-icon {
1449
+ display: flex;
1450
+ align-items: center;
1451
+ justify-content: center;
1452
+ flex-shrink: 0;
1453
+ }
1454
+ .dsh-files-flat-info {
1455
+ flex: 1;
1456
+ min-width: 0;
1457
+ display: flex;
1458
+ flex-direction: column;
1459
+ gap: 1px;
1460
+ }
1461
+ .dsh-files-flat-top {
1462
+ display: flex;
1463
+ align-items: center;
1464
+ justify-content: space-between;
1465
+ gap: 6px;
1466
+ }
1467
+ .dsh-files-flat-name {
1468
+ font-size: 12px;
1469
+ font-weight: 500;
1470
+ color: var(--dsw-alias-label-primary, inherit);
1471
+ overflow: hidden;
1472
+ text-overflow: ellipsis;
1473
+ white-space: nowrap;
1474
+ }
1475
+ .dsh-files-flat-parent {
1476
+ font-size: 10.5px;
1477
+ color: var(--dsw-alias-label-tertiary, #8c9ba5);
1478
+ overflow: hidden;
1479
+ text-overflow: ellipsis;
1480
+ white-space: nowrap;
1481
+ opacity: 0.8;
1482
+ }
1483
+ .dsh-files-jump-hint {
1484
+ font-size: 10px;
1485
+ color: var(--dsw-alias-brand-primary, #4b70e2);
1486
+ opacity: 0;
1487
+ transition: opacity 0.12s ease;
1488
+ margin-right: 6px;
1489
+ font-weight: 500;
1490
+ white-space: nowrap;
1491
+ }
1492
+ .dsh-files-flat-row:hover .dsh-files-jump-hint {
1493
+ opacity: 0.85;
1494
+ }
1495
+ .dsh-files-flat-row.is-directory:hover {
1496
+ background: color-mix(in srgb, var(--dsw-alias-brand-primary, #4b70e2) 6%, transparent);
1497
+ }
1498
+ .dsh-files-action-btn {
1499
+ display: inline-flex;
1500
+ align-items: center;
1501
+ justify-content: center;
1502
+ gap: 3px;
1503
+ height: 20px;
1504
+ padding: 0 5px;
1505
+ border: 1px solid var(--dsw-alias-border-l2, rgba(118,137,166,.25));
1506
+ border-radius: 3px;
1507
+ background: var(--dsw-alias-container-bg, var(--dsw-alias-bg-base, #ffffff));
1508
+ color: var(--dsw-alias-label-secondary, #6f7f9b);
1509
+ cursor: pointer;
1510
+ transition: all 0.12s ease;
1511
+ }
1512
+ .dsh-files-action-btn:hover {
1513
+ color: var(--dsw-alias-label-primary, inherit);
1514
+ border-color: var(--dsw-alias-border-l3, rgba(118,137,166,.4));
1515
+ background: var(--dsw-alias-interactive-bg-hover, rgba(100,120,150,.12));
1516
+ }
1517
+ .dsh-files-action-at {
1518
+ color: var(--dsw-alias-brand-primary, #4b70e2);
1519
+ border-color: color-mix(in srgb, var(--dsw-alias-brand-primary, #4b70e2) 30%, transparent);
1520
+ background: color-mix(in srgb, var(--dsw-alias-brand-primary, #4b70e2) 8%, transparent);
1521
+ }
1522
+ .dsh-files-action-at:hover {
1523
+ color: #ffffff;
1524
+ background: var(--dsw-alias-brand-primary, #4b70e2);
1525
+ border-color: var(--dsw-alias-brand-primary, #4b70e2);
1526
+ }
1527
+ .dsh-files-action-at-text {
1528
+ font-size: 10px;
1529
+ font-weight: 600;
1530
+ }
1531
+
1532
+ /* ── Highlight & Toast ── */
1533
+ .dsh-files-highlight {
1534
+ background: color-mix(in srgb, var(--dsw-alias-brand-primary, #4b70e2) 25%, transparent);
1535
+ color: var(--dsw-alias-brand-primary, #4b70e2);
1536
+ font-weight: 600;
1537
+ border-radius: 2px;
1538
+ padding: 0 1px;
1539
+ }
1540
+ .dsh-files-toast {
1541
+ position: absolute;
1542
+ bottom: 12px;
1543
+ left: 50%;
1544
+ transform: translateX(-50%);
1545
+ display: inline-flex;
1546
+ align-items: center;
1547
+ gap: 6px;
1548
+ padding: 6px 12px;
1549
+ background: var(--dsw-alias-label-primary, #17233c);
1550
+ color: var(--dsw-alias-bg-base, #ffffff);
1551
+ font-size: 11px;
1552
+ border-radius: 99px;
1553
+ box-shadow: 0 4px 16px rgba(0,0,0,.25);
1554
+ animation: dsh-files-fade-in 0.15s ease;
1555
+ z-index: 100;
1556
+ pointer-events: none;
1557
+ white-space: nowrap;
1558
+ }
1559
+ @keyframes dsh-files-fade-in {
1560
+ from { opacity: 0; transform: translate(-50%, 6px); }
1561
+ to { opacity: 1; transform: translate(-50%, 0); }
1562
+ }
1563
+
1564
+ /* ── Empty & Centered ── */
1565
+ .dsh-files-centered {
1566
+ display: flex;
1567
+ flex-direction: column;
1568
+ align-items: center;
1569
+ justify-content: center;
1570
+ height: 180px;
1571
+ gap: 10px;
1572
+ color: var(--dsw-alias-label-secondary, #6f7f9b);
1573
+ }
1574
+ .dsh-files-spinner {
1575
+ width: 18px;
1576
+ height: 18px;
1577
+ border: 2px solid var(--dsw-alias-border-l2, rgba(118,137,166,.3));
1578
+ border-top-color: var(--dsw-alias-brand-primary, #4b70e2);
1579
+ border-radius: 50%;
1580
+ animation: dsh-files-spin 0.8s linear infinite;
1581
+ }
1582
+ @keyframes dsh-files-spin {
1583
+ to { transform: rotate(360deg); }
1584
+ }
1585
+ .dsh-files-empty {
1586
+ display: flex;
1587
+ flex-direction: column;
1588
+ align-items: center;
1589
+ justify-content: center;
1590
+ padding: 36px 16px;
1591
+ text-align: center;
1592
+ color: var(--dsw-alias-label-secondary, #6f7f9b);
1593
+ }
1594
+ .dsh-files-empty-icon {
1595
+ margin-bottom: 8px;
1596
+ opacity: 0.6;
1597
+ }
1598
+ .dsh-files-empty-title {
1599
+ font-weight: 600;
1600
+ font-size: 13px;
1601
+ margin-bottom: 4px;
1602
+ }
1603
+ .dsh-files-empty-detail {
1604
+ font-size: 11.5px;
1605
+ color: var(--dsw-alias-label-tertiary, #8c9ba5);
1606
+ }
1607
+ .dsh-files-banner-error {
1608
+ display: flex;
1609
+ align-items: center;
1610
+ justify-content: space-between;
1611
+ gap: 8px;
1612
+ padding: 6px 12px;
1613
+ font-size: 11.5px;
1614
+ color: var(--dsw-alias-state-error-primary, #e34c59);
1615
+ background: color-mix(in srgb, var(--dsw-alias-state-error-primary, #e34c59) 10%, transparent);
1616
+ border-bottom: 1px solid var(--dsw-alias-border-l1, rgba(118,137,166,.2));
1617
+ }
1618
+ .dsh-files-banner-close {
1619
+ border: none;
1620
+ background: transparent;
1621
+ color: inherit;
1622
+ cursor: pointer;
1623
+ }
1624
+ ` });
1625
+ }
1626
+ //#endregion
1627
+ exports.apply = apply;
1628
+ exports.inject = inject;
1629
+ return module.exports;
1630
+ }
1631
+ });
1632
+
1633
+ //# sourceMappingURL=client.js.map