@youtyan/code-viewer 0.8.7 → 0.8.9

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/web/app.js CHANGED
@@ -445,15 +445,47 @@ ${lines.join(`
445
445
  ]
446
446
  };
447
447
  }
448
- function ensureTerraformHighlightLanguage(hljsRef) {
448
+ function gdscriptLanguageDefinition(hljs) {
449
+ const api = hljs;
450
+ return {
451
+ name: "GDScript",
452
+ aliases: ["gd"],
453
+ keywords: {
454
+ keyword: "and as assert await break breakpoint class class_name const continue elif else enum extends for func if in is match not or pass return self signal static super var void when while yield",
455
+ literal: "true false null PI TAU INF NAN",
456
+ built_in: "print printerr printraw print_rich print_debug push_error push_warning preload load range len abs absf absi sign floor ceil round clamp clampf clampi lerp lerpf min max pow sqrt randf randi randf_range randi_range randomize str int float bool typeof type_exists is_instance_valid Vector2 Vector2i Vector3 Vector3i Vector4 Vector4i Rect2 Rect2i Transform2D Transform3D Basis Quaternion Plane AABB Color NodePath StringName Callable Signal Array Dictionary PackedByteArray PackedInt32Array PackedInt64Array PackedFloat32Array PackedFloat64Array PackedStringArray PackedVector2Array PackedVector3Array PackedColorArray"
457
+ },
458
+ contains: [
459
+ lineComment(api, "#"),
460
+ {
461
+ scope: "string",
462
+ begin: '"""',
463
+ end: '"""'
464
+ },
465
+ api.QUOTE_STRING_MODE || { scope: "string", begin: '"', end: '"' },
466
+ { scope: "string", begin: "'", end: "'" },
467
+ { scope: "meta", begin: "@[A-Za-z_]\\w*" },
468
+ { scope: "symbol", begin: "[$%][A-Za-z_/][\\w/]*" },
469
+ api.NUMBER_MODE || { scope: "number", begin: "\\b\\d+(\\.\\d+)?\\b" },
470
+ { scope: "function", begin: "\\b[A-Za-z_]\\w*(?=\\()" }
471
+ ]
472
+ };
473
+ }
474
+ function ensureHighlightLanguage(hljsRef, name, definition) {
449
475
  if (!hljsRef?.registerLanguage)
450
476
  return;
451
- if (hljsRef.getLanguage?.("terraform"))
477
+ if (hljsRef.getLanguage?.(name))
452
478
  return;
453
479
  try {
454
- hljsRef.registerLanguage("terraform", terraformLanguageDefinition);
480
+ hljsRef.registerLanguage(name, definition);
455
481
  } catch {}
456
482
  }
483
+ function ensureTerraformHighlightLanguage(hljsRef) {
484
+ ensureHighlightLanguage(hljsRef, "terraform", terraformLanguageDefinition);
485
+ }
486
+ function ensureGdscriptHighlightLanguage(hljsRef) {
487
+ ensureHighlightLanguage(hljsRef, "gdscript", gdscriptLanguageDefinition);
488
+ }
457
489
 
458
490
  // web-src/core/icons.ts
459
491
  var FOLDER_ICON_PATHS = {
@@ -513,6 +545,8 @@ ${lines.join(`
513
545
  ];
514
546
  var X_16_PATH = "M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z";
515
547
  var SYNC_16_PATH = "M1.705 8.005a.75.75 0 0 1 .834.656 5.5 5.5 0 0 0 9.592 2.97l-1.204-1.204a.25.25 0 0 1 .177-.427h3.646a.25.25 0 0 1 .25.25v3.646a.25.25 0 0 1-.427.177l-1.38-1.38A7.002 7.002 0 0 1 1.05 8.84a.75.75 0 0 1 .656-.834ZM8 2.5a5.487 5.487 0 0 0-4.131 1.869l1.204 1.204A.25.25 0 0 1 4.896 6H1.25A.25.25 0 0 1 1 5.75V2.104a.25.25 0 0 1 .427-.177l1.38 1.38A7.002 7.002 0 0 1 14.95 7.16a.75.75 0 0 1-1.49.178A5.5 5.5 0 0 0 8 2.5Z";
548
+ var ARROW_RIGHT_16_PATH = "M8.22 2.97a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l2.97-2.97H3.75a.75.75 0 0 1 0-1.5h7.44L8.22 4.03a.75.75 0 0 1 0-1.06Z";
549
+ var UNDO_16_PATH = "M1.22 6.28a.749.749 0 0 1 0-1.06l3.5-3.5a.749.749 0 1 1 1.06 1.06L3.561 5h7.188l.001.007L10.749 5c.058 0 .116.007.171.019A4.501 4.501 0 0 1 10.5 14H8.796a.75.75 0 0 1 0-1.5H10.5a3 3 0 1 0 0-6H3.561L5.78 8.72a.749.749 0 1 1-1.06 1.06l-3.5-3.5Z";
516
550
  function iconSvg(className, paths) {
517
551
  const pathList = Array.isArray(paths) ? paths : [paths];
518
552
  return '<svg class="octicon ' + className + '" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" aria-hidden="true">' + pathList.map((path) => `<path fill="currentColor" d="${path}"></path>`).join("") + "</svg>";
@@ -522,6 +556,9 @@ ${lines.join(`
522
556
  var GRABBER_16_PATH = "M10 13h4v-2h-4v2Zm-8 0h4v-2H2v2Zm8-6h4V5h-4v2ZM2 7h4V5H2v2Zm8-6v2h4V1h-4ZM2 3h4V1H2v2Z";
523
557
  var PULSE_16_PATH = "M6 2c.306 0 .582.187.696.471L10 10.731l1.304-3.26A.751.751 0 0 1 12 7h3.25a.75.75 0 0 1 0 1.5h-2.742l-1.812 4.528a.751.751 0 0 1-1.392 0L6 4.77 4.696 8.03A.75.75 0 0 1 4 8.5H.75a.75.75 0 0 1 0-1.5h2.742l1.812-4.529A.751.751 0 0 1 6 2Z";
524
558
  var MOON_16_PATH = "M9.598 1.591a.749.749 0 0 1 .785-.175 7.001 7.001 0 1 1-8.967 8.967.75.75 0 0 1 .961-.96 5.5 5.5 0 0 0 7.046-7.046.75.75 0 0 1 .175-.786Zm1.616 1.945a7 7 0 0 1-7.678 7.678 5.499 5.499 0 1 0 7.678-7.678Z";
559
+ var QUESTION_16_PATH = "M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.92 6.085h.001a.749.749 0 1 1-1.342-.67c.169-.339.436-.701.849-.977C6.845 4.16 7.369 4 8 4a2.756 2.756 0 0 1 1.637.525c.503.377.863.965.863 1.725 0 .448-.115.83-.329 1.15-.205.307-.47.513-.692.662-.109.072-.22.138-.313.195l-.006.004a6.24 6.24 0 0 0-.26.16.952.952 0 0 0-.276.245.75.75 0 0 1-1.248-.832c.184-.264.42-.489.692-.661.103-.067.207-.132.313-.195l.007-.004c.1-.061.182-.11.258-.161a.969.969 0 0 0 .277-.245C8.96 6.514 9 6.427 9 6.25a.612.612 0 0 0-.262-.525A1.27 1.27 0 0 0 8 5.5c-.369 0-.595.09-.74.187a1.01 1.01 0 0 0-.34.398ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z";
560
+ var MARK_GITHUB_16_PATH = "M6.766 11.328c-2.063-.25-3.516-1.734-3.516-3.656 0-.781.281-1.625.75-2.188-.203-.515-.172-1.609.063-2.062.625-.078 1.468.25 1.968.703.594-.187 1.219-.281 1.985-.281.765 0 1.39.094 1.953.265.484-.437 1.344-.765 1.969-.687.218.422.25 1.515.046 2.047.5.593.766 1.39.766 2.203 0 1.922-1.453 3.375-3.547 3.64.531.344.89 1.094.89 1.954v1.625c0 .468.391.734.86.547C13.781 14.359 16 11.53 16 8.03 16 3.61 12.406 0 7.984 0 3.563 0 0 3.61 0 8.031a7.88 7.88 0 0 0 5.172 7.422c.422.156.828-.125.828-.547v-1.25c-.219.094-.5.156-.75.156-1.031 0-1.64-.562-2.078-1.609-.172-.422-.36-.672-.719-.719-.187-.015-.25-.093-.25-.187 0-.188.313-.328.625-.328.453 0 .844.281 1.25.86.313.452.64.655 1.031.655s.641-.14 1-.5c.266-.265.47-.5.657-.656";
561
+ var BOOK_16_PATH = "M0 1.75A.75.75 0 0 1 .75 1h4.253c1.227 0 2.317.59 3 1.501A3.743 3.743 0 0 1 11.006 1h4.245a.75.75 0 0 1 .75.75v10.5a.75.75 0 0 1-.75.75h-4.507a2.25 2.25 0 0 0-1.591.659l-.622.621a.75.75 0 0 1-1.06 0l-.622-.621A2.25 2.25 0 0 0 5.258 13H.75a.75.75 0 0 1-.75-.75Zm7.251 10.324.004-5.073-.002-2.253A2.25 2.25 0 0 0 5.003 2.5H1.5v9h3.757a3.75 3.75 0 0 1 1.994.574ZM8.755 4.75l-.004 7.322a3.752 3.752 0 0 1 1.992-.572H14.5v-9h-3.495a2.25 2.25 0 0 0-2.25 2.25Z";
525
562
  var SEARCH_16_PATH = "M10.68 11.74a6 6 0 0 1-7.917-8.984 6 6 0 0 1 8.997 7.905l3.05 3.05a.75.75 0 1 1-1.06 1.06l-3.07-3.03ZM11.5 7a4.499 4.499 0 1 1-8.997 0A4.499 4.499 0 0 1 11.5 7Z";
526
563
  var DOWNLOAD_16_PATHS = [
527
564
  "M2.75 14A1.75 1.75 0 0 1 1 12.25v-2.5a.75.75 0 0 1 1.5 0v2.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 1.5 0v2.5A1.75 1.75 0 0 1 13.25 14Z",
@@ -778,6 +815,50 @@ ${lines.join(`
778
815
  };
779
816
  }
780
817
 
818
+ // web-src/core/repository-web-url.ts
819
+ function encodePath(value) {
820
+ return value.split("/").filter(Boolean).map((part) => encodeURIComponent(part)).join("/");
821
+ }
822
+ function resolvedRef(ref, fallbackRef) {
823
+ if (!ref || ref === "worktree" || ref === "HEAD") {
824
+ return fallbackRef || "HEAD";
825
+ }
826
+ return ref;
827
+ }
828
+ function buildRepositoryWebTarget(repoWebUrl, options) {
829
+ if (!repoWebUrl)
830
+ return null;
831
+ let remote;
832
+ try {
833
+ remote = new URL(repoWebUrl);
834
+ } catch {
835
+ return null;
836
+ }
837
+ if (remote.protocol !== "http:" && remote.protocol !== "https:")
838
+ return null;
839
+ remote.search = "";
840
+ remote.hash = "";
841
+ remote.pathname = remote.pathname.replace(/\/+$/, "");
842
+ const base = remote.toString().replace(/\/$/, "");
843
+ if (remote.hostname.toLowerCase() !== "github.com") {
844
+ return { url: base, provider: "web" };
845
+ }
846
+ const ref = encodePath(resolvedRef(options.ref, options.fallbackRef));
847
+ const path = encodePath(options.path || "");
848
+ if (!ref || options.kind === "blob" && !path) {
849
+ return { url: base, provider: "github" };
850
+ }
851
+ const pathSuffix = path ? `/${path}` : "";
852
+ const target = `${base}/${options.kind}/${ref}${pathSuffix}`;
853
+ if (options.kind !== "blob" || !Number.isFinite(options.start) || !Number.isFinite(options.end)) {
854
+ return { url: target, provider: "github" };
855
+ }
856
+ const start = Math.max(1, Math.floor(Math.min(options.start, options.end)));
857
+ const end = Math.max(1, Math.floor(Math.max(options.start, options.end)));
858
+ const lineHash = start === end ? `#L${start}` : `#L${start}-L${end}`;
859
+ return { url: `${target}${lineHash}`, provider: "github" };
860
+ }
861
+
781
862
  // web-src/core/routes.ts
782
863
  function assertNever(value) {
783
864
  throw new Error(`unhandled route: ${JSON.stringify(value)}`);
@@ -1005,7 +1086,7 @@ ${lines.join(`
1005
1086
  }
1006
1087
  case "journal": {
1007
1088
  const params = new URLSearchParams;
1008
- if (route.tab && route.tab !== "journal")
1089
+ if (route.tab && route.tab !== "tasks")
1009
1090
  params.set("tab", route.tab);
1010
1091
  if (route.date)
1011
1092
  params.set("date", route.date);
@@ -1265,7 +1346,8 @@ ${lines.join(`
1265
1346
  cts: "typescript",
1266
1347
  kts: "kotlin",
1267
1348
  cxx: "cpp",
1268
- hxx: "cpp"
1349
+ hxx: "cpp",
1350
+ gd: "gdscript"
1269
1351
  };
1270
1352
  var TEXT_SOURCE_EXTENSIONS = new Set([
1271
1353
  ...Object.keys(EXT_TO_LANG),
@@ -7536,6 +7618,8 @@ ${lines.join(`
7536
7618
  tf: "terraform",
7537
7619
  tfvars: "terraform",
7538
7620
  hcl: "terraform",
7621
+ gd: "gdscript",
7622
+ godot: "gdscript",
7539
7623
  yml: "yaml",
7540
7624
  ts: "typescript",
7541
7625
  tsx: "typescript",
@@ -7552,6 +7636,7 @@ ${lines.join(`
7552
7636
  "csharp",
7553
7637
  "css",
7554
7638
  "dockerfile",
7639
+ "gdscript",
7555
7640
  "go",
7556
7641
  "graphql",
7557
7642
  "html",
@@ -8573,16 +8658,19 @@ ${frontmatter.yaml}
8573
8658
 
8574
8659
  // web-src/views/annotations-ui.ts
8575
8660
  var ANNOTATION_SESSION_PARAM = "annotationSession";
8661
+ var ANNOTATION_PANEL_PARAM = "annotations";
8662
+ var ANNOTATION_ENTRY_PARAM = "annotation";
8576
8663
  function createAnnotationsUi(deps) {
8577
8664
  const { $ } = deps;
8578
8665
  let ANNOTATIONS = { version: 1, sessions: [] };
8579
8666
  let annotationFollow = deps.getAnnotationFollow();
8580
- let activeAnnotationId = null;
8667
+ const initialUrlParams = new URLSearchParams(window.location.search);
8668
+ let activeAnnotationId = initialUrlParams.get(ANNOTATION_ENTRY_PARAM);
8581
8669
  let refreshAnnotationsInFlight = null;
8582
8670
  let inlineAnnotationsMounted = false;
8583
8671
  let databaseAnnotationsMounted = false;
8584
8672
  let annotationPanelDismissed = false;
8585
- let activeSessionId = new URLSearchParams(window.location.search).get(ANNOTATION_SESSION_PARAM);
8673
+ let activeSessionId = initialUrlParams.get(ANNOTATION_SESSION_PARAM);
8586
8674
  const annotationsChangedCallbacks = [];
8587
8675
  function notifyAnnotationsChanged() {
8588
8676
  for (const cb of annotationsChangedCallbacks)
@@ -8639,8 +8727,14 @@ ${frontmatter.yaml}
8639
8727
  if (qhPanel)
8640
8728
  qhPanel.hidden = true;
8641
8729
  document.body.classList.remove("query-history-panel-open");
8730
+ } else if (activeAnnotationId) {
8731
+ activeAnnotationId = null;
8732
+ annotationDetail.hidden = true;
8733
+ updateActiveHighlights();
8734
+ syncInlineAnnotationActive();
8642
8735
  }
8643
8736
  deps.setAnnotationPanelOpenState(open);
8737
+ syncSessionUrl();
8644
8738
  applyInlineAnnotations();
8645
8739
  }
8646
8740
  function annotationLineTarget(entry) {
@@ -8688,13 +8782,33 @@ ${frontmatter.yaml}
8688
8782
  }
8689
8783
  function withSessionParam(rawUrl) {
8690
8784
  const url = new URL(rawUrl, window.location.origin);
8785
+ if (!annotationPanel.hidden)
8786
+ url.searchParams.set(ANNOTATION_PANEL_PARAM, "open");
8787
+ else
8788
+ url.searchParams.delete(ANNOTATION_PANEL_PARAM);
8691
8789
  if (activeSessionId)
8692
8790
  url.searchParams.set(ANNOTATION_SESSION_PARAM, activeSessionId);
8693
8791
  else
8694
8792
  url.searchParams.delete(ANNOTATION_SESSION_PARAM);
8793
+ if (activeAnnotationId)
8794
+ url.searchParams.set(ANNOTATION_ENTRY_PARAM, activeAnnotationId);
8795
+ else
8796
+ url.searchParams.delete(ANNOTATION_ENTRY_PARAM);
8695
8797
  return url.pathname + url.search;
8696
8798
  }
8697
- function buildInlineAnnotationRow(entry, colSpan) {
8799
+ function createStepChip(entry, step) {
8800
+ const chip = document.createElement("button");
8801
+ chip.type = "button";
8802
+ chip.className = "gdp-annotation-step";
8803
+ chip.textContent = `${step.index + 1}/${step.total}`;
8804
+ chip.title = `open step ${step.index + 1} of ${step.total}`;
8805
+ chip.setAttribute("aria-label", `open annotation step ${step.index + 1} of ${step.total}`);
8806
+ chip.addEventListener("click", () => {
8807
+ openAnnotationEntry(entry.id);
8808
+ });
8809
+ return chip;
8810
+ }
8811
+ function buildInlineAnnotationRow(entry, colSpan, step) {
8698
8812
  const tr = document.createElement("tr");
8699
8813
  tr.className = "gdp-annotation-row";
8700
8814
  tr.dataset.annotationId = entry.id;
@@ -8703,13 +8817,17 @@ ${frontmatter.yaml}
8703
8817
  td.colSpan = colSpan;
8704
8818
  const box = document.createElement("div");
8705
8819
  box.className = "gdp-annotation-inline";
8820
+ const head = document.createElement("div");
8821
+ head.className = "gdp-annotation-inline-head";
8822
+ head.appendChild(createStepChip(entry, step));
8706
8823
  if (entry.title) {
8707
8824
  const heading2 = document.createElement("strong");
8708
8825
  heading2.className = "gdp-annotation-inline-title";
8709
8826
  heading2.textContent = entry.title;
8710
- box.appendChild(heading2);
8827
+ head.appendChild(heading2);
8711
8828
  }
8712
- box.appendChild(createCopyRefButton(entry, "gdp-annotation-inline-copy"));
8829
+ head.appendChild(createCopyRefButton(entry, "gdp-annotation-inline-copy"));
8830
+ box.appendChild(head);
8713
8831
  const markdown = document.createElement("div");
8714
8832
  markdown.className = "gdp-annotation-inline-body";
8715
8833
  ensureMarkdownHighlighter();
@@ -8719,7 +8837,7 @@ ${frontmatter.yaml}
8719
8837
  tr.appendChild(td);
8720
8838
  return tr;
8721
8839
  }
8722
- function buildDatabaseAnnotationBlock(entry) {
8840
+ function buildDatabaseAnnotationBlock(entry, step) {
8723
8841
  const box = document.createElement("div");
8724
8842
  box.className = "gdp-db-annotation-inline";
8725
8843
  box.dataset.annotationId = entry.id;
@@ -8736,7 +8854,7 @@ ${frontmatter.yaml}
8736
8854
  const location2 = document.createElement("span");
8737
8855
  location2.className = "gdp-db-annotation-inline-location";
8738
8856
  location2.textContent = annotationLocationLabel(entry);
8739
- head.append(title, location2, createCopyRefButton(entry));
8857
+ head.append(createStepChip(entry, step), title, location2, createCopyRefButton(entry));
8740
8858
  const markdown = document.createElement("div");
8741
8859
  markdown.className = "gdp-db-annotation-inline-body";
8742
8860
  ensureMarkdownHighlighter();
@@ -8763,7 +8881,10 @@ ${frontmatter.yaml}
8763
8881
  strip.className = "gdp-db-annotation-strip";
8764
8882
  strip.setAttribute("aria-label", "Datastore annotations");
8765
8883
  for (const entry of matches) {
8766
- strip.appendChild(buildDatabaseAnnotationBlock(entry));
8884
+ strip.appendChild(buildDatabaseAnnotationBlock(entry, {
8885
+ index: session.entries.indexOf(entry),
8886
+ total: session.entries.length
8887
+ }));
8767
8888
  }
8768
8889
  root.prepend(strip);
8769
8890
  databaseAnnotationsMounted = true;
@@ -8838,16 +8959,19 @@ ${frontmatter.yaml}
8838
8959
  if (!session)
8839
8960
  return;
8840
8961
  let mountedInlineRows = false;
8841
- for (const entry of session.entries) {
8962
+ session.entries.forEach((entry, index) => {
8842
8963
  if (entry.target?.kind === "database")
8843
- continue;
8964
+ return;
8844
8965
  const target = inlineAnnotationTargetRow(entry);
8845
8966
  if (!target)
8846
- continue;
8967
+ return;
8847
8968
  let anchor = target;
8848
8969
  while (anchor.nextElementSibling?.classList.contains("gdp-annotation-row"))
8849
8970
  anchor = anchor.nextElementSibling;
8850
- anchor.after(buildInlineAnnotationRow(entry, target.cells.length));
8971
+ anchor.after(buildInlineAnnotationRow(entry, target.cells.length, {
8972
+ index,
8973
+ total: session.entries.length
8974
+ }));
8851
8975
  const sibling = siblingSideRow(target);
8852
8976
  if (sibling) {
8853
8977
  let sibAnchor = sibling;
@@ -8856,7 +8980,7 @@ ${frontmatter.yaml}
8856
8980
  sibAnchor.after(buildInlineSpacerRow(entry, sibling.cells.length));
8857
8981
  }
8858
8982
  mountedInlineRows = true;
8859
- }
8983
+ });
8860
8984
  inlineAnnotationsMounted = mountedInlineRows;
8861
8985
  syncInlineAnnotationWidths();
8862
8986
  }
@@ -8895,14 +9019,21 @@ ${frontmatter.yaml}
8895
9019
  if (activeSessionId === sessionId)
8896
9020
  return;
8897
9021
  activeSessionId = sessionId;
9022
+ activeAnnotationId = null;
9023
+ annotationDetail.hidden = true;
8898
9024
  syncSessionUrl();
8899
9025
  updateActiveHighlights();
9026
+ syncInlineAnnotationActive();
8900
9027
  applyInlineAnnotations();
8901
9028
  notifyAnnotationsChanged();
8902
9029
  }
8903
9030
  function restoreSessionFromUrl() {
8904
- activeSessionId = new URLSearchParams(window.location.search).get(ANNOTATION_SESSION_PARAM);
9031
+ const params = new URLSearchParams(window.location.search);
9032
+ activeSessionId = params.get(ANNOTATION_SESSION_PARAM);
9033
+ activeAnnotationId = params.get(ANNOTATION_ENTRY_PARAM);
9034
+ setAnnotationPanelOpen(params.get(ANNOTATION_PANEL_PARAM) === "open" || !!activeAnnotationId);
8905
9035
  renderAnnotationPanel();
9036
+ restoreAnnotationDetailFromState();
8906
9037
  applyInlineAnnotations();
8907
9038
  }
8908
9039
  async function waitForAnnotationDiffTarget(entry) {
@@ -9007,9 +9138,8 @@ ${frontmatter.yaml}
9007
9138
  syncSessionUrl();
9008
9139
  }
9009
9140
  renderAnnotationPanel();
9141
+ restoreAnnotationDetailFromState();
9010
9142
  applyInlineAnnotations();
9011
- if (activeAnnotationId && !findAnnotation(activeAnnotationId))
9012
- hideAnnotationDetail();
9013
9143
  notifyAnnotationsChanged();
9014
9144
  }
9015
9145
  async function postAnnotationAction(payload) {
@@ -9169,6 +9299,10 @@ ${frontmatter.yaml}
9169
9299
  time.className = "annotation-session-time";
9170
9300
  time.textContent = annotationTimeLabel(session.created_at);
9171
9301
  time.title = session.created_at;
9302
+ const count = document.createElement("span");
9303
+ count.className = "annotation-session-count";
9304
+ count.textContent = String(session.entries.length);
9305
+ count.title = `${session.entries.length} steps`;
9172
9306
  title.addEventListener("click", () => {
9173
9307
  setActiveSession(session.id === activeSessionId ? null : session.id);
9174
9308
  });
@@ -9204,7 +9338,7 @@ ${frontmatter.yaml}
9204
9338
  return;
9205
9339
  postAnnotationAction({ action: "delete", id: session.id });
9206
9340
  });
9207
- head.append(title, time, rename, del);
9341
+ head.append(title, count, time, rename, del);
9208
9342
  sessionEl.appendChild(head);
9209
9343
  const list2 = document.createElement("ol");
9210
9344
  list2.className = "annotation-entries";
@@ -9263,6 +9397,7 @@ ${frontmatter.yaml}
9263
9397
  function hideAnnotationDetail() {
9264
9398
  activeAnnotationId = null;
9265
9399
  annotationDetail.hidden = true;
9400
+ syncSessionUrl();
9266
9401
  updateActiveHighlights();
9267
9402
  syncInlineAnnotationActive();
9268
9403
  }
@@ -9306,6 +9441,25 @@ ${frontmatter.yaml}
9306
9441
  updateActiveHighlights();
9307
9442
  syncInlineAnnotationActive();
9308
9443
  }
9444
+ function restoreAnnotationDetailFromState() {
9445
+ if (!activeAnnotationId) {
9446
+ annotationDetail.hidden = true;
9447
+ updateActiveHighlights();
9448
+ syncInlineAnnotationActive();
9449
+ return;
9450
+ }
9451
+ const found = findAnnotation(activeAnnotationId);
9452
+ if (!found) {
9453
+ activeAnnotationId = null;
9454
+ annotationDetail.hidden = true;
9455
+ syncSessionUrl();
9456
+ updateActiveHighlights();
9457
+ syncInlineAnnotationActive();
9458
+ return;
9459
+ }
9460
+ activeSessionId = found.session.id;
9461
+ showAnnotationDetail(found.session, found.entry, found.index);
9462
+ }
9309
9463
  function focusStandaloneSourceLines(card, entry) {
9310
9464
  if (!entry.line)
9311
9465
  return;
@@ -9505,7 +9659,7 @@ ${frontmatter.yaml}
9505
9659
  if (persist)
9506
9660
  deps.setAnnotationPanelWidth(clamped);
9507
9661
  }
9508
- if (deps.getAnnotationPanelOpen())
9662
+ if (initialUrlParams.get(ANNOTATION_PANEL_PARAM) === "open" || activeAnnotationId || deps.getAnnotationPanelOpen())
9509
9663
  setAnnotationPanelOpen(true);
9510
9664
  applyAnnotationPanelWidth(deps.getAnnotationPanelWidth() ?? ANNOTATION_PANEL_DEFAULT_WIDTH, false);
9511
9665
  updateDatabaseCaptureButton();
@@ -9681,6 +9835,24 @@ ${frontmatter.yaml}
9681
9835
  function isBlobOrBlameFileRoute(route) {
9682
9836
  return route.screen === "file" && (route.view === "blob" || route.view === "blame");
9683
9837
  }
9838
+ function fileRouteKeepingActiveView(currentRoute, target, range) {
9839
+ const base2 = {
9840
+ screen: "file",
9841
+ path: target.path,
9842
+ ref: target.ref,
9843
+ range
9844
+ };
9845
+ if (currentRoute.screen !== "file")
9846
+ return { ...base2, view: "blob" };
9847
+ if (currentRoute.view === "blame" || currentRoute.view === "history")
9848
+ return { ...base2, view: currentRoute.view };
9849
+ const keepPreview = currentRoute.view === "blob" && !!currentRoute.preview && (isPreviewableSource(target.path) || isMediaPreviewOnlySource(target.path));
9850
+ return {
9851
+ ...base2,
9852
+ view: "blob",
9853
+ ...keepPreview ? { preview: true } : {}
9854
+ };
9855
+ }
9684
9856
  function createFileViewTabButton(deps, target, view, label, active) {
9685
9857
  const btn = document.createElement("button");
9686
9858
  btn.type = "button";
@@ -9786,6 +9958,9 @@ ${frontmatter.yaml}
9786
9958
  name.appendChild(deps.createFileBreadcrumb(target.path, target.ref));
9787
9959
  name.appendChild(createFilePathCopyButton(target));
9788
9960
  header.appendChild(name);
9961
+ const repositoryWebLink = deps.createRepositoryWebLink?.(target);
9962
+ if (repositoryWebLink)
9963
+ header.appendChild(repositoryWebLink);
9789
9964
  sticky.appendChild(header);
9790
9965
  const tabsHost = document.createElement("div");
9791
9966
  tabsHost.className = "gdp-file-detail-tabs";
@@ -9988,7 +10163,8 @@ ${frontmatter.yaml}
9988
10163
  currentRange: deps.currentRange,
9989
10164
  setRoute: deps.setRoute,
9990
10165
  setPreferredSourceTab: deps.setPreferredSourceTab,
9991
- createFileBreadcrumb: deps.createFileBreadcrumb
10166
+ createFileBreadcrumb: deps.createFileBreadcrumb,
10167
+ createRepositoryWebLink: deps.createRepositoryWebLink
9992
10168
  }, target, "blame");
9993
10169
  sticky.appendChild(buildLegend());
9994
10170
  wrapper.appendChild(sticky);
@@ -9998,10 +10174,11 @@ ${frontmatter.yaml}
9998
10174
  wrapper.appendChild(body);
9999
10175
  card.appendChild(wrapper);
10000
10176
  mountFileShellCard(deps, target, card);
10177
+ const sourceShikiLang = normalizeSourceShikiLang(deps.inferLang(target.path));
10001
10178
  const [blameResp, srcText, highlighter] = await Promise.all([
10002
10179
  fetchBlame(target, generation),
10003
10180
  fetchSource(target, base2),
10004
- deps.getSyntaxHighlight() ? deps.loadSourceShikiHighlighter() : Promise.resolve(null)
10181
+ deps.getSyntaxHighlight() && sourceShikiLang ? deps.loadSourceShikiHighlighter(sourceShikiLang) : Promise.resolve(null)
10005
10182
  ]);
10006
10183
  if (generation !== activeGeneration)
10007
10184
  return;
@@ -17931,6 +18108,23 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
17931
18108
  spacer.className = "db-grid-spacer";
17932
18109
  const body = document.createElement("div");
17933
18110
  body.className = "db-grid-body";
18111
+ body.addEventListener("mouseover", (e2) => {
18112
+ const cell = e2.target?.closest(".db-grid-cell");
18113
+ if (!cell || !body.contains(cell))
18114
+ return;
18115
+ if (cell.dataset.gridTooltip) {
18116
+ delete cell.dataset.gridTooltip;
18117
+ cell.removeAttribute("title");
18118
+ }
18119
+ if (cell.title)
18120
+ return;
18121
+ if (cell.querySelector("input, textarea, select"))
18122
+ return;
18123
+ if (cell.scrollWidth > cell.clientWidth + 1) {
18124
+ cell.title = cell.textContent ?? "";
18125
+ cell.dataset.gridTooltip = "1";
18126
+ }
18127
+ });
17934
18128
  const filteredEmpty = document.createElement("div");
17935
18129
  filteredEmpty.className = "db-pane-empty";
17936
18130
  filteredEmpty.hidden = true;
@@ -18119,8 +18313,23 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
18119
18313
  }
18120
18314
  }
18121
18315
  }
18316
+ function defaultColWidth(colName) {
18317
+ const type = (columns.find((c2) => c2.name === colName)?.type ?? "").toLowerCase();
18318
+ let w = DEFAULT_COL_WIDTH;
18319
+ if (/timestamp|datetime/.test(type))
18320
+ w = 210;
18321
+ else if (/^(date|time|year)/.test(type))
18322
+ w = 120;
18323
+ else if (/bool|tinyint\(1\)/.test(type))
18324
+ w = 80;
18325
+ else if (/int|decimal|numeric|float|double|real/.test(type))
18326
+ w = 110;
18327
+ else if (/text|json|xml|blob/.test(type))
18328
+ w = 260;
18329
+ return Math.max(w, Math.min(240, colName.length * 7.5 + 34));
18330
+ }
18122
18331
  function getColWidth(colName) {
18123
- return colWidths.get(colName) ?? DEFAULT_COL_WIDTH;
18332
+ return colWidths.get(colName) ?? defaultColWidth(colName);
18124
18333
  }
18125
18334
  function applyColWidth(colName, index) {
18126
18335
  const w = getColWidth(colName);
@@ -22417,7 +22626,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
22417
22626
  });
22418
22627
  chip.addEventListener("dragend", () => clearDragState());
22419
22628
  }
