@jaxzhou/dsh-file-explorer 0.1.0 → 0.1.3
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 +103 -190
- package/README.zh.md +84 -158
- package/lib/client.js +535 -104
- package/lib/client.js.map +3 -3
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -23,14 +23,17 @@ 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,
|
|
28
|
+
canExportPdf: () => canExportPdf,
|
|
27
29
|
extensionOf: () => extensionOf,
|
|
28
30
|
hasSourceToggle: () => hasSourceToggle,
|
|
29
31
|
inject: () => inject,
|
|
30
32
|
name: () => name,
|
|
31
33
|
parseJsonDocument: () => parseJsonDocument,
|
|
32
34
|
previewFormatFor: () => previewFormatFor,
|
|
33
|
-
previewLines: () => previewLines
|
|
35
|
+
previewLines: () => previewLines,
|
|
36
|
+
tabLabels: () => tabLabels
|
|
34
37
|
});
|
|
35
38
|
module.exports = __toCommonJS(index_exports);
|
|
36
39
|
|
|
@@ -98,10 +101,14 @@ function previewFormatFor(path) {
|
|
|
98
101
|
if (lang === void 0) return { kind: "text", lang: void 0, mediaType: void 0 };
|
|
99
102
|
if (lang === "json") return { kind: "json", lang, mediaType: void 0 };
|
|
100
103
|
if (lang === "markdown") return { kind: "markdown", lang, mediaType: void 0 };
|
|
104
|
+
if (lang === "html") return { kind: "html", lang, mediaType: void 0 };
|
|
101
105
|
return { kind: "code", lang, mediaType: void 0 };
|
|
102
106
|
}
|
|
103
107
|
function hasSourceToggle(format) {
|
|
104
|
-
return format.kind === "markdown" || format.kind === "json";
|
|
108
|
+
return format.kind === "markdown" || format.kind === "html" || format.kind === "json";
|
|
109
|
+
}
|
|
110
|
+
function canExportPdf(format) {
|
|
111
|
+
return format.kind === "markdown" || format.kind === "html";
|
|
105
112
|
}
|
|
106
113
|
|
|
107
114
|
// src/client/FilesView.tsx
|
|
@@ -121,6 +128,24 @@ function pathParts(path) {
|
|
|
121
128
|
if (at < 0) return { directory: "", name: path };
|
|
122
129
|
return { directory: path.slice(0, at + 1), name: path.slice(at + 1) };
|
|
123
130
|
}
|
|
131
|
+
function tabLabels(open) {
|
|
132
|
+
const twins = /* @__PURE__ */ new Map();
|
|
133
|
+
for (const path of open) {
|
|
134
|
+
const { name: name2 } = pathParts(path);
|
|
135
|
+
twins.set(name2, (twins.get(name2) ?? 0) + 1);
|
|
136
|
+
}
|
|
137
|
+
const labels = {};
|
|
138
|
+
for (const path of open) {
|
|
139
|
+
const { directory, name: name2 } = pathParts(path);
|
|
140
|
+
if ((twins.get(name2) ?? 0) < 2) {
|
|
141
|
+
labels[path] = name2;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const parent = pathParts(directory.replace(/[/\\]+$/, "")).name;
|
|
145
|
+
labels[path] = parent === "" ? name2 : `${parent}/${name2}`;
|
|
146
|
+
}
|
|
147
|
+
return labels;
|
|
148
|
+
}
|
|
124
149
|
function previewLines(page) {
|
|
125
150
|
return page.lines === 0 ? [] : page.text.split("\n");
|
|
126
151
|
}
|
|
@@ -213,21 +238,32 @@ function Entry({
|
|
|
213
238
|
] });
|
|
214
239
|
}
|
|
215
240
|
if (entry.type === "file") {
|
|
216
|
-
|
|
217
|
-
|
|
241
|
+
const isOpen = tree.open.has(path);
|
|
242
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
243
|
+
"li",
|
|
218
244
|
{
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
"
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
245
|
+
className: "dsh-fe-item",
|
|
246
|
+
"data-files-entry": "file",
|
|
247
|
+
"data-files-path": path,
|
|
248
|
+
"data-files-open": isOpen || void 0,
|
|
249
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
250
|
+
"button",
|
|
251
|
+
{
|
|
252
|
+
type: "button",
|
|
253
|
+
className: "dsh-fe-row",
|
|
254
|
+
"aria-current": tree.state.active === path,
|
|
255
|
+
onClick: () => {
|
|
256
|
+
tree.onOpen(path);
|
|
257
|
+
},
|
|
258
|
+
children: [
|
|
259
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.FileTypeIcon, { kind: (0, import_dsh_client_ui_primitives.classifyFileType)(entry.name), size: 16 }),
|
|
260
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-fe-name", children: entry.name }),
|
|
261
|
+
isOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-fe-open-dot", "aria-hidden": "true" })
|
|
262
|
+
]
|
|
263
|
+
}
|
|
264
|
+
)
|
|
229
265
|
}
|
|
230
|
-
)
|
|
266
|
+
);
|
|
231
267
|
}
|
|
232
268
|
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
269
|
}
|
|
@@ -248,10 +284,30 @@ function HighlightedSource({
|
|
|
248
284
|
}
|
|
249
285
|
) });
|
|
250
286
|
}
|
|
287
|
+
function HtmlFrame({ html, title }) {
|
|
288
|
+
const url = (0, import_react.useMemo)(
|
|
289
|
+
() => URL.createObjectURL(new Blob([html], { type: "text/html" })),
|
|
290
|
+
[html]
|
|
291
|
+
);
|
|
292
|
+
(0, import_react.useEffect)(() => () => {
|
|
293
|
+
URL.revokeObjectURL(url);
|
|
294
|
+
}, [url]);
|
|
295
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
296
|
+
"iframe",
|
|
297
|
+
{
|
|
298
|
+
className: "dsh-fe-html-frame",
|
|
299
|
+
src: url,
|
|
300
|
+
sandbox: "allow-scripts",
|
|
301
|
+
title,
|
|
302
|
+
"data-preview-html": true
|
|
303
|
+
}
|
|
304
|
+
);
|
|
305
|
+
}
|
|
251
306
|
function FormattedBody({
|
|
252
307
|
page,
|
|
253
308
|
format,
|
|
254
309
|
jsonDocument,
|
|
310
|
+
proseRef,
|
|
255
311
|
t
|
|
256
312
|
}) {
|
|
257
313
|
const copyLabel = t("code.copy");
|
|
@@ -296,7 +352,10 @@ function FormattedBody({
|
|
|
296
352
|
t
|
|
297
353
|
]);
|
|
298
354
|
if (format.kind === "markdown") {
|
|
299
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-fe-prose", "data-preview-format": "markdown", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.MarkdownText, { text: page.text, labels: markdownLabels }) });
|
|
355
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-fe-prose", ref: proseRef, "data-preview-format": "markdown", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.MarkdownText, { text: page.text, labels: markdownLabels }) });
|
|
356
|
+
}
|
|
357
|
+
if (format.kind === "html") {
|
|
358
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-fe-html", "data-preview-format": "html", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HtmlFrame, { html: page.text, title: t("preview.htmlFrame") }) });
|
|
300
359
|
}
|
|
301
360
|
if (jsonDocument === void 0) return null;
|
|
302
361
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-fe-json", "data-preview-format": "json", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
@@ -309,17 +368,81 @@ function FormattedBody({
|
|
|
309
368
|
}
|
|
310
369
|
) });
|
|
311
370
|
}
|
|
371
|
+
function printableHtml(source, title) {
|
|
372
|
+
const parsed = new DOMParser().parseFromString(source, "text/html");
|
|
373
|
+
for (const script of [...parsed.querySelectorAll("script")]) script.remove();
|
|
374
|
+
for (const element of [...parsed.querySelectorAll("*")]) {
|
|
375
|
+
for (const attribute of [...element.attributes]) {
|
|
376
|
+
const name2 = attribute.name.toLowerCase();
|
|
377
|
+
if (name2.startsWith("on")) element.removeAttribute(attribute.name);
|
|
378
|
+
else if (/^\s*javascript:/i.test(attribute.value)) element.removeAttribute(attribute.name);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (parsed.title === "") {
|
|
382
|
+
const tag = parsed.createElement("title");
|
|
383
|
+
tag.textContent = title;
|
|
384
|
+
parsed.head.append(tag);
|
|
385
|
+
}
|
|
386
|
+
return `<!doctype html>${parsed.documentElement.outerHTML}`;
|
|
387
|
+
}
|
|
388
|
+
function appStyleTags() {
|
|
389
|
+
return [...document.querySelectorAll('style, link[rel="stylesheet"]')].map((node) => node.outerHTML).join("\n");
|
|
390
|
+
}
|
|
391
|
+
function styledDocument(title, body) {
|
|
392
|
+
const safeTitle = title.replace(/[<&]/g, (character) => character === "<" ? "<" : "&");
|
|
393
|
+
return [
|
|
394
|
+
'<!doctype html><html><head><meta charset="utf-8">',
|
|
395
|
+
`<title>${safeTitle}</title>`,
|
|
396
|
+
appStyleTags(),
|
|
397
|
+
"<style>body{margin:0;padding:28px 32px;background:#fff;color:#111;",
|
|
398
|
+
"print-color-adjust:exact;-webkit-print-color-adjust:exact}</style>",
|
|
399
|
+
`</head><body>${body}</body></html>`
|
|
400
|
+
].join("");
|
|
401
|
+
}
|
|
402
|
+
function createPrintFrame() {
|
|
403
|
+
const frame = document.createElement("iframe");
|
|
404
|
+
frame.setAttribute("aria-hidden", "true");
|
|
405
|
+
frame.setAttribute("data-preview-print", "");
|
|
406
|
+
frame.style.cssText = "position:fixed;left:-10000px;top:0;width:794px;height:1123px;border:0";
|
|
407
|
+
document.body.appendChild(frame);
|
|
408
|
+
return frame;
|
|
409
|
+
}
|
|
410
|
+
function printDocument(html) {
|
|
411
|
+
for (const stale of document.querySelectorAll("iframe[data-preview-print]")) stale.remove();
|
|
412
|
+
const frame = createPrintFrame();
|
|
413
|
+
const doc = frame.contentDocument;
|
|
414
|
+
if (doc === null) return;
|
|
415
|
+
doc.open();
|
|
416
|
+
doc.write(html);
|
|
417
|
+
doc.close();
|
|
418
|
+
const view = frame.contentWindow;
|
|
419
|
+
if (view === null) return;
|
|
420
|
+
view.addEventListener("afterprint", () => {
|
|
421
|
+
frame.remove();
|
|
422
|
+
}, { once: true });
|
|
423
|
+
view.focus();
|
|
424
|
+
view.print();
|
|
425
|
+
}
|
|
426
|
+
function bodyKindOf(content, format, mode, jsonWalkable) {
|
|
427
|
+
if (content !== null && content.kind === "image") return "image";
|
|
428
|
+
if (!hasSourceToggle(format)) return "source";
|
|
429
|
+
if ((mode ?? "rendered") === "source") return "source";
|
|
430
|
+
if (format.kind === "json" && !jsonWalkable) return "source";
|
|
431
|
+
return "rendered";
|
|
432
|
+
}
|
|
312
433
|
function PreviewBody({
|
|
313
|
-
|
|
434
|
+
path,
|
|
314
435
|
content,
|
|
315
436
|
format,
|
|
316
437
|
body,
|
|
317
438
|
jsonDocument,
|
|
318
439
|
jsonFallback,
|
|
440
|
+
wrap,
|
|
441
|
+
proseRef,
|
|
319
442
|
t
|
|
320
443
|
}) {
|
|
321
444
|
if (content.kind === "image") {
|
|
322
|
-
const { name: name2 } = pathParts(
|
|
445
|
+
const { name: name2 } = pathParts(path);
|
|
323
446
|
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
447
|
}
|
|
325
448
|
const page = content.page;
|
|
@@ -346,19 +469,21 @@ function PreviewBody({
|
|
|
346
469
|
"data-preview-body": body,
|
|
347
470
|
children: [
|
|
348
471
|
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)(
|
|
472
|
+
body === "rendered" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
473
|
+
FormattedBody,
|
|
474
|
+
{
|
|
475
|
+
page,
|
|
476
|
+
format,
|
|
477
|
+
jsonDocument,
|
|
478
|
+
proseRef,
|
|
479
|
+
t
|
|
480
|
+
}
|
|
481
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HighlightedSource, { page, lang: format.lang, wrap, t }),
|
|
350
482
|
!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
483
|
]
|
|
352
484
|
}
|
|
353
485
|
);
|
|
354
486
|
}
|
|
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
487
|
function FilesView({
|
|
363
488
|
sessionId,
|
|
364
489
|
useSessions,
|
|
@@ -370,32 +495,50 @@ function FilesView({
|
|
|
370
495
|
}) {
|
|
371
496
|
const cwd = useSessions((sessions) => sessions.byId[sessionId]?.cwd);
|
|
372
497
|
const state = useStore((store) => store);
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
if (
|
|
379
|
-
|
|
380
|
-
}, [state.mode, state.preview, state.selected]);
|
|
498
|
+
const activeTabRef = (0, import_react.useRef)(null);
|
|
499
|
+
const proseRef = (0, import_react.useRef)(null);
|
|
500
|
+
const [copied, setCopied] = (0, import_react.useState)(null);
|
|
501
|
+
const copyTimer = (0, import_react.useRef)(null);
|
|
502
|
+
(0, import_react.useEffect)(() => () => {
|
|
503
|
+
if (copyTimer.current !== null) clearTimeout(copyTimer.current);
|
|
504
|
+
}, []);
|
|
381
505
|
(0, import_react.useEffect)(() => {
|
|
382
506
|
if (cwd === void 0 || state.root === cwd) return;
|
|
383
507
|
actions.start(cwd);
|
|
384
508
|
list(cwd);
|
|
385
509
|
}, [actions, cwd, list, state.root]);
|
|
510
|
+
const active = state.active;
|
|
511
|
+
const format = active === null ? null : previewFormatFor(active);
|
|
512
|
+
const content = active !== null && state.previews[active]?.kind === "ready" ? state.previews[active].content : null;
|
|
513
|
+
const jsonDocument = (0, import_react.useMemo)(() => {
|
|
514
|
+
if (active === null || content === null || content.kind !== "text") return void 0;
|
|
515
|
+
if ((state.modes[active] ?? "rendered") === "source") return void 0;
|
|
516
|
+
if (previewFormatFor(active).kind !== "json") return void 0;
|
|
517
|
+
return parseJsonDocument(content.page.text);
|
|
518
|
+
}, [active, content, state.modes]);
|
|
519
|
+
(0, import_react.useEffect)(() => {
|
|
520
|
+
if (active === null) return;
|
|
521
|
+
if (state.previews[active] !== void 0) return;
|
|
522
|
+
read(active);
|
|
523
|
+
}, [active, read, state.previews]);
|
|
524
|
+
(0, import_react.useEffect)(() => {
|
|
525
|
+
activeTabRef.current?.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
526
|
+
}, [active]);
|
|
386
527
|
if (cwd === void 0) {
|
|
387
528
|
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
529
|
}
|
|
389
530
|
if (state.root === null) return null;
|
|
531
|
+
const openSet = new Set(state.open);
|
|
390
532
|
const tree = {
|
|
391
533
|
state,
|
|
534
|
+
open: openSet,
|
|
392
535
|
onToggle: (path) => {
|
|
393
536
|
const loaded = state.levels[path] !== void 0;
|
|
394
537
|
actions.toggled(path);
|
|
395
538
|
if (!loaded) list(path);
|
|
396
539
|
},
|
|
397
540
|
onOpen: (path) => {
|
|
398
|
-
|
|
541
|
+
actions.openFile(path);
|
|
399
542
|
},
|
|
400
543
|
t
|
|
401
544
|
};
|
|
@@ -404,25 +547,46 @@ function FilesView({
|
|
|
404
547
|
for (const path of state.expanded) list(path);
|
|
405
548
|
};
|
|
406
549
|
const reloadPreview = () => {
|
|
407
|
-
if (
|
|
550
|
+
if (active !== null) read(active);
|
|
551
|
+
};
|
|
552
|
+
const copyActive = () => {
|
|
553
|
+
if (active === null || content === null || content.kind !== "text") return;
|
|
554
|
+
const path = active;
|
|
555
|
+
const text = content.page.text;
|
|
556
|
+
void (0, import_dsh_client_ui_primitives.writeClipboard)(text).then((ok) => {
|
|
557
|
+
if (!ok) return;
|
|
558
|
+
setCopied(path);
|
|
559
|
+
if (copyTimer.current !== null) clearTimeout(copyTimer.current);
|
|
560
|
+
copyTimer.current = setTimeout(() => {
|
|
561
|
+
setCopied(null);
|
|
562
|
+
}, 1e3);
|
|
563
|
+
});
|
|
564
|
+
};
|
|
565
|
+
const exportPdf = () => {
|
|
566
|
+
if (active === null || format === null || content === null || content.kind !== "text") return;
|
|
567
|
+
if (!canExportPdf(format)) return;
|
|
568
|
+
const name2 = pathParts(active).name;
|
|
569
|
+
if (format.kind === "html") {
|
|
570
|
+
printDocument(printableHtml(content.page.text, name2));
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
const prose = proseRef.current;
|
|
574
|
+
if (prose === null) return;
|
|
575
|
+
printDocument(styledDocument(name2, prose.outerHTML));
|
|
576
|
+
};
|
|
577
|
+
const closeTab = (path) => {
|
|
578
|
+
actions.closeFile(path);
|
|
408
579
|
};
|
|
409
580
|
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);
|
|
581
|
+
const labels = tabLabels(state.open);
|
|
582
|
+
const wrap = active === null ? true : state.wraps[active] ?? true;
|
|
583
|
+
const preview = active === null ? void 0 : state.previews[active];
|
|
584
|
+
const body = bodyKindOf(content, format ?? { kind: "text", lang: void 0, mediaType: void 0 }, active === null ? void 0 : state.modes[active], jsonDocument !== void 0);
|
|
585
|
+
const jsonFallback = format?.kind === "json" && (active === null ? "rendered" : state.modes[active] ?? "rendered") === "rendered" && jsonDocument === void 0 && content !== null && content.kind === "text";
|
|
586
|
+
const meta = content === null ? null : content.kind === "text" ? [
|
|
587
|
+
t("preview.lines", { lines: content.page.lines }),
|
|
588
|
+
content.page.bytes === void 0 ? null : (0, import_dsh_client_ui_primitives.fileSizeText)(content.page.bytes)
|
|
589
|
+
].filter((part) => part !== null).join(" \xB7 ") : content.image.bytes === void 0 ? null : (0, import_dsh_client_ui_primitives.fileSizeText)(content.image.bytes);
|
|
426
590
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
427
591
|
"div",
|
|
428
592
|
{
|
|
@@ -453,13 +617,63 @@ function FilesView({
|
|
|
453
617
|
/* @__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
618
|
] }),
|
|
455
619
|
/* @__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
|
-
|
|
620
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-fe-head dsh-fe-tabhead", children: [
|
|
621
|
+
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) => {
|
|
622
|
+
const isActive = path === active;
|
|
623
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dsh-fe-tab", role: "presentation", "data-preview-tab": path, "data-active": isActive || void 0, children: [
|
|
624
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
625
|
+
"button",
|
|
626
|
+
{
|
|
627
|
+
type: "button",
|
|
628
|
+
role: "tab",
|
|
629
|
+
"aria-selected": isActive,
|
|
630
|
+
className: "dsh-fe-tab-label",
|
|
631
|
+
title: path,
|
|
632
|
+
ref: isActive ? activeTabRef : void 0,
|
|
633
|
+
"data-preview-tab-open": path,
|
|
634
|
+
onClick: () => {
|
|
635
|
+
actions.activate(path);
|
|
636
|
+
},
|
|
637
|
+
onAuxClick: (event) => {
|
|
638
|
+
if (event.button === 1) closeTab(path);
|
|
639
|
+
},
|
|
640
|
+
children: [
|
|
641
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.FileTypeIcon, { kind: (0, import_dsh_client_ui_primitives.classifyFileType)(path), size: 14 }),
|
|
642
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-fe-tab-name", children: labels[path] })
|
|
643
|
+
]
|
|
644
|
+
}
|
|
645
|
+
),
|
|
646
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
647
|
+
"button",
|
|
648
|
+
{
|
|
649
|
+
type: "button",
|
|
650
|
+
className: "dsh-fe-tab-close",
|
|
651
|
+
"aria-label": t("preview.close", { name: labels[path] }),
|
|
652
|
+
title: t("preview.close", { name: labels[path] }),
|
|
653
|
+
"data-preview-tab-close": path,
|
|
654
|
+
onClick: () => {
|
|
655
|
+
closeTab(path);
|
|
656
|
+
},
|
|
657
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: "\xD7" })
|
|
658
|
+
}
|
|
659
|
+
)
|
|
660
|
+
] }, path);
|
|
661
|
+
}) }),
|
|
662
|
+
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 }),
|
|
663
|
+
content !== null && meta !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-fe-meta", "data-preview-meta": true, children: meta }),
|
|
664
|
+
content !== null && content.kind === "text" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
665
|
+
"button",
|
|
666
|
+
{
|
|
667
|
+
type: "button",
|
|
668
|
+
className: "dsh-fe-tool",
|
|
669
|
+
"aria-label": t("preview.copy"),
|
|
670
|
+
title: t("preview.copy"),
|
|
671
|
+
"data-preview-copy": true,
|
|
672
|
+
onClick: copyActive,
|
|
673
|
+
children: copied === active ? t("preview.copied") : t("preview.copy")
|
|
674
|
+
}
|
|
675
|
+
),
|
|
676
|
+
active !== null && format !== null && hasSourceToggle(format) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
463
677
|
"button",
|
|
464
678
|
{
|
|
465
679
|
type: "button",
|
|
@@ -469,27 +683,39 @@ function FilesView({
|
|
|
469
683
|
title: body === "source" ? t("preview.rendered") : t("preview.source"),
|
|
470
684
|
"data-preview-mode-toggle": true,
|
|
471
685
|
onClick: () => {
|
|
472
|
-
actions.setMode(body === "source" ? "rendered" : "source");
|
|
686
|
+
actions.setMode(active, body === "source" ? "rendered" : "source");
|
|
473
687
|
},
|
|
474
688
|
children: body === "source" ? t("preview.rendered") : t("preview.source")
|
|
475
689
|
}
|
|
476
690
|
),
|
|
477
|
-
body === "
|
|
691
|
+
active !== null && body === "rendered" && format !== null && canExportPdf(format) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
478
692
|
"button",
|
|
479
693
|
{
|
|
480
694
|
type: "button",
|
|
481
695
|
className: "dsh-fe-tool",
|
|
482
|
-
"aria-
|
|
483
|
-
|
|
484
|
-
|
|
696
|
+
"aria-label": t("preview.pdf"),
|
|
697
|
+
title: t("preview.pdf"),
|
|
698
|
+
"data-preview-pdf": true,
|
|
699
|
+
onClick: exportPdf,
|
|
700
|
+
children: t("preview.pdf")
|
|
701
|
+
}
|
|
702
|
+
),
|
|
703
|
+
active !== null && body === "source" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
704
|
+
"button",
|
|
705
|
+
{
|
|
706
|
+
type: "button",
|
|
707
|
+
className: "dsh-fe-tool",
|
|
708
|
+
"aria-pressed": wrap,
|
|
709
|
+
"aria-label": wrap ? t("preview.nowrap") : t("preview.wrap"),
|
|
710
|
+
title: wrap ? t("preview.nowrap") : t("preview.wrap"),
|
|
485
711
|
"data-preview-wrap-toggle": true,
|
|
486
712
|
onClick: () => {
|
|
487
|
-
actions.setWrap(!
|
|
713
|
+
actions.setWrap(active, !wrap);
|
|
488
714
|
},
|
|
489
|
-
children:
|
|
715
|
+
children: wrap ? "\u21B5" : "\u2192"
|
|
490
716
|
}
|
|
491
717
|
),
|
|
492
|
-
|
|
718
|
+
active !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
493
719
|
"button",
|
|
494
720
|
{
|
|
495
721
|
type: "button",
|
|
@@ -502,21 +728,23 @@ function FilesView({
|
|
|
502
728
|
}
|
|
503
729
|
)
|
|
504
730
|
] }),
|
|
505
|
-
|
|
506
|
-
preview.kind === "
|
|
731
|
+
active === null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-fe-status", children: t("preview.placeholder") }),
|
|
732
|
+
active !== null && (preview === void 0 || preview.kind === "loading") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-fe-status", children: t("preview.loading") }),
|
|
733
|
+
active !== null && preview?.kind === "failed" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-fe-scroll", "data-preview-state": "failed", children: [
|
|
507
734
|
/* @__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
735
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "dsh-fe-tool", onClick: reloadPreview, children: t("preview.reload") })
|
|
509
736
|
] }),
|
|
510
|
-
|
|
511
|
-
preview.kind === "ready" && content !== null && format !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
737
|
+
active !== null && content !== null && format !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
512
738
|
PreviewBody,
|
|
513
739
|
{
|
|
514
|
-
|
|
740
|
+
path: active,
|
|
515
741
|
content,
|
|
516
742
|
format,
|
|
517
743
|
body,
|
|
518
744
|
jsonDocument,
|
|
519
745
|
jsonFallback,
|
|
746
|
+
wrap,
|
|
747
|
+
proseRef,
|
|
520
748
|
t
|
|
521
749
|
}
|
|
522
750
|
)
|
|
@@ -529,16 +757,16 @@ function FilesView({
|
|
|
529
757
|
// src/client/face.ts
|
|
530
758
|
var PREVIEW_LINES = 2e3;
|
|
531
759
|
function filesFace(remote, sessionId, signal, actions) {
|
|
532
|
-
const
|
|
533
|
-
|
|
760
|
+
const listingGenerations = /* @__PURE__ */ new Map();
|
|
761
|
+
const readGenerations = /* @__PURE__ */ new Map();
|
|
534
762
|
return {
|
|
535
763
|
list(path) {
|
|
536
764
|
if (signal.aborted) return;
|
|
537
|
-
const generation = (
|
|
538
|
-
|
|
765
|
+
const generation = (listingGenerations.get(path) ?? 0) + 1;
|
|
766
|
+
listingGenerations.set(path, generation);
|
|
539
767
|
actions.loading(path);
|
|
540
768
|
void remote.workspaceFiles.list(sessionId, path, signal).then((result) => {
|
|
541
|
-
if (signal.aborted ||
|
|
769
|
+
if (signal.aborted || listingGenerations.get(path) !== generation) return;
|
|
542
770
|
if (result.ok) {
|
|
543
771
|
actions.loaded(path, {
|
|
544
772
|
entries: result.value.entries,
|
|
@@ -551,12 +779,12 @@ function filesFace(remote, sessionId, signal, actions) {
|
|
|
551
779
|
},
|
|
552
780
|
read(path) {
|
|
553
781
|
if (signal.aborted) return;
|
|
554
|
-
|
|
555
|
-
|
|
782
|
+
const generation = (readGenerations.get(path) ?? 0) + 1;
|
|
783
|
+
readGenerations.set(path, generation);
|
|
556
784
|
const format = previewFormatFor(path);
|
|
557
|
-
actions.
|
|
785
|
+
actions.reading(path);
|
|
558
786
|
const settle = (write) => {
|
|
559
|
-
if (signal.aborted ||
|
|
787
|
+
if (signal.aborted || readGenerations.get(path) !== generation) return;
|
|
560
788
|
write();
|
|
561
789
|
};
|
|
562
790
|
if (format.kind === "image") {
|
|
@@ -631,7 +859,13 @@ var zh = {
|
|
|
631
859
|
"json.expandNode": "\u5C55\u5F00",
|
|
632
860
|
"json.copyTitle": "{action}",
|
|
633
861
|
"preview.title": "\u9884\u89C8",
|
|
862
|
+
"preview.tabs": "\u6253\u5F00\u7684\u6587\u4EF6",
|
|
863
|
+
"preview.close": "\u5173\u95ED {name}",
|
|
634
864
|
"preview.loading": "\u6B63\u5728\u8BFB\u53D6\u2026",
|
|
865
|
+
"preview.copy": "\u590D\u5236",
|
|
866
|
+
"preview.copied": "\u5DF2\u590D\u5236",
|
|
867
|
+
"preview.pdf": "\u5BFC\u51FA PDF",
|
|
868
|
+
"preview.htmlFrame": "HTML \u9884\u89C8",
|
|
635
869
|
"preview.reload": "\u91CD\u65B0\u8BFB\u53D6",
|
|
636
870
|
"preview.truncated": "\u6587\u4EF6\u8F83\u957F\uFF0C\u53EA\u663E\u793A\u524D {lines} \u884C\u3002",
|
|
637
871
|
"preview.wrap": "\u81EA\u52A8\u6362\u884C",
|
|
@@ -676,7 +910,13 @@ var en = {
|
|
|
676
910
|
"json.expandNode": "Expand",
|
|
677
911
|
"json.copyTitle": "{action}",
|
|
678
912
|
"preview.title": "Preview",
|
|
913
|
+
"preview.tabs": "Open files",
|
|
914
|
+
"preview.close": "Close {name}",
|
|
679
915
|
"preview.loading": "Reading\u2026",
|
|
916
|
+
"preview.copy": "Copy",
|
|
917
|
+
"preview.copied": "Copied",
|
|
918
|
+
"preview.pdf": "Export PDF",
|
|
919
|
+
"preview.htmlFrame": "HTML preview",
|
|
680
920
|
"preview.reload": "Reload",
|
|
681
921
|
"preview.truncated": "This file is long, showing the first {lines} lines.",
|
|
682
922
|
"preview.wrap": "Wrap long lines",
|
|
@@ -691,27 +931,36 @@ var en = {
|
|
|
691
931
|
|
|
692
932
|
// src/client/store.ts
|
|
693
933
|
var import_dsh_client_store = require("@deepseek-ai/dsh-client-store");
|
|
934
|
+
var RETAINED_PREVIEWS = 5;
|
|
694
935
|
function emptyState() {
|
|
695
936
|
return {
|
|
696
937
|
root: null,
|
|
697
938
|
levels: {},
|
|
698
939
|
expanded: [],
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
940
|
+
open: [],
|
|
941
|
+
active: null,
|
|
942
|
+
previews: {},
|
|
943
|
+
retained: [],
|
|
944
|
+
modes: {},
|
|
945
|
+
wraps: {}
|
|
703
946
|
};
|
|
704
947
|
}
|
|
948
|
+
function retain(d, path) {
|
|
949
|
+
d.retained = [path, ...d.retained.filter((candidate) => candidate !== path)];
|
|
950
|
+
const keep = /* @__PURE__ */ new Set([...d.active === null ? [] : [d.active], ...d.retained.slice(0, RETAINED_PREVIEWS)]);
|
|
951
|
+
for (const candidate of Object.keys(d.previews)) {
|
|
952
|
+
if (!keep.has(candidate)) delete d.previews[candidate];
|
|
953
|
+
}
|
|
954
|
+
d.retained = d.retained.filter((candidate) => keep.has(candidate));
|
|
955
|
+
}
|
|
705
956
|
function createFilesStore() {
|
|
706
957
|
return (0, import_dsh_client_store.defineStore)({
|
|
707
958
|
init: emptyState,
|
|
708
959
|
actions: {
|
|
709
960
|
start: (d, root) => {
|
|
961
|
+
Object.assign(d, emptyState());
|
|
710
962
|
d.root = root;
|
|
711
|
-
d.levels = {};
|
|
712
963
|
d.expanded = [root];
|
|
713
|
-
d.selected = null;
|
|
714
|
-
d.preview = { kind: "idle" };
|
|
715
964
|
},
|
|
716
965
|
loading: (d, path) => {
|
|
717
966
|
d.levels[path] = { kind: "loading" };
|
|
@@ -730,28 +979,48 @@ function createFilesStore() {
|
|
|
730
979
|
reset: (d) => {
|
|
731
980
|
d.levels = {};
|
|
732
981
|
},
|
|
733
|
-
|
|
734
|
-
d.
|
|
735
|
-
d.
|
|
736
|
-
|
|
982
|
+
openFile: (d, path) => {
|
|
983
|
+
if (!d.open.includes(path)) d.open.push(path);
|
|
984
|
+
d.active = path;
|
|
985
|
+
},
|
|
986
|
+
activate: (d, path) => {
|
|
987
|
+
if (!d.open.includes(path)) return;
|
|
988
|
+
d.active = path;
|
|
989
|
+
retain(d, path);
|
|
990
|
+
},
|
|
991
|
+
closeFile: (d, path) => {
|
|
992
|
+
const at = d.open.indexOf(path);
|
|
993
|
+
if (at < 0) return;
|
|
994
|
+
d.open.splice(at, 1);
|
|
995
|
+
delete d.previews[path];
|
|
996
|
+
delete d.modes[path];
|
|
997
|
+
delete d.wraps[path];
|
|
998
|
+
d.retained = d.retained.filter((candidate) => candidate !== path);
|
|
999
|
+
if (d.active !== path) return;
|
|
1000
|
+
d.active = d.open[at] ?? d.open[at - 1] ?? null;
|
|
1001
|
+
},
|
|
1002
|
+
reading: (d, path) => {
|
|
1003
|
+
if (!d.open.includes(path)) return;
|
|
1004
|
+
d.previews[path] = { kind: "loading" };
|
|
737
1005
|
},
|
|
738
1006
|
previewLoaded: (d, path, content) => {
|
|
739
|
-
if (d.
|
|
740
|
-
d.
|
|
1007
|
+
if (!d.open.includes(path)) return;
|
|
1008
|
+
d.previews[path] = { kind: "ready", content };
|
|
1009
|
+
retain(d, path);
|
|
741
1010
|
},
|
|
742
1011
|
previewFailed: (d, path, failure) => {
|
|
743
|
-
if (d.
|
|
744
|
-
d.
|
|
1012
|
+
if (!d.open.includes(path)) return;
|
|
1013
|
+
d.previews[path] = { kind: "failed", failure };
|
|
745
1014
|
},
|
|
746
|
-
|
|
747
|
-
d.
|
|
748
|
-
d.
|
|
1015
|
+
dropPreview: (d, path) => {
|
|
1016
|
+
delete d.previews[path];
|
|
1017
|
+
d.retained = d.retained.filter((candidate) => candidate !== path);
|
|
749
1018
|
},
|
|
750
|
-
setWrap: (d, wrap) => {
|
|
751
|
-
d.
|
|
1019
|
+
setWrap: (d, path, wrap) => {
|
|
1020
|
+
d.wraps[path] = wrap;
|
|
752
1021
|
},
|
|
753
|
-
setMode: (d, mode) => {
|
|
754
|
-
d.
|
|
1022
|
+
setMode: (d, path, mode) => {
|
|
1023
|
+
d.modes[path] = mode;
|
|
755
1024
|
}
|
|
756
1025
|
}
|
|
757
1026
|
});
|
|
@@ -809,9 +1078,26 @@ var CSS = `
|
|
|
809
1078
|
box-sizing: border-box;
|
|
810
1079
|
height: 38px;
|
|
811
1080
|
padding: 0 6px 0 14px;
|
|
1081
|
+
/* The row is filled rather than transparent, so the pane's chrome reads as
|
|
1082
|
+
chrome instead of as the first line of the file. The palette's layer tokens
|
|
1083
|
+
are all pure white in light mode (bg-base, bg-layer-1/2/3 share a value), so
|
|
1084
|
+
the only token that separates a surface from the content in BOTH themes is
|
|
1085
|
+
this wash: 4% ink in light, 8% in dark. See the note in CONTRIBUTING.md. */
|
|
1086
|
+
background: var(--dsw-alias-bg-skeleton);
|
|
812
1087
|
border-bottom: 0.5px solid var(--dsw-alias-border-l3);
|
|
813
1088
|
}
|
|
814
1089
|
|
|
1090
|
+
/* A grammar badge for the source body, which is where the language used to be
|
|
1091
|
+
named: the code block's own banner is hidden in this pane so there is exactly
|
|
1092
|
+
one copy control, in this row. */
|
|
1093
|
+
.dsh-fe-lang {
|
|
1094
|
+
flex: 0 0 auto;
|
|
1095
|
+
color: var(--dsw-alias-label-tertiary);
|
|
1096
|
+
font-family: var(--ds-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
1097
|
+
font-size: 11px;
|
|
1098
|
+
white-space: nowrap;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
815
1101
|
.dsh-fe-path {
|
|
816
1102
|
flex: 1 1 auto;
|
|
817
1103
|
min-width: 0;
|
|
@@ -974,7 +1260,15 @@ var CSS = `
|
|
|
974
1260
|
margin: 0;
|
|
975
1261
|
}
|
|
976
1262
|
|
|
977
|
-
|
|
1263
|
+
/* One copy control per pane, in the header row above: the code block's own
|
|
1264
|
+
banner would be a second, inside the scrollport, and it scrolls away. The
|
|
1265
|
+
grammar it named is shown as the header's badge instead. Markdown fences keep
|
|
1266
|
+
their banners \u2014 there, a banner belongs to the fence, not to the file.
|
|
1267
|
+
|
|
1268
|
+
The banner sits inside a sticky wrapper, hence the descendant match plus the
|
|
1269
|
+
:has rule that takes the wrapper out with it. */
|
|
1270
|
+
.dsh-fe-source [data-code-block-banner],
|
|
1271
|
+
.dsh-fe-source .md-code-block > div:has(> [data-code-block-banner]) {
|
|
978
1272
|
display: none;
|
|
979
1273
|
}
|
|
980
1274
|
|
|
@@ -999,6 +1293,143 @@ var CSS = `
|
|
|
999
1293
|
overflow-wrap: break-word;
|
|
1000
1294
|
}
|
|
1001
1295
|
|
|
1296
|
+
/* The preview pane's header row carries the open tabs and, at its end, the
|
|
1297
|
+
active tab's controls. The tabs take the room that is left and scroll; the
|
|
1298
|
+
controls never shrink away. */
|
|
1299
|
+
.dsh-fe-tabhead {
|
|
1300
|
+
gap: 8px;
|
|
1301
|
+
padding: 0 6px 0 8px;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
.dsh-fe-tabs {
|
|
1305
|
+
display: flex;
|
|
1306
|
+
flex: 1 1 auto;
|
|
1307
|
+
gap: 2px;
|
|
1308
|
+
align-items: stretch;
|
|
1309
|
+
min-width: 90px;
|
|
1310
|
+
height: 100%;
|
|
1311
|
+
overflow-x: auto;
|
|
1312
|
+
overflow-y: hidden;
|
|
1313
|
+
scrollbar-width: none;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
.dsh-fe-tabs::-webkit-scrollbar {
|
|
1317
|
+
height: 0;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
.dsh-fe-tab {
|
|
1321
|
+
display: flex;
|
|
1322
|
+
flex: 0 0 auto;
|
|
1323
|
+
align-items: center;
|
|
1324
|
+
min-width: 0;
|
|
1325
|
+
max-width: 220px;
|
|
1326
|
+
border-radius: 8px;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
.dsh-fe-tab:hover {
|
|
1330
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
.dsh-fe-tab[data-active] {
|
|
1334
|
+
background: var(--dsw-alias-interactive-bg-active);
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
.dsh-fe-tab-label {
|
|
1338
|
+
display: flex;
|
|
1339
|
+
gap: 6px;
|
|
1340
|
+
align-items: center;
|
|
1341
|
+
min-width: 0;
|
|
1342
|
+
padding: 5px 4px 5px 8px;
|
|
1343
|
+
color: var(--dsw-alias-label-secondary);
|
|
1344
|
+
font: inherit;
|
|
1345
|
+
font-size: 12px;
|
|
1346
|
+
line-height: 1;
|
|
1347
|
+
background: transparent;
|
|
1348
|
+
border: 0;
|
|
1349
|
+
border-radius: 8px;
|
|
1350
|
+
cursor: pointer;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
.dsh-fe-tab[data-active] .dsh-fe-tab-label {
|
|
1354
|
+
color: var(--dsw-alias-label-primary);
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
.dsh-fe-tab-name {
|
|
1358
|
+
min-width: 0;
|
|
1359
|
+
overflow: hidden;
|
|
1360
|
+
white-space: nowrap;
|
|
1361
|
+
text-overflow: ellipsis;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
/* The close control appears for the tab under the pointer and for the active
|
|
1365
|
+
tab, so a full strip stays readable. */
|
|
1366
|
+
.dsh-fe-tab-close {
|
|
1367
|
+
display: flex;
|
|
1368
|
+
flex: 0 0 auto;
|
|
1369
|
+
align-items: center;
|
|
1370
|
+
justify-content: center;
|
|
1371
|
+
width: 18px;
|
|
1372
|
+
height: 18px;
|
|
1373
|
+
margin-right: 5px;
|
|
1374
|
+
padding: 0;
|
|
1375
|
+
color: var(--dsw-alias-label-tertiary);
|
|
1376
|
+
font: inherit;
|
|
1377
|
+
font-size: 14px;
|
|
1378
|
+
line-height: 1;
|
|
1379
|
+
background: transparent;
|
|
1380
|
+
border: 0;
|
|
1381
|
+
border-radius: 5px;
|
|
1382
|
+
cursor: pointer;
|
|
1383
|
+
opacity: 0;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
.dsh-fe-tab:hover .dsh-fe-tab-close,
|
|
1387
|
+
.dsh-fe-tab[data-active] .dsh-fe-tab-close,
|
|
1388
|
+
.dsh-fe-tab-close:focus-visible {
|
|
1389
|
+
opacity: 1;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
.dsh-fe-tab-close:hover {
|
|
1393
|
+
color: var(--dsw-alias-label-primary);
|
|
1394
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
/* A file already open in a tab keeps a quiet marker in the tree, so the reader
|
|
1398
|
+
can see where the strip came from without it competing with the active row. */
|
|
1399
|
+
.dsh-fe-open-dot {
|
|
1400
|
+
flex: 0 0 auto;
|
|
1401
|
+
width: 5px;
|
|
1402
|
+
height: 5px;
|
|
1403
|
+
margin-left: auto;
|
|
1404
|
+
background: var(--dsw-alias-brand-primary);
|
|
1405
|
+
border-radius: 50%;
|
|
1406
|
+
opacity: 0.6;
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
/* An HTML file is drawn by the browser, in a frame that fills the pane. The
|
|
1410
|
+
white canvas is the page's own, not this pane's: an unstyled document still
|
|
1411
|
+
reads as a document rather than as a hole in the app.
|
|
1412
|
+
|
|
1413
|
+
The host takes its height from the scrollport's content box, so the frame ends
|
|
1414
|
+
exactly where the pane's bottom inset begins and the scrollport itself never
|
|
1415
|
+
scrolls \u2014 a page scrolls inside its own frame. */
|
|
1416
|
+
.dsh-fe-html {
|
|
1417
|
+
display: flex;
|
|
1418
|
+
height: 100%;
|
|
1419
|
+
min-height: 0;
|
|
1420
|
+
min-width: 0;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
.dsh-fe-html-frame {
|
|
1424
|
+
flex: 1 1 auto;
|
|
1425
|
+
width: 100%;
|
|
1426
|
+
height: 100%;
|
|
1427
|
+
min-width: 0;
|
|
1428
|
+
min-height: 0;
|
|
1429
|
+
border: 0;
|
|
1430
|
+
background: #fff;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1002
1433
|
/* Rendered Markdown: prose lays out in normal white space, with the pane's
|
|
1003
1434
|
insets and no monospace inheritance from the source view. */
|
|
1004
1435
|
.dsh-fe-prose {
|