dsh-flyout-sidebar 0.1.2 → 0.1.4
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 +43 -9
- package/README_EN.md +44 -9
- package/dist/client.js +1127 -276
- package/dist/index.js +483 -113
- package/package.json +4 -4
- package/snapshots/flyout-file-preview.png +0 -0
- package/snapshots/sidebar-file-preview.png +0 -0
- package/snapshots/sidebar.png +0 -0
package/dist/client.js
CHANGED
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
ctx = context;
|
|
24
24
|
}
|
|
25
25
|
async function getJson(url) {
|
|
26
|
-
|
|
26
|
+
const res = await fetch(url);
|
|
27
|
+
if (!res.ok) throw new Error("HTTP " + res.status + " " + url);
|
|
28
|
+
return res.json();
|
|
27
29
|
}
|
|
28
30
|
/** 组装查询串:空值参数不下发(与旧版 URL 形态一致) */
|
|
29
31
|
function qs(...pairs) {
|
|
@@ -38,11 +40,17 @@
|
|
|
38
40
|
gitDiff(path, sessionId) {
|
|
39
41
|
return getJson("/flyout-sidebar/gitdiff" + qs(["path", path], ["sessionId", sessionId]));
|
|
40
42
|
},
|
|
41
|
-
readArtifact(path) {
|
|
42
|
-
return getJson("/flyout-sidebar/content" + qs(["path", path]));
|
|
43
|
+
readArtifact(path, sessionId) {
|
|
44
|
+
return getJson("/flyout-sidebar/content" + qs(["path", path], ["sessionId", sessionId]));
|
|
43
45
|
},
|
|
44
46
|
listDir(path, sessionId) {
|
|
45
47
|
return getJson("/flyout-sidebar/listdir" + qs(["path", path], ["sessionId", sessionId]));
|
|
48
|
+
},
|
|
49
|
+
searchFiles(query, sessionId) {
|
|
50
|
+
return getJson("/flyout-sidebar/search" + qs(["q", query], ["sessionId", sessionId]));
|
|
51
|
+
},
|
|
52
|
+
openInEditor(path, sessionId) {
|
|
53
|
+
return getJson("/flyout-sidebar/open" + qs(["path", path], ["sessionId", sessionId]));
|
|
46
54
|
}
|
|
47
55
|
};
|
|
48
56
|
//#endregion
|
|
@@ -121,7 +129,7 @@ body[data-dsh-flyout-dragging] .artifacts-preview-overlay { transition: none; }
|
|
|
121
129
|
.artifacts-ptab-close:hover { background: var(--dsw-alias-interactive-bg-hover-accent, rgba(0, 0, 0, 0.08)); }
|
|
122
130
|
.artifacts-preview-hide {
|
|
123
131
|
flex: none; width: 32px; display: inline-flex; align-items: center; justify-content: center;
|
|
124
|
-
border: none; border-
|
|
132
|
+
border: none; border-right: 1px solid var(--dsw-alias-border-l1); background: transparent;
|
|
125
133
|
color: var(--dsw-alias-label-secondary); cursor: pointer; padding: 0;
|
|
126
134
|
}
|
|
127
135
|
.artifacts-preview-hide:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
|
|
@@ -156,6 +164,7 @@ body[data-dsh-flyout-dragging] .artifacts-preview-overlay { transition: none; }
|
|
|
156
164
|
color: var(--dsw-alias-label-secondary); cursor: pointer;
|
|
157
165
|
}
|
|
158
166
|
.artifacts-toggle:hover { color: var(--dsw-alias-label-primary); }
|
|
167
|
+
.artifacts-toggle.is-active { color: var(--dsw-alias-state-business-primary); }
|
|
159
168
|
.artifacts-link { color: var(--dsw-alias-state-business-primary); text-decoration: none; padding: 4px; border-radius: 6px; display: inline-flex; align-items: center; }
|
|
160
169
|
.artifacts-link:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
161
170
|
.artifacts-iconbtn {
|
|
@@ -231,6 +240,9 @@ body[data-dsh-flyout-dragging] .artifacts-preview-overlay { transition: none; }
|
|
|
231
240
|
.artifacts-markdown blockquote { border-left: 3px solid var(--dsw-alias-border-l2); margin: 8px 0; padding: 2px 12px; color: var(--dsw-alias-label-secondary); }
|
|
232
241
|
.artifacts-markdown ul, .artifacts-markdown ol { padding-left: 24px; }
|
|
233
242
|
.artifacts-markdown a { color: var(--dsw-alias-state-business-primary); }
|
|
243
|
+
.artifacts-markdown table { display: block; overflow-x: auto; border-collapse: collapse; margin: 10px 0; }
|
|
244
|
+
.artifacts-markdown th, .artifacts-markdown td { border: 1px solid var(--dsw-alias-border-l2); padding: 4px 10px; text-align: left; }
|
|
245
|
+
.artifacts-markdown th { background: var(--dsw-alias-bg-layer-1); font-weight: 600; }
|
|
234
246
|
.artifacts-diff { border-top: 1px solid var(--dsw-alias-border-l2); }
|
|
235
247
|
.artifacts-diff-block { border-bottom: 1px solid var(--dsw-alias-border-l1); }
|
|
236
248
|
.artifacts-diff-label { font-size: 11px; padding: 4px 12px; font-weight: 600; }
|
|
@@ -263,6 +275,11 @@ body[data-ds-dark-theme] .gd-add { color: #69db7c; }
|
|
|
263
275
|
body[data-ds-dark-theme] .gd-del { color: #faa2c1; }
|
|
264
276
|
.artifacts-tree { display: flex; flex-direction: column; height: 100%; min-height: 0; }
|
|
265
277
|
.artifacts-tree-body { flex: 1; min-height: 0; overflow-y: auto; padding: 2px 6px 8px; }
|
|
278
|
+
.artifacts-searchbar { flex: none; padding: 8px 10px 4px; }
|
|
279
|
+
.artifacts-search-input { box-sizing: border-box; width: 100%; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); font: inherit; border-radius: 8px; padding: 5px 10px; outline: none; }
|
|
280
|
+
.artifacts-search-input:focus { border-color: var(--dsw-alias-button-primary-fill); }
|
|
281
|
+
.artifacts-search-input::placeholder { color: var(--dsw-alias-label-tertiary); }
|
|
282
|
+
.artifacts-search-dir { color: var(--dsw-alias-label-tertiary); font-size: 11px; margin-left: 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
266
283
|
.artifacts-tree-row { box-sizing: border-box; width: 100%; height: 34px; font: var(--dsw-font-s-14); color: var(--dsw-alias-label-primary); text-align: left; cursor: pointer; white-space: nowrap; background: transparent; border: none; border-radius: 8px; align-items: center; gap: 6px; padding: 0 8px; display: flex; animation: artifacts-row-in .15s var(--ds-ease-in-out, ease); }
|
|
267
284
|
.artifacts-tree-row:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
268
285
|
.artifacts-tree-dir { font: var(--dsw-font-s-strong-14); }
|
|
@@ -275,6 +292,14 @@ body[data-ds-dark-theme] .gd-del { color: #faa2c1; }
|
|
|
275
292
|
.artifacts-tree-copied { font: var(--dsw-font-xxxs-11); color: var(--dsw-alias-label-tertiary); flex: none; }
|
|
276
293
|
.artifacts-tree-loading { cursor: default; color: var(--dsw-alias-label-tertiary); font-size: 12px; }
|
|
277
294
|
.artifacts-tree-error { cursor: default; color: var(--dsw-alias-state-error-primary); font-size: 12px; }
|
|
295
|
+
.artifacts-tree-fail { padding: 24px 16px; display: flex; flex-direction: column; align-items: center; gap: 10px; }
|
|
296
|
+
.artifacts-tree-fail .artifacts-tree-error { text-align: center; word-break: break-all; }
|
|
297
|
+
.artifacts-retry {
|
|
298
|
+
padding: 4px 16px; font-size: 12px; cursor: pointer; border-radius: 6px;
|
|
299
|
+
border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-base);
|
|
300
|
+
color: var(--dsw-alias-label-primary);
|
|
301
|
+
}
|
|
302
|
+
.artifacts-retry:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
278
303
|
@keyframes artifacts-row-in { 0% { opacity: 0 } }
|
|
279
304
|
.artifacts-settings { display: flex; flex-direction: column; gap: 14px; width: 100%; height: 100%; min-height: 0; overflow-y: auto; padding-bottom: 24px; }
|
|
280
305
|
.artifacts-setintro { color: var(--dsw-alias-label-tertiary); margin: 0; padding: 0 2px; font-size: 13px; line-height: 20px; }
|
|
@@ -294,13 +319,22 @@ body[data-ds-dark-theme] .gd-del { color: #faa2c1; }
|
|
|
294
319
|
.artifacts-switch input:focus-visible + .artifacts-switch-track { outline: 2px solid var(--dsw-alias-state-business-primary); outline-offset: 2px; }
|
|
295
320
|
.artifacts-setcontrol { flex: none; align-items: center; gap: 6px; display: flex; }
|
|
296
321
|
.artifacts-widthinput { width: 76px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); font: inherit; border-radius: 6px; padding: 4px 8px; }
|
|
322
|
+
.artifacts-langselect { border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); font: inherit; border-radius: 6px; padding: 4px 8px; cursor: pointer; }
|
|
297
323
|
.artifacts-suffix { color: var(--dsw-alias-label-secondary); font-size: 14px; line-height: 22px; }
|
|
298
324
|
.artifacts-code { display: flex; flex-direction: column; height: 100%; min-height: 0; }
|
|
299
|
-
|
|
300
|
-
.artifacts-code-
|
|
301
|
-
.artifacts-code-
|
|
325
|
+
/* 代码视图:逐行渲染(行 = 行号 + 代码),软换行时行号仍对齐 */
|
|
326
|
+
.artifacts-code-scroll { flex: 1; min-height: 0; overflow: auto; padding: 12px 0; background: var(--shiki-background, var(--dsw-alias-markdown-code-block, var(--dsw-alias-bg-layer-1))); }
|
|
327
|
+
.artifacts-code-line { display: flex; align-items: flex-start; min-width: fit-content; }
|
|
328
|
+
.artifacts-code-gutter { flex: none; min-width: 2.2em; padding: 0 6px 0 8px; text-align: right; color: var(--dsw-alias-label-tertiary); border-right: 1px solid var(--dsw-alias-border-l1); position: sticky; left: 0; user-select: none; background: var(--shiki-background, var(--dsw-alias-markdown-code-block, var(--dsw-alias-bg-layer-1))); font: 12px/1.6 var(--dsh-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); }
|
|
329
|
+
.artifacts-code-pre { flex: 1 0 auto; margin: 0; padding: 0 12px; color: var(--shiki-foreground, var(--dsw-alias-label-primary)); font: 12px/1.6 var(--dsh-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); white-space: pre; }
|
|
302
330
|
.artifacts-code-pre code { font: inherit; color: inherit; }
|
|
303
|
-
.artifacts-code-
|
|
331
|
+
.artifacts-code-wrap .artifacts-code-pre { white-space: pre-wrap; word-break: break-all; }
|
|
332
|
+
.artifacts-imgview { flex: 1; min-height: 0; overflow: auto; display: flex; align-items: center; justify-content: center; cursor: grab; user-select: none; }
|
|
333
|
+
.artifacts-imgview.is-dragging { cursor: grabbing; }
|
|
334
|
+
.artifacts-imgview .artifacts-img { flex: none; margin: 16px; transform-origin: center; will-change: transform; }
|
|
335
|
+
.artifacts-git-stats { flex: none; display: inline-flex; gap: 4px; font: 11px/16px var(--dsh-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); }
|
|
336
|
+
.artifacts-git-adds { color: var(--p-success-fg, #1a7f37); }
|
|
337
|
+
.artifacts-git-dels { color: var(--p-error, #cf222e); }
|
|
304
338
|
.tok-comment { color: #868e96; }
|
|
305
339
|
.tok-string { color: #2f9e44; }
|
|
306
340
|
.tok-number, .tok-bool, .tok-variable, .tok-hex, .tok-attr { color: #e8590c; }
|
|
@@ -385,11 +419,339 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
385
419
|
return m ? (m[1] || "").toLowerCase() : "";
|
|
386
420
|
}
|
|
387
421
|
//#endregion
|
|
388
|
-
//#region src/
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
422
|
+
//#region src/shared/i18n.js
|
|
423
|
+
/**
|
|
424
|
+
* 共享:中英双语 UI 文案。
|
|
425
|
+
*
|
|
426
|
+
* 与 ext/highlight/markdown 一样遵守「可移植源码」约束:只能使用 JSDoc 标注
|
|
427
|
+
* 类型(不得出现 TS 语法注记)、不得引入本目录之外的依赖。本模块被同时用于:
|
|
428
|
+
* 1. tsdown 打包进 client 侧(浏览器 React bundle);
|
|
429
|
+
* 2. tsdown 构建期经 `?raw` 读入原始文本,内联进独立弹出页 /flyout-sidebar
|
|
430
|
+
* 的经典 <script>(见 src/host/page.ts)。
|
|
431
|
+
*
|
|
432
|
+
* 语言选择:localStorage `dsh-flyout-sidebar:lang`('zh' | 'en',主面板的
|
|
433
|
+
* 设置项写入、弹出页读取),未设置时按浏览器语言自动判定。`t(key)` 取当前
|
|
434
|
+
* 语言文案,缺失时回退另一语言、再回退 key 本身。
|
|
435
|
+
*/
|
|
436
|
+
/** @type {Record<string, string>} */
|
|
437
|
+
const ZH = {
|
|
438
|
+
loading: "加载中…",
|
|
439
|
+
loadingTree: "加载文件树…",
|
|
440
|
+
loadingChanges: "加载变更列表…",
|
|
441
|
+
loadingPdf: "加载 PDF…",
|
|
442
|
+
searching: "搜索中…",
|
|
443
|
+
emptyDir: "(空目录)",
|
|
444
|
+
noChanges: "没有未提交的变更",
|
|
445
|
+
noChangesHead: "没有未提交的变更(相对于 HEAD)",
|
|
446
|
+
noResults: "没有匹配的文件",
|
|
447
|
+
readFailed: "读取失败",
|
|
448
|
+
retry: "重试",
|
|
449
|
+
gitStatusFailed: "git status 失败",
|
|
450
|
+
imageLoadFailed: "图片加载失败",
|
|
451
|
+
pdfLoadFailed: "pdf.js 加载失败",
|
|
452
|
+
searchFailed: "搜索失败",
|
|
453
|
+
treeLoadFailed: "加载失败",
|
|
454
|
+
truncated: "(已截断的预览)",
|
|
455
|
+
copied: "已复制",
|
|
456
|
+
copiedPath: "已复制路径",
|
|
457
|
+
copiedRef: "已复制 @引用(未能写入输入框)",
|
|
458
|
+
insertedInput: "已插入输入框",
|
|
459
|
+
refBtn: "@引用",
|
|
460
|
+
refTitle: "引用到输入框(失败则复制 @path)",
|
|
461
|
+
refFlyoutTitle: "复制 @path 引用",
|
|
462
|
+
copyPath: "复制路径",
|
|
463
|
+
refInput: "@引用到输入框",
|
|
464
|
+
collapsePanel: "收起侧边栏",
|
|
465
|
+
panelAria: "文件面板",
|
|
466
|
+
flyoutTitle: "弹出式侧边栏",
|
|
467
|
+
flyoutOpen: "弹出式侧边栏 — 在新标签页打开(可拖到另一块显示器)",
|
|
468
|
+
resizeHandle: "拖动调整宽度",
|
|
469
|
+
resizePanel: "拖动调整面板宽度",
|
|
470
|
+
refreshTree: "刷新文件树",
|
|
471
|
+
refreshChanges: "刷新变更列表",
|
|
472
|
+
refresh: "刷新",
|
|
473
|
+
viewGit: "查看 Git 变更(未提交)",
|
|
474
|
+
backToFiles: "返回文件列表",
|
|
475
|
+
hidePreview: "隐藏预览(标签页保留)",
|
|
476
|
+
closeTab: "关闭标签页",
|
|
477
|
+
previewRegion: "文件预览",
|
|
478
|
+
diffTabPrefix: "[diff] ",
|
|
479
|
+
searchPlaceholder: "搜索文件…",
|
|
480
|
+
searchToggle: "搜索文件",
|
|
481
|
+
hintClickGit: "点击右侧变更文件查看 diff",
|
|
482
|
+
hintClickTree: "点击右侧文件查看内容",
|
|
483
|
+
movePanelLeft: "将文件面板移到左侧",
|
|
484
|
+
movePanelRight: "将文件面板移到右侧",
|
|
485
|
+
statusU: "未跟踪",
|
|
486
|
+
statusA: "新增",
|
|
487
|
+
statusM: "修改",
|
|
488
|
+
statusD: "删除",
|
|
489
|
+
statusR: "重命名",
|
|
490
|
+
statusC: "复制",
|
|
491
|
+
statusT: "类型变更",
|
|
492
|
+
staged: "(已暂存)",
|
|
493
|
+
unstaged: "(未暂存)",
|
|
494
|
+
diffDeleted: "- 删除",
|
|
495
|
+
diffAdded: "+ 新增",
|
|
496
|
+
zoomOut: "缩小",
|
|
497
|
+
zoomIn: "放大",
|
|
498
|
+
prevPage: "上一页",
|
|
499
|
+
nextPage: "下一页",
|
|
500
|
+
settingsIntro: "管理「Flyout Sidebar」的显示与行为。",
|
|
501
|
+
setDefaultOpen: "默认展开",
|
|
502
|
+
setDefaultOpenDesc: "页面加载后侧边栏默认展开;关闭则默认收起,点右上角图标再打开。",
|
|
503
|
+
setAutoRefresh: "自动刷新",
|
|
504
|
+
setAutoRefreshDesc: "开启后侧边栏展开时将即时同步并更新产物列表",
|
|
505
|
+
setFileTree: "文件树",
|
|
506
|
+
setFileTreeDesc: "在侧边栏显示「文件树」标签页,浏览工作区目录。",
|
|
507
|
+
setMinWidth: "最短面板宽度",
|
|
508
|
+
setMinWidthDesc: "面板的最小宽度(占窗口宽度的百分比,20–60);更宽可通过拖动面板左边缘调整。",
|
|
509
|
+
setCodeWrap: "代码换行",
|
|
510
|
+
setCodeWrapDesc: "代码预览长行软换行;关闭则横向滚动。",
|
|
511
|
+
wordWrap: "自动换行",
|
|
512
|
+
openInEditor: "在系统编辑器打开",
|
|
513
|
+
openedInEditor: "已在编辑器打开",
|
|
514
|
+
openFailed: "打开失败",
|
|
515
|
+
setLang: "界面语言",
|
|
516
|
+
setLangDesc: "侧边栏与独立弹出页的显示语言;独立弹出页需刷新后生效。",
|
|
517
|
+
langAuto: "跟随浏览器",
|
|
518
|
+
langZh: "中文",
|
|
519
|
+
langEn: "English"
|
|
520
|
+
};
|
|
521
|
+
/** @type {Record<string, string>} */
|
|
522
|
+
const EN = {
|
|
523
|
+
loading: "Loading…",
|
|
524
|
+
loadingTree: "Loading file tree…",
|
|
525
|
+
loadingChanges: "Loading change list…",
|
|
526
|
+
loadingPdf: "Loading PDF…",
|
|
527
|
+
searching: "Searching…",
|
|
528
|
+
emptyDir: "(empty directory)",
|
|
529
|
+
noChanges: "No uncommitted changes",
|
|
530
|
+
noChangesHead: "No uncommitted changes (relative to HEAD)",
|
|
531
|
+
noResults: "No matching files",
|
|
532
|
+
readFailed: "Failed to read",
|
|
533
|
+
retry: "Retry",
|
|
534
|
+
gitStatusFailed: "git status failed",
|
|
535
|
+
imageLoadFailed: "Failed to load image",
|
|
536
|
+
pdfLoadFailed: "Failed to load pdf.js",
|
|
537
|
+
searchFailed: "Search failed",
|
|
538
|
+
treeLoadFailed: "Failed to load",
|
|
539
|
+
truncated: "(truncated preview)",
|
|
540
|
+
copied: "Copied",
|
|
541
|
+
copiedPath: "Path copied",
|
|
542
|
+
copiedRef: "Copied @reference (could not write to input box)",
|
|
543
|
+
insertedInput: "Inserted into input box",
|
|
544
|
+
refBtn: "@ref",
|
|
545
|
+
refTitle: "Quote into input box (falls back to copying @path)",
|
|
546
|
+
refFlyoutTitle: "Copy @path reference",
|
|
547
|
+
copyPath: "Copy path",
|
|
548
|
+
refInput: "@reference into input box",
|
|
549
|
+
collapsePanel: "Collapse sidebar",
|
|
550
|
+
panelAria: "File panel",
|
|
551
|
+
flyoutTitle: "Flyout sidebar",
|
|
552
|
+
flyoutOpen: "Flyout sidebar — open in a new tab (drag to another display)",
|
|
553
|
+
resizeHandle: "Drag to resize",
|
|
554
|
+
resizePanel: "Drag to resize panel",
|
|
555
|
+
refreshTree: "Refresh file tree",
|
|
556
|
+
refreshChanges: "Refresh change list",
|
|
557
|
+
refresh: "Refresh",
|
|
558
|
+
viewGit: "View Git changes (uncommitted)",
|
|
559
|
+
backToFiles: "Back to file list",
|
|
560
|
+
hidePreview: "Hide preview (tabs are kept)",
|
|
561
|
+
closeTab: "Close tab",
|
|
562
|
+
previewRegion: "File preview",
|
|
563
|
+
diffTabPrefix: "[diff] ",
|
|
564
|
+
searchPlaceholder: "Search files…",
|
|
565
|
+
searchToggle: "Search files",
|
|
566
|
+
hintClickGit: "Click a changed file to view its diff",
|
|
567
|
+
hintClickTree: "Click a file to view its content",
|
|
568
|
+
movePanelLeft: "Move file panel to the left",
|
|
569
|
+
movePanelRight: "Move file panel to the right",
|
|
570
|
+
statusU: "Untracked",
|
|
571
|
+
statusA: "Added",
|
|
572
|
+
statusM: "Modified",
|
|
573
|
+
statusD: "Deleted",
|
|
574
|
+
statusR: "Renamed",
|
|
575
|
+
statusC: "Copied",
|
|
576
|
+
statusT: "Type change",
|
|
577
|
+
staged: "(staged)",
|
|
578
|
+
unstaged: "(unstaged)",
|
|
579
|
+
diffDeleted: "- Deleted",
|
|
580
|
+
diffAdded: "+ Added",
|
|
581
|
+
zoomOut: "Zoom out",
|
|
582
|
+
zoomIn: "Zoom in",
|
|
583
|
+
prevPage: "Previous page",
|
|
584
|
+
nextPage: "Next page",
|
|
585
|
+
settingsIntro: "Manage the display and behavior of \"Flyout Sidebar\".",
|
|
586
|
+
setDefaultOpen: "Open by default",
|
|
587
|
+
setDefaultOpenDesc: "Expand the sidebar on page load; when off it stays collapsed until the corner icon is clicked.",
|
|
588
|
+
setAutoRefresh: "Auto refresh",
|
|
589
|
+
setAutoRefreshDesc: "Keep the artifact list in sync while the sidebar is open",
|
|
590
|
+
setFileTree: "File tree",
|
|
591
|
+
setFileTreeDesc: "Show the \"File tree\" tab in the sidebar to browse the workspace directory.",
|
|
592
|
+
setMinWidth: "Minimum panel width",
|
|
593
|
+
setMinWidthDesc: "Minimum width of the panel (percentage of window width, 20–60); drag the panel edge to go wider.",
|
|
594
|
+
setCodeWrap: "Code wrap",
|
|
595
|
+
setCodeWrapDesc: "Soft-wrap long lines in code previews; when off they scroll horizontally.",
|
|
596
|
+
wordWrap: "Word wrap",
|
|
597
|
+
openInEditor: "Open in system editor",
|
|
598
|
+
openedInEditor: "Opened in editor",
|
|
599
|
+
openFailed: "Failed to open",
|
|
600
|
+
setLang: "Interface language",
|
|
601
|
+
setLangDesc: "Display language for the sidebar and the standalone flyout page (flyout requires a reload).",
|
|
602
|
+
langAuto: "Auto (browser)",
|
|
603
|
+
langZh: "中文",
|
|
604
|
+
langEn: "English"
|
|
392
605
|
};
|
|
606
|
+
/** @type {Record<string, Record<string, string>>} */
|
|
607
|
+
const DICTS = {
|
|
608
|
+
zh: ZH,
|
|
609
|
+
en: EN
|
|
610
|
+
};
|
|
611
|
+
const LANG_KEY = "dsh-flyout-sidebar:lang";
|
|
612
|
+
/** @type {'zh' | 'en' | null} 已显式选择的语言(null = 跟随浏览器自动判定) */
|
|
613
|
+
let explicitLang = null;
|
|
614
|
+
/** @type {Array<(lang: string) => void>} */
|
|
615
|
+
let listeners = [];
|
|
616
|
+
function readStoredLang() {
|
|
617
|
+
try {
|
|
618
|
+
var v = localStorage.getItem(LANG_KEY);
|
|
619
|
+
return v === "zh" || v === "en" ? v : null;
|
|
620
|
+
} catch (e) {
|
|
621
|
+
return null;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
/** 浏览器语言自动判定:非 zh 开头一律英文 */
|
|
625
|
+
function autoLang() {
|
|
626
|
+
try {
|
|
627
|
+
var langs = navigator.languages || (navigator.language ? [navigator.language] : []);
|
|
628
|
+
for (var i = 0; i < langs.length; i++) {
|
|
629
|
+
var l = String(langs[i] || "").toLowerCase();
|
|
630
|
+
if (l.indexOf("zh") === 0) return "zh";
|
|
631
|
+
if (l.indexOf("en") === 0) return "en";
|
|
632
|
+
}
|
|
633
|
+
} catch (e) {}
|
|
634
|
+
return "en";
|
|
635
|
+
}
|
|
636
|
+
function currentLang() {
|
|
637
|
+
if (explicitLang === null) explicitLang = readStoredLang();
|
|
638
|
+
return explicitLang || autoLang();
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* @param {string} key 文案键
|
|
642
|
+
* @returns {string} 当前语言文案;两级回退(另一语言 → key)
|
|
643
|
+
*/
|
|
644
|
+
function t(key) {
|
|
645
|
+
var lang = currentLang();
|
|
646
|
+
var dict = DICTS[lang] || EN;
|
|
647
|
+
if (dict[key] != null) return dict[key];
|
|
648
|
+
var other = DICTS[lang === "zh" ? "en" : "zh"];
|
|
649
|
+
if (!other) return key;
|
|
650
|
+
return other[key] != null ? other[key] : key;
|
|
651
|
+
}
|
|
652
|
+
/** @returns {'zh' | 'en'} 当前生效语言(含自动判定的结果) */
|
|
653
|
+
function getLang() {
|
|
654
|
+
return currentLang();
|
|
655
|
+
}
|
|
656
|
+
/** @returns {'zh' | 'en' | null} 用户显式选择的语言(null = 跟随浏览器) */
|
|
657
|
+
function getExplicitLang() {
|
|
658
|
+
if (explicitLang === null) explicitLang = readStoredLang();
|
|
659
|
+
return explicitLang;
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* 显式设置语言并持久化(null = 恢复跟随浏览器)。
|
|
663
|
+
* @param {'zh' | 'en' | null} lang
|
|
664
|
+
*/
|
|
665
|
+
function setLang(lang) {
|
|
666
|
+
explicitLang = lang;
|
|
667
|
+
try {
|
|
668
|
+
if (lang === null) localStorage.removeItem(LANG_KEY);
|
|
669
|
+
else localStorage.setItem(LANG_KEY, lang);
|
|
670
|
+
} catch (e) {}
|
|
671
|
+
var next = listeners.slice();
|
|
672
|
+
for (var i = 0; i < next.length; i++) {
|
|
673
|
+
var fn = next[i];
|
|
674
|
+
if (!fn) continue;
|
|
675
|
+
try {
|
|
676
|
+
fn(currentLang());
|
|
677
|
+
} catch (e) {}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
/** @param {(lang: string) => void} fn @returns {() => void} 取消订阅 */
|
|
681
|
+
function subscribeLang(fn) {
|
|
682
|
+
listeners.push(fn);
|
|
683
|
+
return function() {
|
|
684
|
+
listeners = listeners.filter(function(f) {
|
|
685
|
+
return f !== fn;
|
|
686
|
+
});
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
//#endregion
|
|
690
|
+
//#region src/shared/gitui.js
|
|
691
|
+
/**
|
|
692
|
+
* 共享:git 变更列表 / diff 的纯展示逻辑。
|
|
693
|
+
*
|
|
694
|
+
* client 侧 React 组件与独立弹出页(page.ts 内联脚本)共用,避免双端各自
|
|
695
|
+
* 实现后细节漂移。与 ext/highlight/markdown 一样遵守「可移植源码」约束:
|
|
696
|
+
* 只能使用 JSDoc 标注类型、依赖仅限本目录(i18n 的 t 在内联场景由同作用域
|
|
697
|
+
* 函数声明提升提供)。
|
|
698
|
+
*/
|
|
699
|
+
/** @param {string} p @returns {string} */
|
|
700
|
+
function basename(p) {
|
|
701
|
+
var parts = String(p).split("/");
|
|
702
|
+
return parts[parts.length - 1] || p;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* 变更文件的状态徽章字母:未跟踪为 U,否则取工作树状态(y),再退到暂存
|
|
706
|
+
* 状态(x),空缺按 M。
|
|
707
|
+
* @param {{ x: string, y: string }} e @returns {string}
|
|
708
|
+
*/
|
|
709
|
+
function gitLabel(e) {
|
|
710
|
+
if (e.x === "?" || e.y === "?") return "U";
|
|
711
|
+
return (e.y !== " " ? e.y : e.x) || "M";
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* 变更文件行 hover 提示:状态名 + 暂存/未暂存。
|
|
715
|
+
* @param {{ x: string, y: string }} e @returns {string}
|
|
716
|
+
*/
|
|
717
|
+
function gitTitle(e) {
|
|
718
|
+
var label = gitLabel(e);
|
|
719
|
+
/** @type {Record<string, string>} */
|
|
720
|
+
var map = {
|
|
721
|
+
U: t("statusU"),
|
|
722
|
+
A: t("statusA"),
|
|
723
|
+
M: t("statusM"),
|
|
724
|
+
D: t("statusD"),
|
|
725
|
+
R: t("statusR"),
|
|
726
|
+
C: t("statusC"),
|
|
727
|
+
T: t("statusT")
|
|
728
|
+
};
|
|
729
|
+
var staged = e.x !== " " && e.x !== "?";
|
|
730
|
+
return (map[label] || label) + (staged ? t("staged") : t("unstaged"));
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* unified diff 单行的样式类:hunk / 增 / 删 / 元信息(diff --git、index、
|
|
734
|
+
* rename 等),其余为普通行。
|
|
735
|
+
* @param {string} line @returns {string}
|
|
736
|
+
*/
|
|
737
|
+
function gitDiffLineClass(line) {
|
|
738
|
+
var cls = "gd-line";
|
|
739
|
+
if (line.indexOf("@@") === 0) cls += " gd-hunk";
|
|
740
|
+
else if (line.charAt(0) === "+" && line.indexOf("+++") !== 0) cls += " gd-add";
|
|
741
|
+
else if (line.charAt(0) === "-" && line.indexOf("---") !== 0) cls += " gd-del";
|
|
742
|
+
else if (line.indexOf("diff ") === 0 || line.indexOf("index ") === 0 || line.indexOf("--- ") === 0 || line.indexOf("+++ ") === 0 || line.indexOf("new file") === 0 || line.indexOf("deleted file") === 0 || line.indexOf("old mode") === 0 || line.indexOf("new mode") === 0 || line.indexOf("rename ") === 0 || line.indexOf("similarity ") === 0 || line.indexOf("copy ") === 0 || line.indexOf("Binary files") === 0 || line.charAt(0) === "\\") cls += " gd-meta";
|
|
743
|
+
return cls;
|
|
744
|
+
}
|
|
745
|
+
//#endregion
|
|
746
|
+
//#region src/client/store.ts
|
|
747
|
+
/**
|
|
748
|
+
* Client 侧:共享状态与工具函数。
|
|
749
|
+
*
|
|
750
|
+
* - store / settingsStore:面板开关与功能设置(localStorage 持久化),
|
|
751
|
+
* 触发按钮与面板等组件通过 useOpen / useSettings 订阅。
|
|
752
|
+
* - currentSessionId / quoteToComposer:读取客户端会话库、把 @path 引用
|
|
753
|
+
* 写入会话输入框。
|
|
754
|
+
*/
|
|
393
755
|
/** 当前会话 id(读自客户端会话库;文件树把它传给 host 以定位工作区) */
|
|
394
756
|
function currentSessionId() {
|
|
395
757
|
try {
|
|
@@ -475,7 +837,8 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
475
837
|
autoRefresh: true,
|
|
476
838
|
minPanelWidth: 20,
|
|
477
839
|
showFileTree: true,
|
|
478
|
-
defaultOpen: true
|
|
840
|
+
defaultOpen: true,
|
|
841
|
+
codeWrap: false
|
|
479
842
|
};
|
|
480
843
|
function loadSettings() {
|
|
481
844
|
try {
|
|
@@ -518,7 +881,7 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
518
881
|
};
|
|
519
882
|
store.open = !!settingsStore.get().defaultOpen;
|
|
520
883
|
const slideState = {
|
|
521
|
-
visible:
|
|
884
|
+
visible: true,
|
|
522
885
|
slidOut: !store.open
|
|
523
886
|
};
|
|
524
887
|
const slideListeners = [];
|
|
@@ -537,22 +900,8 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
537
900
|
fn(next);
|
|
538
901
|
} catch {}
|
|
539
902
|
};
|
|
540
|
-
let slideTimer = null;
|
|
541
903
|
store.subscribe((open) => {
|
|
542
|
-
|
|
543
|
-
if (slideTimer) {
|
|
544
|
-
clearTimeout(slideTimer);
|
|
545
|
-
slideTimer = null;
|
|
546
|
-
}
|
|
547
|
-
setSlide({ visible: true });
|
|
548
|
-
requestAnimationFrame(() => requestAnimationFrame(() => setSlide({ slidOut: false })));
|
|
549
|
-
} else {
|
|
550
|
-
setSlide({ slidOut: true });
|
|
551
|
-
slideTimer = setTimeout(() => {
|
|
552
|
-
slideTimer = null;
|
|
553
|
-
setSlide({ visible: false });
|
|
554
|
-
}, 240);
|
|
555
|
-
}
|
|
904
|
+
setSlide({ slidOut: !open });
|
|
556
905
|
});
|
|
557
906
|
const useSlide = () => {
|
|
558
907
|
const [s, setS] = React.useState({ ...slideState });
|
|
@@ -585,6 +934,22 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
585
934
|
}, []);
|
|
586
935
|
return sessionId;
|
|
587
936
|
}
|
|
937
|
+
/**
|
|
938
|
+
* 界面语言:订阅 i18n 的语言变更,语言切换时强制订阅组件重渲染(组件内的
|
|
939
|
+
* t() 调用随之取到新语言文案)。返回值用于设置区判断下拉框选项。
|
|
940
|
+
*/
|
|
941
|
+
function useLang() {
|
|
942
|
+
const [, force] = React.useReducer((n) => n + 1, 0);
|
|
943
|
+
React.useEffect(() => {
|
|
944
|
+
const unsubscribe = subscribeLang(() => force());
|
|
945
|
+
return () => unsubscribe();
|
|
946
|
+
}, []);
|
|
947
|
+
return getLang();
|
|
948
|
+
}
|
|
949
|
+
/** 当前设置语言:'zh' / 'en'(显式)或 null(跟随浏览器自动判定) */
|
|
950
|
+
const getLanguageSetting = getExplicitLang;
|
|
951
|
+
/** 设置界面语言并持久化;null = 恢复跟随浏览器 */
|
|
952
|
+
const setLanguage = setLang;
|
|
588
953
|
//#endregion
|
|
589
954
|
//#region src/shared/highlight.js
|
|
590
955
|
/**
|
|
@@ -841,6 +1206,67 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
841
1206
|
const fn = HL_ENGINES[hlLangOf(hint)];
|
|
842
1207
|
return fn ? fn(String(src)) : escHtml(src);
|
|
843
1208
|
}
|
|
1209
|
+
/**
|
|
1210
|
+
* 把 highlightCode 输出的 HTML 按源码行拆分成逐行 HTML 数组。跨行 token
|
|
1211
|
+
* (块注释、Python 三引号串等)在行边界处闭合、下一行重新打开,保证逐行
|
|
1212
|
+
* 渲染时着色与整块渲染一致。逐行渲染使行号与代码天然对齐(含软换行模式),
|
|
1213
|
+
* 也是行级查找高亮的基础。
|
|
1214
|
+
* @param {string} html highlightCode 的输出
|
|
1215
|
+
* @returns {string[]}
|
|
1216
|
+
*/
|
|
1217
|
+
function splitHlLines(html) {
|
|
1218
|
+
var lines = [];
|
|
1219
|
+
var cur = "";
|
|
1220
|
+
/** @type {string[]} */
|
|
1221
|
+
var stack = [];
|
|
1222
|
+
var i = 0;
|
|
1223
|
+
var openAll = function() {
|
|
1224
|
+
var s = "";
|
|
1225
|
+
for (var k = 0; k < stack.length; k++) s += "<span class=\"" + stack[k] + "\">";
|
|
1226
|
+
return s;
|
|
1227
|
+
};
|
|
1228
|
+
var closeAll = function() {
|
|
1229
|
+
var s = "";
|
|
1230
|
+
for (var k = 0; k < stack.length; k++) s += "</span>";
|
|
1231
|
+
return s;
|
|
1232
|
+
};
|
|
1233
|
+
while (i < html.length) {
|
|
1234
|
+
if (html.indexOf("<span class=\"", i) === i) {
|
|
1235
|
+
var end = html.indexOf(">", i);
|
|
1236
|
+
if (end < 0) {
|
|
1237
|
+
cur += html.slice(i);
|
|
1238
|
+
break;
|
|
1239
|
+
}
|
|
1240
|
+
cur += html.slice(i, end + 1);
|
|
1241
|
+
stack.push(html.slice(i + 13, end - 1));
|
|
1242
|
+
i = end + 1;
|
|
1243
|
+
continue;
|
|
1244
|
+
}
|
|
1245
|
+
if (html.indexOf("</span>", i) === i) {
|
|
1246
|
+
cur += "</span>";
|
|
1247
|
+
stack.pop();
|
|
1248
|
+
i += 7;
|
|
1249
|
+
continue;
|
|
1250
|
+
}
|
|
1251
|
+
var nl = html.indexOf("\n", i);
|
|
1252
|
+
var nextTag = html.indexOf("<", i);
|
|
1253
|
+
if (nextTag >= 0 && (nl < 0 || nextTag < nl)) {
|
|
1254
|
+
cur += html.slice(i, nextTag);
|
|
1255
|
+
i = nextTag;
|
|
1256
|
+
continue;
|
|
1257
|
+
}
|
|
1258
|
+
if (nl < 0) {
|
|
1259
|
+
cur += html.slice(i);
|
|
1260
|
+
break;
|
|
1261
|
+
}
|
|
1262
|
+
cur += html.slice(i, nl);
|
|
1263
|
+
lines.push(cur + closeAll());
|
|
1264
|
+
cur = openAll();
|
|
1265
|
+
i = nl + 1;
|
|
1266
|
+
}
|
|
1267
|
+
lines.push(cur + closeAll());
|
|
1268
|
+
return lines;
|
|
1269
|
+
}
|
|
844
1270
|
//#endregion
|
|
845
1271
|
//#region src/shared/markdown.js
|
|
846
1272
|
/**
|
|
@@ -852,17 +1278,69 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
852
1278
|
*/
|
|
853
1279
|
/** @param {string} s @returns {string} */
|
|
854
1280
|
function mdEscape(s) {
|
|
855
|
-
return String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1281
|
+
return String(s).replace(/[\u0000\u0001]/g, "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* URL 白名单:只放行 http(s)、站内相对路径、页内锚点与 data:image/*,
|
|
1285
|
+
* 其余(javascript:、vbscript: 及实体编码变体等)一律替换为 '#',防止
|
|
1286
|
+
* 预览渲染出来的链接/图片在点击时执行脚本。
|
|
1287
|
+
* @param {string} u @returns {string}
|
|
1288
|
+
*/
|
|
1289
|
+
function mdSafeUrl(u) {
|
|
1290
|
+
var s = String(u || "").replace(/[\s\u0000-\u001f]/g, "");
|
|
1291
|
+
if (/^https?:\/\//i.test(s)) return s;
|
|
1292
|
+
if (/^\/(?!\/)/.test(s) || /^\.{1,2}\//.test(s) || s.charAt(0) === "#") return s;
|
|
1293
|
+
if (/^data:image\/(?:png|gif|jpeg|webp|bmp|avif);/i.test(s)) return s;
|
|
1294
|
+
return "#";
|
|
856
1295
|
}
|
|
857
1296
|
/** @param {string} s @returns {string} */
|
|
858
1297
|
function mdInline(s) {
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
s = s.replace(
|
|
1298
|
+
/** @type {string[]} */
|
|
1299
|
+
const codes = [];
|
|
1300
|
+
s = s.replace(/`([^`]+)`/g, (m, c) => {
|
|
1301
|
+
codes.push("<code>" + c + "</code>");
|
|
1302
|
+
return "\0" + (codes.length - 1) + "\0";
|
|
1303
|
+
});
|
|
1304
|
+
s = s.replace(/!\[([^\]]*)\]\(([^)\s]+)\)/g, (m, alt, u) => "<img alt=\"" + alt + "\" src=\"" + mdSafeUrl(u) + "\">");
|
|
1305
|
+
s = s.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (m, text, u) => "<a href=\"" + mdSafeUrl(u) + "\" target=\"_blank\" rel=\"noopener noreferrer\">" + text + "</a>");
|
|
862
1306
|
s = s.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
|
|
863
1307
|
s = s.replace(/\*([^*\n]+)\*/g, "<em>$1</em>");
|
|
1308
|
+
s = s.replace(/\u0000(\d+)\u0000/g, (m, i) => codes[Number(i)] ?? "");
|
|
864
1309
|
return s;
|
|
865
1310
|
}
|
|
1311
|
+
/**
|
|
1312
|
+
* GFM 管道表格辅助:按未转义的 | 切分单元格(行内 \| 还原为字面量竖线),
|
|
1313
|
+
* 并去掉首尾管道产生的空单元格。
|
|
1314
|
+
* @param {string} line @returns {string[]}
|
|
1315
|
+
*/
|
|
1316
|
+
function mdSplitCells(line) {
|
|
1317
|
+
const parts = String(line).replace(/\\\|/g, "").split("|");
|
|
1318
|
+
for (let i = 0; i < parts.length; i += 1) parts[i] = (parts[i] || "").replace(/\u0002/g, "|").trim();
|
|
1319
|
+
return parts;
|
|
1320
|
+
}
|
|
1321
|
+
/** @param {string[]} cells @returns {string[]} */
|
|
1322
|
+
function mdTrimEdgeCells(cells) {
|
|
1323
|
+
if (cells.length && cells[0] === "") cells.shift();
|
|
1324
|
+
if (cells.length && cells[cells.length - 1] === "") cells.pop();
|
|
1325
|
+
return cells;
|
|
1326
|
+
}
|
|
1327
|
+
/** @param {string[]} cells @returns {boolean} 是否为 GFM 分隔行(:--- / :---: / ---: 形态) */
|
|
1328
|
+
function mdIsDelimiterRow(cells) {
|
|
1329
|
+
return cells.length > 0 && cells.every((c) => /^:?-+:?$/.test(c));
|
|
1330
|
+
}
|
|
1331
|
+
/**
|
|
1332
|
+
* @param {string} cell @returns {string} 'center' | 'right' | ''(left 与默认一致,不输出)
|
|
1333
|
+
*/
|
|
1334
|
+
function mdCellAlign(cell) {
|
|
1335
|
+
if (/^:-+:$/.test(cell)) return "center";
|
|
1336
|
+
if (/^:-+$/.test(cell)) return "left";
|
|
1337
|
+
if (/^-+:$/.test(cell)) return "right";
|
|
1338
|
+
return "";
|
|
1339
|
+
}
|
|
1340
|
+
/** @param {string} a @returns {string} 单元格对齐的内联样式属性 */
|
|
1341
|
+
function mdAlignAttr(a) {
|
|
1342
|
+
return a === "center" || a === "right" ? " style=\"text-align:" + a + "\"" : "";
|
|
1343
|
+
}
|
|
866
1344
|
/** @param {string} src @returns {string} HTML */
|
|
867
1345
|
function mdToHtml(src) {
|
|
868
1346
|
const lines = String(src || "").replace(/\r\n/g, "\n").split("\n");
|
|
@@ -931,6 +1409,35 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
931
1409
|
i += 1;
|
|
932
1410
|
continue;
|
|
933
1411
|
}
|
|
1412
|
+
if (line.indexOf("|") >= 0 && i + 1 < lines.length) {
|
|
1413
|
+
const head = mdTrimEdgeCells(mdSplitCells(line));
|
|
1414
|
+
const delim = mdTrimEdgeCells(mdSplitCells(lines[i + 1]));
|
|
1415
|
+
if (head.length && mdIsDelimiterRow(delim)) {
|
|
1416
|
+
const aligns = delim.map(mdCellAlign);
|
|
1417
|
+
const colCount = head.length;
|
|
1418
|
+
/** @type {string[][]} */
|
|
1419
|
+
const rows = [];
|
|
1420
|
+
i += 2;
|
|
1421
|
+
while (i < lines.length && lines[i].trim() !== "" && lines[i].indexOf("|") >= 0) {
|
|
1422
|
+
const cells = mdTrimEdgeCells(mdSplitCells(lines[i]));
|
|
1423
|
+
/** @type {string[]} */
|
|
1424
|
+
const padded = [];
|
|
1425
|
+
for (let c = 0; c < colCount; c += 1) padded.push(cells[c] || "");
|
|
1426
|
+
rows.push(padded);
|
|
1427
|
+
i += 1;
|
|
1428
|
+
}
|
|
1429
|
+
let table = "<table><thead><tr>";
|
|
1430
|
+
for (let c = 0; c < colCount; c += 1) table += "<th" + mdAlignAttr(aligns[c] || "") + ">" + mdInline(mdEscape(head[c] || "")) + "</th>";
|
|
1431
|
+
table += "</tr></thead><tbody>";
|
|
1432
|
+
for (const cells of rows) {
|
|
1433
|
+
table += "<tr>";
|
|
1434
|
+
for (let c = 0; c < colCount; c += 1) table += "<td" + mdAlignAttr(aligns[c] || "") + ">" + mdInline(mdEscape(cells[c] || "")) + "</td>";
|
|
1435
|
+
table += "</tr>";
|
|
1436
|
+
}
|
|
1437
|
+
out.push(table + "</tbody></table>");
|
|
1438
|
+
continue;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
934
1441
|
out.push("<p>" + mdInline(mdEscape(line)) + "</p>");
|
|
935
1442
|
i += 1;
|
|
936
1443
|
}
|
|
@@ -943,15 +1450,23 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
943
1450
|
* 自定义 pdf.js 渲染器、Markdown / 图片 / HTML 沙箱、多类型分发。
|
|
944
1451
|
*/
|
|
945
1452
|
function renderDiff(diff) {
|
|
946
|
-
return /* @__PURE__ */ h("div", { className: "artifacts-diff" }, diff && diff.before != null && diff.before !== "" ? /* @__PURE__ */ h("div", { className: "artifacts-diff-block artifacts-diff-del" }, /* @__PURE__ */ h("div", { className: "artifacts-diff-label" }, "
|
|
1453
|
+
return /* @__PURE__ */ h("div", { className: "artifacts-diff" }, diff && diff.before != null && diff.before !== "" ? /* @__PURE__ */ h("div", { className: "artifacts-diff-block artifacts-diff-del" }, /* @__PURE__ */ h("div", { className: "artifacts-diff-label" }, t("diffDeleted")), /* @__PURE__ */ h("pre", { className: "artifacts-diff-pre" }, diff.before)) : null, /* @__PURE__ */ h("div", { className: "artifacts-diff-block artifacts-diff-add" }, /* @__PURE__ */ h("div", { className: "artifacts-diff-label" }, t("diffAdded")), /* @__PURE__ */ h("pre", { className: "artifacts-diff-pre" }, diff && diff.after != null ? diff.after : "")));
|
|
947
1454
|
}
|
|
948
|
-
/**
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1455
|
+
/** 代码预览:逐行渲染(行号 + 语法高亮同行内对齐,软换行也不串行)。
|
|
1456
|
+
* 文内查找直接用浏览器原生 Ctrl/Cmd+F(真实 DOM 文本可搜)。 */
|
|
1457
|
+
function CodeView({ code, path, wrap }) {
|
|
1458
|
+
const src = String(code || "").replace(/\n$/, "");
|
|
1459
|
+
const hlLines = React.useMemo(() => splitHlLines(highlightCode(src, fileExt(path || ""))), [src, path]);
|
|
1460
|
+
return /* @__PURE__ */ h("div", { className: "artifacts-code" + (wrap ? " artifacts-code-wrap" : "") }, /* @__PURE__ */ h("div", { className: "artifacts-code-scroll" }, hlLines.map((line, i) => /* @__PURE__ */ h("div", {
|
|
1461
|
+
key: i,
|
|
1462
|
+
className: "artifacts-code-line"
|
|
1463
|
+
}, /* @__PURE__ */ h("div", {
|
|
952
1464
|
className: "artifacts-code-gutter",
|
|
953
1465
|
"aria-hidden": "true"
|
|
954
|
-
},
|
|
1466
|
+
}, i + 1), /* @__PURE__ */ h("div", {
|
|
1467
|
+
className: "artifacts-code-pre",
|
|
1468
|
+
dangerouslySetInnerHTML: { __html: line || " " }
|
|
1469
|
+
})))));
|
|
955
1470
|
}
|
|
956
1471
|
let pdfjsPromise = null;
|
|
957
1472
|
function loadPdfjs() {
|
|
@@ -969,7 +1484,7 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
969
1484
|
s.async = true;
|
|
970
1485
|
s.onload = () => {
|
|
971
1486
|
try {
|
|
972
|
-
if (!window.pdfjsLib) throw new Error("
|
|
1487
|
+
if (!window.pdfjsLib) throw new Error(t("pdfLoadFailed"));
|
|
973
1488
|
window.pdfjsLib.GlobalWorkerOptions.workerSrc = "/flyout-sidebar/pdfjs/pdf.worker.min.js";
|
|
974
1489
|
resolve(window.pdfjsLib);
|
|
975
1490
|
} catch (e) {
|
|
@@ -978,7 +1493,7 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
978
1493
|
};
|
|
979
1494
|
s.onerror = () => {
|
|
980
1495
|
pdfjsPromise = null;
|
|
981
|
-
reject(
|
|
1496
|
+
reject(new Error(t("pdfLoadFailed")));
|
|
982
1497
|
};
|
|
983
1498
|
document.head.appendChild(s);
|
|
984
1499
|
});
|
|
@@ -1096,7 +1611,7 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1096
1611
|
const clampPage = (n) => Math.max(1, Math.min(pageCount || 1, n));
|
|
1097
1612
|
const goPage = (n) => setPageNo(clampPage(n));
|
|
1098
1613
|
const zoomBy = (f) => setZoom((z) => Math.max(.25, Math.min(4, Math.round(z * f * 100) / 100)));
|
|
1099
|
-
if (phase === "loading") return /* @__PURE__ */ h("div", { className: "artifacts-pdfview" }, /* @__PURE__ */ h("div", { className: "artifacts-hint" }, "
|
|
1614
|
+
if (phase === "loading") return /* @__PURE__ */ h("div", { className: "artifacts-pdfview" }, /* @__PURE__ */ h("div", { className: "artifacts-hint" }, t("loadingPdf")));
|
|
1100
1615
|
if (phase === "error") return /* @__PURE__ */ h("embed", {
|
|
1101
1616
|
className: "artifacts-pdf",
|
|
1102
1617
|
src: "/flyout-sidebar/media?path=" + encodeURIComponent(path),
|
|
@@ -1107,25 +1622,25 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1107
1622
|
return /* @__PURE__ */ h("div", { className: "artifacts-pdfview" }, /* @__PURE__ */ h("div", { className: "artifacts-pdfview-bar" }, /* @__PURE__ */ h("button", {
|
|
1108
1623
|
type: "button",
|
|
1109
1624
|
className: "artifacts-pdfview-btn",
|
|
1110
|
-
title: "
|
|
1625
|
+
title: t("zoomOut"),
|
|
1111
1626
|
disabled,
|
|
1112
1627
|
onClick: () => zoomBy(.8)
|
|
1113
1628
|
}, "−"), /* @__PURE__ */ h("span", { className: "artifacts-pdfview-zoom" }, Math.round(zoom * 100) + "%"), /* @__PURE__ */ h("button", {
|
|
1114
1629
|
type: "button",
|
|
1115
1630
|
className: "artifacts-pdfview-btn",
|
|
1116
|
-
title: "
|
|
1631
|
+
title: t("zoomIn"),
|
|
1117
1632
|
disabled,
|
|
1118
1633
|
onClick: () => zoomBy(1.25)
|
|
1119
1634
|
}, "+"), /* @__PURE__ */ h("span", { className: "artifacts-pdfview-spacer" }), /* @__PURE__ */ h("button", {
|
|
1120
1635
|
type: "button",
|
|
1121
1636
|
className: "artifacts-pdfview-btn",
|
|
1122
|
-
title: "
|
|
1637
|
+
title: t("prevPage"),
|
|
1123
1638
|
disabled: disabled || pageNo <= 1,
|
|
1124
1639
|
onClick: () => goPage(pageNo - 1)
|
|
1125
1640
|
}, "‹"), /* @__PURE__ */ h("span", { className: "artifacts-pdfview-page" }, pageNo + " / " + pageCount), /* @__PURE__ */ h("button", {
|
|
1126
1641
|
type: "button",
|
|
1127
1642
|
className: "artifacts-pdfview-btn",
|
|
1128
|
-
title: "
|
|
1643
|
+
title: t("nextPage"),
|
|
1129
1644
|
disabled: disabled || pageNo >= pageCount,
|
|
1130
1645
|
onClick: () => goPage(pageNo + 1)
|
|
1131
1646
|
}, "›")), /* @__PURE__ */ h("div", {
|
|
@@ -1136,33 +1651,93 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1136
1651
|
className: "artifacts-pdfview-canvas"
|
|
1137
1652
|
})));
|
|
1138
1653
|
}
|
|
1139
|
-
/**
|
|
1654
|
+
/** 图片预览:滚轮缩放 + 拖拽平移 + 双击复位 */
|
|
1655
|
+
function ImageView({ path }) {
|
|
1656
|
+
const [scale, setScale] = React.useState(1);
|
|
1657
|
+
const [offset, setOffset] = React.useState({
|
|
1658
|
+
x: 0,
|
|
1659
|
+
y: 0
|
|
1660
|
+
});
|
|
1661
|
+
const [dragging, setDragging] = React.useState(false);
|
|
1662
|
+
const dragRef = React.useRef(null);
|
|
1663
|
+
const wrapRef = React.useRef(null);
|
|
1664
|
+
const src = "/flyout-sidebar/media?path=" + encodeURIComponent(path);
|
|
1665
|
+
React.useEffect(() => {
|
|
1666
|
+
const wrap = wrapRef.current;
|
|
1667
|
+
if (!wrap) return;
|
|
1668
|
+
const onWheel = (ev) => {
|
|
1669
|
+
ev.preventDefault();
|
|
1670
|
+
const factor = ev.deltaY < 0 ? 1.15 : 1 / 1.15;
|
|
1671
|
+
setScale((sc) => Math.max(.1, Math.min(12, sc * factor)));
|
|
1672
|
+
};
|
|
1673
|
+
wrap.addEventListener("wheel", onWheel, { passive: false });
|
|
1674
|
+
return () => wrap.removeEventListener("wheel", onWheel);
|
|
1675
|
+
}, []);
|
|
1676
|
+
React.useEffect(() => {
|
|
1677
|
+
if (!dragging) return;
|
|
1678
|
+
const onMove = (ev) => {
|
|
1679
|
+
const start = dragRef.current;
|
|
1680
|
+
if (!start) return;
|
|
1681
|
+
setOffset({
|
|
1682
|
+
x: start.ox + (ev.clientX - start.x),
|
|
1683
|
+
y: start.oy + (ev.clientY - start.y)
|
|
1684
|
+
});
|
|
1685
|
+
};
|
|
1686
|
+
const onUp = () => {
|
|
1687
|
+
setDragging(false);
|
|
1688
|
+
dragRef.current = null;
|
|
1689
|
+
};
|
|
1690
|
+
document.addEventListener("mousemove", onMove);
|
|
1691
|
+
document.addEventListener("mouseup", onUp);
|
|
1692
|
+
return () => {
|
|
1693
|
+
document.removeEventListener("mousemove", onMove);
|
|
1694
|
+
document.removeEventListener("mouseup", onUp);
|
|
1695
|
+
};
|
|
1696
|
+
}, [dragging]);
|
|
1697
|
+
return /* @__PURE__ */ h("div", {
|
|
1698
|
+
ref: wrapRef,
|
|
1699
|
+
className: "artifacts-imgview" + (dragging ? " is-dragging" : ""),
|
|
1700
|
+
onMouseDown: (ev) => {
|
|
1701
|
+
ev.preventDefault();
|
|
1702
|
+
dragRef.current = {
|
|
1703
|
+
x: ev.clientX,
|
|
1704
|
+
y: ev.clientY,
|
|
1705
|
+
ox: offset.x,
|
|
1706
|
+
oy: offset.y
|
|
1707
|
+
};
|
|
1708
|
+
setDragging(true);
|
|
1709
|
+
},
|
|
1710
|
+
onDoubleClick: () => {
|
|
1711
|
+
setScale(1);
|
|
1712
|
+
setOffset({
|
|
1713
|
+
x: 0,
|
|
1714
|
+
y: 0
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
}, /* @__PURE__ */ h("img", {
|
|
1718
|
+
className: "artifacts-img",
|
|
1719
|
+
draggable: false,
|
|
1720
|
+
src,
|
|
1721
|
+
alt: path,
|
|
1722
|
+
style: { transform: "translate(" + offset.x + "px," + offset.y + "px) scale(" + scale + ")" }
|
|
1723
|
+
}));
|
|
1724
|
+
}
|
|
1725
|
+
/** 统一 git diff 渲染:meta/hunk/+/- 行着色,等宽可滚动;行分类与独立弹出页共用 */
|
|
1140
1726
|
function GitDiffView({ diff }) {
|
|
1141
1727
|
const text = String(diff || "");
|
|
1142
|
-
if (!text) return /* @__PURE__ */ h("div", { className: "artifacts-hint" }, "
|
|
1143
|
-
return /* @__PURE__ */ h("div", { className: "artifacts-gitdiff" }, text.replace(/\n$/, "").split("\n").map((line, i) => {
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
else if (line.startsWith("-") && !line.startsWith("---")) cls += " gd-del";
|
|
1148
|
-
else if (line.startsWith("diff ") || line.startsWith("index ") || line.startsWith("--- ") || line.startsWith("+++ ") || line.startsWith("new file") || line.startsWith("deleted file") || line.startsWith("old mode") || line.startsWith("new mode") || line.startsWith("rename ") || line.startsWith("similarity ") || line.startsWith("copy ") || line.startsWith("Binary files") || line.startsWith("\\")) cls += " gd-meta";
|
|
1149
|
-
return /* @__PURE__ */ h("div", {
|
|
1150
|
-
key: i,
|
|
1151
|
-
className: cls
|
|
1152
|
-
}, line);
|
|
1153
|
-
}));
|
|
1728
|
+
if (!text) return /* @__PURE__ */ h("div", { className: "artifacts-hint" }, t("noChangesHead"));
|
|
1729
|
+
return /* @__PURE__ */ h("div", { className: "artifacts-gitdiff" }, text.replace(/\n$/, "").split("\n").map((line, i) => /* @__PURE__ */ h("div", {
|
|
1730
|
+
key: i,
|
|
1731
|
+
className: gitDiffLineClass(line)
|
|
1732
|
+
}, line)));
|
|
1154
1733
|
}
|
|
1155
|
-
function renderPreview(p) {
|
|
1156
|
-
if (p.loading) return /* @__PURE__ */ h("div", { className: "artifacts-hint" }, "
|
|
1157
|
-
if (p.ok === false) return /* @__PURE__ */ h("div", { className: "artifacts-error" }, p.error || "
|
|
1734
|
+
function renderPreview(p, codeWrap = false) {
|
|
1735
|
+
if (p.loading) return /* @__PURE__ */ h("div", { className: "artifacts-hint" }, t("loading"));
|
|
1736
|
+
if (p.ok === false) return /* @__PURE__ */ h("div", { className: "artifacts-error" }, p.error || t("readFailed"));
|
|
1158
1737
|
if (p.git) return /* @__PURE__ */ h("div", { className: "artifacts-preview-body" }, /* @__PURE__ */ h(GitDiffView, { diff: p.diff }));
|
|
1159
1738
|
const type = p.type || extType(p.path);
|
|
1160
1739
|
let view;
|
|
1161
|
-
if (type === "image") view = /* @__PURE__ */ h("
|
|
1162
|
-
className: "artifacts-img",
|
|
1163
|
-
src: "/flyout-sidebar/media?path=" + encodeURIComponent(p.path || ""),
|
|
1164
|
-
alt: p.path || ""
|
|
1165
|
-
});
|
|
1740
|
+
if (type === "image") view = /* @__PURE__ */ h(ImageView, { path: p.path || "" });
|
|
1166
1741
|
else if (type === "html") view = /* @__PURE__ */ h("iframe", {
|
|
1167
1742
|
className: "artifacts-iframe",
|
|
1168
1743
|
sandbox: "allow-scripts",
|
|
@@ -1176,8 +1751,9 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1176
1751
|
});
|
|
1177
1752
|
else view = /* @__PURE__ */ h(Fragment, null, /* @__PURE__ */ h(CodeView, {
|
|
1178
1753
|
code: p.content,
|
|
1179
|
-
path: p.path
|
|
1180
|
-
|
|
1754
|
+
path: p.path,
|
|
1755
|
+
wrap: codeWrap
|
|
1756
|
+
}), p.truncated ? /* @__PURE__ */ h("div", { className: "artifacts-diff-label" }, t("truncated")) : null);
|
|
1181
1757
|
return /* @__PURE__ */ h("div", { className: "artifacts-preview-body" }, p.diff && typeof p.diff === "object" ? renderDiff(p.diff) : null, view);
|
|
1182
1758
|
}
|
|
1183
1759
|
//#endregion
|
|
@@ -1335,43 +1911,139 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1335
1911
|
strokeLinecap: "round",
|
|
1336
1912
|
fill: "none"
|
|
1337
1913
|
}));
|
|
1914
|
+
/** 搜索图标:放大镜(文件搜索开关) */
|
|
1915
|
+
const SearchIcon = ({ size }) => /* @__PURE__ */ h("svg", {
|
|
1916
|
+
width: size,
|
|
1917
|
+
height: size,
|
|
1918
|
+
viewBox: "0 0 16 16",
|
|
1919
|
+
fill: "none",
|
|
1920
|
+
"aria-hidden": "true"
|
|
1921
|
+
}, /* @__PURE__ */ h("circle", {
|
|
1922
|
+
cx: "7",
|
|
1923
|
+
cy: "7",
|
|
1924
|
+
r: "4.4",
|
|
1925
|
+
stroke: "currentColor",
|
|
1926
|
+
strokeWidth: 1.4
|
|
1927
|
+
}), /* @__PURE__ */ h("path", {
|
|
1928
|
+
d: "m10.4 10.4 3.1 3.1",
|
|
1929
|
+
stroke: "currentColor",
|
|
1930
|
+
strokeWidth: 1.4,
|
|
1931
|
+
strokeLinecap: "round"
|
|
1932
|
+
}));
|
|
1338
1933
|
//#endregion
|
|
1339
1934
|
//#region src/client/components.tsx
|
|
1340
1935
|
/**
|
|
1341
1936
|
* Client 侧:UI 组件 —— 文件树、多标签预览侧边面板、角落触发按钮、设置区。
|
|
1342
1937
|
*/
|
|
1343
|
-
function FileTree({ onOpen, selectedPath, refreshToken }) {
|
|
1938
|
+
function FileTree({ onOpen, selectedPath, refreshToken, searchOpen = false, onCloseSearch }) {
|
|
1344
1939
|
const [root, setRoot] = React.useState(null);
|
|
1345
1940
|
const [children, setChildren] = React.useState({});
|
|
1346
1941
|
const [expanded, setExpanded] = React.useState({});
|
|
1347
1942
|
const [copiedPath, setCopiedPath] = React.useState(null);
|
|
1348
1943
|
const [copiedLabel, setCopiedLabel] = React.useState("");
|
|
1944
|
+
const [query, setQuery] = React.useState("");
|
|
1945
|
+
const [search, setSearch] = React.useState(null);
|
|
1349
1946
|
const copyTimer = React.useRef(null);
|
|
1350
1947
|
const rootTimer = React.useRef(null);
|
|
1351
1948
|
const sessionId = useSessionId();
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1949
|
+
React.useEffect(() => {
|
|
1950
|
+
if (!searchOpen) setQuery("");
|
|
1951
|
+
}, [searchOpen]);
|
|
1952
|
+
React.useEffect(() => {
|
|
1953
|
+
const q = query.trim();
|
|
1954
|
+
if (!q) {
|
|
1955
|
+
setSearch(null);
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
let alive = true;
|
|
1959
|
+
setSearch({ loading: true });
|
|
1960
|
+
const timer = setTimeout(() => {
|
|
1961
|
+
host.searchFiles(q, currentSessionId()).then((res) => {
|
|
1962
|
+
if (alive) setSearch(res && res.ok ? { entries: res.entries || [] } : { error: res && res.error || t("searchFailed") });
|
|
1963
|
+
}).catch(() => {
|
|
1964
|
+
if (alive) setSearch({ error: t("searchFailed") });
|
|
1965
|
+
});
|
|
1966
|
+
}, 250);
|
|
1967
|
+
return () => {
|
|
1968
|
+
alive = false;
|
|
1969
|
+
clearTimeout(timer);
|
|
1970
|
+
};
|
|
1971
|
+
}, [
|
|
1972
|
+
query,
|
|
1973
|
+
sessionId,
|
|
1974
|
+
refreshToken
|
|
1975
|
+
]);
|
|
1976
|
+
const fetchDir = (path) => {
|
|
1977
|
+
const sid = currentSessionId();
|
|
1978
|
+
setChildren((prev) => ({
|
|
1979
|
+
...prev,
|
|
1980
|
+
[path]: { loading: true }
|
|
1981
|
+
}));
|
|
1982
|
+
host.listDir(path, sid).then((res) => {
|
|
1983
|
+
if (currentSessionId() !== sid) return;
|
|
1984
|
+
setChildren((prev) => ({
|
|
1985
|
+
...prev,
|
|
1986
|
+
[path]: res && res.ok ? { entries: res.entries || [] } : { error: res && res.error || t("readFailed") }
|
|
1987
|
+
}));
|
|
1988
|
+
}).catch(() => {
|
|
1989
|
+
if (currentSessionId() !== sid) return;
|
|
1990
|
+
setChildren((prev) => ({
|
|
1991
|
+
...prev,
|
|
1992
|
+
[path]: { error: t("readFailed") }
|
|
1993
|
+
}));
|
|
1994
|
+
});
|
|
1995
|
+
};
|
|
1996
|
+
const [rootError, setRootError] = React.useState(null);
|
|
1997
|
+
const loadRoot = (keepState = false) => {
|
|
1998
|
+
if (!keepState) {
|
|
1999
|
+
setChildren({});
|
|
2000
|
+
setExpanded({});
|
|
2001
|
+
setRoot(null);
|
|
2002
|
+
}
|
|
2003
|
+
setRootError(null);
|
|
2004
|
+
if (keepState) {
|
|
2005
|
+
for (const p of Object.keys(expanded)) if (expanded[p]) fetchDir(p);
|
|
2006
|
+
}
|
|
1356
2007
|
if (rootTimer.current) clearTimeout(rootTimer.current);
|
|
1357
|
-
const
|
|
2008
|
+
const delays = [
|
|
2009
|
+
400,
|
|
2010
|
+
800,
|
|
2011
|
+
1600,
|
|
2012
|
+
3200,
|
|
2013
|
+
3200
|
|
2014
|
+
];
|
|
2015
|
+
const sid = currentSessionId();
|
|
2016
|
+
const attempt = (i) => {
|
|
1358
2017
|
host.listDir("", currentSessionId()).then((res) => {
|
|
2018
|
+
if (currentSessionId() !== sid) return;
|
|
1359
2019
|
if (res && res.ok) setRoot({
|
|
1360
2020
|
path: res.path || "",
|
|
1361
2021
|
entries: res.entries || []
|
|
1362
2022
|
});
|
|
1363
|
-
else if (
|
|
2023
|
+
else if (i < delays.length) rootTimer.current = setTimeout(() => attempt(i + 1), delays[i]);
|
|
2024
|
+
else if (!keepState) setRootError(res && res.error || t("treeLoadFailed"));
|
|
1364
2025
|
}).catch(() => {
|
|
1365
|
-
if (
|
|
2026
|
+
if (currentSessionId() !== sid) return;
|
|
2027
|
+
if (i < delays.length) rootTimer.current = setTimeout(() => attempt(i + 1), delays[i]);
|
|
2028
|
+
else if (!keepState) setRootError(t("treeLoadFailed"));
|
|
1366
2029
|
});
|
|
1367
2030
|
};
|
|
1368
|
-
attempt(
|
|
2031
|
+
attempt(0);
|
|
1369
2032
|
};
|
|
2033
|
+
const firstTreeRender = React.useRef(true);
|
|
1370
2034
|
React.useEffect(() => {
|
|
1371
2035
|
loadRoot();
|
|
1372
|
-
}, [sessionId
|
|
2036
|
+
}, [sessionId]);
|
|
2037
|
+
React.useEffect(() => {
|
|
2038
|
+
if (firstTreeRender.current) {
|
|
2039
|
+
firstTreeRender.current = false;
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
2042
|
+
loadRoot(root != null);
|
|
2043
|
+
}, [refreshToken]);
|
|
1373
2044
|
React.useEffect(() => () => {
|
|
1374
2045
|
if (rootTimer.current) clearTimeout(rootTimer.current);
|
|
2046
|
+
if (copyTimer.current) clearTimeout(copyTimer.current);
|
|
1375
2047
|
}, []);
|
|
1376
2048
|
const toggle = (path) => {
|
|
1377
2049
|
const nextExpanded = {
|
|
@@ -1379,27 +2051,11 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1379
2051
|
[path]: !expanded[path]
|
|
1380
2052
|
};
|
|
1381
2053
|
setExpanded(nextExpanded);
|
|
1382
|
-
if (nextExpanded[path] && !children[path])
|
|
1383
|
-
setChildren({
|
|
1384
|
-
...children,
|
|
1385
|
-
[path]: { loading: true }
|
|
1386
|
-
});
|
|
1387
|
-
host.listDir(path, currentSessionId()).then((res) => {
|
|
1388
|
-
setChildren((prev) => ({
|
|
1389
|
-
...prev,
|
|
1390
|
-
[path]: res && res.ok ? { entries: res.entries || [] } : { error: res && res.error || "读取失败" }
|
|
1391
|
-
}));
|
|
1392
|
-
}).catch(() => {
|
|
1393
|
-
setChildren((prev) => ({
|
|
1394
|
-
...prev,
|
|
1395
|
-
[path]: { error: "读取失败" }
|
|
1396
|
-
}));
|
|
1397
|
-
});
|
|
1398
|
-
}
|
|
2054
|
+
if (nextExpanded[path] && !children[path]) fetchDir(path);
|
|
1399
2055
|
};
|
|
1400
2056
|
const copyRef = (path) => {
|
|
1401
2057
|
const text = "@" + path;
|
|
1402
|
-
let label = "
|
|
2058
|
+
let label = t("copied");
|
|
1403
2059
|
const done = () => {
|
|
1404
2060
|
setCopiedPath(path);
|
|
1405
2061
|
setCopiedLabel(label);
|
|
@@ -1410,7 +2066,7 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1410
2066
|
}, 1600);
|
|
1411
2067
|
};
|
|
1412
2068
|
if (quoteToComposer(path)) {
|
|
1413
|
-
label = "
|
|
2069
|
+
label = t("insertedInput");
|
|
1414
2070
|
done();
|
|
1415
2071
|
return;
|
|
1416
2072
|
}
|
|
@@ -1423,15 +2079,15 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1423
2079
|
done();
|
|
1424
2080
|
}
|
|
1425
2081
|
};
|
|
1426
|
-
const rowActions = (entry) => copiedPath === entry.path ? /* @__PURE__ */ h("span", { className: "artifacts-tree-copied" }, copiedLabel || "
|
|
2082
|
+
const rowActions = (entry) => copiedPath === entry.path ? /* @__PURE__ */ h("span", { className: "artifacts-tree-copied" }, copiedLabel || t("copied")) : /* @__PURE__ */ h("button", {
|
|
1427
2083
|
type: "button",
|
|
1428
2084
|
className: "artifacts-tree-ref",
|
|
1429
|
-
title: "
|
|
2085
|
+
title: t("refTitle"),
|
|
1430
2086
|
onClick: (e) => {
|
|
1431
2087
|
e.stopPropagation();
|
|
1432
2088
|
copyRef(entry.path);
|
|
1433
2089
|
}
|
|
1434
|
-
}, "
|
|
2090
|
+
}, t("refBtn"));
|
|
1435
2091
|
const renderNode = (entry, depth, flashDelay) => {
|
|
1436
2092
|
const pad = { paddingLeft: 6 + depth * 20 };
|
|
1437
2093
|
const isSelected = selectedPath === entry.path;
|
|
@@ -1459,7 +2115,7 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1459
2115
|
}, isExpanded ? /* @__PURE__ */ h(FolderOpenIcon, { size: 14 }) : /* @__PURE__ */ h(FolderClosedIcon, { size: 14 }), /* @__PURE__ */ h("span", { className: "artifacts-tree-name" }, entry.name), rowActions(entry)), isExpanded ? node && node.loading ? /* @__PURE__ */ h("div", {
|
|
1460
2116
|
className: "artifacts-tree-row artifacts-tree-loading",
|
|
1461
2117
|
style: { paddingLeft: 6 + (depth + 1) * 20 + 20 }
|
|
1462
|
-
}, "
|
|
2118
|
+
}, t("loading")) : node && node.error ? /* @__PURE__ */ h("div", {
|
|
1463
2119
|
className: "artifacts-tree-row artifacts-tree-error",
|
|
1464
2120
|
style: { paddingLeft: 6 + (depth + 1) * 20 + 20 }
|
|
1465
2121
|
}, node.error) : node && node.entries ? node.entries.map((c) => renderNode(c, depth + 1)) : null : null);
|
|
@@ -1484,15 +2140,64 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1484
2140
|
title: entry.path
|
|
1485
2141
|
}, /* @__PURE__ */ h(FileCodeIcon, { size: 14 }), /* @__PURE__ */ h("span", { className: "artifacts-tree-name" }, entry.name), rowActions(entry));
|
|
1486
2142
|
};
|
|
1487
|
-
|
|
2143
|
+
const renderSearchRow = (path, idx) => {
|
|
2144
|
+
const entry = {
|
|
2145
|
+
name: basename(path),
|
|
2146
|
+
path,
|
|
2147
|
+
isDir: false,
|
|
2148
|
+
hidden: false
|
|
2149
|
+
};
|
|
2150
|
+
const slash = path.lastIndexOf("/");
|
|
2151
|
+
const dir = slash >= 0 ? path.slice(0, slash) : "";
|
|
2152
|
+
return /* @__PURE__ */ h("div", {
|
|
2153
|
+
key: path,
|
|
2154
|
+
role: "button",
|
|
2155
|
+
tabIndex: 0,
|
|
2156
|
+
className: "artifacts-tree-row artifacts-flash-in" + (selectedPath === path ? " is-selected" : ""),
|
|
2157
|
+
style: { animationDelay: Math.min(idx, 12) * 30 + "ms" },
|
|
2158
|
+
onClick: () => {
|
|
2159
|
+
if (onOpen) onOpen(path);
|
|
2160
|
+
},
|
|
2161
|
+
onKeyDown: (ev) => {
|
|
2162
|
+
if (ev.key === "Enter" || ev.key === " ") {
|
|
2163
|
+
ev.preventDefault();
|
|
2164
|
+
if (onOpen) onOpen(path);
|
|
2165
|
+
}
|
|
2166
|
+
},
|
|
2167
|
+
title: path
|
|
2168
|
+
}, /* @__PURE__ */ h(FileCodeIcon, { size: 14 }), /* @__PURE__ */ h("span", { className: "artifacts-tree-name" }, entry.name, dir ? /* @__PURE__ */ h("span", { className: "artifacts-search-dir" }, dir) : null), rowActions(entry));
|
|
2169
|
+
};
|
|
2170
|
+
return /* @__PURE__ */ h("div", { className: "artifacts-tree" }, searchOpen ? /* @__PURE__ */ h("div", { className: "artifacts-searchbar" }, /* @__PURE__ */ h("input", {
|
|
2171
|
+
type: "text",
|
|
2172
|
+
className: "artifacts-search-input",
|
|
2173
|
+
placeholder: t("searchPlaceholder"),
|
|
2174
|
+
value: query,
|
|
2175
|
+
autoFocus: true,
|
|
2176
|
+
onChange: (e) => setQuery(e.currentTarget.value),
|
|
2177
|
+
onKeyDown: (ev) => {
|
|
2178
|
+
if (ev.key === "Escape") {
|
|
2179
|
+
if (onCloseSearch) onCloseSearch();
|
|
2180
|
+
else setQuery("");
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
})) : null, /* @__PURE__ */ h("div", {
|
|
2184
|
+
className: "artifacts-tree-body",
|
|
2185
|
+
key: refreshToken
|
|
2186
|
+
}, query.trim() ? search && search.loading ? /* @__PURE__ */ h("div", { className: "artifacts-hint" }, t("searching")) : search && search.error ? /* @__PURE__ */ h("div", { className: "artifacts-tree-error artifacts-git-error" }, search.error) : search && search.entries && search.entries.length ? search.entries.map((p, i) => renderSearchRow(p, i)) : /* @__PURE__ */ h("div", { className: "artifacts-hint" }, t("noResults")) : !root ? rootError ? /* @__PURE__ */ h("div", { className: "artifacts-tree-fail" }, /* @__PURE__ */ h("div", { className: "artifacts-tree-error artifacts-git-error" }, rootError), /* @__PURE__ */ h("button", {
|
|
2187
|
+
type: "button",
|
|
2188
|
+
className: "artifacts-retry",
|
|
2189
|
+
onClick: () => loadRoot(false)
|
|
2190
|
+
}, t("retry"))) : /* @__PURE__ */ h("div", { className: "artifacts-hint" }, t("loadingTree")) : !root.entries || !root.entries.length ? /* @__PURE__ */ h("div", { className: "artifacts-hint" }, t("emptyDir")) : root.entries.map((e, i) => renderNode(e, 0, Math.min(i, 12) * 45))));
|
|
1488
2191
|
}
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
2192
|
+
const TABS_KEY = "dsh-flyout-sidebar:tabs";
|
|
2193
|
+
/**
|
|
2194
|
+
* 多标签预览状态('p:' 前缀 = 内容预览,'g:' 前缀 = git diff):打开/关闭/
|
|
2195
|
+
* 会话切换清空/持久化与恢复集中在此 hook,面板组件只消费结果。
|
|
2196
|
+
*/
|
|
2197
|
+
function usePreviewTabs(sessionId) {
|
|
1492
2198
|
const [tabs, setTabs] = React.useState([]);
|
|
1493
2199
|
const [activeKey, setActiveKey] = React.useState(null);
|
|
1494
2200
|
const [previewHidden, setPreviewHidden] = React.useState(false);
|
|
1495
|
-
const sessionId = useSessionId();
|
|
1496
2201
|
const firstSession = React.useRef(true);
|
|
1497
2202
|
React.useEffect(() => {
|
|
1498
2203
|
if (firstSession.current) {
|
|
@@ -1502,18 +2207,219 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1502
2207
|
setTabs([]);
|
|
1503
2208
|
setActiveKey(null);
|
|
1504
2209
|
setPreviewHidden(false);
|
|
2210
|
+
try {
|
|
2211
|
+
sessionStorage.removeItem(TABS_KEY);
|
|
2212
|
+
} catch {}
|
|
2213
|
+
}, [sessionId]);
|
|
2214
|
+
const restoredSid = React.useRef(null);
|
|
2215
|
+
React.useEffect(() => {
|
|
2216
|
+
if (restoredSid.current !== sessionId) return;
|
|
2217
|
+
try {
|
|
2218
|
+
if (!tabs.length) sessionStorage.removeItem(TABS_KEY);
|
|
2219
|
+
else sessionStorage.setItem(TABS_KEY, JSON.stringify({
|
|
2220
|
+
sid: sessionId,
|
|
2221
|
+
tabs: tabs.map((tb) => ({
|
|
2222
|
+
key: tb.key,
|
|
2223
|
+
path: tb.path,
|
|
2224
|
+
git: tb.git
|
|
2225
|
+
})),
|
|
2226
|
+
activeKey
|
|
2227
|
+
}));
|
|
2228
|
+
} catch {}
|
|
2229
|
+
}, [
|
|
2230
|
+
tabs,
|
|
2231
|
+
activeKey,
|
|
2232
|
+
sessionId
|
|
2233
|
+
]);
|
|
2234
|
+
const patchTab = (key, patch) => setTabs((prev) => prev.map((tb) => tb.key === key ? {
|
|
2235
|
+
...tb,
|
|
2236
|
+
...patch
|
|
2237
|
+
} : tb));
|
|
2238
|
+
const reqTokens = React.useRef({});
|
|
2239
|
+
const openTab = (key, path, git, initial) => {
|
|
2240
|
+
setPreviewHidden(false);
|
|
2241
|
+
reqTokens.current[key] = (reqTokens.current[key] || 0) + 1;
|
|
2242
|
+
setTabs((prev) => {
|
|
2243
|
+
const i = prev.findIndex((tb) => tb.key === key);
|
|
2244
|
+
if (i >= 0) {
|
|
2245
|
+
const next = prev.slice();
|
|
2246
|
+
const cur = next[i];
|
|
2247
|
+
if (cur) next[i] = {
|
|
2248
|
+
...cur,
|
|
2249
|
+
...initial,
|
|
2250
|
+
ok: void 0,
|
|
2251
|
+
error: void 0
|
|
2252
|
+
};
|
|
2253
|
+
return next;
|
|
2254
|
+
}
|
|
2255
|
+
return prev.concat([{
|
|
2256
|
+
key,
|
|
2257
|
+
path,
|
|
2258
|
+
git,
|
|
2259
|
+
...initial
|
|
2260
|
+
}]);
|
|
2261
|
+
});
|
|
2262
|
+
setActiveKey(key);
|
|
2263
|
+
};
|
|
2264
|
+
const closeTab = (key) => {
|
|
2265
|
+
setTabs((prev) => {
|
|
2266
|
+
prev.findIndex((tb) => tb.key === key);
|
|
2267
|
+
return prev.filter((tb) => tb.key !== key);
|
|
2268
|
+
});
|
|
2269
|
+
setActiveKey((cur) => {
|
|
2270
|
+
if (cur !== key) return cur;
|
|
2271
|
+
const remaining = tabsRef.current.filter((tb) => tb.key !== key);
|
|
2272
|
+
if (!remaining.length) return null;
|
|
2273
|
+
const closedIdx = tabsRef.current.findIndex((tb) => tb.key === key);
|
|
2274
|
+
return remaining[Math.min(Math.max(closedIdx, 0), remaining.length - 1)]?.key ?? null;
|
|
2275
|
+
});
|
|
2276
|
+
};
|
|
2277
|
+
const tabsRef = React.useRef([]);
|
|
2278
|
+
React.useEffect(() => {
|
|
2279
|
+
tabsRef.current = tabs;
|
|
2280
|
+
}, [tabs]);
|
|
2281
|
+
const openFile = (path) => {
|
|
2282
|
+
const key = "p:" + path;
|
|
2283
|
+
const type = extType(path);
|
|
2284
|
+
const initial = {
|
|
2285
|
+
loading: false,
|
|
2286
|
+
type,
|
|
2287
|
+
diff: null
|
|
2288
|
+
};
|
|
2289
|
+
if (type !== "image" && type !== "pdf") initial.loading = true;
|
|
2290
|
+
openTab(key, path, false, initial);
|
|
2291
|
+
if (type === "image" || type === "pdf") return;
|
|
2292
|
+
const myReq = reqTokens.current[key];
|
|
2293
|
+
host.readArtifact(path, sessionId).then((res) => {
|
|
2294
|
+
if (reqTokens.current[key] !== myReq) return;
|
|
2295
|
+
patchTab(key, {
|
|
2296
|
+
loading: false,
|
|
2297
|
+
...res
|
|
2298
|
+
});
|
|
2299
|
+
}).catch((e) => {
|
|
2300
|
+
if (reqTokens.current[key] !== myReq) return;
|
|
2301
|
+
patchTab(key, {
|
|
2302
|
+
loading: false,
|
|
2303
|
+
ok: false,
|
|
2304
|
+
error: String(e instanceof Error && e.message ? e.message : e)
|
|
2305
|
+
});
|
|
2306
|
+
});
|
|
2307
|
+
};
|
|
2308
|
+
const openGitDiff = (path) => {
|
|
2309
|
+
const key = "g:" + path;
|
|
2310
|
+
openTab(key, path, true, { loading: true });
|
|
2311
|
+
const myReq = reqTokens.current[key];
|
|
2312
|
+
host.gitDiff(path, sessionId).then((res) => {
|
|
2313
|
+
if (reqTokens.current[key] !== myReq) return;
|
|
2314
|
+
patchTab(key, {
|
|
2315
|
+
loading: false,
|
|
2316
|
+
...res
|
|
2317
|
+
});
|
|
2318
|
+
}).catch((e) => {
|
|
2319
|
+
if (reqTokens.current[key] !== myReq) return;
|
|
2320
|
+
patchTab(key, {
|
|
2321
|
+
loading: false,
|
|
2322
|
+
ok: false,
|
|
2323
|
+
error: String(e instanceof Error && e.message ? e.message : e)
|
|
2324
|
+
});
|
|
2325
|
+
});
|
|
2326
|
+
};
|
|
2327
|
+
React.useEffect(() => {
|
|
2328
|
+
if (restoredSid.current === sessionId) return;
|
|
2329
|
+
restoredSid.current = sessionId;
|
|
2330
|
+
if (sessionId == null) return;
|
|
2331
|
+
try {
|
|
2332
|
+
const raw = sessionStorage.getItem(TABS_KEY);
|
|
2333
|
+
if (!raw) return;
|
|
2334
|
+
const saved = JSON.parse(raw);
|
|
2335
|
+
const sid = saved.sid;
|
|
2336
|
+
const list = saved.tabs;
|
|
2337
|
+
if (sid !== sessionId || !Array.isArray(list)) return;
|
|
2338
|
+
for (const st of list) {
|
|
2339
|
+
if (typeof st?.key !== "string" || typeof st?.path !== "string") continue;
|
|
2340
|
+
if (st.git) openGitDiff(st.path);
|
|
2341
|
+
else openFile(st.path);
|
|
2342
|
+
}
|
|
2343
|
+
const savedActive = saved.activeKey;
|
|
2344
|
+
if (typeof savedActive === "string") setActiveKey(savedActive);
|
|
2345
|
+
} catch {}
|
|
1505
2346
|
}, [sessionId]);
|
|
2347
|
+
return {
|
|
2348
|
+
tabs,
|
|
2349
|
+
activeKey,
|
|
2350
|
+
setActiveKey,
|
|
2351
|
+
previewHidden,
|
|
2352
|
+
setPreviewHidden,
|
|
2353
|
+
openFile,
|
|
2354
|
+
openGitDiff,
|
|
2355
|
+
closeTab
|
|
2356
|
+
};
|
|
2357
|
+
}
|
|
2358
|
+
/** git 变更列表(文件面板的「变更」视图) */
|
|
2359
|
+
function GitChangesList({ files, error, activeDiffPath, flashKey, onOpen, onCopyPath, onQuote }) {
|
|
2360
|
+
const rows = [];
|
|
2361
|
+
if (error) rows.push(/* @__PURE__ */ h("div", {
|
|
2362
|
+
key: "err",
|
|
2363
|
+
className: "artifacts-tree-error artifacts-git-error"
|
|
2364
|
+
}, error));
|
|
2365
|
+
else if (files == null) rows.push(/* @__PURE__ */ h("div", {
|
|
2366
|
+
key: "load",
|
|
2367
|
+
className: "artifacts-empty"
|
|
2368
|
+
}, t("loadingChanges")));
|
|
2369
|
+
else if (!files.length) rows.push(/* @__PURE__ */ h("div", {
|
|
2370
|
+
key: "empty",
|
|
2371
|
+
className: "artifacts-empty"
|
|
2372
|
+
}, t("noChanges")));
|
|
2373
|
+
(files || []).forEach((e, idx) => {
|
|
2374
|
+
const label = gitLabel(e);
|
|
2375
|
+
const isActive = activeDiffPath === e.path;
|
|
2376
|
+
const flashPrefix = flashKey > 0 ? flashKey + ":" : "";
|
|
2377
|
+
rows.push(/* @__PURE__ */ h("div", {
|
|
2378
|
+
key: flashPrefix + e.path,
|
|
2379
|
+
className: "artifacts-item" + (isActive ? " is-active" : "") + (flashPrefix ? " artifacts-flash-in" : ""),
|
|
2380
|
+
style: flashPrefix ? { animationDelay: Math.min(idx, 12) * 45 + "ms" } : void 0
|
|
2381
|
+
}, /* @__PURE__ */ h("button", {
|
|
2382
|
+
type: "button",
|
|
2383
|
+
className: "artifacts-item-main",
|
|
2384
|
+
title: gitTitle(e),
|
|
2385
|
+
onClick: () => onOpen(e.path)
|
|
2386
|
+
}, /* @__PURE__ */ h("div", { className: "artifacts-item-row" }, /* @__PURE__ */ h("span", { className: "artifacts-git-badge artifacts-git-badge-" + label }, label), /* @__PURE__ */ h("span", { className: "artifacts-item-base" }, basename(e.path)), typeof e.adds === "number" && (e.adds > 0 || (e.dels ?? 0) > 0) ? /* @__PURE__ */ h("span", { className: "artifacts-git-stats" }, /* @__PURE__ */ h("span", { className: "artifacts-git-adds" }, "+", e.adds), /* @__PURE__ */ h("span", { className: "artifacts-git-dels" }, "−", e.dels ?? 0)) : null, e.origPath ? /* @__PURE__ */ h("span", { className: "artifacts-git-orig" }, "← ", basename(e.origPath)) : null), /* @__PURE__ */ h("div", { className: "artifacts-item-full" }, e.path)), /* @__PURE__ */ h("div", { className: "artifacts-item-actions" }, /* @__PURE__ */ h("button", {
|
|
2387
|
+
type: "button",
|
|
2388
|
+
className: "artifacts-minibtn",
|
|
2389
|
+
title: t("copyPath"),
|
|
2390
|
+
onClick: () => onCopyPath(e.path)
|
|
2391
|
+
}, "⧉"), /* @__PURE__ */ h("button", {
|
|
2392
|
+
type: "button",
|
|
2393
|
+
className: "artifacts-minibtn",
|
|
2394
|
+
title: t("refInput"),
|
|
2395
|
+
onClick: () => onQuote(e.path)
|
|
2396
|
+
}, "@"))));
|
|
2397
|
+
});
|
|
2398
|
+
return /* @__PURE__ */ h(Fragment, null, rows);
|
|
2399
|
+
}
|
|
2400
|
+
function ArtifactsPanel() {
|
|
2401
|
+
const open = useOpen();
|
|
2402
|
+
const settings = useSettings();
|
|
2403
|
+
useLang();
|
|
2404
|
+
const sessionId = useSessionId();
|
|
2405
|
+
const { tabs, activeKey, setActiveKey, previewHidden, setPreviewHidden, openFile, openGitDiff, closeTab } = usePreviewTabs(sessionId);
|
|
2406
|
+
const activeTab = tabs.find((t) => t.key === activeKey) || null;
|
|
1506
2407
|
const [notice, setNotice] = React.useState("");
|
|
2408
|
+
const [winW, setWinW] = React.useState(() => typeof window !== "undefined" ? window.innerWidth : 1400);
|
|
1507
2409
|
const [gitFiles, setGitFiles] = React.useState(null);
|
|
1508
2410
|
const [gitError, setGitError] = React.useState(null);
|
|
1509
|
-
const [
|
|
2411
|
+
const [panelFrac, setPanelFrac] = React.useState(null);
|
|
1510
2412
|
const [resizing, setResizing] = React.useState(false);
|
|
1511
2413
|
const [activeView, setActiveView] = React.useState(() => settings.showFileTree ? "tree" : "git");
|
|
1512
2414
|
const [treeRefresh, setTreeRefresh] = React.useState(0);
|
|
2415
|
+
const [searchOpen, setSearchOpen] = React.useState(false);
|
|
1513
2416
|
const [gitRefresh, setGitRefresh] = React.useState(0);
|
|
1514
2417
|
const [gitRefreshing, setGitRefreshing] = React.useState(false);
|
|
1515
2418
|
const [gitFlash, setGitFlash] = React.useState(0);
|
|
1516
2419
|
const noticeTimer = React.useRef(null);
|
|
2420
|
+
React.useEffect(() => () => {
|
|
2421
|
+
if (noticeTimer.current) clearTimeout(noticeTimer.current);
|
|
2422
|
+
}, []);
|
|
1517
2423
|
const gitForceRef = React.useRef(false);
|
|
1518
2424
|
React.useEffect(() => {
|
|
1519
2425
|
if (!open || activeView !== "git") return;
|
|
@@ -1532,7 +2438,7 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1532
2438
|
setGitError(null);
|
|
1533
2439
|
} else {
|
|
1534
2440
|
setGitFiles([]);
|
|
1535
|
-
setGitError(res && res.error || "
|
|
2441
|
+
setGitError(res && res.error || t("gitStatusFailed"));
|
|
1536
2442
|
}
|
|
1537
2443
|
}).catch((e) => {
|
|
1538
2444
|
if (force) setGitRefreshing(false);
|
|
@@ -1595,8 +2501,18 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1595
2501
|
if (document.body) obs.observe(document.body, opts);
|
|
1596
2502
|
return () => obs.disconnect();
|
|
1597
2503
|
}, []);
|
|
1598
|
-
|
|
1599
|
-
|
|
2504
|
+
React.useEffect(() => {
|
|
2505
|
+
const onResize = () => setWinW(window.innerWidth);
|
|
2506
|
+
window.addEventListener("resize", onResize);
|
|
2507
|
+
return () => window.removeEventListener("resize", onResize);
|
|
2508
|
+
}, []);
|
|
2509
|
+
const rightOffset = (() => {
|
|
2510
|
+
const n = parseFloat(document.documentElement.style.getPropertyValue("--dsh-sidebar-width"));
|
|
2511
|
+
return Number.isFinite(n) ? n : 0;
|
|
2512
|
+
})();
|
|
2513
|
+
const avail = Math.max(120, winW - rightOffset);
|
|
2514
|
+
const minWidthPx = Math.max(80, Math.round(winW * (settings.minPanelWidth || 0) / 100));
|
|
2515
|
+
const widthPx = panelFrac != null ? Math.max(minWidthPx, Math.round(panelFrac * avail)) : minWidthPx;
|
|
1600
2516
|
React.useEffect(() => {
|
|
1601
2517
|
const root = document.documentElement;
|
|
1602
2518
|
root.style.setProperty("--dsh-flyout-sidebar-width", open ? widthPx + "px" : "0px");
|
|
@@ -1611,26 +2527,36 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1611
2527
|
document.body.removeAttribute("data-dsh-flyout-dragging");
|
|
1612
2528
|
};
|
|
1613
2529
|
}, [resizing]);
|
|
2530
|
+
const resizeHandlers = React.useRef(null);
|
|
2531
|
+
React.useEffect(() => () => {
|
|
2532
|
+
const h = resizeHandlers.current;
|
|
2533
|
+
if (h) {
|
|
2534
|
+
document.removeEventListener("mousemove", h.onMove);
|
|
2535
|
+
document.removeEventListener("mouseup", h.onUp);
|
|
2536
|
+
}
|
|
2537
|
+
}, []);
|
|
1614
2538
|
const { visible, slidOut } = useSlide();
|
|
1615
2539
|
if (!visible) return null;
|
|
1616
2540
|
const flyoutHref = "/flyout-sidebar" + (sessionId ? "?sessionId=" + encodeURIComponent(sessionId) : "");
|
|
1617
2541
|
const startResize = (e) => {
|
|
1618
2542
|
e.preventDefault();
|
|
1619
2543
|
setResizing(true);
|
|
1620
|
-
const
|
|
1621
|
-
const v = document.documentElement.style.getPropertyValue("--dsh-sidebar-width");
|
|
1622
|
-
const n = parseFloat(v);
|
|
1623
|
-
return Number.isFinite(n) ? n : 0;
|
|
1624
|
-
})();
|
|
2544
|
+
const availAtStart = avail;
|
|
1625
2545
|
const onMove = (ev) => {
|
|
1626
2546
|
const w = window.innerWidth - ev.clientX - rightOffset;
|
|
1627
|
-
|
|
2547
|
+
const frac = Math.max(minWidthPx / availAtStart, Math.min(w / availAtStart, (availAtStart - 24) / availAtStart));
|
|
2548
|
+
setPanelFrac(frac);
|
|
1628
2549
|
};
|
|
1629
2550
|
const onUp = () => {
|
|
1630
2551
|
setResizing(false);
|
|
2552
|
+
resizeHandlers.current = null;
|
|
1631
2553
|
document.removeEventListener("mousemove", onMove);
|
|
1632
2554
|
document.removeEventListener("mouseup", onUp);
|
|
1633
2555
|
};
|
|
2556
|
+
resizeHandlers.current = {
|
|
2557
|
+
onMove,
|
|
2558
|
+
onUp
|
|
2559
|
+
};
|
|
1634
2560
|
document.addEventListener("mousemove", onMove);
|
|
1635
2561
|
document.addEventListener("mouseup", onUp);
|
|
1636
2562
|
};
|
|
@@ -1652,162 +2578,53 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1652
2578
|
};
|
|
1653
2579
|
const quotePath = (path) => {
|
|
1654
2580
|
if (quoteToComposer(path)) {
|
|
1655
|
-
flash("
|
|
2581
|
+
flash(t("insertedInput"));
|
|
1656
2582
|
return;
|
|
1657
2583
|
}
|
|
1658
|
-
copyText("@" + path, "
|
|
1659
|
-
};
|
|
1660
|
-
const patchTab = (key, patch) => setTabs((prev) => prev.map((t) => t.key === key ? {
|
|
1661
|
-
...t,
|
|
1662
|
-
...patch
|
|
1663
|
-
} : t));
|
|
1664
|
-
const openTab = (key, path, git, initial) => {
|
|
1665
|
-
setPreviewHidden(false);
|
|
1666
|
-
setTabs((prev) => {
|
|
1667
|
-
const i = prev.findIndex((t) => t.key === key);
|
|
1668
|
-
if (i >= 0) {
|
|
1669
|
-
const next = prev.slice();
|
|
1670
|
-
const cur = next[i];
|
|
1671
|
-
if (cur) next[i] = {
|
|
1672
|
-
...cur,
|
|
1673
|
-
...initial
|
|
1674
|
-
};
|
|
1675
|
-
return next;
|
|
1676
|
-
}
|
|
1677
|
-
return prev.concat([{
|
|
1678
|
-
key,
|
|
1679
|
-
path,
|
|
1680
|
-
git,
|
|
1681
|
-
...initial
|
|
1682
|
-
}]);
|
|
1683
|
-
});
|
|
1684
|
-
setActiveKey(key);
|
|
1685
|
-
};
|
|
1686
|
-
const closeTab = (key) => {
|
|
1687
|
-
const idx = tabs.findIndex((t) => t.key === key);
|
|
1688
|
-
const next = tabs.filter((t) => t.key !== key);
|
|
1689
|
-
setTabs(next);
|
|
1690
|
-
if (activeKey === key) setActiveKey(next.length ? next[Math.min(idx, next.length - 1)]?.key ?? null : null);
|
|
1691
|
-
};
|
|
1692
|
-
const activeTab = tabs.find((t) => t.key === activeKey) || null;
|
|
1693
|
-
const openFile = (path) => {
|
|
1694
|
-
const key = "p:" + path;
|
|
1695
|
-
const type = extType(path);
|
|
1696
|
-
const initial = {
|
|
1697
|
-
loading: false,
|
|
1698
|
-
type,
|
|
1699
|
-
diff: null
|
|
1700
|
-
};
|
|
1701
|
-
if (type !== "image" && type !== "pdf") initial.loading = true;
|
|
1702
|
-
openTab(key, path, false, initial);
|
|
1703
|
-
if (type === "image" || type === "pdf") return;
|
|
1704
|
-
host.readArtifact(path).then((res) => {
|
|
1705
|
-
patchTab(key, {
|
|
1706
|
-
loading: false,
|
|
1707
|
-
...res
|
|
1708
|
-
});
|
|
1709
|
-
}).catch((e) => {
|
|
1710
|
-
patchTab(key, {
|
|
1711
|
-
loading: false,
|
|
1712
|
-
ok: false,
|
|
1713
|
-
error: String(e instanceof Error && e.message ? e.message : e)
|
|
1714
|
-
});
|
|
1715
|
-
});
|
|
1716
|
-
};
|
|
1717
|
-
const openGitDiff = (path) => {
|
|
1718
|
-
const key = "g:" + path;
|
|
1719
|
-
openTab(key, path, true, { loading: true });
|
|
1720
|
-
host.gitDiff(path, currentSessionId()).then((res) => {
|
|
1721
|
-
patchTab(key, {
|
|
1722
|
-
loading: false,
|
|
1723
|
-
...res
|
|
1724
|
-
});
|
|
1725
|
-
}).catch((e) => {
|
|
1726
|
-
patchTab(key, {
|
|
1727
|
-
loading: false,
|
|
1728
|
-
ok: false,
|
|
1729
|
-
error: String(e instanceof Error && e.message ? e.message : e)
|
|
1730
|
-
});
|
|
1731
|
-
});
|
|
2584
|
+
copyText("@" + path, t("copiedRef"));
|
|
1732
2585
|
};
|
|
1733
|
-
|
|
1734
|
-
if (
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
D: "删除",
|
|
1744
|
-
R: "重命名",
|
|
1745
|
-
C: "复制"
|
|
2586
|
+
React.useEffect(() => {
|
|
2587
|
+
if (!open) return;
|
|
2588
|
+
const onKey = (ev) => {
|
|
2589
|
+
if (ev.key !== "Escape") return;
|
|
2590
|
+
const target = ev.target;
|
|
2591
|
+
if (target && (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable)) return;
|
|
2592
|
+
if (tabs.length && !previewHidden) {
|
|
2593
|
+
if (activeKey) closeTab(activeKey);
|
|
2594
|
+
else setPreviewHidden(true);
|
|
2595
|
+
} else store.setOpen(false);
|
|
1746
2596
|
};
|
|
1747
|
-
|
|
1748
|
-
return (
|
|
1749
|
-
}
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
else if (gitFiles == null) gitListRows.push(/* @__PURE__ */ h("div", {
|
|
1756
|
-
key: "load",
|
|
1757
|
-
className: "artifacts-empty"
|
|
1758
|
-
}, "加载变更列表…"));
|
|
1759
|
-
else if (!gitFiles.length) gitListRows.push(/* @__PURE__ */ h("div", {
|
|
1760
|
-
key: "empty",
|
|
1761
|
-
className: "artifacts-empty"
|
|
1762
|
-
}, "没有未提交的变更"));
|
|
1763
|
-
(gitFiles || []).forEach((e, idx) => {
|
|
1764
|
-
const label = gitLabel(e);
|
|
1765
|
-
const isActive = !!(activeTab && activeTab.git && activeTab.path === e.path);
|
|
1766
|
-
const flashKey = gitFlash > 0 ? gitFlash + ":" : "";
|
|
1767
|
-
gitListRows.push(/* @__PURE__ */ h("div", {
|
|
1768
|
-
key: flashKey + e.path,
|
|
1769
|
-
className: "artifacts-item" + (isActive ? " is-active" : "") + (flashKey ? " artifacts-flash-in" : ""),
|
|
1770
|
-
style: flashKey ? { animationDelay: Math.min(idx, 12) * 45 + "ms" } : void 0
|
|
1771
|
-
}, /* @__PURE__ */ h("button", {
|
|
1772
|
-
type: "button",
|
|
1773
|
-
className: "artifacts-item-main",
|
|
1774
|
-
title: gitTitle(e),
|
|
1775
|
-
onClick: () => openGitDiff(e.path)
|
|
1776
|
-
}, /* @__PURE__ */ h("div", { className: "artifacts-item-row" }, /* @__PURE__ */ h("span", { className: "artifacts-git-badge artifacts-git-badge-" + label }, label), /* @__PURE__ */ h("span", { className: "artifacts-item-base" }, basename(e.path)), e.origPath ? /* @__PURE__ */ h("span", { className: "artifacts-git-orig" }, "← ", basename(e.origPath)) : null), /* @__PURE__ */ h("div", { className: "artifacts-item-full" }, e.path)), /* @__PURE__ */ h("div", { className: "artifacts-item-actions" }, /* @__PURE__ */ h("button", {
|
|
1777
|
-
type: "button",
|
|
1778
|
-
className: "artifacts-minibtn",
|
|
1779
|
-
title: "复制路径",
|
|
1780
|
-
onClick: () => copyText(e.path, "已复制路径")
|
|
1781
|
-
}, "⧉"), /* @__PURE__ */ h("button", {
|
|
1782
|
-
type: "button",
|
|
1783
|
-
className: "artifacts-minibtn",
|
|
1784
|
-
title: "@引用到输入框",
|
|
1785
|
-
onClick: () => quotePath(e.path)
|
|
1786
|
-
}, "@"))));
|
|
1787
|
-
});
|
|
2597
|
+
document.addEventListener("keydown", onKey);
|
|
2598
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
2599
|
+
}, [
|
|
2600
|
+
open,
|
|
2601
|
+
tabs,
|
|
2602
|
+
previewHidden,
|
|
2603
|
+
activeKey
|
|
2604
|
+
]);
|
|
1788
2605
|
const previewOverlay = tabs.length && !previewHidden ? /* @__PURE__ */ h("div", {
|
|
1789
2606
|
className: "artifacts-preview-overlay" + (slidOut ? " artifacts-slid-out" : ""),
|
|
1790
2607
|
role: "region",
|
|
1791
|
-
"aria-label": "
|
|
1792
|
-
}, /* @__PURE__ */ h("div", { className: "artifacts-preview-overlay-tabs" }, /* @__PURE__ */ h("div", { className: "artifacts-ptabs-scroll" }, tabs.map((
|
|
1793
|
-
key:
|
|
1794
|
-
className: "artifacts-ptab" + (
|
|
1795
|
-
title: (
|
|
1796
|
-
onClick: () => setActiveKey(
|
|
1797
|
-
}, /* @__PURE__ */ h("span", { className: "artifacts-ptab-name" }, basename(
|
|
2608
|
+
"aria-label": t("previewRegion")
|
|
2609
|
+
}, /* @__PURE__ */ h("div", { className: "artifacts-preview-overlay-tabs" }, /* @__PURE__ */ h("div", { className: "artifacts-ptabs-scroll" }, tabs.map((tab) => /* @__PURE__ */ h("div", {
|
|
2610
|
+
key: tab.key,
|
|
2611
|
+
className: "artifacts-ptab" + (tab.key === activeKey ? " is-active" : ""),
|
|
2612
|
+
title: (tab.git ? t("diffTabPrefix") : "") + (tab.path || ""),
|
|
2613
|
+
onClick: () => setActiveKey(tab.key)
|
|
2614
|
+
}, /* @__PURE__ */ h("span", { className: "artifacts-ptab-name" }, basename(tab.path || "")), /* @__PURE__ */ h("button", {
|
|
1798
2615
|
type: "button",
|
|
1799
2616
|
className: "artifacts-ptab-close",
|
|
1800
|
-
title: "
|
|
2617
|
+
title: t("closeTab"),
|
|
1801
2618
|
onClick: (e) => {
|
|
1802
2619
|
e.stopPropagation();
|
|
1803
|
-
closeTab(
|
|
2620
|
+
closeTab(tab.key);
|
|
1804
2621
|
}
|
|
1805
2622
|
}, "×")))), /* @__PURE__ */ h("button", {
|
|
1806
2623
|
type: "button",
|
|
1807
2624
|
className: "artifacts-preview-hide",
|
|
1808
|
-
title: "
|
|
2625
|
+
title: t("hidePreview"),
|
|
1809
2626
|
onClick: () => setPreviewHidden(true)
|
|
1810
|
-
}, /* @__PURE__ */ h(PanelCollapseIcon, { size: 16 }))), activeTab ? renderPreview(activeTab) : null) : null;
|
|
2627
|
+
}, /* @__PURE__ */ h(PanelCollapseIcon, { size: 16 }))), activeTab ? renderPreview(activeTab, settings.codeWrap) : null) : null;
|
|
1811
2628
|
return /* @__PURE__ */ h(Fragment, null, previewOverlay, /* @__PURE__ */ h("div", {
|
|
1812
2629
|
className: "artifacts-panel" + (slidOut ? " artifacts-slid-out" : "") + (resizing ? " artifacts-resizing" : ""),
|
|
1813
2630
|
style: { width: widthPx },
|
|
@@ -1815,23 +2632,29 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1815
2632
|
"aria-label": "Artifacts"
|
|
1816
2633
|
}, /* @__PURE__ */ h("div", {
|
|
1817
2634
|
className: "artifacts-resize",
|
|
1818
|
-
title: "
|
|
2635
|
+
title: t("resizeHandle"),
|
|
1819
2636
|
onMouseDown: startResize
|
|
1820
2637
|
}), /* @__PURE__ */ h("div", { className: "artifacts-head" }, /* @__PURE__ */ h("div", { className: "artifacts-head-left" }, /* @__PURE__ */ h("button", {
|
|
1821
2638
|
type: "button",
|
|
1822
2639
|
className: "artifacts-toggle",
|
|
1823
|
-
title: "
|
|
2640
|
+
title: t("collapsePanel"),
|
|
1824
2641
|
onClick: () => store.setOpen(false)
|
|
1825
2642
|
}, /* @__PURE__ */ h(PanelIcon, { size: 16 })), /* @__PURE__ */ h("a", {
|
|
1826
2643
|
className: "artifacts-link",
|
|
1827
2644
|
href: flyoutHref,
|
|
1828
2645
|
target: "_blank",
|
|
1829
2646
|
rel: "noreferrer noopener",
|
|
1830
|
-
title: "
|
|
1831
|
-
}, /* @__PURE__ */ h(FlyoutIcon, { size: 16 }))), /* @__PURE__ */ h("span", { className: "artifacts-spacer" }), notice ? /* @__PURE__ */ h("span", { className: "artifacts-notice" }, notice) : null, /* @__PURE__ */ h("button", {
|
|
2647
|
+
title: t("flyoutOpen")
|
|
2648
|
+
}, /* @__PURE__ */ h(FlyoutIcon, { size: 16 }))), /* @__PURE__ */ h("span", { className: "artifacts-spacer" }), notice ? /* @__PURE__ */ h("span", { className: "artifacts-notice" }, notice) : null, activeView === "tree" && settings.showFileTree ? /* @__PURE__ */ h("button", {
|
|
2649
|
+
type: "button",
|
|
2650
|
+
className: "artifacts-toggle artifacts-search-toggle" + (searchOpen ? " is-active" : ""),
|
|
2651
|
+
title: t("searchToggle"),
|
|
2652
|
+
"aria-pressed": searchOpen,
|
|
2653
|
+
onClick: () => setSearchOpen((v) => !v)
|
|
2654
|
+
}, /* @__PURE__ */ h(SearchIcon, { size: 16 })) : null, /* @__PURE__ */ h("button", {
|
|
1832
2655
|
type: "button",
|
|
1833
2656
|
className: "artifacts-toggle",
|
|
1834
|
-
title: activeView === "tree" ? "
|
|
2657
|
+
title: activeView === "tree" ? t("refreshTree") : t("refreshChanges"),
|
|
1835
2658
|
onClick: () => {
|
|
1836
2659
|
if (activeView === "tree") setTreeRefresh((n) => n + 1);
|
|
1837
2660
|
else {
|
|
@@ -1843,24 +2666,35 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1843
2666
|
}, /* @__PURE__ */ h(RefreshIcon, { size: 16 })), settings.showFileTree ? /* @__PURE__ */ h("button", {
|
|
1844
2667
|
type: "button",
|
|
1845
2668
|
className: "artifacts-iconbtn artifacts-viewbtn" + (activeView === "git" ? " is-active" : ""),
|
|
1846
|
-
title: activeView === "tree" ? "
|
|
2669
|
+
title: activeView === "tree" ? t("viewGit") : t("backToFiles"),
|
|
1847
2670
|
"aria-pressed": activeView === "git",
|
|
1848
2671
|
onClick: () => setActiveView(activeView === "tree" ? "git" : "tree")
|
|
1849
2672
|
}, activeView === "tree" ? /* @__PURE__ */ h(GitBranchIcon, { size: 16 }) : /* @__PURE__ */ h(FolderClosedIcon, { size: 16 })) : null), /* @__PURE__ */ h("div", { className: "artifacts-main" }, /* @__PURE__ */ h("div", {
|
|
1850
2673
|
className: "artifacts-body" + (activeView === "git" && gitRefreshing ? " artifacts-refreshing" : ""),
|
|
1851
2674
|
style: { flex: "1 1 auto" }
|
|
1852
|
-
}, activeView === "tree" && settings.showFileTree ? /* @__PURE__ */ h(FileTree, {
|
|
2675
|
+
}, open ? activeView === "tree" && settings.showFileTree ? /* @__PURE__ */ h(FileTree, {
|
|
1853
2676
|
onOpen: openFile,
|
|
1854
2677
|
selectedPath: activeTab && !activeTab.git ? activeTab.path : null,
|
|
1855
|
-
refreshToken: treeRefresh
|
|
1856
|
-
|
|
2678
|
+
refreshToken: treeRefresh,
|
|
2679
|
+
searchOpen,
|
|
2680
|
+
onCloseSearch: () => setSearchOpen(false)
|
|
2681
|
+
}) : /* @__PURE__ */ h(GitChangesList, {
|
|
2682
|
+
files: gitFiles,
|
|
2683
|
+
error: gitError,
|
|
2684
|
+
activeDiffPath: activeTab && activeTab.git ? activeTab.path : null,
|
|
2685
|
+
flashKey: gitFlash,
|
|
2686
|
+
onOpen: openGitDiff,
|
|
2687
|
+
onCopyPath: (p) => copyText(p, t("copiedPath")),
|
|
2688
|
+
onQuote: quotePath
|
|
2689
|
+
}) : null))));
|
|
1857
2690
|
}
|
|
1858
2691
|
function CornerButton() {
|
|
1859
2692
|
const open = useOpen();
|
|
2693
|
+
useLang();
|
|
1860
2694
|
return /* @__PURE__ */ h("button", {
|
|
1861
2695
|
type: "button",
|
|
1862
2696
|
className: "artifacts-corner-btn" + (open ? " artifacts-slid-out" : ""),
|
|
1863
|
-
title: "
|
|
2697
|
+
title: t("flyoutTitle"),
|
|
1864
2698
|
"aria-expanded": open,
|
|
1865
2699
|
onClick: () => store.toggle()
|
|
1866
2700
|
}, /* @__PURE__ */ h(PanelIcon, { size: 18 }));
|
|
@@ -1878,23 +2712,29 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1878
2712
|
}
|
|
1879
2713
|
function SettingsSection() {
|
|
1880
2714
|
const settings = useSettings();
|
|
2715
|
+
useLang();
|
|
1881
2716
|
const set = (key, value) => settingsStore.set(key, value);
|
|
1882
|
-
return /* @__PURE__ */ h("div", { className: "artifacts-settings" }, /* @__PURE__ */ h("p", { className: "artifacts-setintro" }, "
|
|
1883
|
-
label: "
|
|
1884
|
-
desc: "
|
|
2717
|
+
return /* @__PURE__ */ h("div", { className: "artifacts-settings" }, /* @__PURE__ */ h("p", { className: "artifacts-setintro" }, t("settingsIntro")), /* @__PURE__ */ h("div", { className: "artifacts-setgroup" }, /* @__PURE__ */ h(SettingsToggle, {
|
|
2718
|
+
label: t("setDefaultOpen"),
|
|
2719
|
+
desc: t("setDefaultOpenDesc"),
|
|
1885
2720
|
value: settings.defaultOpen,
|
|
1886
2721
|
onToggle: (v) => set("defaultOpen", v)
|
|
1887
2722
|
}), /* @__PURE__ */ h(SettingsToggle, {
|
|
1888
|
-
label: "
|
|
1889
|
-
desc: "
|
|
2723
|
+
label: t("setAutoRefresh"),
|
|
2724
|
+
desc: t("setAutoRefreshDesc"),
|
|
1890
2725
|
value: settings.autoRefresh,
|
|
1891
2726
|
onToggle: (v) => set("autoRefresh", v)
|
|
1892
2727
|
}), /* @__PURE__ */ h(SettingsToggle, {
|
|
1893
|
-
label: "
|
|
1894
|
-
desc: "
|
|
2728
|
+
label: t("setFileTree"),
|
|
2729
|
+
desc: t("setFileTreeDesc"),
|
|
1895
2730
|
value: settings.showFileTree,
|
|
1896
2731
|
onToggle: (v) => set("showFileTree", v)
|
|
1897
|
-
}), /* @__PURE__ */ h(
|
|
2732
|
+
}), /* @__PURE__ */ h(SettingsToggle, {
|
|
2733
|
+
label: t("setCodeWrap"),
|
|
2734
|
+
desc: t("setCodeWrapDesc"),
|
|
2735
|
+
value: settings.codeWrap,
|
|
2736
|
+
onToggle: (v) => set("codeWrap", v)
|
|
2737
|
+
}), /* @__PURE__ */ h("div", { className: "artifacts-setrow" }, /* @__PURE__ */ h("div", { className: "artifacts-settext" }, /* @__PURE__ */ h("div", { className: "artifacts-settitle" }, t("setMinWidth")), /* @__PURE__ */ h("div", { className: "artifacts-setdesc" }, t("setMinWidthDesc"))), /* @__PURE__ */ h("div", { className: "artifacts-setcontrol" }, /* @__PURE__ */ h("input", {
|
|
1898
2738
|
type: "number",
|
|
1899
2739
|
className: "artifacts-widthinput",
|
|
1900
2740
|
min: 20,
|
|
@@ -1904,8 +2744,19 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1904
2744
|
const n = parseInt(e.currentTarget.value, 10);
|
|
1905
2745
|
if (Number.isNaN(n)) return;
|
|
1906
2746
|
set("minPanelWidth", Math.max(20, Math.min(60, n)));
|
|
2747
|
+
},
|
|
2748
|
+
onBlur: (e) => {
|
|
2749
|
+
const n = parseInt(e.currentTarget.value, 10);
|
|
2750
|
+
set("minPanelWidth", Number.isNaN(n) ? 20 : Math.max(20, Math.min(60, n)));
|
|
2751
|
+
}
|
|
2752
|
+
}), /* @__PURE__ */ h("span", { className: "artifacts-suffix" }, "%"))), /* @__PURE__ */ h("div", { className: "artifacts-setrow" }, /* @__PURE__ */ h("div", { className: "artifacts-settext" }, /* @__PURE__ */ h("div", { className: "artifacts-settitle" }, t("setLang")), /* @__PURE__ */ h("div", { className: "artifacts-setdesc" }, t("setLangDesc"))), /* @__PURE__ */ h("div", { className: "artifacts-setcontrol" }, /* @__PURE__ */ h("select", {
|
|
2753
|
+
className: "artifacts-langselect",
|
|
2754
|
+
value: getLanguageSetting() || "",
|
|
2755
|
+
onChange: (e) => {
|
|
2756
|
+
const v = e.currentTarget.value;
|
|
2757
|
+
setLanguage(v === "zh" || v === "en" ? v : null);
|
|
1907
2758
|
}
|
|
1908
|
-
}), /* @__PURE__ */ h("
|
|
2759
|
+
}, /* @__PURE__ */ h("option", { value: "" }, t("langAuto")), /* @__PURE__ */ h("option", { value: "zh" }, t("langZh")), /* @__PURE__ */ h("option", { value: "en" }, t("langEn")))))));
|
|
1909
2760
|
}
|
|
1910
2761
|
//#endregion
|
|
1911
2762
|
//#region src/client/index.tsx
|