@youtyan/code-viewer 0.2.0 → 0.2.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
@@ -291,6 +291,11 @@
291
291
  }
292
292
  var PENCIL_16_PATH = "M11.013 1.427a1.75 1.75 0 0 1 2.474 0l1.086 1.086a1.75 1.75 0 0 1 0 2.474l-8.61 8.61c-.21.21-.47.364-.756.445l-3.251.93a.75.75 0 0 1-.927-.928l.929-3.25c.081-.286.235-.547.445-.758l8.61-8.609Zm.176 4.823L9.75 4.81l-6.286 6.287a.253.253 0 0 0-.064.108l-.558 1.953 1.953-.558a.253.253 0 0 0 .108-.064Zm1.238-3.763a.25.25 0 0 0-.354 0L10.811 3.75l1.439 1.44 1.263-1.263a.25.25 0 0 0 0-.354Z";
293
293
 
294
+ // web-src/core/keyboard.ts
295
+ function isImeComposing(event) {
296
+ return event.isComposing === true || event.keyCode === 229;
297
+ }
298
+
294
299
  // web-src/core/keymap.ts
295
300
  var DEFAULT_KEY_BINDINGS = [
296
301
  {
@@ -429,6 +434,108 @@
429
434
  return null;
430
435
  }
431
436
 
437
+ // web-src/core/network-activity.ts
438
+ function abortReason(message) {
439
+ return typeof DOMException === "function" ? new DOMException(message, "AbortError") : new Error(message);
440
+ }
441
+ function signalReason(signal) {
442
+ return "reason" in signal ? signal.reason : abortReason("aborted");
443
+ }
444
+ function createNetworkActivityTracker(options = {}) {
445
+ let inFlight = 0;
446
+ let nextRequestId = 0;
447
+ const cancellableRequests = new Map;
448
+ const state = () => ({
449
+ inFlight,
450
+ cancellable: cancellableRequests.size
451
+ });
452
+ const notify = () => options.onChange?.(state());
453
+ function begin() {
454
+ inFlight++;
455
+ notify();
456
+ let ended = false;
457
+ return () => {
458
+ if (ended)
459
+ return;
460
+ ended = true;
461
+ inFlight = Math.max(0, inFlight - 1);
462
+ notify();
463
+ };
464
+ }
465
+ function track(promise) {
466
+ const end = begin();
467
+ return Promise.resolve(promise).finally(end);
468
+ }
469
+ function linkSignal(source, target, cleanup) {
470
+ if (!source)
471
+ return;
472
+ if (source.aborted) {
473
+ target.abort(signalReason(source));
474
+ return;
475
+ }
476
+ const onAbort = () => target.abort(signalReason(source));
477
+ source.addEventListener("abort", onAbort, { once: true });
478
+ cleanup.push(() => source.removeEventListener("abort", onAbort));
479
+ }
480
+ function requestSignalFromInput(input) {
481
+ return typeof Request !== "undefined" && input instanceof Request ? input.signal : null;
482
+ }
483
+ function makeTrackedFetch(originalFetch) {
484
+ return (input, init) => {
485
+ const end = begin();
486
+ const requestId = ++nextRequestId;
487
+ const requestController = new AbortController;
488
+ const cleanup = [];
489
+ cancellableRequests.set(requestId, requestController);
490
+ linkSignal(requestSignalFromInput(input), requestController, cleanup);
491
+ linkSignal(init?.signal, requestController, cleanup);
492
+ notify();
493
+ const finish = () => {
494
+ cancellableRequests.delete(requestId);
495
+ for (const fn of cleanup)
496
+ fn();
497
+ end();
498
+ };
499
+ try {
500
+ const trackedInit = {
501
+ ...init ?? {},
502
+ signal: requestController.signal
503
+ };
504
+ return Promise.resolve(originalFetch(input, trackedInit)).finally(finish);
505
+ } catch (err) {
506
+ finish();
507
+ throw err;
508
+ }
509
+ };
510
+ }
511
+ function installFetch(target = globalThis) {
512
+ const originalFetch = target.fetch;
513
+ const trackedFetch = makeTrackedFetch(originalFetch.bind(target));
514
+ target.fetch = trackedFetch;
515
+ return () => {
516
+ if (target.fetch === trackedFetch)
517
+ target.fetch = originalFetch;
518
+ };
519
+ }
520
+ function cancelAll(message = "cancelled by user") {
521
+ const reason = abortReason(message);
522
+ let count = 0;
523
+ for (const controller of cancellableRequests.values()) {
524
+ if (controller.signal.aborted)
525
+ continue;
526
+ controller.abort(reason);
527
+ count++;
528
+ }
529
+ return count;
530
+ }
531
+ return {
532
+ getState: state,
533
+ track,
534
+ installFetch,
535
+ cancelAll
536
+ };
537
+ }
538
+
432
539
  // web-src/core/routes.ts
433
540
  function assertNever(value) {
434
541
  throw new Error(`unhandled route: ${JSON.stringify(value)}`);
@@ -504,7 +611,8 @@
504
611
  ref,
505
612
  range,
506
613
  view: target ? "blob" : "detail",
507
- ...line ? { line } : {}
614
+ ...line ? { line } : {},
615
+ ...params.get("virtual") === "off" ? { virtual: "off" } : {}
508
616
  };
509
617
  }
510
618
  case "/help":
@@ -559,9 +667,9 @@
559
667
  }
560
668
  case "file":