22420
- tabsList.addEventListener("dragover", (e2) => {
22629
+ tabsBar.addEventListener("dragover", (e2) => {
22421
22630
  if (!draggingTabId)
22422
22631
  return;
22423
22632
  const target = e2.target?.closest(".db-tabs-chip");
@@ -22428,7 +22637,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
22428
22637
  e2.dataTransfer.dropEffect = "move";
22429
22638
  clearDropTarget();
22430
22639
  });
22431
- tabsList.addEventListener("drop", (e2) => {
22640
+ tabsBar.addEventListener("drop", (e2) => {
22432
22641
  if (!draggingTabId)
22433
22642
  return;
22434
22643
  const target = e2.target?.closest(".db-tabs-chip");
@@ -23072,7 +23281,9 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
23072
23281
  if (!el)
23073
23282
  return;
23074
23283
  el.hidden = !branch;
23075
- el.textContent = branch;
23284
+ const name = el.querySelector(".project-branch-name");
23285
+ if (name)
23286
+ name.textContent = branch;
23076
23287
  el.title = branch ? `Current branch: ${branch}` : "";
23077
23288
  }
23078
23289
  let metaFilesForViewedProgress = [];
@@ -23892,9 +24103,9 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
23892
24103
  const unfold = card.querySelector(".gdp-file-unfold");
23893
24104
  if (unfold)
23894
24105
  unfold.disabled = collapsed;
23895
- const viewFile = card.querySelector(".gdp-view-file");
23896
- if (viewFile)
23897
- viewFile.disabled = collapsed;
24106
+ card.querySelectorAll(".gdp-preview-file, .gdp-view-file").forEach((button2) => {
24107
+ button2.disabled = collapsed;
24108
+ });
23898
24109
  }
23899
24110
  function setViewFileButtonState(button, sourceMode) {
23900
24111
  if (!button)
@@ -24094,6 +24305,27 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
24094
24305
  wrap.appendChild(box);
24095
24306
  }
24096
24307
  header.appendChild(wrap);
24308
+ if (!header.querySelector(".gdp-preview-file") && isPreviewableSource(fileSourceTarget(file).path)) {
24309
+ const previewFile = document.createElement("button");
24310
+ previewFile.type = "button";
24311
+ previewFile.className = "gdp-preview-file gdp-btn gdp-btn-sm";
24312
+ previewFile.textContent = "Preview";
24313
+ previewFile.title = "Preview rendered file";
24314
+ previewFile.addEventListener("click", (e2) => {
24315
+ e2.stopPropagation();
24316
+ const target = fileSourceTarget(file);
24317
+ setRoute({
24318
+ screen: "file",
24319
+ path: target.path,
24320
+ ref: target.ref,
24321
+ view: "blob",
24322
+ preview: true,
24323
+ range: currentRange()
24324
+ });
24325
+ applySourceRouteToShell();
24326
+ });
24327
+ header.appendChild(previewFile);
24328
+ }
24097
24329
  if (!header.querySelector(".gdp-view-file")) {
24098
24330
  const viewFile = document.createElement("button");
24099
24331
  viewFile.type = "button";
@@ -26070,7 +26302,11 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
26070
26302
  },
26071
26303
  {
26072
26304
  kind: "paragraph",
26073
- text: "A file detail page exposes up to four tabs — Preview, Code, Blame, History. For text files Code is the default and ?preview=1 opts in to the Markdown / HTML preview; media files (images, video, audio, PDF) show a Preview tab only, with no Code tab. Blame and History have their own canonical URLs (view=blame, view=history) so deep links and the browser back/forward stay in sync, and they keep the Repository sidebar visible."
26305
+ text: "A file detail page exposes up to four tabs — Preview, Code, Blame, History. For text files Code is the default and ?preview=1 opts in to the Markdown / HTML preview; media files (images, video, audio, PDF) show a Preview tab only, with no Code tab. Blame and History have their own canonical URLs (view=blame, view=history) so deep links and the browser back/forward stay in sync, and they keep the Repository sidebar visible. Opening another file from the repository tree keeps the active tab (a file that cannot be previewed falls back to Code)."
26306
+ },
26307
+ {
26308
+ kind: "paragraph",
26309
+ text: "When the repository remote is on GitHub, repository and file headers can open the current path there. Selecting source lines also exposes separate actions to copy the AI reference, open the exact GitHub line range, or copy its URL. Markdown and HTML diff cards include a direct Preview shortcut."
26074
26310
  },
26075
26311
  {
26076
26312
  kind: "paragraph",
@@ -26163,6 +26399,10 @@ code-viewer doctor --bin git=/opt/bin/git --bin docker=/opt/bin/docker --bin gh=
26163
26399
  kind: "paragraph",
26164
26400
  text: "Everything in .code-viewer/ is owned by code-viewer. JSON files are rewritten safely with validation on every change, so unknown keys are dropped and invalid values are reset to defaults. Hand-edit at your own risk — a corrupt file is renamed with a .corrupt suffix and replaced with an empty default."
26165
26401
  },
26402
+ {
26403
+ kind: "paragraph",
26404
+ text: "The directory appears in the repository tree and its text files can be inspected in the Code view. It remains excluded from repository searches and diffs."
26405
+ },
26166
26406
  {
26167
26407
  kind: "paragraph",
26168
26408
  text: "Deleting the whole directory is the supported way to reset all per-repository state. Removing a single file resets only that subsystem (for example, deleting tabs.json closes all DB tabs on the next load)."
@@ -26271,6 +26511,10 @@ code-viewer annotate add-db --db app.db --tab query \\
26271
26511
  [
26272
26512
  "Persistent state",
26273
26513
  "Open/closed, width, follow, mute, and rate are stored under .code-viewer/settings.json; annotations themselves live in .code-viewer/annotations.json."
26514
+ ],
26515
+ [
26516
+ "Restorable URL",
26517
+ "The panel-open state, selected session, and selected annotation are kept in the URL, so reloads and shared links restore the same walkthrough context."
26274
26518
  ]
26275
26519
  ]
26276
26520
  }
@@ -26741,7 +26985,11 @@ code-viewer query agent-help`
26741
26985
  },
26742
26986
  {
26743
26987
  kind: "paragraph",
26744
- text: "ファイル詳細ページには最大 4 つのタブ (Preview / Code / Blame / History) があります。テキストファイルは Code がデフォルトで、?preview=1 を付けると Markdown / HTML プレビューに切り替わります。画像・動画・音声・PDF などのメディアファイルは Preview タブのみ表示され、Code タブは表示されません。Blame と History はそれぞれ専用 URL (view=blame, view=history) を持つため、ディープリンクとブラウザの戻る / 進むが同期し、いずれも Repository サイドバーは表示されたままです。"
26988
+ text: "ファイル詳細ページには最大 4 つのタブ (Preview / Code / Blame / History) があります。テキストファイルは Code がデフォルトで、?preview=1 を付けると Markdown / HTML プレビューに切り替わります。画像・動画・音声・PDF などのメディアファイルは Preview タブのみ表示され、Code タブは表示されません。Blame と History はそれぞれ専用 URL (view=blame, view=history) を持つため、ディープリンクとブラウザの戻る / 進むが同期し、いずれも Repository サイドバーは表示されたままです。ツリーから別のファイルを開いても選択中のタブは維持されます (プレビューできないファイルでは Code に戻ります)。"
26989
+ },
26990
+ {
26991
+ kind: "paragraph",
26992
+ text: "リポジトリの remote が GitHub の場合、リポジトリとファイルのヘッダーから現在のパスを GitHub で開けます。ソース行を選択すると、AI 参照のコピー、選択行範囲を GitHub で開く、GitHub URL のコピーを個別に選べます。Markdown / HTML の diff カードには直接 Preview を開く導線も表示されます。"
26745
26993
  },
26746
26994
  {
26747
26995
  kind: "paragraph",
@@ -26834,6 +27082,10 @@ code-viewer doctor --bin git=/opt/bin/git --bin docker=/opt/bin/docker --bin gh=
26834
27082
  kind: "paragraph",
26835
27083
  text: ".code-viewer/ の中身はすべて code-viewer が管理します。JSON ファイルは書き込みごとにバリデーションを通して安全に書き換えられるため、未知のキーは破棄され、不正な値は既定値に戻されます。手で編集すると壊れる可能性があり、壊れたファイルは .corrupt サフィックスに改名されて空の既定値で置き換えられます。"
26836
27084
  },
27085
+ {
27086
+ kind: "paragraph",
27087
+ text: "ディレクトリはリポジトリツリーに表示され、テキストファイルは Code ビューで確認できます。リポジトリ検索と diff の対象からは引き続き除外されます。"
27088
+ },
26837
27089
  {
26838
27090
  kind: "paragraph",
26839
27091
  text: "ディレクトリごと削除するのが、このリポジトリの全状態をリセットする推奨手順です。個別ファイルだけを消した場合はその系統だけがリセットされます(例: tabs.json を消すと次回起動時に DB タブがすべて閉じた状態になります)。"
@@ -26942,6 +27194,10 @@ code-viewer annotate add-db --db app.db --tab query \\
26942
27194
  [
26943
27195
  "永続化",
26944
27196
  "パネルの開閉・幅・follow・ミュート・速度は .code-viewer/settings.json に、注釈自体は .code-viewer/annotations.json に保存されます。"
27197
+ ],
27198
+ [
27199
+ "復元できる URL",
27200
+ "パネルの開閉、選択中のセッション、選択中の注釈は URL に反映されるため、リロードや共有リンクでも同じウォークスルー状態を復元できます。"
26945
27201
  ]
26946
27202
  ]
26947
27203
  }
@@ -28224,7 +28480,7 @@ code-viewer query agent-help`
28224
28480
  let mounted = false;
28225
28481
  let lifecycle = 0;
28226
28482
  let data = null;
28227
- let activeTab = "journal";
28483
+ let activeTab = "tasks";
28228
28484
  let selectedDate = todayIsoDate();
28229
28485
  let selectedEntryId = "";
28230
28486
  let creatingEntry = false;
@@ -28397,7 +28653,7 @@ code-viewer query agent-help`
28397
28653
  function applyRoute(route = deps.getRoute()) {
28398
28654
  if (!isJournalRoute(route))
28399
28655
  return;
28400
- activeTab = route.tab || activeTab || "journal";
28656
+ activeTab = route.tab || activeTab || "tasks";
28401
28657
  selectedDate = route.date || selectedDate || todayIsoDate();
28402
28658
  labelFilter = route.label || "";
28403
28659
  selectedTaskId = route.task || "";
@@ -29960,21 +30216,50 @@ code-viewer query agent-help`
29960
30216
  const copyButton = document.createElement("button");
29961
30217
  copyButton.id = "line-ref-pill-copy";
29962
30218
  copyButton.type = "button";
29963
- copyButton.title = "選択行の参照をコピー(Claude Code / Codex に貼り付け用)。Shift+Click でコード本体も添付。";
29964
30219
  const closeButton = document.createElement("button");
29965
30220
  closeButton.id = "line-ref-pill-close";
29966
30221
  closeButton.type = "button";
29967
30222
  closeButton.title = "選択を解除";
29968
30223
  closeButton.setAttribute("aria-label", "選択を解除");
29969
30224
  closeButton.textContent = "×";
29970
- pill.appendChild(copyButton);
29971
- pill.appendChild(closeButton);
30225
+ const githubActions = document.createElement("div");
30226
+ githubActions.id = "line-ref-pill-github-actions";
30227
+ githubActions.hidden = true;
30228
+ const githubOpen = document.createElement("a");
30229
+ githubOpen.id = "line-ref-pill-github-open";
30230
+ githubOpen.target = "_blank";
30231
+ githubOpen.rel = "noopener";
30232
+ githubOpen.innerHTML = iconSvg("octicon-mark-github", MARK_GITHUB_16_PATH) + '<span class="lrp-github-open-label"></span>' + iconSvg("octicon-link-external", OPEN_EXTERNAL_16_PATH);
30233
+ const githubCopy = document.createElement("button");
30234
+ githubCopy.id = "line-ref-pill-github-copy";
30235
+ githubCopy.type = "button";
30236
+ githubCopy.innerHTML = `<span class="lrp-github-copy-icon">${iconSvg("octicon-copy", COPY_16_PATHS)}</span>` + '<span class="lrp-github-copy-label"></span>';
30237
+ githubActions.append(githubOpen, githubCopy);
30238
+ pill.append(copyButton, githubActions, closeButton);
29972
30239
  document.body.appendChild(pill);
29973
30240
  let refText = "";
29974
30241
  let currentPath = "";
29975
30242
  let currentStart = 0;
29976
30243
  let currentEnd = 0;
30244
+ let githubUrl = "";
29977
30245
  let feedbackTimer = null;
30246
+ let githubFeedbackTimer = null;
30247
+ function renderGithubActions() {
30248
+ const openTitle = deps.githubOpenTitle();
30249
+ const copyTitle = deps.githubCopyTitle();
30250
+ githubOpen.title = openTitle;
30251
+ githubOpen.setAttribute("aria-label", openTitle);
30252
+ githubCopy.title = copyTitle;
30253
+ githubCopy.setAttribute("aria-label", copyTitle);
30254
+ const openLabel = githubOpen.querySelector(".lrp-github-open-label");
30255
+ if (openLabel)
30256
+ openLabel.textContent = openTitle;
30257
+ const copyLabel = githubCopy.querySelector(".lrp-github-copy-label");
30258
+ if (copyLabel)
30259
+ copyLabel.textContent = copyTitle;
30260
+ githubOpen.href = githubUrl || "#";
30261
+ githubActions.hidden = !githubUrl;
30262
+ }
29978
30263
  function selectionLineCount() {
29979
30264
  return currentEnd - currentStart + 1;
29980
30265
  }
@@ -29983,25 +30268,16 @@ code-viewer query agent-help`
29983
30268
  if (count <= 1)
29984
30269
  return "";
29985
30270
  const warn = count >= AI_CONTEXT_LARGE_SELECTION_LINE_THRESHOLD;
29986
- return `<span class="lrp-count${warn ? " lrp-count-warn" : ""}">${count} lines</span>`;
30271
+ return `<span class="lrp-count${warn ? " lrp-count-warn" : ""}">${escapeHtml2(deps.lineCountLabel(count))}</span>`;
29987
30272
  }
29988
30273
  function render(state) {
29989
- pill.classList.toggle("copied", state === "copied" || state === "copied-code");
29990
- if (state === "copied") {
29991
- copyButton.innerHTML = `${CHECK_ICON}<span class="lrp-label">Copied!</span>`;
29992
- return;
29993
- }
29994
- if (state === "copied-code") {
29995
- const count = selectionLineCount();
29996
- const suffix = count > 1 ? ` (${count} lines)` : "";
29997
- copyButton.innerHTML = `${CHECK_ICON}<span class="lrp-label">Copied + code${suffix}</span>`;
29998
- return;
29999
- }
30000
- if (state === "failed") {
30001
- copyButton.innerHTML = `${COPY_ICON}<span class="lrp-label">copy failed</span>`;
30002
- return;
30003
- }
30004
- copyButton.innerHTML = `${COPY_ICON}<span class="lrp-label">Copy</span>` + `<span class="lrp-ref">${escapeHtml2(refText)}</span>` + countBadgeHtml();
30274
+ copyButton.classList.toggle("copied", state === "copied" || state === "copied-code");
30275
+ copyButton.classList.toggle("failed", state === "failed");
30276
+ const icon = state === "copied" || state === "copied-code" ? CHECK_ICON : COPY_ICON;
30277
+ const label = deps.copyReferenceLabel();
30278
+ copyButton.innerHTML = `${icon}<span class="lrp-label">${escapeHtml2(label)}</span>` + `<span class="lrp-ref">${escapeHtml2(refText)}</span>` + countBadgeHtml();
30279
+ copyButton.title = label;
30280
+ copyButton.setAttribute("aria-label", label);
30005
30281
  }
30006
30282
  copyButton.addEventListener("click", async (event) => {
30007
30283
  if (!refText)
@@ -30030,6 +30306,28 @@ code-viewer query agent-help`
30030
30306
  render("ready");
30031
30307
  }, 1200);
30032
30308
  });
