@weniatall/dsh-file-viewer 0.3.1 → 0.3.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 +3 -1
- package/lib/client.js +193 -40
- package/lib/index.js +14 -1
- package/package.json +1 -1
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
|
@@ -378,6 +378,10 @@ window.__ModuleLoader__.load({
|
|
|
378
378
|
.fv-crumb:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
379
379
|
.fv-crumb-sep{flex:none;opacity:.5}
|
|
380
380
|
.fv-icon-button{display:inline-grid;flex:none;place-items:center;width:30px;height:30px;padding:0;border:none;border-radius:8px;background:none;color:var(--dsw-alias-label-secondary);cursor:pointer;transition:color .12s,background .12s;line-height:1}
|
|
381
|
+
/* A control with nowhere to go must look unavailable, not merely behave
|
|
382
|
+
that way — otherwise a disabled back button reads as broken. */
|
|
383
|
+
.fv-icon-button:disabled{opacity:.35;cursor:default}
|
|
384
|
+
.fv-icon-button:disabled:hover{background:none;color:var(--dsw-alias-label-secondary)}
|
|
381
385
|
.fv-icon-button svg{display:block;flex:none}
|
|
382
386
|
.fv-icon-button:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
383
387
|
.fv-icon-button[aria-pressed="true"]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
@@ -3343,6 +3347,9 @@ window.__ModuleLoader__.load({
|
|
|
3343
3347
|
// from the working tree: the two differ exactly when the history is
|
|
3344
3348
|
// interesting, and a deleted file has no working-tree copy at all.
|
|
3345
3349
|
const fromCommit = typeof commit === "string" && commit !== "";
|
|
3350
|
+
// Whether the reader has picked a view themselves. The automatic landing
|
|
3351
|
+
// on the diff happens once per mount and then stays out of the way.
|
|
3352
|
+
const viewChosen = react.useRef(false);
|
|
3346
3353
|
|
|
3347
3354
|
react.useEffect(() => {
|
|
3348
3355
|
let live = true;
|
|
@@ -3362,10 +3369,40 @@ window.__ModuleLoader__.load({
|
|
|
3362
3369
|
}).catch((error) => {
|
|
3363
3370
|
if (live) setState({ status: "error", data: null, error });
|
|
3364
3371
|
});
|
|
3365
|
-
//
|
|
3366
|
-
//
|
|
3372
|
+
// A historical file is diffed against its own commit, not against the
|
|
3373
|
+
// index: the working-tree comparison would answer a different
|
|
3374
|
+
// question and show changes made after this commit.
|
|
3375
|
+
const diffSha = typeof meta.diffSha === "string" && meta.diffSha !== "" ? meta.diffSha : "";
|
|
3367
3376
|
if (fromCommit) {
|
|
3368
|
-
|
|
3377
|
+
if (diffSha === "") {
|
|
3378
|
+
setDiffData(null);
|
|
3379
|
+
return () => { live = false; };
|
|
3380
|
+
}
|
|
3381
|
+
request("gitCommitDiff", {
|
|
3382
|
+
root,
|
|
3383
|
+
repo,
|
|
3384
|
+
sha: diffSha,
|
|
3385
|
+
path: meta.rowPath || meta.path,
|
|
3386
|
+
...(meta.oldPath ? { oldPath: meta.oldPath } : {}),
|
|
3387
|
+
}).then((val) => {
|
|
3388
|
+
if (!live) return;
|
|
3389
|
+
// `gitCommitDiff` returns per-file sections; this view shows the
|
|
3390
|
+
// one file it asked for.
|
|
3391
|
+
const first = val !== null && Array.isArray(val.files) ? val.files[0] : null;
|
|
3392
|
+
const loaded = first === null || first === undefined
|
|
3393
|
+
? null
|
|
3394
|
+
: { hasDiff: first.rows.length > 0, rows: first.rows, diffs: [], summary: { added: first.added, removed: first.removed } };
|
|
3395
|
+
setDiffData(loaded);
|
|
3396
|
+
// The reader opened this file *from* the change list, so the
|
|
3397
|
+
// change is what they came to see; the full text is one press
|
|
3398
|
+
// away and their own choice wins from then on.
|
|
3399
|
+
if (!viewChosen.current && loaded !== null && loaded.hasDiff) {
|
|
3400
|
+
viewChosen.current = true;
|
|
3401
|
+
setDiffMode(true);
|
|
3402
|
+
}
|
|
3403
|
+
}).catch(() => {
|
|
3404
|
+
if (live) setDiffData(null);
|
|
3405
|
+
});
|
|
3369
3406
|
return () => { live = false; };
|
|
3370
3407
|
}
|
|
3371
3408
|
request("diff", { root, path: meta.path }).then((val) => {
|
|
@@ -3374,7 +3411,7 @@ window.__ModuleLoader__.load({
|
|
|
3374
3411
|
if (live) setDiffData(null);
|
|
3375
3412
|
});
|
|
3376
3413
|
return () => { live = false; };
|
|
3377
|
-
}, [root, meta.path, offset, lang, fromCommit, commit, repo]);
|
|
3414
|
+
}, [root, meta.path, offset, lang, fromCommit, commit, repo, meta.diffSha, meta.rowPath, meta.oldPath]);
|
|
3378
3415
|
|
|
3379
3416
|
if (state.status === "error") {
|
|
3380
3417
|
return react.createElement("div", { className: "fv-note fv-error" }, messageOf(state.error));
|
|
@@ -3385,7 +3422,10 @@ window.__ModuleLoader__.load({
|
|
|
3385
3422
|
// A rendered preview needs the whole document. Past the host's preview
|
|
3386
3423
|
// budget only windowed lines arrive, so the source view is all there is.
|
|
3387
3424
|
const canPreview = (data.kind === "markdown" || data.kind === "json") && typeof data.text === "string";
|
|
3388
|
-
const hasDiff = Boolean(diffData && diffData.hasDiff &&
|
|
3425
|
+
const hasDiff = Boolean(diffData && diffData.hasDiff && (
|
|
3426
|
+
(Array.isArray(diffData.rows) && diffData.rows.length > 0)
|
|
3427
|
+
|| (Array.isArray(diffData.diffs) && diffData.diffs.length > 0)
|
|
3428
|
+
));
|
|
3389
3429
|
const diffSummary = diffData && diffData.summary;
|
|
3390
3430
|
const showTabs = canPreview || hasDiff;
|
|
3391
3431
|
|
|
@@ -3461,36 +3501,42 @@ window.__ModuleLoader__.load({
|
|
|
3461
3501
|
// Only the source view scrolls sideways; a rendered preview already
|
|
3462
3502
|
// reflows, so the attribute is pointless there and would just confuse.
|
|
3463
3503
|
const wrapping = wrap && (!canPreview || raw);
|
|
3504
|
+
// Both answers belong to the same question when a file is opened from a
|
|
3505
|
+
// commit: "what changed here" and "what does it contain". The diff leads
|
|
3506
|
+
// in that case, because it is what the reader came for; opening the
|
|
3507
|
+
// working-tree copy keeps the content tab first.
|
|
3508
|
+
const diffTab = hasDiff
|
|
3509
|
+
? react.createElement("button", {
|
|
3510
|
+
key: "diff",
|
|
3511
|
+
type: "button", className: "fv-tab fv-tab-diff", "aria-selected": diffMode ? "true" : "false",
|
|
3512
|
+
onClick: () => { viewChosen.current = true; setDiffMode(true); },
|
|
3513
|
+
},
|
|
3514
|
+
"改动",
|
|
3515
|
+
diffSummary && (diffSummary.added > 0 || diffSummary.removed > 0)
|
|
3516
|
+
? react.createElement("span", { className: "fv-diff-stat" },
|
|
3517
|
+
diffSummary.added > 0 ? react.createElement("span", { className: "fv-diff-add" }, `+${diffSummary.added}`) : null,
|
|
3518
|
+
(diffSummary.added > 0 && diffSummary.removed > 0) ? " " : "",
|
|
3519
|
+
diffSummary.removed > 0 ? react.createElement("span", { className: "fv-diff-del" }, `-${diffSummary.removed}`) : null)
|
|
3520
|
+
: null)
|
|
3521
|
+
: null;
|
|
3522
|
+
const contentTab = react.createElement("button", {
|
|
3523
|
+
key: "content",
|
|
3524
|
+
type: "button", className: "fv-tab", "aria-selected": (!raw && !diffMode) ? "true" : "false",
|
|
3525
|
+
onClick: () => { viewChosen.current = true; setRaw(false); setDiffMode(false); },
|
|
3526
|
+
}, canPreview ? (data.kind === "markdown" ? "预览" : "结构") : (fromCommit ? "完整内容" : "内容"));
|
|
3527
|
+
const sourceTab = canPreview
|
|
3528
|
+
? react.createElement("button", {
|
|
3529
|
+
key: "source",
|
|
3530
|
+
type: "button", className: "fv-tab", "aria-selected": (raw && !diffMode) ? "true" : "false",
|
|
3531
|
+
onClick: () => { viewChosen.current = true; setRaw(true); setDiffMode(false); },
|
|
3532
|
+
}, "源码")
|
|
3533
|
+
: null;
|
|
3534
|
+
|
|
3464
3535
|
const tabs = showTabs
|
|
3465
3536
|
? react.createElement("div", { className: "fv-sheet-tabs", role: "tablist" },
|
|
3466
|
-
|
|
3467
|
-
?
|
|
3468
|
-
|
|
3469
|
-
onClick: () => { setRaw(false); setDiffMode(false); },
|
|
3470
|
-
}, data.kind === "markdown" ? "预览" : "结构")
|
|
3471
|
-
: react.createElement("button", {
|
|
3472
|
-
type: "button", className: "fv-tab", "aria-selected": (!raw && !diffMode) ? "true" : "false",
|
|
3473
|
-
onClick: () => { setRaw(false); setDiffMode(false); },
|
|
3474
|
-
}, "内容"),
|
|
3475
|
-
canPreview
|
|
3476
|
-
? react.createElement("button", {
|
|
3477
|
-
type: "button", className: "fv-tab", "aria-selected": (raw && !diffMode) ? "true" : "false",
|
|
3478
|
-
onClick: () => { setRaw(true); setDiffMode(false); },
|
|
3479
|
-
}, "源码")
|
|
3480
|
-
: null,
|
|
3481
|
-
hasDiff
|
|
3482
|
-
? react.createElement("button", {
|
|
3483
|
-
type: "button", className: "fv-tab fv-tab-diff", "aria-selected": diffMode ? "true" : "false",
|
|
3484
|
-
onClick: () => setDiffMode(true),
|
|
3485
|
-
},
|
|
3486
|
-
"改动",
|
|
3487
|
-
diffSummary && (diffSummary.added > 0 || diffSummary.removed > 0)
|
|
3488
|
-
? react.createElement("span", { className: "fv-diff-stat" },
|
|
3489
|
-
diffSummary.added > 0 ? react.createElement("span", { className: "fv-diff-add" }, `+${diffSummary.added}`) : null,
|
|
3490
|
-
(diffSummary.added > 0 && diffSummary.removed > 0) ? " " : "",
|
|
3491
|
-
diffSummary.removed > 0 ? react.createElement("span", { className: "fv-diff-del" }, `-${diffSummary.removed}`) : null)
|
|
3492
|
-
: null)
|
|
3493
|
-
: null)
|
|
3537
|
+
...(fromCommit && diffTab !== null
|
|
3538
|
+
? [diffTab, contentTab, sourceTab]
|
|
3539
|
+
: [contentTab, sourceTab, diffTab]).filter((tab) => tab !== null))
|
|
3494
3540
|
: null;
|
|
3495
3541
|
return react.createElement(react.Fragment, null,
|
|
3496
3542
|
tabs,
|
|
@@ -4032,6 +4078,22 @@ window.__ModuleLoader__.load({
|
|
|
4032
4078
|
const [previewCommit, setPreviewCommit] = react.useState("");
|
|
4033
4079
|
// Which half of the commit pane is showing: the file list or the patch.
|
|
4034
4080
|
const [commitView, setCommitView] = react.useState("files");
|
|
4081
|
+
/**
|
|
4082
|
+
* What the main pane was showing before the current file preview.
|
|
4083
|
+
*
|
|
4084
|
+
* The back control has to undo exactly one step. Opening a file from a
|
|
4085
|
+
* commit's changed-file list must return to that list — not to the file
|
|
4086
|
+
* tree, which is a different place entirely — so the origin is recorded
|
|
4087
|
+
* when the file is opened rather than guessed when back is pressed.
|
|
4088
|
+
*
|
|
4089
|
+
* `null` means there is nowhere to go back to.
|
|
4090
|
+
* @type {"commit" | "changes" | null}
|
|
4091
|
+
*/
|
|
4092
|
+
const [previewOrigin, setPreviewOrigin] = react.useState(null);
|
|
4093
|
+
// The working-tree change shown in the main pane. It cannot ride on the
|
|
4094
|
+
// commit detail, which is keyed by hash, and `meta` belongs to the file
|
|
4095
|
+
// preview — so the row the reader clicked is kept here.
|
|
4096
|
+
const [previewChangePath, setPreviewChangePath] = react.useState("");
|
|
4035
4097
|
const gitState = useStore(git.store);
|
|
4036
4098
|
|
|
4037
4099
|
react.useEffect(() => {
|
|
@@ -4189,6 +4251,19 @@ window.__ModuleLoader__.load({
|
|
|
4189
4251
|
react.useEffect(() => {
|
|
4190
4252
|
if (request_ === null) return undefined;
|
|
4191
4253
|
let live = true;
|
|
4254
|
+
// The drawer stays mounted while closed (`open` only gates rendering),
|
|
4255
|
+
// so every view state survives from the previous visit. Opening a
|
|
4256
|
+
// plain file after browsing Git used to inherit the old commit hash
|
|
4257
|
+
// and the Git sidebar, and the file rendered as "history preview"
|
|
4258
|
+
// instead of its content. A new open is a new session: clear it all.
|
|
4259
|
+
setSide("files");
|
|
4260
|
+
setShowCommit(false);
|
|
4261
|
+
setCommitView("files");
|
|
4262
|
+
setPreviewCommit("");
|
|
4263
|
+
setPreviewOrigin(null);
|
|
4264
|
+
setPreviewChangePath("");
|
|
4265
|
+
setMeta(null);
|
|
4266
|
+
setError(null);
|
|
4192
4267
|
const payload = {
|
|
4193
4268
|
...(request_.sessionId !== undefined ? { sessionId: request_.sessionId } : {}),
|
|
4194
4269
|
...(request_.filePath !== undefined ? { filePath: request_.filePath } : {}),
|
|
@@ -4402,6 +4477,9 @@ window.__ModuleLoader__.load({
|
|
|
4402
4477
|
setShowCommit(false);
|
|
4403
4478
|
setPane("content");
|
|
4404
4479
|
setPreviewCommit(readSha);
|
|
4480
|
+
// Back must return to the changed-file list this row came from,
|
|
4481
|
+
// not to the file tree, which is somewhere else entirely.
|
|
4482
|
+
setPreviewOrigin("commit");
|
|
4405
4483
|
setLangChoice("auto");
|
|
4406
4484
|
const ticket = metaTicket.current + 1;
|
|
4407
4485
|
metaTicket.current = ticket;
|
|
@@ -4413,10 +4491,20 @@ window.__ModuleLoader__.load({
|
|
|
4413
4491
|
// The blob path is what the host reads; the display path keeps the
|
|
4414
4492
|
// row the reader clicked.
|
|
4415
4493
|
path: blobPath,
|
|
4494
|
+
// A rename is read from its old path but highlighted as the row
|
|
4495
|
+
// the reader actually clicked.
|
|
4496
|
+
rowPath: file.path,
|
|
4416
4497
|
name: baseNameOf(blobPath),
|
|
4417
4498
|
displayPath: baseNameOf(blobPath) + " @ " + readSha.slice(0, 7),
|
|
4418
4499
|
kind: component,
|
|
4419
4500
|
commit: readSha,
|
|
4501
|
+
// Opening a changed file must offer both halves of the question:
|
|
4502
|
+
// what this commit changed in it, and what the file looks like.
|
|
4503
|
+
// `diffSha` is the commit being viewed (the blob may come from
|
|
4504
|
+
// its parent for a deletion), and `oldPath` is what lets the host
|
|
4505
|
+
// keep rename detection intact when filtering the patch.
|
|
4506
|
+
diffSha: sha,
|
|
4507
|
+
oldPath: typeof file.oldPath === "string" && file.oldPath !== "" ? file.oldPath : null,
|
|
4420
4508
|
// The commit records whether the file was added, modified or
|
|
4421
4509
|
// deleted; the preview says so instead of looking like a normal
|
|
4422
4510
|
// working-tree file.
|
|
@@ -4431,6 +4519,44 @@ window.__ModuleLoader__.load({
|
|
|
4431
4519
|
selectFile({ path: file.path, name: baseNameOf(file.path), type: "file" });
|
|
4432
4520
|
};
|
|
4433
4521
|
|
|
4522
|
+
/**
|
|
4523
|
+
* Step back one place from the main pane.
|
|
4524
|
+
*
|
|
4525
|
+
* Three destinations, in the order the reader arrived from: a commit's
|
|
4526
|
+
* changed-file list, the working-tree change list, or the file tree.
|
|
4527
|
+
* Each step clears the state that belonged to the place being left, so
|
|
4528
|
+
* a stale hash or blob path cannot leak into the next preview.
|
|
4529
|
+
*/
|
|
4530
|
+
const goBack = () => {
|
|
4531
|
+
if (previewOrigin === "commit") {
|
|
4532
|
+
// Re-open the changed-file view of the commit still in `detail`;
|
|
4533
|
+
// its hash is what the sidebar is highlighting.
|
|
4534
|
+
setShowCommit(true);
|
|
4535
|
+
setCommitView("files");
|
|
4536
|
+
setPreviewCommit("");
|
|
4537
|
+
setPreviewOrigin(null);
|
|
4538
|
+
setPane("content");
|
|
4539
|
+
return;
|
|
4540
|
+
}
|
|
4541
|
+
if (previewOrigin === "changes") {
|
|
4542
|
+
setShowCommit(true);
|
|
4543
|
+
setCommitView("changes");
|
|
4544
|
+
setPreviewCommit("");
|
|
4545
|
+
setPreviewOrigin(null);
|
|
4546
|
+
setPane("content");
|
|
4547
|
+
return;
|
|
4548
|
+
}
|
|
4549
|
+
// Nothing to undo inside the pane: hand control back to the sidebar.
|
|
4550
|
+
// On a phone that actually swaps panes; on desktop the tree is
|
|
4551
|
+
// already alongside, so the selection is cleared instead of the
|
|
4552
|
+
// button doing nothing at all.
|
|
4553
|
+
setPreviewCommit("");
|
|
4554
|
+
setPreviewOrigin(null);
|
|
4555
|
+
setMeta(null);
|
|
4556
|
+
setError(null);
|
|
4557
|
+
setPane("tree");
|
|
4558
|
+
};
|
|
4559
|
+
|
|
4434
4560
|
/**
|
|
4435
4561
|
* Open one working-tree change as a diff.
|
|
4436
4562
|
*
|
|
@@ -4443,7 +4569,10 @@ window.__ModuleLoader__.load({
|
|
|
4443
4569
|
setPane("content");
|
|
4444
4570
|
setCommitView("changes");
|
|
4445
4571
|
setPreviewCommit("");
|
|
4446
|
-
|
|
4572
|
+
setPreviewOrigin(null);
|
|
4573
|
+
// The pane diffs exactly this path; ignoring the row left it reading
|
|
4574
|
+
// whatever file happened to be selected before.
|
|
4575
|
+
setPreviewChangePath(row.path);
|
|
4447
4576
|
};
|
|
4448
4577
|
|
|
4449
4578
|
// Clicking two files quickly must not let the slower answer win: only
|
|
@@ -4451,8 +4580,11 @@ window.__ModuleLoader__.load({
|
|
|
4451
4580
|
const selectFile = (entry) => {
|
|
4452
4581
|
setPane("content");
|
|
4453
4582
|
// Selecting from the file tree is always the working tree, so any
|
|
4454
|
-
// commit a preview was read from is dropped here
|
|
4583
|
+
// commit a preview was read from is dropped here — and there is no
|
|
4584
|
+
// intermediate pane to return to.
|
|
4455
4585
|
setPreviewCommit("");
|
|
4586
|
+
setPreviewOrigin(null);
|
|
4587
|
+
setPreviewChangePath("");
|
|
4456
4588
|
// The remembered choice for this file takes effect immediately, so
|
|
4457
4589
|
// the read that follows is already resolved with it.
|
|
4458
4590
|
setLangChoice(readLangOverride(root, entry.path));
|
|
@@ -4712,9 +4844,28 @@ window.__ModuleLoader__.load({
|
|
|
4712
4844
|
}),
|
|
4713
4845
|
react.createElement("div", { className: "fv-main" },
|
|
4714
4846
|
react.createElement("div", { className: "fv-main-head" },
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4847
|
+
(() => {
|
|
4848
|
+
// The control returns one step, so it names that step and
|
|
4849
|
+
// is disabled when there is nowhere to go — a button that
|
|
4850
|
+
// silently does nothing reads as broken.
|
|
4851
|
+
const backTarget = previewOrigin === "commit"
|
|
4852
|
+
? "返回变更文件列表"
|
|
4853
|
+
: (previewOrigin === "changes" ? "返回工作区改动列表" : "返回文件列表");
|
|
4854
|
+
// On a phone the panes are exclusive, so leaving the content
|
|
4855
|
+
// pane is always meaningful; on desktop the sidebar is
|
|
4856
|
+
// already visible and only an origin is worth returning to.
|
|
4857
|
+
const canGoBack = isMobile || pane === "content"
|
|
4858
|
+
? (previewOrigin !== null || meta !== null || showCommit)
|
|
4859
|
+
: false;
|
|
4860
|
+
return react.createElement("button", {
|
|
4861
|
+
type: "button",
|
|
4862
|
+
className: "fv-icon-button fv-btn-back",
|
|
4863
|
+
"aria-label": backTarget,
|
|
4864
|
+
title: backTarget,
|
|
4865
|
+
disabled: !canGoBack,
|
|
4866
|
+
onClick: goBack,
|
|
4867
|
+
}, react.createElement("span", { "aria-hidden": "true" }, "‹"));
|
|
4868
|
+
})(),
|
|
4718
4869
|
react.createElement("span", { className: "fv-main-name" },
|
|
4719
4870
|
commitHeadline !== ""
|
|
4720
4871
|
? commitHeadline
|
|
@@ -4755,13 +4906,15 @@ window.__ModuleLoader__.load({
|
|
|
4755
4906
|
}, commitView === "changes"
|
|
4756
4907
|
? react.createElement(WorkspaceChangeDetail, {
|
|
4757
4908
|
state: gitState,
|
|
4758
|
-
path:
|
|
4909
|
+
path: previewChangePath,
|
|
4759
4910
|
request,
|
|
4760
4911
|
repo: gitState.repoId,
|
|
4761
4912
|
})
|
|
4762
4913
|
: react.createElement(CommitDetail, {
|
|
4763
4914
|
state: gitState,
|
|
4764
|
-
|
|
4915
|
+
// A rename is read from its old path but highlighted as
|
|
4916
|
+
// the row that was clicked, so the two stay in step.
|
|
4917
|
+
selectedPath: meta === null ? "" : (meta.rowPath || meta.path),
|
|
4765
4918
|
onSelectFile: openCommitFile,
|
|
4766
4919
|
view: commitView,
|
|
4767
4920
|
onSwitchView: setCommitView,
|
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.3",
|
|
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",
|