561
669
  if (route.view === "blob") {
562
- return "/file?path=" + encodeURIComponent(route.path) + "&target=" + encodeURIComponent(route.ref || "worktree") + (route.line ? `&line=${encodeURIComponent(formatLineTarget(route.line))}` : "");
670
+ return "/file?path=" + encodeURIComponent(route.path) + "&target=" + encodeURIComponent(route.ref || "worktree") + (route.line ? `&line=${encodeURIComponent(formatLineTarget(route.line))}` : "") + (route.virtual === "off" ? "&virtual=off" : "");
563
671
  }
564
- return "/file?path=" + encodeURIComponent(route.path) + "&ref=" + encodeURIComponent(route.ref || "worktree") + "&from=" + encodeURIComponent(route.range.from || "") + "&to=" + encodeURIComponent(route.range.to || "worktree") + (route.line ? `&line=${encodeURIComponent(formatLineTarget(route.line))}` : "");
672
+ return "/file?path=" + encodeURIComponent(route.path) + "&ref=" + encodeURIComponent(route.ref || "worktree") + "&from=" + encodeURIComponent(route.range.from || "") + "&to=" + encodeURIComponent(route.range.to || "worktree") + (route.line ? `&line=${encodeURIComponent(formatLineTarget(route.line))}` : "") + (route.virtual === "off" ? "&virtual=off" : "");
565
673
  case "diff":
566
674
  return "/todif?from=" + encodeURIComponent(route.range.from || "") + "&to=" + encodeURIComponent(route.range.to || "worktree") + (route.path ? `&path=${encodeURIComponent(route.path)}` : "") + (route.line ? `&line=${encodeURIComponent(formatLineTarget(route.line))}` : "");
567
675
  case "help": {
@@ -7086,6 +7194,8 @@ ${frontmatter.yaml}
7086
7194
  overlay.classList.remove("dragging");
7087
7195
  };
7088
7196
  const onKey = (e2) => {
7197
+ if (isImeComposing(e2))
7198
+ return;
7089
7199
  if (e2.key === "Escape")
7090
7200
  close();
7091
7201
  else if (e2.key === "0")
@@ -8896,6 +9006,8 @@ ${frontmatter.yaml}
8896
9006
  }
8897
9007
  searchBtn.addEventListener("click", runSearch);