30309
+ githubCopy.addEventListener("click", async () => {
30310
+ if (!githubUrl)
30311
+ return;
30312
+ try {
30313
+ await navigator.clipboard.writeText(githubUrl);
30314
+ githubCopy.classList.add("copied");
30315
+ const icon = githubCopy.querySelector(".lrp-github-copy-icon");
30316
+ if (icon)
30317
+ icon.innerHTML = CHECK_ICON;
30318
+ } catch {
30319
+ githubCopy.classList.add("failed");
30320
+ }
30321
+ if (githubFeedbackTimer)
30322
+ clearTimeout(githubFeedbackTimer);
30323
+ githubFeedbackTimer = setTimeout(() => {
30324
+ githubFeedbackTimer = null;
30325
+ githubCopy.classList.remove("copied", "failed");
30326
+ const icon = githubCopy.querySelector(".lrp-github-copy-icon");
30327
+ if (icon)
30328
+ icon.innerHTML = iconSvg("octicon-copy", COPY_16_PATHS);
30329
+ }, 1200);
30330
+ });
30033
30331
  closeButton.addEventListener("click", () => {
30034
30332
  deps.onClose();
30035
30333
  });
@@ -30043,6 +30341,8 @@ code-viewer query agent-help`
30043
30341
  currentPath = path;
30044
30342
  currentStart = Math.max(1, Math.floor(Math.min(start, end)));
30045
30343
  currentEnd = Math.max(1, Math.floor(Math.max(start, end)));
30344
+ githubUrl = deps.githubUrlForSelection(currentPath, currentStart, currentEnd) || "";
30345
+ renderGithubActions();
30046
30346
  if (feedbackTimer) {
30047
30347
  clearTimeout(feedbackTimer);
30048
30348
  feedbackTimer = null;
@@ -30060,6 +30360,8 @@ code-viewer query agent-help`
30060
30360
  currentPath = "";
