@jaxzhou/dsh-file-explorer 0.1.0 → 0.1.2
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 +91 -190
- package/README.zh.md +73 -155
- package/lib/client.js +386 -102
- package/lib/client.js.map +3 -3
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -23,6 +23,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
23
23
|
var index_exports = {};
|
|
24
24
|
__export(index_exports, {
|
|
25
25
|
FILES_VIEW_ID: () => FILES_VIEW_ID,
|
|
26
|
+
RETAINED_PREVIEWS: () => RETAINED_PREVIEWS,
|
|
26
27
|
apply: () => apply,
|
|
27
28
|
extensionOf: () => extensionOf,
|
|
28
29
|
hasSourceToggle: () => hasSourceToggle,
|
|
@@ -30,7 +31,8 @@ __export(index_exports, {
|
|
|
30
31
|
name: () => name,
|
|
31
32
|
parseJsonDocument: () => parseJsonDocument,
|
|
32
33
|
previewFormatFor: () => previewFormatFor,
|
|
33
|
-
previewLines: () => previewLines
|
|
34
|
+
previewLines: () => previewLines,
|
|
35
|
+
tabLabels: () => tabLabels
|
|
34
36
|
});
|
|
35
37
|
module.exports = __toCommonJS(index_exports);
|
|
36
38
|
|
|
@@ -121,6 +123,24 @@ function pathParts(path) {
|
|
|
121
123
|
if (at < 0) return { directory: "", name: path };
|
|
122
124
|
return { directory: path.slice(0, at + 1), name: path.slice(at + 1) };
|
|
123
125
|
}
|
|
126
|
+
function tabLabels(open) {
|
|
127
|
+
const twins = /* @__PURE__ */ new Map();
|
|
128
|
+
for (const path of open) {
|
|
129
|
+
const { name: name2 } = pathParts(path);
|
|
130
|
+
twins.set(name2, (twins.get(name2) ?? 0) + 1);
|
|
131
|
+
}
|
|
132
|
+
const labels = {};
|
|
133
|
+
for (const path of open) {
|
|
134
|
+
const { directory, name: name2 } = pathParts(path);
|
|
135
|
+
if ((twins.get(name2) ?? 0) < 2) {
|
|
136
|
+
labels[path] = name2;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const parent = pathParts(directory.replace(/[/\\]+$/, "")).name;
|
|
140
|
+
labels[path] = parent === "" ? name2 : `${parent}/${name2}`;
|
|
141
|
+
}
|
|
142
|
+
return labels;
|
|
143
|
+
}
|
|
124
144
|
function previewLines(page) {
|
|
125
145
|
return page.lines === 0 ? [] : page.text.split("\n");
|
|
126
146
|
}
|
|
@@ -213,21 +233,32 @@ function Entry({
|
|
|
213
233
|
] });
|
|
214
234
|
}
|
|
215
235
|
if (entry.type === "file") {
|
|
216
|
-
|
|
217
|
-
|
|
236
|
+
const isOpen = tree.open.has(path);
|
|
237
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
238
|
+
"li",
|
|
218
239
|
{
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
"
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
240
|
+
className: "dsh-fe-item",
|
|
241
|
+
"data-files-entry": "file",
|
|
242
|
+
"data-files-path": path,
|
|
243
|
+
"data-files-open": isOpen || void 0,
|
|
244
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
245
|
+
"button",
|
|
246
|
+
{
|
|
247
|
+
type: "button",
|
|
248
|
+
className: "dsh-fe-row",
|
|
249
|
+
"aria-current": tree.state.active === path,
|
|
250
|
+
onClick: () => {
|
|
251
|
+
tree.onOpen(path);
|
|
252
|
+
},
|
|
253
|
+
children: [
|
|
254
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.FileTypeIcon, { kind: (0, import_dsh_client_ui_primitives.classifyFileType)(entry.name), size: 16 }),
|
|
255
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-fe-name", children: entry.name }),
|
|
256
|
+
isOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-fe-open-dot", "aria-hidden": "true" })
|
|
257
|
+
]
|
|
258
|
+
}
|
|
259
|
+
)
|
|
229
260
|
}
|
|
230
|
-
)
|
|
261
|
+
);
|
|
231
262
|
}
|
|
232
263
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { className: "dsh-fe-item", "data-files-entry": "other", "data-files-path": path, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-fe-row dsh-fe-row-static", title: tree.t("tree.other"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-fe-name", children: entry.name }) }) });
|
|
233
264
|
}
|
|
@@ -309,17 +340,25 @@ function FormattedBody({
|
|
|
309
340
|
}
|
|
310
341
|
) });
|
|
311
342
|
}
|
|
343
|
+
function bodyKindOf(content, format, mode, jsonWalkable) {
|
|
344
|
+
if (content !== null && content.kind === "image") return "image";
|
|
345
|
+
if (!hasSourceToggle(format)) return "source";
|
|
346
|
+
if ((mode ?? "rendered") === "source") return "source";
|
|
347
|
+
if (format.kind === "json" && !jsonWalkable) return "source";
|
|
348
|
+
return "rendered";
|
|
349
|
+
}
|
|
312
350
|
function PreviewBody({
|
|
313
|
-
|
|
351
|
+
path,
|
|
314
352
|
content,
|
|
315
353
|
format,
|
|
316
354
|
body,
|
|
317
355
|
jsonDocument,
|
|
318
356
|
jsonFallback,
|
|
357
|
+
wrap,
|
|
319
358
|
t
|
|
320
359
|
}) {
|
|
321
360
|
if (content.kind === "image") {
|
|
322
|
-
const { name: name2 } = pathParts(
|
|
361
|
+
const { name: name2 } = pathParts(path);
|
|
323
362
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-fe-scroll dsh-fe-imagehost", "data-preview-state": "ready", "data-preview-format": "image", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { className: "dsh-fe-image", src: content.image.dataUrl, alt: name2, "data-preview-image": true }) });
|
|
324
363
|
}
|
|
325
364
|
const page = content.page;
|
|
@@ -346,19 +385,12 @@ function PreviewBody({
|
|
|
346
385
|
"data-preview-body": body,
|
|
347
386
|
children: [
|
|
348
387
|
jsonFallback && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsh-fe-note dsh-fe-note-error", "data-preview-row": "invalid-json", children: t("preview.jsonInvalid") }),
|
|
349
|
-
body === "rendered" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FormattedBody, { page, format, jsonDocument, t }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HighlightedSource, { page, lang: format.lang, wrap
|
|
388
|
+
body === "rendered" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FormattedBody, { page, format, jsonDocument, t }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HighlightedSource, { page, lang: format.lang, wrap, t }),
|
|
350
389
|
!page.eof && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsh-fe-note", "data-preview-row": "truncated", children: t("preview.truncated", { lines: page.lines }) })
|
|
351
390
|
]
|
|
352
391
|
}
|
|
353
392
|
);
|
|
354
393
|
}
|
|
355
|
-
function bodyKindOf(content, format, mode, jsonWalkable) {
|
|
356
|
-
if (content !== null && content.kind === "image") return "image";
|
|
357
|
-
if (!hasSourceToggle(format)) return "source";
|
|
358
|
-
if ((mode ?? "rendered") === "source") return "source";
|
|
359
|
-
if (format.kind === "json" && !jsonWalkable) return "source";
|
|
360
|
-
return "rendered";
|
|
361
|
-
}
|
|
362
394
|
function FilesView({
|
|
363
395
|
sessionId,
|
|
364
396
|
useSessions,
|
|
@@ -370,32 +402,49 @@ function FilesView({
|
|
|
370
402
|
}) {
|
|
371
403
|
const cwd = useSessions((sessions) => sessions.byId[sessionId]?.cwd);
|
|
372
404
|
const state = useStore((store) => store);
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
return parseJsonDocument(preview2.content.page.text);
|
|
380
|
-
}, [state.mode, state.preview, state.selected]);
|
|
405
|
+
const activeTabRef = (0, import_react.useRef)(null);
|
|
406
|
+
const [copied, setCopied] = (0, import_react.useState)(null);
|
|
407
|
+
const copyTimer = (0, import_react.useRef)(null);
|
|
408
|
+
(0, import_react.useEffect)(() => () => {
|
|
409
|
+
if (copyTimer.current !== null) clearTimeout(copyTimer.current);
|
|
410
|
+
}, []);
|
|
381
411
|
(0, import_react.useEffect)(() => {
|
|
382
412
|
if (cwd === void 0 || state.root === cwd) return;
|
|
383
413
|
actions.start(cwd);
|
|
384
414
|
list(cwd);
|
|
385
415
|
}, [actions, cwd, list, state.root]);
|
|
416
|
+
const active = state.active;
|
|
417
|
+
const format = active === null ? null : previewFormatFor(active);
|
|
418
|
+
const content = active !== null && state.previews[active]?.kind === "ready" ? state.previews[active].content : null;
|
|
419
|
+
const jsonDocument = (0, import_react.useMemo)(() => {
|
|
420
|
+
if (active === null || content === null || content.kind !== "text") return void 0;
|
|
421
|
+
if ((state.modes[active] ?? "rendered") === "source") return void 0;
|
|
422
|
+
if (previewFormatFor(active).kind !== "json") return void 0;
|
|
423
|
+
return parseJsonDocument(content.page.text);
|
|
424
|
+
}, [active, content, state.modes]);
|
|
425
|
+
(0, import_react.useEffect)(() => {
|
|
426
|
+
if (active === null) return;
|
|
427
|
+
if (state.previews[active] !== void 0) return;
|
|
428
|
+
read(active);
|
|
429
|
+
}, [active, read, state.previews]);
|
|
430
|
+
(0, import_react.useEffect)(() => {
|
|
431
|
+
activeTabRef.current?.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
432
|
+
}, [active]);
|
|
386
433
|
if (cwd === void 0) {
|
|
387
434
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-fe-root", "data-files-state": "no-workspace", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-fe-status", children: t("tree.noWorkspace") }) });
|
|
388
435
|
}
|
|
389
436
|
if (state.root === null) return null;
|
|
437
|
+
const openSet = new Set(state.open);
|
|
390
438
|
const tree = {
|
|
391
439
|
state,
|
|
440
|
+
open: openSet,
|
|
392
441
|
onToggle: (path) => {
|
|
393
442
|
const loaded = state.levels[path] !== void 0;
|
|
394
443
|
actions.toggled(path);
|
|
395
444
|
if (!loaded) list(path);
|
|
396
445
|
},
|
|
397
446
|
onOpen: (path) => {
|
|
398
|
-
|
|
447
|
+
actions.openFile(path);
|
|
399
448
|
},
|
|
400
449
|
t
|
|
401
450
|
};
|
|
@@ -404,25 +453,34 @@ function FilesView({
|
|
|
404
453
|
for (const path of state.expanded) list(path);
|
|
405
454
|
};
|
|
406
455
|
const reloadPreview = () => {
|
|
407
|
-
if (
|
|
456
|
+
if (active !== null) read(active);
|
|
457
|
+
};
|
|
458
|
+
const copyActive = () => {
|
|
459
|
+
if (active === null || content === null || content.kind !== "text") return;
|
|
460
|
+
const path = active;
|
|
461
|
+
const text = content.page.text;
|
|
462
|
+
void (0, import_dsh_client_ui_primitives.writeClipboard)(text).then((ok) => {
|
|
463
|
+
if (!ok) return;
|
|
464
|
+
setCopied(path);
|
|
465
|
+
if (copyTimer.current !== null) clearTimeout(copyTimer.current);
|
|
466
|
+
copyTimer.current = setTimeout(() => {
|
|
467
|
+
setCopied(null);
|
|
468
|
+
}, 1e3);
|
|
469
|
+
});
|
|
470
|
+
};
|
|
471
|
+
const closeTab = (path) => {
|
|
472
|
+
actions.closeFile(path);
|
|
408
473
|
};
|
|
409
474
|
const root = pathParts(state.root);
|
|
410
|
-
const
|
|
411
|
-
const
|
|
412
|
-
const
|
|
413
|
-
const
|
|
414
|
-
const
|
|
415
|
-
const
|
|
416
|
-
content,
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
jsonDocument !== void 0
|
|
420
|
-
);
|
|
421
|
-
const jsonFallback = format?.kind === "json" && (state.mode ?? "rendered") === "rendered" && jsonDocument === void 0 && content !== null && content.kind === "text";
|
|
422
|
-
const meta = preview.kind !== "ready" ? null : preview.content.kind === "text" ? [
|
|
423
|
-
t("preview.lines", { lines: preview.content.page.lines }),
|
|
424
|
-
preview.content.page.bytes === void 0 ? null : (0, import_dsh_client_ui_primitives.fileSizeText)(preview.content.page.bytes)
|
|
425
|
-
].filter((part) => part !== null).join(" \xB7 ") : preview.content.image.bytes === void 0 ? null : (0, import_dsh_client_ui_primitives.fileSizeText)(preview.content.image.bytes);
|
|
475
|
+
const labels = tabLabels(state.open);
|
|
476
|
+
const wrap = active === null ? true : state.wraps[active] ?? true;
|
|
477
|
+
const preview = active === null ? void 0 : state.previews[active];
|
|
478
|
+
const body = bodyKindOf(content, format ?? { kind: "text", lang: void 0, mediaType: void 0 }, active === null ? void 0 : state.modes[active], jsonDocument !== void 0);
|
|
479
|
+
const jsonFallback = format?.kind === "json" && (active === null ? "rendered" : state.modes[active] ?? "rendered") === "rendered" && jsonDocument === void 0 && content !== null && content.kind === "text";
|
|
480
|
+
const meta = content === null ? null : content.kind === "text" ? [
|
|
481
|
+
t("preview.lines", { lines: content.page.lines }),
|
|
482
|
+
content.page.bytes === void 0 ? null : (0, import_dsh_client_ui_primitives.fileSizeText)(content.page.bytes)
|
|
483
|
+
].filter((part) => part !== null).join(" \xB7 ") : content.image.bytes === void 0 ? null : (0, import_dsh_client_ui_primitives.fileSizeText)(content.image.bytes);
|
|
426
484
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
427
485
|
"div",
|
|
428
486
|
{
|
|
@@ -453,13 +511,63 @@ function FilesView({
|
|
|
453
511
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-fe-scroll", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "dsh-fe-level", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Level, { path: state.root, tree }) }) })
|
|
454
512
|
] }),
|
|
455
513
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-fe-preview", "data-files-pane": "preview", children: [
|
|
456
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-fe-head", children: [
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
514
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-fe-head dsh-fe-tabhead", children: [
|
|
515
|
+
state.open.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-fe-path dsh-fe-path-muted", children: t("preview.title") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-fe-tabs", role: "tablist", "aria-label": t("preview.tabs"), "data-preview-tabs": true, children: state.open.map((path) => {
|
|
516
|
+
const isActive = path === active;
|
|
517
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dsh-fe-tab", role: "presentation", "data-preview-tab": path, "data-active": isActive || void 0, children: [
|
|
518
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
519
|
+
"button",
|
|
520
|
+
{
|
|
521
|
+
type: "button",
|
|
522
|
+
role: "tab",
|
|
523
|
+
"aria-selected": isActive,
|
|
524
|
+
className: "dsh-fe-tab-label",
|
|
525
|
+
title: path,
|
|
526
|
+
ref: isActive ? activeTabRef : void 0,
|
|
527
|
+
"data-preview-tab-open": path,
|
|
528
|
+
onClick: () => {
|
|
529
|
+
actions.activate(path);
|
|
530
|
+
},
|
|
531
|
+
onAuxClick: (event) => {
|
|
532
|
+
if (event.button === 1) closeTab(path);
|
|
533
|
+
},
|
|
534
|
+
children: [
|
|
535
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.FileTypeIcon, { kind: (0, import_dsh_client_ui_primitives.classifyFileType)(path), size: 14 }),
|
|
536
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-fe-tab-name", children: labels[path] })
|
|
537
|
+
]
|
|
538
|
+
}
|
|
539
|
+
),
|
|
540
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
541
|
+
"button",
|
|
542
|
+
{
|
|
543
|
+
type: "button",
|
|
544
|
+
className: "dsh-fe-tab-close",
|
|
545
|
+
"aria-label": t("preview.close", { name: labels[path] }),
|
|
546
|
+
title: t("preview.close", { name: labels[path] }),
|
|
547
|
+
"data-preview-tab-close": path,
|
|
548
|
+
onClick: () => {
|
|
549
|
+
closeTab(path);
|
|
550
|
+
},
|
|
551
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: "\xD7" })
|
|
552
|
+
}
|
|
553
|
+
)
|
|
554
|
+
] }, path);
|
|
555
|
+
}) }),
|
|
556
|
+
active !== null && body === "source" && format?.lang !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-fe-lang", "data-preview-lang-badge": true, children: format.lang }),
|
|
557
|
+
content !== null && meta !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-fe-meta", "data-preview-meta": true, children: meta }),
|
|
558
|
+
content !== null && content.kind === "text" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
559
|
+
"button",
|
|
560
|
+
{
|
|
561
|
+
type: "button",
|
|
562
|
+
className: "dsh-fe-tool",
|
|
563
|
+
"aria-label": t("preview.copy"),
|
|
564
|
+
title: t("preview.copy"),
|
|
565
|
+
"data-preview-copy": true,
|
|
566
|
+
onClick: copyActive,
|
|
567
|
+
children: copied === active ? t("preview.copied") : t("preview.copy")
|
|
568
|
+
}
|
|
569
|
+
),
|
|
570
|
+
active !== null && format !== null && hasSourceToggle(format) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
463
571
|
"button",
|
|
464
572
|
{
|
|
465
573
|
type: "button",
|
|
@@ -469,27 +577,27 @@ function FilesView({
|
|
|
469
577
|
title: body === "source" ? t("preview.rendered") : t("preview.source"),
|
|
470
578
|
"data-preview-mode-toggle": true,
|
|
471
579
|
onClick: () => {
|
|
472
|
-
actions.setMode(body === "source" ? "rendered" : "source");
|
|
580
|
+
actions.setMode(active, body === "source" ? "rendered" : "source");
|
|
473
581
|
},
|
|
474
582
|
children: body === "source" ? t("preview.rendered") : t("preview.source")
|
|
475
583
|
}
|
|
476
584
|
),
|
|
477
|
-
body === "source" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
585
|
+
active !== null && body === "source" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
478
586
|
"button",
|
|
479
587
|
{
|
|
480
588
|
type: "button",
|
|
481
589
|
className: "dsh-fe-tool",
|
|
482
|
-
"aria-pressed":
|
|
483
|
-
"aria-label":
|
|
484
|
-
title:
|
|
590
|
+
"aria-pressed": wrap,
|
|
591
|
+
"aria-label": wrap ? t("preview.nowrap") : t("preview.wrap"),
|
|
592
|
+
title: wrap ? t("preview.nowrap") : t("preview.wrap"),
|
|
485
593
|
"data-preview-wrap-toggle": true,
|
|
486
594
|
onClick: () => {
|
|
487
|
-
actions.setWrap(!
|
|
595
|
+
actions.setWrap(active, !wrap);
|
|
488
596
|
},
|
|
489
|
-
children:
|
|
597
|
+
children: wrap ? "\u21B5" : "\u2192"
|
|
490
598
|
}
|
|
491
599
|
),
|
|
492
|
-
|
|
600
|
+
active !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
493
601
|
"button",
|
|
494
602
|
{
|
|
495
603
|
type: "button",
|
|
@@ -502,21 +610,22 @@ function FilesView({
|
|
|
502
610
|
}
|
|
503
611
|
)
|
|
504
612
|
] }),
|
|
505
|
-
|
|
506
|
-
preview.kind === "
|
|
613
|
+
active === null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-fe-status", children: t("preview.placeholder") }),
|
|
614
|
+
active !== null && (preview === void 0 || preview.kind === "loading") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-fe-status", children: t("preview.loading") }),
|
|
615
|
+
active !== null && preview?.kind === "failed" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-fe-scroll", "data-preview-state": "failed", children: [
|
|
507
616
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsh-fe-note dsh-fe-note-error", "data-preview-code": preview.failure.code, children: previewFailureLine(t, preview.failure) }),
|
|
508
617
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "dsh-fe-tool", onClick: reloadPreview, children: t("preview.reload") })
|
|
509
618
|
] }),
|
|
510
|
-
|
|
511
|
-
preview.kind === "ready" && content !== null && format !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
619
|
+
active !== null && content !== null && format !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
512
620
|
PreviewBody,
|
|
513
621
|
{
|
|
514
|
-
|
|
622
|
+
path: active,
|
|
515
623
|
content,
|
|
516
624
|
format,
|
|
517
625
|
body,
|
|
518
626
|
jsonDocument,
|
|
519
627
|
jsonFallback,
|
|
628
|
+
wrap,
|
|
520
629
|
t
|
|
521
630
|
}
|
|
522
631
|
)
|
|
@@ -529,16 +638,16 @@ function FilesView({
|
|
|
529
638
|
// src/client/face.ts
|
|
530
639
|
var PREVIEW_LINES = 2e3;
|
|
531
640
|
function filesFace(remote, sessionId, signal, actions) {
|
|
532
|
-
const
|
|
533
|
-
|
|
641
|
+
const listingGenerations = /* @__PURE__ */ new Map();
|
|
642
|
+
const readGenerations = /* @__PURE__ */ new Map();
|
|
534
643
|
return {
|
|
535
644
|
list(path) {
|
|
536
645
|
if (signal.aborted) return;
|
|
537
|
-
const generation = (
|
|
538
|
-
|
|
646
|
+
const generation = (listingGenerations.get(path) ?? 0) + 1;
|
|
647
|
+
listingGenerations.set(path, generation);
|
|
539
648
|
actions.loading(path);
|
|
540
649
|
void remote.workspaceFiles.list(sessionId, path, signal).then((result) => {
|
|
541
|
-
if (signal.aborted ||
|
|
650
|
+
if (signal.aborted || listingGenerations.get(path) !== generation) return;
|
|
542
651
|
if (result.ok) {
|
|
543
652
|
actions.loaded(path, {
|
|
544
653
|
entries: result.value.entries,
|
|
@@ -551,12 +660,12 @@ function filesFace(remote, sessionId, signal, actions) {
|
|
|
551
660
|
},
|
|
552
661
|
read(path) {
|
|
553
662
|
if (signal.aborted) return;
|
|
554
|
-
|
|
555
|
-
|
|
663
|
+
const generation = (readGenerations.get(path) ?? 0) + 1;
|
|
664
|
+
readGenerations.set(path, generation);
|
|
556
665
|
const format = previewFormatFor(path);
|
|
557
|
-
actions.
|
|
666
|
+
actions.reading(path);
|
|
558
667
|
const settle = (write) => {
|
|
559
|
-
if (signal.aborted ||
|
|
668
|
+
if (signal.aborted || readGenerations.get(path) !== generation) return;
|
|
560
669
|
write();
|
|
561
670
|
};
|
|
562
671
|
if (format.kind === "image") {
|
|
@@ -631,7 +740,11 @@ var zh = {
|
|
|
631
740
|
"json.expandNode": "\u5C55\u5F00",
|
|
632
741
|
"json.copyTitle": "{action}",
|
|
633
742
|
"preview.title": "\u9884\u89C8",
|
|
743
|
+
"preview.tabs": "\u6253\u5F00\u7684\u6587\u4EF6",
|
|
744
|
+
"preview.close": "\u5173\u95ED {name}",
|
|
634
745
|
"preview.loading": "\u6B63\u5728\u8BFB\u53D6\u2026",
|
|
746
|
+
"preview.copy": "\u590D\u5236",
|
|
747
|
+
"preview.copied": "\u5DF2\u590D\u5236",
|
|
635
748
|
"preview.reload": "\u91CD\u65B0\u8BFB\u53D6",
|
|
636
749
|
"preview.truncated": "\u6587\u4EF6\u8F83\u957F\uFF0C\u53EA\u663E\u793A\u524D {lines} \u884C\u3002",
|
|
637
750
|
"preview.wrap": "\u81EA\u52A8\u6362\u884C",
|
|
@@ -676,7 +789,11 @@ var en = {
|
|
|
676
789
|
"json.expandNode": "Expand",
|
|
677
790
|
"json.copyTitle": "{action}",
|
|
678
791
|
"preview.title": "Preview",
|
|
792
|
+
"preview.tabs": "Open files",
|
|
793
|
+
"preview.close": "Close {name}",
|
|
679
794
|
"preview.loading": "Reading\u2026",
|
|
795
|
+
"preview.copy": "Copy",
|
|
796
|
+
"preview.copied": "Copied",
|
|
680
797
|
"preview.reload": "Reload",
|
|
681
798
|
"preview.truncated": "This file is long, showing the first {lines} lines.",
|
|
682
799
|
"preview.wrap": "Wrap long lines",
|
|
@@ -691,27 +808,36 @@ var en = {
|
|
|
691
808
|
|
|
692
809
|
// src/client/store.ts
|
|
693
810
|
var import_dsh_client_store = require("@deepseek-ai/dsh-client-store");
|
|
811
|
+
var RETAINED_PREVIEWS = 5;
|
|
694
812
|
function emptyState() {
|
|
695
813
|
return {
|
|
696
814
|
root: null,
|
|
697
815
|
levels: {},
|
|
698
816
|
expanded: [],
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
817
|
+
open: [],
|
|
818
|
+
active: null,
|
|
819
|
+
previews: {},
|
|
820
|
+
retained: [],
|
|
821
|
+
modes: {},
|
|
822
|
+
wraps: {}
|
|
703
823
|
};
|
|
704
824
|
}
|
|
825
|
+
function retain(d, path) {
|
|
826
|
+
d.retained = [path, ...d.retained.filter((candidate) => candidate !== path)];
|
|
827
|
+
const keep = /* @__PURE__ */ new Set([...d.active === null ? [] : [d.active], ...d.retained.slice(0, RETAINED_PREVIEWS)]);
|
|
828
|
+
for (const candidate of Object.keys(d.previews)) {
|
|
829
|
+
if (!keep.has(candidate)) delete d.previews[candidate];
|
|
830
|
+
}
|
|
831
|
+
d.retained = d.retained.filter((candidate) => keep.has(candidate));
|
|
832
|
+
}
|
|
705
833
|
function createFilesStore() {
|
|
706
834
|
return (0, import_dsh_client_store.defineStore)({
|
|
707
835
|
init: emptyState,
|
|
708
836
|
actions: {
|
|
709
837
|
start: (d, root) => {
|
|
838
|
+
Object.assign(d, emptyState());
|
|
710
839
|
d.root = root;
|
|
711
|
-
d.levels = {};
|
|
712
840
|
d.expanded = [root];
|
|
713
|
-
d.selected = null;
|
|
714
|
-
d.preview = { kind: "idle" };
|
|
715
841
|
},
|
|
716
842
|
loading: (d, path) => {
|
|
717
843
|
d.levels[path] = { kind: "loading" };
|
|
@@ -730,28 +856,48 @@ function createFilesStore() {
|
|
|
730
856
|
reset: (d) => {
|
|
731
857
|
d.levels = {};
|
|
732
858
|
},
|
|
733
|
-
|
|
734
|
-
d.
|
|
735
|
-
d.
|
|
736
|
-
|
|
859
|
+
openFile: (d, path) => {
|
|
860
|
+
if (!d.open.includes(path)) d.open.push(path);
|
|
861
|
+
d.active = path;
|
|
862
|
+
},
|
|
863
|
+
activate: (d, path) => {
|
|
864
|
+
if (!d.open.includes(path)) return;
|
|
865
|
+
d.active = path;
|
|
866
|
+
retain(d, path);
|
|
867
|
+
},
|
|
868
|
+
closeFile: (d, path) => {
|
|
869
|
+
const at = d.open.indexOf(path);
|
|
870
|
+
if (at < 0) return;
|
|
871
|
+
d.open.splice(at, 1);
|
|
872
|
+
delete d.previews[path];
|
|
873
|
+
delete d.modes[path];
|
|
874
|
+
delete d.wraps[path];
|
|
875
|
+
d.retained = d.retained.filter((candidate) => candidate !== path);
|
|
876
|
+
if (d.active !== path) return;
|
|
877
|
+
d.active = d.open[at] ?? d.open[at - 1] ?? null;
|
|
878
|
+
},
|
|
879
|
+
reading: (d, path) => {
|
|
880
|
+
if (!d.open.includes(path)) return;
|
|
881
|
+
d.previews[path] = { kind: "loading" };
|
|
737
882
|
},
|
|
738
883
|
previewLoaded: (d, path, content) => {
|
|
739
|
-
if (d.
|
|
740
|
-
d.
|
|
884
|
+
if (!d.open.includes(path)) return;
|
|
885
|
+
d.previews[path] = { kind: "ready", content };
|
|
886
|
+
retain(d, path);
|
|
741
887
|
},
|
|
742
888
|
previewFailed: (d, path, failure) => {
|
|
743
|
-
if (d.
|
|
744
|
-
d.
|
|
889
|
+
if (!d.open.includes(path)) return;
|
|
890
|
+
d.previews[path] = { kind: "failed", failure };
|
|
745
891
|
},
|
|
746
|
-
|
|
747
|
-
d.
|
|
748
|
-
d.
|
|
892
|
+
dropPreview: (d, path) => {
|
|
893
|
+
delete d.previews[path];
|
|
894
|
+
d.retained = d.retained.filter((candidate) => candidate !== path);
|
|
749
895
|
},
|
|
750
|
-
setWrap: (d, wrap) => {
|
|
751
|
-
d.
|
|
896
|
+
setWrap: (d, path, wrap) => {
|
|
897
|
+
d.wraps[path] = wrap;
|
|
752
898
|
},
|
|
753
|
-
setMode: (d, mode) => {
|
|
754
|
-
d.
|
|
899
|
+
setMode: (d, path, mode) => {
|
|
900
|
+
d.modes[path] = mode;
|
|
755
901
|
}
|
|
756
902
|
}
|
|
757
903
|
});
|
|
@@ -809,9 +955,26 @@ var CSS = `
|
|
|
809
955
|
box-sizing: border-box;
|
|
810
956
|
height: 38px;
|
|
811
957
|
padding: 0 6px 0 14px;
|
|
958
|
+
/* The row is filled rather than transparent, so the pane's chrome reads as
|
|
959
|
+
chrome instead of as the first line of the file. The palette's layer tokens
|
|
960
|
+
are all pure white in light mode (bg-base, bg-layer-1/2/3 share a value), so
|
|
961
|
+
the only token that separates a surface from the content in BOTH themes is
|
|
962
|
+
this wash: 4% ink in light, 8% in dark. See the note in CONTRIBUTING.md. */
|
|
963
|
+
background: var(--dsw-alias-bg-skeleton);
|
|
812
964
|
border-bottom: 0.5px solid var(--dsw-alias-border-l3);
|
|
813
965
|
}
|
|
814
966
|
|
|
967
|
+
/* A grammar badge for the source body, which is where the language used to be
|
|
968
|
+
named: the code block's own banner is hidden in this pane so there is exactly
|
|
969
|
+
one copy control, in this row. */
|
|
970
|
+
.dsh-fe-lang {
|
|
971
|
+
flex: 0 0 auto;
|
|
972
|
+
color: var(--dsw-alias-label-tertiary);
|
|
973
|
+
font-family: var(--ds-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
974
|
+
font-size: 11px;
|
|
975
|
+
white-space: nowrap;
|
|
976
|
+
}
|
|
977
|
+
|
|
815
978
|
.dsh-fe-path {
|
|
816
979
|
flex: 1 1 auto;
|
|
817
980
|
min-width: 0;
|
|
@@ -974,7 +1137,15 @@ var CSS = `
|
|
|
974
1137
|
margin: 0;
|
|
975
1138
|
}
|
|
976
1139
|
|
|
977
|
-
|
|
1140
|
+
/* One copy control per pane, in the header row above: the code block's own
|
|
1141
|
+
banner would be a second, inside the scrollport, and it scrolls away. The
|
|
1142
|
+
grammar it named is shown as the header's badge instead. Markdown fences keep
|
|
1143
|
+
their banners \u2014 there, a banner belongs to the fence, not to the file.
|
|
1144
|
+
|
|
1145
|
+
The banner sits inside a sticky wrapper, hence the descendant match plus the
|
|
1146
|
+
:has rule that takes the wrapper out with it. */
|
|
1147
|
+
.dsh-fe-source [data-code-block-banner],
|
|
1148
|
+
.dsh-fe-source .md-code-block > div:has(> [data-code-block-banner]) {
|
|
978
1149
|
display: none;
|
|
979
1150
|
}
|
|
980
1151
|
|
|
@@ -999,6 +1170,119 @@ var CSS = `
|
|
|
999
1170
|
overflow-wrap: break-word;
|
|
1000
1171
|
}
|
|
1001
1172
|
|
|
1173
|
+
/* The preview pane's header row carries the open tabs and, at its end, the
|
|
1174
|
+
active tab's controls. The tabs take the room that is left and scroll; the
|
|
1175
|
+
controls never shrink away. */
|
|
1176
|
+
.dsh-fe-tabhead {
|
|
1177
|
+
gap: 8px;
|
|
1178
|
+
padding: 0 6px 0 8px;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
.dsh-fe-tabs {
|
|
1182
|
+
display: flex;
|
|
1183
|
+
flex: 1 1 auto;
|
|
1184
|
+
gap: 2px;
|
|
1185
|
+
align-items: stretch;
|
|
1186
|
+
min-width: 90px;
|
|
1187
|
+
height: 100%;
|
|
1188
|
+
overflow-x: auto;
|
|
1189
|
+
overflow-y: hidden;
|
|
1190
|
+
scrollbar-width: none;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
.dsh-fe-tabs::-webkit-scrollbar {
|
|
1194
|
+
height: 0;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
.dsh-fe-tab {
|
|
1198
|
+
display: flex;
|
|
1199
|
+
flex: 0 0 auto;
|
|
1200
|
+
align-items: center;
|
|
1201
|
+
min-width: 0;
|
|
1202
|
+
max-width: 220px;
|
|
1203
|
+
border-radius: 8px;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
.dsh-fe-tab:hover {
|
|
1207
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
.dsh-fe-tab[data-active] {
|
|
1211
|
+
background: var(--dsw-alias-interactive-bg-active);
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
.dsh-fe-tab-label {
|
|
1215
|
+
display: flex;
|
|
1216
|
+
gap: 6px;
|
|
1217
|
+
align-items: center;
|
|
1218
|
+
min-width: 0;
|
|
1219
|
+
padding: 5px 4px 5px 8px;
|
|
1220
|
+
color: var(--dsw-alias-label-secondary);
|
|
1221
|
+
font: inherit;
|
|
1222
|
+
font-size: 12px;
|
|
1223
|
+
line-height: 1;
|
|
1224
|
+
background: transparent;
|
|
1225
|
+
border: 0;
|
|
1226
|
+
border-radius: 8px;
|
|
1227
|
+
cursor: pointer;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
.dsh-fe-tab[data-active] .dsh-fe-tab-label {
|
|
1231
|
+
color: var(--dsw-alias-label-primary);
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
.dsh-fe-tab-name {
|
|
1235
|
+
min-width: 0;
|
|
1236
|
+
overflow: hidden;
|
|
1237
|
+
white-space: nowrap;
|
|
1238
|
+
text-overflow: ellipsis;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/* The close control appears for the tab under the pointer and for the active
|
|
1242
|
+
tab, so a full strip stays readable. */
|
|
1243
|
+
.dsh-fe-tab-close {
|
|
1244
|
+
display: flex;
|
|
1245
|
+
flex: 0 0 auto;
|
|
1246
|
+
align-items: center;
|
|
1247
|
+
justify-content: center;
|
|
1248
|
+
width: 18px;
|
|
1249
|
+
height: 18px;
|
|
1250
|
+
margin-right: 5px;
|
|
1251
|
+
padding: 0;
|
|
1252
|
+
color: var(--dsw-alias-label-tertiary);
|
|
1253
|
+
font: inherit;
|
|
1254
|
+
font-size: 14px;
|
|
1255
|
+
line-height: 1;
|
|
1256
|
+
background: transparent;
|
|
1257
|
+
border: 0;
|
|
1258
|
+
border-radius: 5px;
|
|
1259
|
+
cursor: pointer;
|
|
1260
|
+
opacity: 0;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
.dsh-fe-tab:hover .dsh-fe-tab-close,
|
|
1264
|
+
.dsh-fe-tab[data-active] .dsh-fe-tab-close,
|
|
1265
|
+
.dsh-fe-tab-close:focus-visible {
|
|
1266
|
+
opacity: 1;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
.dsh-fe-tab-close:hover {
|
|
1270
|
+
color: var(--dsw-alias-label-primary);
|
|
1271
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
/* A file already open in a tab keeps a quiet marker in the tree, so the reader
|
|
1275
|
+
can see where the strip came from without it competing with the active row. */
|
|
1276
|
+
.dsh-fe-open-dot {
|
|
1277
|
+
flex: 0 0 auto;
|
|
1278
|
+
width: 5px;
|
|
1279
|
+
height: 5px;
|
|
1280
|
+
margin-left: auto;
|
|
1281
|
+
background: var(--dsw-alias-brand-primary);
|
|
1282
|
+
border-radius: 50%;
|
|
1283
|
+
opacity: 0.6;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1002
1286
|
/* Rendered Markdown: prose lays out in normal white space, with the pane's
|
|
1003
1287
|
insets and no monospace inheritance from the source view. */
|
|
1004
1288
|
.dsh-fe-prose {
|