@weniatall/dsh-file-viewer 0.3.14 → 0.3.17
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 +200 -72
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -41,17 +41,6 @@ window.__ModuleLoader__.load({
|
|
|
41
41
|
const IconRefreshOutline16 = primitives.IconRefreshOutline16 || primitives.IconRefreshOutlineRegular;
|
|
42
42
|
|
|
43
43
|
// 统一风格的矢量线性图标 (16x16, 统一 currentColor 与线条粗细)
|
|
44
|
-
function SvgIconWrap({ size = 15 }) {
|
|
45
|
-
return react.createElement("svg", {
|
|
46
|
-
width: size, height: size, viewBox: "0 0 16 16", fill: "none",
|
|
47
|
-
stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round"
|
|
48
|
-
},
|
|
49
|
-
react.createElement("path", { d: "M2.5 4h11" }),
|
|
50
|
-
react.createElement("path", { d: "M2.5 8h7.5a2.5 2.5 0 0 1 2.5 2.5v0a2.5 2.5 0 0 1-2.5 2.5H5" }),
|
|
51
|
-
react.createElement("path", { d: "M7 10.5L4.5 13 7 15.5" })
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
44
|
/** Outline / table-of-contents glyph: short bars stepping in by level. */
|
|
56
45
|
function SvgIconOutline({ size = 15 }) {
|
|
57
46
|
return react.createElement("svg", {
|
|
@@ -219,9 +208,19 @@ window.__ModuleLoader__.load({
|
|
|
219
208
|
// Host ReadBlock/JsonTree now require localized copy for banners, copy
|
|
220
209
|
// buttons and expand/collapse. Missing these throws during render:
|
|
221
210
|
// "Cannot read properties of undefined (reading 'copy'|'window')".
|
|
211
|
+
// The host's ReadBlock banner renders a wrap toggle beside the copy button
|
|
212
|
+
// and names it from `labels.wrapLabel` / `labels.unwrapLabel`. Those two
|
|
213
|
+
// keys are easy to miss, and a blanket "复制" fallback here made the wrap
|
|
214
|
+
// toggle announce itself as "复制" — the wrong control with the wrong verb.
|
|
215
|
+
// Every key the banner needs is therefore named explicitly, and an unknown
|
|
216
|
+
// key now resolves to an empty string instead of borrowing another
|
|
217
|
+
// control's label.
|
|
222
218
|
const READ_BLOCK_LABELS = new Proxy({
|
|
223
219
|
copy: "复制",
|
|
224
220
|
copied: "已复制",
|
|
221
|
+
copyFailed: "复制失败",
|
|
222
|
+
wrapLabel: "自动换行",
|
|
223
|
+
unwrapLabel: "取消自动换行",
|
|
225
224
|
window: (shown, total) => `第 ${shown} / ${total} 行`,
|
|
226
225
|
collapse: "收起",
|
|
227
226
|
expand: (hidden) => `展开其余 ${hidden} 行`,
|
|
@@ -233,7 +232,7 @@ window.__ModuleLoader__.load({
|
|
|
233
232
|
if (typeof prop === "string" && (prop === "window" || prop === "expand" || prop.endsWith("Aria"))) {
|
|
234
233
|
return (...args) => String(args[0] ?? "");
|
|
235
234
|
}
|
|
236
|
-
return "
|
|
235
|
+
return "";
|
|
237
236
|
}
|
|
238
237
|
});
|
|
239
238
|
const JSON_TREE_LABELS = new Proxy({
|
|
@@ -261,6 +260,9 @@ window.__ModuleLoader__.load({
|
|
|
261
260
|
const DIFF_BLOCK_LABELS = new Proxy({
|
|
262
261
|
copy: "复制",
|
|
263
262
|
copied: "已复制",
|
|
263
|
+
copyFailed: "复制失败",
|
|
264
|
+
wrapLabel: "自动换行",
|
|
265
|
+
unwrapLabel: "取消自动换行",
|
|
264
266
|
files: (count) => `${count} 个文件`,
|
|
265
267
|
expand: (hidden) => `展开其余 ${hidden} 行`,
|
|
266
268
|
collapse: "收起",
|
|
@@ -272,7 +274,7 @@ window.__ModuleLoader__.load({
|
|
|
272
274
|
if (typeof prop === "string" && (prop === "files" || prop === "expand" || prop.endsWith("Aria"))) {
|
|
273
275
|
return (...args) => String(args[0] ?? "");
|
|
274
276
|
}
|
|
275
|
-
return "
|
|
277
|
+
return "";
|
|
276
278
|
}
|
|
277
279
|
});
|
|
278
280
|
|
|
@@ -367,6 +369,15 @@ window.__ModuleLoader__.load({
|
|
|
367
369
|
const DEFAULT_GIT_CHANGES_HEIGHT = 180;
|
|
368
370
|
const MIN_GIT_CHANGES_HEIGHT = 64;
|
|
369
371
|
const MAX_GIT_CHANGES_HEIGHT = 800;
|
|
372
|
+
// Reading size for the preview. A file's own text is what the reader spends
|
|
373
|
+
// their time on, so it gets its own, remembered size instead of inheriting
|
|
374
|
+
// the chat's. Bounded so the control cannot produce an unreadable pane in
|
|
375
|
+
// either direction, and stored per browser like every other view pref.
|
|
376
|
+
const FONT_SIZE_KEY = "dsh-file-viewer.font-size";
|
|
377
|
+
const DEFAULT_FONT_SIZE = 13;
|
|
378
|
+
const MIN_FONT_SIZE = 11;
|
|
379
|
+
const MAX_FONT_SIZE = 20;
|
|
380
|
+
const FONT_SIZE_STEP = 1;
|
|
370
381
|
/**
|
|
371
382
|
* Smallest width the drawer may be dragged to.
|
|
372
383
|
*
|
|
@@ -568,6 +579,24 @@ window.__ModuleLoader__.load({
|
|
|
568
579
|
.fv-sheet-tabs-end{display:inline-flex;align-items:center;gap:8px;margin-left:auto;flex:none}
|
|
569
580
|
.fv-sheet-tabs .fv-btn-back{width:26px;height:26px;font-size:16px;line-height:24px;border-radius:6px;margin-right:2px}
|
|
570
581
|
.fv-main-size{font-size:11px;color:var(--dsw-alias-label-caption);white-space:nowrap}
|
|
582
|
+
/* Reading-size stepper. Deliberately a compact group: the range is 11–20px,
|
|
583
|
+
so a slider would be all friction and no precision. */
|
|
584
|
+
.fv-font-size{display:inline-flex;align-items:center;gap:0;height:28px;border:1px solid var(--dsw-alias-border-l2);border-radius:14px;background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-base,#fff));overflow:hidden;flex:none}
|
|
585
|
+
.fv-font-size-btn{display:inline-flex;align-items:center;justify-content:center;height:100%;min-width:30px;padding:0 8px;border:none;background:none;color:var(--dsw-alias-label-secondary);font:inherit;font-size:11px;font-weight:600;line-height:1;cursor:pointer;transition:background-color .12s,color .12s}
|
|
586
|
+
.fv-font-size-btn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
587
|
+
.fv-font-size-btn:disabled{opacity:.38;cursor:default}
|
|
588
|
+
.fv-font-size-value{min-width:38px;padding:0 2px;text-align:center;color:var(--dsw-alias-label-caption);font-size:11px;font-variant-numeric:tabular-nums;white-space:nowrap}
|
|
589
|
+
/* The size reaches the preview through one custom property, so every
|
|
590
|
+
viewer that renders text picks it up without repeating the value.
|
|
591
|
+
The host's code card declares the font shorthand, which resets both size
|
|
592
|
+
and line height; the structural selectors below carry one element plus
|
|
593
|
+
one attribute, so they outrank that single class and win regardless of
|
|
594
|
+
sheet order. Line height is re-declared beside the size, otherwise the
|
|
595
|
+
host's fixed value would make larger text overlap. */
|
|
596
|
+
.fv-content,.fv-content-wrap{font-size:var(--fv-font-size,13px)}
|
|
597
|
+
.fv-content div[class*="_card_"],.fv-content div[class*="_body_"]{font-size:var(--fv-font-size,13px);line-height:1.65}
|
|
598
|
+
.fv-content div[class*="_line_"]{font-size:var(--fv-font-size,13px);line-height:1.65}
|
|
599
|
+
.fv-content div[class*="_line_"] span[class*="_content_"]{font-size:inherit;line-height:inherit}
|
|
571
600
|
.fv-content .fv-sheet-tabs{position:sticky;top:0;margin:-12px -12px 10px;padding:12px 12px 8px}
|
|
572
601
|
.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}
|
|
573
602
|
.fv-tab[aria-selected="true"]{border-color:transparent;background:var(--dsw-alias-brand-primary,#4d6bfe);color:var(--dsw-alias-label-primary-foreground,#fff)}
|
|
@@ -591,7 +620,7 @@ window.__ModuleLoader__.load({
|
|
|
591
620
|
.fv-diff-view{display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-base,#fff);overflow:hidden;font-family:var(--dsw-font-code,monospace);margin:4px 0}
|
|
592
621
|
.fv-diff-toolbar{display:flex;align-items:center;padding:8px 12px;background:var(--dsw-alias-bg-layer-1,rgba(0,0,0,.02));border-bottom:1px solid var(--dsw-alias-border-l1);font-family:var(--dsw-font-family);font-size:12px}
|
|
593
622
|
.fv-diff-source-tag{font-size:11px;font-weight:600;padding:2px 8px;border-radius:6px;background:color-mix(in srgb,var(--dsw-alias-brand-primary,#4d6bfe) 12%,transparent);color:var(--dsw-alias-brand-primary,#4d6bfe)}
|
|
594
|
-
.fv-diff-table{display:flex;flex-direction:column;overflow-x:auto;min-width:0;font-size:12px;line-height:
|
|
623
|
+
.fv-diff-table{display:flex;flex-direction:column;overflow-x:auto;min-width:0;font-size:var(--fv-font-size,12px);line-height:1.65;font-family:var(--dsw-font-code,monospace)}
|
|
595
624
|
.fv-diff-line{display:flex;min-width:max-content;align-items:stretch;transition:background-color .08s}
|
|
596
625
|
.fv-diff-num{flex:none;width:44px;padding:0 6px;text-align:right;user-select:none;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-caption);opacity:.65;font-size:11px}
|
|
597
626
|
.fv-diff-num-new{border-right:1px solid var(--dsw-alias-border-l1)}
|
|
@@ -724,6 +753,10 @@ window.__ModuleLoader__.load({
|
|
|
724
753
|
.fv-content[data-wrap="on"] div[class*="_line_"]{align-items:flex-start}
|
|
725
754
|
.fv-content[data-wrap="on"] span[class*="_gutter_"]{flex:none;white-space:pre}
|
|
726
755
|
.fv-content[data-wrap="on"] span[class*="_content_"]{min-width:0;flex:1 1 auto;white-space:pre-wrap;overflow-wrap:anywhere}
|
|
756
|
+
/* Tooltip 气泡:浅色模式下防止宿主静态白色文字在浅色背景上隐形 */
|
|
757
|
+
span[class*="_bubble_"],
|
|
758
|
+
[data-portal] span[class*="_bubble_"]{color:var(--dsw-alias-label-primary,#0f1115)!important;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.08))!important;box-shadow:var(--dsw-shadow-lv2,0 2px 8px rgba(0,0,0,.12))!important}
|
|
759
|
+
span[class*="_bubble_"] span[class*="_label_"]{color:inherit!important}
|
|
727
760
|
.fv-context-backdrop{position:fixed;inset:0;z-index:99;background:transparent!important;-webkit-backdrop-filter:none!important;backdrop-filter:none!important;touch-action:none}
|
|
728
761
|
/* 菜单是实底卡片,不吃主题的毛玻璃。
|
|
729
762
|
玻璃主题(glass.css)里有一条规则,给 [role="menu"] 套上
|
|
@@ -1213,6 +1246,42 @@ window.__ModuleLoader__.load({
|
|
|
1213
1246
|
} catch { /* storage is unavailable */ }
|
|
1214
1247
|
}
|
|
1215
1248
|
|
|
1249
|
+
/**
|
|
1250
|
+
* The preview's reading size, clamped on read.
|
|
1251
|
+
*
|
|
1252
|
+
* Storage survives upgrades and can be hand-edited, so an out-of-range
|
|
1253
|
+
* value falls back to the default rather than producing a pane nobody can
|
|
1254
|
+
* read (or one so large the code never fits).
|
|
1255
|
+
* @returns {number} a font size in px.
|
|
1256
|
+
*/
|
|
1257
|
+
function readFontSize() {
|
|
1258
|
+
try {
|
|
1259
|
+
const raw = globalThis.localStorage?.getItem(FONT_SIZE_KEY);
|
|
1260
|
+
const parsed = Number(raw);
|
|
1261
|
+
if (Number.isFinite(parsed) && parsed >= MIN_FONT_SIZE && parsed <= MAX_FONT_SIZE) {
|
|
1262
|
+
return Math.round(parsed);
|
|
1263
|
+
}
|
|
1264
|
+
} catch { /* storage is unavailable */ }
|
|
1265
|
+
return DEFAULT_FONT_SIZE;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
function writeFontSize(size) {
|
|
1269
|
+
try {
|
|
1270
|
+
if (typeof size === "number" && Number.isFinite(size)) {
|
|
1271
|
+
globalThis.localStorage?.setItem(FONT_SIZE_KEY, String(Math.round(size)));
|
|
1272
|
+
} else {
|
|
1273
|
+
globalThis.localStorage?.removeItem(FONT_SIZE_KEY);
|
|
1274
|
+
}
|
|
1275
|
+
} catch { /* storage is unavailable */ }
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
/** Clamp a requested size onto the supported range. */
|
|
1279
|
+
function clampFontSize(size) {
|
|
1280
|
+
const value = Number(size);
|
|
1281
|
+
if (!Number.isFinite(value)) return DEFAULT_FONT_SIZE;
|
|
1282
|
+
return Math.round(Math.min(MAX_FONT_SIZE, Math.max(MIN_FONT_SIZE, value)));
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1216
1285
|
/**
|
|
1217
1286
|
* Read the per-file language overrides.
|
|
1218
1287
|
*
|
|
@@ -2165,6 +2234,10 @@ window.__ModuleLoader__.load({
|
|
|
2165
2234
|
total: 0,
|
|
2166
2235
|
hasMore: false,
|
|
2167
2236
|
});
|
|
2237
|
+
// The working tree's pending changes belong on screen immediately;
|
|
2238
|
+
// load them alongside the first page of history so the section does
|
|
2239
|
+
// not stay on "loading" waiting for an explicit click.
|
|
2240
|
+
loadChanges();
|
|
2168
2241
|
// Returned so a caller that awaits loadRepos also observes the first
|
|
2169
2242
|
// page of history, which is what makes the panel testable end to end.
|
|
2170
2243
|
return loadCommits(store.get().branch, 0);
|
|
@@ -2211,6 +2284,7 @@ window.__ModuleLoader__.load({
|
|
|
2211
2284
|
detailStatus: "idle",
|
|
2212
2285
|
notice: "",
|
|
2213
2286
|
});
|
|
2287
|
+
loadChanges();
|
|
2214
2288
|
return loadCommits(store.get().branch, 0);
|
|
2215
2289
|
};
|
|
2216
2290
|
|
|
@@ -3759,6 +3833,52 @@ window.__ModuleLoader__.load({
|
|
|
3759
3833
|
: null);
|
|
3760
3834
|
}
|
|
3761
3835
|
|
|
3836
|
+
/**
|
|
3837
|
+
* Reading-size stepper for the preview pane.
|
|
3838
|
+
*
|
|
3839
|
+
* A file's own text is what the reader spends their time on, so its size is
|
|
3840
|
+
* adjustable on its own and remembered per browser. The control is a
|
|
3841
|
+
* minus/value/plus group rather than a slider: the useful range is small and
|
|
3842
|
+
* discrete, and a slider at this width would be impossible to hit.
|
|
3843
|
+
*
|
|
3844
|
+
* @param {{value: number, onChange: Function, disabled?: boolean}} props
|
|
3845
|
+
*/
|
|
3846
|
+
function FontSizeControl({ value, onChange, disabled }) {
|
|
3847
|
+
const size = clampFontSize(value);
|
|
3848
|
+
const atMin = size <= MIN_FONT_SIZE;
|
|
3849
|
+
const atMax = size >= MAX_FONT_SIZE;
|
|
3850
|
+
const step = (delta) => {
|
|
3851
|
+
const next = clampFontSize(size + delta);
|
|
3852
|
+
if (next !== size) onChange(next);
|
|
3853
|
+
};
|
|
3854
|
+
return react.createElement("div", {
|
|
3855
|
+
className: "fv-font-size",
|
|
3856
|
+
role: "group",
|
|
3857
|
+
"aria-label": "文件内字号",
|
|
3858
|
+
},
|
|
3859
|
+
react.createElement("button", {
|
|
3860
|
+
type: "button",
|
|
3861
|
+
className: "fv-font-size-btn",
|
|
3862
|
+
"aria-label": "减小字号",
|
|
3863
|
+
title: "减小字号",
|
|
3864
|
+
disabled: disabled === true || atMin,
|
|
3865
|
+
onClick: (e) => { e.stopPropagation(); step(-FONT_SIZE_STEP); },
|
|
3866
|
+
}, "A−"),
|
|
3867
|
+
react.createElement("span", {
|
|
3868
|
+
className: "fv-font-size-value",
|
|
3869
|
+
title: "当前字号",
|
|
3870
|
+
"aria-live": "polite",
|
|
3871
|
+
}, size + "px"),
|
|
3872
|
+
react.createElement("button", {
|
|
3873
|
+
type: "button",
|
|
3874
|
+
className: "fv-font-size-btn",
|
|
3875
|
+
"aria-label": "增大字号",
|
|
3876
|
+
title: "增大字号",
|
|
3877
|
+
disabled: disabled === true || atMax,
|
|
3878
|
+
onClick: (e) => { e.stopPropagation(); step(FONT_SIZE_STEP); },
|
|
3879
|
+
}, "A+"));
|
|
3880
|
+
}
|
|
3881
|
+
|
|
3762
3882
|
function apply(ctx) {
|
|
3763
3883
|
ensureStyles(typeof document === "undefined" ? null : document);
|
|
3764
3884
|
// `null` is closed; an object is open. Carrying the requesting session lets
|
|
@@ -4254,7 +4374,7 @@ window.__ModuleLoader__.load({
|
|
|
4254
4374
|
|
|
4255
4375
|
/** Line-numbered diff viewer with dual line number gutters. */
|
|
4256
4376
|
/** Text, Markdown and JSON: one windowed read, three renderings. */
|
|
4257
|
-
function TextView({ meta, root, wrap, lang, commit, repo, backControl, sizeControl, langControl, onToast }) {
|
|
4377
|
+
function TextView({ meta, root, wrap, lang, commit, repo, backControl, sizeControl, langControl, onToast, fontSize, onFontSizeChange }) {
|
|
4258
4378
|
// Callers mount this with a per-file key, so paging state starts fresh
|
|
4259
4379
|
// for every file instead of leaking the previous file's window into the
|
|
4260
4380
|
// first request.
|
|
@@ -4544,17 +4664,23 @@ window.__ModuleLoader__.load({
|
|
|
4544
4664
|
? react.createElement(PathCopyDropdown, { meta, root, onToast })
|
|
4545
4665
|
: null;
|
|
4546
4666
|
|
|
4547
|
-
|
|
4667
|
+
// Reading size belongs with the content it resizes, not in the drawer
|
|
4668
|
+
// header: it applies to this pane, and the stepper is next to the text.
|
|
4669
|
+
const fontControl = react.createElement(FontSizeControl, {
|
|
4670
|
+
value: fontSize,
|
|
4671
|
+
onChange: onFontSizeChange,
|
|
4672
|
+
});
|
|
4673
|
+
|
|
4674
|
+
const tabs = showTabs || Boolean(backControl || langControl || sizeControl || copyControl || fontControl)
|
|
4548
4675
|
? react.createElement("div", { className: "fv-sheet-tabs", role: "tablist" },
|
|
4549
4676
|
react.createElement("div", { className: "fv-sheet-tabs-start" },
|
|
4550
4677
|
backControl,
|
|
4551
4678
|
...tabButtons,
|
|
4552
4679
|
copyControl),
|
|
4553
|
-
(
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
: null)
|
|
4680
|
+
react.createElement("div", { className: "fv-sheet-tabs-end" },
|
|
4681
|
+
fontControl,
|
|
4682
|
+
sizeControl,
|
|
4683
|
+
langControl))
|
|
4558
4684
|
: null;
|
|
4559
4685
|
|
|
4560
4686
|
|
|
@@ -4580,7 +4706,13 @@ window.__ModuleLoader__.load({
|
|
|
4580
4706
|
|
|
4581
4707
|
return react.createElement(react.Fragment, null,
|
|
4582
4708
|
tabs,
|
|
4583
|
-
react.createElement("div", {
|
|
4709
|
+
react.createElement("div", {
|
|
4710
|
+
className: "fv-content-wrap",
|
|
4711
|
+
// The size reaches the rendered text through one inherited custom
|
|
4712
|
+
// property, so Markdown, JSON and plain source all follow it without
|
|
4713
|
+
// each view having to know about the setting.
|
|
4714
|
+
style: { "--fv-font-size": fontSize + "px" },
|
|
4715
|
+
},
|
|
4584
4716
|
react.createElement("div", {
|
|
4585
4717
|
className: "fv-content",
|
|
4586
4718
|
"data-wrap": wrapping ? "on" : "off",
|
|
@@ -4621,10 +4753,18 @@ window.__ModuleLoader__.load({
|
|
|
4621
4753
|
}
|
|
4622
4754
|
|
|
4623
4755
|
/** Pick the viewer for a file and keep unsupported formats useful. */
|
|
4624
|
-
function FileView({ meta, root, api, wrap, lang, onDownload, commit, repo, loading, backControl, sizeControl, langControl, onToast }) {
|
|
4756
|
+
function FileView({ meta, root, api, wrap, lang, onDownload, commit, repo, loading, backControl, sizeControl, langControl, onToast, fontSize, onFontSizeChange }) {
|
|
4625
4757
|
const copyControl = meta !== null
|
|
4626
4758
|
? react.createElement(PathCopyDropdown, { meta, root, onToast })
|
|
4627
4759
|
: null;
|
|
4760
|
+
// Only views that render text have a size to adjust; an image or a PDF
|
|
4761
|
+
// has its own zoom, and a sheet's cells are not prose.
|
|
4762
|
+
const textish = meta !== null
|
|
4763
|
+
&& !meta.tooLarge
|
|
4764
|
+
&& (meta.kind === "text" || meta.kind === "markdown" || meta.kind === "json");
|
|
4765
|
+
const fontControl = textish
|
|
4766
|
+
? react.createElement(FontSizeControl, { value: fontSize, onChange: onFontSizeChange })
|
|
4767
|
+
: null;
|
|
4628
4768
|
|
|
4629
4769
|
if (meta === null) {
|
|
4630
4770
|
// A tab exists but its metadata is still in flight. Saying "pick a
|
|
@@ -4672,10 +4812,10 @@ window.__ModuleLoader__.load({
|
|
|
4672
4812
|
react.createElement("span", { className: "fv-history-hint" }, "这是历史版本,不是工作区当前内容。"))
|
|
4673
4813
|
: null;
|
|
4674
4814
|
|
|
4675
|
-
const headBar = (backControl || sizeControl || copyControl)
|
|
4815
|
+
const headBar = (backControl || sizeControl || copyControl || fontControl)
|
|
4676
4816
|
? react.createElement("div", { className: "fv-sheet-tabs", role: "toolbar" },
|
|
4677
4817
|
react.createElement("div", { className: "fv-sheet-tabs-start" }, backControl, copyControl),
|
|
4678
|
-
|
|
4818
|
+
react.createElement("div", { className: "fv-sheet-tabs-end" }, fontControl, sizeControl))
|
|
4679
4819
|
: null;
|
|
4680
4820
|
|
|
4681
4821
|
if (fromHistory && meta.kind !== "text" && meta.kind !== "markdown" && meta.kind !== "json") {
|
|
@@ -4692,7 +4832,7 @@ window.__ModuleLoader__.load({
|
|
|
4692
4832
|
return react.createElement("div", { className: "fv-content" }, headBar, react.createElement(DocView, { key, meta, root }));
|
|
4693
4833
|
}
|
|
4694
4834
|
return react.createElement(react.Fragment, null, banner,
|
|
4695
|
-
react.createElement(TextView, { key, meta, root, wrap, lang, commit, repo, backControl, sizeControl, langControl, onToast }));
|
|
4835
|
+
react.createElement(TextView, { key, meta, root, wrap, lang, commit, repo, backControl, sizeControl, langControl, onToast, fontSize, onFontSizeChange }));
|
|
4696
4836
|
}
|
|
4697
4837
|
|
|
4698
4838
|
|
|
@@ -5133,9 +5273,22 @@ window.__ModuleLoader__.load({
|
|
|
5133
5273
|
setShellWidth(clamped);
|
|
5134
5274
|
writeShellWidth(clamped);
|
|
5135
5275
|
};
|
|
5136
|
-
// Soft wrap
|
|
5137
|
-
//
|
|
5138
|
-
|
|
5276
|
+
// Soft wrap is owned entirely by the host ReadBlock banner, which
|
|
5277
|
+
// renders its own toggle beside the copy button and reports the state
|
|
5278
|
+
// through `labels.wrapLabel` / `labels.unwrapLabel`. The viewer used to
|
|
5279
|
+
// keep a second, parallel switch in its header; two controls for one
|
|
5280
|
+
// flag could disagree, so the header's is gone and this flag is no
|
|
5281
|
+
// longer state — there is nothing left to toggle from here.
|
|
5282
|
+
const wrap = false;
|
|
5283
|
+
// Reading size for the preview, remembered per browser. Kept here rather
|
|
5284
|
+
// than inside TextView because the stepper sits in the pane's toolbar
|
|
5285
|
+
// while the text it sizes lives one level down.
|
|
5286
|
+
const [fontSize, setFontSize] = react.useState(readFontSize);
|
|
5287
|
+
const changeFontSize = (next) => {
|
|
5288
|
+
const clamped = clampFontSize(next);
|
|
5289
|
+
setFontSize(clamped);
|
|
5290
|
+
writeFontSize(clamped);
|
|
5291
|
+
};
|
|
5139
5292
|
// The language override now lives on the tab it applies to (see
|
|
5140
5293
|
// `localeTagOfActive`), so switching tabs switches the pin with it.
|
|
5141
5294
|
const [fullscreen, setFullscreen] = react.useState(false);
|
|
@@ -5250,10 +5403,9 @@ window.__ModuleLoader__.load({
|
|
|
5250
5403
|
// tab, and discovery spawns a git process per candidate repository.
|
|
5251
5404
|
react.useEffect(() => {
|
|
5252
5405
|
if (!open || side !== "git" || root === "") return;
|
|
5253
|
-
git.ensure()
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
git.loadChanges();
|
|
5406
|
+
git.ensure().then(() => {
|
|
5407
|
+
git.loadChanges();
|
|
5408
|
+
}).catch(() => {});
|
|
5257
5409
|
}, [open, side, root]);
|
|
5258
5410
|
const [toast, setToast] = react.useState(null);
|
|
5259
5411
|
const toastTimer = react.useRef(null);
|
|
@@ -5773,30 +5925,12 @@ window.__ModuleLoader__.load({
|
|
|
5773
5925
|
}).catch(() => {});
|
|
5774
5926
|
};
|
|
5775
5927
|
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
* they care about unchanged.
|
|
5783
|
-
*/
|
|
5784
|
-
const refresh = () => {
|
|
5785
|
-
reloadWorkingTree();
|
|
5786
|
-
if (side === "git") {
|
|
5787
|
-
const state = git.store.get();
|
|
5788
|
-
// The commit to come back to belongs to the tab on screen, not to
|
|
5789
|
-
// whichever detail the controller happens to hold.
|
|
5790
|
-
const keepSha = activeTab !== null && activeTab.kind === "commit"
|
|
5791
|
-
? activeTab.id.slice("commit:".length)
|
|
5792
|
-
: "";
|
|
5793
|
-
if (state.root !== root) git.reset(root);
|
|
5794
|
-
git.loadRepos().then(() => {
|
|
5795
|
-
if (keepSha !== "") git.openCommit(keepSha);
|
|
5796
|
-
});
|
|
5797
|
-
}
|
|
5798
|
-
if (activeTabId !== "") refreshTab(activeTabId);
|
|
5799
|
-
};
|
|
5928
|
+
// Refreshing is offered per subject now: the tab's own context menu
|
|
5929
|
+
// re-reads the file/commit on screen, the Git panel has its own
|
|
5930
|
+
// "refresh working tree" control, and the sidebar re-lists a directory
|
|
5931
|
+
// when it is reopened. The old header button duplicated all three at
|
|
5932
|
+
// once, which made a single click re-read things the reader was not
|
|
5933
|
+
// looking at.
|
|
5800
5934
|
|
|
5801
5935
|
/** Open one commit in its own tab, or activate the tab already showing it. */
|
|
5802
5936
|
const openCommit = (commit) => {
|
|
@@ -6205,18 +6339,10 @@ window.__ModuleLoader__.load({
|
|
|
6205
6339
|
// file's path instead of navigable breadcrumbs.
|
|
6206
6340
|
react.createElement("div", { className: "fv-crumbs fv-file-path", title: showCommit ? "" : (meta === null ? "" : meta.path) },
|
|
6207
6341
|
showCommit ? "" : (meta === null ? "" : meta.path)),
|
|
6208
|
-
//
|
|
6209
|
-
//
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
type: "button",
|
|
6213
|
-
className: "fv-icon-button",
|
|
6214
|
-
"aria-pressed": wrap ? "true" : "false",
|
|
6215
|
-
"aria-label": wrap ? "取消自动换行" : "自动换行",
|
|
6216
|
-
title: wrap ? "取消自动换行" : "自动换行",
|
|
6217
|
-
onClick: () => setWrap((current) => !current),
|
|
6218
|
-
}, react.createElement(SvgIconWrap, { size: 15 }))
|
|
6219
|
-
: null,
|
|
6342
|
+
// Soft wrap is offered by the code banner itself (next to its copy
|
|
6343
|
+
// button), so the header no longer carries a second toggle for the
|
|
6344
|
+
// same setting — two switches for one flag only invited them to
|
|
6345
|
+
// drift apart. Refresh lives in the tab's context menu likewise.
|
|
6220
6346
|
!showCommit && meta !== null && meta.kind !== "directory"
|
|
6221
6347
|
? react.createElement("button", {
|
|
6222
6348
|
type: "button",
|
|
@@ -6226,9 +6352,6 @@ window.__ModuleLoader__.load({
|
|
|
6226
6352
|
onClick: () => handleDownloadItem(meta),
|
|
6227
6353
|
}, react.createElement(SvgIconDownload, { size: 15 }))
|
|
6228
6354
|
: null,
|
|
6229
|
-
react.createElement("button", {
|
|
6230
|
-
type: "button", className: "fv-icon-button", "aria-label": "刷新", title: "刷新", onClick: refresh,
|
|
6231
|
-
}, react.createElement(SvgIconRefresh, { size: 15 })),
|
|
6232
6355
|
react.createElement("button", {
|
|
6233
6356
|
type: "button",
|
|
6234
6357
|
className: "fv-icon-button",
|
|
@@ -6412,6 +6535,8 @@ window.__ModuleLoader__.load({
|
|
|
6412
6535
|
sizeControl,
|
|
6413
6536
|
langControl,
|
|
6414
6537
|
onToast: showToast,
|
|
6538
|
+
fontSize,
|
|
6539
|
+
onFontSizeChange: changeFontSize,
|
|
6415
6540
|
})
|
|
6416
6541
|
: react.createElement(FileView, {
|
|
6417
6542
|
meta,
|
|
@@ -6431,6 +6556,8 @@ window.__ModuleLoader__.load({
|
|
|
6431
6556
|
sizeControl,
|
|
6432
6557
|
langControl,
|
|
6433
6558
|
onToast: showToast,
|
|
6559
|
+
fontSize,
|
|
6560
|
+
onFontSizeChange: changeFontSize,
|
|
6434
6561
|
})))),
|
|
6435
6562
|
contextMenu !== null
|
|
6436
6563
|
? react.createElement(ContextMenu, {
|
|
@@ -6770,6 +6897,7 @@ window.__ModuleLoader__.load({
|
|
|
6770
6897
|
MARKDOWN_LABELS, MARKDOWN_CODE_LABELS, READ_BLOCK_LABELS, JSON_TREE_LABELS, DIFF_BLOCK_LABELS, ErrorBoundary,
|
|
6771
6898
|
openViewerForPath, wrapWorkspaceOpenPath, wrapConnectionRpc, wrapChangesOpenFetch, wrapSidebarRight, parseFileResource, setupGlobalFileClickInterceptor, extractClickedPath, normalizeClickedPath, looksLikeFilePath, isFileOpenControl, isFileMentionCode, renderRootOptions,
|
|
6772
6899
|
triggerDownload, performDownload, isDesktopShell, SvgIconDownload, SvgIconCopy, PathCopyDropdown, resolveAbsPath, extractRelativeAndAbsolutePaths, isAbsolutePath, SvgIconBranch, SvgIconCommit,
|
|
6900
|
+
FONT_SIZE_KEY, DEFAULT_FONT_SIZE, MIN_FONT_SIZE, MAX_FONT_SIZE, FONT_SIZE_STEP, readFontSize, writeFontSize, clampFontSize, FontSizeControl,
|
|
6773
6901
|
// Git panel: controller, views and the small formatters they share.
|
|
6774
6902
|
GIT_PAGE_SIZE, createGitController, GitSidebar, CommitDetail, WorkspaceChangeDetail, DiffView, kindOfPath,
|
|
6775
6903
|
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.17",
|
|
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",
|