@weniatall/dsh-file-viewer 0.3.12 → 0.3.13
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 +145 -15
- 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,102 @@ window.__ModuleLoader__.load({
|
|
|
3602
3629
|
: null);
|
|
3603
3630
|
}
|
|
3604
3631
|
|
|
3632
|
+
function resolveAbsPath(rootPath, relPath) {
|
|
3633
|
+
if (!rootPath) return relPath || "";
|
|
3634
|
+
const cleanRel = (relPath || "").replace(/^[\\/]+/, "");
|
|
3635
|
+
if (rootPath.includes("\\")) {
|
|
3636
|
+
return rootPath.replace(/[\\/]+$/, "") + "\\" + cleanRel.replace(/\//g, "\\");
|
|
3637
|
+
}
|
|
3638
|
+
return rootPath.replace(/[\\/]+$/, "") + "/" + cleanRel.replace(/\\/g, "/");
|
|
3639
|
+
}
|
|
3640
|
+
|
|
3641
|
+
/** Path copy dropdown menu on viewer toolbar. */
|
|
3642
|
+
function PathCopyDropdown({ meta, root, onToast }) {
|
|
3643
|
+
const [open, setOpen] = react.useState(false);
|
|
3644
|
+
const containerRef = react.useRef(null);
|
|
3645
|
+
|
|
3646
|
+
react.useEffect(() => {
|
|
3647
|
+
if (!open) return undefined;
|
|
3648
|
+
const handlePointerDown = (e) => {
|
|
3649
|
+
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
3650
|
+
setOpen(false);
|
|
3651
|
+
}
|
|
3652
|
+
};
|
|
3653
|
+
const handleKeyDown = (e) => {
|
|
3654
|
+
if (e.key === "Escape") setOpen(false);
|
|
3655
|
+
};
|
|
3656
|
+
window.addEventListener("pointerdown", handlePointerDown);
|
|
3657
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
3658
|
+
return () => {
|
|
3659
|
+
window.removeEventListener("pointerdown", handlePointerDown);
|
|
3660
|
+
window.removeEventListener("keydown", handleKeyDown);
|
|
3661
|
+
};
|
|
3662
|
+
}, [open]);
|
|
3663
|
+
|
|
3664
|
+
if (!meta || (!meta.path && !meta.displayPath)) return null;
|
|
3665
|
+
|
|
3666
|
+
const relPath = meta.displayPath || meta.path || "";
|
|
3667
|
+
const absPath = resolveAbsPath(root, relPath);
|
|
3668
|
+
|
|
3669
|
+
const handleCopy = (e, text, label) => {
|
|
3670
|
+
e.preventDefault();
|
|
3671
|
+
e.stopPropagation();
|
|
3672
|
+
copyToClipboard(text).then((ok) => {
|
|
3673
|
+
if (onToast) onToast(ok ? "已复制" + label : "复制失败");
|
|
3674
|
+
setOpen(false);
|
|
3675
|
+
});
|
|
3676
|
+
};
|
|
3677
|
+
|
|
3678
|
+
return react.createElement("div", {
|
|
3679
|
+
ref: containerRef,
|
|
3680
|
+
className: "fv-path-copy-wrap",
|
|
3681
|
+
},
|
|
3682
|
+
react.createElement("button", {
|
|
3683
|
+
type: "button",
|
|
3684
|
+
className: "fv-tab fv-tab-copy-path",
|
|
3685
|
+
"aria-expanded": open ? "true" : "false",
|
|
3686
|
+
"aria-label": "路径复制",
|
|
3687
|
+
title: "点击选择复制相对路径或绝对路径",
|
|
3688
|
+
onClick: (e) => {
|
|
3689
|
+
e.stopPropagation();
|
|
3690
|
+
setOpen((curr) => !curr);
|
|
3691
|
+
},
|
|
3692
|
+
},
|
|
3693
|
+
react.createElement(SvgIconCopy, { size: 12 }),
|
|
3694
|
+
react.createElement("span", { className: "fv-tab-text" }, "复制路径"),
|
|
3695
|
+
react.createElement("span", { className: "fv-tab-arrow" }, open ? "▴" : "▾")),
|
|
3696
|
+
open
|
|
3697
|
+
? react.createElement("div", {
|
|
3698
|
+
className: "fv-path-copy-menu",
|
|
3699
|
+
role: "menu",
|
|
3700
|
+
"aria-label": "路径复制选项",
|
|
3701
|
+
},
|
|
3702
|
+
react.createElement("button", {
|
|
3703
|
+
type: "button",
|
|
3704
|
+
className: "fv-path-copy-item",
|
|
3705
|
+
role: "menuitem",
|
|
3706
|
+
title: relPath,
|
|
3707
|
+
onClick: (e) => handleCopy(e, relPath, "相对路径"),
|
|
3708
|
+
},
|
|
3709
|
+
react.createElement("div", { className: "fv-path-copy-item-head" },
|
|
3710
|
+
react.createElement("span", { className: "fv-path-copy-item-icon" }, "📋"),
|
|
3711
|
+
react.createElement("span", { className: "fv-path-copy-item-label" }, "复制相对路径")),
|
|
3712
|
+
react.createElement("span", { className: "fv-path-copy-item-preview" }, relPath)),
|
|
3713
|
+
react.createElement("div", { className: "fv-context-divider" }),
|
|
3714
|
+
react.createElement("button", {
|
|
3715
|
+
type: "button",
|
|
3716
|
+
className: "fv-path-copy-item",
|
|
3717
|
+
role: "menuitem",
|
|
3718
|
+
title: absPath,
|
|
3719
|
+
onClick: (e) => handleCopy(e, absPath, "绝对路径"),
|
|
3720
|
+
},
|
|
3721
|
+
react.createElement("div", { className: "fv-path-copy-item-head" },
|
|
3722
|
+
react.createElement("span", { className: "fv-path-copy-item-icon" }, "📁"),
|
|
3723
|
+
react.createElement("span", { className: "fv-path-copy-item-label" }, "复制绝对路径")),
|
|
3724
|
+
react.createElement("span", { className: "fv-path-copy-item-preview" }, absPath)))
|
|
3725
|
+
: null);
|
|
3726
|
+
}
|
|
3727
|
+
|
|
3605
3728
|
function apply(ctx) {
|
|
3606
3729
|
ensureStyles(typeof document === "undefined" ? null : document);
|
|
3607
3730
|
// `null` is closed; an object is open. Carrying the requesting session lets
|
|
@@ -4097,7 +4220,7 @@ window.__ModuleLoader__.load({
|
|
|
4097
4220
|
|
|
4098
4221
|
/** Line-numbered diff viewer with dual line number gutters. */
|
|
4099
4222
|
/** Text, Markdown and JSON: one windowed read, three renderings. */
|
|
4100
|
-
function TextView({ meta, root, wrap, lang, commit, repo, backControl, sizeControl, langControl }) {
|
|
4223
|
+
function TextView({ meta, root, wrap, lang, commit, repo, backControl, sizeControl, langControl, onToast }) {
|
|
4101
4224
|
// Callers mount this with a per-file key, so paging state starts fresh
|
|
4102
4225
|
// for every file instead of leaking the previous file's window into the
|
|
4103
4226
|
// first request.
|
|
@@ -4383,11 +4506,16 @@ window.__ModuleLoader__.load({
|
|
|
4383
4506
|
: [contentTab, sourceTab, diffTab])].filter((tab) => tab !== null)
|
|
4384
4507
|
: [];
|
|
4385
4508
|
|
|
4386
|
-
const
|
|
4509
|
+
const copyControl = meta !== null
|
|
4510
|
+
? react.createElement(PathCopyDropdown, { meta, root, onToast })
|
|
4511
|
+
: null;
|
|
4512
|
+
|
|
4513
|
+
const tabs = showTabs || Boolean(backControl || langControl || sizeControl || copyControl)
|
|
4387
4514
|
? react.createElement("div", { className: "fv-sheet-tabs", role: "tablist" },
|
|
4388
4515
|
react.createElement("div", { className: "fv-sheet-tabs-start" },
|
|
4389
4516
|
backControl,
|
|
4390
|
-
...tabButtons
|
|
4517
|
+
...tabButtons,
|
|
4518
|
+
copyControl),
|
|
4391
4519
|
(sizeControl || langControl)
|
|
4392
4520
|
? react.createElement("div", { className: "fv-sheet-tabs-end" },
|
|
4393
4521
|
sizeControl,
|
|
@@ -4459,7 +4587,11 @@ window.__ModuleLoader__.load({
|
|
|
4459
4587
|
}
|
|
4460
4588
|
|
|
4461
4589
|
/** 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 }) {
|
|
4590
|
+
function FileView({ meta, root, api, wrap, lang, onDownload, commit, repo, loading, backControl, sizeControl, langControl, onToast }) {
|
|
4591
|
+
const copyControl = meta !== null
|
|
4592
|
+
? react.createElement(PathCopyDropdown, { meta, root, onToast })
|
|
4593
|
+
: null;
|
|
4594
|
+
|
|
4463
4595
|
if (meta === null) {
|
|
4464
4596
|
// A tab exists but its metadata is still in flight. Saying "pick a
|
|
4465
4597
|
// file" there would be a lie, and the reader would click again.
|
|
@@ -4470,8 +4602,8 @@ window.__ModuleLoader__.load({
|
|
|
4470
4602
|
}
|
|
4471
4603
|
if (meta.tooLarge || meta.kind === "binary") {
|
|
4472
4604
|
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),
|
|
4605
|
+
(backControl || copyControl) ? react.createElement("div", { className: "fv-sheet-tabs", role: "toolbar" },
|
|
4606
|
+
react.createElement("div", { className: "fv-sheet-tabs-start" }, backControl, copyControl),
|
|
4475
4607
|
sizeControl ? react.createElement("div", { className: "fv-sheet-tabs-end" }, sizeControl) : null) : null,
|
|
4476
4608
|
react.createElement("div", { className: "fv-note" },
|
|
4477
4609
|
react.createElement("strong", null, meta.name),
|
|
@@ -4506,9 +4638,9 @@ window.__ModuleLoader__.load({
|
|
|
4506
4638
|
react.createElement("span", { className: "fv-history-hint" }, "这是历史版本,不是工作区当前内容。"))
|
|
4507
4639
|
: null;
|
|
4508
4640
|
|
|
4509
|
-
const headBar = (backControl || sizeControl)
|
|
4641
|
+
const headBar = (backControl || sizeControl || copyControl)
|
|
4510
4642
|
? react.createElement("div", { className: "fv-sheet-tabs", role: "toolbar" },
|
|
4511
|
-
react.createElement("div", { className: "fv-sheet-tabs-start" }, backControl),
|
|
4643
|
+
react.createElement("div", { className: "fv-sheet-tabs-start" }, backControl, copyControl),
|
|
4512
4644
|
sizeControl ? react.createElement("div", { className: "fv-sheet-tabs-end" }, sizeControl) : null)
|
|
4513
4645
|
: null;
|
|
4514
4646
|
|
|
@@ -4526,7 +4658,7 @@ window.__ModuleLoader__.load({
|
|
|
4526
4658
|
return react.createElement("div", { className: "fv-content" }, headBar, react.createElement(DocView, { key, meta, root }));
|
|
4527
4659
|
}
|
|
4528
4660
|
return react.createElement(react.Fragment, null, banner,
|
|
4529
|
-
react.createElement(TextView, { key, meta, root, wrap, lang, commit, repo, backControl, sizeControl, langControl }));
|
|
4661
|
+
react.createElement(TextView, { key, meta, root, wrap, lang, commit, repo, backControl, sizeControl, langControl, onToast }));
|
|
4530
4662
|
}
|
|
4531
4663
|
|
|
4532
4664
|
|
|
@@ -4562,11 +4694,7 @@ window.__ModuleLoader__.load({
|
|
|
4562
4694
|
if (!menu) return null;
|
|
4563
4695
|
const { x, y, entry } = menu;
|
|
4564
4696
|
const relPath = entry.displayPath || entry.path;
|
|
4565
|
-
const absPath = rootPath
|
|
4566
|
-
? (rootPath.includes("\\")
|
|
4567
|
-
? (rootPath.replace(/[\\/]+$/, "") + "\\" + relPath.replace(/\//g, "\\"))
|
|
4568
|
-
: (rootPath.replace(/[\\/]+$/, "") + "/" + relPath.replace(/\\/g, "/")))
|
|
4569
|
-
: relPath;
|
|
4697
|
+
const absPath = resolveAbsPath(rootPath, relPath);
|
|
4570
4698
|
|
|
4571
4699
|
const handleCopyRel = (e) => {
|
|
4572
4700
|
e.stopPropagation();
|
|
@@ -6250,6 +6378,7 @@ window.__ModuleLoader__.load({
|
|
|
6250
6378
|
backControl,
|
|
6251
6379
|
sizeControl,
|
|
6252
6380
|
langControl,
|
|
6381
|
+
onToast: showToast,
|
|
6253
6382
|
})
|
|
6254
6383
|
: react.createElement(FileView, {
|
|
6255
6384
|
meta,
|
|
@@ -6268,6 +6397,7 @@ window.__ModuleLoader__.load({
|
|
|
6268
6397
|
backControl,
|
|
6269
6398
|
sizeControl,
|
|
6270
6399
|
langControl,
|
|
6400
|
+
onToast: showToast,
|
|
6271
6401
|
})))),
|
|
6272
6402
|
contextMenu !== null
|
|
6273
6403
|
? react.createElement(ContextMenu, {
|
|
@@ -6606,7 +6736,7 @@ window.__ModuleLoader__.load({
|
|
|
6606
6736
|
langOverrideKey, isKnownLanguageId, setLanguageCatalog, getLanguageCatalog: () => languageCatalog, LanguagePicker,
|
|
6607
6737
|
MARKDOWN_LABELS, MARKDOWN_CODE_LABELS, READ_BLOCK_LABELS, JSON_TREE_LABELS, DIFF_BLOCK_LABELS, ErrorBoundary,
|
|
6608
6738
|
openViewerForPath, wrapWorkspaceOpenPath, wrapConnectionRpc, wrapChangesOpenFetch, wrapSidebarRight, parseFileResource, setupGlobalFileClickInterceptor, extractClickedPath, normalizeClickedPath, looksLikeFilePath, isFileOpenControl, isFileMentionCode, renderRootOptions,
|
|
6609
|
-
triggerDownload, performDownload, isDesktopShell, SvgIconDownload, SvgIconBranch, SvgIconCommit,
|
|
6739
|
+
triggerDownload, performDownload, isDesktopShell, SvgIconDownload, SvgIconCopy, PathCopyDropdown, resolveAbsPath, SvgIconBranch, SvgIconCommit,
|
|
6610
6740
|
// Git panel: controller, views and the small formatters they share.
|
|
6611
6741
|
GIT_PAGE_SIZE, createGitController, GitSidebar, CommitDetail, WorkspaceChangeDetail, DiffView, kindOfPath,
|
|
6612
6742
|
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.13",
|
|
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",
|