30061
30361
  currentStart = 0;
30062
30362
  currentEnd = 0;
30363
+ githubUrl = "";
30364
+ githubActions.hidden = true;
30063
30365
  pill.hidden = true;
30064
30366
  }
30065
30367
  };
@@ -30529,6 +30831,19 @@ code-viewer query agent-help`
30529
30831
  return trimmed;
30530
30832
  }
30531
30833
 
30834
+ // web-src/views/repository-web-link.ts
30835
+ function createRepositoryWebLink(target, label) {
30836
+ const link2 = document.createElement("a");
30837
+ link2.className = "gdp-btn gdp-btn-sm gdp-repo-web-link";
30838
+ link2.href = target.url;
30839
+ link2.target = "_blank";
30840
+ link2.rel = "noopener";
30841
+ link2.title = label;
30842
+ link2.setAttribute("aria-label", label);
30843
+ link2.innerHTML = `${iconSvg(target.provider === "github" ? "octicon-mark-github" : "octicon-link-external", target.provider === "github" ? MARK_GITHUB_16_PATH : OPEN_EXTERNAL_16_PATH)}<span>${label}</span>`;
30844
+ return link2;
30845
+ }
30846
+
30532
30847
  // web-src/core/file-filter.ts
30533
30848
  function normalizeFileFilterQuery(value) {
30534
30849
  return (value || "").toLowerCase().trim();
@@ -32240,6 +32555,9 @@ code-viewer query agent-help`
32240
32555
  repositoryFallback,
32241
32556
  repositoryRootFallback,
32242
32557
  commitEntryMeta,
32558
+ repositoryWebTarget,
32559
+ openGithubLabel,
32560
+ openRepositoryWebLabel,
32243
32561
  fileBadge
32244
32562
  } = deps;
