@youtyan/code-viewer 0.8.9 → 0.9.1

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
@@ -7669,15 +7669,24 @@ ${lines.join(`
7669
7669
  function markdownSlugify(text2) {
7670
7670
  return text2.trim().toLowerCase().replace(/[\s ]+/g, "-").replace(/[^\p{L}\p{N}\-_]/gu, "").slice(0, 80) || "section";
7671
7671
  }
7672
- function resolveMarkdownRelativePath(currentPath, href) {
7672
+ function resolveMarkdownLinkTarget(currentPath, href) {
7673
7673
  if (!href || href.startsWith("#"))
7674
7674
  return null;
7675
7675
  if (/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(href))
7676
7676
  return null;
7677
- const cleanHref = href.replace(/[?#].*$/, "");
7678
- if (!/\.(md|markdown|mdown|mkd|mkdn|mdx)$/i.test(cleanHref))
7677
+ const hashAt = href.indexOf("#");
7678
+ const hash = hashAt < 0 ? "" : href.slice(hashAt + 1);
7679
+ const cleanHref = (hashAt < 0 ? href : href.slice(0, hashAt)).replace(/\?.*$/, "");
7680
+ if (!cleanHref)
7679
7681
  return null;
7680
- return resolveRepoRelative(currentPath, decodeURIComponent(cleanHref));
7682
+ const path = resolveRepoRelative(currentPath, decodeUriComponentSafe(cleanHref));
7683
+ if (path == null)
7684
+ return null;
7685
+ return {
7686
+ path,
7687
+ hash: decodeUriComponentSafe(hash),
7688
+ directory: cleanHref.endsWith("/")
7689
+ };
7681
7690
  }
7682
7691
  function resolveMarkdownAssetPath(currentPath, src) {
7683
7692
  if (!src || src.startsWith("#") || /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(src))
@@ -7685,6 +7694,13 @@ ${lines.join(`
7685
7694
  const cleanSrc = src.split(/[?#]/, 1)[0];
7686
7695
  return resolveRepoRelative(currentPath, cleanSrc);
7687
7696
  }
7697
+ function decodeUriComponentSafe(value) {
7698
+ try {
7699
+ return decodeURIComponent(value);
7700
+ } catch {
7701
+ return value;
7702
+ }
7703
+ }
7688
7704
  function resolveRepoRelative(currentPath, requestedPath) {
7689
7705
  const base2 = currentPath.split("/").slice(0, -1);
7690
7706
  const parts = [
@@ -7780,11 +7796,15 @@ ${lines.join(`
7780
7796
  md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
7781
7797
  const token = tokens[idx];
7782
7798
  const href = token.attrGet("href") || "";
7783
- const mdPath = resolveMarkdownRelativePath(target.path, href);
7784
- if (mdPath) {
7799
+ const link2 = resolveMarkdownLinkTarget(target.path, href);
7800
+ if (link2) {
7785
7801
  token.attrSet("href", "#");
7786
- token.attrSet("data-gdp-md-link", mdPath);
7802
+ token.attrSet("data-gdp-md-link", link2.path);
7787
7803
  token.attrSet("data-gdp-md-ref", target.ref || "worktree");
7804
+ if (link2.hash)
7805
+ token.attrSet("data-gdp-md-hash", link2.hash);
7806
+ if (link2.directory)
7807
+ token.attrSet("data-gdp-md-dir", "1");
7788
7808
  } else if (/^(?:https?:)?\/\//i.test(href)) {
7789
7809
  token.attrSet("target", "_blank");
7790
7810
  token.attrSet("rel", "noopener noreferrer");
@@ -7899,11 +7919,15 @@ ${frontmatter.yaml}
7899
7919
  if (!link2)
7900
7920
  return;
7901
7921
  const path = link2.dataset.gdpMdLink;
7902
- const ref = link2.dataset.gdpMdRef || target.ref;
7903
- if (!path)
7922
+ if (path == null)
7904
7923
  return;
7905
7924
  e2.preventDefault();
7906
- options.onNavigateMarkdown?.(path, ref);
7925
+ options.onNavigateMarkdown?.({
7926
+ path,
7927
+ ref: link2.dataset.gdpMdRef || target.ref,
7928
+ hash: link2.dataset.gdpMdHash || "",
7929
+ directory: link2.dataset.gdpMdDir === "1"
7930
+ });
7907
7931
  });
7908
7932
  setupMarkdownScrollSpy(root);
7909
7933
  setupMermaidLightbox(root);
@@ -7986,12 +8010,7 @@ ${frontmatter.yaml}
7986
8010
  scrollMarkdownSectionIntoView(section, "auto");
7987
8011
  }
7988
8012
  function decodeHashFragment(hash) {
7989
- const value = hash.startsWith("#") ? hash.slice(1) : hash;
7990
- try {
7991
- return decodeURIComponent(value);
7992
- } catch {
7993
- return value;
7994
- }
8013
+ return decodeUriComponentSafe(hash.startsWith("#") ? hash.slice(1) : hash);
7995
8014
  }
7996
8015
  function scrollMarkdownSectionIntoView(section, behavior) {
7997
8016
  const top = section.getBoundingClientRect().top + window.scrollY - markdownAnchorOffset() - 12;
@@ -10309,11 +10328,21 @@ ${frontmatter.yaml}
10309
10328
  var KINDS = [
10310
10329
  "postgresql",
10311
10330
  "mysql",
10331
+ "d1",
10312
10332
  "redis",
10313
10333
  "elasticsearch",
10314
10334
  "s3",
10315
10335
  "dynamodb"
10316
10336
  ];
10337
+ var S3_PROVIDERS = ["custom", "r2"];
10338
+ var R2_ENDPOINT_RE = /^https:\/\/([a-z0-9-]+)\.r2\.cloudflarestorage\.com$/i;
10339
+ var R2_REGION = "auto";
10340
+ function r2EndpointFor(accountId) {
10341
+ return `https://${accountId}.r2.cloudflarestorage.com`;
10342
+ }
10343
+ function r2AccountIdFromEndpoint(endpoint) {
10344
+ return endpoint ? R2_ENDPOINT_RE.exec(endpoint)?.[1] ?? null : null;
10345
+ }
10317
10346
  function text2(language) {
10318
10347
  return language === "ja" ? {
10319
10348
  titleAdd: "データストア接続を追加",
@@ -10327,6 +10356,12 @@ ${frontmatter.yaml}
10327
10356
  password: "パスワード",
10328
10357
  database: "データベース",
10329
10358
  schema: "既定スキーマ(任意)",
10359
+ provider: "プロバイダ",
10360
+ providerCustom: "S3 互換(AWS / MinIO / LocalStack)",
10361
+ providerR2: "Cloudflare R2",
10362
+ accountId: "Cloudflare アカウント ID",
10363
+ databaseId: "データベース ID",
10364
+ apiToken: "API トークン",
10330
10365
  endpoint: "エンドポイント URL",
10331
10366
  region: "リージョン",
10332
10367
  accessKeyId: "アクセスキー ID",
@@ -10345,7 +10380,9 @@ ${frontmatter.yaml}
10345
10380
  requestFailed: "接続情報を保存できませんでした",
10346
10381
  deleteTitle: "保存済み接続を削除",
10347
10382
  deleteBody: "この接続情報を削除します。タブやスナップショットのデータは削除されません。",
10348
- delete: "削除"
10383
+ delete: "削除",
10384
+ secretsLeftTitle: "資格情報がキーチェーンに残りました",
10385
+ secretsLeftBody: "接続は削除しましたが、キーチェーンから資格情報を削除できませんでした。キーチェーンがロックされている可能性があります。ロックを解除して「キーチェーンアクセス」から code-viewer の項目を削除してください。"
10349
10386
  } : {
10350
10387
  titleAdd: "Add datastore connection",
10351
10388
  titleEdit: "Edit datastore connection",
@@ -10358,6 +10395,12 @@ ${frontmatter.yaml}
10358
10395
  password: "Password",
10359
10396
  database: "Database",
10360
10397
  schema: "Default schema (optional)",
10398
+ provider: "Provider",
10399
+ providerCustom: "S3-compatible (AWS / MinIO / LocalStack)",
10400
+ providerR2: "Cloudflare R2",
10401
+ accountId: "Cloudflare account ID",
10402
+ databaseId: "Database ID",
10403
+ apiToken: "API token",
10361
10404
  endpoint: "Endpoint URL",
10362
10405
  region: "Region",
10363
10406
  accessKeyId: "Access key ID",
@@ -10376,7 +10419,9 @@ ${frontmatter.yaml}
10376
10419
  requestFailed: "Failed to save the connection",
10377
10420
  deleteTitle: "Delete saved connection",
10378
10421
  deleteBody: "This removes the saved connection. Tabs and snapshot data are not deleted.",
10379
- delete: "Delete"
10422
+ delete: "Delete",
10423
+ secretsLeftTitle: "Credentials left in the keychain",
10424
+ secretsLeftBody: "The connection was removed, but its credentials could not be deleted from the keychain — it may be locked. Unlock it and remove the code-viewer item from Keychain Access."
10380
10425
  };
10381
10426
  }
10382
10427
  function field(labelText, input, required = false, requiredLabel = "Required") {
@@ -10440,6 +10485,21 @@ ${frontmatter.yaml}
10440
10485
  database.value = current?.database ?? "";
10441
10486
  const schema = input();
10442
10487
  schema.value = current?.schema ?? "";
10488
+ const provider = document.createElement("select");
10489
+ for (const value of S3_PROVIDERS) {
10490
+ const option = document.createElement("option");
10491
+ option.value = value;
10492
+ option.textContent = value === "r2" ? labels.providerR2 : labels.providerCustom;
10493
+ provider.appendChild(option);
10494
+ }
10495
+ const currentR2AccountId = r2AccountIdFromEndpoint(current?.endpoint);
10496
+ provider.value = currentR2AccountId ? "r2" : "custom";
10497
+ const accountId = input();
10498
+ accountId.value = current?.accountId ?? currentR2AccountId ?? "";
10499
+ const databaseId = input();
10500
+ databaseId.value = current?.databaseId ?? "";
10501
+ const apiToken = input("password");
10502
+ apiToken.autocomplete = "new-password";
10443
10503
  const endpoint = input("url");
10444
10504
  endpoint.value = current?.endpoint ?? "http://127.0.0.1:9200";
10445
10505
  const region = input();
@@ -10461,6 +10521,10 @@ ${frontmatter.yaml}
10461
10521
  password: field(labels.password, password),
10462
10522
  database: field(labels.database, database, true, labels.requiredField),
10463
10523
  schema: field(labels.schema, schema),
10524
+ provider: field(labels.provider, provider),
10525
+ accountId: field(labels.accountId, accountId, true, labels.requiredField),
10526
+ databaseId: field(labels.databaseId, databaseId, true, labels.requiredField),
10527
+ apiToken: field(labels.apiToken, apiToken, true, labels.requiredField),
10464
10528
  endpoint: field(labels.endpoint, endpoint, true, labels.requiredField),
10465
10529
  region: field(labels.region, region, true, labels.requiredField),
10466
10530
  accessKeyId: field(labels.accessKeyId, accessKeyId, true, labels.requiredField),
@@ -10469,21 +10533,28 @@ ${frontmatter.yaml}
10469
10533
  tls: field(labels.tls, tls)
10470
10534
  };
10471
10535
  form.append(...Object.values(rows));
10536
+ const isR2 = () => kind.value === "s3" && provider.value === "r2";
10472
10537
  const syncKind = () => {
10473
10538
  const value = kind.value;
10474
10539
  const sql = value === "postgresql" || value === "mysql";
10475
10540
  const redis = value === "redis";
10476
10541
  const http = value === "elasticsearch";
10477
10542
  const aws = value === "s3" || value === "dynamodb";
10543
+ const d1 = value === "d1";
10544
+ const r2 = isR2();
10478
10545
  rows.host.hidden = !sql && !redis;
10479
10546
  rows.port.hidden = !sql && !redis;
10480
10547
  rows.user.hidden = !sql;
10481
10548
  rows.username.hidden = !redis && !http;
10482
- rows.password.hidden = aws;
10549
+ rows.password.hidden = aws || d1;
10483
10550
  rows.database.hidden = !sql;
10484
10551
  rows.schema.hidden = value !== "postgresql";
10485
- rows.endpoint.hidden = !http && !aws;
10486
- rows.region.hidden = !aws;
10552
+ rows.provider.hidden = value !== "s3";
10553
+ rows.accountId.hidden = !d1 && !r2;
10554
+ rows.databaseId.hidden = !d1;
10555
+ rows.apiToken.hidden = !d1;
10556
+ rows.endpoint.hidden = !http && !aws || r2;
10557
+ rows.region.hidden = !aws || r2;
10487
10558
  rows.accessKeyId.hidden = !aws;
10488
10559
  rows.secretAccessKey.hidden = !aws;
10489
10560
  rows.sessionToken.hidden = !aws;
@@ -10498,8 +10569,11 @@ ${frontmatter.yaml}
10498
10569
  if (!current && (http || aws)) {
10499
10570
  endpoint.value = value === "elasticsearch" ? "http://127.0.0.1:9200" : "http://127.0.0.1:4566";
10500
10571
  }
10572
+ if (!current && aws)
10573
+ region.value = r2 ? R2_REGION : "us-east-1";
10501
10574
  };
10502
10575
  kind.addEventListener("change", syncKind);
10576
+ provider.addEventListener("change", syncKind);
10503
10577
  syncKind();
10504
10578
  const validateConnection = () => {
10505
10579
  const value = kind.value;
@@ -10516,10 +10590,16 @@ ${frontmatter.yaml}
10516
10590
  if ((value === "postgresql" || value === "mysql") && (!user.value.trim() || !database.value.trim())) {
10517
10591
  return labels.required;
10518
10592
  }
10519
- if ((value === "elasticsearch" || value === "s3" || value === "dynamodb") && !endpoint.value.trim()) {
10593
+ if (value === "d1" && (!accountId.value.trim() || !databaseId.value.trim() || !apiToken.value)) {
10594
+ return labels.required;
10595
+ }
10596
+ if (isR2() && !accountId.value.trim()) {
10520
10597
  return labels.required;
10521
10598
  }
10522
- if ((value === "s3" || value === "dynamodb") && (!region.value.trim() || !accessKeyId.value.trim())) {
10599
+ if ((value === "elasticsearch" || value === "s3" && !isR2() || value === "dynamodb") && !endpoint.value.trim()) {
10600
+ return labels.required;
10601
+ }
10602
+ if ((value === "s3" || value === "dynamodb") && (!isR2() && !region.value.trim() || !accessKeyId.value.trim())) {
10523
10603
  return labels.required;
10524
10604
  }
10525
10605
  return null;
@@ -10555,10 +10635,16 @@ ${frontmatter.yaml}
10555
10635
  ...username.value.trim() || !current ? { username: username.value.trim() || undefined } : {},
10556
10636
  ...password.value || !current ? { password: password.value } : {}
10557
10637
  });
10638
+ } else if (value === "d1") {
10639
+ Object.assign(payload, {
10640
+ accountId: accountId.value.trim(),
10641
+ databaseId: databaseId.value.trim(),
10642
+ apiToken: apiToken.value
10643
+ });
10558
10644
  } else {
10559
10645
  Object.assign(payload, {
10560
- endpoint: endpoint.value.trim(),
10561
- region: region.value.trim(),
10646
+ endpoint: isR2() ? r2EndpointFor(accountId.value.trim()) : endpoint.value.trim(),
10647
+ region: isR2() ? R2_REGION : region.value.trim(),
10562
10648
  ...accessKeyId.value.trim() || !current ? { accessKeyId: accessKeyId.value.trim() } : {},
10563
10649
  ...secretAccessKey.value || !current ? { secretAccessKey: secretAccessKey.value } : {},
10564
10650
  ...sessionToken.value ? { sessionToken: sessionToken.value } : {}
@@ -10668,6 +10754,13 @@ ${frontmatter.yaml}
10668
10754
  }));
10669
10755
  if (!response.ok)
10670
10756
  throw new Error(await response.text() || labels.requestFailed);
10757
+ const body = await response.json().catch(() => ({}));
10758
+ if (body.secretsRemoved === false) {
10759
+ await showAlertDialog({
10760
+ title: labels.secretsLeftTitle,
10761
+ body: labels.secretsLeftBody
10762
+ });
10763
+ }
10671
10764
  return true;
10672
10765
  }
10673
10766
 
@@ -20343,8 +20436,22 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20343
20436
  function isAbortError2(err) {
20344
20437
  return err instanceof DOMException && err.name === "AbortError" || err instanceof Error && err.name === "AbortError";
20345
20438
  }
20439
+ var SQL_KINDS = new Set([
20440
+ "sqlite",
20441
+ "postgresql",
20442
+ "mysql",
20443
+ "d1"
20444
+ ]);
20445
+ var ROW_EDITABLE_KINDS = new Set([
20446
+ "sqlite",
20447
+ "postgresql",
20448
+ "mysql"
20449
+ ]);
20346
20450
  function isSqlKind(kind) {
20347
- return kind === "sqlite" || kind === "postgresql" || kind === "mysql";
20451
+ return !!kind && SQL_KINDS.has(kind);
20452
+ }
20453
+ function isRowEditableKind(kind) {
20454
+ return !!kind && ROW_EDITABLE_KINDS.has(kind);
20348
20455
  }
20349
20456
  function isPostgresKind(kind) {
20350
20457
  return kind === "postgresql";
@@ -20700,7 +20807,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20700
20807
  cb.onStateChange();
20701
20808
  },
20702
20809
  getText: () => paneText(),
20703
- getEditable: () => isSqlKind(currentDbInfo?.kind),
20810
+ getEditable: () => isRowEditableKind(currentDbInfo?.kind),
20704
20811
  applyMutations: (mutations) => applyRowMutations(mutations),
20705
20812
  onRefreshComplete: ({ table: table2, filters }) => {
20706
20813
  if (filters.length === 0) {
@@ -23241,7 +23348,14 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
23241
23348
  const span = document.createElement("span");
23242
23349
  span.className = `badge ${ch}`;
23243
23350
  span.textContent = ch;
23244
- span.title = { M: "modified", A: "added", D: "deleted", R: "renamed" }[ch] || ch;
23351
+ span.title = {
23352
+ M: "modified",
23353
+ A: "added",
23354
+ D: "deleted",
23355
+ R: "renamed",
23356
+ U: "untracked",
23357
+ I: "ignored"
23358
+ }[ch] || ch;
23245
23359
  return span;
23246
23360
  }
23247
23361
  function setFileViewed(path, viewed) {
@@ -26304,6 +26418,10 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
26304
26418
  kind: "paragraph",
26305
26419
  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
26420
  },
26421
+ {
26422
+ kind: "paragraph",
26423
+ text: "Relative links inside a Markdown preview lead to the same destinations as they do on GitHub: another Markdown file opens its file page, an #anchor opens the preview and scrolls to that heading, a non-Markdown file opens in the Code view, and a link to a directory opens that folder in the repository tree."
26424
+ },
26307
26425
  {
26308
26426
  kind: "paragraph",
26309
26427
  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."
@@ -26314,7 +26432,11 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
26314
26432
  },
26315
26433
  {
26316
26434
  kind: "paragraph",
26317
- text: 'Symlinks show a distinct icon and a "→ target" label so they are never mistaken for a regular file or folder, and clicking one navigates to its resolved target. A broken symlink is visually flagged and disabled. Files with pending git changes (new, modified, renamed, deleted) show a status badge in the tree instead of the regular type icon.'
26435
+ text: 'Symlinks show a distinct icon and a "→ target" label so they are never mistaken for a regular file or folder, and clicking one navigates to its resolved target. A broken symlink is visually flagged and disabled.'
26436
+ },
26437
+ {
26438
+ kind: "paragraph",
26439
+ text: "Files with pending git changes show a status badge in the tree instead of the regular type icon: M (modified), A (added — staged for commit), D (deleted), R (renamed), U (untracked — in the worktree but not under version control yet), and I (ignored by a .gitignore rule). U and A stay separate so a file you have never run git add on does not look like one that is already staged. A wholly untracked or ignored directory is badged as a whole and keeps its folder icon, so it stays recognizable while collapsed; its contents inherit the badge unless an ignore rule names a file specifically."
26318
26440
  }
26319
26441
  ]
26320
26442
  },
@@ -26555,7 +26677,7 @@ code-viewer annotate add-db --db app.db --tab query \\
26555
26677
  database: {
26556
26678
  nav: "Datastores",
26557
26679
  title: "Datastore Viewer",
26558
- intro: "Browse SQLite files, Docker-hosted databases, Redis, Elasticsearch, DynamoDB, and S3-compatible object stores from one local viewer.",
26680
+ intro: "Browse SQLite files, Docker-hosted databases, Cloudflare D1, Redis, Elasticsearch, DynamoDB, and S3-compatible object stores (including Cloudflare R2) from one local viewer.",
26559
26681
  groups: [
26560
26682
  {
26561
26683
  title: "Supported datastores",
@@ -26565,7 +26687,7 @@ code-viewer annotate add-db --db app.db --tab query \\
26565
26687
  rows: [
26566
26688
  [
26567
26689
  "Saved connections",
26568
- "Use + beside the datastore selector to add an arbitrary PostgreSQL, MySQL, Redis, Elasticsearch, S3-compatible, or DynamoDB endpoint. Required fields are marked, and Test connection verifies the current values before saving. Drivers are included, so no database CLI or curl is required. Non-secret settings are saved locally; authentication values stay in server memory and must be entered again after a restart."
26690
+ "Use + beside the datastore selector to add an arbitrary PostgreSQL, MySQL, Cloudflare D1, Redis, Elasticsearch, S3-compatible (including a Cloudflare R2 preset), or DynamoDB endpoint. Required fields are marked, and Test connection verifies the current values before saving. Drivers are included, so no database CLI or curl is required. Non-secret settings are saved locally; credentials are never written into the repository — on macOS they are kept in the Keychain so they survive a restart, and elsewhere they stay in server memory and must be entered again."
26569
26691
  ],
26570
26692
  [
26571
26693
  "SQLite",
@@ -26579,6 +26701,10 @@ code-viewer annotate add-db --db app.db --tab query \\
26579
26701
  "PostgreSQL",
26580
26702
  "Detected from compose files. Multiple databases per server, plus a schema selector for switching schemas without reopening. Same inline row edit / insert / delete as SQLite. Local Supabase CLI (`supabase start`) projects are also auto-discovered from `supabase/config.toml`, without needing a docker-compose file."
26581
26703
  ],
26704
+ [
26705
+ "Cloudflare D1",
26706
+ "Added as a saved connection with an account ID, database ID, and API token (needs D1:Read). Browsed over the D1 REST API and reuses the SQL screens — table list, row grid, query editor, schema, ER diagram, snapshots and diffs. Read-only: the query editor accepts SELECT / PRAGMA / EXPLAIN / WITH only, and grid Edit mode is not offered."
26707
+ ],
26582
26708
  [
26583
26709
  "Redis",
26584
26710
  "Detected from compose files. Browse DB 0-15, SCAN keys, dedicated string/hash/list panes, JSON view for set/zset/stream. Edit values, delete keys, and create new keys (all types) via in-pane editors. Participates in snapshots and diffs."
@@ -26592,8 +26718,8 @@ code-viewer annotate add-db --db app.db --tab query \\
26592
26718
  "Detected when DynamoDB is enabled on a LocalStack compose service. List tables, browse a Structure tab (key schema, GSI/LSI, and non-key attribute types inferred from loaded items), scan or query items, follow pagination tokens, and open item details with a copyable key. Browsing is read-only."
26593
26719
  ],
26594
26720
  [
26595
- "S3 / MinIO / LocalStack",
26596
- "Detected from compose files. Folder-tree browse, prefix/filename search, updated-time sort, and previews for images, video, audio, PDF, Markdown, HTML, and text. Edit text/markdown/JSON object bodies inline, upload new objects, and delete existing ones. LocalStack falls back to `docker exec curl` when no host port is published; MinIO requires a published host port."
26721
+ "S3 / MinIO / LocalStack / Cloudflare R2",
26722
+ "Detected from compose files, or added as a saved connection. R2 uses the Cloudflare R2 provider preset: enter the account ID and an R2 access key pair, and the endpoint and the required auto signing region are filled in. Folder-tree browse, prefix/filename search, updated-time sort, and previews for images, video, audio, PDF, Markdown, HTML, and text. Edit text/markdown/JSON object bodies inline, upload new objects, and delete existing ones. LocalStack falls back to `docker exec curl` when no host port is published; MinIO requires a published host port."
26597
26723
  ]
26598
26724
  ]
26599
26725
  }
@@ -26987,6 +27113,10 @@ code-viewer query agent-help`
26987
27113
  kind: "paragraph",
26988
27114
  text: "ファイル詳細ページには最大 4 つのタブ (Preview / Code / Blame / History) があります。テキストファイルは Code がデフォルトで、?preview=1 を付けると Markdown / HTML プレビューに切り替わります。画像・動画・音声・PDF などのメディアファイルは Preview タブのみ表示され、Code タブは表示されません。Blame と History はそれぞれ専用 URL (view=blame, view=history) を持つため、ディープリンクとブラウザの戻る / 進むが同期し、いずれも Repository サイドバーは表示されたままです。ツリーから別のファイルを開いても選択中のタブは維持されます (プレビューできないファイルでは Code に戻ります)。"
26989
27115
  },
27116
+ {
27117
+ kind: "paragraph",
27118
+ text: "Markdown プレビュー内の相対リンクは GitHub と同じ行き先に解決されます。別の Markdown ファイルはそのファイルページを開き、#見出し 付きのリンクはプレビューを開いて該当見出しまでスクロールし、Markdown 以外のファイルは Code ビュー、ディレクトリへのリンクはリポジトリツリーのそのフォルダを開きます。"
27119
+ },
26990
27120
  {
26991
27121
  kind: "paragraph",
26992
27122
  text: "リポジトリの remote が GitHub の場合、リポジトリとファイルのヘッダーから現在のパスを GitHub で開けます。ソース行を選択すると、AI 参照のコピー、選択行範囲を GitHub で開く、GitHub URL のコピーを個別に選べます。Markdown / HTML の diff カードには直接 Preview を開く導線も表示されます。"
@@ -26997,7 +27127,11 @@ code-viewer query agent-help`
26997
27127
  },
26998
27128
  {
26999
27129
  kind: "paragraph",
27000
- text: "シンボリックリンクは専用アイコンと「→ リンク先」ラベルで表示されるため通常のファイル/フォルダと区別でき、クリックするとリンク先に遷移します。リンク切れのシンボリックリンクは無効化されたことが分かる表示になります。未コミットの git 変更(新規・変更・リネーム・削除)があるファイルは、通常の種類アイコンの代わりにステータスバッジがツリーに表示されます。"
27130
+ text: "シンボリックリンクは専用アイコンと「→ リンク先」ラベルで表示されるため通常のファイル/フォルダと区別でき、クリックするとリンク先に遷移します。リンク切れのシンボリックリンクは無効化されたことが分かる表示になります。"
27131
+ },
27132
+ {
27133
+ kind: "paragraph",
27134
+ text: "git の状態があるファイルは、通常の種類アイコンの代わりにステータスバッジがツリーに表示されます。M(変更)、A(追加 — コミット予定としてステージ済み)、D(削除)、R(リネーム)、U(未追跡 — ワークツリーにあるがまだバージョン管理下にない)、I(.gitignore の対象) の 6 種類です。U と A を分けているのは、git add をまだ一度もしていないファイルが、既にステージ済みのファイルと同じ見た目にならないようにするためです。丸ごと未追跡・無視のディレクトリにはディレクトリ単位でバッジが付き、フォルダアイコンは残るので折りたたんだままでも判別できます。配下のファイルはそのバッジを引き継ぎますが、無視ルールが個別に名指ししているファイルはそちらが優先されます。"
27001
27135
  }
27002
27136
  ]
27003
27137
  },
@@ -27238,7 +27372,7 @@ code-viewer annotate add-db --db app.db --tab query \\
27238
27372
  database: {
27239
27373
  nav: "データストア",
27240
27374
  title: "データストアビューア",
27241
- intro: "SQLite ファイル、Docker 上のデータベース、Redis、Elasticsearch、DynamoDB、S3 互換オブジェクトストアをローカルビューアで閲覧できます。",
27375
+ intro: "SQLite ファイル、Docker 上のデータベース、Cloudflare D1、Redis、Elasticsearch、DynamoDB、S3 互換オブジェクトストア (Cloudflare R2 を含む) をローカルビューアで閲覧できます。",
27242
27376
  groups: [
27243
27377
  {
27244
27378
  title: "対応データストア",
@@ -27248,7 +27382,7 @@ code-viewer annotate add-db --db app.db --tab query \\
27248
27382
  rows: [
27249
27383
  [
27250
27384
  "保存済み接続",
27251
- "データストア選択の横にある + から、任意の PostgreSQL、MySQL、Redis、Elasticsearch、S3 互換、DynamoDB エンドポイントを追加できます。必須項目にはマークが付き、保存前に「接続テスト」で入力内容を確認できます。ドライバーは同梱されているため、データベース CLI や curl は不要です。非機密設定はローカルに保存されますが、認証情報はサーバーメモリだけに保持され、再起動後は再入力が必要です。"
27385
+ "データストア選択の横にある + から、任意の PostgreSQL、MySQL、Cloudflare D1、Redis、Elasticsearch、S3 互換 (Cloudflare R2 プリセットあり)、DynamoDB エンドポイントを追加できます。必須項目にはマークが付き、保存前に「接続テスト」で入力内容を確認できます。ドライバーは同梱されているため、データベース CLI や curl は不要です。非機密設定はローカルに保存されます。資格情報はリポジトリ配下には一切書かれず、macOS ではキーチェーンに保存されるため再起動をまたいで保持されます (それ以外の OS ではサーバーメモリのみで、再起動後は再入力が必要です)。"
27252
27386
  ],
27253
27387
  [
27254
27388
  "SQLite",
@@ -27262,6 +27396,10 @@ code-viewer annotate add-db --db app.db --tab query \\
27262
27396
  "PostgreSQL",
27263
27397
  "compose ファイルから検出。同一サーバー上の複数データベースに対応し、スキーマ切替セレクターで再オープンせずスキーマを切り替えられます。SQLite と同じく行のインライン編集 / 追加 / 削除に対応。ローカルの Supabase CLI (`supabase start`) プロジェクトも `supabase/config.toml` から自動検出され、docker-compose ファイルは不要です。"
27264
27398
  ],
27399
+ [
27400
+ "Cloudflare D1",
27401
+ "アカウント ID・データベース ID・API トークン (D1:Read 権限が必要) を入力して保存済み接続として追加します。D1 REST API 経由で閲覧し、SQL 系の画面 (テーブル一覧・行グリッド・クエリエディタ・スキーマ・ER 図・スナップショット/差分) をそのまま使えます。閲覧専用で、クエリエディタは SELECT / PRAGMA / EXPLAIN / WITH のみ受け付け、グリッドの Edit モードは表示されません。"
27402
+ ],
27265
27403
  [
27266
27404
  "Redis",
27267
27405
  "compose ファイルから検出。DB 0-15 を SCAN し、string/hash/list は専用ペイン、set/zset/stream は JSON ビュー。値の編集 / キー削除 / 新規キー作成 (全タイプ) に対応。スナップショット/差分にも参加します。"
@@ -27275,8 +27413,8 @@ code-viewer annotate add-db --db app.db --tab query \\
27275
27413
  "LocalStack の compose サービスで DynamoDB が有効な場合に検出。テーブル一覧、構造タブ(キースキーマ・GSI/LSI・読み込み済みアイテムから推測した非キー属性の型)、scan / query、継続トークンによるページング、コピー可能なキー付きのアイテム詳細を表示します。閲覧専用です。"
27276
27414
  ],
27277
27415
  [
27278
- "S3 / MinIO / LocalStack",
27279
- "compose ファイルから検出。フォルダツリー型ブラウザ、prefix/ファイル名検索、更新日時順表示、画像/動画/音声/PDF/Markdown/HTML/テキストのプレビューに対応。テキスト/Markdown/JSON のインライン編集、新規オブジェクトアップロード、オブジェクト削除も可能。LocalStack はホストポート未公開時 `docker exec curl` にフォールバックしますが、MinIO はホストポート公開が必須です。"
27416
+ "S3 / MinIO / LocalStack / Cloudflare R2",
27417
+ "compose ファイルから検出するほか、保存済み接続としても追加できます。R2 は「Cloudflare R2」プロバイダプリセットを選び、アカウント ID と R2 のアクセスキーを入力すればエンドポイントと必須リージョン auto が自動で入ります。フォルダツリー型ブラウザ、prefix/ファイル名検索、更新日時順表示、画像/動画/音声/PDF/Markdown/HTML/テキストのプレビューに対応。テキスト/Markdown/JSON のインライン編集、新規オブジェクトアップロード、オブジェクト削除も可能。LocalStack はホストポート未公開時 `docker exec curl` にフォールバックしますが、MinIO はホストポート公開が必須です。"
27280
27418
  ]
27281
27419
  ]
27282
27420
  }
@@ -30831,6 +30969,59 @@ code-viewer query agent-help`
30831
30969
  return trimmed;
30832
30970
  }
30833
30971
 
30972
+ // web-src/views/markdown-link-navigation.ts
30973
+ async function openMarkdownLink(target, deps) {
30974
+ const ref = target.ref || "worktree";
30975
+ const routeAtClick = currentRouteKey();
30976
+ const directory = await isMarkdownDirectoryLink(target, deps);
30977
+ if (directory === null || currentRouteKey() !== routeAtClick)
30978
+ return;
30979
+ if (directory) {
30980
+ deps.setRoute(deps.repoRoute(ref, target.path));
30981
+ await deps.loadRepo();
30982
+ return;
30983
+ }
30984
+ const anchorInMarkdown = !!target.hash && sourcePreviewKind(target.path) === "markdown";
30985
+ deps.setRoute({
30986
+ screen: "file",
30987
+ path: target.path,
30988
+ ref,
30989
+ view: "blob",
30990
+ ...anchorInMarkdown ? { preview: true } : {},
30991
+ range: deps.currentRange()
30992
+ });
30993
+ applyMarkdownLinkHash(target.hash);
30994
+ await deps.renderStandaloneSource({ path: target.path, ref });
30995
+ }
30996
+ function currentRouteKey() {
30997
+ return window.location.pathname + window.location.search;
30998
+ }
30999
+ async function isMarkdownDirectoryLink(target, deps) {
31000
+ if (target.directory)
31001
+ return true;
31002
+ if (!target.path)
31003
+ return true;
31004
+ const name = target.path.split("/").pop() || "";
31005
+ if (/.\.[^.]+$/.test(name))
31006
+ return false;
31007
+ try {
31008
+ const res = await deps.trackLoad(fetch(buildRawFileUrl({ path: target.path, ref: target.ref }), {
31009
+ method: "HEAD"
31010
+ }));
31011
+ return res.status === 404;
31012
+ } catch (err) {
31013
+ if (deps.isAbortError(err))
31014
+ return null;
31015
+ return false;
31016
+ }
31017
+ }
31018
+ function applyMarkdownLinkHash(hash) {
31019
+ if (!hash)
31020
+ return;
31021
+ const base2 = window.location.pathname + window.location.search;
31022
+ history.replaceState(history.state, "", `${base2}#${encodeURIComponent(hash)}`);
31023
+ }
31024
+
30834
31025
  // web-src/views/repository-web-link.ts
30835
31026
  function createRepositoryWebLink(target, label) {
30836
31027
  const link2 = document.createElement("a");
@@ -31059,6 +31250,43 @@ code-viewer query agent-help`
31059
31250
  function toggleSidebarHidden() {
31060
31251
  applySidebarHidden(!STATE.sidebarHidden);
31061
31252
  }
31253
+ function applyDirEntryToNode(node, entry) {
31254
+ node.explicit = true;
31255
+ if (entry.children_omitted === true) {
31256
+ node.children_omitted = true;
31257
+ node.children_omitted_reason = entry.children_omitted_reason;
31258
+ }
31259
+ if (entry.is_symlink) {
31260
+ node.is_symlink = true;
31261
+ node.symlink_target = entry.symlink_target;
31262
+ node.resolved_path = entry.resolved_path;
31263
+ }
31264
+ if (entry.status)
31265
+ node.status = entry.status;
31266
+ }
31267
+ function createTreeDirLabel(dir) {
31268
+ const label = document.createElement("span");
31269
+ label.className = "dir-label";
31270
+ const dn = document.createElement("span");
31271
+ dn.className = "dir-name";
31272
+ dn.textContent = dir.name;
31273
+ dn.title = dir.path;
31274
+ label.appendChild(dn);
31275
+ if (dir.status)
31276
+ label.appendChild(fileBadge(dir.status));
31277
+ if (dir.children_omitted) {
31278
+ const omitted = document.createElement("span");
31279
+ omitted.className = "dir-omitted " + (dir.children_omitted_reason === "heavy" ? "dir-omitted-heavy" : "dir-omitted-internal");
31280
+ const badge = omittedDirectoryBadge(dir.children_omitted_reason);
31281
+ omitted.textContent = badge.label;
31282
+ omitted.title = badge.title;
31283
+ label.appendChild(omitted);
31284
+ }
31285
+ const dirSymlinkLabel = symlinkTargetLabel(dir);
31286
+ if (dirSymlinkLabel)
31287
+ label.appendChild(dirSymlinkLabel);
31288
+ return label;
31289
+ }
31062
31290
  function buildTree(files) {
31063
31291
  const root = {
31064
31292
  name: "",
@@ -31090,16 +31318,7 @@ code-viewer query agent-help`
31090
31318
  node.minOrder = f2.order;
31091
31319
  }
31092
31320
  if (f2.type === "tree") {
31093
- node.explicit = true;
31094
- if (f2.children_omitted === true) {
31095
- node.children_omitted = true;
31096
- node.children_omitted_reason = f2.children_omitted_reason;
31097
- }
31098
- if (f2.is_symlink) {
31099
- node.is_symlink = true;
31100
- node.symlink_target = f2.symlink_target;
31101
- node.resolved_path = f2.resolved_path;
31102
- }
31321
+ applyDirEntryToNode(node, f2);
31103
31322
  continue;
31104
31323
  }
31105
31324
  node.files.push(f2);
@@ -31169,25 +31388,7 @@ code-viewer query agent-help`
31169
31388
  const dirIcon = document.createElement("span");
31170
31389
  dirIcon.className = "dir-icon";
31171
31390
  li.appendChild(dirIcon);
31172
- const label = document.createElement("span");
31173
- label.className = "dir-label";
31174
- const dn = document.createElement("span");
31175
- dn.className = "dir-name";
31176
- dn.textContent = dir.name;
31177
- dn.title = dir.path;
31178
- label.appendChild(dn);
31179
- if (dir.children_omitted) {
31180
- const omitted = document.createElement("span");
31181
- omitted.className = "dir-omitted " + (dir.children_omitted_reason === "heavy" ? "dir-omitted-heavy" : "dir-omitted-internal");
31182
- const badge = omittedDirectoryBadge(dir.children_omitted_reason);
31183
- omitted.textContent = badge.label;
31184
- omitted.title = badge.title;
31185
- label.appendChild(omitted);
31186
- }
31187
- const dirSymlinkLabel = symlinkTargetLabel(dir);
31188
- if (dirSymlinkLabel)
31189
- label.appendChild(dirSymlinkLabel);
31190
- li.appendChild(label);
31391
+ li.appendChild(createTreeDirLabel(dir));
31191
31392
  li.appendChild(createOpenPathButton(dir.path, "directory", openDirectoryInOsTitle()));
31192
31393
  const collapsed = STATE.collapsedDirs.has(dir.path);
31193
31394
  if (collapsed)
@@ -31284,16 +31485,7 @@ code-viewer query agent-help`
31284
31485
  node.minOrder = Math.min(node.minOrder, order);
31285
31486
  }
31286
31487
  if (entry.type === "tree") {
31287
- node.explicit = true;
31288
- if (entry.children_omitted === true) {
31289
- node.children_omitted = true;
31290
- node.children_omitted_reason = entry.children_omitted_reason;
31291
- }
31292
- if (entry.is_symlink) {
31293
- node.is_symlink = true;
31294
- node.symlink_target = entry.symlink_target;
31295
- node.resolved_path = entry.resolved_path;
31296
- }
31488
+ applyDirEntryToNode(node, entry);
31297
31489
  return;
31298
31490
  }
31299
31491
  if (!node.files.some((file) => file.path === entry.path))
@@ -31377,25 +31569,7 @@ code-viewer query agent-help`
31377
31569
  const dirIcon = document.createElement("span");
31378
31570
  dirIcon.className = "dir-icon";
31379
31571
  li.appendChild(dirIcon);
31380
- const label = document.createElement("span");
31381
- label.className = "dir-label";
31382
- const dn = document.createElement("span");
31383
- dn.className = "dir-name";
31384
- dn.textContent = dir.name;
31385
- dn.title = dir.path;
31386
- label.appendChild(dn);
31387
- if (dir.children_omitted) {
31388
- const omitted = document.createElement("span");
31389
- omitted.className = "dir-omitted " + (dir.children_omitted_reason === "heavy" ? "dir-omitted-heavy" : "dir-omitted-internal");
31390
- const badge = omittedDirectoryBadge(dir.children_omitted_reason);
31391
- omitted.textContent = badge.label;
31392
- omitted.title = badge.title;
31393
- label.appendChild(omitted);
31394
- }
31395
- const dirSymlinkLabel = symlinkTargetLabel(dir);
31396
- if (dirSymlinkLabel)
31397
- label.appendChild(dirSymlinkLabel);
31398
- li.appendChild(label);
31572
+ li.appendChild(createTreeDirLabel(dir));
31399
31573
  li.appendChild(createOpenPathButton(dir.path, "directory", openDirectoryInOsTitle()));
31400
31574
  const updateIcon = () => {
31401
31575
  setFolderIcon(dirIcon, li.classList.contains("collapsed"));
@@ -32533,6 +32707,7 @@ code-viewer query agent-help`
32533
32707
  renderStandaloneSource,
32534
32708
  repoFileTargetFromRoute,
32535
32709
  trackLoad,
32710
+ isAbortError: isAbortError3,
32536
32711
  syncSidebarHeaderHeight,
32537
32712
  clearLoadQueue,
32538
32713
  getProjectName,
@@ -32894,6 +33069,17 @@ code-viewer query agent-help`
32894
33069
  range: currentRange()
32895
33070
  };
32896
33071
  }
33072
+ function markdownLinkNavigationDeps() {
33073
+ return {
33074
+ setRoute,
33075
+ currentRange,
33076
+ loadRepo,
33077
+ repoRoute,
33078
+ renderStandaloneSource,
33079
+ trackLoad,
33080
+ isAbortError: isAbortError3
33081
+ };
33082
+ }
32897
33083
  function createRepoBreadcrumb(target, path) {
32898
33084
  const nav = document.createElement("nav");
32899
33085
  nav.className = "gdp-file-breadcrumb gdp-repo-breadcrumb";
@@ -33021,10 +33207,17 @@ code-viewer query agent-help`
33021
33207
  row.className = nonBrowsableCommit ? `gdp-repo-row ${entry.type} gdp-repo-row-gitlink` : entry.is_symlink ? `gdp-repo-row ${entry.type} symlink-row${brokenSymlink ? " symlink-broken-row gdp-row-disabled" : ""}` : `gdp-repo-row ${entry.type}`;
33022
33208
  if (deletedEntry)
33023
33209
  row.classList.add("gdp-row-disabled");
33024
- const icon = entry.status ? fileBadge(entry.status) : repoEntryTypeIcon(entry, browsable, nonBrowsableCommit);
33210
+ const directoryRow = entry.type === "tree";
33211
+ const icon = entry.status && !directoryRow ? fileBadge(entry.status) : repoEntryTypeIcon(entry, browsable, nonBrowsableCommit);
33025
33212
  const name = document.createElement("span");
33026
33213
  name.className = "name";
33027
33214
  name.textContent = entry.name;
33215
+ let nameCell = name;
33216
+ if (directoryRow && entry.status) {
33217
+ nameCell = document.createElement("span");
33218
+ nameCell.className = "name-cell";
33219
+ nameCell.append(name, fileBadge(entry.status));
33220
+ }
33028
33221
  if (nonBrowsableCommit) {
33029
33222
  row.title = commitEntryMeta(entry.submodule).title;
33030
33223
  row.setAttribute("aria-disabled", "true");
@@ -33033,7 +33226,7 @@ code-viewer query agent-help`
33033
33226
  }
33034
33227
  const metaBlock = createRepoEntryMeta(entry, browsable);
33035
33228
  const size = createRepoEntrySize(entry);
33036
- row.append(icon, name, metaBlock, size);
33229
+ row.append(icon, nameCell, metaBlock, size);
33037
33230
  row.addEventListener("click", () => {
33038
33231
  if (brokenSymlink || deletedEntry)
33039
33232
  return;
@@ -33087,16 +33280,7 @@ code-viewer query agent-help`
33087
33280
  try {
33088
33281
  wrapper.appendChild(await renderMarkdownPreview(meta.readme.text, { path: meta.readme.path, ref: meta.ref }, {
33089
33282
  syntaxHighlight: STATE.syntaxHighlight,
33090
- onNavigateMarkdown: (path, ref) => {
33091
- setRoute({
33092
- screen: "file",
33093
- path,
33094
- ref,
33095
- view: "blob",
33096
- range: currentRange()
33097
- });
33098
- renderStandaloneSource({ path, ref });
33099
- }
33283
+ onNavigateMarkdown: (link2) => void openMarkdownLink(link2, markdownLinkNavigationDeps())
33100
33284
  }));
33101
33285
  } catch {
33102
33286
  const fallback = document.createElement("pre");
@@ -34321,6 +34505,17 @@ code-viewer query agent-help`
34321
34505
  focusMainSurface,
34322
34506
  isPaletteOpen
34323
34507
  } = deps;
34508
+ function markdownLinkNavigationDeps() {
34509
+ return {
34510
+ setRoute,
34511
+ currentRange,
34512
+ loadRepo,
34513
+ repoRoute,
34514
+ renderStandaloneSource,
34515
+ trackLoad,
34516
+ isAbortError: isAbortError3
34517
+ };
34518
+ }
34324
34519
  const VIRTUAL_SOURCE_LINE_THRESHOLD = 3000;
34325
34520
  const VIRTUAL_SOURCE_SIZE_THRESHOLD = 1024 * 1024;
34326
34521
  const VIRTUAL_SOURCE_PAGE_SIZE = 2000;
@@ -34562,16 +34757,7 @@ code-viewer query agent-help`
34562
34757
  (deps.renderMarkdownPreview ?? renderMarkdownPreview)(textValue, target, {
34563
34758
  syntaxHighlight: true,
34564
34759
  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
- }
34760
+ onNavigateMarkdown: (link2) => void openMarkdownLink(link2, markdownLinkNavigationDeps())
34575
34761
  }).then((next) => {
34576
34762
  if (signal?.aborted || !preview.isConnected || !sourceTargetsEqual(sourceTargetFromRoute(), target))
34577
34763
  return;
@@ -34856,16 +35042,7 @@ code-viewer query agent-help`
34856
35042
  let preview = previewKind === "html" ? renderHtmlPreview(target, textValue) : await (deps.renderMarkdownPreview ?? renderMarkdownPreview)(textValue, target, {
34857
35043
  syntaxHighlight: false,
34858
35044
  signal,
34859
- onNavigateMarkdown: (path, ref) => {
34860
- setRoute({
34861
- screen: "file",
34862
- path,
34863
- ref,
34864
- view: "blob",
34865
- range: currentRange()
34866
- });
34867
- renderStandaloneSource({ path, ref });
34868
- }
35045
+ onNavigateMarkdown: (link2) => void openMarkdownLink(link2, markdownLinkNavigationDeps())
34869
35046
  });
34870
35047
  if (signal?.aborted)
34871
35048
  return false;
@@ -34956,16 +35133,7 @@ code-viewer query agent-help`
34956
35133
  let preview = previewKind === "html" ? renderHtmlPreview(target, textValue) : await (deps.renderMarkdownPreview ?? renderMarkdownPreview)(textValue, target, {
34957
35134
  syntaxHighlight: false,
34958
35135
  signal,
34959
- onNavigateMarkdown: (path, ref) => {
34960
- setRoute({
34961
- screen: "file",
34962
- path,
34963
- ref,
34964
- view: "blob",
34965
- range: currentRange()
34966
- });
34967
- renderStandaloneSource({ path, ref });
34968
- }
35136
+ onNavigateMarkdown: (link2) => void openMarkdownLink(link2, markdownLinkNavigationDeps())
34969
35137
  });
34970
35138
  if (signal?.aborted)
34971
35139
  return false;
@@ -36754,6 +36922,7 @@ code-viewer query agent-help`
36754
36922
  renderStandaloneSource,
36755
36923
  repoFileTargetFromRoute,
36756
36924
  trackLoad,
36925
+ isAbortError: isAbortError3,
36757
36926
  syncSidebarHeaderHeight,
36758
36927
  clearLoadQueue: () => DIFF_VIEW.clearLoadQueue(),
36759
36928
  getProjectName: () => PROJECT_NAME,