8898
9008
  searchInput.addEventListener("keydown", (e2) => {
9009
+ if (isImeComposing(e2))
9010
+ return;
8899
9011
  if (e2.key === "Enter") {
8900
9012
  e2.preventDefault();
8901
9013
  runSearch();
@@ -9414,6 +9526,8 @@ ${frontmatter.yaml}
9414
9526
  }
9415
9527
  searchBtn.addEventListener("click", startSearch);
9416
9528
  input.addEventListener("keydown", (e2) => {
9529
+ if (isImeComposing(e2))
9530
+ return;
9417
9531
  if (e2.key === "Enter")
9418
9532
  startSearch();
9419
9533
  });
@@ -9741,6 +9855,8 @@ ${frontmatter.yaml}
9741
9855
  };
9742
9856
  document.addEventListener("click", onDocumentClick);
9743
9857
  textarea.addEventListener("keydown", (e2) => {
9858
+ if (isImeComposing(e2))
9859
+ return;
9744
9860
  if ((e2.ctrlKey || e2.metaKey) && e2.key === "Enter") {
9745
9861
  e2.preventDefault();
9746
9862
  run();
@@ -11287,6 +11403,8 @@ ${frontmatter.yaml}
11287
11403
  refresh();
11288
11404
  });
11289
11405
  input.addEventListener("keydown", (e2) => {
11406
+ if (isImeComposing(e2))
11407
+ return;
11290
11408
  if (e2.key === "Enter")
11291
11409
  saveBtn.click();
11292
11410
  if (e2.key === "Escape")
@@ -11454,8 +11572,7 @@ ${frontmatter.yaml}
11454
11572
  measure.style.cssText = "position:absolute;visibility:hidden;white-space:nowrap;font:inherit;padding:0 8px;";
11455
11573
  document.body.appendChild(measure);
11456
11574
  const headerLabel = columns[colIndex]?.name || colName;
11457
- const typeLabel = columns[colIndex]?.type || "";
11458
- measure.textContent = `${headerLabel} ${typeLabel} ▲`;
11575
+ measure.textContent = `${headerLabel} ▲`;
11459
11576
  let maxW = measure.offsetWidth + 16;
11460
11577
  const rows = body.querySelectorAll(".db-grid-row");
11461
11578
  for (const row of rows) {
@@ -11605,11 +11722,6 @@ ${frontmatter.yaml}
11605
11722
  const label = document.createElement("span");
11606
11723
  label.className = "db-grid-header-label";
11607
11724
  label.textContent = col.name;
11608
- const typeTag = document.createElement("span");
11609
- typeTag.className = "db-grid-header-type";
11610
- typeTag.textContent = col.type;
11611
- if (col.primaryKey)
11612
- typeTag.classList.add("pk");
11613
11725
  const sortIcon = document.createElement("span");
11614
11726
  sortIcon.className = "db-grid-sort-icon";
11615
11727
  sortIcon.textContent = sort?.column === col.name ? sort.direction === "asc" ? "▲" : "▼" : "";
@@ -11621,7 +11733,7 @@ ${frontmatter.yaml}
11621
11733
  e2.stopPropagation();
11622
11734
  startResize(colIndex, e2);
11623
11735
  });
11624
- cell.append(label, typeTag, sortIcon, resizeHandle);
11736
+ cell.append(label, sortIcon, resizeHandle);
11625
11737
  cell.addEventListener("click", (e2) => {
11626
11738
  if (e2.target.classList.contains("db-grid-resize-handle"))
11627
11739
  return;
@@ -11695,6 +11807,8 @@ ${frontmatter.yaml}
11695
11807
  scheduleFilter();
11696
11808
  });
11697
11809
  input.addEventListener("keydown", (e2) => {
11810
+ if (isImeComposing(e2))
11811
+ return;
11698
11812
  if (e2.key === "Escape") {
11699
11813
  input.value = "";
11700
11814
  columnFilters.delete(col.name);
@@ -11962,6 +12076,8 @@ ${frontmatter.yaml}
11962
12076
  scheduleFilter();
11963
12077
  });
11964
12078
  filterInput.addEventListener("keydown", (e2) => {
12079
+ if (isImeComposing(e2))
12080
+ return;
11965
12081
  if (e2.key === "Escape") {
11966
12082
  filterInput.value = "";
11967
12083
  globalSearchValue = "";
@@ -12114,6 +12230,8 @@ ${frontmatter.yaml}
12114
12230
  }
12115
12231
  };
12116
12232
  const onKeyDown = (ev) => {
12233
+ if (isImeComposing(ev))
12234
+ return;
12117
12235
  if (ev.key === "Escape") {
12118
12236
  closeContextMenu();
12119
12237
  }
@@ -12221,7 +12339,6 @@ ${frontmatter.yaml}
12221
12339
  toggleExpand(table2.name, node, arrow, children);
12222
12340
  });
12223
12341
  row.addEventListener("click", () => callbacks.onSelectTable(table2.name));
12224
- row.addEventListener("dblclick", () => callbacks.onSelectSchema(table2.name));
12225
12342
  row.addEventListener("contextmenu", (e2) => showContextMenu(e2, table2.name));
12226
12343
  node.append(row, children);
12227
12344
  el.appendChild(node);
@@ -12307,11 +12424,23 @@ ${frontmatter.yaml}
12307
12424
  return "data";
12308
12425
  return view && isSqlView(view) ? view : "data";
12309
12426
  }
12427
+ function labelFromDbId(dbId) {
12428
+ if (!dbId)
12429
+ return "(empty)";
12430
+ if (dbId.startsWith("docker:")) {
12431
+ const rest = dbId.slice("docker:".length);
12432
+ const service = rest.split(/[@:]/, 1)[0];
12433
+ return service || "Docker";
12434
+ }
12435
+ const normalized = dbId.replace(/\\/g, "/");
12436
+ const lastSlash = normalized.lastIndexOf("/");
12437
+ return lastSlash >= 0 ? normalized.slice(lastSlash + 1) : normalized;
12438
+ }
12310
12439
  function createTabPane(outerDeps, cb, initial = {}) {
12311
12440
  const deps = {
12312
12441
  ...outerDeps,
12313
12442
  setRoute: (route, replace2) => {
12314
- if (cb.isActive())
12443
+ if (cb.isActive() && cb.canSyncRoute())
12315
12444
  outerDeps.setRoute(route, replace2);
12316
12445
  cb.onStateChange();
12317
12446
  }
@@ -12335,7 +12464,6 @@ ${frontmatter.yaml}
12335
12464
  let currentTab = "data";
12336
12465
  const tableList = createTableList({
12337
12466
  onSelectTable: (table2) => selectTable(table2),
12338
- onSelectSchema: (table2) => showSchema(table2),
12339
12467
  onViewCreateTable: (table2) => showDdl(table2),
12340
12468
  onViewDefinition: (table2) => showSchema(table2),
12341
12469
  getColumns: (table2) => fetchColumns(table2)
@@ -12641,7 +12769,7 @@ ${frontmatter.yaml}
12641
12769
  historyView.refresh();
12642
12770
  return result;
12643
12771
  }
12644
- async function selectDb(dbId, explorerInitial, generation = loadGeneration) {
12772
+ async function selectDb(dbId, explorerInitial, generation = loadGeneration, preferredTable) {
12645
12773
  if (generation !== loadGeneration || currentDbInfo?.id !== dbId)
12646
12774
  return;
12647
12775
  currentTable = null;
@@ -12695,8 +12823,9 @@ ${frontmatter.yaml}
12695
12823
  schemaView.clear();
12696
12824
  erDiagram.clear();
12697
12825
  setActiveTab("data", false);
12698
- if (schema.tables.length > 0) {
12699
- await selectTable(schema.tables[0].name, generation);
12826
+ const initialTable = preferredTable || schema.tables[0]?.name;
12827
+ if (initialTable) {
12828
+ await selectTable(initialTable, generation);
12700
12829
  }
12701
12830
  applyVisibility();
12702
12831
  cb.onStateChange();
@@ -12856,10 +12985,10 @@ ${frontmatter.yaml}
12856
12985
  opt.textContent = label;
12857
12986
  dbSelect.appendChild(opt);
12858
12987
  }
12988
+ const autoSelectFirst = options.autoSelectFirst ?? true;
12859
12989
  if (db && !files.find((f2) => f2.id === db)) {
12860
- db = files[0].id;
12990
+ db = autoSelectFirst ? files[0].id : null;
12861
12991
  }
12862
- const autoSelectFirst = options.autoSelectFirst ?? true;
12863
12992
  if (!db && !autoSelectFirst) {
12864
12993
  dbSelect.value = "";
12865
12994
  currentDbInfo = null;
@@ -12884,7 +13013,7 @@ ${frontmatter.yaml}
12884
13013
  };
12885
13014
  pendingRedisInitial = undefined;
12886
13015
  pendingEsInitial = undefined;
12887
- await selectDb(target, explorerInitial, generation);
13016
+ await selectDb(target, explorerInitial, generation, table2);
12888
13017
  if (generation !== loadGeneration || currentDbInfo?.id !== target)
12889
13018
  return;
12890
13019
  if (currentDbInfo?.kind === "redis" || currentDbInfo?.kind === "elasticsearch") {
@@ -12895,9 +13024,6 @@ ${frontmatter.yaml}
12895
13024
  cb.onStateChange();
12896
13025
  return;
12897
13026
  }
12898
- if (table2) {
12899
- await selectTable(table2, generation);
12900
- }
12901
13027
  if (generation !== loadGeneration)
12902
13028
  return;
12903
13029
  const normalizedView = normalizeViewForDb(view, currentDbInfo);
@@ -13002,7 +13128,7 @@ ${frontmatter.yaml}
13002
13128
  }
13003
13129
  function getLabel() {
13004
13130
  if (!currentDbInfo)
13005
- return "(empty)";
13131
+ return labelFromDbId(initial.dbId);
13006
13132
  if (currentDbInfo.id.startsWith("docker:")) {
13007
13133
  const m = currentDbInfo.name.match(/^(\S+)/);
13008
13134
  return m ? m[1] : currentDbInfo.name;
@@ -13093,11 +13219,6 @@ ${frontmatter.yaml}
13093
13219
  return;
13094
13220
  const body = { version: 1, tabs, activeTabId };
13095
13221
  const raw = JSON.stringify(body);
13096
- if (options.keepalive && navigator.sendBeacon) {
13097
- const blob = new Blob([raw], { type: "application/json" });
13098
- if (navigator.sendBeacon("/_db/tabs", blob))
13099
- return;
13100
- }
13101
13222
  saveChain = saveChain.catch(() => {}).then(async () => {
13102
13223
  try {
13103
13224
  await fetch("/_db/tabs", {
@@ -13380,13 +13501,16 @@ ${frontmatter.yaml}
13380
13501
  chip.tabIndex = -1;
13381
13502
  const labelEl = document.createElement("span");
13382
13503
  labelEl.className = "db-tabs-chip-label";
13383
- labelEl.textContent = "(empty)";
13504
+ const initialLabel = labelFromDbId(initial?.dbId);
13505
+ labelEl.textContent = initialLabel;
13506
+ labelEl.title = initialLabel;
13384
13507
  const closeBtn = document.createElement("button");
13385
13508
  closeBtn.type = "button";
13386
13509
  closeBtn.className = "db-tabs-chip-close";
13387
13510
  closeBtn.title = "閉じる";
13388
13511
  closeBtn.tabIndex = -1;
13389
13512
  closeBtn.textContent = "×";
13513
+ closeBtn.setAttribute("aria-label", `${initialLabel} を閉じる`);
13390
13514
  closeBtn.addEventListener("click", (e2) => {
13391
13515
  e2.stopPropagation();
13392
13516
  closeTab(id);
@@ -13395,6 +13519,8 @@ ${frontmatter.yaml}
13395
13519
  attachTabDragHandlers(chip, closeBtn, id);
13396
13520
  chip.addEventListener("click", () => setActive(id));
13397
13521
  chip.addEventListener("keydown", (e2) => {
13522
+ if (isImeComposing(e2))
13523
+ return;
13398
13524
  if (e2.key === "Enter" || e2.key === " ") {
13399
13525
  e2.preventDefault();
13400
13526
  setActive(id);
@@ -13409,6 +13535,7 @@ ${frontmatter.yaml}
13409
13535
  const pane = createTabPane(deps, {
13410
13536
  tabId: id,
13411
13537
  isActive: () => activeTabId === id,
13538
+ canSyncRoute: () => !restoring,
13412
13539
  onStateChange: () => {
13413
13540
  refreshChipLabel(id);
13414
13541
  if (activeTabId === id && !restoring)
@@ -13431,6 +13558,16 @@ ${frontmatter.yaml}
13431
13558
  tabHost.appendChild(pane.el);
13432
13559
  tabsById.set(id, { pane, chip, label: labelEl, closeBtn });
13433
13560
  setActive(id);
13561
+ if (!options.deferInitialEnter) {
13562
+ startInitialEnter(id, initial, options);
13563
+ }
13564
+ return id;
13565
+ }
13566
+ function startInitialEnter(id, initial, options = {}) {
13567
+ const entry = tabsById.get(id);
13568
+ if (!entry)
13569
+ return Promise.resolve();
13570
+ const pane = entry.pane;
13434
13571
  const ready = (async () => {
13435
13572
  if (options.annotationTarget)
13436
13573
  restoring = true;
@@ -13444,7 +13581,7 @@ ${frontmatter.yaml}
13444
13581
  }
13445
13582
  })();
13446
13583
  paneReadyById.set(id, ready);
13447
- return id;
13584
+ return ready;
13448
13585
  }
13449
13586
  function closeTab(id) {
13450
13587
  const entry = tabsById.get(id);
@@ -13496,17 +13633,6 @@ ${frontmatter.yaml}
13496
13633
  restoring = false;
13497
13634
  }
13498
13635
  }
13499
- if (options.reuseActiveTab && activeTabId) {
13500
- const targetId = activeTabId;
13501
- const active = tabsById.get(activeTabId);
13502
- if (active) {
13503
- await enterPane(active.pane, db, table2, view, options);
13504
- if (!mounted)
13505
- return;
13506
- refreshChipLabel(targetId);
13507
- return;
13508
- }
13509
- }
13510
13636
  for (const [id2, entry] of tabsById) {
13511
13637
  if (routeMatchesState(entry.pane.getState(), db, table2, view)) {
13512
13638
  setActive(id2);
@@ -13519,11 +13645,22 @@ ${frontmatter.yaml}
13519
13645
  return;
13520
13646
  }
13521
13647
  }
13648
+ if (options.reuseActiveTab && activeTabId) {
13649
+ const targetId = activeTabId;
13650
+ const active = tabsById.get(activeTabId);
13651
+ if (active) {
13652
+ await enterPane(active.pane, db, table2, view, options);
13653
+ if (!mounted)
13654
+ return;
13655
+ refreshChipLabel(targetId);
13656
+ return;
13657
+ }
13658
+ }
13522
13659
  const id = openTab({
13523
13660
  dbId: db ?? null,
13524
13661
  table: table2 ?? null,
13525
13662
  view: view ?? "data"
13526
- }, { autoSelectFirst: db !== undefined, ...options });
13663
+ }, { autoSelectFirst: db === undefined, ...options });
13527
13664
  const ready = paneReadyById.get(id);
13528
13665
  if (ready)
13529
13666
  await ready;
@@ -13553,6 +13690,7 @@ ${frontmatter.yaml}
13553
13690
  const empty = document.getElementById("empty");
13554
13691
  if (empty)
13555
13692
  empty.classList.add("hidden");
13693
+ root.hidden = false;
13556
13694
  content.appendChild(root);
13557
13695
  document.body.classList.add("gdp-database-page");
13558
13696
  mounted = true;
@@ -13562,30 +13700,50 @@ ${frontmatter.yaml}
13562
13700
  }
13563
13701
  deps.setPageMode();
13564
13702
  deps.syncHeaderMenu();
13565
- const restored = await fetchTabs();
13703
+ const restored = tabsById.size === 0 ? await fetchTabs() : null;
13566
13704
  if (!mounted || seq !== lifecycleSeq)
13567
13705
  return;
13568
13706
  if (restored && restored.tabs.length > 0) {
13569
13707
  restoring = true;
13570
13708
  const restoredIds = [];
13709
+ const restoredById = new Map;
13571
13710
  try {
13572
13711
  const restoredTabs = dedupeTabs(restored.tabs);
13573
13712
  for (const t2 of restoredTabs) {
13574
13713
  if (!mounted || seq !== lifecycleSeq)
13575
13714
  return;
13576
- restoredIds.push(openTab(t2, { autoSelectFirst: false }));
13715
+ const id = openTab(t2, {
13716
+ autoSelectFirst: false,
13717
+ deferInitialEnter: true
13718
+ });
13719
+ restoredIds.push(id);
13720
+ restoredById.set(id, t2);
13577
13721
  }
13578
13722
  const targetId = restored.activeTabId && tabsById.has(restored.activeTabId) ? restored.activeTabId : tabsById.keys().next().value;
13579
13723
  if (targetId)
13580
13724
  setActive(targetId);
13581
- await Promise.all(restoredIds.map((id) => paneReadyById.get(id)).filter(Boolean));
13725
+ if (targetId) {
13726
+ await startInitialEnter(targetId, restoredById.get(targetId), {
13727
+ autoSelectFirst: false
13728
+ });
13729
+ }
13582
13730
  if (!mounted || seq !== lifecycleSeq)
13583
13731
  return;
13732
+ for (const id of restoredIds) {
13733
+ if (id === targetId)
13734
+ continue;
13735
+ startInitialEnter(id, restoredById.get(id), {
13736
+ autoSelectFirst: false
13737
+ }).catch(() => {});
13738
+ }
13584
13739
  } finally {
13585
13740
  restoring = false;
13586
13741
  }
13587
13742
  if (db || table2 || view) {
13588
- await applyRouteToTab(db, table2, view, options);
13743
+ await applyRouteToTab(db, table2, view, {
13744
+ ...options,
13745
+ reuseActiveTab: options.reuseActiveTab ?? true
13746
+ });
13589
13747
  } else {
13590
13748
  syncActiveRoute();
13591
13749
  }
@@ -13623,7 +13781,12 @@ ${frontmatter.yaml}
13623
13781
  if (!active)
13624
13782
  return;
13625
13783
  if (db || table2 || view) {
13626
- await applyRouteToTab(db, table2, view, options);
13784
+ await applyRouteToTab(db, table2, view, {
13785
+ ...options,
13786
+ reuseActiveTab: options.reuseActiveTab ?? true
13787
+ });
13788
+ } else {
13789
+ syncActiveRoute();
13627
13790
  }
13628
13791
  }
13629
13792
  async function enter(db, table2, view, options = {}) {
@@ -13655,14 +13818,24 @@ ${frontmatter.yaml}
13655
13818
  mounted = false;
13656
13819
  }
13657
13820
  function suspend() {
13658
- if (!mounted)
13821
+ lifecycleSeq++;
13822
+ if (!mounted) {
13823
+ root.remove();
13824
+ root.hidden = false;
13825
+ document.body.classList.remove("gdp-database-page");
13826
+ const diff2 = document.getElementById("diff");
13827
+ if (diff2)
13828
+ diff2.hidden = false;
13659
13829
  return;
13830
+ }
13660
13831
  flushPendingSave();
13661
- root.hidden = true;
13832
+ root.remove();
13833
+ root.hidden = false;
13662
13834
  document.body.classList.remove("gdp-database-page");
13663
13835
  const diff = document.getElementById("diff");
13664
13836
  if (diff)
13665
13837
  diff.hidden = false;
13838
+ mounted = false;
13666
13839
  }
13667
13840
  function handleSse(event, data) {
13668
13841
  if (!mounted)
@@ -13793,6 +13966,8 @@ ${frontmatter.yaml}
13793
13966
  drag = null;
13794
13967
  });
13795
13968
  document.addEventListener("keydown", (e2) => {
13969
+ if (isImeComposing(e2))
13970
+ return;
13796
13971
  if (e2.key === "Escape" && selection && !drag)
13797
13972
  clear();
13798
13973
  });
@@ -16039,6 +16214,8 @@ code-viewer annotate add-db --db app.db --tab query \\
16039
16214
  };
16040
16215
  button.addEventListener("click", toggle);
16041
16216
  button.addEventListener("keydown", (event) => {
16217
+ if (isImeComposing(event))
16218
+ return;
16042
16219
  if (event.key !== "Enter" && event.key !== " ")
16043
16220
  return;
16044
16221
  event.preventDefault();
@@ -16424,6 +16601,8 @@ code-viewer annotate add-db --db app.db --tab query \\
16424
16601
  }, 250);
16425
16602
  });
16426
16603
  filterInput?.addEventListener("keydown", (e2) => {
16604
+ if (isImeComposing(e2))
16605
+ return;
16427
16606
  if (e2.key === "Escape" && filterInput.value) {
16428
16607
  filterInput.value = "";
16429
16608
  applyFilter("");
@@ -16914,6 +17093,8 @@ code-viewer annotate add-db --db app.db --tab query \\
16914
17093
  openPopover(input);
16915
17094
  });
16916
17095
  input.addEventListener("keydown", (e2) => {
17096
+ if (isImeComposing(e2))
17097
+ return;
16917
17098
  if (e2.key === "Enter" || e2.key === " ") {
16918
17099
  e2.preventDefault();
16919
17100
  openPopover(input);
@@ -17179,6 +17360,8 @@ code-viewer annotate add-db --db app.db --tab query \\
17179
17360
  });
17180
17361
  popBody.addEventListener("scroll", maybeLoadMoreCommits, { passive: true });
17181
17362
  popSearch.addEventListener("keydown", (e2) => {
17363
+ if (isImeComposing(e2))
17364
+ return;
17182
17365
  if (e2.key === "Escape") {
17183
17366
  closePopover();
17184
17367
  }
@@ -17374,6 +17557,8 @@ code-viewer annotate add-db --db app.db --tab query \\
17374
17557
  resolve(ok);
17375
17558
  };
17376
17559
  const onKeydown = (event) => {
17560
+ if (isImeComposing(event))
17561
+ return;
17377
17562
  if (event.key === "Escape") {
17378
17563
  event.preventDefault();
17379
17564
  event.stopPropagation();
@@ -17450,14 +17635,14 @@ code-viewer annotate add-db --db app.db --tab query \\
17450
17635
  done(name);
17451
17636
  };
17452
17637
  const onKeydown = (event) => {
17638
+ if (isImeComposing(event))
17639
+ return;
17453
17640
  if (event.key === "Escape") {
17454
17641
  event.preventDefault();
17455
17642
  event.stopPropagation();
17456
17643
  done(null);
17457
17644
  return;
17458
17645
  }
17459
- if (event.isComposing || event.keyCode === 229)
17460
- return;
17461
17646
  if (event.key === "Enter") {
17462
17647
  event.preventDefault();
17463
17648
  submit();
@@ -18889,6 +19074,8 @@ code-viewer annotate add-db --db app.db --tab query \\
18889
19074
  }
18890
19075
  }
18891
19076
  function handlePaletteKeydown(e2, state) {
19077
+ if (isImeComposing(e2))
19078
+ return;
18892
19079
  if (e2.key === "Escape") {
18893
19080
  e2.preventDefault();
18894
19081
  closeSearchPalette();
@@ -20879,7 +21066,7 @@ code-viewer annotate add-db --db app.db --tab query \\
20879
21066
  return textValue.length >= VIRTUAL_SOURCE_SIZE_THRESHOLD || lines.length >= VIRTUAL_SOURCE_LINE_THRESHOLD;
20880
21067
  }
20881
21068
  function isVirtualSourceDisabled() {
20882
- return new URLSearchParams(window.location.search).get("virtual") === "off";
21069
+ return deps.STATE.route.screen === "file" && deps.STATE.route.virtual === "off";
20883
21070
  }
20884
21071
  function buildCurrentFileRouteWithVirtualMode(target, virtualMode) {
20885
21072
  const route = {
@@ -20887,14 +21074,10 @@ code-viewer annotate add-db --db app.db --tab query \\
20887
21074
  path: target.path,
20888
21075
  ref: target.ref,
20889
21076
  view: STATE.route.screen === "file" ? STATE.route.view : "blob",
20890
- range: currentRange()
21077
+ range: currentRange(),
21078
+ ...virtualMode === "off" ? { virtual: "off" } : {}
20891
21079
  };
20892
- const url = new URL(buildRoute(route), window.location.origin);
20893
- if (virtualMode === "off")
20894
- url.searchParams.set("virtual", "off");
20895
- else
20896
- url.searchParams.delete("virtual");
20897
- return url.pathname + url.search;
21080
+ return buildRoute(route);
20898
21081
  }
20899
21082
  function buildFileRangeUrl(target, start, end) {
20900
21083
  return "/file_range?path=" + encodeURIComponent(target.path) + "&ref=" + encodeURIComponent(target.ref || "worktree") + "&start=" + encodeURIComponent(String(start)) + "&end=" + encodeURIComponent(String(end));
@@ -21086,6 +21269,8 @@ code-viewer annotate add-db --db app.db --tab query \\
21086
21269
  scheduleSync();
21087
21270
  });
21088
21271
  input.addEventListener("keydown", (e2) => {
21272
+ if (isImeComposing(e2))
21273
+ return;
21089
21274
  if (e2.key === "Escape") {
21090
21275
  e2.preventDefault();
21091
21276
  hide();
@@ -21776,7 +21961,7 @@ code-viewer annotate add-db --db app.db --tab query \\
21776
21961
  function handleVirtualSourcePagingKey(e2, targetEl) {
21777
21962
  if (e2.__gdpVirtualSourcePagingHandled)
21778
21963
  return true;
21779
- if (e2.defaultPrevented || e2.isComposing || isPaletteOpen() || document.querySelector(".mkdp-lightbox"))
21964
+ if (e2.defaultPrevented || isImeComposing(e2) || isPaletteOpen() || document.querySelector(".mkdp-lightbox"))
21780
21965
  return false;
21781
21966
  const editable = isEditableKeyTarget(targetEl);
21782
21967
  const inVirtualSearch = !!targetEl?.closest(".gdp-source-virtual-search");
@@ -21862,6 +22047,26 @@ code-viewer annotate add-db --db app.db --tab query \\
21862
22047
  const SCOPE_EXCLUDE_NAMES_STORAGE_KEY_PREFIX = "gdp:scope-exclude-names:";
21863
22048
  const CODE_FONT_SIZE_STORAGE_KEY = "gdp:code-font-size";
21864
22049
  const VIEWER_LANGUAGE_STORAGE_KEY = "gdp:language";
22050
+ const NETWORK_ACTIVITY = createNetworkActivityTracker({
22051
+ onChange: updateNetworkActivity
22052
+ });
22053
+ NETWORK_ACTIVITY.installFetch(window);
22054
+ function updateNetworkActivity(state = NETWORK_ACTIVITY.getState()) {
22055
+ const loadBar = document.querySelector("#load-bar");
22056
+ if (loadBar)
22057
+ loadBar.classList.toggle("active", state.inFlight > 0);
22058
+ const cancelButton = document.querySelector("#cancel-requests");
22059
+ if (!cancelButton)
22060
+ return;
22061
+ const cancellable = state.cancellable > 0;
22062
+ cancelButton.disabled = !cancellable;
22063
+ cancelButton.classList.toggle("active", cancellable);
22064
+ cancelButton.title = cancellable ? `cancel ${state.cancellable} in-flight request${state.cancellable === 1 ? "" : "s"}` : "no in-flight requests";
22065
+ }
22066
+ function cancelInFlightRequests() {
22067
+ NETWORK_ACTIVITY.cancelAll();
22068
+ updateNetworkActivity();
22069
+ }
21865
22070
  function scopedKey(base2) {
21866
22071
  return PROJECT_NAME ? `${base2}:${PROJECT_NAME}` : base2;
21867
22072
  }
@@ -22884,26 +23089,8 @@ code-viewer annotate add-db --db app.db --tab query \\
22884
23089
  });
22885
23090
  }
22886
23091
  let SERVER_GENERATION = 0;
22887
- let IN_FLIGHT = 0;
22888
- function updateLoadBar() {
22889
- const el = $("#load-bar");
22890
- if (el)
22891
- el.classList.toggle("active", IN_FLIGHT > 0);
22892
- }
22893
23092
  function trackLoad(promise) {
22894
- IN_FLIGHT++;
22895
- updateLoadBar();
22896
- const done = () => {
22897
- IN_FLIGHT = Math.max(0, IN_FLIGHT - 1);
22898
- updateLoadBar();
22899
- };
22900
- return Promise.resolve(promise).then((v) => {
22901
- done();
22902
- return v;
22903
- }, (e2) => {
22904
- done();
22905
- throw e2;
22906
- });
23093
+ return NETWORK_ACTIVITY.track(promise);
22907
23094
  }
22908
23095
  function escapeHtml3(s2) {
22909
23096
  return String(s2 == null ? "" : s2).replace(/[&<>"']/g, (c2) => ({
@@ -22949,6 +23136,21 @@ code-viewer annotate add-db --db app.db --tab query \\
22949
23136
  function withAnnotationSessionParam(rawUrl) {
22950
23137
  return ANNOTATIONS_UI ? ANNOTATIONS_UI.withSessionParam(rawUrl) : rawUrl;
22951
23138
  }
23139
+ function historyStateForRoute(route) {
23140
+ return route.screen === "file" ? {
23141
+ screen: "file",
23142
+ path: route.path,
23143
+ ref: route.ref,
23144
+ view: route.view || "detail"
23145
+ } : { view: route.screen };
23146
+ }
23147
+ function replaceUrlWithCurrentRoute() {
23148
+ const url = withAnnotationSessionParam(buildRoute(STATE.route));
23149
+ const current = window.location.pathname + window.location.search;
23150
+ if (url !== current) {
23151
+ history.replaceState(historyStateForRoute(STATE.route), "", url + window.location.hash);
23152
+ }
23153
+ }
22952
23154
  function setRoute(route, replace2 = false) {
22953
23155
  const nextRoute = route.screen === "unknown" ? { screen: "diff", range: route.range } : route;
22954
23156
  STATE.route = nextRoute;
@@ -22958,12 +23160,7 @@ code-viewer annotate add-db --db app.db --tab query \\
22958
23160
  STATE.repoRef = nextRoute.ref || "worktree";
22959
23161
  }
22960
23162
  const url = withAnnotationSessionParam(buildRoute(nextRoute));
22961
- const state = nextRoute.screen === "file" ? {
22962
- screen: "file",
22963
- path: nextRoute.path,
22964
- ref: nextRoute.ref,
22965
- view: nextRoute.view || "detail"
22966
- } : { view: nextRoute.screen };
23163
+ const state = historyStateForRoute(nextRoute);
22967
23164
  if (replace2)
22968
23165
  history.replaceState(state, "", url);
22969
23166
  else
@@ -23230,6 +23427,8 @@ code-viewer annotate add-db --db app.db --tab query \\
23230
23427
  source.textContent = uiText().settings.scopeSource(PROJECT_NAME || "default", scopeOmitSourceLabel());
23231
23428
  });
23232
23429
  $("#scope-settings-popover")?.addEventListener("keydown", (e2) => {
23430
+ if (isImeComposing(e2))
23431
+ return;
23233
23432
  if (e2.key === "Escape")
23234
23433
  closeScopeSettings();
23235
23434
  });
@@ -23371,6 +23570,8 @@ code-viewer annotate add-db --db app.db --tab query \\
23371
23570
  if (sbFilter) {
23372
23571
  sbFilter.addEventListener("input", () => scheduleApplyFilter());
23373
23572
  sbFilter.addEventListener("keydown", (e2) => {
23573
+ if (isImeComposing(e2))
23574
+ return;
23374
23575
  if (e2.key === "Enter") {
23375
23576
  e2.preventDefault();
23376
23577
  flushSidebarFilter();
@@ -23537,6 +23738,8 @@ code-viewer annotate add-db --db app.db --tab query \\
23537
23738
  document.addEventListener("click", closeRepoContextMenu);
23538
23739
  $("#filelist").addEventListener("contextmenu", handleSidebarContextMenu);
23539
23740
  document.addEventListener("keydown", async (e2) => {
23741
+ if (isImeComposing(e2))
23742
+ return;
23540
23743
  if (e2.key === "Escape")
23541
23744
  closeRepoContextMenu();
23542
23745
  if (e2.__gdpVirtualSourcePagingHandled)
@@ -23557,7 +23760,7 @@ code-viewer annotate add-db --db app.db --tab query \\
23557
23760
  const action = resolveKeymapAction(e2, {
23558
23761
  scope,
23559
23762
  editable: isEditableKeyTarget(targetEl),
23560
- composing: e2.isComposing,
23763
+ composing: isImeComposing(e2),
23561
23764
  paletteOpen: isPaletteOpen(),
23562
23765
  pendingG: PENDING_G_SCOPE === scope && performance.now() <= PENDING_G_UNTIL,
23563
23766
  lightboxOpen: !!document.querySelector(".mkdp-lightbox")
@@ -23666,6 +23869,7 @@ code-viewer annotate add-db --db app.db --tab query \\
23666
23869
  }
23667
23870
  function setRange(from, to) {
23668
23871
  preHistoryRange = null;
23872
+ const wasDatabaseRoute = STATE.route.screen === "database";
23669
23873
  STATE.from = from || "";
23670
23874
  STATE.to = to || "";
23671
23875
  writeScopedStorage("gdp:from", STATE.from);
@@ -23684,6 +23888,8 @@ code-viewer annotate add-db --db app.db --tab query \\
23684
23888
  renderHelpPage();
23685
23889
  } else {
23686
23890
  setRoute({ screen: "diff", range }, true);
23891
+ if (wasDatabaseRoute)
23892
+ DATABASE_VIEW.suspend();
23687
23893
  setPageMode();
23688
23894
  load();
23689
23895
  }
@@ -23769,6 +23975,7 @@ code-viewer annotate add-db --db app.db --tab query \\
23769
23975
  if (STATE.route.screen === "repo")
23770
23976
  STATE.repoRef = STATE.route.ref || "worktree";
23771
23977
  ANNOTATIONS_UI?.restoreSessionFromUrl();
23978
+ replaceUrlWithCurrentRoute();
23772
23979
  syncRefInputs();
23773
23980
  syncHeaderMenu();
23774
23981
  syncLineRefPill();
@@ -23929,6 +24136,7 @@ code-viewer annotate add-db --db app.db --tab query \\
23929
24136
  writeScopedStorage("gdp:to", to);
23930
24137
  }
23931
24138
  });
24139
+ replaceUrlWithCurrentRoute();
23932
24140
  createAnnotationsPlayer({
23933
24141
  $,
23934
24142
  getActiveSessionEntries: () => ANNOTATIONS_UI?.getActiveSessionEntries() ?? [],
@@ -24078,6 +24286,7 @@ code-viewer annotate add-db --db app.db --tab query \\
24078
24286
  document.getElementById("auto-update")?.addEventListener("click", () => {
24079
24287
  setAutoUpdate(!STATE.autoUpdate);
24080
24288
  });
24289
+ document.getElementById("cancel-requests")?.addEventListener("click", cancelInFlightRequests);
24081
24290
  applyAutoUpdateButton();
24082
24291
  function shouldAutoLoadCurrentRoute(route = STATE.route) {
24083
24292
  return shouldAutoLoadForRoute(route, {