@weniatall/dsh-file-viewer 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +3 -1
- package/lib/client.js +88 -36
- package/lib/index.js +14 -1
- package/package.json +5 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 veildawn
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -86,7 +86,9 @@ dsh plugin --profile web add <release-tgz-url>
|
|
|
86
86
|
- **提交记录**:按 30 条一页加载,显示标题、短 SHA、作者与日期,底部「加载更多」按需追加,并显示「已显示 N / 总数」。
|
|
87
87
|
- **改动文件列表**:点击任一提交,右侧显示该提交的完整信息(标题、完整 SHA、作者、时间、正文)与相对**第一父提交**的改动文件列表(合并提交同理;根提交与空树比对)。`A / M / D / R / C` 等状态用颜色区分。
|
|
88
88
|
- **该提交的 diff**:提交详情有「变更文件 / 改动内容」两个页签,后者是该提交针对同一基准(第一父提交)的完整 patch,按文件分段并各自标注 `+N -M`。两个页签取的是同一对 revision,不会出现列表与 diff 基准不一致的情况。
|
|
89
|
-
- **跳转到文件**:点击改动文件会在右侧已有的预览器里打开**该提交中的版本**,而不是工作区当前内容——两者恰恰在历史最有价值的时候不同。被该提交删除的文件从父提交读取,所以删除行也能打开;重命名显示旧路径的内容。预览顶部会标明来源提交,避免被误认为当前文件。`‹`
|
|
89
|
+
- **跳转到文件**:点击改动文件会在右侧已有的预览器里打开**该提交中的版本**,而不是工作区当前内容——两者恰恰在历史最有价值的时候不同。被该提交删除的文件从父提交读取,所以删除行也能打开;重命名显示旧路径的内容。预览顶部会标明来源提交,避免被误认为当前文件。`‹` 返回 「变更文件」列表继续浏览。
|
|
90
|
+
|
|
91
|
+
打开后有两个页签:**改动**(这个提交对该文件做了什么,默认显示)和**完整内容**(该版本文件的全文,可翻页、切换语法高亮)。重命名的 diff 会保留为「重命名」而不是退化成「删除 + 新增」——宿主端把新旧两侧路径一起交给 git,否则 `-M` 无法配对。
|
|
90
92
|
|
|
91
93
|
只读操作(列出仓库、分支、历史、改动文件)不会修改仓库;面板中唯一的写操作是用户显式点击「检出」。
|
|
92
94
|
|
package/lib/client.js
CHANGED
|
@@ -604,11 +604,14 @@ window.__ModuleLoader__.load({
|
|
|
604
604
|
@keyframes fv-toast-in{from{opacity:0;transform:translate(-50%,8px)}to{opacity:1;transform:translate(-50%,0)}}
|
|
605
605
|
/* Floating entry button is strictly disabled across both PC and mobile. */
|
|
606
606
|
.fv-float-entry{display:none!important}
|
|
607
|
-
/* Composer toolbar entry (PC): seated in conversation.input.left beside the + command button
|
|
608
|
-
|
|
607
|
+
/* Composer toolbar entry (PC): seated in conversation.input.left beside the + command button.
|
|
608
|
+
几何必须与宿主原生「+」(.uV2eYG_add) 对齐:28×28、radius-sm、specific-selector 底色、
|
|
609
|
+
label-primary 前景。此前用 999px 圆角 + label-secondary,夹在原生按钮中间显得是一枚
|
|
610
|
+
突兀的实心圆扣,尺寸与圆角都对不上。 */
|
|
611
|
+
.fv-composer-entry{box-sizing:border-box;display:inline-grid;place-items:center;width:28px;height:28px;padding:0;border:none;border-radius:var(--dsw-radius-sm);background:var(--dsw-specific-selector);color:var(--dsw-alias-label-primary);cursor:pointer;flex:none;line-height:1;corner-shape:round;transition:background-color .12s,color .12s,transform .1s}
|
|
609
612
|
.fv-composer-entry:hover{background:var(--dsw-alias-interactive-bg-hover-solid,var(--dsw-alias-interactive-bg-hover));color:var(--dsw-alias-label-primary)}
|
|
610
613
|
.fv-composer-entry:active{transform:scale(.95)}
|
|
611
|
-
.fv-composer-entry svg{display:block;flex:none}
|
|
614
|
+
.fv-composer-entry svg{display:block;flex:none;width:16px;height:16px}
|
|
612
615
|
@media(max-width:768px){.fv-composer-entry{display:none!important}}
|
|
613
616
|
@media(max-width:1100px){.fv-shell{width:min(82vw,900px)}}
|
|
614
617
|
.fv-shell[data-mobile="true"]{width:100%!important;border-left:none!important}
|
|
@@ -3347,6 +3350,9 @@ window.__ModuleLoader__.load({
|
|
|
3347
3350
|
// from the working tree: the two differ exactly when the history is
|
|
3348
3351
|
// interesting, and a deleted file has no working-tree copy at all.
|
|
3349
3352
|
const fromCommit = typeof commit === "string" && commit !== "";
|
|
3353
|
+
// Whether the reader has picked a view themselves. The automatic landing
|
|
3354
|
+
// on the diff happens once per mount and then stays out of the way.
|
|
3355
|
+
const viewChosen = react.useRef(false);
|
|
3350
3356
|
|
|
3351
3357
|
react.useEffect(() => {
|
|
3352
3358
|
let live = true;
|
|
@@ -3366,10 +3372,40 @@ window.__ModuleLoader__.load({
|
|
|
3366
3372
|
}).catch((error) => {
|
|
3367
3373
|
if (live) setState({ status: "error", data: null, error });
|
|
3368
3374
|
});
|
|
3369
|
-
//
|
|
3370
|
-
//
|
|
3375
|
+
// A historical file is diffed against its own commit, not against the
|
|
3376
|
+
// index: the working-tree comparison would answer a different
|
|
3377
|
+
// question and show changes made after this commit.
|
|
3378
|
+
const diffSha = typeof meta.diffSha === "string" && meta.diffSha !== "" ? meta.diffSha : "";
|
|
3371
3379
|
if (fromCommit) {
|
|
3372
|
-
|
|
3380
|
+
if (diffSha === "") {
|
|
3381
|
+
setDiffData(null);
|
|
3382
|
+
return () => { live = false; };
|
|
3383
|
+
}
|
|
3384
|
+
request("gitCommitDiff", {
|
|
3385
|
+
root,
|
|
3386
|
+
repo,
|
|
3387
|
+
sha: diffSha,
|
|
3388
|
+
path: meta.rowPath || meta.path,
|
|
3389
|
+
...(meta.oldPath ? { oldPath: meta.oldPath } : {}),
|
|
3390
|
+
}).then((val) => {
|
|
3391
|
+
if (!live) return;
|
|
3392
|
+
// `gitCommitDiff` returns per-file sections; this view shows the
|
|
3393
|
+
// one file it asked for.
|
|
3394
|
+
const first = val !== null && Array.isArray(val.files) ? val.files[0] : null;
|
|
3395
|
+
const loaded = first === null || first === undefined
|
|
3396
|
+
? null
|
|
3397
|
+
: { hasDiff: first.rows.length > 0, rows: first.rows, diffs: [], summary: { added: first.added, removed: first.removed } };
|
|
3398
|
+
setDiffData(loaded);
|
|
3399
|
+
// The reader opened this file *from* the change list, so the
|
|
3400
|
+
// change is what they came to see; the full text is one press
|
|
3401
|
+
// away and their own choice wins from then on.
|
|
3402
|
+
if (!viewChosen.current && loaded !== null && loaded.hasDiff) {
|
|
3403
|
+
viewChosen.current = true;
|
|
3404
|
+
setDiffMode(true);
|
|
3405
|
+
}
|
|
3406
|
+
}).catch(() => {
|
|
3407
|
+
if (live) setDiffData(null);
|
|
3408
|
+
});
|
|
3373
3409
|
return () => { live = false; };
|
|
3374
3410
|
}
|
|
3375
3411
|
request("diff", { root, path: meta.path }).then((val) => {
|
|
@@ -3378,7 +3414,7 @@ window.__ModuleLoader__.load({
|
|
|
3378
3414
|
if (live) setDiffData(null);
|
|
3379
3415
|
});
|
|
3380
3416
|
return () => { live = false; };
|
|
3381
|
-
}, [root, meta.path, offset, lang, fromCommit, commit, repo]);
|
|
3417
|
+
}, [root, meta.path, offset, lang, fromCommit, commit, repo, meta.diffSha, meta.rowPath, meta.oldPath]);
|
|
3382
3418
|
|
|
3383
3419
|
if (state.status === "error") {
|
|
3384
3420
|
return react.createElement("div", { className: "fv-note fv-error" }, messageOf(state.error));
|
|
@@ -3389,7 +3425,10 @@ window.__ModuleLoader__.load({
|
|
|
3389
3425
|
// A rendered preview needs the whole document. Past the host's preview
|
|
3390
3426
|
// budget only windowed lines arrive, so the source view is all there is.
|
|
3391
3427
|
const canPreview = (data.kind === "markdown" || data.kind === "json") && typeof data.text === "string";
|
|
3392
|
-
const hasDiff = Boolean(diffData && diffData.hasDiff &&
|
|
3428
|
+
const hasDiff = Boolean(diffData && diffData.hasDiff && (
|
|
3429
|
+
(Array.isArray(diffData.rows) && diffData.rows.length > 0)
|
|
3430
|
+
|| (Array.isArray(diffData.diffs) && diffData.diffs.length > 0)
|
|
3431
|
+
));
|
|
3393
3432
|
const diffSummary = diffData && diffData.summary;
|
|
3394
3433
|
const showTabs = canPreview || hasDiff;
|
|
3395
3434
|
|
|
@@ -3465,36 +3504,42 @@ window.__ModuleLoader__.load({
|
|
|
3465
3504
|
// Only the source view scrolls sideways; a rendered preview already
|
|
3466
3505
|
// reflows, so the attribute is pointless there and would just confuse.
|
|
3467
3506
|
const wrapping = wrap && (!canPreview || raw);
|
|
3507
|
+
// Both answers belong to the same question when a file is opened from a
|
|
3508
|
+
// commit: "what changed here" and "what does it contain". The diff leads
|
|
3509
|
+
// in that case, because it is what the reader came for; opening the
|
|
3510
|
+
// working-tree copy keeps the content tab first.
|
|
3511
|
+
const diffTab = hasDiff
|
|
3512
|
+
? react.createElement("button", {
|
|
3513
|
+
key: "diff",
|
|
3514
|
+
type: "button", className: "fv-tab fv-tab-diff", "aria-selected": diffMode ? "true" : "false",
|
|
3515
|
+
onClick: () => { viewChosen.current = true; setDiffMode(true); },
|
|
3516
|
+
},
|
|
3517
|
+
"改动",
|
|
3518
|
+
diffSummary && (diffSummary.added > 0 || diffSummary.removed > 0)
|
|
3519
|
+
? react.createElement("span", { className: "fv-diff-stat" },
|
|
3520
|
+
diffSummary.added > 0 ? react.createElement("span", { className: "fv-diff-add" }, `+${diffSummary.added}`) : null,
|
|
3521
|
+
(diffSummary.added > 0 && diffSummary.removed > 0) ? " " : "",
|
|
3522
|
+
diffSummary.removed > 0 ? react.createElement("span", { className: "fv-diff-del" }, `-${diffSummary.removed}`) : null)
|
|
3523
|
+
: null)
|
|
3524
|
+
: null;
|
|
3525
|
+
const contentTab = react.createElement("button", {
|
|
3526
|
+
key: "content",
|
|
3527
|
+
type: "button", className: "fv-tab", "aria-selected": (!raw && !diffMode) ? "true" : "false",
|
|
3528
|
+
onClick: () => { viewChosen.current = true; setRaw(false); setDiffMode(false); },
|
|
3529
|
+
}, canPreview ? (data.kind === "markdown" ? "预览" : "结构") : (fromCommit ? "完整内容" : "内容"));
|
|
3530
|
+
const sourceTab = canPreview
|
|
3531
|
+
? react.createElement("button", {
|
|
3532
|
+
key: "source",
|
|
3533
|
+
type: "button", className: "fv-tab", "aria-selected": (raw && !diffMode) ? "true" : "false",
|
|
3534
|
+
onClick: () => { viewChosen.current = true; setRaw(true); setDiffMode(false); },
|
|
3535
|
+
}, "源码")
|
|
3536
|
+
: null;
|
|
3537
|
+
|
|
3468
3538
|
const tabs = showTabs
|
|
3469
3539
|
? react.createElement("div", { className: "fv-sheet-tabs", role: "tablist" },
|
|
3470
|
-
|
|
3471
|
-
?
|
|
3472
|
-
|
|
3473
|
-
onClick: () => { setRaw(false); setDiffMode(false); },
|
|
3474
|
-
}, data.kind === "markdown" ? "预览" : "结构")
|
|
3475
|
-
: react.createElement("button", {
|
|
3476
|
-
type: "button", className: "fv-tab", "aria-selected": (!raw && !diffMode) ? "true" : "false",
|
|
3477
|
-
onClick: () => { setRaw(false); setDiffMode(false); },
|
|
3478
|
-
}, "内容"),
|
|
3479
|
-
canPreview
|
|
3480
|
-
? react.createElement("button", {
|
|
3481
|
-
type: "button", className: "fv-tab", "aria-selected": (raw && !diffMode) ? "true" : "false",
|
|
3482
|
-
onClick: () => { setRaw(true); setDiffMode(false); },
|
|
3483
|
-
}, "源码")
|
|
3484
|
-
: null,
|
|
3485
|
-
hasDiff
|
|
3486
|
-
? react.createElement("button", {
|
|
3487
|
-
type: "button", className: "fv-tab fv-tab-diff", "aria-selected": diffMode ? "true" : "false",
|
|
3488
|
-
onClick: () => setDiffMode(true),
|
|
3489
|
-
},
|
|
3490
|
-
"改动",
|
|
3491
|
-
diffSummary && (diffSummary.added > 0 || diffSummary.removed > 0)
|
|
3492
|
-
? react.createElement("span", { className: "fv-diff-stat" },
|
|
3493
|
-
diffSummary.added > 0 ? react.createElement("span", { className: "fv-diff-add" }, `+${diffSummary.added}`) : null,
|
|
3494
|
-
(diffSummary.added > 0 && diffSummary.removed > 0) ? " " : "",
|
|
3495
|
-
diffSummary.removed > 0 ? react.createElement("span", { className: "fv-diff-del" }, `-${diffSummary.removed}`) : null)
|
|
3496
|
-
: null)
|
|
3497
|
-
: null)
|
|
3540
|
+
...(fromCommit && diffTab !== null
|
|
3541
|
+
? [diffTab, contentTab, sourceTab]
|
|
3542
|
+
: [contentTab, sourceTab, diffTab]).filter((tab) => tab !== null))
|
|
3498
3543
|
: null;
|
|
3499
3544
|
return react.createElement(react.Fragment, null,
|
|
3500
3545
|
tabs,
|
|
@@ -4456,6 +4501,13 @@ window.__ModuleLoader__.load({
|
|
|
4456
4501
|
displayPath: baseNameOf(blobPath) + " @ " + readSha.slice(0, 7),
|
|
4457
4502
|
kind: component,
|
|
4458
4503
|
commit: readSha,
|
|
4504
|
+
// Opening a changed file must offer both halves of the question:
|
|
4505
|
+
// what this commit changed in it, and what the file looks like.
|
|
4506
|
+
// `diffSha` is the commit being viewed (the blob may come from
|
|
4507
|
+
// its parent for a deletion), and `oldPath` is what lets the host
|
|
4508
|
+
// keep rename detection intact when filtering the patch.
|
|
4509
|
+
diffSha: sha,
|
|
4510
|
+
oldPath: typeof file.oldPath === "string" && file.oldPath !== "" ? file.oldPath : null,
|
|
4459
4511
|
// The commit records whether the file was added, modified or
|
|
4460
4512
|
// deleted; the preview says so instead of looking like a normal
|
|
4461
4513
|
// working-tree file.
|
package/lib/index.js
CHANGED
|
@@ -1931,11 +1931,24 @@ export async function readGitCommitDiff(ctx, options, payload, signal) {
|
|
|
1931
1931
|
if (wanted === null && typeof payload?.path === 'string' && payload.path !== '') {
|
|
1932
1932
|
throw new ViewerError('outside-root', '该文件不在当前根目录内')
|
|
1933
1933
|
}
|
|
1934
|
+
// A rename has two paths, and filtering by only one of them defeats rename
|
|
1935
|
+
// detection: git then reports the pair as an unrelated delete plus an add
|
|
1936
|
+
// (measured — `-- new-name` prints "new file mode", `-- old-name` prints
|
|
1937
|
+
// "deleted file mode"). Passing both keeps `-M` able to pair them.
|
|
1938
|
+
const wantedOld = typeof payload?.oldPath === 'string' && payload.oldPath !== ''
|
|
1939
|
+
? repoRelativeFromRoot(placement, payload.oldPath)
|
|
1940
|
+
: null
|
|
1941
|
+
if (wantedOld === null && typeof payload?.oldPath === 'string' && payload.oldPath !== '') {
|
|
1942
|
+
throw new ViewerError('outside-root', '该文件不在当前根目录内')
|
|
1943
|
+
}
|
|
1934
1944
|
|
|
1935
1945
|
const base = hasParent
|
|
1936
1946
|
? ['diff', '--no-color', '-M', parentSha, sha]
|
|
1937
1947
|
: ['show', '--no-color', '--format=', '--first-parent', sha]
|
|
1938
|
-
const
|
|
1948
|
+
const paths = wanted === null
|
|
1949
|
+
? []
|
|
1950
|
+
: [...new Set([wanted, ...(wantedOld === null ? [] : [wantedOld])])]
|
|
1951
|
+
const args = paths.length === 0 ? base : [...base, '--', ...paths]
|
|
1939
1952
|
const result = await run(repo.toplevel, args, signal)
|
|
1940
1953
|
if (result?.ok !== true) throw new ViewerError('git-failed', '无法读取该提交的改动内容')
|
|
1941
1954
|
|
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.4",
|
|
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",
|
|
@@ -17,9 +17,6 @@
|
|
|
17
17
|
"README.md",
|
|
18
18
|
"cordis.patch.yml"
|
|
19
19
|
],
|
|
20
|
-
"scripts": {
|
|
21
|
-
"test": "node --test test/*.test.mjs && node ../../scripts/check-host-settings.mjs plugins/dsh-file-viewer file-viewer"
|
|
22
|
-
},
|
|
23
20
|
"license": "MIT",
|
|
24
21
|
"keywords": [
|
|
25
22
|
"deepseek-harness",
|
|
@@ -72,5 +69,8 @@
|
|
|
72
69
|
},
|
|
73
70
|
"engines": {
|
|
74
71
|
"node": ">=20.3"
|
|
72
|
+
},
|
|
73
|
+
"scripts": {
|
|
74
|
+
"test": "node --test test/*.test.mjs && node ../../scripts/check-host-settings.mjs plugins/dsh-file-viewer file-viewer"
|
|
75
75
|
}
|
|
76
|
-
}
|
|
76
|
+
}
|