@youtyan/code-viewer 0.8.8 → 0.9.0

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 = {
@@ -1314,7 +1346,8 @@ ${lines.join(`
1314
1346
  cts: "typescript",
1315
1347
  kts: "kotlin",
1316
1348
  cxx: "cpp",
1317
- hxx: "cpp"
1349
+ hxx: "cpp",
1350
+ gd: "gdscript"
1318
1351
  };
1319
1352
  var TEXT_SOURCE_EXTENSIONS = new Set([
1320
1353
  ...Object.keys(EXT_TO_LANG),
@@ -7585,6 +7618,8 @@ ${lines.join(`
7585
7618
  tf: "terraform",
7586
7619
  tfvars: "terraform",
7587
7620
  hcl: "terraform",
7621
+ gd: "gdscript",
7622
+ godot: "gdscript",
7588
7623
  yml: "yaml",
7589
7624
  ts: "typescript",
7590
7625
  tsx: "typescript",
@@ -7601,6 +7636,7 @@ ${lines.join(`
7601
7636
  "csharp",
7602
7637
  "css",
7603
7638
  "dockerfile",
7639
+ "gdscript",
7604
7640
  "go",
7605
7641
  "graphql",
7606
7642
  "html",
@@ -7633,15 +7669,24 @@ ${lines.join(`
7633
7669
  function markdownSlugify(text2) {
7634
7670
  return text2.trim().toLowerCase().replace(/[\s ]+/g, "-").replace(/[^\p{L}\p{N}\-_]/gu, "").slice(0, 80) || "section";
7635
7671
  }
7636
- function resolveMarkdownRelativePath(currentPath, href) {
7672
+ function resolveMarkdownLinkTarget(currentPath, href) {
7637
7673
  if (!href || href.startsWith("#"))
7638
7674
  return null;
7639
7675
  if (/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(href))
7640
7676
  return null;
7641
- const cleanHref = href.replace(/[?#].*$/, "");
7642
- 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)
7643
7681
  return null;
7644
- 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
+ };
7645
7690
  }
7646
7691
  function resolveMarkdownAssetPath(currentPath, src) {
7647
7692
  if (!src || src.startsWith("#") || /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(src))
@@ -7649,6 +7694,13 @@ ${lines.join(`
7649
7694
  const cleanSrc = src.split(/[?#]/, 1)[0];
7650
7695
  return resolveRepoRelative(currentPath, cleanSrc);
7651
7696
  }
7697
+ function decodeUriComponentSafe(value) {
7698
+ try {
7699
+ return decodeURIComponent(value);
7700
+ } catch {
7701
+ return value;
7702
+ }
7703
+ }
7652
7704
  function resolveRepoRelative(currentPath, requestedPath) {
7653
7705
  const base2 = currentPath.split("/").slice(0, -1);
7654
7706
  const parts = [
@@ -7744,11 +7796,15 @@ ${lines.join(`
7744
7796
  md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
7745
7797
  const token = tokens[idx];
7746
7798
  const href = token.attrGet("href") || "";
7747
- const mdPath = resolveMarkdownRelativePath(target.path, href);
7748
- if (mdPath) {
7799
+ const link2 = resolveMarkdownLinkTarget(target.path, href);
7800
+ if (link2) {
7749
7801
  token.attrSet("href", "#");
7750
- token.attrSet("data-gdp-md-link", mdPath);
7802
+ token.attrSet("data-gdp-md-link", link2.path);
7751
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");
7752
7808
  } else if (/^(?:https?:)?\/\//i.test(href)) {
7753
7809
  token.attrSet("target", "_blank");
7754
7810
  token.attrSet("rel", "noopener noreferrer");
@@ -7863,11 +7919,15 @@ ${frontmatter.yaml}
7863
7919
  if (!link2)
7864
7920
  return;
7865
7921
  const path = link2.dataset.gdpMdLink;
7866
- const ref = link2.dataset.gdpMdRef || target.ref;
7867
- if (!path)
7922
+ if (path == null)
7868
7923
  return;
7869
7924
  e2.preventDefault();
7870
- 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
+ });
7871
7931
  });
7872
7932
  setupMarkdownScrollSpy(root);
7873
7933
  setupMermaidLightbox(root);
@@ -7950,12 +8010,7 @@ ${frontmatter.yaml}
7950
8010
  scrollMarkdownSectionIntoView(section, "auto");
7951
8011
  }
7952
8012
  function decodeHashFragment(hash) {
7953
- const value = hash.startsWith("#") ? hash.slice(1) : hash;
7954
- try {
7955
- return decodeURIComponent(value);
7956
- } catch {
7957
- return value;
7958
- }
8013
+ return decodeUriComponentSafe(hash.startsWith("#") ? hash.slice(1) : hash);
7959
8014
  }
7960
8015
  function scrollMarkdownSectionIntoView(section, behavior) {
7961
8016
  const top = section.getBoundingClientRect().top + window.scrollY - markdownAnchorOffset() - 12;
@@ -9799,6 +9854,24 @@ ${frontmatter.yaml}
9799
9854
  function isBlobOrBlameFileRoute(route) {
9800
9855
  return route.screen === "file" && (route.view === "blob" || route.view === "blame");
9801
9856
  }
9857
+ function fileRouteKeepingActiveView(currentRoute, target, range) {
9858
+ const base2 = {
9859
+ screen: "file",
9860
+ path: target.path,
9861
+ ref: target.ref,
9862
+ range
9863
+ };
9864
+ if (currentRoute.screen !== "file")
9865
+ return { ...base2, view: "blob" };
9866
+ if (currentRoute.view === "blame" || currentRoute.view === "history")
9867
+ return { ...base2, view: currentRoute.view };
9868
+ const keepPreview = currentRoute.view === "blob" && !!currentRoute.preview && (isPreviewableSource(target.path) || isMediaPreviewOnlySource(target.path));
9869
+ return {
9870
+ ...base2,
9871
+ view: "blob",
9872
+ ...keepPreview ? { preview: true } : {}
9873
+ };
9874
+ }
9802
9875
  function createFileViewTabButton(deps, target, view, label, active) {
9803
9876
  const btn = document.createElement("button");
9804
9877
  btn.type = "button";
@@ -10255,11 +10328,21 @@ ${frontmatter.yaml}
10255
10328
  var KINDS = [
10256
10329
  "postgresql",
10257
10330
  "mysql",
10331
+ "d1",
10258
10332
  "redis",
10259
10333
  "elasticsearch",
10260
10334
  "s3",
10261
10335
  "dynamodb"
10262
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
+ }
10263
10346
  function text2(language) {
10264
10347
  return language === "ja" ? {
10265
10348
  titleAdd: "データストア接続を追加",
@@ -10273,6 +10356,12 @@ ${frontmatter.yaml}
10273
10356
  password: "パスワード",
10274
10357
  database: "データベース",
10275
10358
  schema: "既定スキーマ(任意)",
10359
+ provider: "プロバイダ",
10360
+ providerCustom: "S3 互換(AWS / MinIO / LocalStack)",
10361
+ providerR2: "Cloudflare R2",
10362
+ accountId: "Cloudflare アカウント ID",
10363
+ databaseId: "データベース ID",
10364
+ apiToken: "API トークン",
10276
10365
  endpoint: "エンドポイント URL",
10277
10366
  region: "リージョン",
10278
10367
  accessKeyId: "アクセスキー ID",
@@ -10291,7 +10380,9 @@ ${frontmatter.yaml}
10291
10380
  requestFailed: "接続情報を保存できませんでした",
10292
10381
  deleteTitle: "保存済み接続を削除",
10293
10382
  deleteBody: "この接続情報を削除します。タブやスナップショットのデータは削除されません。",
10294
- delete: "削除"
10383
+ delete: "削除",
10384
+ secretsLeftTitle: "資格情報がキーチェーンに残りました",
10385
+ secretsLeftBody: "接続は削除しましたが、キーチェーンから資格情報を削除できませんでした。キーチェーンがロックされている可能性があります。ロックを解除して「キーチェーンアクセス」から code-viewer の項目を削除してください。"
10295
10386
  } : {
10296
10387
  titleAdd: "Add datastore connection",
10297
10388
  titleEdit: "Edit datastore connection",
@@ -10304,6 +10395,12 @@ ${frontmatter.yaml}
10304
10395
  password: "Password",
10305
10396
  database: "Database",
10306
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",
10307
10404
  endpoint: "Endpoint URL",
10308
10405
  region: "Region",
10309
10406
  accessKeyId: "Access key ID",
@@ -10322,7 +10419,9 @@ ${frontmatter.yaml}
10322
10419
  requestFailed: "Failed to save the connection",
10323
10420
  deleteTitle: "Delete saved connection",
10324
10421
  deleteBody: "This removes the saved connection. Tabs and snapshot data are not deleted.",
10325
- 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."
10326
10425
  };
10327
10426
  }
10328
10427
  function field(labelText, input, required = false, requiredLabel = "Required") {
@@ -10386,6 +10485,21 @@ ${frontmatter.yaml}
10386
10485
  database.value = current?.database ?? "";
10387
10486
  const schema = input();
10388
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";
10389
10503
  const endpoint = input("url");
10390
10504
  endpoint.value = current?.endpoint ?? "http://127.0.0.1:9200";
10391
10505
  const region = input();
@@ -10407,6 +10521,10 @@ ${frontmatter.yaml}
10407
10521
  password: field(labels.password, password),
10408
10522
  database: field(labels.database, database, true, labels.requiredField),
10409
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),
10410
10528
  endpoint: field(labels.endpoint, endpoint, true, labels.requiredField),
10411
10529
  region: field(labels.region, region, true, labels.requiredField),
10412
10530
  accessKeyId: field(labels.accessKeyId, accessKeyId, true, labels.requiredField),
@@ -10415,21 +10533,28 @@ ${frontmatter.yaml}
10415
10533
  tls: field(labels.tls, tls)
10416
10534
  };
10417
10535
  form.append(...Object.values(rows));
10536
+ const isR2 = () => kind.value === "s3" && provider.value === "r2";
10418
10537
  const syncKind = () => {
10419
10538
  const value = kind.value;
10420
10539
  const sql = value === "postgresql" || value === "mysql";
10421
10540
  const redis = value === "redis";
10422
10541
  const http = value === "elasticsearch";
10423
10542
  const aws = value === "s3" || value === "dynamodb";
10543
+ const d1 = value === "d1";
10544
+ const r2 = isR2();
10424
10545
  rows.host.hidden = !sql && !redis;
10425
10546
  rows.port.hidden = !sql && !redis;
10426
10547
  rows.user.hidden = !sql;
10427
10548
  rows.username.hidden = !redis && !http;
10428
- rows.password.hidden = aws;
10549
+ rows.password.hidden = aws || d1;
10429
10550
  rows.database.hidden = !sql;
10430
10551
  rows.schema.hidden = value !== "postgresql";
10431
- rows.endpoint.hidden = !http && !aws;
10432
- 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;
10433
10558
  rows.accessKeyId.hidden = !aws;
10434
10559
  rows.secretAccessKey.hidden = !aws;
10435
10560
  rows.sessionToken.hidden = !aws;
@@ -10444,8 +10569,11 @@ ${frontmatter.yaml}
10444
10569
  if (!current && (http || aws)) {
10445
10570
  endpoint.value = value === "elasticsearch" ? "http://127.0.0.1:9200" : "http://127.0.0.1:4566";
10446
10571
  }
10572
+ if (!current && aws)
10573
+ region.value = r2 ? R2_REGION : "us-east-1";
10447
10574
  };
10448
10575
  kind.addEventListener("change", syncKind);
10576
+ provider.addEventListener("change", syncKind);
10449
10577
  syncKind();
10450
10578
  const validateConnection = () => {
10451
10579
  const value = kind.value;
@@ -10462,10 +10590,16 @@ ${frontmatter.yaml}
10462
10590
  if ((value === "postgresql" || value === "mysql") && (!user.value.trim() || !database.value.trim())) {
10463
10591
  return labels.required;
10464
10592
  }
10465
- 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()) {
10597
+ return labels.required;
10598
+ }
10599
+ if ((value === "elasticsearch" || value === "s3" && !isR2() || value === "dynamodb") && !endpoint.value.trim()) {
10466
10600
  return labels.required;
10467
10601
  }
10468
- if ((value === "s3" || value === "dynamodb") && (!region.value.trim() || !accessKeyId.value.trim())) {
10602
+ if ((value === "s3" || value === "dynamodb") && (!isR2() && !region.value.trim() || !accessKeyId.value.trim())) {
10469
10603
  return labels.required;
10470
10604
  }
10471
10605
  return null;
@@ -10501,10 +10635,16 @@ ${frontmatter.yaml}
10501
10635
  ...username.value.trim() || !current ? { username: username.value.trim() || undefined } : {},
10502
10636
  ...password.value || !current ? { password: password.value } : {}
10503
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
+ });
10504
10644
  } else {
10505
10645
  Object.assign(payload, {
10506
- endpoint: endpoint.value.trim(),
10507
- region: region.value.trim(),
10646
+ endpoint: isR2() ? r2EndpointFor(accountId.value.trim()) : endpoint.value.trim(),
10647
+ region: isR2() ? R2_REGION : region.value.trim(),
10508
10648
  ...accessKeyId.value.trim() || !current ? { accessKeyId: accessKeyId.value.trim() } : {},
10509
10649
  ...secretAccessKey.value || !current ? { secretAccessKey: secretAccessKey.value } : {},
10510
10650
  ...sessionToken.value ? { sessionToken: sessionToken.value } : {}
@@ -10614,6 +10754,13 @@ ${frontmatter.yaml}
10614
10754
  }));
10615
10755
  if (!response.ok)
10616
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
+ }
10617
10764
  return true;
10618
10765
  }
10619
10766
 
@@ -20289,8 +20436,22 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20289
20436
  function isAbortError2(err) {
20290
20437
  return err instanceof DOMException && err.name === "AbortError" || err instanceof Error && err.name === "AbortError";
20291
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
+ ]);
20292
20450
  function isSqlKind(kind) {
20293
- 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);
20294
20455
  }
20295
20456
  function isPostgresKind(kind) {
20296
20457
  return kind === "postgresql";
@@ -20646,7 +20807,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20646
20807
  cb.onStateChange();
20647
20808
  },
20648
20809
  getText: () => paneText(),
20649
- getEditable: () => isSqlKind(currentDbInfo?.kind),
20810
+ getEditable: () => isRowEditableKind(currentDbInfo?.kind),
20650
20811
  applyMutations: (mutations) => applyRowMutations(mutations),
20651
20812
  onRefreshComplete: ({ table: table2, filters }) => {
20652
20813
  if (filters.length === 0) {
@@ -26248,7 +26409,11 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
26248
26409
  },
26249
26410
  {
26250
26411
  kind: "paragraph",
26251
- 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."
26412
+ 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)."
26413
+ },
26414
+ {
26415
+ kind: "paragraph",
26416
+ 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."
26252
26417
  },
26253
26418
  {
26254
26419
  kind: "paragraph",
@@ -26501,7 +26666,7 @@ code-viewer annotate add-db --db app.db --tab query \\
26501
26666
  database: {
26502
26667
  nav: "Datastores",
26503
26668
  title: "Datastore Viewer",
26504
- intro: "Browse SQLite files, Docker-hosted databases, Redis, Elasticsearch, DynamoDB, and S3-compatible object stores from one local viewer.",
26669
+ intro: "Browse SQLite files, Docker-hosted databases, Cloudflare D1, Redis, Elasticsearch, DynamoDB, and S3-compatible object stores (including Cloudflare R2) from one local viewer.",
26505
26670
  groups: [
26506
26671
  {
26507
26672
  title: "Supported datastores",
@@ -26511,7 +26676,7 @@ code-viewer annotate add-db --db app.db --tab query \\
26511
26676
  rows: [
26512
26677
  [
26513
26678
  "Saved connections",
26514
- "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."
26679
+ "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."
26515
26680
  ],
26516
26681
  [
26517
26682
  "SQLite",
@@ -26525,6 +26690,10 @@ code-viewer annotate add-db --db app.db --tab query \\
26525
26690
  "PostgreSQL",
26526
26691
  "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."
26527
26692
  ],
26693
+ [
26694
+ "Cloudflare D1",
26695
+ "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."
26696
+ ],
26528
26697
  [
26529
26698
  "Redis",
26530
26699
  "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."
@@ -26538,8 +26707,8 @@ code-viewer annotate add-db --db app.db --tab query \\
26538
26707
  "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."
26539
26708
  ],
26540
26709
  [
26541
- "S3 / MinIO / LocalStack",
26542
- "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."
26710
+ "S3 / MinIO / LocalStack / Cloudflare R2",
26711
+ "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."
26543
26712
  ]
26544
26713
  ]
26545
26714
  }
@@ -26931,7 +27100,11 @@ code-viewer query agent-help`
26931
27100
  },
26932
27101
  {
26933
27102
  kind: "paragraph",
26934
- text: "ファイル詳細ページには最大 4 つのタブ (Preview / Code / Blame / History) があります。テキストファイルは Code がデフォルトで、?preview=1 を付けると Markdown / HTML プレビューに切り替わります。画像・動画・音声・PDF などのメディアファイルは Preview タブのみ表示され、Code タブは表示されません。Blame と History はそれぞれ専用 URL (view=blame, view=history) を持つため、ディープリンクとブラウザの戻る / 進むが同期し、いずれも Repository サイドバーは表示されたままです。"
27103
+ text: "ファイル詳細ページには最大 4 つのタブ (Preview / Code / Blame / History) があります。テキストファイルは Code がデフォルトで、?preview=1 を付けると Markdown / HTML プレビューに切り替わります。画像・動画・音声・PDF などのメディアファイルは Preview タブのみ表示され、Code タブは表示されません。Blame と History はそれぞれ専用 URL (view=blame, view=history) を持つため、ディープリンクとブラウザの戻る / 進むが同期し、いずれも Repository サイドバーは表示されたままです。ツリーから別のファイルを開いても選択中のタブは維持されます (プレビューできないファイルでは Code に戻ります)。"
27104
+ },
27105
+ {
27106
+ kind: "paragraph",
27107
+ text: "Markdown プレビュー内の相対リンクは GitHub と同じ行き先に解決されます。別の Markdown ファイルはそのファイルページを開き、#見出し 付きのリンクはプレビューを開いて該当見出しまでスクロールし、Markdown 以外のファイルは Code ビュー、ディレクトリへのリンクはリポジトリツリーのそのフォルダを開きます。"
26935
27108
  },
26936
27109
  {
26937
27110
  kind: "paragraph",
@@ -27184,7 +27357,7 @@ code-viewer annotate add-db --db app.db --tab query \\
27184
27357
  database: {
27185
27358
  nav: "データストア",
27186
27359
  title: "データストアビューア",
27187
- intro: "SQLite ファイル、Docker 上のデータベース、Redis、Elasticsearch、DynamoDB、S3 互換オブジェクトストアをローカルビューアで閲覧できます。",
27360
+ intro: "SQLite ファイル、Docker 上のデータベース、Cloudflare D1、Redis、Elasticsearch、DynamoDB、S3 互換オブジェクトストア (Cloudflare R2 を含む) をローカルビューアで閲覧できます。",
27188
27361
  groups: [
27189
27362
  {
27190
27363
  title: "対応データストア",
@@ -27194,7 +27367,7 @@ code-viewer annotate add-db --db app.db --tab query \\
27194
27367
  rows: [
27195
27368
  [
27196
27369
  "保存済み接続",
27197
- "データストア選択の横にある + から、任意の PostgreSQL、MySQL、Redis、Elasticsearch、S3 互換、DynamoDB エンドポイントを追加できます。必須項目にはマークが付き、保存前に「接続テスト」で入力内容を確認できます。ドライバーは同梱されているため、データベース CLI や curl は不要です。非機密設定はローカルに保存されますが、認証情報はサーバーメモリだけに保持され、再起動後は再入力が必要です。"
27370
+ "データストア選択の横にある + から、任意の PostgreSQL、MySQL、Cloudflare D1、Redis、Elasticsearch、S3 互換 (Cloudflare R2 プリセットあり)、DynamoDB エンドポイントを追加できます。必須項目にはマークが付き、保存前に「接続テスト」で入力内容を確認できます。ドライバーは同梱されているため、データベース CLI や curl は不要です。非機密設定はローカルに保存されます。資格情報はリポジトリ配下には一切書かれず、macOS ではキーチェーンに保存されるため再起動をまたいで保持されます (それ以外の OS ではサーバーメモリのみで、再起動後は再入力が必要です)。"
27198
27371
  ],
27199
27372
  [
27200
27373
  "SQLite",
@@ -27208,6 +27381,10 @@ code-viewer annotate add-db --db app.db --tab query \\
27208
27381
  "PostgreSQL",
27209
27382
  "compose ファイルから検出。同一サーバー上の複数データベースに対応し、スキーマ切替セレクターで再オープンせずスキーマを切り替えられます。SQLite と同じく行のインライン編集 / 追加 / 削除に対応。ローカルの Supabase CLI (`supabase start`) プロジェクトも `supabase/config.toml` から自動検出され、docker-compose ファイルは不要です。"
27210
27383
  ],
27384
+ [
27385
+ "Cloudflare D1",
27386
+ "アカウント ID・データベース ID・API トークン (D1:Read 権限が必要) を入力して保存済み接続として追加します。D1 REST API 経由で閲覧し、SQL 系の画面 (テーブル一覧・行グリッド・クエリエディタ・スキーマ・ER 図・スナップショット/差分) をそのまま使えます。閲覧専用で、クエリエディタは SELECT / PRAGMA / EXPLAIN / WITH のみ受け付け、グリッドの Edit モードは表示されません。"
27387
+ ],
27211
27388
  [
27212
27389
  "Redis",
27213
27390
  "compose ファイルから検出。DB 0-15 を SCAN し、string/hash/list は専用ペイン、set/zset/stream は JSON ビュー。値の編集 / キー削除 / 新規キー作成 (全タイプ) に対応。スナップショット/差分にも参加します。"
@@ -27221,8 +27398,8 @@ code-viewer annotate add-db --db app.db --tab query \\
27221
27398
  "LocalStack の compose サービスで DynamoDB が有効な場合に検出。テーブル一覧、構造タブ(キースキーマ・GSI/LSI・読み込み済みアイテムから推測した非キー属性の型)、scan / query、継続トークンによるページング、コピー可能なキー付きのアイテム詳細を表示します。閲覧専用です。"
27222
27399
  ],
27223
27400
  [
27224
- "S3 / MinIO / LocalStack",
27225
- "compose ファイルから検出。フォルダツリー型ブラウザ、prefix/ファイル名検索、更新日時順表示、画像/動画/音声/PDF/Markdown/HTML/テキストのプレビューに対応。テキスト/Markdown/JSON のインライン編集、新規オブジェクトアップロード、オブジェクト削除も可能。LocalStack はホストポート未公開時 `docker exec curl` にフォールバックしますが、MinIO はホストポート公開が必須です。"
27401
+ "S3 / MinIO / LocalStack / Cloudflare R2",
27402
+ "compose ファイルから検出するほか、保存済み接続としても追加できます。R2 は「Cloudflare R2」プロバイダプリセットを選び、アカウント ID と R2 のアクセスキーを入力すればエンドポイントと必須リージョン auto が自動で入ります。フォルダツリー型ブラウザ、prefix/ファイル名検索、更新日時順表示、画像/動画/音声/PDF/Markdown/HTML/テキストのプレビューに対応。テキスト/Markdown/JSON のインライン編集、新規オブジェクトアップロード、オブジェクト削除も可能。LocalStack はホストポート未公開時 `docker exec curl` にフォールバックしますが、MinIO はホストポート公開が必須です。"
27226
27403
  ]
27227
27404
  ]
27228
27405
  }
@@ -30777,6 +30954,59 @@ code-viewer query agent-help`
30777
30954
  return trimmed;
30778
30955
  }
30779
30956
 
30957
+ // web-src/views/markdown-link-navigation.ts
30958
+ async function openMarkdownLink(target, deps) {
30959
+ const ref = target.ref || "worktree";
30960
+ const routeAtClick = currentRouteKey();
30961
+ const directory = await isMarkdownDirectoryLink(target, deps);
30962
+ if (directory === null || currentRouteKey() !== routeAtClick)
30963
+ return;
30964
+ if (directory) {
30965
+ deps.setRoute(deps.repoRoute(ref, target.path));
30966
+ await deps.loadRepo();
30967
+ return;
30968
+ }
30969
+ const anchorInMarkdown = !!target.hash && sourcePreviewKind(target.path) === "markdown";
30970
+ deps.setRoute({
30971
+ screen: "file",
30972
+ path: target.path,
30973
+ ref,
30974
+ view: "blob",
30975
+ ...anchorInMarkdown ? { preview: true } : {},
30976
+ range: deps.currentRange()
30977
+ });
30978
+ applyMarkdownLinkHash(target.hash);
30979
+ await deps.renderStandaloneSource({ path: target.path, ref });
30980
+ }
30981
+ function currentRouteKey() {
30982
+ return window.location.pathname + window.location.search;
30983
+ }
30984
+ async function isMarkdownDirectoryLink(target, deps) {
30985
+ if (target.directory)
30986
+ return true;
30987
+ if (!target.path)
30988
+ return true;
30989
+ const name = target.path.split("/").pop() || "";
30990
+ if (/.\.[^.]+$/.test(name))
30991
+ return false;
30992
+ try {
30993
+ const res = await deps.trackLoad(fetch(buildRawFileUrl({ path: target.path, ref: target.ref }), {
30994
+ method: "HEAD"
30995
+ }));
30996
+ return res.status === 404;
30997
+ } catch (err) {
30998
+ if (deps.isAbortError(err))
30999
+ return null;
31000
+ return false;
31001
+ }
31002
+ }
31003
+ function applyMarkdownLinkHash(hash) {
31004
+ if (!hash)
31005
+ return;
31006
+ const base2 = window.location.pathname + window.location.search;
31007
+ history.replaceState(history.state, "", `${base2}#${encodeURIComponent(hash)}`);
31008
+ }
31009
+
30780
31010
  // web-src/views/repository-web-link.ts
30781
31011
  function createRepositoryWebLink(target, label) {
30782
31012
  const link2 = document.createElement("a");
@@ -32479,6 +32709,7 @@ code-viewer query agent-help`
32479
32709
  renderStandaloneSource,
32480
32710
  repoFileTargetFromRoute,
32481
32711
  trackLoad,
32712
+ isAbortError: isAbortError3,
32482
32713
  syncSidebarHeaderHeight,
32483
32714
  clearLoadQueue,
32484
32715
  getProjectName,
@@ -32840,6 +33071,17 @@ code-viewer query agent-help`
32840
33071
  range: currentRange()
32841
33072
  };
32842
33073
  }
33074
+ function markdownLinkNavigationDeps() {
33075
+ return {
33076
+ setRoute,
33077
+ currentRange,
33078
+ loadRepo,
33079
+ repoRoute,
33080
+ renderStandaloneSource,
33081
+ trackLoad,
33082
+ isAbortError: isAbortError3
33083
+ };
33084
+ }
32843
33085
  function createRepoBreadcrumb(target, path) {
32844
33086
  const nav = document.createElement("nav");
32845
33087
  nav.className = "gdp-file-breadcrumb gdp-repo-breadcrumb";
@@ -33033,16 +33275,7 @@ code-viewer query agent-help`
33033
33275
  try {
33034
33276
  wrapper.appendChild(await renderMarkdownPreview(meta.readme.text, { path: meta.readme.path, ref: meta.ref }, {
33035
33277
  syntaxHighlight: STATE.syntaxHighlight,
33036
- onNavigateMarkdown: (path, ref) => {
33037
- setRoute({
33038
- screen: "file",
33039
- path,
33040
- ref,
33041
- view: "blob",
33042
- range: currentRange()
33043
- });
33044
- renderStandaloneSource({ path, ref });
33045
- }
33278
+ onNavigateMarkdown: (link2) => void openMarkdownLink(link2, markdownLinkNavigationDeps())
33046
33279
  }));
33047
33280
  } catch {
33048
33281
  const fallback = document.createElement("pre");
@@ -33118,14 +33351,10 @@ code-viewer query agent-help`
33118
33351
  loadRepo();
33119
33352
  return;
33120
33353
  }
33121
- setRoute({
33122
- screen: "file",
33123
- path: file.path,
33124
- ref: normalizedRef,
33125
- view: "blob",
33126
- range: currentRange()
33127
- });
33128
- renderStandaloneSource({ path: file.path, ref: normalizedRef });
33354
+ const fileRoute = fileRouteKeepingActiveView(STATE.route, { path: file.path, ref: normalizedRef }, currentRange());
33355
+ setRoute(fileRoute);
33356
+ if (fileRoute.view === "blob")
33357
+ renderStandaloneSource({ path: file.path, ref: normalizedRef });
33129
33358
  });