32245
32563
  let REPO_SORT = {
@@ -32395,6 +32713,13 @@ code-viewer query agent-help`
32395
32713
  });
32396
32714
  return button;
32397
32715
  }
32716
+ function createRepositoryWebLink2(path, ref) {
32717
+ const target = repositoryWebTarget(path, ref);
32718
+ if (!target)
32719
+ return null;
32720
+ const label = target.provider === "github" ? openGithubLabel() : openRepositoryWebLabel();
32721
+ return createRepositoryWebLink(target, label);
32722
+ }
32398
32723
  function showRepoContextMenu(event, entry, ref, onChanged) {
32399
32724
  if (document.querySelector(".gdp-dialog-backdrop"))
32400
32725
  return false;
@@ -32632,6 +32957,9 @@ code-viewer query agent-help`
32632
32957
  pathHeader.appendChild(createCopyPathButton(meta.path));
32633
32958
  pathHeader.appendChild(createOpenPathButton(meta.path || "", "directory", openDirectoryInOsTitle()));
32634
32959
  toolbar.appendChild(pathHeader);
32960
+ const repositoryWebLink = createRepositoryWebLink2(meta.path || "", meta.ref);
32961
+ if (repositoryWebLink)
32962
+ toolbar.appendChild(repositoryWebLink);
32635
32963
  if (canTrashWorktreeRef(meta.ref)) {
32636
32964
  toolbar.appendChild(createNewFolderButton(meta.path || "", () => loadRepo()));
32637
32965
  if (meta.path) {
@@ -32844,14 +33172,10 @@ code-viewer query agent-help`
32844
33172
  loadRepo();
32845
33173
  return;
32846
33174
  }
32847
- setRoute({
32848
- screen: "file",
32849
- path: file.path,
32850
- ref: normalizedRef,
32851
- view: "blob",
32852
- range: currentRange()
32853
- });
32854
- renderStandaloneSource({ path: file.path, ref: normalizedRef });
33175
+ const fileRoute = fileRouteKeepingActiveView(STATE.route, { path: file.path, ref: normalizedRef }, currentRange());
33176
+ setRoute(fileRoute);
33177
+ if (fileRoute.view === "blob")
33178
+ renderStandaloneSource({ path: file.path, ref: normalizedRef });
32855
33179
  });
32856
33180
  await activateRepoSidebarPath(currentPath);
32857
33181
  }).catch(() => {
@@ -33985,6 +34309,7 @@ code-viewer query agent-help`
33985
34309
  renderRepoBlobSidebar,
33986
34310
  placeSidebarToggle,
33987
34311
  createFileBreadcrumb,
34312
+ createRepositoryWebLink: createRepositoryWebLink2,
33988
34313
  createFileDetailMeta,
33989
34314
  createOpenPathButton,
33990
34315
  createMoveToTrashButton,
@@ -34003,7 +34328,6 @@ code-viewer query agent-help`
34003
34328
  const VIRTUAL_SOURCE_HIGHLIGHT_MAX_LINE_LENGTH = 2000;
34004
34329
  const UNKNOWN_TEXT_SNIFF_BYTES = 8192;
34005
34330
  const SOURCE_LOADING_SLOW_THRESHOLD_MS = 3000;
34006
- let sourceShikiLoadPromise = null;
34007
34331
  let PREFERRED_SOURCE_TAB = null;
34008
34332
  let SOURCE_CURSOR = null;
34009
34333
  let SOURCE_CURSOR_ROWS = [];
@@ -34016,8 +34340,13 @@ code-viewer query agent-help`
34016
34340
  if (sourceRow)
34017
34341
  return sourceRow.getBoundingClientRect().height || 20;
34018
34342
  const preview = document.querySelector("#content .gdp-markdown-preview:not([hidden])");
34019
- const lineHeight = Number.parseFloat(getComputedStyle(preview || document.body).lineHeight);
34020
- return Number.isFinite(lineHeight) && lineHeight > 0 ? lineHeight : 20;
34343
+ if (preview) {
34344
+ const previewLineHeight = Number.parseFloat(getComputedStyle(preview).lineHeight);
34345
+ if (Number.isFinite(previewLineHeight) && previewLineHeight > 0)
34346
+ return previewLineHeight;
34347
+ }
34348
+ const configuredLineHeight = Number.parseFloat(getComputedStyle(document.body).getPropertyValue("--code-line-height"));
34349
+ return Number.isFinite(configuredLineHeight) && configuredLineHeight > 0 ? configuredLineHeight : VIRTUAL_SOURCE_ROW_HEIGHT;
34021
34350
  }
34022
34351
  function hasVisibleSourceCodeSurface() {
34023
34352
  return Array.from(document.querySelectorAll("#content .gdp-source-virtual-scroller, #content .gdp-source-table")).some((item) => item.offsetParent !== null);
@@ -34058,8 +34387,9 @@ code-viewer query agent-help`
34058
34387
  }
34059
34388
  function visibleSourceLineFallback() {
34060
34389
  const scroller = findMainScrollTarget();
34390
+ const rowHeight = sourceLineScrollAmount() || VIRTUAL_SOURCE_ROW_HEIGHT;
34061
34391
  if (scroller)
34062
- return Math.max(1, Math.floor(scroller.scrollTop / VIRTUAL_SOURCE_ROW_HEIGHT) + 1);
34392
+ return Math.max(1, Math.floor(scroller.scrollTop / rowHeight) + 1);
34063
34393
  const rows = $$("#content .gdp-source-table tr[data-line]");
34064
34394
  const contentTop = document.querySelector("#content")?.getBoundingClientRect().top ?? 0;
34065
34395
  const row = rows.find((item) => item.getBoundingClientRect().bottom >= Math.max(0, contentTop));
@@ -34083,8 +34413,9 @@ code-viewer query agent-help`
34083
34413
  function scrollSourceCursorIntoView(cursor, edge = "nearest") {
34084
34414
  const scroller = findMainScrollTarget();
34085
34415
  if (scroller) {
34086
- const top = (cursor.line - 1) * VIRTUAL_SOURCE_ROW_HEIGHT;
34087
- const bottom = top + VIRTUAL_SOURCE_ROW_HEIGHT;
34416
+ const rowHeight = sourceLineScrollAmount() || VIRTUAL_SOURCE_ROW_HEIGHT;
34417
+ const top = (cursor.line - 1) * rowHeight;
34418
+ const bottom = top + rowHeight;
34088
34419
  const before = scroller.scrollTop;
34089
34420
  if (edge === "center")
34090
34421
  scroller.scrollTop = Math.max(0, top - Math.round(scroller.clientHeight / 2));
@@ -34177,86 +34508,13 @@ code-viewer query agent-help`
34177
34508
  focusMainPanel();
34178
34509
  return true;
34179
34510
  }
34180
- const SOURCE_SHIKI_LANGS = Array.from(new Set([
34181
- "asciidoc",
34182
- "astro",
34183
- "bash",
34184
- "bibtex",
34185
- "c",
34186
- "clojure",
34187
- "cmake",
34188
- "cpp",
34189
- "csharp",
34190
- "css",
34191
- "dart",
34192
- "diff",
34193
- "dockerfile",
34194
- "dotenv",
34195
- "elixir",
34196
- "erb",
34197
- "erlang",
34198
- "fortran",
34199
- "go",
34200
- "gradle",
34201
- "graphql",
34202
- "haml",
34203
- "handlebars",
34204
- "haskell",
34205
- "html",
34206
- "ini",
34207
- "java",
34208
- "javascript",
34209
- "json",
34210
- "jsonc",
34211
- "julia",
34212
- "kotlin",
34213
- "less",
34214
- "liquid",
34215
- "lua",
34216
- "make",
34217
- "markdown",
34218
- "nix",
34219
- "ocaml",
34220
- "pascal",
34221
- "perl",
34222
- "php",
34223
- "powershell",
34224
- "prisma",
34225
- "properties",
34226
- "protobuf",
34227
- "pug",
34228
- "python",
34229
- "r",
34230
- "rst",
34231
- "ruby",
34232
- "rust",
34233
- "sass",
34234
- "scala",
34235
- "scss",
34236
- "sql",
34237
- "swift",
34238
- "terraform",
34239
- "tex",
34240
- "toml",
34241
- "twig",
34242
- "typescript",
34243
- "vim",
34244
- "vue",
34245
- "xml",
34246
- "yaml"
34247
- ]));
34248
- function loadSourceShikiHighlighter() {
34249
- if (!sourceShikiLoadPromise) {
34250
- sourceShikiLoadPromise = import("/shiki.js").then((mod) => {
34251
- const typed = mod;
34252
- const langs = typed.bundledLanguages ? SOURCE_SHIKI_LANGS.filter((lang) => !!typed.bundledLanguages?.[lang]) : SOURCE_SHIKI_LANGS;
34253
- return typed.createHighlighter({
34254
- themes: ["github-light", "github-dark"],
34255
- langs
34256
- });
34257
- }).catch(() => null);
34258
- }
34259
- return sourceShikiLoadPromise;
34511
+ function loadSourceShikiHighlighter(lang) {
34512
+ if (deps.loadSourceHighlighter)
34513
+ return deps.loadSourceHighlighter(lang);
34514
+ return loadShikiHighlighter({
34515
+ themes: ["github-light", "github-dark"],
34516
+ langs: [lang]
34517
+ });
34260
34518
  }
34261
34519
  function sourceShikiLines(textValue, lang, highlighter) {
34262
34520
  try {
@@ -34275,6 +34533,57 @@ code-viewer query agent-help`
34275
34533
  return null;
34276
34534
  }
34277
34535
  }
34536
+ function scheduleSourceHighlight(table2, target, textValue, lang, signal) {
34537
+ setTimeout(() => {
34538
+ if (signal?.aborted || !table2.isConnected || !STATE.syntaxHighlight || !sourceTargetsEqual(sourceTargetFromRoute(), target))
34539
+ return;
34540
+ loadSourceShikiHighlighter(lang).then((highlighter) => {
34541
+ if (!highlighter || signal?.aborted || !table2.isConnected || !STATE.syntaxHighlight || !sourceTargetsEqual(sourceTargetFromRoute(), target))
34542
+ return;
34543
+ const highlightedLines = sourceShikiLines(textValue, lang, highlighter);
34544
+ if (!highlightedLines || signal?.aborted || !table2.isConnected || !sourceTargetsEqual(sourceTargetFromRoute(), target))
34545
+ return;
34546
+ const cells = table2.querySelectorAll(".gdp-source-line-code");
34547
+ cells.forEach((cell, index) => {
34548
+ if (highlightedLines[index] == null)
34549
+ return;
34550
+ cell.innerHTML = highlightedLines[index] || " ";
34551
+ cell.classList.add("shiki");
34552
+ });
34553
+ }).catch((err) => {
34554
+ console.error("Failed to apply source syntax highlighting", err);
34555
+ });
34556
+ }, 0);
34557
+ }
34558
+ function scheduleMarkdownPreviewHighlight(preview, target, textValue, signal, onReplace) {
34559
+ setTimeout(() => {
34560
+ if (signal?.aborted || !preview.isConnected || !STATE.syntaxHighlight || !sourceTargetsEqual(sourceTargetFromRoute(), target))
34561
+ return;
34562
+ (deps.renderMarkdownPreview ?? renderMarkdownPreview)(textValue, target, {
34563
+ syntaxHighlight: true,
34564
+ signal,
34565
+ onNavigateMarkdown: (path, ref) => {
34566
+ setRoute({
34567
+ screen: "file",
34568
+ path,
34569
+ ref,
34570
+ view: "blob",
34571
+ range: currentRange()
34572
+ });
34573
+ renderStandaloneSource({ path, ref });
34574
+ }
34575
+ }).then((next) => {
34576
+ if (signal?.aborted || !preview.isConnected || !sourceTargetsEqual(sourceTargetFromRoute(), target))
34577
+ return;
34578
+ next.dataset.sourcePane = "preview";
34579
+ next.hidden = preview.hidden;
34580
+ preview.replaceWith(next);
34581
+ onReplace(next);
34582
+ }).catch((err) => {
34583
+ console.error("Failed to apply preview syntax highlighting", err);
34584
+ });
34585
+ }, 0);
34586
+ }
34278
34587
  function setPreferredSourceTab(tab) {
34279
34588
  PREFERRED_SOURCE_TAB = tab;
34280
34589
  }
@@ -34438,24 +34747,14 @@ code-viewer query agent-help`
34438
34747
  card.appendChild(view);
34439
34748
  setSourceCardState(card, "done");
34440
34749
  }
34441
- function renderSourceInternalPath(card, target, kind) {
34750
+ function renderSourceInternalPath(card, target) {
34442
34751
  const body = card.querySelector(".gdp-file-detail-body, .d2h-files-diff, .d2h-file-diff, .gdp-media, .gdp-source-viewer");
34443
34752
  const info = createSourceFileInfo(target, "internal metadata", {
34444
34753
  loadMeta: false
34445
34754
  });
34446
- const extraChildren = [info];
34447
- if (kind === "code-viewer") {
34448
- const link2 = document.createElement("a");
34449
- link2.className = "gdp-btn gdp-btn-sm gdp-source-download";
34450
- link2.href = buildRawFileUrl(target);
34451
- link2.textContent = "Download raw";
34452
- link2.target = "_blank";
34453
- link2.rel = "noreferrer";
34454
- extraChildren.push(link2);
34455
- }
34456
34755
  const view = renderUnsupportedPreview({
34457
- message: kind === "code-viewer" ? "This path is managed by code-viewer and is not previewed from the file viewer." : "Git internal metadata is not previewed from the file viewer.",
34458
- extraChildren
34756
+ message: "Git internal metadata is not previewed from the file viewer.",
34757
+ extraChildren: [info]
34459
34758
  });
34460
34759
  if (body)
34461
34760
  body.replaceWith(view);
@@ -34512,7 +34811,7 @@ code-viewer query agent-help`
34512
34811
  const tabs = document.createElement("div");
34513
34812
  tabs.className = "gdp-source-tabs";
34514
34813
  const { codeButton, previewButton } = appendFileViewTabs(tabs, { currentRange, setRoute, setPreferredSourceTab }, target, active, {
34515
- includeFileTabs: STATE.route.screen === "file",
34814
+ includeFileTabs: STATE.route.screen === "file" && sourceInternalPathKind(target.path) !== "code-viewer",
34516
34815
  previewable: !!options.previewable || active === "preview",
34517
34816
  sourceTabClick: "manual",
34518
34817
  ...textValue != null ? { sourceCopyButton: createSourceCopyButton(textValue) } : {}
@@ -34536,9 +34835,9 @@ code-viewer query agent-help`
34536
34835
  header.textContent = `${target.path} @ ${target.ref}`;
34537
34836
  }
34538
34837
  const lang = inferLang(target.path);
34838
+ const sourceShikiLang = normalizeSourceShikiLang(lang);
34539
34839
  const usesVirtualSource = shouldVirtualizeSource(textValue, lines) && !isVirtualSourceDisabled();
34540
34840
  const hljsRef = STATE.syntaxHighlight && usesVirtualSource ? await loadSyntaxHighlighter() : null;
34541
- const sourceShikiRef = STATE.syntaxHighlight && !usesVirtualSource ? await loadSourceShikiHighlighter() : null;
34542
34841
  if (signal?.aborted)
34543
34842
  return false;
34544
34843
  const previewable = isPreviewableSource(target.path);
@@ -34554,8 +34853,8 @@ code-viewer query agent-help`
34554
34853
  tabsHost.hidden = false;
34555
34854
  tabsHost.replaceChildren(tabs3);
34556
34855
  }
34557
- const preview = previewKind === "html" ? renderHtmlPreview(target, textValue) : await renderMarkdownPreview(textValue, target, {
34558
- syntaxHighlight: STATE.syntaxHighlight,
34856
+ let preview = previewKind === "html" ? renderHtmlPreview(target, textValue) : await (deps.renderMarkdownPreview ?? renderMarkdownPreview)(textValue, target, {
34857
+ syntaxHighlight: false,
34559
34858
  signal,
34560
34859
  onNavigateMarkdown: (path, ref) => {
34561
34860
  setRoute({
@@ -34571,9 +34870,19 @@ code-viewer query agent-help`
34571
34870
  if (signal?.aborted)
34572
34871
  return false;
34573
34872
  preview.dataset.sourcePane = "preview";
34873
+ let previewHighlightScheduled = false;
34874
+ const ensurePreviewHighlight = () => {
34875
+ if (previewKind === "html" || !STATE.syntaxHighlight || previewHighlightScheduled)
34876
+ return;
34877
+ previewHighlightScheduled = true;
34878
+ scheduleMarkdownPreviewHighlight(preview, target, textValue, signal, (next) => {
34879
+ preview = next;
34880
+ });
34881
+ };
34574
34882
  applyRenderedSourceTab(initialSourceTab, codeButton2, previewButton2, virtualCode, preview, false);
34575
34883
  previewButton2?.addEventListener("click", () => {
34576
34884
  applyRenderedSourceTab("preview", codeButton2, previewButton2, virtualCode, preview);
34885
+ ensurePreviewHighlight();
34577
34886
  });
34578
34887
  codeButton2?.addEventListener("click", () => {
34579
34888
  applyRenderedSourceTab("code", codeButton2, previewButton2, virtualCode, preview);
@@ -34586,6 +34895,8 @@ code-viewer query agent-help`
34586
34895
  body.replaceWith(view);
34587
34896
  else
34588
34897
  card.appendChild(view);
34898
+ if (initialSourceTab === "preview")
34899
+ ensurePreviewHighlight();
34589
34900
  return true;
34590
34901
  }
34591
34902
  const { tabs: tabs2 } = createSourceTabs(target, "code", textValue, {
@@ -34611,8 +34922,6 @@ code-viewer query agent-help`
34611
34922
  table2.className = "gdp-source-table";
34612
34923
  table2.dataset.sourcePane = "code";
34613
34924
  const tbody = document.createElement("tbody");
34614
- const sourceShikiLang = normalizeSourceShikiLang(lang);
34615
- const shikiLines = sourceShikiRef && sourceShikiLang ? sourceShikiLines(textValue, sourceShikiLang, sourceShikiRef) : null;
34616
34925
  for (let index = 0;index < lines.length; index++) {
34617
34926
  if (signal?.aborted)
34618
34927
  return false;
@@ -34627,12 +34936,7 @@ code-viewer query agent-help`
34627
34936
  bindSourceLineNumber(num, card, target, index + 1);
34628
34937
  const code2 = document.createElement("td");
34629
34938
  code2.className = "gdp-source-line-code";
34630
- if (shikiLines && shikiLines[index] != null) {
34631
- code2.innerHTML = shikiLines[index] || " ";
34632
- code2.classList.add("shiki");
34633
- } else {
34634
- code2.textContent = line || " ";
34635
- }
34939
+ code2.textContent = line || " ";
34636
34940
  tr.appendChild(num);
34637
34941
  tr.appendChild(code2);
34638
34942
  tbody.appendChild(tr);
@@ -34649,8 +34953,8 @@ code-viewer query agent-help`
34649
34953
  tabsHost.replaceChildren(tabs);
34650
34954
  }
34651
34955
  if (previewable) {
34652
- const preview = previewKind === "html" ? renderHtmlPreview(target, textValue) : await renderMarkdownPreview(textValue, target, {
34653
- syntaxHighlight: STATE.syntaxHighlight,
34956
+ let preview = previewKind === "html" ? renderHtmlPreview(target, textValue) : await (deps.renderMarkdownPreview ?? renderMarkdownPreview)(textValue, target, {
34957
+ syntaxHighlight: false,
34654
34958
  signal,
34655
34959
  onNavigateMarkdown: (path, ref) => {
34656
34960
  setRoute({
@@ -34666,9 +34970,19 @@ code-viewer query agent-help`
34666
34970
  if (signal?.aborted)
34667
34971
  return false;
34668
34972
  preview.dataset.sourcePane = "preview";
34973
+ let previewHighlightScheduled = false;
34974
+ const ensurePreviewHighlight = () => {
34975
+ if (previewKind === "html" || !STATE.syntaxHighlight || previewHighlightScheduled)
34976
+ return;
34977
+ previewHighlightScheduled = true;
34978
+ scheduleMarkdownPreviewHighlight(preview, target, textValue, signal, (next) => {
34979
+ preview = next;
34980
+ });
34981
+ };
34669
34982
  applyRenderedSourceTab(initialSourceTab, codeButton, previewButton, table2, preview, false);
34670
34983
  previewButton?.addEventListener("click", () => {
34671
34984
  applyRenderedSourceTab("preview", codeButton, previewButton, table2, preview);
34985
+ ensurePreviewHighlight();
34672
34986
  });
34673
34987
  codeButton?.addEventListener("click", () => {
34674
34988
  applyRenderedSourceTab("code", codeButton, previewButton, table2, preview);
@@ -34683,6 +34997,10 @@ code-viewer query agent-help`
34683
34997
  body.replaceWith(view);
34684
34998
  else
34685
34999
  card.appendChild(view);
35000
+ if (STATE.syntaxHighlight && sourceShikiLang)
35001
+ scheduleSourceHighlight(table2, target, textValue, sourceShikiLang, signal);
35002
+ if (initialSourceTab === "preview")
35003
+ ensurePreviewHighlight();
34686
35004
  return true;
34687
35005
  }
34688
35006
  if (header)
@@ -34694,6 +35012,8 @@ code-viewer query agent-help`
34694
35012
  body.replaceWith(view);
34695
35013
  else
34696
35014
  card.appendChild(view);
35015
+ if (STATE.syntaxHighlight && sourceShikiLang)
35016
+ scheduleSourceHighlight(table2, target, textValue, sourceShikiLang, signal);
34697
35017
  return true;
34698
35018
  }
34699
35019
  function shouldVirtualizeSource(textValue, lines) {
@@ -34885,8 +35205,10 @@ code-viewer query agent-help`
34885
35205
  matches = nextMatches;
34886
35206
  active = matches.length ? Math.max(0, Math.min(active, matches.length - 1)) : -1;
34887
35207
  count.textContent = matches.length ? `${active + 1} / ${matches.length}` : "0 / 0";
34888
- if (active >= 0)
34889
- scroller.scrollTop = Math.max(0, (matches[active].line - 1) * VIRTUAL_SOURCE_ROW_HEIGHT - VIRTUAL_SOURCE_ROW_HEIGHT * 3);
35208
+ if (active >= 0) {
35209
+ const rowHeight = sourceLineScrollAmount() || VIRTUAL_SOURCE_ROW_HEIGHT;
35210
+ scroller.scrollTop = Math.max(0, (matches[active].line - 1) * rowHeight - rowHeight * 3);
35211
+ }
34890
35212
  renderFn();
34891
35213
  }).catch(() => {
34892
35214
  if (version !== searchVersion)
@@ -34907,7 +35229,8 @@ code-viewer query agent-help`
34907
35229
  return;
34908
35230
  active = (active + direction + matches.length) % matches.length;
34909
35231
  count.textContent = `${active + 1} / ${matches.length}`;
34910
- scroller.scrollTop = Math.max(0, (matches[active].line - 1) * VIRTUAL_SOURCE_ROW_HEIGHT - VIRTUAL_SOURCE_ROW_HEIGHT * 3);
35232
+ const rowHeight = sourceLineScrollAmount() || VIRTUAL_SOURCE_ROW_HEIGHT;
35233
+ scroller.scrollTop = Math.max(0, (matches[active].line - 1) * rowHeight - rowHeight * 3);
34911
35234
  renderFn();
34912
35235
  };
34913
35236
  input2.addEventListener("input", () => {
@@ -35001,7 +35324,7 @@ code-viewer query agent-help`
35001
35324
  scroller.setAttribute("aria-label", `${target.path} source code`);
35002
35325
  const spacer = document.createElement("div");
35003
35326
  spacer.className = "gdp-source-virtual-spacer";
35004
- spacer.style.height = `${Math.max(1, lines.length * VIRTUAL_SOURCE_ROW_HEIGHT)}px`;
35327
+ spacer.style.height = `${Math.max(1, lines.length * (sourceLineScrollAmount() || VIRTUAL_SOURCE_ROW_HEIGHT))}px`;
35005
35328
  const windowEl = document.createElement("div");
35006
35329
  windowEl.className = "gdp-source-virtual-window";
35007
35330
  spacer.appendChild(windowEl);
@@ -35010,19 +35333,23 @@ code-viewer query agent-help`
35010
35333
  let raf = 0;
35011
35334
  let renderedStart = -1;
35012
35335
  let renderedEnd = -1;
35336
+ let renderedRowHeight = -1;
35013
35337
  let search = null;
35014
35338
  const render = () => {
35015
35339
  raf = 0;
35340
+ const rowHeight = sourceLineScrollAmount() || VIRTUAL_SOURCE_ROW_HEIGHT;
35341
+ spacer.style.height = `${Math.max(1, lines.length * rowHeight)}px`;
35016
35342
  const viewportHeight = scroller.clientHeight || window.innerHeight;
35017
35343
  const overscan = 20;
35018
- const start = Math.max(0, Math.floor(scroller.scrollTop / VIRTUAL_SOURCE_ROW_HEIGHT) - overscan);
35019
- const end = Math.min(lines.length, Math.ceil((scroller.scrollTop + viewportHeight) / VIRTUAL_SOURCE_ROW_HEIGHT) + overscan);
35020
- if (start === renderedStart && end === renderedEnd)
35344
+ const start = Math.max(0, Math.floor(scroller.scrollTop / rowHeight) - overscan);
35345
+ const end = Math.min(lines.length, Math.ceil((scroller.scrollTop + viewportHeight) / rowHeight) + overscan);
35346
+ if (start === renderedStart && end === renderedEnd && rowHeight === renderedRowHeight)
35021
35347
  return;
35022
35348
  renderedStart = start;
35023
35349
  renderedEnd = end;
35350
+ renderedRowHeight = rowHeight;
35024
35351
  windowEl.replaceChildren();
35025
- windowEl.style.transform = `translateY(${start * VIRTUAL_SOURCE_ROW_HEIGHT}px)`;
35352
+ windowEl.style.transform = `translateY(${start * rowHeight}px)`;
35026
35353
  const fragment = document.createDocumentFragment();
35027
35354
  for (let index = start;index < end; index++) {
35028
35355
  const row = document.createElement("div");
@@ -35138,7 +35465,8 @@ code-viewer query agent-help`
35138
35465
  const updateTotals = () => {
35139
35466
  SOURCE_CURSOR_TOTALS.set(sourceCursorKey(target), totalRows);
35140
35467
  summary.textContent = (complete ? totalRows.toLocaleString() : `${lines.size.toLocaleString()}+`) + " lines loaded from " + formatBytes(size) + ". More rows load as you scroll.";
35141
- spacer.style.height = `${Math.max(1, totalRows * VIRTUAL_SOURCE_ROW_HEIGHT)}px`;
35468
+ const rowHeight = sourceLineScrollAmount() || VIRTUAL_SOURCE_ROW_HEIGHT;
35469
+ spacer.style.height = `${Math.max(1, totalRows * rowHeight)}px`;
35142
35470
  };
35143
35471
  const loadPage = (line) => {
35144
35472
  if (signal?.aborted || complete && line > totalRows)
@@ -35175,20 +35503,24 @@ code-viewer query agent-help`
35175
35503
  let raf = 0;
35176
35504
  let renderedStart = -1;
35177
35505
  let renderedEnd = -1;
35506
+ let renderedRowHeight = -1;
35178
35507
  let search = null;
35179
35508
  let searchController = null;
35180
35509
  const render = () => {
35181
35510
  raf = 0;
35511
+ const rowHeight = sourceLineScrollAmount() || VIRTUAL_SOURCE_ROW_HEIGHT;
35512
+ spacer.style.height = `${Math.max(1, totalRows * rowHeight)}px`;
35182
35513
  const viewportHeight = scroller.clientHeight || window.innerHeight;
35183
35514
  const overscan = 20;
35184
- const start = Math.max(0, Math.floor(scroller.scrollTop / VIRTUAL_SOURCE_ROW_HEIGHT) - overscan);
35185
- const end = Math.min(totalRows, Math.ceil((scroller.scrollTop + viewportHeight) / VIRTUAL_SOURCE_ROW_HEIGHT) + overscan);
35186
- if (start === renderedStart && end === renderedEnd)
35515
+ const start = Math.max(0, Math.floor(scroller.scrollTop / rowHeight) - overscan);
35516
+ const end = Math.min(totalRows, Math.ceil((scroller.scrollTop + viewportHeight) / rowHeight) + overscan);
35517
+ if (start === renderedStart && end === renderedEnd && rowHeight === renderedRowHeight)
35187
35518
  return;
35188
35519
  renderedStart = start;
35189
35520
  renderedEnd = end;
35521
+ renderedRowHeight = rowHeight;
35190
35522
  windowEl.replaceChildren();
35191
- windowEl.style.transform = `translateY(${start * VIRTUAL_SOURCE_ROW_HEIGHT}px)`;
35523
+ windowEl.style.transform = `translateY(${start * rowHeight}px)`;
35192
35524
  const fragment = document.createDocumentFragment();
35193
35525
  for (let index = start;index < end; index++) {
35194
35526
  const lineNumber = index + 1;
@@ -35417,8 +35749,8 @@ code-viewer query agent-help`
35417
35749
  scrollStandaloneSourceLine(mounted, lineTargetStart(STATE.route.screen === "file" ? STATE.route.line : undefined));
35418
35750
  return;
35419
35751
  }
35420
- if (mounted && state === "loading")
35421
- return;
35752
+ if (mounted && state === "loading" && mounted._loadPromise)
35753
+ return mounted._loadPromise;
35422
35754
  }
35423
35755
  cancelActiveSourceLoad("navigation");
35424
35756
  const req = ++SOURCE_REQ_SEQ;
@@ -35442,7 +35774,8 @@ code-viewer query agent-help`
35442
35774
  currentRange,
35443
35775
  setRoute,
35444
35776
  setPreferredSourceTab,
35445
- createFileBreadcrumb
35777
+ createFileBreadcrumb,
35778
+ createRepositoryWebLink: createRepositoryWebLink2
35446
35779
  }, target, activeTab, internalKind ? { includeFileTabs: false, previewable: false } : {});
35447
35780
  const pathActions = header.querySelector(".gdp-file-detail-path") || header;
35448
35781
  pathActions.appendChild(createOpenPathButton(target.path, "file-parent", "open parent folder in OS"));
@@ -35453,7 +35786,7 @@ code-viewer query agent-help`
35453
35786
  loadRepo();
35454
35787
  }));
35455
35788
  }
35456
- if (!internalKind) {
35789
+ if (internalKind !== "git") {
35457
35790
  loadRawFileInfo(target).then((meta) => {
35458
35791
  if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35459
35792
  return;
@@ -35478,94 +35811,103 @@ code-viewer query agent-help`
35478
35811
  wrapper.appendChild(detailBody);
35479
35812
  card.appendChild(wrapper);
35480
35813
  mountFileShellCard({ $, repoFileTargetFromRoute, renderRepoBlobSidebar, placeSidebarToggle }, target, card, repoTarget);
35481
- if (internalKind) {
35482
- renderSourceInternalPath(card, target, internalKind);
35814
+ if (internalKind === "git") {
35815
+ renderSourceInternalPath(card, target);
35483
35816
  return;
35484
35817
  }
35485
35818
  const controller = new AbortController;
35486
35819
  ACTIVE_SOURCE_LOAD = { controller, req, target, card };
35487
35820
  renderSourceLoading(card, target, () => cancelActiveSourceLoad("user"));
35488
- try {
35489
- let displayKind = sourceDisplayKind(target.path);
35490
- let rawInfo = null;
35491
- if (displayKind === "unsupported") {
35492
- rawInfo = await loadRawFileInfo(target);
35493
- if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35494
- return;
35495
- if (await sourceLooksTextByContent(target, rawInfo, controller.signal)) {
35496
- displayKind = "text";
35821
+ const sourceLoad = (async () => {
35822
+ try {
35823
+ let displayKind = sourceDisplayKind(target.path);
35824
+ let rawInfo = null;
35825
+ if (displayKind === "unsupported") {
35826
+ rawInfo = await loadRawFileInfo(target);
35827
+ if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35828
+ return;
35829
+ if (await sourceLooksTextByContent(target, rawInfo, controller.signal)) {
35830
+ displayKind = "text";
35831
+ }
35497
35832
  }
35498
- }
35499
- if (displayKind === "unsupported") {
35500
- if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35501
- return;
35502
- finishSourceLoad(req);
35503
- renderSourceUnsupported(card, target);
35504
- return;
35505
- }
35506
- if (displayKind === "image" || displayKind === "video" || displayKind === "audio" || displayKind === "pdf") {
35507
- if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35833
+ if (displayKind === "unsupported") {
35834
+ if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35835
+ return;
35836
+ finishSourceLoad(req);
35837
+ renderSourceUnsupported(card, target);
35508
35838
  return;
35509
- finishSourceLoad(req);
35510
- renderSourceMedia(card, target, displayKind);
35511
- return;
35512
- }
35513
- if (displayKind === "text") {
35514
- const meta = rawInfo ?? await loadRawFileInfo(target);
35515
- if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35839
+ }
35840
+ if (displayKind === "image" || displayKind === "video" || displayKind === "audio" || displayKind === "pdf") {
35841
+ if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35842
+ return;
35843
+ finishSourceLoad(req);
35844
+ renderSourceMedia(card, target, displayKind);
35516
35845
  return;
35517
- if (!isVirtualSourceDisabled() && meta.size != null && meta.size >= VIRTUAL_SOURCE_SIZE_THRESHOLD) {
35518
- const rendered2 = await renderPagedSourceText(card, target, meta.size, controller.signal);
35846
+ }
35847
+ if (displayKind === "text") {
35848
+ const meta = rawInfo ?? await loadRawFileInfo(target);
35849
+ if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35850
+ return;
35851
+ if (!isVirtualSourceDisabled() && meta.size != null && meta.size >= VIRTUAL_SOURCE_SIZE_THRESHOLD) {
35852
+ const rendered2 = await renderPagedSourceText(card, target, meta.size, controller.signal);
35853
+ if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35854
+ return;
35855
+ if (!rendered2) {
35856
+ finishSourceLoad(req);
35857
+ renderSourceError(card, target, `Cannot load ${target.path} at ${target.ref}`);
35858
+ return;
35859
+ }
35860
+ scrollStandaloneSourceLine(card, lineTargetStart(STATE.route.screen === "file" ? STATE.route.line : undefined));
35861
+ setSourceCardState(card, "done");
35862
+ finishSourceLoad(req);
35863
+ return;
35864
+ }
35865
+ const response = await trackLoad(fetch(buildRawFileUrl(target), { signal: controller.signal }));
35519
35866
  if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35520
35867
  return;
35521
- if (!rendered2) {
35868
+ if (!response.ok) {
35522
35869
  finishSourceLoad(req);
35523
35870
  renderSourceError(card, target, `Cannot load ${target.path} at ${target.ref}`);
35524
35871
  return;
35525
35872
  }
35873
+ const textValue = await response.text();
35874
+ if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35875
+ return;
35876
+ const rendered = await renderSourceText(card, target, textValue, controller.signal);
35877
+ if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35878
+ return;
35879
+ if (!rendered)
35880
+ return;
35526
35881
  scrollStandaloneSourceLine(card, lineTargetStart(STATE.route.screen === "file" ? STATE.route.line : undefined));
35527
35882
  setSourceCardState(card, "done");
35528
35883
  finishSourceLoad(req);
35529
- return;
35530
35884
  }
35531
- const response = await trackLoad(fetch(buildRawFileUrl(target), { signal: controller.signal }));
35885
+ } catch (err) {
35532
35886
  if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35533
35887
  return;
35534
- if (!response.ok) {
35535
- finishSourceLoad(req);
35536
- renderSourceError(card, target, `Cannot load ${target.path} at ${target.ref}`);
35888
+ finishSourceLoad(req);
35889
+ if (isAbortError3(err)) {
35890
+ renderSourceCancelled(card, target);
35537
35891
  return;
35538
35892
  }
35539
- const textValue = await response.text();
35540
- if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35541
- return;
35542
- const rendered = await renderSourceText(card, target, textValue, controller.signal);
35543
- if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35544
- return;
35545
- if (!rendered)
35546
- return;
35547
- scrollStandaloneSourceLine(card, lineTargetStart(STATE.route.screen === "file" ? STATE.route.line : undefined));
35548
- setSourceCardState(card, "done");
35549
- finishSourceLoad(req);
35550
- }
35551
- } catch (err) {
35552
- if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35553
- return;
35554
- finishSourceLoad(req);
35555
- if (isAbortError3(err)) {
35556
- renderSourceCancelled(card, target);
35557
- return;
35893
+ renderSourceError(card, target, `Cannot load ${target.path} at ${target.ref}`);
35558
35894
  }
35559
- renderSourceError(card, target, `Cannot load ${target.path} at ${target.ref}`);
35560
- }
35895
+ })();
35896
+ const completed = sourceLoad.finally(() => {
35897
+ if (card._loadPromise === completed)
35898
+ delete card._loadPromise;
35899
+ });
35900
+ card._loadPromise = completed;
35901
+ return completed;
35561
35902
  }
35562
35903
  function scrollStandaloneSourceLine(card, line) {
35563
35904
  if (!line || line < 1)
35564
35905
  return;
35565
35906
  const virtualScroller = card.querySelector(".gdp-source-virtual-scroller");
35566
35907
  if (virtualScroller) {
35567
- const centeredOffset = virtualScroller.clientHeight / 2 - VIRTUAL_SOURCE_ROW_HEIGHT / 2;
35568
- virtualScroller.scrollTop = Math.max(0, (line - 1) * VIRTUAL_SOURCE_ROW_HEIGHT - Math.max(0, centeredOffset));
35908
+ const rowHeight = sourceLineScrollAmount() || VIRTUAL_SOURCE_ROW_HEIGHT;
35909
+ const centeredOffset = virtualScroller.clientHeight / 2 - rowHeight / 2;
35910
+ virtualScroller.scrollTop = Math.max(0, (line - 1) * rowHeight - Math.max(0, centeredOffset));
35569
35911
  virtualScroller.__gdpRenderVirtualSource?.();
35570
35912
  return;
35571
35913
  }
@@ -35689,6 +36031,8 @@ code-viewer query agent-help`
35689
36031
  let PENDING_G_SCOPE = null;
35690
36032
  let PENDING_G_UNTIL = 0;
35691
36033
  let PROJECT_NAME = "";
36034
+ let PROJECT_BRANCH = "";
36035
+ let REPO_WEB_URL = null;
35692
36036
  let APP_SETTINGS = { version: 1 };
35693
36037
  let VIEW_STATE = {
35694
36038
  version: 1,
@@ -35833,11 +36177,14 @@ code-viewer query agent-help`
35833
36177
  }
35834
36178
  }
35835
36179
  function setProjectBranch(branch) {
36180
+ PROJECT_BRANCH = branch;
35836
36181
  const el = document.querySelector("#project-branch");
35837
36182
  if (!el)
35838
36183
  return;
35839
36184
  el.hidden = !branch;
35840
- el.textContent = branch;
36185
+ const name = el.querySelector(".project-branch-name");
36186
+ if (name)
36187
+ name.textContent = branch;
35841
36188
  el.title = branch ? `Current branch: ${branch}` : "";
35842
36189
  }
35843
36190
  function mergeLocalSettings(patch) {
@@ -36024,7 +36371,14 @@ code-viewer query agent-help`
36024
36371
  return normalizeViewerFontSize(APP_SETTINGS.codeFontSize);
36025
36372
  }
36026
36373
  function applyCodeFontSize(size = savedCodeFontSize()) {
36374
+ const previousLineHeight = Number.parseFloat(getComputedStyle(document.body).getPropertyValue("--code-line-height"));
36027
36375
  document.body.dataset.codeFontSize = size;
36376
+ const nextLineHeight = Number.parseFloat(getComputedStyle(document.body).getPropertyValue("--code-line-height"));
36377
+ document.querySelectorAll(".gdp-source-virtual-scroller").forEach((scroller) => {
36378
+ if (Number.isFinite(previousLineHeight) && previousLineHeight > 0 && Number.isFinite(nextLineHeight) && nextLineHeight > 0)
36379
+ scroller.scrollTop *= nextLineHeight / previousLineHeight;
36380
+ scroller.__gdpRenderVirtualSource?.();
36381
+ });
36028
36382
  }
36029
36383
  function savedSidebarFontSizeSetting() {
36030
36384
  return normalizeViewerFontSize(APP_SETTINGS.sidebarFontSize);
@@ -36057,10 +36411,11 @@ code-viewer query agent-help`
36057
36411
  const settings = await res.json();
36058
36412
  setProjectName(settings.project || "");
36059
36413
  setProjectBranch(settings.branch || "");
36414
+ REPO_WEB_URL = settings.repo_web_url;
36060
36415
  const repoLink = document.querySelector("#repo-web-link");
36061
- if (repoLink && settings.repo_web_url) {
36062
- repoLink.href = settings.repo_web_url;
36063
- repoLink.hidden = false;
36416
+ if (repoLink) {
36417
+ repoLink.href = settings.repo_web_url || "#";
36418
+ repoLink.hidden = !settings.repo_web_url;
36064
36419
  }
36065
36420
  SERVER_SCOPE_OMIT_DIRS_DEFAULT = normalizeScopeOmitDirs(settings.scope.omit_dirs_effective);
36066
36421
  SERVER_SCOPE_EXCLUDE_NAMES_DEFAULT = normalizeScopeExcludeNames(settings.scope.exclude_names_effective);
@@ -36157,7 +36512,24 @@ code-viewer query agent-help`
36157
36512
  const LINE_REF_PILL = createLineRefPill({
36158
36513
  onClose: () => {
36159
36514
  clearLineSelection();
36160
- }
36515
+ },
36516
+ githubUrlForSelection: (path, start, end) => {
36517
+ const route = STATE.route;
36518
+ const ref = route.screen === "file" ? route.commit || route.ref : route.screen === "diff" ? route.range.to : "worktree";
36519
+ const target = buildRepositoryWebTarget(REPO_WEB_URL, {
36520
+ ref,
36521
+ fallbackRef: PROJECT_BRANCH,
36522
+ path,
36523
+ kind: "blob",
36524
+ start,
36525
+ end
36526
+ });
36527
+ return target?.provider === "github" ? target.url : null;
36528
+ },
36529
+ copyReferenceLabel: () => uiText().global.copyLineReference,
36530
+ lineCountLabel: (count) => uiText().global.selectedLineCount(count),
36531
+ githubOpenTitle: () => uiText().global.githubSelectionOpen,
36532
+ githubCopyTitle: () => uiText().global.githubSelectionCopy
36161
36533
  });
36162
36534
  const DIFF_LINE_SELECT = createDiffLineSelect({ pill: LINE_REF_PILL });
36163
36535
  function clearRenderedSourceLineTargets() {
@@ -36308,6 +36680,7 @@ code-viewer query agent-help`
36308
36680
  renderRepoBlobSidebar: (path, ref) => REPO_VIEW.renderRepoBlobSidebar(path, ref),
36309
36681
  placeSidebarToggle,
36310
36682
  createFileBreadcrumb: (path, ref) => DIFF_VIEW.createFileBreadcrumb(path, ref),
36683
+ createRepositoryWebLink: createFileRepositoryWebLink,
36311
36684
  createFileDetailMeta: (target, meta) => REPO_VIEW.createFileDetailMeta(target, meta),
36312
36685
  createOpenPathButton,
36313
36686
  createMoveToTrashButton: (path, onDeleted) => REPO_VIEW.createMoveToTrashButton(path, onDeleted),
@@ -36341,7 +36714,7 @@ code-viewer query agent-help`
36341
36714
  currentRange,
36342
36715
  trackLoad,
36343
36716
  getSyntaxHighlight: () => STATE.syntaxHighlight,
36344
- loadSourceShikiHighlighter: () => SOURCE_VIEW.loadSourceShikiHighlighter(),
36717
+ loadSourceShikiHighlighter: (lang) => SOURCE_VIEW.loadSourceShikiHighlighter(lang),
36345
36718
  sourceShikiLines: (textValue, lang, highlighter) => SOURCE_VIEW.sourceShikiLines(textValue, lang, highlighter),
36346
36719
  inferLang: (path) => SOURCE_VIEW.inferLang(path),
36347
36720
  currentSourceLineTarget: (target) => SOURCE_VIEW.currentSourceLineTarget(target),
@@ -36349,6 +36722,7 @@ code-viewer query agent-help`
36349
36722
  bindSourceLineNumber: (num, card, target, line) => SOURCE_VIEW.bindSourceLineNumber(num, card, target, line),
36350
36723
  setPreferredSourceTab: (tab) => SOURCE_VIEW.setPreferredSourceTab(tab),
36351
36724
  createFileBreadcrumb: (path, ref) => DIFF_VIEW.createFileBreadcrumb(path, ref),
36725
+ createRepositoryWebLink: createFileRepositoryWebLink,
36352
36726
  removeStandaloneSource,
36353
36727
  placeSidebarToggle,
36354
36728
  escapeHtml: escapeHtml3,
@@ -36423,6 +36797,14 @@ code-viewer query agent-help`
36423
36797
  const text3 = uiText().repo;
36424
36798
  return submodule ? { label: text3.submoduleLabel, title: text3.submoduleTitle } : { label: text3.gitlinkLabel, title: text3.gitlinkTitle };
36425
36799
  },
36800
+ repositoryWebTarget: (path, ref) => buildRepositoryWebTarget(REPO_WEB_URL, {
36801
+ ref,
36802
+ fallbackRef: PROJECT_BRANCH,
36803
+ path,
36804
+ kind: "tree"
36805
+ }),
36806
+ openGithubLabel: () => uiText().repo.openGithub,
36807
+ openRepositoryWebLabel: () => uiText().repo.openRepositoryWeb,
36426
36808
  fileBadge: (status) => DIFF_VIEW.fileBadge(status)
36427
36809
  });
36428
36810
  const {
@@ -36465,9 +36847,7 @@ code-viewer query agent-help`
36465
36847
  queryHistory: "query history",
36466
36848
  settings: "viewer settings",
36467
36849
  theme: "toggle theme",
36468
- product: "code viewer",
36469
36850
  copyAiContext: "Copy AI context (Shift+Click to include code)",
36470
- copyAiContextLabel: "AI context",
36471
36851
  copyAiContextCopied: "Copied AI context",
36472
36852
  copyAiContextCopiedWithCode: (lines) => `Copied AI context + code (${lines} line${lines === 1 ? "" : "s"})`,
36473
36853
  copyAiContextFailed: "Copy failed",
@@ -36480,7 +36860,12 @@ code-viewer query agent-help`
36480
36860
  cancelRequestsActiveTitle: (count) => `cancel ${count} in-flight request${count === 1 ? "" : "s"}`,
36481
36861
  cancelRequestsInactiveTitle: "no in-flight requests",
36482
36862
  brandHome: "Repository home",
36483
- menuViews: "Views"
36863
+ menuViews: "Views",
36864
+ repoWebLink: "open repository web page",
36865
+ copyLineReference: "Copy AI reference",
36866
+ selectedLineCount: (count) => `${count} line${count === 1 ? "" : "s"}`,
36867
+ githubSelectionOpen: "Open selected lines on GitHub",
36868
+ githubSelectionCopy: "Copy GitHub link"
36484
36869
  },
36485
36870
  topbar: {
36486
36871
  resetRange: "reset to HEAD .. worktree",
@@ -36491,15 +36876,13 @@ code-viewer query agent-help`
36491
36876
  ignoreWs: "ignore whitespace changes (-w)",
36492
36877
  ignoreWsLabel: "ws",
36493
36878
  syntaxLoading: "loading...",
36494
- syntaxOn: "syntax on",
36495
- syntaxOff: "syntax off",
36879
+ syntax: "syntax",
36496
36880
  syntaxOnTitle: "syntax highlighting on",
36497
36881
  syntaxLoadingTitle: "loading syntax highlighter",
36498
36882
  syntaxErrorTitle: "failed to load syntax highlighter",
36499
36883
  syntaxOffTitle: "syntax highlighting off",
36500
36884
  hideTests: "hide test files (test|spec)",
36501
36885
  hideTestsLabel: "no test",
36502
- autoUpdate: "auto",
36503
36886
  autoUpdateOnTitle: "auto update on file change",
36504
36887
  autoUpdateOffTitle: "auto update off — manual reload"
36505
36888
  },
@@ -36586,7 +36969,9 @@ code-viewer query agent-help`
36586
36969
  gitlinkLabel: "gitlink",
36587
36970
  gitlinkTitle: "Git commit entry is not directly browsable at this ref",
36588
36971
  submoduleLabel: "submodule",
36589
- submoduleTitle: "Git submodule pinned to a commit"
36972
+ submoduleTitle: "Git submodule pinned to a commit",
36973
+ openGithub: "Open on GitHub",
36974
+ openRepositoryWeb: "Open repository web page"
36590
36975
  },
36591
36976
  history: {
36592
36977
  title: "Commits",
@@ -36703,7 +37088,7 @@ code-viewer query agent-help`
36703
37088
  display: "Display",
36704
37089
  language: "Language",
36705
37090
  fileListFontSize: "UI font size",
36706
- fileListFontSizeHelp: "Applies to the file sidebar and datastore UI.",
37091
+ fileListFontSizeHelp: "Applies to all UI except code content.",
36707
37092
  codeFontSize: "Code font size",
36708
37093
  sizeSmall: "Small",
36709
37094
  sizeRegular: "Regular",
@@ -36755,9 +37140,7 @@ code-viewer query agent-help`
36755
37140
  queryHistory: "クエリ履歴",
36756
37141
  settings: "ビューア設定",
36757
37142
  theme: "テーマ切り替え",
36758
- product: "code viewer",
36759
37143
  copyAiContext: "AI 用コンテキストをコピー(Shift+Click でコードも添付)",
36760
- copyAiContextLabel: "AI文脈",
36761
37144
  copyAiContextCopied: "コピーしました",
36762
37145
  copyAiContextCopiedWithCode: (lines) => `コピーしました(コード付き・${lines}行)`,
36763
37146
  copyAiContextFailed: "コピーに失敗しました",
@@ -36770,7 +37153,12 @@ code-viewer query agent-help`
36770
37153
  cancelRequestsActiveTitle: (count) => `実行中のリクエストを${count}件キャンセル`,
36771
37154
  cancelRequestsInactiveTitle: "実行中のリクエストはありません",
36772
37155
  brandHome: "リポジトリホーム",
36773
- menuViews: "ビュー切り替え"
37156
+ menuViews: "ビュー切り替え",
37157
+ repoWebLink: "リポジトリのウェブページを開く",
37158
+ copyLineReference: "AI参照をコピー",
37159
+ selectedLineCount: (count) => `${count}行`,
37160
+ githubSelectionOpen: "選択行をGitHubで開く",
37161
+ githubSelectionCopy: "GitHubリンクをコピー"
36774
37162
  },
36775
37163
  topbar: {
36776
37164
  resetRange: "HEAD .. worktree に戻す",
@@ -36781,15 +37169,13 @@ code-viewer query agent-help`
36781
37169
  ignoreWs: "空白差分を無視 (-w)",
36782
37170
  ignoreWsLabel: "空白",
36783
37171
  syntaxLoading: "読み込み中...",
36784
- syntaxOn: "構文あり",
36785
- syntaxOff: "構文なし",
37172
+ syntax: "構文",
36786
37173
  syntaxOnTitle: "シンタックスハイライト有効",
36787
37174
  syntaxLoadingTitle: "シンタックスハイライトを読み込み中",
36788
37175
  syntaxErrorTitle: "シンタックスハイライトの読み込みに失敗",
36789
37176
  syntaxOffTitle: "シンタックスハイライト無効",
36790
37177
  hideTests: "test/spec ファイルを隠す",
36791
37178
  hideTestsLabel: "テスト非表示",
36792
- autoUpdate: "自動",
36793
37179
  autoUpdateOnTitle: "ファイル変更時に自動更新",
36794
37180
  autoUpdateOffTitle: "自動更新オフ — 手動で再読み込み"
36795
37181
  },
@@ -36876,7 +37262,9 @@ code-viewer query agent-help`
36876
37262
  gitlinkLabel: "固定コミット",
36877
37263
  gitlinkTitle: "特定のコミットに固定された参照です。この ref では直接開けません。",
36878
37264
  submoduleLabel: "サブモジュール",
36879
- submoduleTitle: "Git サブモジュール: 特定のコミットに固定されています。直接は開けません。"
37265
+ submoduleTitle: "Git サブモジュール: 特定のコミットに固定されています。直接は開けません。",
37266
+ openGithub: "GitHubで開く",
37267
+ openRepositoryWeb: "リポジトリのウェブページを開く"
36880
37268
  },
36881
37269
  history: {
36882
37270
  title: "コミット",
@@ -36993,7 +37381,7 @@ code-viewer query agent-help`
36993
37381
  display: "表示",
36994
37382
  language: "言語",
36995
37383
  fileListFontSize: "UIの文字サイズ",
36996
- fileListFontSizeHelp: "ファイル一覧とデータストア画面に適用されます。",
37384
+ fileListFontSizeHelp: "コード本文を除くUI全体に適用されます。",
36997
37385
  codeFontSize: "コード表示の文字サイズ",
36998
37386
  sizeSmall: "小",
36999
37387
  sizeRegular: "標準",
@@ -37059,8 +37447,16 @@ code-viewer query agent-help`
37059
37447
  if (route && text3.nav[route])
37060
37448
  link2.textContent = text3.nav[route];
37061
37449
  });
37062
- setElementText(".global-help-link[data-route='help']", text3.nav.help);
37063
- setElementText(".product-label", text3.global.product);
37450
+ const helpLink = document.querySelector(".global-icon-link[data-route='help']");
37451
+ if (helpLink) {
37452
+ helpLink.title = text3.nav.help;
37453
+ helpLink.setAttribute("aria-label", text3.nav.help);
37454
+ }
37455
+ const repoWebLink = document.querySelector("#repo-web-link");
37456
+ if (repoWebLink) {
37457
+ repoWebLink.title = text3.global.repoWebLink;
37458
+ repoWebLink.setAttribute("aria-label", text3.global.repoWebLink);
37459
+ }
37064
37460
  document.querySelector(".brand")?.setAttribute("aria-label", text3.global.brandHome);
37065
37461
  document.querySelector(".app-menu")?.setAttribute("aria-label", text3.global.menuViews);
37066
37462
  const annotationsToggle = document.querySelector("#annotations-toggle");
@@ -37095,9 +37491,6 @@ code-viewer query agent-help`
37095
37491
  if (copyAiContext) {
37096
37492
  copyAiContext.title = text3.global.copyAiContext;
37097
37493
  copyAiContext.setAttribute("aria-label", text3.global.copyAiContext);
37098
- const copyAiContextLabel = copyAiContext.querySelector(".ai-context-label");
37099
- if (copyAiContextLabel)
37100
- copyAiContextLabel.textContent = text3.global.copyAiContextLabel;
37101
37494
  }
37102
37495
  const refReset = document.querySelector("#ref-reset");
37103
37496
  if (refReset) {
@@ -37301,6 +37694,7 @@ code-viewer query agent-help`
37301
37694
  if (!hljsRef)
37302
37695
  return null;
37303
37696
  ensureTerraformHighlightLanguage(hljsRef);
37697
+ ensureGdscriptHighlightLanguage(hljsRef);
37304
37698
  if (!highlightConfigured && typeof hljsRef.configure === "function") {
37305
37699
  hljsRef.configure({ ignoreUnescapedHTML: true });
37306
37700
  highlightConfigured = true;
@@ -37314,7 +37708,7 @@ code-viewer query agent-help`
37314
37708
  const text3 = uiText();
37315
37709
  btn.classList.toggle("active", STATE.syntaxHighlight);
37316
37710
  btn.classList.toggle("loading", state === "loading");
37317
- btn.textContent = state === "loading" ? text3.topbar.syntaxLoading : STATE.syntaxHighlight ? text3.topbar.syntaxOn : text3.topbar.syntaxOff;
37711
+ btn.textContent = state === "loading" ? text3.topbar.syntaxLoading : text3.topbar.syntax;
37318
37712
  btn.setAttribute("aria-pressed", STATE.syntaxHighlight ? "true" : "false");
37319
37713
  btn.title = STATE.syntaxHighlight ? text3.topbar.syntaxOnTitle : state === "loading" ? text3.topbar.syntaxLoadingTitle : state === "error" ? text3.topbar.syntaxErrorTitle : text3.topbar.syntaxOffTitle;
37320
37714
  }
@@ -37699,6 +38093,7 @@ code-viewer query agent-help`
37699
38093
  setRoute,
37700
38094
  setPreferredSourceTab: (tab) => SOURCE_VIEW.setPreferredSourceTab(tab),
37701
38095
  createFileBreadcrumb: (path, ref) => DIFF_VIEW.createFileBreadcrumb(path, ref),
38096
+ createRepositoryWebLink: createFileRepositoryWebLink,
37702
38097
  emptyText: () => uiText().diff
37703
38098
  }, historyRoute, { loadSidebar: false });
37704
38099
  activeFileHistoryDiffHost = mount.diffHost;
@@ -37874,7 +38269,7 @@ code-viewer query agent-help`
37874
38269
  applyHideTests();
37875
38270
  }
37876
38271
  function syncHeaderMenu() {
37877
- document.querySelectorAll(".app-menu-item, .global-help-link").forEach((link2) => {
38272
+ document.querySelectorAll(".app-menu-item, .global-icon-link").forEach((link2) => {
37878
38273
  const fileRouteOwner = STATE.route.screen === "file" && (STATE.route.view === "blob" || STATE.route.view === "blame" || STATE.route.view === "history") ? "repo" : "diff";
37879
38274
  const active = link2.dataset.route === STATE.route.screen || STATE.route.screen === "file" && link2.dataset.route === fileRouteOwner;
37880
38275
  link2.classList.toggle("active", active);
@@ -37999,6 +38394,18 @@ code-viewer query agent-help`
37999
38394
  });
38000
38395
  return button;
38001
38396
  }
38397
+ function createFileRepositoryWebLink(target) {
38398
+ const webTarget = buildRepositoryWebTarget(REPO_WEB_URL, {
38399
+ ref: target.ref,
38400
+ fallbackRef: PROJECT_BRANCH,
38401
+ path: target.path,
38402
+ kind: "blob"
38403
+ });
38404
+ if (!webTarget)
38405
+ return null;
38406
+ const label = webTarget.provider === "github" ? uiText().repo.openGithub : uiText().repo.openRepositoryWeb;
38407
+ return createRepositoryWebLink(webTarget, label);
38408
+ }
38002
38409
  window.addEventListener("scroll", () => enqueueInitialLoads(), {
38003
38410
  passive: true
38004
38411
  });
@@ -38108,14 +38515,34 @@ code-viewer query agent-help`
38108
38515
  if (cancelRequestsIcon) {
38109
38516
  cancelRequestsIcon.innerHTML = iconSvg("octicon-x", X_16_PATH);
38110
38517
  }
38518
+ const repoWebLinkIcon = document.querySelector("#repo-web-link .goi-icon");
38519
+ if (repoWebLinkIcon) {
38520
+ repoWebLinkIcon.innerHTML = iconSvg("octicon-mark-github", MARK_GITHUB_16_PATH);
38521
+ }
38522
+ const quickHelpIcon = document.querySelector("#quick-help-btn .goi-icon");
38523
+ if (quickHelpIcon) {
38524
+ quickHelpIcon.innerHTML = iconSvg("octicon-question", QUESTION_16_PATH);
38525
+ }
38526
+ const helpLinkIcon = document.querySelector(".global-icon-link[data-route='help'] .goi-icon");
38527
+ if (helpLinkIcon) {
38528
+ helpLinkIcon.innerHTML = iconSvg("octicon-book", BOOK_16_PATH);
38529
+ }
38530
+ const branchIcon = document.querySelector("#project-branch .goi-icon");
38531
+ if (branchIcon) {
38532
+ branchIcon.innerHTML = iconSvg("octicon-git-branch", GIT_BRANCH_16_PATH);
38533
+ }
38111
38534
  }
38112
38535
  function setRefActionIcons() {
38113
38536
  const refReset = document.querySelector("#ref-reset");
38114
38537
  if (refReset)
38115
- refReset.innerHTML = iconSvg("octicon-x", X_16_PATH);
38538
+ refReset.innerHTML = iconSvg("octicon-undo", UNDO_16_PATH);
38116
38539
  const reload = document.querySelector("#reload-prom");
38117
38540
  if (reload)
38118
38541
  reload.innerHTML = iconSvg("octicon-sync", SYNC_16_PATH);
38542
+ const refDots = document.querySelector(".ref-dots .goi-icon");
38543
+ if (refDots) {
38544
+ refDots.innerHTML = iconSvg("octicon-arrow-right", ARROW_RIGHT_16_PATH);
38545
+ }
38119
38546
  }
38120
38547
  applySidebarFontSize();
38121
38548
  applyCodeFontSize();
@@ -39136,7 +39563,7 @@ code-viewer query agent-help`
39136
39563
  }
39137
39564
  window.addEventListener("popstate", applyRouteFromLocation);
39138
39565
  window.addEventListener("pagehide", () => flushViewStatePatch(true));
39139
- document.querySelectorAll(".app-menu-item, .global-help-link").forEach((link2) => {
39566
+ document.querySelectorAll(".app-menu-item, .global-icon-link").forEach((link2) => {
39140
39567
  if (link2.target === "_blank")
39141
39568
  return;
39142
39569
  link2.addEventListener("click", (e2) => {
@@ -39371,9 +39798,6 @@ code-viewer query agent-help`
39371
39798
  btn.title = autoUpdateTitle;
39372
39799
  btn.setAttribute("aria-label", autoUpdateTitle);
39373
39800
  btn.setAttribute("aria-pressed", STATE.autoUpdate ? "true" : "false");
39374
- const label = btn.querySelector(".auto-update-label");
39375
- if (label)
39376
- label.textContent = text3.topbar.autoUpdate;
39377
39801
  }
39378
39802
  function setAutoUpdate(on) {
39379
39803
  STATE.autoUpdate = on;