@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.
Files changed (2) hide show
  1. package/lib/client.js +200 -72
  2. 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:20px;font-family:var(--dsw-font-code,monospace)}
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
- const tabs = showTabs || Boolean(backControl || langControl || sizeControl || copyControl)
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
- (sizeControl || langControl)
4554
- ? react.createElement("div", { className: "fv-sheet-tabs-end" },
4555
- sizeControl,
4556
- langControl)
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", { className: "fv-content-wrap" },
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
- sizeControl ? react.createElement("div", { className: "fv-sheet-tabs-end" }, sizeControl) : null)
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 for source views. Off by default: code is written with
5137
- // meaningful line breaks and wrapping obscures them.
5138
- const [wrap, setWrap] = react.useState(false);
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
- // The working-tree list is part of the panel's first paint, not a
5255
- // second click: "what is uncommitted" is the first thing to look at.
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
- * The top toolbar's refresh: the tree, the Git panel, and the tab on
5778
- * screen.
5779
- *
5780
- * The tab is refreshed too because that is what the reader is looking
5781
- * at — refreshing the tree around a stale preview would leave the file
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
- // Only offered where it changes anything: images, PDFs, sheets and
6209
- // documents do not scroll sideways the way source lines do.
6210
- !showCommit && meta !== null && !meta.tooLarge && (meta.kind === "text" || meta.kind === "markdown" || meta.kind === "json")
6211
- ? react.createElement("button", {
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.14",
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",