@weniatall/dsh-file-viewer 0.3.7 → 0.3.12
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 +306 -105
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -348,6 +348,10 @@ window.__ModuleLoader__.load({
|
|
|
348
348
|
const DEFAULT_TREE_WIDTH = 280;
|
|
349
349
|
const MIN_TREE_WIDTH = 160;
|
|
350
350
|
const MAX_TREE_WIDTH = 700;
|
|
351
|
+
const GIT_CHANGES_HEIGHT_KEY = "dsh-file-viewer.git-changes-height";
|
|
352
|
+
const DEFAULT_GIT_CHANGES_HEIGHT = 180;
|
|
353
|
+
const MIN_GIT_CHANGES_HEIGHT = 64;
|
|
354
|
+
const MAX_GIT_CHANGES_HEIGHT = 800;
|
|
351
355
|
/**
|
|
352
356
|
* Smallest width the drawer may be dragged to.
|
|
353
357
|
*
|
|
@@ -544,7 +548,11 @@ window.__ModuleLoader__.load({
|
|
|
544
548
|
.fv-image-canvas.fv-image-dragging{cursor:grabbing}
|
|
545
549
|
.fv-image-element{max-width:calc(100% - 32px);max-height:calc(100% - 32px);object-fit:contain;transition:transform .05s ease-out;pointer-events:none;box-shadow:var(--dsw-shadow-lv2,0 4px 12px rgba(0,0,0,.08))}
|
|
546
550
|
.fv-frame{width:100%;height:100%;min-height:420px;border:none;background:var(--dsw-alias-bg-layer-1,#fff)}
|
|
547
|
-
.fv-sheet-tabs{display:flex;flex-wrap:wrap;flex:none;align-items:center;gap:6px;padding:
|
|
551
|
+
.fv-sheet-tabs{display:flex;flex-wrap:wrap;flex:none;align-items:center;justify-content:space-between;gap:6px;padding:6px 12px;background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-base,#fff));border-bottom:1px solid var(--dsw-alias-border-l1);z-index:2;min-height:38px}
|
|
552
|
+
.fv-sheet-tabs-start{display:inline-flex;align-items:center;gap:6px;min-width:0;flex:1 1 auto}
|
|
553
|
+
.fv-sheet-tabs-end{display:inline-flex;align-items:center;gap:8px;margin-left:auto;flex:none}
|
|
554
|
+
.fv-sheet-tabs .fv-btn-back{width:26px;height:26px;font-size:16px;line-height:24px;border-radius:6px;margin-right:2px}
|
|
555
|
+
.fv-main-size{font-size:11px;color:var(--dsw-alias-label-caption);white-space:nowrap}
|
|
548
556
|
.fv-content .fv-sheet-tabs{position:sticky;top:0;margin:-12px -12px 10px;padding:12px 12px 8px}
|
|
549
557
|
.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}
|
|
550
558
|
.fv-tab[aria-selected="true"]{border-color:transparent;background:var(--dsw-alias-brand-primary,#4d6bfe);color:var(--dsw-alias-label-primary-foreground,#fff)}
|
|
@@ -631,7 +639,19 @@ window.__ModuleLoader__.load({
|
|
|
631
639
|
.fv-commit-tag{flex:none;padding:1px 6px;border-radius:6px;background:var(--dsw-alias-bg-layer-3,rgba(0,0,0,.05));color:var(--dsw-alias-label-caption);font-family:var(--dsw-font-family);font-size:10px}
|
|
632
640
|
/* Working-tree changes and a commit's patch share the commit-row visuals
|
|
633
641
|
so the two panels read as one surface. */
|
|
634
|
-
.fv-git-changes{display:flex;flex:none;flex-direction:column;gap:1px;
|
|
642
|
+
.fv-git-changes{display:flex;flex:none;flex-direction:column;gap:1px;min-height:48px;overflow-y:auto;overscroll-behavior:contain;padding-bottom:2px}
|
|
643
|
+
.fv-git-panel[data-v-resizing="true"]{cursor:row-resize!important;user-select:none!important}
|
|
644
|
+
.fv-git-panel[data-v-resizing="true"] *{pointer-events:none!important}
|
|
645
|
+
.fv-git-panel[data-v-resizing="true"] .fv-git-v-resizer,
|
|
646
|
+
.fv-git-panel[data-v-resizing="true"] .fv-git-v-resizer *{pointer-events:auto!important}
|
|
647
|
+
.fv-git-v-resizer{position:relative;flex:none;display:flex;align-items:center;justify-content:center;height:14px;margin:2px 0;cursor:row-resize;user-select:none;touch-action:none;z-index:3}
|
|
648
|
+
.fv-git-v-resizer-line{position:absolute;left:0;right:0;top:50%;height:1px;background:var(--dsw-alias-border-l1);transform:translateY(-50%);transition:background-color .15s ease;pointer-events:none}
|
|
649
|
+
.fv-git-v-resizer-btn{position:relative;display:inline-flex;align-items:center;justify-content:center;width:44px;height:13px;padding:0;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-base,#fff));box-shadow:0 1px 3px rgba(0,0,0,.08);cursor:row-resize;pointer-events:auto;transition:background-color .15s ease,border-color .15s ease,box-shadow .15s ease;z-index:2}
|
|
650
|
+
.fv-git-v-resizer-grip{display:block;width:14px;height:2px;border-radius:1px;background:var(--dsw-alias-label-caption);box-shadow:0 -3px 0 0 var(--dsw-alias-label-caption);pointer-events:none}
|
|
651
|
+
.fv-git-v-resizer:hover .fv-git-v-resizer-line,.fv-git-v-resizer[data-dragging="true"] .fv-git-v-resizer-line{background:var(--dsw-alias-brand-primary,#4d6bfe)}
|
|
652
|
+
.fv-git-v-resizer:hover .fv-git-v-resizer-btn,.fv-git-v-resizer[data-dragging="true"] .fv-git-v-resizer-btn,.fv-git-v-resizer-btn:hover{border-color:var(--dsw-alias-brand-primary,#4d6bfe);background:var(--dsw-alias-bg-layer-1,#fff);box-shadow:0 1px 6px color-mix(in srgb,var(--dsw-alias-brand-primary,#4d6bfe) 35%,transparent)}
|
|
653
|
+
.fv-git-v-resizer:hover .fv-git-v-resizer-grip,.fv-git-v-resizer[data-dragging="true"] .fv-git-v-resizer-grip,.fv-git-v-resizer-btn:hover .fv-git-v-resizer-grip{background:var(--dsw-alias-brand-primary,#4d6bfe);box-shadow:0 -3px 0 0 var(--dsw-alias-brand-primary,#4d6bfe)}
|
|
654
|
+
.fv-git-v-resizer:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4d6bfe);outline-offset:-1px}
|
|
635
655
|
.fv-git-change-group{display:flex;flex-direction:column;gap:1px}
|
|
636
656
|
.fv-git-refresh{width:22px;height:22px;margin-left:auto}
|
|
637
657
|
.fv-commit-diff{display:flex;flex:1 1 auto;flex-direction:column;gap:2px;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:4px 6px}
|
|
@@ -837,7 +857,8 @@ window.__ModuleLoader__.load({
|
|
|
837
857
|
.fv-git-more{height:38px!important;font-size:13px!important}
|
|
838
858
|
.fv-commit-file{min-height:42px!important;font-size:13.5px!important}
|
|
839
859
|
.fv-commit-path{font-size:12px!important}
|
|
840
|
-
.fv-git-changes{max-height:none!important}
|
|
860
|
+
.fv-git-changes{height:auto!important;max-height:none!important}
|
|
861
|
+
.fv-git-v-resizer{display:none!important}
|
|
841
862
|
.fv-commit-diff-file .fv-diff-table{font-size:12px!important}
|
|
842
863
|
}
|
|
843
864
|
`;
|
|
@@ -1144,6 +1165,27 @@ window.__ModuleLoader__.load({
|
|
|
1144
1165
|
} catch { /* storage is unavailable */ }
|
|
1145
1166
|
}
|
|
1146
1167
|
|
|
1168
|
+
function readGitChangesHeight() {
|
|
1169
|
+
try {
|
|
1170
|
+
const raw = globalThis.localStorage?.getItem(GIT_CHANGES_HEIGHT_KEY);
|
|
1171
|
+
const parsed = Number(raw);
|
|
1172
|
+
if (Number.isFinite(parsed) && parsed >= MIN_GIT_CHANGES_HEIGHT && parsed <= MAX_GIT_CHANGES_HEIGHT) {
|
|
1173
|
+
return Math.round(parsed);
|
|
1174
|
+
}
|
|
1175
|
+
} catch { /* storage is unavailable */ }
|
|
1176
|
+
return DEFAULT_GIT_CHANGES_HEIGHT;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
function writeGitChangesHeight(height) {
|
|
1180
|
+
try {
|
|
1181
|
+
if (typeof height === "number" && Number.isFinite(height)) {
|
|
1182
|
+
globalThis.localStorage?.setItem(GIT_CHANGES_HEIGHT_KEY, String(Math.round(height)));
|
|
1183
|
+
} else {
|
|
1184
|
+
globalThis.localStorage?.removeItem(GIT_CHANGES_HEIGHT_KEY);
|
|
1185
|
+
}
|
|
1186
|
+
} catch { /* storage is unavailable */ }
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1147
1189
|
/**
|
|
1148
1190
|
* Read the per-file language overrides.
|
|
1149
1191
|
*
|
|
@@ -2306,10 +2348,72 @@ window.__ModuleLoader__.load({
|
|
|
2306
2348
|
const canCheckout = repo !== null && viewingBranch !== "" && viewingBranch !== current
|
|
2307
2349
|
&& checkoutable && state.busy !== true;
|
|
2308
2350
|
|
|
2351
|
+
const [changesHeight, setChangesHeight] = react.useState(readGitChangesHeight);
|
|
2352
|
+
const [isResizingChanges, setIsResizingChanges] = react.useState(false);
|
|
2353
|
+
const changesResizeRef = react.useRef(null);
|
|
2354
|
+
const changesHeightRef = react.useRef(changesHeight);
|
|
2355
|
+
changesHeightRef.current = changesHeight;
|
|
2356
|
+
|
|
2357
|
+
const onChangesResizerPointerDown = (event) => {
|
|
2358
|
+
if (event.button !== 0 && event.button !== undefined) return;
|
|
2359
|
+
event.preventDefault();
|
|
2360
|
+
event.stopPropagation();
|
|
2361
|
+
const startY = event.clientY;
|
|
2362
|
+
const startH = changesHeightRef.current;
|
|
2363
|
+
changesResizeRef.current = { startY, startH };
|
|
2364
|
+
setIsResizingChanges(true);
|
|
2365
|
+
try { event.currentTarget.setPointerCapture(event.pointerId); } catch (_) {}
|
|
2366
|
+
};
|
|
2367
|
+
|
|
2368
|
+
const onChangesResizerPointerMove = (event) => {
|
|
2369
|
+
if (!changesResizeRef.current) return;
|
|
2370
|
+
const { startY, startH } = changesResizeRef.current;
|
|
2371
|
+
const deltaY = event.clientY - startY;
|
|
2372
|
+
const panelEl = event.currentTarget.closest(".fv-git-panel");
|
|
2373
|
+
const panelHeight = panelEl ? panelEl.clientHeight : 600;
|
|
2374
|
+
const maxBound = Math.max(MIN_GIT_CHANGES_HEIGHT, panelHeight - 140);
|
|
2375
|
+
const nextH = Math.round(Math.min(Math.min(MAX_GIT_CHANGES_HEIGHT, maxBound), Math.max(MIN_GIT_CHANGES_HEIGHT, startH + deltaY)));
|
|
2376
|
+
setChangesHeight(nextH);
|
|
2377
|
+
};
|
|
2378
|
+
|
|
2379
|
+
const onChangesResizerPointerUp = (event) => {
|
|
2380
|
+
if (!changesResizeRef.current) return;
|
|
2381
|
+
changesResizeRef.current = null;
|
|
2382
|
+
setIsResizingChanges(false);
|
|
2383
|
+
try { event.currentTarget.releasePointerCapture(event.pointerId); } catch (_) {}
|
|
2384
|
+
writeGitChangesHeight(changesHeightRef.current);
|
|
2385
|
+
};
|
|
2386
|
+
|
|
2387
|
+
const onChangesResizerDoubleClick = (event) => {
|
|
2388
|
+
event.preventDefault();
|
|
2389
|
+
event.stopPropagation();
|
|
2390
|
+
setChangesHeight(DEFAULT_GIT_CHANGES_HEIGHT);
|
|
2391
|
+
writeGitChangesHeight(DEFAULT_GIT_CHANGES_HEIGHT);
|
|
2392
|
+
};
|
|
2393
|
+
|
|
2394
|
+
const onChangesResizerKeyDown = (event) => {
|
|
2395
|
+
if (event.key === "ArrowUp") {
|
|
2396
|
+
event.preventDefault();
|
|
2397
|
+
const nextH = Math.max(MIN_GIT_CHANGES_HEIGHT, changesHeightRef.current - 16);
|
|
2398
|
+
setChangesHeight(nextH);
|
|
2399
|
+
writeGitChangesHeight(nextH);
|
|
2400
|
+
} else if (event.key === "ArrowDown") {
|
|
2401
|
+
event.preventDefault();
|
|
2402
|
+
const nextH = Math.min(MAX_GIT_CHANGES_HEIGHT, changesHeightRef.current + 16);
|
|
2403
|
+
setChangesHeight(nextH);
|
|
2404
|
+
writeGitChangesHeight(nextH);
|
|
2405
|
+
} else if (event.key === "Home" || event.key === "Enter") {
|
|
2406
|
+
event.preventDefault();
|
|
2407
|
+
setChangesHeight(DEFAULT_GIT_CHANGES_HEIGHT);
|
|
2408
|
+
writeGitChangesHeight(DEFAULT_GIT_CHANGES_HEIGHT);
|
|
2409
|
+
}
|
|
2410
|
+
};
|
|
2411
|
+
|
|
2309
2412
|
const wrap = (...children) => react.createElement("div", {
|
|
2310
2413
|
className: "fv-tree fv-git-panel",
|
|
2311
2414
|
role: "group",
|
|
2312
2415
|
"aria-label": "Git 面板",
|
|
2416
|
+
"data-v-resizing": isResizingChanges ? "true" : "false",
|
|
2313
2417
|
style: typeof width === "number" ? { width: width + "px" } : undefined,
|
|
2314
2418
|
}, header, ...children);
|
|
2315
2419
|
|
|
@@ -2447,7 +2551,14 @@ window.__ModuleLoader__.load({
|
|
|
2447
2551
|
: null);
|
|
2448
2552
|
}
|
|
2449
2553
|
|
|
2450
|
-
const changesSection = react.createElement("div", {
|
|
2554
|
+
const changesSection = react.createElement("div", {
|
|
2555
|
+
className: "fv-git-changes",
|
|
2556
|
+
style: {
|
|
2557
|
+
height: changesHeight + "px",
|
|
2558
|
+
maxHeight: "none",
|
|
2559
|
+
flex: "none",
|
|
2560
|
+
},
|
|
2561
|
+
},
|
|
2451
2562
|
react.createElement("div", { className: "fv-git-section" },
|
|
2452
2563
|
react.createElement("span", null, "工作区改动"),
|
|
2453
2564
|
react.createElement("span", { className: "fv-git-count" },
|
|
@@ -2461,6 +2572,37 @@ window.__ModuleLoader__.load({
|
|
|
2461
2572
|
}, react.createElement(SvgIconRefresh, { size: 13 }))),
|
|
2462
2573
|
changesBody);
|
|
2463
2574
|
|
|
2575
|
+
const vResizer = react.createElement("div", {
|
|
2576
|
+
className: "fv-git-v-resizer",
|
|
2577
|
+
role: "separator",
|
|
2578
|
+
tabIndex: 0,
|
|
2579
|
+
"aria-orientation": "horizontal",
|
|
2580
|
+
"aria-label": "调整工作区改动与提交记录高度",
|
|
2581
|
+
"aria-valuenow": changesHeight,
|
|
2582
|
+
"aria-valuemin": MIN_GIT_CHANGES_HEIGHT,
|
|
2583
|
+
"aria-valuemax": MAX_GIT_CHANGES_HEIGHT,
|
|
2584
|
+
title: "拖动调整区域高度,双击恢复默认",
|
|
2585
|
+
"data-dragging": isResizingChanges ? "true" : "false",
|
|
2586
|
+
onPointerDown: onChangesResizerPointerDown,
|
|
2587
|
+
onPointerMove: onChangesResizerPointerMove,
|
|
2588
|
+
onPointerUp: onChangesResizerPointerUp,
|
|
2589
|
+
onPointerCancel: onChangesResizerPointerUp,
|
|
2590
|
+
onDoubleClick: onChangesResizerDoubleClick,
|
|
2591
|
+
onKeyDown: onChangesResizerKeyDown,
|
|
2592
|
+
},
|
|
2593
|
+
react.createElement("div", { className: "fv-git-v-resizer-line" }),
|
|
2594
|
+
react.createElement("button", {
|
|
2595
|
+
type: "button",
|
|
2596
|
+
tabIndex: -1,
|
|
2597
|
+
className: "fv-git-v-resizer-btn",
|
|
2598
|
+
"aria-label": "拖动手柄",
|
|
2599
|
+
title: "按住拖动调整高度,双击恢复默认",
|
|
2600
|
+
"data-dragging": isResizingChanges ? "true" : "false",
|
|
2601
|
+
},
|
|
2602
|
+
react.createElement("span", { className: "fv-git-v-resizer-grip" })
|
|
2603
|
+
)
|
|
2604
|
+
);
|
|
2605
|
+
|
|
2464
2606
|
const commitRows = commits.map((commit) => react.createElement("button", {
|
|
2465
2607
|
key: commit.sha,
|
|
2466
2608
|
type: "button",
|
|
@@ -2507,6 +2649,7 @@ window.__ModuleLoader__.load({
|
|
|
2507
2649
|
|
|
2508
2650
|
return wrap(toolbar,
|
|
2509
2651
|
changesSection,
|
|
2652
|
+
vResizer,
|
|
2510
2653
|
react.createElement("div", { className: "fv-git-section" },
|
|
2511
2654
|
react.createElement("span", null, "提交记录"),
|
|
2512
2655
|
react.createElement("span", { className: "fv-git-count" }, state.total > 0 ? String(state.total) : "")),
|
|
@@ -2608,7 +2751,7 @@ window.__ModuleLoader__.load({
|
|
|
2608
2751
|
*
|
|
2609
2752
|
* @param {{state: object, path: string}} props - panel snapshot and the file to diff.
|
|
2610
2753
|
*/
|
|
2611
|
-
function WorkspaceChangeDetail({ state, path: filePath, request, repo }) {
|
|
2754
|
+
function WorkspaceChangeDetail({ state, path: filePath, request, repo, backControl }) {
|
|
2612
2755
|
const [diff, setDiff] = react.useState({ status: "idle", data: null, error: null });
|
|
2613
2756
|
// A change can be staged, unstaged, or both. The reader picks which side
|
|
2614
2757
|
// to look at instead of the pane guessing.
|
|
@@ -2660,6 +2803,7 @@ window.__ModuleLoader__.load({
|
|
|
2660
2803
|
|
|
2661
2804
|
return react.createElement("div", { className: "fv-commit" },
|
|
2662
2805
|
react.createElement("div", { className: "fv-commit-head" },
|
|
2806
|
+
backControl ? react.createElement("div", { style: { marginBottom: "6px" } }, backControl) : null,
|
|
2663
2807
|
react.createElement("span", { className: "fv-commit-subject" }, filePath || "工作区改动"),
|
|
2664
2808
|
react.createElement("span", { className: "fv-commit-meta" },
|
|
2665
2809
|
row !== null
|
|
@@ -2687,7 +2831,7 @@ window.__ModuleLoader__.load({
|
|
|
2687
2831
|
* second viewer; a row that lands outside the current root stays visible
|
|
2688
2832
|
* but disabled rather than silently disappearing.
|
|
2689
2833
|
*/
|
|
2690
|
-
function CommitDetail({ state, selectedPath, onSelectFile, view, onSwitchView, onLoadDiff }) {
|
|
2834
|
+
function CommitDetail({ state, selectedPath, onSelectFile, view, onSwitchView, onLoadDiff, backControl }) {
|
|
2691
2835
|
if (state.detailStatus === "loading" || state.detailStatus === "idle") {
|
|
2692
2836
|
return react.createElement("div", { className: "fv-note" }, "正在读取提交详情…");
|
|
2693
2837
|
}
|
|
@@ -2733,27 +2877,29 @@ window.__ModuleLoader__.load({
|
|
|
2733
2877
|
// Two views of one commit: which files changed, and what changed inside
|
|
2734
2878
|
// them. Both come from the same revision pair, so they cannot disagree.
|
|
2735
2879
|
const tabs = react.createElement("div", { className: "fv-sheet-tabs", role: "tablist" },
|
|
2736
|
-
react.createElement("
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2880
|
+
react.createElement("div", { className: "fv-sheet-tabs-start" },
|
|
2881
|
+
backControl,
|
|
2882
|
+
react.createElement("button", {
|
|
2883
|
+
type: "button", className: "fv-tab", "aria-selected": view !== "diff" ? "true" : "false",
|
|
2884
|
+
onClick: () => onSwitchView("files"),
|
|
2885
|
+
}, "变更文件", react.createElement("span", { className: "fv-git-count" }, String(files.length))),
|
|
2886
|
+
react.createElement("button", {
|
|
2887
|
+
type: "button", className: "fv-tab fv-tab-diff", "aria-selected": view === "diff" ? "true" : "false",
|
|
2888
|
+
onClick: () => {
|
|
2889
|
+
onSwitchView("diff");
|
|
2890
|
+
if (onLoadDiff !== undefined) onLoadDiff();
|
|
2891
|
+
},
|
|
2892
|
+
}, "改动内容",
|
|
2893
|
+
state.commitDiff !== null && state.commitDiff !== undefined && state.commitDiff.summary !== undefined
|
|
2894
|
+
? react.createElement("span", { className: "fv-diff-stat" },
|
|
2895
|
+
state.commitDiff.summary.added > 0
|
|
2896
|
+
? react.createElement("span", { className: "fv-diff-add" }, "+" + state.commitDiff.summary.added)
|
|
2897
|
+
: null,
|
|
2898
|
+
state.commitDiff.summary.added > 0 && state.commitDiff.summary.removed > 0 ? " " : "",
|
|
2899
|
+
state.commitDiff.summary.removed > 0
|
|
2900
|
+
? react.createElement("span", { className: "fv-diff-del" }, "-" + state.commitDiff.summary.removed)
|
|
2901
|
+
: null)
|
|
2902
|
+
: null)));
|
|
2757
2903
|
|
|
2758
2904
|
let patchBody;
|
|
2759
2905
|
if (state.commitDiffStatus === "loading" || state.commitDiffStatus === "idle") {
|
|
@@ -3343,7 +3489,20 @@ window.__ModuleLoader__.load({
|
|
|
3343
3489
|
return react.createElement(react.Fragment, null,
|
|
3344
3490
|
react.createElement("div", {
|
|
3345
3491
|
className: "fv-context-backdrop",
|
|
3492
|
+
onPointerDown: (event) => {
|
|
3493
|
+
event.preventDefault();
|
|
3494
|
+
event.stopPropagation();
|
|
3495
|
+
if (Date.now() - mountTimeRef.current < 400) return;
|
|
3496
|
+
onClose();
|
|
3497
|
+
},
|
|
3498
|
+
onMouseDown: (event) => {
|
|
3499
|
+
event.preventDefault();
|
|
3500
|
+
event.stopPropagation();
|
|
3501
|
+
if (Date.now() - mountTimeRef.current < 400) return;
|
|
3502
|
+
onClose();
|
|
3503
|
+
},
|
|
3346
3504
|
onClick: (event) => {
|
|
3505
|
+
event.preventDefault();
|
|
3347
3506
|
event.stopPropagation();
|
|
3348
3507
|
if (Date.now() - mountTimeRef.current < 400) return;
|
|
3349
3508
|
onClose();
|
|
@@ -3938,7 +4097,7 @@ window.__ModuleLoader__.load({
|
|
|
3938
4097
|
|
|
3939
4098
|
/** Line-numbered diff viewer with dual line number gutters. */
|
|
3940
4099
|
/** Text, Markdown and JSON: one windowed read, three renderings. */
|
|
3941
|
-
function TextView({ meta, root, wrap, lang, commit, repo }) {
|
|
4100
|
+
function TextView({ meta, root, wrap, lang, commit, repo, backControl, sizeControl, langControl }) {
|
|
3942
4101
|
// Callers mount this with a per-file key, so paging state starts fresh
|
|
3943
4102
|
// for every file instead of leaking the previous file's window into the
|
|
3944
4103
|
// first request.
|
|
@@ -4218,11 +4377,22 @@ window.__ModuleLoader__.load({
|
|
|
4218
4377
|
}, "源码")
|
|
4219
4378
|
: null;
|
|
4220
4379
|
|
|
4221
|
-
const
|
|
4380
|
+
const tabButtons = showTabs
|
|
4381
|
+
? [...(fromCommit && diffTab !== null
|
|
4382
|
+
? [diffTab, contentTab, sourceTab]
|
|
4383
|
+
: [contentTab, sourceTab, diffTab])].filter((tab) => tab !== null)
|
|
4384
|
+
: [];
|
|
4385
|
+
|
|
4386
|
+
const tabs = showTabs || Boolean(backControl || langControl || sizeControl)
|
|
4222
4387
|
? react.createElement("div", { className: "fv-sheet-tabs", role: "tablist" },
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4388
|
+
react.createElement("div", { className: "fv-sheet-tabs-start" },
|
|
4389
|
+
backControl,
|
|
4390
|
+
...tabButtons),
|
|
4391
|
+
(sizeControl || langControl)
|
|
4392
|
+
? react.createElement("div", { className: "fv-sheet-tabs-end" },
|
|
4393
|
+
sizeControl,
|
|
4394
|
+
langControl)
|
|
4395
|
+
: null)
|
|
4226
4396
|
: null;
|
|
4227
4397
|
|
|
4228
4398
|
|
|
@@ -4289,32 +4459,38 @@ window.__ModuleLoader__.load({
|
|
|
4289
4459
|
}
|
|
4290
4460
|
|
|
4291
4461
|
/** Pick the viewer for a file and keep unsupported formats useful. */
|
|
4292
|
-
function FileView({ meta, root, api, wrap, lang, onDownload, commit, repo, loading }) {
|
|
4462
|
+
function FileView({ meta, root, api, wrap, lang, onDownload, commit, repo, loading, backControl, sizeControl, langControl }) {
|
|
4293
4463
|
if (meta === null) {
|
|
4294
4464
|
// A tab exists but its metadata is still in flight. Saying "pick a
|
|
4295
4465
|
// file" there would be a lie, and the reader would click again.
|
|
4296
|
-
return react.createElement("div", { className: "fv-
|
|
4297
|
-
|
|
4466
|
+
return react.createElement("div", { className: "fv-empty-pane" },
|
|
4467
|
+
backControl ? react.createElement("div", { className: "fv-sheet-tabs", role: "toolbar" }, backControl) : null,
|
|
4468
|
+
react.createElement("div", { className: "fv-note" },
|
|
4469
|
+
loading === true ? "正在读取文件…" : "从左侧选择一个文件。"));
|
|
4298
4470
|
}
|
|
4299
4471
|
if (meta.tooLarge || meta.kind === "binary") {
|
|
4300
|
-
return react.createElement("div", { className: "fv-
|
|
4301
|
-
react.createElement("
|
|
4302
|
-
|
|
4303
|
-
?
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
?
|
|
4472
|
+
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),
|
|
4475
|
+
sizeControl ? react.createElement("div", { className: "fv-sheet-tabs-end" }, sizeControl) : null) : null,
|
|
4476
|
+
react.createElement("div", { className: "fv-note" },
|
|
4477
|
+
react.createElement("strong", null, meta.name),
|
|
4478
|
+
react.createElement("span", null, meta.tooLarge
|
|
4479
|
+
? `文件大小 ${formatBytes(meta.size)},超过查看器上限 ${formatBytes(meta.limit)}。`
|
|
4480
|
+
: "这是二进制文件,查看器无法显示其内容。"),
|
|
4481
|
+
react.createElement("div", { style: { display: "flex", flexWrap: "wrap", gap: "8px", marginTop: "4px" } },
|
|
4482
|
+
api && api.host
|
|
4483
|
+
? react.createElement("button", {
|
|
4484
|
+
type: "button",
|
|
4485
|
+
className: "fv-button",
|
|
4486
|
+
onClick: () => api.host.openPath({ path: meta.displayPath }),
|
|
4487
|
+
}, "用本地程序打开")
|
|
4488
|
+
: null,
|
|
4489
|
+
react.createElement("button", {
|
|
4308
4490
|
type: "button",
|
|
4309
|
-
className: "fv-button",
|
|
4310
|
-
onClick: () =>
|
|
4311
|
-
}, "
|
|
4312
|
-
: null,
|
|
4313
|
-
react.createElement("button", {
|
|
4314
|
-
type: "button",
|
|
4315
|
-
className: "fv-button fv-btn-download-note",
|
|
4316
|
-
onClick: () => onDownload && onDownload(meta),
|
|
4317
|
-
}, "下载到本地")));
|
|
4491
|
+
className: "fv-button fv-btn-download-note",
|
|
4492
|
+
onClick: () => onDownload && onDownload(meta),
|
|
4493
|
+
}, "下载到本地"))));
|
|
4318
4494
|
}
|
|
4319
4495
|
// The key remounts each viewer per file so no per-file state (paging,
|
|
4320
4496
|
// active sheet, blob URL) survives a switch.
|
|
@@ -4329,21 +4505,28 @@ window.__ModuleLoader__.load({
|
|
|
4329
4505
|
"以下内容来自提交 " + commit.slice(0, 7) + (meta.change ? ",该文件本次为「" + meta.change + "」" : "")),
|
|
4330
4506
|
react.createElement("span", { className: "fv-history-hint" }, "这是历史版本,不是工作区当前内容。"))
|
|
4331
4507
|
: null;
|
|
4508
|
+
|
|
4509
|
+
const headBar = (backControl || sizeControl)
|
|
4510
|
+
? react.createElement("div", { className: "fv-sheet-tabs", role: "toolbar" },
|
|
4511
|
+
react.createElement("div", { className: "fv-sheet-tabs-start" }, backControl),
|
|
4512
|
+
sizeControl ? react.createElement("div", { className: "fv-sheet-tabs-end" }, sizeControl) : null)
|
|
4513
|
+
: null;
|
|
4514
|
+
|
|
4332
4515
|
if (fromHistory && meta.kind !== "text" && meta.kind !== "markdown" && meta.kind !== "json") {
|
|
4333
|
-
return react.createElement(react.Fragment, null, banner,
|
|
4516
|
+
return react.createElement(react.Fragment, null, headBar, banner,
|
|
4334
4517
|
react.createElement("div", { className: "fv-note" },
|
|
4335
4518
|
"该提交中的文件是 " + meta.kind + " 格式,历史版本暂不支持预览。"));
|
|
4336
4519
|
}
|
|
4337
|
-
if (meta.kind === "image") return react.createElement(ImageView, { key, meta, root });
|
|
4338
|
-
if (meta.kind === "pdf") return react.createElement(BinaryView, { key, meta, root });
|
|
4520
|
+
if (meta.kind === "image") return react.createElement(react.Fragment, null, headBar, react.createElement(ImageView, { key, meta, root }));
|
|
4521
|
+
if (meta.kind === "pdf") return react.createElement(react.Fragment, null, headBar, react.createElement(BinaryView, { key, meta, root }));
|
|
4339
4522
|
if (meta.kind === "sheet") {
|
|
4340
|
-
return react.createElement("div", { className: "fv-content" }, react.createElement(SheetView, { key, meta, root }));
|
|
4523
|
+
return react.createElement("div", { className: "fv-content" }, headBar, react.createElement(SheetView, { key, meta, root }));
|
|
4341
4524
|
}
|
|
4342
4525
|
if (meta.kind === "doc") {
|
|
4343
|
-
return react.createElement("div", { className: "fv-content" }, react.createElement(DocView, { key, meta, root }));
|
|
4526
|
+
return react.createElement("div", { className: "fv-content" }, headBar, react.createElement(DocView, { key, meta, root }));
|
|
4344
4527
|
}
|
|
4345
4528
|
return react.createElement(react.Fragment, null, banner,
|
|
4346
|
-
react.createElement(TextView, { key, meta, root, wrap, lang, commit, repo }));
|
|
4529
|
+
react.createElement(TextView, { key, meta, root, wrap, lang, commit, repo, backControl, sizeControl, langControl }));
|
|
4347
4530
|
}
|
|
4348
4531
|
|
|
4349
4532
|
|
|
@@ -4421,7 +4604,20 @@ window.__ModuleLoader__.load({
|
|
|
4421
4604
|
return react.createElement(react.Fragment, null,
|
|
4422
4605
|
react.createElement("div", {
|
|
4423
4606
|
className: "fv-context-backdrop",
|
|
4607
|
+
onPointerDown: (e) => {
|
|
4608
|
+
e.preventDefault();
|
|
4609
|
+
e.stopPropagation();
|
|
4610
|
+
if (Date.now() - mountTimeRef.current < 400) return;
|
|
4611
|
+
onClose();
|
|
4612
|
+
},
|
|
4613
|
+
onMouseDown: (e) => {
|
|
4614
|
+
e.preventDefault();
|
|
4615
|
+
e.stopPropagation();
|
|
4616
|
+
if (Date.now() - mountTimeRef.current < 400) return;
|
|
4617
|
+
onClose();
|
|
4618
|
+
},
|
|
4424
4619
|
onClick: (e) => {
|
|
4620
|
+
e.preventDefault();
|
|
4425
4621
|
e.stopPropagation();
|
|
4426
4622
|
if (Date.now() - mountTimeRef.current < 400) return;
|
|
4427
4623
|
onClose();
|
|
@@ -4836,6 +5032,15 @@ window.__ModuleLoader__.load({
|
|
|
4836
5032
|
// it. Kept apart from the tree's `contextMenu`: the two menus describe
|
|
4837
5033
|
// different subjects and must never be confused for one another.
|
|
4838
5034
|
const [tabMenu, setTabMenu] = react.useState(null);
|
|
5035
|
+
const lastMenuClosedAtRef = react.useRef(0);
|
|
5036
|
+
const closeContextMenu = react.useCallback(() => {
|
|
5037
|
+
lastMenuClosedAtRef.current = Date.now();
|
|
5038
|
+
setContextMenu(null);
|
|
5039
|
+
}, []);
|
|
5040
|
+
const closeTabMenu = react.useCallback(() => {
|
|
5041
|
+
lastMenuClosedAtRef.current = Date.now();
|
|
5042
|
+
setTabMenu(null);
|
|
5043
|
+
}, []);
|
|
4839
5044
|
const tabsRef = react.useRef(null);
|
|
4840
5045
|
tabsRef.current = tabs;
|
|
4841
5046
|
const activeTabIdRef = react.useRef("");
|
|
@@ -5747,6 +5952,36 @@ window.__ModuleLoader__.load({
|
|
|
5747
5952
|
performDownload(request, root, targetPath, targetName, showToast);
|
|
5748
5953
|
};
|
|
5749
5954
|
|
|
5955
|
+
const originKind = previewOrigin === null || previewOrigin === undefined ? null : previewOrigin.kind;
|
|
5956
|
+
const backTarget = originKind === "commit"
|
|
5957
|
+
? "返回变更文件列表"
|
|
5958
|
+
: (originKind === "changes" ? "返回工作区改动列表" : "返回文件列表");
|
|
5959
|
+
const canGoBack = isMobile || pane === "content"
|
|
5960
|
+
? (previewOrigin !== null || meta !== null || showCommit)
|
|
5961
|
+
: false;
|
|
5962
|
+
const backControl = react.createElement("button", {
|
|
5963
|
+
type: "button",
|
|
5964
|
+
className: "fv-icon-button fv-btn-back",
|
|
5965
|
+
"aria-label": backTarget,
|
|
5966
|
+
title: backTarget,
|
|
5967
|
+
disabled: !canGoBack,
|
|
5968
|
+
onClick: goBack,
|
|
5969
|
+
}, react.createElement("span", { "aria-hidden": "true" }, "‹"));
|
|
5970
|
+
|
|
5971
|
+
const sizeControl = meta !== null && !showCommit && typeof meta.size === "number"
|
|
5972
|
+
? react.createElement("span", { className: "fv-main-size" }, formatBytes(meta.size))
|
|
5973
|
+
: null;
|
|
5974
|
+
|
|
5975
|
+
const langControl = meta !== null && !showCommit && !meta.tooLarge && (meta.kind === "text" || meta.kind === "markdown" || meta.kind === "json")
|
|
5976
|
+
? react.createElement(LanguagePicker, {
|
|
5977
|
+
choice: localeTagOfActive,
|
|
5978
|
+
catalog: languageCatalog,
|
|
5979
|
+
source: meta.langSource,
|
|
5980
|
+
resolved: meta.lang,
|
|
5981
|
+
onChange: chooseLang,
|
|
5982
|
+
})
|
|
5983
|
+
: null;
|
|
5984
|
+
|
|
5750
5985
|
return react.createElement("div", {
|
|
5751
5986
|
className: "fv-scrim",
|
|
5752
5987
|
role: "dialog",
|
|
@@ -5755,6 +5990,8 @@ window.__ModuleLoader__.load({
|
|
|
5755
5990
|
onMouseDown: (event) => {
|
|
5756
5991
|
if (event.target !== event.currentTarget) return;
|
|
5757
5992
|
if (Date.now() - openedAtRef.current < 400) return;
|
|
5993
|
+
if (contextMenu !== null || tabMenu !== null) return;
|
|
5994
|
+
if (Date.now() - lastMenuClosedAtRef.current < 400) return;
|
|
5758
5995
|
openStore.set(null);
|
|
5759
5996
|
},
|
|
5760
5997
|
},
|
|
@@ -5934,51 +6171,6 @@ window.__ModuleLoader__.load({
|
|
|
5934
6171
|
onContextMenu: (menu) => setTabMenu(menu),
|
|
5935
6172
|
menuFor: tabMenu === null || tabMenu === undefined ? "" : tabMenu.tabId,
|
|
5936
6173
|
}),
|
|
5937
|
-
react.createElement("div", { className: "fv-main-head" },
|
|
5938
|
-
(() => {
|
|
5939
|
-
// The control returns one step, so it names that step and
|
|
5940
|
-
// is disabled when there is nowhere to go — a button that
|
|
5941
|
-
// silently does nothing reads as broken.
|
|
5942
|
-
const originKind = previewOrigin === null || previewOrigin === undefined ? null : previewOrigin.kind;
|
|
5943
|
-
const backTarget = originKind === "commit"
|
|
5944
|
-
? "返回变更文件列表"
|
|
5945
|
-
: (originKind === "changes" ? "返回工作区改动列表" : "返回文件列表");
|
|
5946
|
-
// On a phone the panes are exclusive, so leaving the content
|
|
5947
|
-
// pane is always meaningful; on desktop the sidebar is
|
|
5948
|
-
// already visible and only an origin is worth returning to.
|
|
5949
|
-
const canGoBack = isMobile || pane === "content"
|
|
5950
|
-
? (previewOrigin !== null || meta !== null || showCommit)
|
|
5951
|
-
: false;
|
|
5952
|
-
return react.createElement("button", {
|
|
5953
|
-
type: "button",
|
|
5954
|
-
className: "fv-icon-button fv-btn-back",
|
|
5955
|
-
"aria-label": backTarget,
|
|
5956
|
-
title: backTarget,
|
|
5957
|
-
disabled: !canGoBack,
|
|
5958
|
-
onClick: goBack,
|
|
5959
|
-
}, react.createElement("span", { "aria-hidden": "true" }, "‹"));
|
|
5960
|
-
})(),
|
|
5961
|
-
react.createElement("span", { className: "fv-main-name" },
|
|
5962
|
-
commitHeadline !== ""
|
|
5963
|
-
? commitHeadline
|
|
5964
|
-
: (activeTab === null
|
|
5965
|
-
? "未选择文件"
|
|
5966
|
-
: (meta === null ? activeTab.title : meta.name))),
|
|
5967
|
-
meta !== null && !showCommit && typeof meta.size === "number"
|
|
5968
|
-
? react.createElement("span", null, formatBytes(meta.size))
|
|
5969
|
-
: null,
|
|
5970
|
-
// The language picker sits with the file it applies to, and
|
|
5971
|
-
// is offered only for source views: an image, sheet or
|
|
5972
|
-
// document has no grammar to choose.
|
|
5973
|
-
meta !== null && !showCommit && !meta.tooLarge && (meta.kind === "text" || meta.kind === "markdown" || meta.kind === "json")
|
|
5974
|
-
? react.createElement(LanguagePicker, {
|
|
5975
|
-
choice: localeTagOfActive,
|
|
5976
|
-
catalog: languageCatalog,
|
|
5977
|
-
source: meta.langSource,
|
|
5978
|
-
resolved: meta.lang,
|
|
5979
|
-
onChange: chooseLang,
|
|
5980
|
-
})
|
|
5981
|
-
: null),
|
|
5982
6174
|
error !== null && meta === null && !showCommit && activeTab === null
|
|
5983
6175
|
? react.createElement("div", { className: "fv-note fv-error" },
|
|
5984
6176
|
react.createElement("span", null, messageOf(error)),
|
|
@@ -6021,6 +6213,7 @@ window.__ModuleLoader__.load({
|
|
|
6021
6213
|
path: previewChangePath,
|
|
6022
6214
|
request,
|
|
6023
6215
|
repo: gitState.repoId,
|
|
6216
|
+
backControl,
|
|
6024
6217
|
})
|
|
6025
6218
|
: react.createElement(CommitDetail, {
|
|
6026
6219
|
state: gitState,
|
|
@@ -6036,6 +6229,7 @@ window.__ModuleLoader__.load({
|
|
|
6036
6229
|
const sha = commitSha;
|
|
6037
6230
|
if (sha !== "") git.loadCommitDiff(sha);
|
|
6038
6231
|
},
|
|
6232
|
+
backControl,
|
|
6039
6233
|
}))
|
|
6040
6234
|
: react.createElement(ErrorBoundary, {
|
|
6041
6235
|
// One viewer per tab (and per refresh epoch), so no
|
|
@@ -6053,6 +6247,9 @@ window.__ModuleLoader__.load({
|
|
|
6053
6247
|
onDownload: handleDownloadItem,
|
|
6054
6248
|
commit: "",
|
|
6055
6249
|
repo: gitState.repoId,
|
|
6250
|
+
backControl,
|
|
6251
|
+
sizeControl,
|
|
6252
|
+
langControl,
|
|
6056
6253
|
})
|
|
6057
6254
|
: react.createElement(FileView, {
|
|
6058
6255
|
meta,
|
|
@@ -6068,12 +6265,15 @@ window.__ModuleLoader__.load({
|
|
|
6068
6265
|
// A tab is created before its metadata arrives, so the
|
|
6069
6266
|
// pane has to say "reading" instead of "pick a file".
|
|
6070
6267
|
loading: activeTab.status === "loading",
|
|
6268
|
+
backControl,
|
|
6269
|
+
sizeControl,
|
|
6270
|
+
langControl,
|
|
6071
6271
|
})))),
|
|
6072
6272
|
contextMenu !== null
|
|
6073
6273
|
? react.createElement(ContextMenu, {
|
|
6074
6274
|
menu: contextMenu,
|
|
6075
6275
|
rootPath: activeRoot ? (activeRoot.path || activeRoot.id) : root,
|
|
6076
|
-
onClose:
|
|
6276
|
+
onClose: closeContextMenu,
|
|
6077
6277
|
onMention: mention,
|
|
6078
6278
|
onToast: showToast,
|
|
6079
6279
|
onDownload: handleDownloadItem,
|
|
@@ -6086,7 +6286,7 @@ window.__ModuleLoader__.load({
|
|
|
6086
6286
|
? react.createElement(TabContextMenu, {
|
|
6087
6287
|
menu: tabMenu,
|
|
6088
6288
|
tabs,
|
|
6089
|
-
onClose:
|
|
6289
|
+
onClose: closeTabMenu,
|
|
6090
6290
|
onAction: onTabAction,
|
|
6091
6291
|
})
|
|
6092
6292
|
: null,
|
|
@@ -6400,6 +6600,7 @@ window.__ModuleLoader__.load({
|
|
|
6400
6600
|
mediaTypeOf, messageOf, createStore, createRequest, blobOf, ensureStyles,
|
|
6401
6601
|
ENTRY_POSITION_KEY, DRAG_SLOP, settleEntry, readEntryPosition, writeEntryPosition,
|
|
6402
6602
|
TREE_WIDTH_KEY, DEFAULT_TREE_WIDTH, MIN_TREE_WIDTH, MAX_TREE_WIDTH, readTreeWidth, writeTreeWidth,
|
|
6603
|
+
GIT_CHANGES_HEIGHT_KEY, DEFAULT_GIT_CHANGES_HEIGHT, MIN_GIT_CHANGES_HEIGHT, MAX_GIT_CHANGES_HEIGHT, readGitChangesHeight, writeGitChangesHeight,
|
|
6403
6604
|
SHELL_WIDTH_KEY, MIN_SHELL_WIDTH, readShellWidth, writeShellWidth, clampShellWidth,
|
|
6404
6605
|
LANG_OVERRIDES_KEY, LANG_OVERRIDES_MAX, readLangOverrides, writeLangOverrides, readLangOverride, writeLangOverride,
|
|
6405
6606
|
langOverrideKey, isKnownLanguageId, setLanguageCatalog, getLanguageCatalog: () => languageCatalog, LanguagePicker,
|
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.12",
|
|
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",
|