@weniatall/dsh-file-viewer 0.3.8 → 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 +328 -21
- 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) {
|
|
@@ -348,6 +363,10 @@ window.__ModuleLoader__.load({
|
|
|
348
363
|
const DEFAULT_TREE_WIDTH = 280;
|
|
349
364
|
const MIN_TREE_WIDTH = 160;
|
|
350
365
|
const MAX_TREE_WIDTH = 700;
|
|
366
|
+
const GIT_CHANGES_HEIGHT_KEY = "dsh-file-viewer.git-changes-height";
|
|
367
|
+
const DEFAULT_GIT_CHANGES_HEIGHT = 180;
|
|
368
|
+
const MIN_GIT_CHANGES_HEIGHT = 64;
|
|
369
|
+
const MAX_GIT_CHANGES_HEIGHT = 800;
|
|
351
370
|
/**
|
|
352
371
|
* Smallest width the drawer may be dragged to.
|
|
353
372
|
*
|
|
@@ -387,7 +406,7 @@ window.__ModuleLoader__.load({
|
|
|
387
406
|
body>div[role="presentation"]:has(.fv-scrim)>[aria-hidden="true"]{display:none!important}
|
|
388
407
|
.fv-scrim[role="dialog"]{position:absolute!important;inset:0!important;width:auto!important;max-width:none!important;max-height:none!important;min-height:0!important;margin:0!important;border:none!important;border-radius:0!important;box-shadow:none!important;background:transparent!important;-webkit-backdrop-filter:none!important;backdrop-filter:none!important;overflow:visible!important}
|
|
389
408
|
.fv-scrim{position:absolute;inset:0;z-index:40;display:flex;align-items:stretch;justify-content:flex-end;background:transparent;pointer-events:auto;animation:fv-fade .16s ease-out}
|
|
390
|
-
.fv-shell{display:flex;flex-direction:column;width:min(64vw,1100px);min-width:0;min-height:0;overflow:hidden;border-left:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-base,#fff));box-shadow:var(--dsw-shadow-lv3);font-family:var(--dsw-font-family);color:var(--dsw-alias-label-primary)
|
|
409
|
+
.fv-shell{display:flex;flex-direction:column;width:min(64vw,1100px);min-width:0;min-height:0;overflow:hidden;border-left:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-base,#fff));box-shadow:var(--dsw-shadow-lv3);font-family:var(--dsw-font-family);color:var(--dsw-alias-label-primary);animation:fv-slide .2s cubic-bezier(.2,.8,.2,1);transition:width .2s cubic-bezier(.2,.8,.2,1)}
|
|
391
410
|
.fv-shell[data-fullscreen="true"]{width:100vw;border-left:none}
|
|
392
411
|
/* The leading edge is the drawer's own resize handle. It is positioned
|
|
393
412
|
over the border rather than beside it so the panel does not shift when
|
|
@@ -553,6 +572,18 @@ window.__ModuleLoader__.load({
|
|
|
553
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}
|
|
554
573
|
.fv-tab[aria-selected="true"]{border-color:transparent;background:var(--dsw-alias-brand-primary,#4d6bfe);color:var(--dsw-alias-label-primary-foreground,#fff)}
|
|
555
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}
|
|
556
587
|
.fv-diff-stat{font-size:11px;font-weight:600;font-variant-numeric:tabular-nums;margin-left:2px}
|
|
557
588
|
.fv-diff-add{color:var(--dsw-alias-state-success-primary,#10b981)}
|
|
558
589
|
.fv-diff-del{color:var(--dsw-alias-state-error-primary,#ef4444)}
|
|
@@ -635,7 +666,19 @@ window.__ModuleLoader__.load({
|
|
|
635
666
|
.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}
|
|
636
667
|
/* Working-tree changes and a commit's patch share the commit-row visuals
|
|
637
668
|
so the two panels read as one surface. */
|
|
638
|
-
.fv-git-changes{display:flex;flex:none;flex-direction:column;gap:1px;
|
|
669
|
+
.fv-git-changes{display:flex;flex:none;flex-direction:column;gap:1px;min-height:48px;overflow-y:auto;overscroll-behavior:contain;padding-bottom:2px}
|
|
670
|
+
.fv-git-panel[data-v-resizing="true"]{cursor:row-resize!important;user-select:none!important}
|
|
671
|
+
.fv-git-panel[data-v-resizing="true"] *{pointer-events:none!important}
|
|
672
|
+
.fv-git-panel[data-v-resizing="true"] .fv-git-v-resizer,
|
|
673
|
+
.fv-git-panel[data-v-resizing="true"] .fv-git-v-resizer *{pointer-events:auto!important}
|
|
674
|
+
.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}
|
|
675
|
+
.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}
|
|
676
|
+
.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}
|
|
677
|
+
.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}
|
|
678
|
+
.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)}
|
|
679
|
+
.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)}
|
|
680
|
+
.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)}
|
|
681
|
+
.fv-git-v-resizer:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4d6bfe);outline-offset:-1px}
|
|
639
682
|
.fv-git-change-group{display:flex;flex-direction:column;gap:1px}
|
|
640
683
|
.fv-git-refresh{width:22px;height:22px;margin-left:auto}
|
|
641
684
|
.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}
|
|
@@ -841,7 +884,8 @@ window.__ModuleLoader__.load({
|
|
|
841
884
|
.fv-git-more{height:38px!important;font-size:13px!important}
|
|
842
885
|
.fv-commit-file{min-height:42px!important;font-size:13.5px!important}
|
|
843
886
|
.fv-commit-path{font-size:12px!important}
|
|
844
|
-
.fv-git-changes{max-height:none!important}
|
|
887
|
+
.fv-git-changes{height:auto!important;max-height:none!important}
|
|
888
|
+
.fv-git-v-resizer{display:none!important}
|
|
845
889
|
.fv-commit-diff-file .fv-diff-table{font-size:12px!important}
|
|
846
890
|
}
|
|
847
891
|
`;
|
|
@@ -1148,6 +1192,27 @@ window.__ModuleLoader__.load({
|
|
|
1148
1192
|
} catch { /* storage is unavailable */ }
|
|
1149
1193
|
}
|
|
1150
1194
|
|
|
1195
|
+
function readGitChangesHeight() {
|
|
1196
|
+
try {
|
|
1197
|
+
const raw = globalThis.localStorage?.getItem(GIT_CHANGES_HEIGHT_KEY);
|
|
1198
|
+
const parsed = Number(raw);
|
|
1199
|
+
if (Number.isFinite(parsed) && parsed >= MIN_GIT_CHANGES_HEIGHT && parsed <= MAX_GIT_CHANGES_HEIGHT) {
|
|
1200
|
+
return Math.round(parsed);
|
|
1201
|
+
}
|
|
1202
|
+
} catch { /* storage is unavailable */ }
|
|
1203
|
+
return DEFAULT_GIT_CHANGES_HEIGHT;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
function writeGitChangesHeight(height) {
|
|
1207
|
+
try {
|
|
1208
|
+
if (typeof height === "number" && Number.isFinite(height)) {
|
|
1209
|
+
globalThis.localStorage?.setItem(GIT_CHANGES_HEIGHT_KEY, String(Math.round(height)));
|
|
1210
|
+
} else {
|
|
1211
|
+
globalThis.localStorage?.removeItem(GIT_CHANGES_HEIGHT_KEY);
|
|
1212
|
+
}
|
|
1213
|
+
} catch { /* storage is unavailable */ }
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1151
1216
|
/**
|
|
1152
1217
|
* Read the per-file language overrides.
|
|
1153
1218
|
*
|
|
@@ -2310,10 +2375,72 @@ window.__ModuleLoader__.load({
|
|
|
2310
2375
|
const canCheckout = repo !== null && viewingBranch !== "" && viewingBranch !== current
|
|
2311
2376
|
&& checkoutable && state.busy !== true;
|
|
2312
2377
|
|
|
2378
|
+
const [changesHeight, setChangesHeight] = react.useState(readGitChangesHeight);
|
|
2379
|
+
const [isResizingChanges, setIsResizingChanges] = react.useState(false);
|
|
2380
|
+
const changesResizeRef = react.useRef(null);
|
|
2381
|
+
const changesHeightRef = react.useRef(changesHeight);
|
|
2382
|
+
changesHeightRef.current = changesHeight;
|
|
2383
|
+
|
|
2384
|
+
const onChangesResizerPointerDown = (event) => {
|
|
2385
|
+
if (event.button !== 0 && event.button !== undefined) return;
|
|
2386
|
+
event.preventDefault();
|
|
2387
|
+
event.stopPropagation();
|
|
2388
|
+
const startY = event.clientY;
|
|
2389
|
+
const startH = changesHeightRef.current;
|
|
2390
|
+
changesResizeRef.current = { startY, startH };
|
|
2391
|
+
setIsResizingChanges(true);
|
|
2392
|
+
try { event.currentTarget.setPointerCapture(event.pointerId); } catch (_) {}
|
|
2393
|
+
};
|
|
2394
|
+
|
|
2395
|
+
const onChangesResizerPointerMove = (event) => {
|
|
2396
|
+
if (!changesResizeRef.current) return;
|
|
2397
|
+
const { startY, startH } = changesResizeRef.current;
|
|
2398
|
+
const deltaY = event.clientY - startY;
|
|
2399
|
+
const panelEl = event.currentTarget.closest(".fv-git-panel");
|
|
2400
|
+
const panelHeight = panelEl ? panelEl.clientHeight : 600;
|
|
2401
|
+
const maxBound = Math.max(MIN_GIT_CHANGES_HEIGHT, panelHeight - 140);
|
|
2402
|
+
const nextH = Math.round(Math.min(Math.min(MAX_GIT_CHANGES_HEIGHT, maxBound), Math.max(MIN_GIT_CHANGES_HEIGHT, startH + deltaY)));
|
|
2403
|
+
setChangesHeight(nextH);
|
|
2404
|
+
};
|
|
2405
|
+
|
|
2406
|
+
const onChangesResizerPointerUp = (event) => {
|
|
2407
|
+
if (!changesResizeRef.current) return;
|
|
2408
|
+
changesResizeRef.current = null;
|
|
2409
|
+
setIsResizingChanges(false);
|
|
2410
|
+
try { event.currentTarget.releasePointerCapture(event.pointerId); } catch (_) {}
|
|
2411
|
+
writeGitChangesHeight(changesHeightRef.current);
|
|
2412
|
+
};
|
|
2413
|
+
|
|
2414
|
+
const onChangesResizerDoubleClick = (event) => {
|
|
2415
|
+
event.preventDefault();
|
|
2416
|
+
event.stopPropagation();
|
|
2417
|
+
setChangesHeight(DEFAULT_GIT_CHANGES_HEIGHT);
|
|
2418
|
+
writeGitChangesHeight(DEFAULT_GIT_CHANGES_HEIGHT);
|
|
2419
|
+
};
|
|
2420
|
+
|
|
2421
|
+
const onChangesResizerKeyDown = (event) => {
|
|
2422
|
+
if (event.key === "ArrowUp") {
|
|
2423
|
+
event.preventDefault();
|
|
2424
|
+
const nextH = Math.max(MIN_GIT_CHANGES_HEIGHT, changesHeightRef.current - 16);
|
|
2425
|
+
setChangesHeight(nextH);
|
|
2426
|
+
writeGitChangesHeight(nextH);
|
|
2427
|
+
} else if (event.key === "ArrowDown") {
|
|
2428
|
+
event.preventDefault();
|
|
2429
|
+
const nextH = Math.min(MAX_GIT_CHANGES_HEIGHT, changesHeightRef.current + 16);
|
|
2430
|
+
setChangesHeight(nextH);
|
|
2431
|
+
writeGitChangesHeight(nextH);
|
|
2432
|
+
} else if (event.key === "Home" || event.key === "Enter") {
|
|
2433
|
+
event.preventDefault();
|
|
2434
|
+
setChangesHeight(DEFAULT_GIT_CHANGES_HEIGHT);
|
|
2435
|
+
writeGitChangesHeight(DEFAULT_GIT_CHANGES_HEIGHT);
|
|
2436
|
+
}
|
|
2437
|
+
};
|
|
2438
|
+
|
|
2313
2439
|
const wrap = (...children) => react.createElement("div", {
|
|
2314
2440
|
className: "fv-tree fv-git-panel",
|
|
2315
2441
|
role: "group",
|
|
2316
2442
|
"aria-label": "Git 面板",
|
|
2443
|
+
"data-v-resizing": isResizingChanges ? "true" : "false",
|
|
2317
2444
|
style: typeof width === "number" ? { width: width + "px" } : undefined,
|
|
2318
2445
|
}, header, ...children);
|
|
2319
2446
|
|
|
@@ -2451,7 +2578,14 @@ window.__ModuleLoader__.load({
|
|
|
2451
2578
|
: null);
|
|
2452
2579
|
}
|
|
2453
2580
|
|
|
2454
|
-
const changesSection = react.createElement("div", {
|
|
2581
|
+
const changesSection = react.createElement("div", {
|
|
2582
|
+
className: "fv-git-changes",
|
|
2583
|
+
style: {
|
|
2584
|
+
height: changesHeight + "px",
|
|
2585
|
+
maxHeight: "none",
|
|
2586
|
+
flex: "none",
|
|
2587
|
+
},
|
|
2588
|
+
},
|
|
2455
2589
|
react.createElement("div", { className: "fv-git-section" },
|
|
2456
2590
|
react.createElement("span", null, "工作区改动"),
|
|
2457
2591
|
react.createElement("span", { className: "fv-git-count" },
|
|
@@ -2465,6 +2599,37 @@ window.__ModuleLoader__.load({
|
|
|
2465
2599
|
}, react.createElement(SvgIconRefresh, { size: 13 }))),
|
|
2466
2600
|
changesBody);
|
|
2467
2601
|
|
|
2602
|
+
const vResizer = react.createElement("div", {
|
|
2603
|
+
className: "fv-git-v-resizer",
|
|
2604
|
+
role: "separator",
|
|
2605
|
+
tabIndex: 0,
|
|
2606
|
+
"aria-orientation": "horizontal",
|
|
2607
|
+
"aria-label": "调整工作区改动与提交记录高度",
|
|
2608
|
+
"aria-valuenow": changesHeight,
|
|
2609
|
+
"aria-valuemin": MIN_GIT_CHANGES_HEIGHT,
|
|
2610
|
+
"aria-valuemax": MAX_GIT_CHANGES_HEIGHT,
|
|
2611
|
+
title: "拖动调整区域高度,双击恢复默认",
|
|
2612
|
+
"data-dragging": isResizingChanges ? "true" : "false",
|
|
2613
|
+
onPointerDown: onChangesResizerPointerDown,
|
|
2614
|
+
onPointerMove: onChangesResizerPointerMove,
|
|
2615
|
+
onPointerUp: onChangesResizerPointerUp,
|
|
2616
|
+
onPointerCancel: onChangesResizerPointerUp,
|
|
2617
|
+
onDoubleClick: onChangesResizerDoubleClick,
|
|
2618
|
+
onKeyDown: onChangesResizerKeyDown,
|
|
2619
|
+
},
|
|
2620
|
+
react.createElement("div", { className: "fv-git-v-resizer-line" }),
|
|
2621
|
+
react.createElement("button", {
|
|
2622
|
+
type: "button",
|
|
2623
|
+
tabIndex: -1,
|
|
2624
|
+
className: "fv-git-v-resizer-btn",
|
|
2625
|
+
"aria-label": "拖动手柄",
|
|
2626
|
+
title: "按住拖动调整高度,双击恢复默认",
|
|
2627
|
+
"data-dragging": isResizingChanges ? "true" : "false",
|
|
2628
|
+
},
|
|
2629
|
+
react.createElement("span", { className: "fv-git-v-resizer-grip" })
|
|
2630
|
+
)
|
|
2631
|
+
);
|
|
2632
|
+
|
|
2468
2633
|
const commitRows = commits.map((commit) => react.createElement("button", {
|
|
2469
2634
|
key: commit.sha,
|
|
2470
2635
|
type: "button",
|
|
@@ -2511,6 +2676,7 @@ window.__ModuleLoader__.load({
|
|
|
2511
2676
|
|
|
2512
2677
|
return wrap(toolbar,
|
|
2513
2678
|
changesSection,
|
|
2679
|
+
vResizer,
|
|
2514
2680
|
react.createElement("div", { className: "fv-git-section" },
|
|
2515
2681
|
react.createElement("span", null, "提交记录"),
|
|
2516
2682
|
react.createElement("span", { className: "fv-git-count" }, state.total > 0 ? String(state.total) : "")),
|
|
@@ -3350,7 +3516,20 @@ window.__ModuleLoader__.load({
|
|
|
3350
3516
|
return react.createElement(react.Fragment, null,
|
|
3351
3517
|
react.createElement("div", {
|
|
3352
3518
|
className: "fv-context-backdrop",
|
|
3519
|
+
onPointerDown: (event) => {
|
|
3520
|
+
event.preventDefault();
|
|
3521
|
+
event.stopPropagation();
|
|
3522
|
+
if (Date.now() - mountTimeRef.current < 400) return;
|
|
3523
|
+
onClose();
|
|
3524
|
+
},
|
|
3525
|
+
onMouseDown: (event) => {
|
|
3526
|
+
event.preventDefault();
|
|
3527
|
+
event.stopPropagation();
|
|
3528
|
+
if (Date.now() - mountTimeRef.current < 400) return;
|
|
3529
|
+
onClose();
|
|
3530
|
+
},
|
|
3353
3531
|
onClick: (event) => {
|
|
3532
|
+
event.preventDefault();
|
|
3354
3533
|
event.stopPropagation();
|
|
3355
3534
|
if (Date.now() - mountTimeRef.current < 400) return;
|
|
3356
3535
|
onClose();
|
|
@@ -3450,6 +3629,102 @@ window.__ModuleLoader__.load({
|
|
|
3450
3629
|
: null);
|
|
3451
3630
|
}
|
|
3452
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
|
+
|
|
3453
3728
|
function apply(ctx) {
|
|
3454
3729
|
ensureStyles(typeof document === "undefined" ? null : document);
|
|
3455
3730
|
// `null` is closed; an object is open. Carrying the requesting session lets
|
|
@@ -3945,7 +4220,7 @@ window.__ModuleLoader__.load({
|
|
|
3945
4220
|
|
|
3946
4221
|
/** Line-numbered diff viewer with dual line number gutters. */
|
|
3947
4222
|
/** Text, Markdown and JSON: one windowed read, three renderings. */
|
|
3948
|
-
function TextView({ meta, root, wrap, lang, commit, repo, backControl, sizeControl, langControl }) {
|
|
4223
|
+
function TextView({ meta, root, wrap, lang, commit, repo, backControl, sizeControl, langControl, onToast }) {
|
|
3949
4224
|
// Callers mount this with a per-file key, so paging state starts fresh
|
|
3950
4225
|
// for every file instead of leaking the previous file's window into the
|
|
3951
4226
|
// first request.
|
|
@@ -4231,11 +4506,16 @@ window.__ModuleLoader__.load({
|
|
|
4231
4506
|
: [contentTab, sourceTab, diffTab])].filter((tab) => tab !== null)
|
|
4232
4507
|
: [];
|
|
4233
4508
|
|
|
4234
|
-
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)
|
|
4235
4514
|
? react.createElement("div", { className: "fv-sheet-tabs", role: "tablist" },
|
|
4236
4515
|
react.createElement("div", { className: "fv-sheet-tabs-start" },
|
|
4237
4516
|
backControl,
|
|
4238
|
-
...tabButtons
|
|
4517
|
+
...tabButtons,
|
|
4518
|
+
copyControl),
|
|
4239
4519
|
(sizeControl || langControl)
|
|
4240
4520
|
? react.createElement("div", { className: "fv-sheet-tabs-end" },
|
|
4241
4521
|
sizeControl,
|
|
@@ -4307,7 +4587,11 @@ window.__ModuleLoader__.load({
|
|
|
4307
4587
|
}
|
|
4308
4588
|
|
|
4309
4589
|
/** Pick the viewer for a file and keep unsupported formats useful. */
|
|
4310
|
-
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
|
+
|
|
4311
4595
|
if (meta === null) {
|
|
4312
4596
|
// A tab exists but its metadata is still in flight. Saying "pick a
|
|
4313
4597
|
// file" there would be a lie, and the reader would click again.
|
|
@@ -4318,8 +4602,8 @@ window.__ModuleLoader__.load({
|
|
|
4318
4602
|
}
|
|
4319
4603
|
if (meta.tooLarge || meta.kind === "binary") {
|
|
4320
4604
|
return react.createElement("div", { className: "fv-binary-pane" },
|
|
4321
|
-
backControl ? react.createElement("div", { className: "fv-sheet-tabs", role: "toolbar" },
|
|
4322
|
-
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),
|
|
4323
4607
|
sizeControl ? react.createElement("div", { className: "fv-sheet-tabs-end" }, sizeControl) : null) : null,
|
|
4324
4608
|
react.createElement("div", { className: "fv-note" },
|
|
4325
4609
|
react.createElement("strong", null, meta.name),
|
|
@@ -4354,9 +4638,9 @@ window.__ModuleLoader__.load({
|
|
|
4354
4638
|
react.createElement("span", { className: "fv-history-hint" }, "这是历史版本,不是工作区当前内容。"))
|
|
4355
4639
|
: null;
|
|
4356
4640
|
|
|
4357
|
-
const headBar = (backControl || sizeControl)
|
|
4641
|
+
const headBar = (backControl || sizeControl || copyControl)
|
|
4358
4642
|
? react.createElement("div", { className: "fv-sheet-tabs", role: "toolbar" },
|
|
4359
|
-
react.createElement("div", { className: "fv-sheet-tabs-start" }, backControl),
|
|
4643
|
+
react.createElement("div", { className: "fv-sheet-tabs-start" }, backControl, copyControl),
|
|
4360
4644
|
sizeControl ? react.createElement("div", { className: "fv-sheet-tabs-end" }, sizeControl) : null)
|
|
4361
4645
|
: null;
|
|
4362
4646
|
|
|
@@ -4374,7 +4658,7 @@ window.__ModuleLoader__.load({
|
|
|
4374
4658
|
return react.createElement("div", { className: "fv-content" }, headBar, react.createElement(DocView, { key, meta, root }));
|
|
4375
4659
|
}
|
|
4376
4660
|
return react.createElement(react.Fragment, null, banner,
|
|
4377
|
-
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 }));
|
|
4378
4662
|
}
|
|
4379
4663
|
|
|
4380
4664
|
|
|
@@ -4410,11 +4694,7 @@ window.__ModuleLoader__.load({
|
|
|
4410
4694
|
if (!menu) return null;
|
|
4411
4695
|
const { x, y, entry } = menu;
|
|
4412
4696
|
const relPath = entry.displayPath || entry.path;
|
|
4413
|
-
const absPath = rootPath
|
|
4414
|
-
? (rootPath.includes("\\")
|
|
4415
|
-
? (rootPath.replace(/[\\/]+$/, "") + "\\" + relPath.replace(/\//g, "\\"))
|
|
4416
|
-
: (rootPath.replace(/[\\/]+$/, "") + "/" + relPath.replace(/\\/g, "/")))
|
|
4417
|
-
: relPath;
|
|
4697
|
+
const absPath = resolveAbsPath(rootPath, relPath);
|
|
4418
4698
|
|
|
4419
4699
|
const handleCopyRel = (e) => {
|
|
4420
4700
|
e.stopPropagation();
|
|
@@ -4452,7 +4732,20 @@ window.__ModuleLoader__.load({
|
|
|
4452
4732
|
return react.createElement(react.Fragment, null,
|
|
4453
4733
|
react.createElement("div", {
|
|
4454
4734
|
className: "fv-context-backdrop",
|
|
4735
|
+
onPointerDown: (e) => {
|
|
4736
|
+
e.preventDefault();
|
|
4737
|
+
e.stopPropagation();
|
|
4738
|
+
if (Date.now() - mountTimeRef.current < 400) return;
|
|
4739
|
+
onClose();
|
|
4740
|
+
},
|
|
4741
|
+
onMouseDown: (e) => {
|
|
4742
|
+
e.preventDefault();
|
|
4743
|
+
e.stopPropagation();
|
|
4744
|
+
if (Date.now() - mountTimeRef.current < 400) return;
|
|
4745
|
+
onClose();
|
|
4746
|
+
},
|
|
4455
4747
|
onClick: (e) => {
|
|
4748
|
+
e.preventDefault();
|
|
4456
4749
|
e.stopPropagation();
|
|
4457
4750
|
if (Date.now() - mountTimeRef.current < 400) return;
|
|
4458
4751
|
onClose();
|
|
@@ -4867,6 +5160,15 @@ window.__ModuleLoader__.load({
|
|
|
4867
5160
|
// it. Kept apart from the tree's `contextMenu`: the two menus describe
|
|
4868
5161
|
// different subjects and must never be confused for one another.
|
|
4869
5162
|
const [tabMenu, setTabMenu] = react.useState(null);
|
|
5163
|
+
const lastMenuClosedAtRef = react.useRef(0);
|
|
5164
|
+
const closeContextMenu = react.useCallback(() => {
|
|
5165
|
+
lastMenuClosedAtRef.current = Date.now();
|
|
5166
|
+
setContextMenu(null);
|
|
5167
|
+
}, []);
|
|
5168
|
+
const closeTabMenu = react.useCallback(() => {
|
|
5169
|
+
lastMenuClosedAtRef.current = Date.now();
|
|
5170
|
+
setTabMenu(null);
|
|
5171
|
+
}, []);
|
|
4870
5172
|
const tabsRef = react.useRef(null);
|
|
4871
5173
|
tabsRef.current = tabs;
|
|
4872
5174
|
const activeTabIdRef = react.useRef("");
|
|
@@ -5816,6 +6118,8 @@ window.__ModuleLoader__.load({
|
|
|
5816
6118
|
onMouseDown: (event) => {
|
|
5817
6119
|
if (event.target !== event.currentTarget) return;
|
|
5818
6120
|
if (Date.now() - openedAtRef.current < 400) return;
|
|
6121
|
+
if (contextMenu !== null || tabMenu !== null) return;
|
|
6122
|
+
if (Date.now() - lastMenuClosedAtRef.current < 400) return;
|
|
5819
6123
|
openStore.set(null);
|
|
5820
6124
|
},
|
|
5821
6125
|
},
|
|
@@ -6074,6 +6378,7 @@ window.__ModuleLoader__.load({
|
|
|
6074
6378
|
backControl,
|
|
6075
6379
|
sizeControl,
|
|
6076
6380
|
langControl,
|
|
6381
|
+
onToast: showToast,
|
|
6077
6382
|
})
|
|
6078
6383
|
: react.createElement(FileView, {
|
|
6079
6384
|
meta,
|
|
@@ -6092,12 +6397,13 @@ window.__ModuleLoader__.load({
|
|
|
6092
6397
|
backControl,
|
|
6093
6398
|
sizeControl,
|
|
6094
6399
|
langControl,
|
|
6400
|
+
onToast: showToast,
|
|
6095
6401
|
})))),
|
|
6096
6402
|
contextMenu !== null
|
|
6097
6403
|
? react.createElement(ContextMenu, {
|
|
6098
6404
|
menu: contextMenu,
|
|
6099
6405
|
rootPath: activeRoot ? (activeRoot.path || activeRoot.id) : root,
|
|
6100
|
-
onClose:
|
|
6406
|
+
onClose: closeContextMenu,
|
|
6101
6407
|
onMention: mention,
|
|
6102
6408
|
onToast: showToast,
|
|
6103
6409
|
onDownload: handleDownloadItem,
|
|
@@ -6110,7 +6416,7 @@ window.__ModuleLoader__.load({
|
|
|
6110
6416
|
? react.createElement(TabContextMenu, {
|
|
6111
6417
|
menu: tabMenu,
|
|
6112
6418
|
tabs,
|
|
6113
|
-
onClose:
|
|
6419
|
+
onClose: closeTabMenu,
|
|
6114
6420
|
onAction: onTabAction,
|
|
6115
6421
|
})
|
|
6116
6422
|
: null,
|
|
@@ -6424,12 +6730,13 @@ window.__ModuleLoader__.load({
|
|
|
6424
6730
|
mediaTypeOf, messageOf, createStore, createRequest, blobOf, ensureStyles,
|
|
6425
6731
|
ENTRY_POSITION_KEY, DRAG_SLOP, settleEntry, readEntryPosition, writeEntryPosition,
|
|
6426
6732
|
TREE_WIDTH_KEY, DEFAULT_TREE_WIDTH, MIN_TREE_WIDTH, MAX_TREE_WIDTH, readTreeWidth, writeTreeWidth,
|
|
6733
|
+
GIT_CHANGES_HEIGHT_KEY, DEFAULT_GIT_CHANGES_HEIGHT, MIN_GIT_CHANGES_HEIGHT, MAX_GIT_CHANGES_HEIGHT, readGitChangesHeight, writeGitChangesHeight,
|
|
6427
6734
|
SHELL_WIDTH_KEY, MIN_SHELL_WIDTH, readShellWidth, writeShellWidth, clampShellWidth,
|
|
6428
6735
|
LANG_OVERRIDES_KEY, LANG_OVERRIDES_MAX, readLangOverrides, writeLangOverrides, readLangOverride, writeLangOverride,
|
|
6429
6736
|
langOverrideKey, isKnownLanguageId, setLanguageCatalog, getLanguageCatalog: () => languageCatalog, LanguagePicker,
|
|
6430
6737
|
MARKDOWN_LABELS, MARKDOWN_CODE_LABELS, READ_BLOCK_LABELS, JSON_TREE_LABELS, DIFF_BLOCK_LABELS, ErrorBoundary,
|
|
6431
6738
|
openViewerForPath, wrapWorkspaceOpenPath, wrapConnectionRpc, wrapChangesOpenFetch, wrapSidebarRight, parseFileResource, setupGlobalFileClickInterceptor, extractClickedPath, normalizeClickedPath, looksLikeFilePath, isFileOpenControl, isFileMentionCode, renderRootOptions,
|
|
6432
|
-
triggerDownload, performDownload, isDesktopShell, SvgIconDownload, SvgIconBranch, SvgIconCommit,
|
|
6739
|
+
triggerDownload, performDownload, isDesktopShell, SvgIconDownload, SvgIconCopy, PathCopyDropdown, resolveAbsPath, SvgIconBranch, SvgIconCommit,
|
|
6433
6740
|
// Git panel: controller, views and the small formatters they share.
|
|
6434
6741
|
GIT_PAGE_SIZE, createGitController, GitSidebar, CommitDetail, WorkspaceChangeDetail, DiffView, kindOfPath,
|
|
6435
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",
|