dsh-flyout-sidebar 0.1.2 → 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 +43 -9
- package/README_EN.md +44 -9
- package/dist/client.js +1062 -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 {
|
|
@@ -263,6 +272,11 @@ body[data-ds-dark-theme] .gd-add { color: #69db7c; }
|
|
|
263
272
|
body[data-ds-dark-theme] .gd-del { color: #faa2c1; }
|
|
264
273
|
.artifacts-tree { display: flex; flex-direction: column; height: 100%; min-height: 0; }
|
|
265
274
|
.artifacts-tree-body { flex: 1; min-height: 0; overflow-y: auto; padding: 2px 6px 8px; }
|
|
275
|
+
.artifacts-searchbar { flex: none; padding: 8px 10px 4px; }
|
|
276
|
+
.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; }
|
|
277
|
+
.artifacts-search-input:focus { border-color: var(--dsw-alias-button-primary-fill); }
|
|
278
|
+
.artifacts-search-input::placeholder { color: var(--dsw-alias-label-tertiary); }
|
|
279
|
+
.artifacts-search-dir { color: var(--dsw-alias-label-tertiary); font-size: 11px; margin-left: 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
266
280
|
.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
281
|
.artifacts-tree-row:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
268
282
|
.artifacts-tree-dir { font: var(--dsw-font-s-strong-14); }
|
|
@@ -275,6 +289,14 @@ body[data-ds-dark-theme] .gd-del { color: #faa2c1; }
|
|
|
275
289
|
.artifacts-tree-copied { font: var(--dsw-font-xxxs-11); color: var(--dsw-alias-label-tertiary); flex: none; }
|
|
276
290
|
.artifacts-tree-loading { cursor: default; color: var(--dsw-alias-label-tertiary); font-size: 12px; }
|
|
277
291
|
.artifacts-tree-error { cursor: default; color: var(--dsw-alias-state-error-primary); font-size: 12px; }
|
|
292
|
+
.artifacts-tree-fail { padding: 24px 16px; display: flex; flex-direction: column; align-items: center; gap: 10px; }
|
|
293
|
+
.artifacts-tree-fail .artifacts-tree-error { text-align: center; word-break: break-all; }
|
|
294
|
+
.artifacts-retry {
|
|
295
|
+
padding: 4px 16px; font-size: 12px; cursor: pointer; border-radius: 6px;
|
|
296
|
+
border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-base);
|
|
297
|
+
color: var(--dsw-alias-label-primary);
|
|
298
|
+
}
|
|
299
|
+
.artifacts-retry:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
278
300
|
@keyframes artifacts-row-in { 0% { opacity: 0 } }
|
|
279
301
|
.artifacts-settings { display: flex; flex-direction: column; gap: 14px; width: 100%; height: 100%; min-height: 0; overflow-y: auto; padding-bottom: 24px; }
|
|
280
302
|
.artifacts-setintro { color: var(--dsw-alias-label-tertiary); margin: 0; padding: 0 2px; font-size: 13px; line-height: 20px; }
|
|
@@ -294,13 +316,22 @@ body[data-ds-dark-theme] .gd-del { color: #faa2c1; }
|
|
|
294
316
|
.artifacts-switch input:focus-visible + .artifacts-switch-track { outline: 2px solid var(--dsw-alias-state-business-primary); outline-offset: 2px; }
|
|
295
317
|
.artifacts-setcontrol { flex: none; align-items: center; gap: 6px; display: flex; }
|
|
296
318
|
.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; }
|
|
319
|
+
.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
320
|
.artifacts-suffix { color: var(--dsw-alias-label-secondary); font-size: 14px; line-height: 22px; }
|
|
298
321
|
.artifacts-code { display: flex; flex-direction: column; height: 100%; min-height: 0; }
|
|
299
|
-
|
|
300
|
-
.artifacts-code-
|
|
301
|
-
.artifacts-code-
|
|
322
|
+
/* 代码视图:逐行渲染(行 = 行号 + 代码),软换行时行号仍对齐 */
|
|
323
|
+
.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))); }
|
|
324
|
+
.artifacts-code-line { display: flex; align-items: flex-start; min-width: fit-content; }
|
|
325
|
+
.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); }
|
|
326
|
+
.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
327
|
.artifacts-code-pre code { font: inherit; color: inherit; }
|
|
303
|
-
.artifacts-code-
|
|
328
|
+
.artifacts-code-wrap .artifacts-code-pre { white-space: pre-wrap; word-break: break-all; }
|
|
329
|
+
.artifacts-imgview { flex: 1; min-height: 0; overflow: auto; display: flex; align-items: center; justify-content: center; cursor: grab; user-select: none; }
|
|
330
|
+
.artifacts-imgview.is-dragging { cursor: grabbing; }
|
|
331
|
+
.artifacts-imgview .artifacts-img { flex: none; margin: 16px; transform-origin: center; will-change: transform; }
|
|
332
|
+
.artifacts-git-stats { flex: none; display: inline-flex; gap: 4px; font: 11px/16px var(--dsh-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); }
|
|
333
|
+
.artifacts-git-adds { color: var(--p-success-fg, #1a7f37); }
|
|
334
|
+
.artifacts-git-dels { color: var(--p-error, #cf222e); }
|
|
304
335
|
.tok-comment { color: #868e96; }
|
|
305
336
|
.tok-string { color: #2f9e44; }
|
|
306
337
|
.tok-number, .tok-bool, .tok-variable, .tok-hex, .tok-attr { color: #e8590c; }
|
|
@@ -385,11 +416,339 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
385
416
|
return m ? (m[1] || "").toLowerCase() : "";
|
|
386
417
|
}
|
|
387
418
|
//#endregion
|
|
388
|
-
//#region src/
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
419
|
+
//#region src/shared/i18n.js
|
|
420
|
+
/**
|
|
421
|
+
* 共享:中英双语 UI 文案。
|
|
422
|
+
*
|
|
423
|
+
* 与 ext/highlight/markdown 一样遵守「可移植源码」约束:只能使用 JSDoc 标注
|
|
424
|
+
* 类型(不得出现 TS 语法注记)、不得引入本目录之外的依赖。本模块被同时用于:
|
|
425
|
+
* 1. tsdown 打包进 client 侧(浏览器 React bundle);
|
|
426
|
+
* 2. tsdown 构建期经 `?raw` 读入原始文本,内联进独立弹出页 /flyout-sidebar
|
|
427
|
+
* 的经典 <script>(见 src/host/page.ts)。
|
|
428
|
+
*
|
|
429
|
+
* 语言选择:localStorage `dsh-flyout-sidebar:lang`('zh' | 'en',主面板的
|
|
430
|
+
* 设置项写入、弹出页读取),未设置时按浏览器语言自动判定。`t(key)` 取当前
|
|
431
|
+
* 语言文案,缺失时回退另一语言、再回退 key 本身。
|
|
432
|
+
*/
|
|
433
|
+
/** @type {Record<string, string>} */
|
|
434
|
+
const ZH = {
|
|
435
|
+
loading: "加载中…",
|
|
436
|
+
loadingTree: "加载文件树…",
|
|
437
|
+
loadingChanges: "加载变更列表…",
|
|
438
|
+
loadingPdf: "加载 PDF…",
|
|
439
|
+
searching: "搜索中…",
|
|
440
|
+
emptyDir: "(空目录)",
|
|
441
|
+
noChanges: "没有未提交的变更",
|
|
442
|
+
noChangesHead: "没有未提交的变更(相对于 HEAD)",
|
|
443
|
+
noResults: "没有匹配的文件",
|
|
444
|
+
readFailed: "读取失败",
|
|
445
|
+
retry: "重试",
|
|
446
|
+
gitStatusFailed: "git status 失败",
|
|
447
|
+
imageLoadFailed: "图片加载失败",
|
|
448
|
+
pdfLoadFailed: "pdf.js 加载失败",
|
|
449
|
+
searchFailed: "搜索失败",
|
|
450
|
+
treeLoadFailed: "加载失败",
|
|
451
|
+
truncated: "(已截断的预览)",
|
|
452
|
+
copied: "已复制",
|
|
453
|
+
copiedPath: "已复制路径",
|
|
454
|
+
copiedRef: "已复制 @引用(未能写入输入框)",
|
|
455
|
+
insertedInput: "已插入输入框",
|
|
456
|
+
refBtn: "@引用",
|
|
457
|
+
refTitle: "引用到输入框(失败则复制 @path)",
|
|
458
|
+
refFlyoutTitle: "复制 @path 引用",
|
|
459
|
+
copyPath: "复制路径",
|
|
460
|
+
refInput: "@引用到输入框",
|
|
461
|
+
collapsePanel: "收起侧边栏",
|
|
462
|
+
panelAria: "文件面板",
|
|
463
|
+
flyoutTitle: "弹出式侧边栏",
|
|
464
|
+
flyoutOpen: "弹出式侧边栏 — 在新标签页打开(可拖到另一块显示器)",
|
|
465
|
+
resizeHandle: "拖动调整宽度",
|
|
466
|
+
resizePanel: "拖动调整面板宽度",
|
|
467
|
+
refreshTree: "刷新文件树",
|
|
468
|
+
refreshChanges: "刷新变更列表",
|
|
469
|
+
refresh: "刷新",
|
|
470
|
+
viewGit: "查看 Git 变更(未提交)",
|
|
471
|
+
backToFiles: "返回文件列表",
|
|
472
|
+
hidePreview: "隐藏预览(标签页保留)",
|
|
473
|
+
closeTab: "关闭标签页",
|
|
474
|
+
previewRegion: "文件预览",
|
|
475
|
+
diffTabPrefix: "[diff] ",
|
|
476
|
+
searchPlaceholder: "搜索文件…",
|
|
477
|
+
searchToggle: "搜索文件",
|
|
478
|
+
hintClickGit: "点击右侧变更文件查看 diff",
|
|
479
|
+
hintClickTree: "点击右侧文件查看内容",
|
|
480
|
+
movePanelLeft: "将文件面板移到左侧",
|
|
481
|
+
movePanelRight: "将文件面板移到右侧",
|
|
482
|
+
statusU: "未跟踪",
|
|
483
|
+
statusA: "新增",
|
|
484
|
+
statusM: "修改",
|
|
485
|
+
statusD: "删除",
|
|
486
|
+
statusR: "重命名",
|
|
487
|
+
statusC: "复制",
|
|
488
|
+
statusT: "类型变更",
|
|
489
|
+
staged: "(已暂存)",
|
|
490
|
+
unstaged: "(未暂存)",
|
|
491
|
+
diffDeleted: "- 删除",
|
|
492
|
+
diffAdded: "+ 新增",
|
|
493
|
+
zoomOut: "缩小",
|
|
494
|
+
zoomIn: "放大",
|
|
495
|
+
prevPage: "上一页",
|
|
496
|
+
nextPage: "下一页",
|
|
497
|
+
settingsIntro: "管理「Flyout Sidebar」的显示与行为。",
|
|
498
|
+
setDefaultOpen: "默认展开",
|
|
499
|
+
setDefaultOpenDesc: "页面加载后侧边栏默认展开;关闭则默认收起,点右上角图标再打开。",
|
|
500
|
+
setAutoRefresh: "自动刷新",
|
|
501
|
+
setAutoRefreshDesc: "开启后侧边栏展开时将即时同步并更新产物列表",
|
|
502
|
+
setFileTree: "文件树",
|
|
503
|
+
setFileTreeDesc: "在侧边栏显示「文件树」标签页,浏览工作区目录。",
|
|
504
|
+
setMinWidth: "最短面板宽度",
|
|
505
|
+
setMinWidthDesc: "面板的最小宽度(占窗口宽度的百分比,20–60);更宽可通过拖动面板左边缘调整。",
|
|
506
|
+
setCodeWrap: "代码换行",
|
|
507
|
+
setCodeWrapDesc: "代码预览长行软换行;关闭则横向滚动。",
|
|
508
|
+
wordWrap: "自动换行",
|
|
509
|
+
openInEditor: "在系统编辑器打开",
|
|
510
|
+
openedInEditor: "已在编辑器打开",
|
|
511
|
+
openFailed: "打开失败",
|
|
512
|
+
setLang: "界面语言",
|
|
513
|
+
setLangDesc: "侧边栏与独立弹出页的显示语言;独立弹出页需刷新后生效。",
|
|
514
|
+
langAuto: "跟随浏览器",
|
|
515
|
+
langZh: "中文",
|
|
516
|
+
langEn: "English"
|
|
517
|
+
};
|
|
518
|
+
/** @type {Record<string, string>} */
|
|
519
|
+
const EN = {
|
|
520
|
+
loading: "Loading…",
|
|
521
|
+
loadingTree: "Loading file tree…",
|
|
522
|
+
loadingChanges: "Loading change list…",
|
|
523
|
+
loadingPdf: "Loading PDF…",
|
|
524
|
+
searching: "Searching…",
|
|
525
|
+
emptyDir: "(empty directory)",
|
|
526
|
+
noChanges: "No uncommitted changes",
|
|
527
|
+
noChangesHead: "No uncommitted changes (relative to HEAD)",
|
|
528
|
+
noResults: "No matching files",
|
|
529
|
+
readFailed: "Failed to read",
|
|
530
|
+
retry: "Retry",
|
|
531
|
+
gitStatusFailed: "git status failed",
|
|
532
|
+
imageLoadFailed: "Failed to load image",
|
|
533
|
+
pdfLoadFailed: "Failed to load pdf.js",
|
|
534
|
+
searchFailed: "Search failed",
|
|
535
|
+
treeLoadFailed: "Failed to load",
|
|
536
|
+
truncated: "(truncated preview)",
|
|
537
|
+
copied: "Copied",
|
|
538
|
+
copiedPath: "Path copied",
|
|
539
|
+
copiedRef: "Copied @reference (could not write to input box)",
|
|
540
|
+
insertedInput: "Inserted into input box",
|
|
541
|
+
refBtn: "@ref",
|
|
542
|
+
refTitle: "Quote into input box (falls back to copying @path)",
|
|
543
|
+
refFlyoutTitle: "Copy @path reference",
|
|
544
|
+
copyPath: "Copy path",
|
|
545
|
+
refInput: "@reference into input box",
|
|
546
|
+
collapsePanel: "Collapse sidebar",
|
|
547
|
+
panelAria: "File panel",
|
|
548
|
+
flyoutTitle: "Flyout sidebar",
|
|
549
|
+
flyoutOpen: "Flyout sidebar — open in a new tab (drag to another display)",
|
|
550
|
+
resizeHandle: "Drag to resize",
|
|
551
|
+
resizePanel: "Drag to resize panel",
|
|
552
|
+
refreshTree: "Refresh file tree",
|
|
553
|
+
refreshChanges: "Refresh change list",
|
|
554
|
+
refresh: "Refresh",
|
|
555
|
+
viewGit: "View Git changes (uncommitted)",
|
|
556
|
+
backToFiles: "Back to file list",
|
|
557
|
+
hidePreview: "Hide preview (tabs are kept)",
|
|
558
|
+
closeTab: "Close tab",
|
|
559
|
+
previewRegion: "File preview",
|
|
560
|
+
diffTabPrefix: "[diff] ",
|
|
561
|
+
searchPlaceholder: "Search files…",
|
|
562
|
+
searchToggle: "Search files",
|
|
563
|
+
hintClickGit: "Click a changed file to view its diff",
|
|
564
|
+
hintClickTree: "Click a file to view its content",
|
|
565
|
+
movePanelLeft: "Move file panel to the left",
|
|
566
|
+
movePanelRight: "Move file panel to the right",
|
|
567
|
+
statusU: "Untracked",
|
|
568
|
+
statusA: "Added",
|
|
569
|
+
statusM: "Modified",
|
|
570
|
+
statusD: "Deleted",
|
|
571
|
+
statusR: "Renamed",
|
|
572
|
+
statusC: "Copied",
|
|
573
|
+
statusT: "Type change",
|
|
574
|
+
staged: "(staged)",
|
|
575
|
+
unstaged: "(unstaged)",
|
|
576
|
+
diffDeleted: "- Deleted",
|
|
577
|
+
diffAdded: "+ Added",
|
|
578
|
+
zoomOut: "Zoom out",
|
|
579
|
+
zoomIn: "Zoom in",
|
|
580
|
+
prevPage: "Previous page",
|
|
581
|
+
nextPage: "Next page",
|
|
582
|
+
settingsIntro: "Manage the display and behavior of \"Flyout Sidebar\".",
|
|
583
|
+
setDefaultOpen: "Open by default",
|
|
584
|
+
setDefaultOpenDesc: "Expand the sidebar on page load; when off it stays collapsed until the corner icon is clicked.",
|
|
585
|
+
setAutoRefresh: "Auto refresh",
|
|
586
|
+
setAutoRefreshDesc: "Keep the artifact list in sync while the sidebar is open",
|
|
587
|
+
setFileTree: "File tree",
|
|
588
|
+
setFileTreeDesc: "Show the \"File tree\" tab in the sidebar to browse the workspace directory.",
|
|
589
|
+
setMinWidth: "Minimum panel width",
|
|
590
|
+
setMinWidthDesc: "Minimum width of the panel (percentage of window width, 20–60); drag the panel edge to go wider.",
|
|
591
|
+
setCodeWrap: "Code wrap",
|
|
592
|
+
setCodeWrapDesc: "Soft-wrap long lines in code previews; when off they scroll horizontally.",
|
|
593
|
+
wordWrap: "Word wrap",
|
|
594
|
+
openInEditor: "Open in system editor",
|
|
595
|
+
openedInEditor: "Opened in editor",
|
|
596
|
+
openFailed: "Failed to open",
|
|
597
|
+
setLang: "Interface language",
|
|
598
|
+
setLangDesc: "Display language for the sidebar and the standalone flyout page (flyout requires a reload).",
|
|
599
|
+
langAuto: "Auto (browser)",
|
|
600
|
+
langZh: "中文",
|
|
601
|
+
langEn: "English"
|
|
602
|
+
};
|
|
603
|
+
/** @type {Record<string, Record<string, string>>} */
|
|
604
|
+
const DICTS = {
|
|
605
|
+
zh: ZH,
|
|
606
|
+
en: EN
|
|
392
607
|
};
|
|
608
|
+
const LANG_KEY = "dsh-flyout-sidebar:lang";
|
|
609
|
+
/** @type {'zh' | 'en' | null} 已显式选择的语言(null = 跟随浏览器自动判定) */
|
|
610
|
+
let explicitLang = null;
|
|
611
|
+
/** @type {Array<(lang: string) => void>} */
|
|
612
|
+
let listeners = [];
|
|
613
|
+
function readStoredLang() {
|
|
614
|
+
try {
|
|
615
|
+
var v = localStorage.getItem(LANG_KEY);
|
|
616
|
+
return v === "zh" || v === "en" ? v : null;
|
|
617
|
+
} catch (e) {
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
/** 浏览器语言自动判定:非 zh 开头一律英文 */
|
|
622
|
+
function autoLang() {
|
|
623
|
+
try {
|
|
624
|
+
var langs = navigator.languages || (navigator.language ? [navigator.language] : []);
|
|
625
|
+
for (var i = 0; i < langs.length; i++) {
|
|
626
|
+
var l = String(langs[i] || "").toLowerCase();
|
|
627
|
+
if (l.indexOf("zh") === 0) return "zh";
|
|
628
|
+
if (l.indexOf("en") === 0) return "en";
|
|
629
|
+
}
|
|
630
|
+
} catch (e) {}
|
|
631
|
+
return "en";
|
|
632
|
+
}
|
|
633
|
+
function currentLang() {
|
|
634
|
+
if (explicitLang === null) explicitLang = readStoredLang();
|
|
635
|
+
return explicitLang || autoLang();
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* @param {string} key 文案键
|
|
639
|
+
* @returns {string} 当前语言文案;两级回退(另一语言 → key)
|
|
640
|
+
*/
|
|
641
|
+
function t(key) {
|
|
642
|
+
var lang = currentLang();
|
|
643
|
+
var dict = DICTS[lang] || EN;
|
|
644
|
+
if (dict[key] != null) return dict[key];
|
|
645
|
+
var other = DICTS[lang === "zh" ? "en" : "zh"];
|
|
646
|
+
if (!other) return key;
|
|
647
|
+
return other[key] != null ? other[key] : key;
|
|
648
|
+
}
|
|
649
|
+
/** @returns {'zh' | 'en'} 当前生效语言(含自动判定的结果) */
|
|
650
|
+
function getLang() {
|
|
651
|
+
return currentLang();
|
|
652
|
+
}
|
|
653
|
+
/** @returns {'zh' | 'en' | null} 用户显式选择的语言(null = 跟随浏览器) */
|
|
654
|
+
function getExplicitLang() {
|
|
655
|
+
if (explicitLang === null) explicitLang = readStoredLang();
|
|
656
|
+
return explicitLang;
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* 显式设置语言并持久化(null = 恢复跟随浏览器)。
|
|
660
|
+
* @param {'zh' | 'en' | null} lang
|
|
661
|
+
*/
|
|
662
|
+
function setLang(lang) {
|
|
663
|
+
explicitLang = lang;
|
|
664
|
+
try {
|
|
665
|
+
if (lang === null) localStorage.removeItem(LANG_KEY);
|
|
666
|
+
else localStorage.setItem(LANG_KEY, lang);
|
|
667
|
+
} catch (e) {}
|
|
668
|
+
var next = listeners.slice();
|
|
669
|
+
for (var i = 0; i < next.length; i++) {
|
|
670
|
+
var fn = next[i];
|
|
671
|
+
if (!fn) continue;
|
|
672
|
+
try {
|
|
673
|
+
fn(currentLang());
|
|
674
|
+
} catch (e) {}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
/** @param {(lang: string) => void} fn @returns {() => void} 取消订阅 */
|
|
678
|
+
function subscribeLang(fn) {
|
|
679
|
+
listeners.push(fn);
|
|
680
|
+
return function() {
|
|
681
|
+
listeners = listeners.filter(function(f) {
|
|
682
|
+
return f !== fn;
|
|
683
|
+
});
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
//#endregion
|
|
687
|
+
//#region src/shared/gitui.js
|
|
688
|
+
/**
|
|
689
|
+
* 共享:git 变更列表 / diff 的纯展示逻辑。
|
|
690
|
+
*
|
|
691
|
+
* client 侧 React 组件与独立弹出页(page.ts 内联脚本)共用,避免双端各自
|
|
692
|
+
* 实现后细节漂移。与 ext/highlight/markdown 一样遵守「可移植源码」约束:
|
|
693
|
+
* 只能使用 JSDoc 标注类型、依赖仅限本目录(i18n 的 t 在内联场景由同作用域
|
|
694
|
+
* 函数声明提升提供)。
|
|
695
|
+
*/
|
|
696
|
+
/** @param {string} p @returns {string} */
|
|
697
|
+
function basename(p) {
|
|
698
|
+
var parts = String(p).split("/");
|
|
699
|
+
return parts[parts.length - 1] || p;
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* 变更文件的状态徽章字母:未跟踪为 U,否则取工作树状态(y),再退到暂存
|
|
703
|
+
* 状态(x),空缺按 M。
|
|
704
|
+
* @param {{ x: string, y: string }} e @returns {string}
|
|
705
|
+
*/
|
|
706
|
+
function gitLabel(e) {
|
|
707
|
+
if (e.x === "?" || e.y === "?") return "U";
|
|
708
|
+
return (e.y !== " " ? e.y : e.x) || "M";
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* 变更文件行 hover 提示:状态名 + 暂存/未暂存。
|
|
712
|
+
* @param {{ x: string, y: string }} e @returns {string}
|
|
713
|
+
*/
|
|
714
|
+
function gitTitle(e) {
|
|
715
|
+
var label = gitLabel(e);
|
|
716
|
+
/** @type {Record<string, string>} */
|
|
717
|
+
var map = {
|
|
718
|
+
U: t("statusU"),
|
|
719
|
+
A: t("statusA"),
|
|
720
|
+
M: t("statusM"),
|
|
721
|
+
D: t("statusD"),
|
|
722
|
+
R: t("statusR"),
|
|
723
|
+
C: t("statusC"),
|
|
724
|
+
T: t("statusT")
|
|
725
|
+
};
|
|
726
|
+
var staged = e.x !== " " && e.x !== "?";
|
|
727
|
+
return (map[label] || label) + (staged ? t("staged") : t("unstaged"));
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* unified diff 单行的样式类:hunk / 增 / 删 / 元信息(diff --git、index、
|
|
731
|
+
* rename 等),其余为普通行。
|
|
732
|
+
* @param {string} line @returns {string}
|
|
733
|
+
*/
|
|
734
|
+
function gitDiffLineClass(line) {
|
|
735
|
+
var cls = "gd-line";
|
|
736
|
+
if (line.indexOf("@@") === 0) cls += " gd-hunk";
|
|
737
|
+
else if (line.charAt(0) === "+" && line.indexOf("+++") !== 0) cls += " gd-add";
|
|
738
|
+
else if (line.charAt(0) === "-" && line.indexOf("---") !== 0) cls += " gd-del";
|
|
739
|
+
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";
|
|
740
|
+
return cls;
|
|
741
|
+
}
|
|
742
|
+
//#endregion
|
|
743
|
+
//#region src/client/store.ts
|
|
744
|
+
/**
|
|
745
|
+
* Client 侧:共享状态与工具函数。
|
|
746
|
+
*
|
|
747
|
+
* - store / settingsStore:面板开关与功能设置(localStorage 持久化),
|
|
748
|
+
* 触发按钮与面板等组件通过 useOpen / useSettings 订阅。
|
|
749
|
+
* - currentSessionId / quoteToComposer:读取客户端会话库、把 @path 引用
|
|
750
|
+
* 写入会话输入框。
|
|
751
|
+
*/
|
|
393
752
|
/** 当前会话 id(读自客户端会话库;文件树把它传给 host 以定位工作区) */
|
|
394
753
|
function currentSessionId() {
|
|
395
754
|
try {
|
|
@@ -475,7 +834,8 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
475
834
|
autoRefresh: true,
|
|
476
835
|
minPanelWidth: 20,
|
|
477
836
|
showFileTree: true,
|
|
478
|
-
defaultOpen: true
|
|
837
|
+
defaultOpen: true,
|
|
838
|
+
codeWrap: false
|
|
479
839
|
};
|
|
480
840
|
function loadSettings() {
|
|
481
841
|
try {
|
|
@@ -518,7 +878,7 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
518
878
|
};
|
|
519
879
|
store.open = !!settingsStore.get().defaultOpen;
|
|
520
880
|
const slideState = {
|
|
521
|
-
visible:
|
|
881
|
+
visible: true,
|
|
522
882
|
slidOut: !store.open
|
|
523
883
|
};
|
|
524
884
|
const slideListeners = [];
|
|
@@ -537,22 +897,8 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
537
897
|
fn(next);
|
|
538
898
|
} catch {}
|
|
539
899
|
};
|
|
540
|
-
let slideTimer = null;
|
|
541
900
|
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
|
-
}
|
|
901
|
+
setSlide({ slidOut: !open });
|
|
556
902
|
});
|
|
557
903
|
const useSlide = () => {
|
|
558
904
|
const [s, setS] = React.useState({ ...slideState });
|
|
@@ -585,6 +931,22 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
585
931
|
}, []);
|
|
586
932
|
return sessionId;
|
|
587
933
|
}
|
|
934
|
+
/**
|
|
935
|
+
* 界面语言:订阅 i18n 的语言变更,语言切换时强制订阅组件重渲染(组件内的
|
|
936
|
+
* t() 调用随之取到新语言文案)。返回值用于设置区判断下拉框选项。
|
|
937
|
+
*/
|
|
938
|
+
function useLang() {
|
|
939
|
+
const [, force] = React.useReducer((n) => n + 1, 0);
|
|
940
|
+
React.useEffect(() => {
|
|
941
|
+
const unsubscribe = subscribeLang(() => force());
|
|
942
|
+
return () => unsubscribe();
|
|
943
|
+
}, []);
|
|
944
|
+
return getLang();
|
|
945
|
+
}
|
|
946
|
+
/** 当前设置语言:'zh' / 'en'(显式)或 null(跟随浏览器自动判定) */
|
|
947
|
+
const getLanguageSetting = getExplicitLang;
|
|
948
|
+
/** 设置界面语言并持久化;null = 恢复跟随浏览器 */
|
|
949
|
+
const setLanguage = setLang;
|
|
588
950
|
//#endregion
|
|
589
951
|
//#region src/shared/highlight.js
|
|
590
952
|
/**
|
|
@@ -841,6 +1203,67 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
841
1203
|
const fn = HL_ENGINES[hlLangOf(hint)];
|
|
842
1204
|
return fn ? fn(String(src)) : escHtml(src);
|
|
843
1205
|
}
|
|
1206
|
+
/**
|
|
1207
|
+
* 把 highlightCode 输出的 HTML 按源码行拆分成逐行 HTML 数组。跨行 token
|
|
1208
|
+
* (块注释、Python 三引号串等)在行边界处闭合、下一行重新打开,保证逐行
|
|
1209
|
+
* 渲染时着色与整块渲染一致。逐行渲染使行号与代码天然对齐(含软换行模式),
|
|
1210
|
+
* 也是行级查找高亮的基础。
|
|
1211
|
+
* @param {string} html highlightCode 的输出
|
|
1212
|
+
* @returns {string[]}
|
|
1213
|
+
*/
|
|
1214
|
+
function splitHlLines(html) {
|
|
1215
|
+
var lines = [];
|
|
1216
|
+
var cur = "";
|
|
1217
|
+
/** @type {string[]} */
|
|
1218
|
+
var stack = [];
|
|
1219
|
+
var i = 0;
|
|
1220
|
+
var openAll = function() {
|
|
1221
|
+
var s = "";
|
|
1222
|
+
for (var k = 0; k < stack.length; k++) s += "<span class=\"" + stack[k] + "\">";
|
|
1223
|
+
return s;
|
|
1224
|
+
};
|
|
1225
|
+
var closeAll = function() {
|
|
1226
|
+
var s = "";
|
|
1227
|
+
for (var k = 0; k < stack.length; k++) s += "</span>";
|
|
1228
|
+
return s;
|
|
1229
|
+
};
|
|
1230
|
+
while (i < html.length) {
|
|
1231
|
+
if (html.indexOf("<span class=\"", i) === i) {
|
|
1232
|
+
var end = html.indexOf(">", i);
|
|
1233
|
+
if (end < 0) {
|
|
1234
|
+
cur += html.slice(i);
|
|
1235
|
+
break;
|
|
1236
|
+
}
|
|
1237
|
+
cur += html.slice(i, end + 1);
|
|
1238
|
+
stack.push(html.slice(i + 13, end - 1));
|
|
1239
|
+
i = end + 1;
|
|
1240
|
+
continue;
|
|
1241
|
+
}
|
|
1242
|
+
if (html.indexOf("</span>", i) === i) {
|
|
1243
|
+
cur += "</span>";
|
|
1244
|
+
stack.pop();
|
|
1245
|
+
i += 7;
|
|
1246
|
+
continue;
|
|
1247
|
+
}
|
|
1248
|
+
var nl = html.indexOf("\n", i);
|
|
1249
|
+
var nextTag = html.indexOf("<", i);
|
|
1250
|
+
if (nextTag >= 0 && (nl < 0 || nextTag < nl)) {
|
|
1251
|
+
cur += html.slice(i, nextTag);
|
|
1252
|
+
i = nextTag;
|
|
1253
|
+
continue;
|
|
1254
|
+
}
|
|
1255
|
+
if (nl < 0) {
|
|
1256
|
+
cur += html.slice(i);
|
|
1257
|
+
break;
|
|
1258
|
+
}
|
|
1259
|
+
cur += html.slice(i, nl);
|
|
1260
|
+
lines.push(cur + closeAll());
|
|
1261
|
+
cur = openAll();
|
|
1262
|
+
i = nl + 1;
|
|
1263
|
+
}
|
|
1264
|
+
lines.push(cur + closeAll());
|
|
1265
|
+
return lines;
|
|
1266
|
+
}
|
|
844
1267
|
//#endregion
|
|
845
1268
|
//#region src/shared/markdown.js
|
|
846
1269
|
/**
|
|
@@ -852,15 +1275,34 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
852
1275
|
*/
|
|
853
1276
|
/** @param {string} s @returns {string} */
|
|
854
1277
|
function mdEscape(s) {
|
|
855
|
-
return String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1278
|
+
return String(s).replace(/[\u0000\u0001]/g, "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* URL 白名单:只放行 http(s)、站内相对路径、页内锚点与 data:image/*,
|
|
1282
|
+
* 其余(javascript:、vbscript: 及实体编码变体等)一律替换为 '#',防止
|
|
1283
|
+
* 预览渲染出来的链接/图片在点击时执行脚本。
|
|
1284
|
+
* @param {string} u @returns {string}
|
|
1285
|
+
*/
|
|
1286
|
+
function mdSafeUrl(u) {
|
|
1287
|
+
var s = String(u || "").replace(/[\s\u0000-\u001f]/g, "");
|
|
1288
|
+
if (/^https?:\/\//i.test(s)) return s;
|
|
1289
|
+
if (/^\/(?!\/)/.test(s) || /^\.{1,2}\//.test(s) || s.charAt(0) === "#") return s;
|
|
1290
|
+
if (/^data:image\/(?:png|gif|jpeg|webp|bmp|avif);/i.test(s)) return s;
|
|
1291
|
+
return "#";
|
|
856
1292
|
}
|
|
857
1293
|
/** @param {string} s @returns {string} */
|
|
858
1294
|
function mdInline(s) {
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
s = s.replace(
|
|
1295
|
+
/** @type {string[]} */
|
|
1296
|
+
const codes = [];
|
|
1297
|
+
s = s.replace(/`([^`]+)`/g, (m, c) => {
|
|
1298
|
+
codes.push("<code>" + c + "</code>");
|
|
1299
|
+
return "\0" + (codes.length - 1) + "\0";
|
|
1300
|
+
});
|
|
1301
|
+
s = s.replace(/!\[([^\]]*)\]\(([^)\s]+)\)/g, (m, alt, u) => "<img alt=\"" + alt + "\" src=\"" + mdSafeUrl(u) + "\">");
|
|
1302
|
+
s = s.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (m, text, u) => "<a href=\"" + mdSafeUrl(u) + "\" target=\"_blank\" rel=\"noopener noreferrer\">" + text + "</a>");
|
|
862
1303
|
s = s.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
|
|
863
1304
|
s = s.replace(/\*([^*\n]+)\*/g, "<em>$1</em>");
|
|
1305
|
+
s = s.replace(/\u0000(\d+)\u0000/g, (m, i) => codes[Number(i)] ?? "");
|
|
864
1306
|
return s;
|
|
865
1307
|
}
|
|
866
1308
|
/** @param {string} src @returns {string} HTML */
|
|
@@ -943,15 +1385,23 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
943
1385
|
* 自定义 pdf.js 渲染器、Markdown / 图片 / HTML 沙箱、多类型分发。
|
|
944
1386
|
*/
|
|
945
1387
|
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" }, "
|
|
1388
|
+
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
1389
|
}
|
|
948
|
-
/**
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1390
|
+
/** 代码预览:逐行渲染(行号 + 语法高亮同行内对齐,软换行也不串行)。
|
|
1391
|
+
* 文内查找直接用浏览器原生 Ctrl/Cmd+F(真实 DOM 文本可搜)。 */
|
|
1392
|
+
function CodeView({ code, path, wrap }) {
|
|
1393
|
+
const src = String(code || "").replace(/\n$/, "");
|
|
1394
|
+
const hlLines = React.useMemo(() => splitHlLines(highlightCode(src, fileExt(path || ""))), [src, path]);
|
|
1395
|
+
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", {
|
|
1396
|
+
key: i,
|
|
1397
|
+
className: "artifacts-code-line"
|
|
1398
|
+
}, /* @__PURE__ */ h("div", {
|
|
952
1399
|
className: "artifacts-code-gutter",
|
|
953
1400
|
"aria-hidden": "true"
|
|
954
|
-
},
|
|
1401
|
+
}, i + 1), /* @__PURE__ */ h("div", {
|
|
1402
|
+
className: "artifacts-code-pre",
|
|
1403
|
+
dangerouslySetInnerHTML: { __html: line || " " }
|
|
1404
|
+
})))));
|
|
955
1405
|
}
|
|
956
1406
|
let pdfjsPromise = null;
|
|
957
1407
|
function loadPdfjs() {
|
|
@@ -969,7 +1419,7 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
969
1419
|
s.async = true;
|
|
970
1420
|
s.onload = () => {
|
|
971
1421
|
try {
|
|
972
|
-
if (!window.pdfjsLib) throw new Error("
|
|
1422
|
+
if (!window.pdfjsLib) throw new Error(t("pdfLoadFailed"));
|
|
973
1423
|
window.pdfjsLib.GlobalWorkerOptions.workerSrc = "/flyout-sidebar/pdfjs/pdf.worker.min.js";
|
|
974
1424
|
resolve(window.pdfjsLib);
|
|
975
1425
|
} catch (e) {
|
|
@@ -978,7 +1428,7 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
978
1428
|
};
|
|
979
1429
|
s.onerror = () => {
|
|
980
1430
|
pdfjsPromise = null;
|
|
981
|
-
reject(
|
|
1431
|
+
reject(new Error(t("pdfLoadFailed")));
|
|
982
1432
|
};
|
|
983
1433
|
document.head.appendChild(s);
|
|
984
1434
|
});
|
|
@@ -1096,7 +1546,7 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1096
1546
|
const clampPage = (n) => Math.max(1, Math.min(pageCount || 1, n));
|
|
1097
1547
|
const goPage = (n) => setPageNo(clampPage(n));
|
|
1098
1548
|
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" }, "
|
|
1549
|
+
if (phase === "loading") return /* @__PURE__ */ h("div", { className: "artifacts-pdfview" }, /* @__PURE__ */ h("div", { className: "artifacts-hint" }, t("loadingPdf")));
|
|
1100
1550
|
if (phase === "error") return /* @__PURE__ */ h("embed", {
|
|
1101
1551
|
className: "artifacts-pdf",
|
|
1102
1552
|
src: "/flyout-sidebar/media?path=" + encodeURIComponent(path),
|
|
@@ -1107,25 +1557,25 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1107
1557
|
return /* @__PURE__ */ h("div", { className: "artifacts-pdfview" }, /* @__PURE__ */ h("div", { className: "artifacts-pdfview-bar" }, /* @__PURE__ */ h("button", {
|
|
1108
1558
|
type: "button",
|
|
1109
1559
|
className: "artifacts-pdfview-btn",
|
|
1110
|
-
title: "
|
|
1560
|
+
title: t("zoomOut"),
|
|
1111
1561
|
disabled,
|
|
1112
1562
|
onClick: () => zoomBy(.8)
|
|
1113
1563
|
}, "−"), /* @__PURE__ */ h("span", { className: "artifacts-pdfview-zoom" }, Math.round(zoom * 100) + "%"), /* @__PURE__ */ h("button", {
|
|
1114
1564
|
type: "button",
|
|
1115
1565
|
className: "artifacts-pdfview-btn",
|
|
1116
|
-
title: "
|
|
1566
|
+
title: t("zoomIn"),
|
|
1117
1567
|
disabled,
|
|
1118
1568
|
onClick: () => zoomBy(1.25)
|
|
1119
1569
|
}, "+"), /* @__PURE__ */ h("span", { className: "artifacts-pdfview-spacer" }), /* @__PURE__ */ h("button", {
|
|
1120
1570
|
type: "button",
|
|
1121
1571
|
className: "artifacts-pdfview-btn",
|
|
1122
|
-
title: "
|
|
1572
|
+
title: t("prevPage"),
|
|
1123
1573
|
disabled: disabled || pageNo <= 1,
|
|
1124
1574
|
onClick: () => goPage(pageNo - 1)
|
|
1125
1575
|
}, "‹"), /* @__PURE__ */ h("span", { className: "artifacts-pdfview-page" }, pageNo + " / " + pageCount), /* @__PURE__ */ h("button", {
|
|
1126
1576
|
type: "button",
|
|
1127
1577
|
className: "artifacts-pdfview-btn",
|
|
1128
|
-
title: "
|
|
1578
|
+
title: t("nextPage"),
|
|
1129
1579
|
disabled: disabled || pageNo >= pageCount,
|
|
1130
1580
|
onClick: () => goPage(pageNo + 1)
|
|
1131
1581
|
}, "›")), /* @__PURE__ */ h("div", {
|
|
@@ -1136,33 +1586,93 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1136
1586
|
className: "artifacts-pdfview-canvas"
|
|
1137
1587
|
})));
|
|
1138
1588
|
}
|
|
1139
|
-
/**
|
|
1589
|
+
/** 图片预览:滚轮缩放 + 拖拽平移 + 双击复位 */
|
|
1590
|
+
function ImageView({ path }) {
|
|
1591
|
+
const [scale, setScale] = React.useState(1);
|
|
1592
|
+
const [offset, setOffset] = React.useState({
|
|
1593
|
+
x: 0,
|
|
1594
|
+
y: 0
|
|
1595
|
+
});
|
|
1596
|
+
const [dragging, setDragging] = React.useState(false);
|
|
1597
|
+
const dragRef = React.useRef(null);
|
|
1598
|
+
const wrapRef = React.useRef(null);
|
|
1599
|
+
const src = "/flyout-sidebar/media?path=" + encodeURIComponent(path);
|
|
1600
|
+
React.useEffect(() => {
|
|
1601
|
+
const wrap = wrapRef.current;
|
|
1602
|
+
if (!wrap) return;
|
|
1603
|
+
const onWheel = (ev) => {
|
|
1604
|
+
ev.preventDefault();
|
|
1605
|
+
const factor = ev.deltaY < 0 ? 1.15 : 1 / 1.15;
|
|
1606
|
+
setScale((sc) => Math.max(.1, Math.min(12, sc * factor)));
|
|
1607
|
+
};
|
|
1608
|
+
wrap.addEventListener("wheel", onWheel, { passive: false });
|
|
1609
|
+
return () => wrap.removeEventListener("wheel", onWheel);
|
|
1610
|
+
}, []);
|
|
1611
|
+
React.useEffect(() => {
|
|
1612
|
+
if (!dragging) return;
|
|
1613
|
+
const onMove = (ev) => {
|
|
1614
|
+
const start = dragRef.current;
|
|
1615
|
+
if (!start) return;
|
|
1616
|
+
setOffset({
|
|
1617
|
+
x: start.ox + (ev.clientX - start.x),
|
|
1618
|
+
y: start.oy + (ev.clientY - start.y)
|
|
1619
|
+
});
|
|
1620
|
+
};
|
|
1621
|
+
const onUp = () => {
|
|
1622
|
+
setDragging(false);
|
|
1623
|
+
dragRef.current = null;
|
|
1624
|
+
};
|
|
1625
|
+
document.addEventListener("mousemove", onMove);
|
|
1626
|
+
document.addEventListener("mouseup", onUp);
|
|
1627
|
+
return () => {
|
|
1628
|
+
document.removeEventListener("mousemove", onMove);
|
|
1629
|
+
document.removeEventListener("mouseup", onUp);
|
|
1630
|
+
};
|
|
1631
|
+
}, [dragging]);
|
|
1632
|
+
return /* @__PURE__ */ h("div", {
|
|
1633
|
+
ref: wrapRef,
|
|
1634
|
+
className: "artifacts-imgview" + (dragging ? " is-dragging" : ""),
|
|
1635
|
+
onMouseDown: (ev) => {
|
|
1636
|
+
ev.preventDefault();
|
|
1637
|
+
dragRef.current = {
|
|
1638
|
+
x: ev.clientX,
|
|
1639
|
+
y: ev.clientY,
|
|
1640
|
+
ox: offset.x,
|
|
1641
|
+
oy: offset.y
|
|
1642
|
+
};
|
|
1643
|
+
setDragging(true);
|
|
1644
|
+
},
|
|
1645
|
+
onDoubleClick: () => {
|
|
1646
|
+
setScale(1);
|
|
1647
|
+
setOffset({
|
|
1648
|
+
x: 0,
|
|
1649
|
+
y: 0
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1652
|
+
}, /* @__PURE__ */ h("img", {
|
|
1653
|
+
className: "artifacts-img",
|
|
1654
|
+
draggable: false,
|
|
1655
|
+
src,
|
|
1656
|
+
alt: path,
|
|
1657
|
+
style: { transform: "translate(" + offset.x + "px," + offset.y + "px) scale(" + scale + ")" }
|
|
1658
|
+
}));
|
|
1659
|
+
}
|
|
1660
|
+
/** 统一 git diff 渲染:meta/hunk/+/- 行着色,等宽可滚动;行分类与独立弹出页共用 */
|
|
1140
1661
|
function GitDiffView({ diff }) {
|
|
1141
1662
|
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
|
-
}));
|
|
1663
|
+
if (!text) return /* @__PURE__ */ h("div", { className: "artifacts-hint" }, t("noChangesHead"));
|
|
1664
|
+
return /* @__PURE__ */ h("div", { className: "artifacts-gitdiff" }, text.replace(/\n$/, "").split("\n").map((line, i) => /* @__PURE__ */ h("div", {
|
|
1665
|
+
key: i,
|
|
1666
|
+
className: gitDiffLineClass(line)
|
|
1667
|
+
}, line)));
|
|
1154
1668
|
}
|
|
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 || "
|
|
1669
|
+
function renderPreview(p, codeWrap = false) {
|
|
1670
|
+
if (p.loading) return /* @__PURE__ */ h("div", { className: "artifacts-hint" }, t("loading"));
|
|
1671
|
+
if (p.ok === false) return /* @__PURE__ */ h("div", { className: "artifacts-error" }, p.error || t("readFailed"));
|
|
1158
1672
|
if (p.git) return /* @__PURE__ */ h("div", { className: "artifacts-preview-body" }, /* @__PURE__ */ h(GitDiffView, { diff: p.diff }));
|
|
1159
1673
|
const type = p.type || extType(p.path);
|
|
1160
1674
|
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
|
-
});
|
|
1675
|
+
if (type === "image") view = /* @__PURE__ */ h(ImageView, { path: p.path || "" });
|
|
1166
1676
|
else if (type === "html") view = /* @__PURE__ */ h("iframe", {
|
|
1167
1677
|
className: "artifacts-iframe",
|
|
1168
1678
|
sandbox: "allow-scripts",
|
|
@@ -1176,8 +1686,9 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1176
1686
|
});
|
|
1177
1687
|
else view = /* @__PURE__ */ h(Fragment, null, /* @__PURE__ */ h(CodeView, {
|
|
1178
1688
|
code: p.content,
|
|
1179
|
-
path: p.path
|
|
1180
|
-
|
|
1689
|
+
path: p.path,
|
|
1690
|
+
wrap: codeWrap
|
|
1691
|
+
}), p.truncated ? /* @__PURE__ */ h("div", { className: "artifacts-diff-label" }, t("truncated")) : null);
|
|
1181
1692
|
return /* @__PURE__ */ h("div", { className: "artifacts-preview-body" }, p.diff && typeof p.diff === "object" ? renderDiff(p.diff) : null, view);
|
|
1182
1693
|
}
|
|
1183
1694
|
//#endregion
|
|
@@ -1335,43 +1846,139 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1335
1846
|
strokeLinecap: "round",
|
|
1336
1847
|
fill: "none"
|
|
1337
1848
|
}));
|
|
1849
|
+
/** 搜索图标:放大镜(文件搜索开关) */
|
|
1850
|
+
const SearchIcon = ({ size }) => /* @__PURE__ */ h("svg", {
|
|
1851
|
+
width: size,
|
|
1852
|
+
height: size,
|
|
1853
|
+
viewBox: "0 0 16 16",
|
|
1854
|
+
fill: "none",
|
|
1855
|
+
"aria-hidden": "true"
|
|
1856
|
+
}, /* @__PURE__ */ h("circle", {
|
|
1857
|
+
cx: "7",
|
|
1858
|
+
cy: "7",
|
|
1859
|
+
r: "4.4",
|
|
1860
|
+
stroke: "currentColor",
|
|
1861
|
+
strokeWidth: 1.4
|
|
1862
|
+
}), /* @__PURE__ */ h("path", {
|
|
1863
|
+
d: "m10.4 10.4 3.1 3.1",
|
|
1864
|
+
stroke: "currentColor",
|
|
1865
|
+
strokeWidth: 1.4,
|
|
1866
|
+
strokeLinecap: "round"
|
|
1867
|
+
}));
|
|
1338
1868
|
//#endregion
|
|
1339
1869
|
//#region src/client/components.tsx
|
|
1340
1870
|
/**
|
|
1341
1871
|
* Client 侧:UI 组件 —— 文件树、多标签预览侧边面板、角落触发按钮、设置区。
|
|
1342
1872
|
*/
|
|
1343
|
-
function FileTree({ onOpen, selectedPath, refreshToken }) {
|
|
1873
|
+
function FileTree({ onOpen, selectedPath, refreshToken, searchOpen = false, onCloseSearch }) {
|
|
1344
1874
|
const [root, setRoot] = React.useState(null);
|
|
1345
1875
|
const [children, setChildren] = React.useState({});
|
|
1346
1876
|
const [expanded, setExpanded] = React.useState({});
|
|
1347
1877
|
const [copiedPath, setCopiedPath] = React.useState(null);
|
|
1348
1878
|
const [copiedLabel, setCopiedLabel] = React.useState("");
|
|
1879
|
+
const [query, setQuery] = React.useState("");
|
|
1880
|
+
const [search, setSearch] = React.useState(null);
|
|
1349
1881
|
const copyTimer = React.useRef(null);
|
|
1350
1882
|
const rootTimer = React.useRef(null);
|
|
1351
1883
|
const sessionId = useSessionId();
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1884
|
+
React.useEffect(() => {
|
|
1885
|
+
if (!searchOpen) setQuery("");
|
|
1886
|
+
}, [searchOpen]);
|
|
1887
|
+
React.useEffect(() => {
|
|
1888
|
+
const q = query.trim();
|
|
1889
|
+
if (!q) {
|
|
1890
|
+
setSearch(null);
|
|
1891
|
+
return;
|
|
1892
|
+
}
|
|
1893
|
+
let alive = true;
|
|
1894
|
+
setSearch({ loading: true });
|
|
1895
|
+
const timer = setTimeout(() => {
|
|
1896
|
+
host.searchFiles(q, currentSessionId()).then((res) => {
|
|
1897
|
+
if (alive) setSearch(res && res.ok ? { entries: res.entries || [] } : { error: res && res.error || t("searchFailed") });
|
|
1898
|
+
}).catch(() => {
|
|
1899
|
+
if (alive) setSearch({ error: t("searchFailed") });
|
|
1900
|
+
});
|
|
1901
|
+
}, 250);
|
|
1902
|
+
return () => {
|
|
1903
|
+
alive = false;
|
|
1904
|
+
clearTimeout(timer);
|
|
1905
|
+
};
|
|
1906
|
+
}, [
|
|
1907
|
+
query,
|
|
1908
|
+
sessionId,
|
|
1909
|
+
refreshToken
|
|
1910
|
+
]);
|
|
1911
|
+
const fetchDir = (path) => {
|
|
1912
|
+
const sid = currentSessionId();
|
|
1913
|
+
setChildren((prev) => ({
|
|
1914
|
+
...prev,
|
|
1915
|
+
[path]: { loading: true }
|
|
1916
|
+
}));
|
|
1917
|
+
host.listDir(path, sid).then((res) => {
|
|
1918
|
+
if (currentSessionId() !== sid) return;
|
|
1919
|
+
setChildren((prev) => ({
|
|
1920
|
+
...prev,
|
|
1921
|
+
[path]: res && res.ok ? { entries: res.entries || [] } : { error: res && res.error || t("readFailed") }
|
|
1922
|
+
}));
|
|
1923
|
+
}).catch(() => {
|
|
1924
|
+
if (currentSessionId() !== sid) return;
|
|
1925
|
+
setChildren((prev) => ({
|
|
1926
|
+
...prev,
|
|
1927
|
+
[path]: { error: t("readFailed") }
|
|
1928
|
+
}));
|
|
1929
|
+
});
|
|
1930
|
+
};
|
|
1931
|
+
const [rootError, setRootError] = React.useState(null);
|
|
1932
|
+
const loadRoot = (keepState = false) => {
|
|
1933
|
+
if (!keepState) {
|
|
1934
|
+
setChildren({});
|
|
1935
|
+
setExpanded({});
|
|
1936
|
+
setRoot(null);
|
|
1937
|
+
}
|
|
1938
|
+
setRootError(null);
|
|
1939
|
+
if (keepState) {
|
|
1940
|
+
for (const p of Object.keys(expanded)) if (expanded[p]) fetchDir(p);
|
|
1941
|
+
}
|
|
1356
1942
|
if (rootTimer.current) clearTimeout(rootTimer.current);
|
|
1357
|
-
const
|
|
1943
|
+
const delays = [
|
|
1944
|
+
400,
|
|
1945
|
+
800,
|
|
1946
|
+
1600,
|
|
1947
|
+
3200,
|
|
1948
|
+
3200
|
|
1949
|
+
];
|
|
1950
|
+
const sid = currentSessionId();
|
|
1951
|
+
const attempt = (i) => {
|
|
1358
1952
|
host.listDir("", currentSessionId()).then((res) => {
|
|
1953
|
+
if (currentSessionId() !== sid) return;
|
|
1359
1954
|
if (res && res.ok) setRoot({
|
|
1360
1955
|
path: res.path || "",
|
|
1361
1956
|
entries: res.entries || []
|
|
1362
1957
|
});
|
|
1363
|
-
else if (
|
|
1958
|
+
else if (i < delays.length) rootTimer.current = setTimeout(() => attempt(i + 1), delays[i]);
|
|
1959
|
+
else if (!keepState) setRootError(res && res.error || t("treeLoadFailed"));
|
|
1364
1960
|
}).catch(() => {
|
|
1365
|
-
if (
|
|
1961
|
+
if (currentSessionId() !== sid) return;
|
|
1962
|
+
if (i < delays.length) rootTimer.current = setTimeout(() => attempt(i + 1), delays[i]);
|
|
1963
|
+
else if (!keepState) setRootError(t("treeLoadFailed"));
|
|
1366
1964
|
});
|
|
1367
1965
|
};
|
|
1368
|
-
attempt(
|
|
1966
|
+
attempt(0);
|
|
1369
1967
|
};
|
|
1968
|
+
const firstTreeRender = React.useRef(true);
|
|
1370
1969
|
React.useEffect(() => {
|
|
1371
1970
|
loadRoot();
|
|
1372
|
-
}, [sessionId
|
|
1971
|
+
}, [sessionId]);
|
|
1972
|
+
React.useEffect(() => {
|
|
1973
|
+
if (firstTreeRender.current) {
|
|
1974
|
+
firstTreeRender.current = false;
|
|
1975
|
+
return;
|
|
1976
|
+
}
|
|
1977
|
+
loadRoot(root != null);
|
|
1978
|
+
}, [refreshToken]);
|
|
1373
1979
|
React.useEffect(() => () => {
|
|
1374
1980
|
if (rootTimer.current) clearTimeout(rootTimer.current);
|
|
1981
|
+
if (copyTimer.current) clearTimeout(copyTimer.current);
|
|
1375
1982
|
}, []);
|
|
1376
1983
|
const toggle = (path) => {
|
|
1377
1984
|
const nextExpanded = {
|
|
@@ -1379,27 +1986,11 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1379
1986
|
[path]: !expanded[path]
|
|
1380
1987
|
};
|
|
1381
1988
|
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
|
-
}
|
|
1989
|
+
if (nextExpanded[path] && !children[path]) fetchDir(path);
|
|
1399
1990
|
};
|
|
1400
1991
|
const copyRef = (path) => {
|
|
1401
1992
|
const text = "@" + path;
|
|
1402
|
-
let label = "
|
|
1993
|
+
let label = t("copied");
|
|
1403
1994
|
const done = () => {
|
|
1404
1995
|
setCopiedPath(path);
|
|
1405
1996
|
setCopiedLabel(label);
|
|
@@ -1410,7 +2001,7 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1410
2001
|
}, 1600);
|
|
1411
2002
|
};
|
|
1412
2003
|
if (quoteToComposer(path)) {
|
|
1413
|
-
label = "
|
|
2004
|
+
label = t("insertedInput");
|
|
1414
2005
|
done();
|
|
1415
2006
|
return;
|
|
1416
2007
|
}
|
|
@@ -1423,15 +2014,15 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1423
2014
|
done();
|
|
1424
2015
|
}
|
|
1425
2016
|
};
|
|
1426
|
-
const rowActions = (entry) => copiedPath === entry.path ? /* @__PURE__ */ h("span", { className: "artifacts-tree-copied" }, copiedLabel || "
|
|
2017
|
+
const rowActions = (entry) => copiedPath === entry.path ? /* @__PURE__ */ h("span", { className: "artifacts-tree-copied" }, copiedLabel || t("copied")) : /* @__PURE__ */ h("button", {
|
|
1427
2018
|
type: "button",
|
|
1428
2019
|
className: "artifacts-tree-ref",
|
|
1429
|
-
title: "
|
|
2020
|
+
title: t("refTitle"),
|
|
1430
2021
|
onClick: (e) => {
|
|
1431
2022
|
e.stopPropagation();
|
|
1432
2023
|
copyRef(entry.path);
|
|
1433
2024
|
}
|
|
1434
|
-
}, "
|
|
2025
|
+
}, t("refBtn"));
|
|
1435
2026
|
const renderNode = (entry, depth, flashDelay) => {
|
|
1436
2027
|
const pad = { paddingLeft: 6 + depth * 20 };
|
|
1437
2028
|
const isSelected = selectedPath === entry.path;
|
|
@@ -1459,7 +2050,7 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1459
2050
|
}, 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
2051
|
className: "artifacts-tree-row artifacts-tree-loading",
|
|
1461
2052
|
style: { paddingLeft: 6 + (depth + 1) * 20 + 20 }
|
|
1462
|
-
}, "
|
|
2053
|
+
}, t("loading")) : node && node.error ? /* @__PURE__ */ h("div", {
|
|
1463
2054
|
className: "artifacts-tree-row artifacts-tree-error",
|
|
1464
2055
|
style: { paddingLeft: 6 + (depth + 1) * 20 + 20 }
|
|
1465
2056
|
}, node.error) : node && node.entries ? node.entries.map((c) => renderNode(c, depth + 1)) : null : null);
|
|
@@ -1484,15 +2075,64 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1484
2075
|
title: entry.path
|
|
1485
2076
|
}, /* @__PURE__ */ h(FileCodeIcon, { size: 14 }), /* @__PURE__ */ h("span", { className: "artifacts-tree-name" }, entry.name), rowActions(entry));
|
|
1486
2077
|
};
|
|
1487
|
-
|
|
2078
|
+
const renderSearchRow = (path, idx) => {
|
|
2079
|
+
const entry = {
|
|
2080
|
+
name: basename(path),
|
|
2081
|
+
path,
|
|
2082
|
+
isDir: false,
|
|
2083
|
+
hidden: false
|
|
2084
|
+
};
|
|
2085
|
+
const slash = path.lastIndexOf("/");
|
|
2086
|
+
const dir = slash >= 0 ? path.slice(0, slash) : "";
|
|
2087
|
+
return /* @__PURE__ */ h("div", {
|
|
2088
|
+
key: path,
|
|
2089
|
+
role: "button",
|
|
2090
|
+
tabIndex: 0,
|
|
2091
|
+
className: "artifacts-tree-row artifacts-flash-in" + (selectedPath === path ? " is-selected" : ""),
|
|
2092
|
+
style: { animationDelay: Math.min(idx, 12) * 30 + "ms" },
|
|
2093
|
+
onClick: () => {
|
|
2094
|
+
if (onOpen) onOpen(path);
|
|
2095
|
+
},
|
|
2096
|
+
onKeyDown: (ev) => {
|
|
2097
|
+
if (ev.key === "Enter" || ev.key === " ") {
|
|
2098
|
+
ev.preventDefault();
|
|
2099
|
+
if (onOpen) onOpen(path);
|
|
2100
|
+
}
|
|
2101
|
+
},
|
|
2102
|
+
title: path
|
|
2103
|
+
}, /* @__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));
|
|
2104
|
+
};
|
|
2105
|
+
return /* @__PURE__ */ h("div", { className: "artifacts-tree" }, searchOpen ? /* @__PURE__ */ h("div", { className: "artifacts-searchbar" }, /* @__PURE__ */ h("input", {
|
|
2106
|
+
type: "text",
|
|
2107
|
+
className: "artifacts-search-input",
|
|
2108
|
+
placeholder: t("searchPlaceholder"),
|
|
2109
|
+
value: query,
|
|
2110
|
+
autoFocus: true,
|
|
2111
|
+
onChange: (e) => setQuery(e.currentTarget.value),
|
|
2112
|
+
onKeyDown: (ev) => {
|
|
2113
|
+
if (ev.key === "Escape") {
|
|
2114
|
+
if (onCloseSearch) onCloseSearch();
|
|
2115
|
+
else setQuery("");
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
})) : null, /* @__PURE__ */ h("div", {
|
|
2119
|
+
className: "artifacts-tree-body",
|
|
2120
|
+
key: refreshToken
|
|
2121
|
+
}, 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", {
|
|
2122
|
+
type: "button",
|
|
2123
|
+
className: "artifacts-retry",
|
|
2124
|
+
onClick: () => loadRoot(false)
|
|
2125
|
+
}, 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
2126
|
}
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
2127
|
+
const TABS_KEY = "dsh-flyout-sidebar:tabs";
|
|
2128
|
+
/**
|
|
2129
|
+
* 多标签预览状态('p:' 前缀 = 内容预览,'g:' 前缀 = git diff):打开/关闭/
|
|
2130
|
+
* 会话切换清空/持久化与恢复集中在此 hook,面板组件只消费结果。
|
|
2131
|
+
*/
|
|
2132
|
+
function usePreviewTabs(sessionId) {
|
|
1492
2133
|
const [tabs, setTabs] = React.useState([]);
|
|
1493
2134
|
const [activeKey, setActiveKey] = React.useState(null);
|
|
1494
2135
|
const [previewHidden, setPreviewHidden] = React.useState(false);
|
|
1495
|
-
const sessionId = useSessionId();
|
|
1496
2136
|
const firstSession = React.useRef(true);
|
|
1497
2137
|
React.useEffect(() => {
|
|
1498
2138
|
if (firstSession.current) {
|
|
@@ -1502,18 +2142,219 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1502
2142
|
setTabs([]);
|
|
1503
2143
|
setActiveKey(null);
|
|
1504
2144
|
setPreviewHidden(false);
|
|
2145
|
+
try {
|
|
2146
|
+
sessionStorage.removeItem(TABS_KEY);
|
|
2147
|
+
} catch {}
|
|
2148
|
+
}, [sessionId]);
|
|
2149
|
+
const restoredSid = React.useRef(null);
|
|
2150
|
+
React.useEffect(() => {
|
|
2151
|
+
if (restoredSid.current !== sessionId) return;
|
|
2152
|
+
try {
|
|
2153
|
+
if (!tabs.length) sessionStorage.removeItem(TABS_KEY);
|
|
2154
|
+
else sessionStorage.setItem(TABS_KEY, JSON.stringify({
|
|
2155
|
+
sid: sessionId,
|
|
2156
|
+
tabs: tabs.map((tb) => ({
|
|
2157
|
+
key: tb.key,
|
|
2158
|
+
path: tb.path,
|
|
2159
|
+
git: tb.git
|
|
2160
|
+
})),
|
|
2161
|
+
activeKey
|
|
2162
|
+
}));
|
|
2163
|
+
} catch {}
|
|
2164
|
+
}, [
|
|
2165
|
+
tabs,
|
|
2166
|
+
activeKey,
|
|
2167
|
+
sessionId
|
|
2168
|
+
]);
|
|
2169
|
+
const patchTab = (key, patch) => setTabs((prev) => prev.map((tb) => tb.key === key ? {
|
|
2170
|
+
...tb,
|
|
2171
|
+
...patch
|
|
2172
|
+
} : tb));
|
|
2173
|
+
const reqTokens = React.useRef({});
|
|
2174
|
+
const openTab = (key, path, git, initial) => {
|
|
2175
|
+
setPreviewHidden(false);
|
|
2176
|
+
reqTokens.current[key] = (reqTokens.current[key] || 0) + 1;
|
|
2177
|
+
setTabs((prev) => {
|
|
2178
|
+
const i = prev.findIndex((tb) => tb.key === key);
|
|
2179
|
+
if (i >= 0) {
|
|
2180
|
+
const next = prev.slice();
|
|
2181
|
+
const cur = next[i];
|
|
2182
|
+
if (cur) next[i] = {
|
|
2183
|
+
...cur,
|
|
2184
|
+
...initial,
|
|
2185
|
+
ok: void 0,
|
|
2186
|
+
error: void 0
|
|
2187
|
+
};
|
|
2188
|
+
return next;
|
|
2189
|
+
}
|
|
2190
|
+
return prev.concat([{
|
|
2191
|
+
key,
|
|
2192
|
+
path,
|
|
2193
|
+
git,
|
|
2194
|
+
...initial
|
|
2195
|
+
}]);
|
|
2196
|
+
});
|
|
2197
|
+
setActiveKey(key);
|
|
2198
|
+
};
|
|
2199
|
+
const closeTab = (key) => {
|
|
2200
|
+
setTabs((prev) => {
|
|
2201
|
+
prev.findIndex((tb) => tb.key === key);
|
|
2202
|
+
return prev.filter((tb) => tb.key !== key);
|
|
2203
|
+
});
|
|
2204
|
+
setActiveKey((cur) => {
|
|
2205
|
+
if (cur !== key) return cur;
|
|
2206
|
+
const remaining = tabsRef.current.filter((tb) => tb.key !== key);
|
|
2207
|
+
if (!remaining.length) return null;
|
|
2208
|
+
const closedIdx = tabsRef.current.findIndex((tb) => tb.key === key);
|
|
2209
|
+
return remaining[Math.min(Math.max(closedIdx, 0), remaining.length - 1)]?.key ?? null;
|
|
2210
|
+
});
|
|
2211
|
+
};
|
|
2212
|
+
const tabsRef = React.useRef([]);
|
|
2213
|
+
React.useEffect(() => {
|
|
2214
|
+
tabsRef.current = tabs;
|
|
2215
|
+
}, [tabs]);
|
|
2216
|
+
const openFile = (path) => {
|
|
2217
|
+
const key = "p:" + path;
|
|
2218
|
+
const type = extType(path);
|
|
2219
|
+
const initial = {
|
|
2220
|
+
loading: false,
|
|
2221
|
+
type,
|
|
2222
|
+
diff: null
|
|
2223
|
+
};
|
|
2224
|
+
if (type !== "image" && type !== "pdf") initial.loading = true;
|
|
2225
|
+
openTab(key, path, false, initial);
|
|
2226
|
+
if (type === "image" || type === "pdf") return;
|
|
2227
|
+
const myReq = reqTokens.current[key];
|
|
2228
|
+
host.readArtifact(path, sessionId).then((res) => {
|
|
2229
|
+
if (reqTokens.current[key] !== myReq) return;
|
|
2230
|
+
patchTab(key, {
|
|
2231
|
+
loading: false,
|
|
2232
|
+
...res
|
|
2233
|
+
});
|
|
2234
|
+
}).catch((e) => {
|
|
2235
|
+
if (reqTokens.current[key] !== myReq) return;
|
|
2236
|
+
patchTab(key, {
|
|
2237
|
+
loading: false,
|
|
2238
|
+
ok: false,
|
|
2239
|
+
error: String(e instanceof Error && e.message ? e.message : e)
|
|
2240
|
+
});
|
|
2241
|
+
});
|
|
2242
|
+
};
|
|
2243
|
+
const openGitDiff = (path) => {
|
|
2244
|
+
const key = "g:" + path;
|
|
2245
|
+
openTab(key, path, true, { loading: true });
|
|
2246
|
+
const myReq = reqTokens.current[key];
|
|
2247
|
+
host.gitDiff(path, sessionId).then((res) => {
|
|
2248
|
+
if (reqTokens.current[key] !== myReq) return;
|
|
2249
|
+
patchTab(key, {
|
|
2250
|
+
loading: false,
|
|
2251
|
+
...res
|
|
2252
|
+
});
|
|
2253
|
+
}).catch((e) => {
|
|
2254
|
+
if (reqTokens.current[key] !== myReq) return;
|
|
2255
|
+
patchTab(key, {
|
|
2256
|
+
loading: false,
|
|
2257
|
+
ok: false,
|
|
2258
|
+
error: String(e instanceof Error && e.message ? e.message : e)
|
|
2259
|
+
});
|
|
2260
|
+
});
|
|
2261
|
+
};
|
|
2262
|
+
React.useEffect(() => {
|
|
2263
|
+
if (restoredSid.current === sessionId) return;
|
|
2264
|
+
restoredSid.current = sessionId;
|
|
2265
|
+
if (sessionId == null) return;
|
|
2266
|
+
try {
|
|
2267
|
+
const raw = sessionStorage.getItem(TABS_KEY);
|
|
2268
|
+
if (!raw) return;
|
|
2269
|
+
const saved = JSON.parse(raw);
|
|
2270
|
+
const sid = saved.sid;
|
|
2271
|
+
const list = saved.tabs;
|
|
2272
|
+
if (sid !== sessionId || !Array.isArray(list)) return;
|
|
2273
|
+
for (const st of list) {
|
|
2274
|
+
if (typeof st?.key !== "string" || typeof st?.path !== "string") continue;
|
|
2275
|
+
if (st.git) openGitDiff(st.path);
|
|
2276
|
+
else openFile(st.path);
|
|
2277
|
+
}
|
|
2278
|
+
const savedActive = saved.activeKey;
|
|
2279
|
+
if (typeof savedActive === "string") setActiveKey(savedActive);
|
|
2280
|
+
} catch {}
|
|
1505
2281
|
}, [sessionId]);
|
|
2282
|
+
return {
|
|
2283
|
+
tabs,
|
|
2284
|
+
activeKey,
|
|
2285
|
+
setActiveKey,
|
|
2286
|
+
previewHidden,
|
|
2287
|
+
setPreviewHidden,
|
|
2288
|
+
openFile,
|
|
2289
|
+
openGitDiff,
|
|
2290
|
+
closeTab
|
|
2291
|
+
};
|
|
2292
|
+
}
|
|
2293
|
+
/** git 变更列表(文件面板的「变更」视图) */
|
|
2294
|
+
function GitChangesList({ files, error, activeDiffPath, flashKey, onOpen, onCopyPath, onQuote }) {
|
|
2295
|
+
const rows = [];
|
|
2296
|
+
if (error) rows.push(/* @__PURE__ */ h("div", {
|
|
2297
|
+
key: "err",
|
|
2298
|
+
className: "artifacts-tree-error artifacts-git-error"
|
|
2299
|
+
}, error));
|
|
2300
|
+
else if (files == null) rows.push(/* @__PURE__ */ h("div", {
|
|
2301
|
+
key: "load",
|
|
2302
|
+
className: "artifacts-empty"
|
|
2303
|
+
}, t("loadingChanges")));
|
|
2304
|
+
else if (!files.length) rows.push(/* @__PURE__ */ h("div", {
|
|
2305
|
+
key: "empty",
|
|
2306
|
+
className: "artifacts-empty"
|
|
2307
|
+
}, t("noChanges")));
|
|
2308
|
+
(files || []).forEach((e, idx) => {
|
|
2309
|
+
const label = gitLabel(e);
|
|
2310
|
+
const isActive = activeDiffPath === e.path;
|
|
2311
|
+
const flashPrefix = flashKey > 0 ? flashKey + ":" : "";
|
|
2312
|
+
rows.push(/* @__PURE__ */ h("div", {
|
|
2313
|
+
key: flashPrefix + e.path,
|
|
2314
|
+
className: "artifacts-item" + (isActive ? " is-active" : "") + (flashPrefix ? " artifacts-flash-in" : ""),
|
|
2315
|
+
style: flashPrefix ? { animationDelay: Math.min(idx, 12) * 45 + "ms" } : void 0
|
|
2316
|
+
}, /* @__PURE__ */ h("button", {
|
|
2317
|
+
type: "button",
|
|
2318
|
+
className: "artifacts-item-main",
|
|
2319
|
+
title: gitTitle(e),
|
|
2320
|
+
onClick: () => onOpen(e.path)
|
|
2321
|
+
}, /* @__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", {
|
|
2322
|
+
type: "button",
|
|
2323
|
+
className: "artifacts-minibtn",
|
|
2324
|
+
title: t("copyPath"),
|
|
2325
|
+
onClick: () => onCopyPath(e.path)
|
|
2326
|
+
}, "⧉"), /* @__PURE__ */ h("button", {
|
|
2327
|
+
type: "button",
|
|
2328
|
+
className: "artifacts-minibtn",
|
|
2329
|
+
title: t("refInput"),
|
|
2330
|
+
onClick: () => onQuote(e.path)
|
|
2331
|
+
}, "@"))));
|
|
2332
|
+
});
|
|
2333
|
+
return /* @__PURE__ */ h(Fragment, null, rows);
|
|
2334
|
+
}
|
|
2335
|
+
function ArtifactsPanel() {
|
|
2336
|
+
const open = useOpen();
|
|
2337
|
+
const settings = useSettings();
|
|
2338
|
+
useLang();
|
|
2339
|
+
const sessionId = useSessionId();
|
|
2340
|
+
const { tabs, activeKey, setActiveKey, previewHidden, setPreviewHidden, openFile, openGitDiff, closeTab } = usePreviewTabs(sessionId);
|
|
2341
|
+
const activeTab = tabs.find((t) => t.key === activeKey) || null;
|
|
1506
2342
|
const [notice, setNotice] = React.useState("");
|
|
2343
|
+
const [winW, setWinW] = React.useState(() => typeof window !== "undefined" ? window.innerWidth : 1400);
|
|
1507
2344
|
const [gitFiles, setGitFiles] = React.useState(null);
|
|
1508
2345
|
const [gitError, setGitError] = React.useState(null);
|
|
1509
|
-
const [
|
|
2346
|
+
const [panelFrac, setPanelFrac] = React.useState(null);
|
|
1510
2347
|
const [resizing, setResizing] = React.useState(false);
|
|
1511
2348
|
const [activeView, setActiveView] = React.useState(() => settings.showFileTree ? "tree" : "git");
|
|
1512
2349
|
const [treeRefresh, setTreeRefresh] = React.useState(0);
|
|
2350
|
+
const [searchOpen, setSearchOpen] = React.useState(false);
|
|
1513
2351
|
const [gitRefresh, setGitRefresh] = React.useState(0);
|
|
1514
2352
|
const [gitRefreshing, setGitRefreshing] = React.useState(false);
|
|
1515
2353
|
const [gitFlash, setGitFlash] = React.useState(0);
|
|
1516
2354
|
const noticeTimer = React.useRef(null);
|
|
2355
|
+
React.useEffect(() => () => {
|
|
2356
|
+
if (noticeTimer.current) clearTimeout(noticeTimer.current);
|
|
2357
|
+
}, []);
|
|
1517
2358
|
const gitForceRef = React.useRef(false);
|
|
1518
2359
|
React.useEffect(() => {
|
|
1519
2360
|
if (!open || activeView !== "git") return;
|
|
@@ -1532,7 +2373,7 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1532
2373
|
setGitError(null);
|
|
1533
2374
|
} else {
|
|
1534
2375
|
setGitFiles([]);
|
|
1535
|
-
setGitError(res && res.error || "
|
|
2376
|
+
setGitError(res && res.error || t("gitStatusFailed"));
|
|
1536
2377
|
}
|
|
1537
2378
|
}).catch((e) => {
|
|
1538
2379
|
if (force) setGitRefreshing(false);
|
|
@@ -1595,8 +2436,18 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1595
2436
|
if (document.body) obs.observe(document.body, opts);
|
|
1596
2437
|
return () => obs.disconnect();
|
|
1597
2438
|
}, []);
|
|
1598
|
-
|
|
1599
|
-
|
|
2439
|
+
React.useEffect(() => {
|
|
2440
|
+
const onResize = () => setWinW(window.innerWidth);
|
|
2441
|
+
window.addEventListener("resize", onResize);
|
|
2442
|
+
return () => window.removeEventListener("resize", onResize);
|
|
2443
|
+
}, []);
|
|
2444
|
+
const rightOffset = (() => {
|
|
2445
|
+
const n = parseFloat(document.documentElement.style.getPropertyValue("--dsh-sidebar-width"));
|
|
2446
|
+
return Number.isFinite(n) ? n : 0;
|
|
2447
|
+
})();
|
|
2448
|
+
const avail = Math.max(120, winW - rightOffset);
|
|
2449
|
+
const minWidthPx = Math.max(80, Math.round(winW * (settings.minPanelWidth || 0) / 100));
|
|
2450
|
+
const widthPx = panelFrac != null ? Math.max(minWidthPx, Math.round(panelFrac * avail)) : minWidthPx;
|
|
1600
2451
|
React.useEffect(() => {
|
|
1601
2452
|
const root = document.documentElement;
|
|
1602
2453
|
root.style.setProperty("--dsh-flyout-sidebar-width", open ? widthPx + "px" : "0px");
|
|
@@ -1611,26 +2462,36 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1611
2462
|
document.body.removeAttribute("data-dsh-flyout-dragging");
|
|
1612
2463
|
};
|
|
1613
2464
|
}, [resizing]);
|
|
2465
|
+
const resizeHandlers = React.useRef(null);
|
|
2466
|
+
React.useEffect(() => () => {
|
|
2467
|
+
const h = resizeHandlers.current;
|
|
2468
|
+
if (h) {
|
|
2469
|
+
document.removeEventListener("mousemove", h.onMove);
|
|
2470
|
+
document.removeEventListener("mouseup", h.onUp);
|
|
2471
|
+
}
|
|
2472
|
+
}, []);
|
|
1614
2473
|
const { visible, slidOut } = useSlide();
|
|
1615
2474
|
if (!visible) return null;
|
|
1616
2475
|
const flyoutHref = "/flyout-sidebar" + (sessionId ? "?sessionId=" + encodeURIComponent(sessionId) : "");
|
|
1617
2476
|
const startResize = (e) => {
|
|
1618
2477
|
e.preventDefault();
|
|
1619
2478
|
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
|
-
})();
|
|
2479
|
+
const availAtStart = avail;
|
|
1625
2480
|
const onMove = (ev) => {
|
|
1626
2481
|
const w = window.innerWidth - ev.clientX - rightOffset;
|
|
1627
|
-
|
|
2482
|
+
const frac = Math.max(minWidthPx / availAtStart, Math.min(w / availAtStart, (availAtStart - 24) / availAtStart));
|
|
2483
|
+
setPanelFrac(frac);
|
|
1628
2484
|
};
|
|
1629
2485
|
const onUp = () => {
|
|
1630
2486
|
setResizing(false);
|
|
2487
|
+
resizeHandlers.current = null;
|
|
1631
2488
|
document.removeEventListener("mousemove", onMove);
|
|
1632
2489
|
document.removeEventListener("mouseup", onUp);
|
|
1633
2490
|
};
|
|
2491
|
+
resizeHandlers.current = {
|
|
2492
|
+
onMove,
|
|
2493
|
+
onUp
|
|
2494
|
+
};
|
|
1634
2495
|
document.addEventListener("mousemove", onMove);
|
|
1635
2496
|
document.addEventListener("mouseup", onUp);
|
|
1636
2497
|
};
|
|
@@ -1652,162 +2513,53 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1652
2513
|
};
|
|
1653
2514
|
const quotePath = (path) => {
|
|
1654
2515
|
if (quoteToComposer(path)) {
|
|
1655
|
-
flash("
|
|
2516
|
+
flash(t("insertedInput"));
|
|
1656
2517
|
return;
|
|
1657
2518
|
}
|
|
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);
|
|
2519
|
+
copyText("@" + path, t("copiedRef"));
|
|
1691
2520
|
};
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
const
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
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
|
-
});
|
|
1732
|
-
};
|
|
1733
|
-
const gitLabel = (e) => {
|
|
1734
|
-
if (e.x === "?" || e.y === "?") return "U";
|
|
1735
|
-
return (e.y !== " " ? e.y : e.x) || "M";
|
|
1736
|
-
};
|
|
1737
|
-
const gitTitle = (e) => {
|
|
1738
|
-
const label = gitLabel(e);
|
|
1739
|
-
const map = {
|
|
1740
|
-
U: "未跟踪",
|
|
1741
|
-
A: "新增",
|
|
1742
|
-
M: "修改",
|
|
1743
|
-
D: "删除",
|
|
1744
|
-
R: "重命名",
|
|
1745
|
-
C: "复制"
|
|
2521
|
+
React.useEffect(() => {
|
|
2522
|
+
if (!open) return;
|
|
2523
|
+
const onKey = (ev) => {
|
|
2524
|
+
if (ev.key !== "Escape") return;
|
|
2525
|
+
const target = ev.target;
|
|
2526
|
+
if (target && (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable)) return;
|
|
2527
|
+
if (tabs.length && !previewHidden) {
|
|
2528
|
+
if (activeKey) closeTab(activeKey);
|
|
2529
|
+
else setPreviewHidden(true);
|
|
2530
|
+
} else store.setOpen(false);
|
|
1746
2531
|
};
|
|
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
|
-
});
|
|
2532
|
+
document.addEventListener("keydown", onKey);
|
|
2533
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
2534
|
+
}, [
|
|
2535
|
+
open,
|
|
2536
|
+
tabs,
|
|
2537
|
+
previewHidden,
|
|
2538
|
+
activeKey
|
|
2539
|
+
]);
|
|
1788
2540
|
const previewOverlay = tabs.length && !previewHidden ? /* @__PURE__ */ h("div", {
|
|
1789
2541
|
className: "artifacts-preview-overlay" + (slidOut ? " artifacts-slid-out" : ""),
|
|
1790
2542
|
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(
|
|
2543
|
+
"aria-label": t("previewRegion")
|
|
2544
|
+
}, /* @__PURE__ */ h("div", { className: "artifacts-preview-overlay-tabs" }, /* @__PURE__ */ h("div", { className: "artifacts-ptabs-scroll" }, tabs.map((tab) => /* @__PURE__ */ h("div", {
|
|
2545
|
+
key: tab.key,
|
|
2546
|
+
className: "artifacts-ptab" + (tab.key === activeKey ? " is-active" : ""),
|
|
2547
|
+
title: (tab.git ? t("diffTabPrefix") : "") + (tab.path || ""),
|
|
2548
|
+
onClick: () => setActiveKey(tab.key)
|
|
2549
|
+
}, /* @__PURE__ */ h("span", { className: "artifacts-ptab-name" }, basename(tab.path || "")), /* @__PURE__ */ h("button", {
|
|
1798
2550
|
type: "button",
|
|
1799
2551
|
className: "artifacts-ptab-close",
|
|
1800
|
-
title: "
|
|
2552
|
+
title: t("closeTab"),
|
|
1801
2553
|
onClick: (e) => {
|
|
1802
2554
|
e.stopPropagation();
|
|
1803
|
-
closeTab(
|
|
2555
|
+
closeTab(tab.key);
|
|
1804
2556
|
}
|
|
1805
2557
|
}, "×")))), /* @__PURE__ */ h("button", {
|
|
1806
2558
|
type: "button",
|
|
1807
2559
|
className: "artifacts-preview-hide",
|
|
1808
|
-
title: "
|
|
2560
|
+
title: t("hidePreview"),
|
|
1809
2561
|
onClick: () => setPreviewHidden(true)
|
|
1810
|
-
}, /* @__PURE__ */ h(PanelCollapseIcon, { size: 16 }))), activeTab ? renderPreview(activeTab) : null) : null;
|
|
2562
|
+
}, /* @__PURE__ */ h(PanelCollapseIcon, { size: 16 }))), activeTab ? renderPreview(activeTab, settings.codeWrap) : null) : null;
|
|
1811
2563
|
return /* @__PURE__ */ h(Fragment, null, previewOverlay, /* @__PURE__ */ h("div", {
|
|
1812
2564
|
className: "artifacts-panel" + (slidOut ? " artifacts-slid-out" : "") + (resizing ? " artifacts-resizing" : ""),
|
|
1813
2565
|
style: { width: widthPx },
|
|
@@ -1815,23 +2567,29 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1815
2567
|
"aria-label": "Artifacts"
|
|
1816
2568
|
}, /* @__PURE__ */ h("div", {
|
|
1817
2569
|
className: "artifacts-resize",
|
|
1818
|
-
title: "
|
|
2570
|
+
title: t("resizeHandle"),
|
|
1819
2571
|
onMouseDown: startResize
|
|
1820
2572
|
}), /* @__PURE__ */ h("div", { className: "artifacts-head" }, /* @__PURE__ */ h("div", { className: "artifacts-head-left" }, /* @__PURE__ */ h("button", {
|
|
1821
2573
|
type: "button",
|
|
1822
2574
|
className: "artifacts-toggle",
|
|
1823
|
-
title: "
|
|
2575
|
+
title: t("collapsePanel"),
|
|
1824
2576
|
onClick: () => store.setOpen(false)
|
|
1825
2577
|
}, /* @__PURE__ */ h(PanelIcon, { size: 16 })), /* @__PURE__ */ h("a", {
|
|
1826
2578
|
className: "artifacts-link",
|
|
1827
2579
|
href: flyoutHref,
|
|
1828
2580
|
target: "_blank",
|
|
1829
2581
|
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", {
|
|
2582
|
+
title: t("flyoutOpen")
|
|
2583
|
+
}, /* @__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", {
|
|
2584
|
+
type: "button",
|
|
2585
|
+
className: "artifacts-toggle artifacts-search-toggle" + (searchOpen ? " is-active" : ""),
|
|
2586
|
+
title: t("searchToggle"),
|
|
2587
|
+
"aria-pressed": searchOpen,
|
|
2588
|
+
onClick: () => setSearchOpen((v) => !v)
|
|
2589
|
+
}, /* @__PURE__ */ h(SearchIcon, { size: 16 })) : null, /* @__PURE__ */ h("button", {
|
|
1832
2590
|
type: "button",
|
|
1833
2591
|
className: "artifacts-toggle",
|
|
1834
|
-
title: activeView === "tree" ? "
|
|
2592
|
+
title: activeView === "tree" ? t("refreshTree") : t("refreshChanges"),
|
|
1835
2593
|
onClick: () => {
|
|
1836
2594
|
if (activeView === "tree") setTreeRefresh((n) => n + 1);
|
|
1837
2595
|
else {
|
|
@@ -1843,24 +2601,35 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1843
2601
|
}, /* @__PURE__ */ h(RefreshIcon, { size: 16 })), settings.showFileTree ? /* @__PURE__ */ h("button", {
|
|
1844
2602
|
type: "button",
|
|
1845
2603
|
className: "artifacts-iconbtn artifacts-viewbtn" + (activeView === "git" ? " is-active" : ""),
|
|
1846
|
-
title: activeView === "tree" ? "
|
|
2604
|
+
title: activeView === "tree" ? t("viewGit") : t("backToFiles"),
|
|
1847
2605
|
"aria-pressed": activeView === "git",
|
|
1848
2606
|
onClick: () => setActiveView(activeView === "tree" ? "git" : "tree")
|
|
1849
2607
|
}, activeView === "tree" ? /* @__PURE__ */ h(GitBranchIcon, { size: 16 }) : /* @__PURE__ */ h(FolderClosedIcon, { size: 16 })) : null), /* @__PURE__ */ h("div", { className: "artifacts-main" }, /* @__PURE__ */ h("div", {
|
|
1850
2608
|
className: "artifacts-body" + (activeView === "git" && gitRefreshing ? " artifacts-refreshing" : ""),
|
|
1851
2609
|
style: { flex: "1 1 auto" }
|
|
1852
|
-
}, activeView === "tree" && settings.showFileTree ? /* @__PURE__ */ h(FileTree, {
|
|
2610
|
+
}, open ? activeView === "tree" && settings.showFileTree ? /* @__PURE__ */ h(FileTree, {
|
|
1853
2611
|
onOpen: openFile,
|
|
1854
2612
|
selectedPath: activeTab && !activeTab.git ? activeTab.path : null,
|
|
1855
|
-
refreshToken: treeRefresh
|
|
1856
|
-
|
|
2613
|
+
refreshToken: treeRefresh,
|
|
2614
|
+
searchOpen,
|
|
2615
|
+
onCloseSearch: () => setSearchOpen(false)
|
|
2616
|
+
}) : /* @__PURE__ */ h(GitChangesList, {
|
|
2617
|
+
files: gitFiles,
|
|
2618
|
+
error: gitError,
|
|
2619
|
+
activeDiffPath: activeTab && activeTab.git ? activeTab.path : null,
|
|
2620
|
+
flashKey: gitFlash,
|
|
2621
|
+
onOpen: openGitDiff,
|
|
2622
|
+
onCopyPath: (p) => copyText(p, t("copiedPath")),
|
|
2623
|
+
onQuote: quotePath
|
|
2624
|
+
}) : null))));
|
|
1857
2625
|
}
|
|
1858
2626
|
function CornerButton() {
|
|
1859
2627
|
const open = useOpen();
|
|
2628
|
+
useLang();
|
|
1860
2629
|
return /* @__PURE__ */ h("button", {
|
|
1861
2630
|
type: "button",
|
|
1862
2631
|
className: "artifacts-corner-btn" + (open ? " artifacts-slid-out" : ""),
|
|
1863
|
-
title: "
|
|
2632
|
+
title: t("flyoutTitle"),
|
|
1864
2633
|
"aria-expanded": open,
|
|
1865
2634
|
onClick: () => store.toggle()
|
|
1866
2635
|
}, /* @__PURE__ */ h(PanelIcon, { size: 18 }));
|
|
@@ -1878,23 +2647,29 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1878
2647
|
}
|
|
1879
2648
|
function SettingsSection() {
|
|
1880
2649
|
const settings = useSettings();
|
|
2650
|
+
useLang();
|
|
1881
2651
|
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: "
|
|
2652
|
+
return /* @__PURE__ */ h("div", { className: "artifacts-settings" }, /* @__PURE__ */ h("p", { className: "artifacts-setintro" }, t("settingsIntro")), /* @__PURE__ */ h("div", { className: "artifacts-setgroup" }, /* @__PURE__ */ h(SettingsToggle, {
|
|
2653
|
+
label: t("setDefaultOpen"),
|
|
2654
|
+
desc: t("setDefaultOpenDesc"),
|
|
1885
2655
|
value: settings.defaultOpen,
|
|
1886
2656
|
onToggle: (v) => set("defaultOpen", v)
|
|
1887
2657
|
}), /* @__PURE__ */ h(SettingsToggle, {
|
|
1888
|
-
label: "
|
|
1889
|
-
desc: "
|
|
2658
|
+
label: t("setAutoRefresh"),
|
|
2659
|
+
desc: t("setAutoRefreshDesc"),
|
|
1890
2660
|
value: settings.autoRefresh,
|
|
1891
2661
|
onToggle: (v) => set("autoRefresh", v)
|
|
1892
2662
|
}), /* @__PURE__ */ h(SettingsToggle, {
|
|
1893
|
-
label: "
|
|
1894
|
-
desc: "
|
|
2663
|
+
label: t("setFileTree"),
|
|
2664
|
+
desc: t("setFileTreeDesc"),
|
|
1895
2665
|
value: settings.showFileTree,
|
|
1896
2666
|
onToggle: (v) => set("showFileTree", v)
|
|
1897
|
-
}), /* @__PURE__ */ h(
|
|
2667
|
+
}), /* @__PURE__ */ h(SettingsToggle, {
|
|
2668
|
+
label: t("setCodeWrap"),
|
|
2669
|
+
desc: t("setCodeWrapDesc"),
|
|
2670
|
+
value: settings.codeWrap,
|
|
2671
|
+
onToggle: (v) => set("codeWrap", v)
|
|
2672
|
+
}), /* @__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
2673
|
type: "number",
|
|
1899
2674
|
className: "artifacts-widthinput",
|
|
1900
2675
|
min: 20,
|
|
@@ -1904,8 +2679,19 @@ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
|
|
|
1904
2679
|
const n = parseInt(e.currentTarget.value, 10);
|
|
1905
2680
|
if (Number.isNaN(n)) return;
|
|
1906
2681
|
set("minPanelWidth", Math.max(20, Math.min(60, n)));
|
|
2682
|
+
},
|
|
2683
|
+
onBlur: (e) => {
|
|
2684
|
+
const n = parseInt(e.currentTarget.value, 10);
|
|
2685
|
+
set("minPanelWidth", Number.isNaN(n) ? 20 : Math.max(20, Math.min(60, n)));
|
|
2686
|
+
}
|
|
2687
|
+
}), /* @__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", {
|
|
2688
|
+
className: "artifacts-langselect",
|
|
2689
|
+
value: getLanguageSetting() || "",
|
|
2690
|
+
onChange: (e) => {
|
|
2691
|
+
const v = e.currentTarget.value;
|
|
2692
|
+
setLanguage(v === "zh" || v === "en" ? v : null);
|
|
1907
2693
|
}
|
|
1908
|
-
}), /* @__PURE__ */ h("
|
|
2694
|
+
}, /* @__PURE__ */ h("option", { value: "" }, t("langAuto")), /* @__PURE__ */ h("option", { value: "zh" }, t("langZh")), /* @__PURE__ */ h("option", { value: "en" }, t("langEn")))))));
|
|
1909
2695
|
}
|
|
1910
2696
|
//#endregion
|
|
1911
2697
|
//#region src/client/index.tsx
|