@weniatall/dsh-file-viewer 0.3.12 → 0.3.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +179 -16
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -163,6 +163,21 @@ window.__ModuleLoader__.load({
|
|
|
163
163
|
);
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
function SvgIconCopy({ size = 13 }) {
|
|
167
|
+
return react.createElement("svg", {
|
|
168
|
+
width: size, height: size, viewBox: "0 0 16 16", fill: "none",
|
|
169
|
+
stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round", strokeLinejoin: "round",
|
|
170
|
+
style: { flex: "none" },
|
|
171
|
+
},
|
|
172
|
+
react.createElement("rect", {
|
|
173
|
+
x: "5", y: "5", width: "8.5", height: "8.5", rx: "1.5",
|
|
174
|
+
}),
|
|
175
|
+
react.createElement("path", {
|
|
176
|
+
d: "M3.5 10.5H2.75C2.05964 10.5 1.5 9.94036 1.5 9.25V2.75C1.5 2.05964 2.05964 1.5 2.75 1.5H9.25C9.94036 1.5 10.5 2.05964 10.5 2.75V3.5",
|
|
177
|
+
})
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
166
181
|
const Component = react.Component || class Component { constructor(props) { this.props = props; this.state = {}; } setState(s) { Object.assign(this.state, typeof s === "function" ? s(this.state) : s); } };
|
|
167
182
|
class ErrorBoundary extends Component {
|
|
168
183
|
constructor(props) {
|
|
@@ -557,6 +572,18 @@ window.__ModuleLoader__.load({
|
|
|
557
572
|
.fv-tab{height:28px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:14px;background:none;color:var(--dsw-alias-label-secondary);font-size:12px;cursor:pointer}
|
|
558
573
|
.fv-tab[aria-selected="true"]{border-color:transparent;background:var(--dsw-alias-brand-primary,#4d6bfe);color:var(--dsw-alias-label-primary-foreground,#fff)}
|
|
559
574
|
.fv-tab-diff{display:inline-flex;align-items:center;gap:4px}
|
|
575
|
+
.fv-path-copy-wrap{position:relative;display:inline-flex;align-items:center}
|
|
576
|
+
.fv-tab.fv-tab-copy-path{display:inline-flex;align-items:center;gap:5px;height:28px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:14px;background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-base,#fff));color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;cursor:pointer;user-select:none;transition:all .15s ease}
|
|
577
|
+
.fv-tab.fv-tab-copy-path:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l3,var(--dsw-alias-border-l2))}
|
|
578
|
+
.fv-tab.fv-tab-copy-path[aria-expanded="true"]{background:var(--dsw-alias-interactive-bg-hover-solid,var(--dsw-alias-interactive-bg-hover));color:var(--dsw-alias-brand-primary,#4d6bfe);border-color:var(--dsw-alias-brand-primary,#4d6bfe)}
|
|
579
|
+
.fv-tab-copy-path .fv-tab-arrow{font-size:9px;margin-left:2px;opacity:.75}
|
|
580
|
+
.fv-path-copy-menu{position:absolute;top:calc(100% + 5px);left:0;z-index:60;min-width:210px;max-width:380px;padding:5px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-base,#fff));box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);animation:fv-menu-in .12s cubic-bezier(.2,.8,.2,1)}
|
|
581
|
+
.fv-path-copy-item{display:flex;flex-direction:column;gap:2px;width:100%;padding:6px 10px;border:none;border-radius:8px;background:none;color:inherit;font:inherit;text-align:left;cursor:pointer;user-select:none;transition:background-color .12s ease}
|
|
582
|
+
.fv-path-copy-item:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
583
|
+
.fv-path-copy-item-head{display:flex;align-items:center;gap:8px;font-size:12.5px;font-weight:500}
|
|
584
|
+
.fv-path-copy-item-icon{flex:none;font-size:13px}
|
|
585
|
+
.fv-path-copy-item-label{flex:1 1 auto;white-space:nowrap}
|
|
586
|
+
.fv-path-copy-item-preview{font-size:11px;color:var(--dsw-alias-label-tertiary);font-family:var(--dsw-font-code,monospace);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-left:21px;line-height:1.35}
|
|
560
587
|
.fv-diff-stat{font-size:11px;font-weight:600;font-variant-numeric:tabular-nums;margin-left:2px}
|
|
561
588
|
.fv-diff-add{color:var(--dsw-alias-state-success-primary,#10b981)}
|
|
562
589
|
.fv-diff-del{color:var(--dsw-alias-state-error-primary,#ef4444)}
|
|
@@ -3602,6 +3629,136 @@ window.__ModuleLoader__.load({
|
|
|
3602
3629
|
: null);
|
|
3603
3630
|
}
|
|
3604
3631
|
|
|
3632
|
+
function isAbsolutePath(p) {
|
|
3633
|
+
if (typeof p !== "string" || p === "") return false;
|
|
3634
|
+
return /^[a-zA-Z]:[\\/]/.test(p) || p.startsWith("/") || p.startsWith("\\\\");
|
|
3635
|
+
}
|
|
3636
|
+
|
|
3637
|
+
function resolveAbsPath(rootPath, relPath) {
|
|
3638
|
+
if (!rootPath) return relPath || "";
|
|
3639
|
+
const cleanRel = (relPath || "").replace(/^\.[\\/]+/, "").replace(/^[\\/]+/, "");
|
|
3640
|
+
if (isAbsolutePath(cleanRel)) return cleanRel;
|
|
3641
|
+
if (rootPath.includes("\\")) {
|
|
3642
|
+
return rootPath.replace(/[\\/]+$/, "") + "\\" + cleanRel.replace(/\//g, "\\");
|
|
3643
|
+
}
|
|
3644
|
+
return rootPath.replace(/[\\/]+$/, "") + "/" + cleanRel.replace(/\\/g, "/");
|
|
3645
|
+
}
|
|
3646
|
+
|
|
3647
|
+
function extractRelativeAndAbsolutePaths(item, rootPath) {
|
|
3648
|
+
if (!item) return { relPath: "", absPath: "" };
|
|
3649
|
+
let rel = item.path || "";
|
|
3650
|
+
if (isAbsolutePath(rel)) {
|
|
3651
|
+
// 如果 path 意外传了绝对路径,且位于 rootPath 之下,提取相对路径
|
|
3652
|
+
const normRoot = rootPath ? rootPath.replace(/\\/g, "/").replace(/\/+$/, "") : "";
|
|
3653
|
+
const normRel = rel.replace(/\\/g, "/");
|
|
3654
|
+
if (normRoot && (normRel.toLowerCase().startsWith(normRoot.toLowerCase() + "/") || normRel.startsWith(normRoot + "/"))) {
|
|
3655
|
+
rel = normRel.slice(normRoot.length + 1);
|
|
3656
|
+
}
|
|
3657
|
+
}
|
|
3658
|
+
if (!rel) {
|
|
3659
|
+
rel = item.name || "";
|
|
3660
|
+
}
|
|
3661
|
+
const cleanRel = String(rel).replace(/^\.[\\/]+/, "").replace(/^[\\/]+/, "");
|
|
3662
|
+
|
|
3663
|
+
// 绝对路径:优先考虑真实宿主路径 displayPath,或由 rootPath 与相对路径拼接
|
|
3664
|
+
let abs = "";
|
|
3665
|
+
if (isAbsolutePath(item.displayPath)) {
|
|
3666
|
+
abs = item.displayPath;
|
|
3667
|
+
} else if (isAbsolutePath(item.path)) {
|
|
3668
|
+
abs = item.path;
|
|
3669
|
+
} else {
|
|
3670
|
+
abs = resolveAbsPath(rootPath, cleanRel);
|
|
3671
|
+
}
|
|
3672
|
+
|
|
3673
|
+
return { relPath: cleanRel, absPath: abs };
|
|
3674
|
+
}
|
|
3675
|
+
|
|
3676
|
+
/** Path copy dropdown menu on viewer toolbar. */
|
|
3677
|
+
function PathCopyDropdown({ meta, root, onToast }) {
|
|
3678
|
+
const [open, setOpen] = react.useState(false);
|
|
3679
|
+
const containerRef = react.useRef(null);
|
|
3680
|
+
|
|
3681
|
+
react.useEffect(() => {
|
|
3682
|
+
if (!open) return undefined;
|
|
3683
|
+
const handlePointerDown = (e) => {
|
|
3684
|
+
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
3685
|
+
setOpen(false);
|
|
3686
|
+
}
|
|
3687
|
+
};
|
|
3688
|
+
const handleKeyDown = (e) => {
|
|
3689
|
+
if (e.key === "Escape") setOpen(false);
|
|
3690
|
+
};
|
|
3691
|
+
window.addEventListener("pointerdown", handlePointerDown);
|
|
3692
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
3693
|
+
return () => {
|
|
3694
|
+
window.removeEventListener("pointerdown", handlePointerDown);
|
|
3695
|
+
window.removeEventListener("keydown", handleKeyDown);
|
|
3696
|
+
};
|
|
3697
|
+
}, [open]);
|
|
3698
|
+
|
|
3699
|
+
if (!meta || (!meta.path && !meta.displayPath && !meta.name)) return null;
|
|
3700
|
+
|
|
3701
|
+
const { relPath, absPath } = extractRelativeAndAbsolutePaths(meta, root);
|
|
3702
|
+
|
|
3703
|
+
const handleCopy = (e, text, label) => {
|
|
3704
|
+
e.preventDefault();
|
|
3705
|
+
e.stopPropagation();
|
|
3706
|
+
copyToClipboard(text).then((ok) => {
|
|
3707
|
+
if (onToast) onToast(ok ? "已复制" + label : "复制失败");
|
|
3708
|
+
setOpen(false);
|
|
3709
|
+
});
|
|
3710
|
+
};
|
|
3711
|
+
|
|
3712
|
+
return react.createElement("div", {
|
|
3713
|
+
ref: containerRef,
|
|
3714
|
+
className: "fv-path-copy-wrap",
|
|
3715
|
+
},
|
|
3716
|
+
react.createElement("button", {
|
|
3717
|
+
type: "button",
|
|
3718
|
+
className: "fv-tab fv-tab-copy-path",
|
|
3719
|
+
"aria-expanded": open ? "true" : "false",
|
|
3720
|
+
"aria-label": "路径复制",
|
|
3721
|
+
title: "点击选择复制相对路径或绝对路径",
|
|
3722
|
+
onClick: (e) => {
|
|
3723
|
+
e.stopPropagation();
|
|
3724
|
+
setOpen((curr) => !curr);
|
|
3725
|
+
},
|
|
3726
|
+
},
|
|
3727
|
+
react.createElement(SvgIconCopy, { size: 12 }),
|
|
3728
|
+
react.createElement("span", { className: "fv-tab-text" }, "复制路径"),
|
|
3729
|
+
react.createElement("span", { className: "fv-tab-arrow" }, open ? "▴" : "▾")),
|
|
3730
|
+
open
|
|
3731
|
+
? react.createElement("div", {
|
|
3732
|
+
className: "fv-path-copy-menu",
|
|
3733
|
+
role: "menu",
|
|
3734
|
+
"aria-label": "路径复制选项",
|
|
3735
|
+
},
|
|
3736
|
+
react.createElement("button", {
|
|
3737
|
+
type: "button",
|
|
3738
|
+
className: "fv-path-copy-item",
|
|
3739
|
+
role: "menuitem",
|
|
3740
|
+
title: relPath,
|
|
3741
|
+
onClick: (e) => handleCopy(e, relPath, "相对路径"),
|
|
3742
|
+
},
|
|
3743
|
+
react.createElement("div", { className: "fv-path-copy-item-head" },
|
|
3744
|
+
react.createElement("span", { className: "fv-path-copy-item-icon" }, "📋"),
|
|
3745
|
+
react.createElement("span", { className: "fv-path-copy-item-label" }, "复制相对路径")),
|
|
3746
|
+
react.createElement("span", { className: "fv-path-copy-item-preview" }, relPath)),
|
|
3747
|
+
react.createElement("div", { className: "fv-context-divider" }),
|
|
3748
|
+
react.createElement("button", {
|
|
3749
|
+
type: "button",
|
|
3750
|
+
className: "fv-path-copy-item",
|
|
3751
|
+
role: "menuitem",
|
|
3752
|
+
title: absPath,
|
|
3753
|
+
onClick: (e) => handleCopy(e, absPath, "绝对路径"),
|
|
3754
|
+
},
|
|
3755
|
+
react.createElement("div", { className: "fv-path-copy-item-head" },
|
|
3756
|
+
react.createElement("span", { className: "fv-path-copy-item-icon" }, "📁"),
|
|
3757
|
+
react.createElement("span", { className: "fv-path-copy-item-label" }, "复制绝对路径")),
|
|
3758
|
+
react.createElement("span", { className: "fv-path-copy-item-preview" }, absPath)))
|
|
3759
|
+
: null);
|
|
3760
|
+
}
|
|
3761
|
+
|
|
3605
3762
|
function apply(ctx) {
|
|
3606
3763
|
ensureStyles(typeof document === "undefined" ? null : document);
|
|
3607
3764
|
// `null` is closed; an object is open. Carrying the requesting session lets
|
|
@@ -4097,7 +4254,7 @@ window.__ModuleLoader__.load({
|
|
|
4097
4254
|
|
|
4098
4255
|
/** Line-numbered diff viewer with dual line number gutters. */
|
|
4099
4256
|
/** Text, Markdown and JSON: one windowed read, three renderings. */
|
|
4100
|
-
function TextView({ meta, root, wrap, lang, commit, repo, backControl, sizeControl, langControl }) {
|
|
4257
|
+
function TextView({ meta, root, wrap, lang, commit, repo, backControl, sizeControl, langControl, onToast }) {
|
|
4101
4258
|
// Callers mount this with a per-file key, so paging state starts fresh
|
|
4102
4259
|
// for every file instead of leaking the previous file's window into the
|
|
4103
4260
|
// first request.
|
|
@@ -4383,11 +4540,16 @@ window.__ModuleLoader__.load({
|
|
|
4383
4540
|
: [contentTab, sourceTab, diffTab])].filter((tab) => tab !== null)
|
|
4384
4541
|
: [];
|
|
4385
4542
|
|
|
4386
|
-
const
|
|
4543
|
+
const copyControl = meta !== null
|
|
4544
|
+
? react.createElement(PathCopyDropdown, { meta, root, onToast })
|
|
4545
|
+
: null;
|
|
4546
|
+
|
|
4547
|
+
const tabs = showTabs || Boolean(backControl || langControl || sizeControl || copyControl)
|
|
4387
4548
|
? react.createElement("div", { className: "fv-sheet-tabs", role: "tablist" },
|
|
4388
4549
|
react.createElement("div", { className: "fv-sheet-tabs-start" },
|
|
4389
4550
|
backControl,
|
|
4390
|
-
...tabButtons
|
|
4551
|
+
...tabButtons,
|
|
4552
|
+
copyControl),
|
|
4391
4553
|
(sizeControl || langControl)
|
|
4392
4554
|
? react.createElement("div", { className: "fv-sheet-tabs-end" },
|
|
4393
4555
|
sizeControl,
|
|
@@ -4459,7 +4621,11 @@ window.__ModuleLoader__.load({
|
|
|
4459
4621
|
}
|
|
4460
4622
|
|
|
4461
4623
|
/** Pick the viewer for a file and keep unsupported formats useful. */
|
|
4462
|
-
function FileView({ meta, root, api, wrap, lang, onDownload, commit, repo, loading, backControl, sizeControl, langControl }) {
|
|
4624
|
+
function FileView({ meta, root, api, wrap, lang, onDownload, commit, repo, loading, backControl, sizeControl, langControl, onToast }) {
|
|
4625
|
+
const copyControl = meta !== null
|
|
4626
|
+
? react.createElement(PathCopyDropdown, { meta, root, onToast })
|
|
4627
|
+
: null;
|
|
4628
|
+
|
|
4463
4629
|
if (meta === null) {
|
|
4464
4630
|
// A tab exists but its metadata is still in flight. Saying "pick a
|
|
4465
4631
|
// file" there would be a lie, and the reader would click again.
|
|
@@ -4470,8 +4636,8 @@ window.__ModuleLoader__.load({
|
|
|
4470
4636
|
}
|
|
4471
4637
|
if (meta.tooLarge || meta.kind === "binary") {
|
|
4472
4638
|
return react.createElement("div", { className: "fv-binary-pane" },
|
|
4473
|
-
backControl ? react.createElement("div", { className: "fv-sheet-tabs", role: "toolbar" },
|
|
4474
|
-
react.createElement("div", { className: "fv-sheet-tabs-start" }, backControl),
|
|
4639
|
+
(backControl || copyControl) ? react.createElement("div", { className: "fv-sheet-tabs", role: "toolbar" },
|
|
4640
|
+
react.createElement("div", { className: "fv-sheet-tabs-start" }, backControl, copyControl),
|
|
4475
4641
|
sizeControl ? react.createElement("div", { className: "fv-sheet-tabs-end" }, sizeControl) : null) : null,
|
|
4476
4642
|
react.createElement("div", { className: "fv-note" },
|
|
4477
4643
|
react.createElement("strong", null, meta.name),
|
|
@@ -4506,9 +4672,9 @@ window.__ModuleLoader__.load({
|
|
|
4506
4672
|
react.createElement("span", { className: "fv-history-hint" }, "这是历史版本,不是工作区当前内容。"))
|
|
4507
4673
|
: null;
|
|
4508
4674
|
|
|
4509
|
-
const headBar = (backControl || sizeControl)
|
|
4675
|
+
const headBar = (backControl || sizeControl || copyControl)
|
|
4510
4676
|
? react.createElement("div", { className: "fv-sheet-tabs", role: "toolbar" },
|
|
4511
|
-
react.createElement("div", { className: "fv-sheet-tabs-start" }, backControl),
|
|
4677
|
+
react.createElement("div", { className: "fv-sheet-tabs-start" }, backControl, copyControl),
|
|
4512
4678
|
sizeControl ? react.createElement("div", { className: "fv-sheet-tabs-end" }, sizeControl) : null)
|
|
4513
4679
|
: null;
|
|
4514
4680
|
|
|
@@ -4526,7 +4692,7 @@ window.__ModuleLoader__.load({
|
|
|
4526
4692
|
return react.createElement("div", { className: "fv-content" }, headBar, react.createElement(DocView, { key, meta, root }));
|
|
4527
4693
|
}
|
|
4528
4694
|
return react.createElement(react.Fragment, null, banner,
|
|
4529
|
-
react.createElement(TextView, { key, meta, root, wrap, lang, commit, repo, backControl, sizeControl, langControl }));
|
|
4695
|
+
react.createElement(TextView, { key, meta, root, wrap, lang, commit, repo, backControl, sizeControl, langControl, onToast }));
|
|
4530
4696
|
}
|
|
4531
4697
|
|
|
4532
4698
|
|
|
@@ -4561,12 +4727,7 @@ window.__ModuleLoader__.load({
|
|
|
4561
4727
|
|
|
4562
4728
|
if (!menu) return null;
|
|
4563
4729
|
const { x, y, entry } = menu;
|
|
4564
|
-
const relPath = entry
|
|
4565
|
-
const absPath = rootPath
|
|
4566
|
-
? (rootPath.includes("\\")
|
|
4567
|
-
? (rootPath.replace(/[\\/]+$/, "") + "\\" + relPath.replace(/\//g, "\\"))
|
|
4568
|
-
: (rootPath.replace(/[\\/]+$/, "") + "/" + relPath.replace(/\\/g, "/")))
|
|
4569
|
-
: relPath;
|
|
4730
|
+
const { relPath, absPath } = extractRelativeAndAbsolutePaths(entry, rootPath);
|
|
4570
4731
|
|
|
4571
4732
|
const handleCopyRel = (e) => {
|
|
4572
4733
|
e.stopPropagation();
|
|
@@ -6250,6 +6411,7 @@ window.__ModuleLoader__.load({
|
|
|
6250
6411
|
backControl,
|
|
6251
6412
|
sizeControl,
|
|
6252
6413
|
langControl,
|
|
6414
|
+
onToast: showToast,
|
|
6253
6415
|
})
|
|
6254
6416
|
: react.createElement(FileView, {
|
|
6255
6417
|
meta,
|
|
@@ -6268,6 +6430,7 @@ window.__ModuleLoader__.load({
|
|
|
6268
6430
|
backControl,
|
|
6269
6431
|
sizeControl,
|
|
6270
6432
|
langControl,
|
|
6433
|
+
onToast: showToast,
|
|
6271
6434
|
})))),
|
|
6272
6435
|
contextMenu !== null
|
|
6273
6436
|
? react.createElement(ContextMenu, {
|
|
@@ -6606,7 +6769,7 @@ window.__ModuleLoader__.load({
|
|
|
6606
6769
|
langOverrideKey, isKnownLanguageId, setLanguageCatalog, getLanguageCatalog: () => languageCatalog, LanguagePicker,
|
|
6607
6770
|
MARKDOWN_LABELS, MARKDOWN_CODE_LABELS, READ_BLOCK_LABELS, JSON_TREE_LABELS, DIFF_BLOCK_LABELS, ErrorBoundary,
|
|
6608
6771
|
openViewerForPath, wrapWorkspaceOpenPath, wrapConnectionRpc, wrapChangesOpenFetch, wrapSidebarRight, parseFileResource, setupGlobalFileClickInterceptor, extractClickedPath, normalizeClickedPath, looksLikeFilePath, isFileOpenControl, isFileMentionCode, renderRootOptions,
|
|
6609
|
-
triggerDownload, performDownload, isDesktopShell, SvgIconDownload, SvgIconBranch, SvgIconCommit,
|
|
6772
|
+
triggerDownload, performDownload, isDesktopShell, SvgIconDownload, SvgIconCopy, PathCopyDropdown, resolveAbsPath, extractRelativeAndAbsolutePaths, isAbsolutePath, SvgIconBranch, SvgIconCommit,
|
|
6610
6773
|
// Git panel: controller, views and the small formatters they share.
|
|
6611
6774
|
GIT_PAGE_SIZE, createGitController, GitSidebar, CommitDetail, WorkspaceChangeDetail, DiffView, kindOfPath,
|
|
6612
6775
|
formatCommitDate, formatCommitTimestamp,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weniatall/dsh-file-viewer",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.14",
|
|
4
4
|
"description": "Workspace file viewer and Git panel for DeepSeek Harness: file tree, syntax-highlighted code, images, PDF, Excel and Word, plus local/remote branches, paged commit history, commit diffs, working-tree changes and changed-file navigation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|