33130
33359
  await activateRepoSidebarPath(currentPath);
33131
33360
  }).catch(() => {
@@ -34271,6 +34500,17 @@ code-viewer query agent-help`
34271
34500
  focusMainSurface,
34272
34501
  isPaletteOpen
34273
34502
  } = deps;
34503
+ function markdownLinkNavigationDeps() {
34504
+ return {
34505
+ setRoute,
34506
+ currentRange,
34507
+ loadRepo,
34508
+ repoRoute,
34509
+ renderStandaloneSource,
34510
+ trackLoad,
34511
+ isAbortError: isAbortError3
34512
+ };
34513
+ }
34274
34514
  const VIRTUAL_SOURCE_LINE_THRESHOLD = 3000;
34275
34515
  const VIRTUAL_SOURCE_SIZE_THRESHOLD = 1024 * 1024;
34276
34516
  const VIRTUAL_SOURCE_PAGE_SIZE = 2000;
@@ -34512,16 +34752,7 @@ code-viewer query agent-help`
34512
34752
  (deps.renderMarkdownPreview ?? renderMarkdownPreview)(textValue, target, {
34513
34753
  syntaxHighlight: true,
34514
34754
  signal,
34515
- onNavigateMarkdown: (path, ref) => {
34516
- setRoute({
34517
- screen: "file",
34518
- path,
34519
- ref,
34520
- view: "blob",
34521
- range: currentRange()
34522
- });
34523
- renderStandaloneSource({ path, ref });
34524
- }
34755
+ onNavigateMarkdown: (link2) => void openMarkdownLink(link2, markdownLinkNavigationDeps())
34525
34756
  }).then((next) => {
34526
34757
  if (signal?.aborted || !preview.isConnected || !sourceTargetsEqual(sourceTargetFromRoute(), target))
34527
34758
  return;
@@ -34806,16 +35037,7 @@ code-viewer query agent-help`
34806
35037
  let preview = previewKind === "html" ? renderHtmlPreview(target, textValue) : await (deps.renderMarkdownPreview ?? renderMarkdownPreview)(textValue, target, {
34807
35038
  syntaxHighlight: false,
34808
35039
  signal,
34809
- onNavigateMarkdown: (path, ref) => {
34810
- setRoute({
34811
- screen: "file",
34812
- path,
34813
- ref,
34814
- view: "blob",
34815
- range: currentRange()
34816
- });
34817
- renderStandaloneSource({ path, ref });
34818
- }
35040
+ onNavigateMarkdown: (link2) => void openMarkdownLink(link2, markdownLinkNavigationDeps())
34819
35041
  });
34820
35042
  if (signal?.aborted)
34821
35043
  return false;
@@ -34906,16 +35128,7 @@ code-viewer query agent-help`
34906
35128
  let preview = previewKind === "html" ? renderHtmlPreview(target, textValue) : await (deps.renderMarkdownPreview ?? renderMarkdownPreview)(textValue, target, {
34907
35129
  syntaxHighlight: false,
34908
35130
  signal,
34909
- onNavigateMarkdown: (path, ref) => {
34910
- setRoute({
34911
- screen: "file",
34912
- path,
34913
- ref,
34914
- view: "blob",
34915
- range: currentRange()
34916
- });
34917
- renderStandaloneSource({ path, ref });
34918
- }
35131
+ onNavigateMarkdown: (link2) => void openMarkdownLink(link2, markdownLinkNavigationDeps())
34919
35132
  });
34920
35133
  if (signal?.aborted)
34921
35134
  return false;
@@ -36704,6 +36917,7 @@ code-viewer query agent-help`
36704
36917
  renderStandaloneSource,
36705
36918
  repoFileTargetFromRoute,
36706
36919
  trackLoad,
36920
+ isAbortError: isAbortError3,
36707
36921
  syncSidebarHeaderHeight,
36708
36922
  clearLoadQueue: () => DIFF_VIEW.clearLoadQueue(),
36709
36923
  getProjectName: () => PROJECT_NAME,
@@ -37644,6 +37858,7 @@ code-viewer query agent-help`
37644
37858
  if (!hljsRef)
37645
37859
  return null;
37646
37860
  ensureTerraformHighlightLanguage(hljsRef);
37861
+ ensureGdscriptHighlightLanguage(hljsRef);
37647
37862
  if (!highlightConfigured && typeof hljsRef.configure === "function") {
37648
37863
  hljsRef.configure({ ignoreUnescapedHTML: true });
37649
37864
  highlightConfigured = true;