@youtyan/code-viewer 0.1.25 → 0.1.27

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
@@ -36,6 +36,23 @@
36
36
  };
37
37
  }
38
38
 
39
+ // web-src/directory-name.ts
40
+ function normalizeNewDirectoryName(name) {
41
+ if (typeof name !== "string")
42
+ return null;
43
+ const trimmed = name.trim();
44
+ if (!trimmed || trimmed.length > 180)
45
+ return null;
46
+ if (trimmed.includes("/") || trimmed.includes("\\") || trimmed.includes("\x00") || Array.from(trimmed).some((char) => {
47
+ const code = char.charCodeAt(0);
48
+ return code < 32 || code === 127;
49
+ }))
50
+ return null;
51
+ if (trimmed === "." || trimmed === ".." || trimmed.toLowerCase() === ".git")
52
+ return null;
53
+ return trimmed;
54
+ }
55
+
39
56
  // web-src/expand-logic.ts
40
57
  function initExpandState(prevHunkEndNew, hunkNewStart) {
41
58
  return {
@@ -145,6 +162,12 @@
145
162
  function filePathClipboardText(path) {
146
163
  return path || "";
147
164
  }
165
+ function fileNameClipboardText(path) {
166
+ if (!path)
167
+ return "";
168
+ const parts = path.split("/").filter(Boolean);
169
+ return parts[parts.length - 1] || "";
170
+ }
148
171
 
149
172
  // web-src/focus-scope.ts
150
173
  function isEditableKeyTarget(target) {
@@ -6578,8 +6601,8 @@ ${frontmatter.yaml}
6578
6601
  if (!section)
6579
6602
  return;
6580
6603
  e2.preventDefault();
6581
- section.scrollIntoView({ block: "start", behavior: "smooth" });
6582
6604
  history.replaceState(history.state, "", `#${encodeURIComponent(section.id)}`);
6605
+ scrollMarkdownSectionIntoView(section, "smooth");
6583
6606
  });
6584
6607
  const controller = new AbortController;
6585
6608
  const scrollRoot = document.scrollingElement || document.documentElement;
@@ -6596,8 +6619,9 @@ ${frontmatter.yaml}
6596
6619
  return;
6597
6620
  }
6598
6621
  let active = entries[0];
6622
+ const activeThreshold = markdownAnchorOffset() + 40;
6599
6623
  for (const entry of entries) {
6600
- if (entry.target.getBoundingClientRect().top <= 96)
6624
+ if (entry.target.getBoundingClientRect().top <= activeThreshold)
6601
6625
  active = entry;
6602
6626
  else
6603
6627
  break;
@@ -6622,9 +6646,43 @@ ${frontmatter.yaml}
6622
6646
  setTimeout(() => {
6623
6647
  if (!root.isConnected)
6624
6648
  return;
6649
+ scrollInitialMarkdownHash(root);
6625
6650
  update();
6626
6651
  }, 0);
6627
6652
  }
6653
+ function scrollInitialMarkdownHash(root) {
6654
+ if (!location.hash)
6655
+ return;
6656
+ const id = decodeHashFragment(location.hash);
6657
+ const section = root.querySelector(`#${CSS.escape(id)}`);
6658
+ if (!section)
6659
+ return;
6660
+ scrollMarkdownSectionIntoView(section, "auto");
6661
+ }
6662
+ function decodeHashFragment(hash) {
6663
+ const value = hash.startsWith("#") ? hash.slice(1) : hash;
6664
+ try {
6665
+ return decodeURIComponent(value);
6666
+ } catch {
6667
+ return value;
6668
+ }
6669
+ }
6670
+ function scrollMarkdownSectionIntoView(section, behavior) {
6671
+ const top = section.getBoundingClientRect().top + window.scrollY - markdownAnchorOffset() - 12;
6672
+ window.scrollTo({ top: Math.max(0, top), behavior });
6673
+ }
6674
+ function markdownAnchorOffset() {
6675
+ const bottoms = Array.from(document.querySelectorAll("#global-header, .gdp-file-detail-sticky")).map((element) => {
6676
+ const style = getComputedStyle(element);
6677
+ if (style.display === "none" || style.visibility === "hidden")
6678
+ return 0;
6679
+ const rect = element.getBoundingClientRect();
6680
+ if (rect.height <= 0)
6681
+ return 0;
6682
+ return rect.bottom > 0 ? rect.bottom : 0;
6683
+ }).filter((bottom) => Number.isFinite(bottom));
6684
+ return Math.max(0, ...bottoms);
6685
+ }
6628
6686
  function keepTocLinkVisible(toc, link2) {
6629
6687
  if (toc.scrollHeight <= toc.clientHeight)
6630
6688
  return;
@@ -6894,6 +6952,8 @@ ${frontmatter.yaml}
6894
6952
  ];
6895
6953
  const FILE_16_PATH = "M2 1.75C2 .784 2.784 0 3.75 0h5.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 12.25 16h-8.5A1.75 1.75 0 0 1 2 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25V6h-2.75A1.75 1.75 0 0 1 8 4.25V1.5Zm5.75.062V4.25c0 .138.112.25.25.25h2.688Z";
6896
6954
  const OPEN_EXTERNAL_16_PATH = "M3.75 2A1.75 1.75 0 0 0 2 3.75v8.5C2 13.216 2.784 14 3.75 14h8.5A1.75 1.75 0 0 0 14 12.25v-3.5a.75.75 0 0 0-1.5 0v3.5a.25.25 0 0 1-.25.25h-8.5a.25.25 0 0 1-.25-.25v-8.5a.25.25 0 0 1 .25-.25h3.5a.75.75 0 0 0 0-1.5h-3.5Zm6.5 0a.75.75 0 0 0 0 1.5h1.19L7.72 7.22a.749.749 0 1 0 1.06 1.06l3.72-3.72v1.19a.75.75 0 0 0 1.5 0v-3A.75.75 0 0 0 13.25 2h-3Z";
6955
+ const PLUS_16_PATH = "M7.75 2a.75.75 0 0 1 .75.75V7.5h4.75a.75.75 0 0 1 0 1.5H8.5v4.75a.75.75 0 0 1-1.5 0V9H2.25a.75.75 0 0 1 0-1.5H7V2.75A.75.75 0 0 1 7.75 2Z";
6956
+ const TRASH_16_PATH = "M6.5 1.75A1.75 1.75 0 0 1 8.25 0h1.5A1.75 1.75 0 0 1 11.5 1.75V2h3.75a.75.75 0 0 1 0 1.5h-.75v10.75A1.75 1.75 0 0 1 12.75 16h-9.5A1.75 1.75 0 0 1 1.5 14.25V3.5H.75a.75.75 0 0 1 0-1.5H4.5v-.25ZM6 2h4v-.25a.25.25 0 0 0-.25-.25h-1.5a.25.25 0 0 0-.25.25V2Zm-3 1.5v10.75c0 .138.112.25.25.25h9.5a.25.25 0 0 0 .25-.25V3.5Zm3 2.25a.75.75 0 0 1 .75.75v5.5a.75.75 0 0 1-1.5 0v-5.5A.75.75 0 0 1 6 5.75Zm4 0a.75.75 0 0 1 .75.75v5.5a.75.75 0 0 1-1.5 0v-5.5A.75.75 0 0 1 10 5.75Z";
6897
6957
  const GIT_BRANCH_16_PATH = "M9.5 3.25a2.25 2.25 0 1 1 3 2.122V6A2.5 2.5 0 0 1 10 8.5H6a1 1 0 0 0-1 1v1.128a2.251 2.251 0 1 1-1.5 0V5.372a2.25 2.25 0 1 1 1.5 0v1.836A2.493 2.493 0 0 1 6 7h4a1 1 0 0 0 1-1v-.628A2.25 2.25 0 0 1 9.5 3.25Zm-6 0a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Zm8.25-.75a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5ZM4.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Z";
6898
6958
  const TRIANGLE_DOWN_16_PATH = "m4.427 7.427 3.396 3.396a.25.25 0 0 0 .354 0l3.396-3.396A.25.25 0 0 0 11.396 7H4.604a.25.25 0 0 0-.177.427Z";
6899
6959
  const SIDEBAR_SHOW_16_PATHS = [
@@ -6931,6 +6991,7 @@ ${frontmatter.yaml}
6931
6991
  let sourceShikiLoadPromise = null;
6932
6992
  let highlightConfigured = false;
6933
6993
  let PROJECT_NAME = "";
6994
+ let creatingDirectory = false;
6934
6995
  let REPO_SIDEBAR_REF = null;
6935
6996
  let REPO_SIDEBAR_LOAD_REF = null;
6936
6997
  let REPO_SIDEBAR_LOAD = null;
@@ -6947,6 +7008,8 @@ ${frontmatter.yaml}
6947
7008
  direction: "asc"
6948
7009
  };
6949
7010
  let SERVER_SCOPE_OMIT_DIRS_DEFAULT = [];
7011
+ let SERVER_SCOPE_EXCLUDE_NAMES_DEFAULT = [];
7012
+ const UNDO_STACK = [];
6950
7013
  let PENDING_G_SCOPE = null;
6951
7014
  let PENDING_G_UNTIL = 0;
6952
7015
  let SOURCE_CURSOR = null;
@@ -6954,6 +7017,7 @@ ${frontmatter.yaml}
6954
7017
  const HELP_LANGUAGES = ["en", "ja"];
6955
7018
  const HELP_SECTIONS = ["keybindings"];
6956
7019
  const SCOPE_OMIT_DIRS_STORAGE_KEY_PREFIX = "gdp:scope-omit-dirs:";
7020
+ const SCOPE_EXCLUDE_NAMES_STORAGE_KEY_PREFIX = "gdp:scope-exclude-names:";
6957
7021
  const SIDEBAR_FONT_SIZE_STORAGE_KEY = "gdp:sidebar-font-size";
6958
7022
  const CODE_FONT_SIZE_STORAGE_KEY = "gdp:code-font-size";
6959
7023
  const CLIENT_SCOPE_OMIT_DIRS_DEFAULT = [
@@ -6984,6 +7048,7 @@ ${frontmatter.yaml}
6984
7048
  "bin",
6985
7049
  "obj"
6986
7050
  ];
7051
+ const CLIENT_SCOPE_EXCLUDE_NAMES_DEFAULT = [".DS_Store"];
6987
7052
  const HELP_CONTENT = {
6988
7053
  en: {
6989
7054
  languageLabel: "Language",
@@ -7255,14 +7320,28 @@ ${frontmatter.yaml}
7255
7320
  ...new Set(raw.map((item) => item.trim()).filter((item) => item && item.length <= 64 && !item.includes("/") && !item.includes("\\") && item !== "." && item !== ".." && item !== ".git"))
7256
7321
  ].slice(0, 100).sort((a2, b2) => a2.localeCompare(b2));
7257
7322
  }
7323
+ function normalizeScopeExcludeNames(value) {
7324
+ const raw = Array.isArray(value) ? value : value.split(/[\n,]+/);
7325
+ return [
7326
+ ...new Set(raw.map((item) => item.trim()).filter((item) => item && item.length <= 128 && !item.includes("/") && !item.includes("\\") && item !== "." && item !== ".." && item !== ".git"))
7327
+ ].slice(0, 200).sort((a2, b2) => a2.localeCompare(b2));
7328
+ }
7258
7329
  function scopeOmitDirsStorageKey() {
7259
7330
  return SCOPE_OMIT_DIRS_STORAGE_KEY_PREFIX + (PROJECT_NAME || "default");
7260
7331
  }
7332
+ function scopeExcludeNamesStorageKey() {
7333
+ return SCOPE_EXCLUDE_NAMES_STORAGE_KEY_PREFIX + (PROJECT_NAME || "default");
7334
+ }
7261
7335
  function setProjectName(project) {
7262
7336
  if (!project)
7263
7337
  return;
7264
7338
  PROJECT_NAME = project;
7265
7339
  document.title = `${project} - code viewer`;
7340
+ const projectTitle = document.querySelector("#project-title");
7341
+ if (projectTitle) {
7342
+ projectTitle.textContent = project;
7343
+ projectTitle.title = project;
7344
+ }
7266
7345
  }
7267
7346
  function savedScopeOmitDirs() {
7268
7347
  const raw = localStorage.getItem(scopeOmitDirsStorageKey());
@@ -7275,16 +7354,36 @@ ${frontmatter.yaml}
7275
7354
  return normalizeScopeOmitDirs(raw);
7276
7355
  }
7277
7356
  }
7357
+ function savedScopeExcludeNames() {
7358
+ const raw = localStorage.getItem(scopeExcludeNamesStorageKey());
7359
+ if (raw == null)
7360
+ return null;
7361
+ try {
7362
+ const parsed = JSON.parse(raw);
7363
+ return normalizeScopeExcludeNames(Array.isArray(parsed) ? parsed : []);
7364
+ } catch {
7365
+ return normalizeScopeExcludeNames(raw);
7366
+ }
7367
+ }
7278
7368
  function serverScopeOmitDirsDefault() {
7279
7369
  return SERVER_SCOPE_OMIT_DIRS_DEFAULT.length ? SERVER_SCOPE_OMIT_DIRS_DEFAULT : CLIENT_SCOPE_OMIT_DIRS_DEFAULT;
7280
7370
  }
7371
+ function serverScopeExcludeNamesDefault() {
7372
+ return SERVER_SCOPE_EXCLUDE_NAMES_DEFAULT.length ? SERVER_SCOPE_EXCLUDE_NAMES_DEFAULT : CLIENT_SCOPE_EXCLUDE_NAMES_DEFAULT;
7373
+ }
7281
7374
  function effectiveScopeOmitDirs() {
7282
7375
  return savedScopeOmitDirs() ?? serverScopeOmitDirsDefault();
7283
7376
  }
7284
- function appendScopeOmitDirsParam(params) {
7285
- const saved = savedScopeOmitDirs();
7286
- if (saved != null)
7287
- params.set("omit_dirs", saved.join(","));
7377
+ function effectiveScopeExcludeNames() {
7378
+ return savedScopeExcludeNames() ?? serverScopeExcludeNamesDefault();
7379
+ }
7380
+ function appendScopeParams(params) {
7381
+ const omit = savedScopeOmitDirs();
7382
+ if (omit != null)
7383
+ params.set("omit_dirs", omit.join(","));
7384
+ const exclude = savedScopeExcludeNames();
7385
+ if (exclude != null)
7386
+ params.set("exclude_names", exclude.join(","));
7288
7387
  }
7289
7388
  function normalizeViewerFontSize(value) {
7290
7389
  return value === "compact" || value === "large" || value === "xlarge" ? value : "regular";
@@ -7319,8 +7418,9 @@ ${frontmatter.yaml}
7319
7418
  syncSidebarHeaderHeight();
7320
7419
  }
7321
7420
  function repoFileCacheKey(ref) {
7322
- const saved = savedScopeOmitDirs();
7323
- return `${ref}\x00${saved ? saved.join("\x00") : "server"}`;
7421
+ const omit = savedScopeOmitDirs();
7422
+ const exclude = savedScopeExcludeNames();
7423
+ return `${ref}\x00${omit ? omit.join("\x00") : "server"}\x00${exclude ? exclude.join("\x00") : "server"}`;
7324
7424
  }
7325
7425
  async function loadSettings() {
7326
7426
  try {
@@ -7330,6 +7430,7 @@ ${frontmatter.yaml}
7330
7430
  const settings = await res.json();
7331
7431
  setProjectName(settings.project || "");
7332
7432
  SERVER_SCOPE_OMIT_DIRS_DEFAULT = normalizeScopeOmitDirs(settings.scope.omit_dirs_effective);
7433
+ SERVER_SCOPE_EXCLUDE_NAMES_DEFAULT = normalizeScopeExcludeNames(settings.scope.exclude_names_effective);
7333
7434
  return settings;
7334
7435
  } catch {
7335
7436
  return null;
@@ -7671,7 +7772,7 @@ ${frontmatter.yaml}
7671
7772
  applySidebarHidden(!STATE.sidebarHidden);
7672
7773
  }
7673
7774
  function scopeOmitSourceLabel() {
7674
- return savedScopeOmitDirs() != null ? "Browser override" : "Server default";
7775
+ return savedScopeOmitDirs() != null || savedScopeExcludeNames() != null ? "Browser override" : "Server default";
7675
7776
  }
7676
7777
  function refreshRepositoryTreeAfterSettings() {
7677
7778
  REPO_FILE_CACHE.clear();
@@ -7687,15 +7788,18 @@ ${frontmatter.yaml}
7687
7788
  async function openScopeSettings() {
7688
7789
  const pop = document.querySelector("#scope-settings-popover");
7689
7790
  const input = document.querySelector("#scope-omit-dirs");
7791
+ const excludeInput = document.querySelector("#scope-exclude-names");
7690
7792
  const sidebarFontSize = document.querySelector("#sidebar-font-size");
7691
7793
  const codeFontSize = document.querySelector("#code-font-size");
7692
7794
  const source = document.querySelector("#scope-omit-source");
7693
- if (!pop || !input || !sidebarFontSize || !codeFontSize || !source)
7795
+ if (!pop || !input || !excludeInput || !sidebarFontSize || !codeFontSize || !source)
7694
7796
  return;
7695
7797
  await loadSettings();
7696
7798
  sidebarFontSize.value = savedSidebarFontSize();
7697
7799
  codeFontSize.value = savedCodeFontSize();
7698
7800
  input.value = effectiveScopeOmitDirs().join(`
7801
+ `);
7802
+ excludeInput.value = effectiveScopeExcludeNames().join(`
7699
7803
  `);
7700
7804
  source.textContent = 'Saved for project "' + (PROJECT_NAME || "default") + '" in this browser. Source: ' + scopeOmitSourceLabel() + ". Used by tree, Ctrl+K, and Ctrl+G. Reset removes the browser override.";
7701
7805
  pop.hidden = false;
@@ -7708,15 +7812,17 @@ ${frontmatter.yaml}
7708
7812
  }
7709
7813
  function saveScopeSettings() {
7710
7814
  const input = document.querySelector("#scope-omit-dirs");
7815
+ const excludeInput = document.querySelector("#scope-exclude-names");
7711
7816
  const sidebarFontSize = document.querySelector("#sidebar-font-size");
7712
7817
  const codeFontSize = document.querySelector("#code-font-size");
7713
- if (!input || !sidebarFontSize || !codeFontSize)
7818
+ if (!input || !excludeInput || !sidebarFontSize || !codeFontSize)
7714
7819
  return;
7715
7820
  localStorage.setItem(SIDEBAR_FONT_SIZE_STORAGE_KEY, normalizeViewerFontSize(sidebarFontSize.value));
7716
7821
  localStorage.setItem(CODE_FONT_SIZE_STORAGE_KEY, normalizeViewerFontSize(codeFontSize.value));
7717
7822
  applySidebarFontSize();
7718
7823
  applyCodeFontSize();
7719
7824
  localStorage.setItem(scopeOmitDirsStorageKey(), JSON.stringify(normalizeScopeOmitDirs(input.value)));
7825
+ localStorage.setItem(scopeExcludeNamesStorageKey(), JSON.stringify(normalizeScopeExcludeNames(excludeInput.value)));
7720
7826
  closeScopeSettings();
7721
7827
  refreshRepositoryTreeAfterSettings();
7722
7828
  }
@@ -7726,6 +7832,7 @@ ${frontmatter.yaml}
7726
7832
  applySidebarFontSize("regular");
7727
7833
  applyCodeFontSize("regular");
7728
7834
  localStorage.removeItem(scopeOmitDirsStorageKey());
7835
+ localStorage.removeItem(scopeExcludeNamesStorageKey());
7729
7836
  closeScopeSettings();
7730
7837
  refreshRepositoryTreeAfterSettings();
7731
7838
  }
@@ -7809,6 +7916,9 @@ ${frontmatter.yaml}
7809
7916
  li.className = "tree-dir";
7810
7917
  li.tabIndex = -1;
7811
7918
  li.dataset.dirpath = dir.path;
7919
+ li.dataset.type = "tree";
7920
+ if (dir.children_omitted_reason)
7921
+ li.dataset.childrenOmittedReason = dir.children_omitted_reason;
7812
7922
  if (dir.explicit)
7813
7923
  li.dataset.explicit = "true";
7814
7924
  if (dir.children_omitted) {
@@ -7894,6 +8004,7 @@ ${frontmatter.yaml}
7894
8004
  li.className = "tree-file";
7895
8005
  li.tabIndex = -1;
7896
8006
  li.dataset.path = f2.path;
8007
+ li.dataset.type = "blob";
7897
8008
  li.classList.toggle("viewed", !onFileClick && STATE.viewedFiles.has(f2.path));
7898
8009
  li.style.setProperty("--lvl-pad", `${12 + depth * 14}px`);
7899
8010
  const spacer = document.createElement("span");
@@ -7952,6 +8063,9 @@ ${frontmatter.yaml}
7952
8063
  li.className = "tree-dir";
7953
8064
  li.tabIndex = -1;
7954
8065
  li.dataset.dirpath = dir.path;
8066
+ li.dataset.type = "tree";
8067
+ if (dir.children_omitted_reason)
8068
+ li.dataset.childrenOmittedReason = dir.children_omitted_reason;
7955
8069
  if (dir.explicit)
7956
8070
  li.dataset.explicit = "true";
7957
8071
  if (dir.children_omitted) {
@@ -8032,6 +8146,7 @@ ${frontmatter.yaml}
8032
8146
  li.className = "tree-file";
8033
8147
  li.tabIndex = -1;
8034
8148
  li.dataset.path = f2.path;
8149
+ li.dataset.type = "blob";
8035
8150
  li.classList.toggle("viewed", !onFileClick && STATE.viewedFiles.has(f2.path));
8036
8151
  li.classList.toggle("hidden-by-tests", STATE.hideTests && TEST_RE.test(f2.path || ""));
8037
8152
  li.style.setProperty("--lvl-pad", `${12 + depth * 14}px`);
@@ -8944,6 +9059,422 @@ ${frontmatter.yaml}
8944
9059
  button.disabled = false;
8945
9060
  }
8946
9061
  }
9062
+ function closeRepoContextMenu() {
9063
+ document.querySelector(".gdp-context-menu")?.remove();
9064
+ }
9065
+ function closeTrashDialog() {
9066
+ document.querySelector(".gdp-trash-dialog-backdrop")?.remove();
9067
+ }
9068
+ function createTrashDialog(title, body, actions) {
9069
+ closeTrashDialog();
9070
+ const backdrop = document.createElement("div");
9071
+ backdrop.className = "gdp-trash-dialog-backdrop";
9072
+ const dialog = document.createElement("div");
9073
+ dialog.className = "gdp-trash-dialog";
9074
+ const titleId = "gdp-trash-dialog-title";
9075
+ const bodyId = "gdp-trash-dialog-body";
9076
+ dialog.setAttribute("role", "dialog");
9077
+ dialog.setAttribute("aria-modal", "true");
9078
+ dialog.setAttribute("aria-labelledby", titleId);
9079
+ dialog.setAttribute("aria-describedby", bodyId);
9080
+ const heading2 = document.createElement("div");
9081
+ heading2.id = titleId;
9082
+ heading2.className = "gdp-trash-dialog-title";
9083
+ heading2.textContent = title;
9084
+ const message = document.createElement("div");
9085
+ message.id = bodyId;
9086
+ message.className = "gdp-trash-dialog-body";
9087
+ message.textContent = body;
9088
+ const actionRow = document.createElement("div");
9089
+ actionRow.className = "gdp-trash-dialog-actions";
9090
+ actionRow.append(...actions);
9091
+ dialog.append(heading2, message, actionRow);
9092
+ backdrop.appendChild(dialog);
9093
+ document.body.appendChild(backdrop);
9094
+ return backdrop;
9095
+ }
9096
+ function confirmMoveToTrash(path, focusReturnTarget) {
9097
+ return new Promise((resolve) => {
9098
+ const previousFocus = focusReturnTarget || document.activeElement;
9099
+ const cancel = document.createElement("button");
9100
+ cancel.type = "button";
9101
+ cancel.className = "gdp-btn gdp-btn-sm";
9102
+ cancel.textContent = "Cancel";
9103
+ const move = document.createElement("button");
9104
+ move.type = "button";
9105
+ move.className = "gdp-btn gdp-btn-sm gdp-trash-dialog-danger";
9106
+ move.textContent = "Move to Trash";
9107
+ const done = (ok) => {
9108
+ document.removeEventListener("keydown", onKeydown);
9109
+ closeTrashDialog();
9110
+ previousFocus?.focus?.();
9111
+ resolve(ok);
9112
+ };
9113
+ const onKeydown = (event) => {
9114
+ if (event.key === "Escape") {
9115
+ event.preventDefault();
9116
+ event.stopPropagation();
9117
+ done(false);
9118
+ return;
9119
+ }
9120
+ if (event.key !== "Tab")
9121
+ return;
9122
+ const focusables = [cancel, move];
9123
+ const index = focusables.indexOf(document.activeElement);
9124
+ if (index < 0) {
9125
+ event.preventDefault();
9126
+ focusables[0].focus();
9127
+ return;
9128
+ }
9129
+ if (event.shiftKey && index <= 0) {
9130
+ event.preventDefault();
9131
+ focusables[focusables.length - 1].focus();
9132
+ } else if (!event.shiftKey && index === focusables.length - 1) {
9133
+ event.preventDefault();
9134
+ focusables[0].focus();
9135
+ }
9136
+ };
9137
+ cancel.addEventListener("click", () => done(false));
9138
+ move.addEventListener("click", () => done(true));
9139
+ const backdrop = createTrashDialog("Move to Trash?", `Move "${path}" to Trash?`, [cancel, move]);
9140
+ backdrop.addEventListener("pointerdown", (event) => {
9141
+ if (event.target === backdrop)
9142
+ done(false);
9143
+ });
9144
+ document.addEventListener("keydown", onKeydown);
9145
+ cancel.focus();
9146
+ });
9147
+ }
9148
+ function showTrashError(message) {
9149
+ const ok = document.createElement("button");
9150
+ ok.type = "button";
9151
+ ok.className = "gdp-btn gdp-btn-sm";
9152
+ ok.textContent = "OK";
9153
+ ok.addEventListener("click", closeTrashDialog);
9154
+ createTrashDialog("Trash failed", message, [ok]);
9155
+ ok.focus();
9156
+ }
9157
+ function showCreateDirectoryError(message) {
9158
+ const ok = document.createElement("button");
9159
+ ok.type = "button";
9160
+ ok.className = "gdp-btn gdp-btn-sm";
9161
+ ok.textContent = "OK";
9162
+ ok.addEventListener("click", closeTrashDialog);
9163
+ createTrashDialog("New folder failed", message, [ok]);
9164
+ ok.focus();
9165
+ }
9166
+ function askNewDirectoryName(path, focusReturnTarget) {
9167
+ return new Promise((resolve) => {
9168
+ const previousFocus = focusReturnTarget || document.activeElement;
9169
+ const cancel = document.createElement("button");
9170
+ cancel.type = "button";
9171
+ cancel.className = "gdp-btn gdp-btn-sm";
9172
+ cancel.textContent = "Cancel";
9173
+ const create = document.createElement("button");
9174
+ create.type = "button";
9175
+ create.className = "gdp-btn gdp-btn-sm";
9176
+ create.textContent = "Create";
9177
+ const input = document.createElement("input");
9178
+ input.className = "gdp-create-dir-input";
9179
+ input.type = "text";
9180
+ input.autocomplete = "off";
9181
+ input.placeholder = "Folder name";
9182
+ input.setAttribute("aria-label", "Folder name");
9183
+ const error2 = document.createElement("div");
9184
+ error2.className = "gdp-create-dir-error";
9185
+ error2.setAttribute("role", "alert");
9186
+ const syncValidity = () => {
9187
+ const valid = !!normalizeNewDirectoryName(input.value);
9188
+ create.disabled = !valid;
9189
+ error2.textContent = input.value && !valid ? "Use a folder name without slashes, control characters, . or .." : "";
9190
+ return valid;
9191
+ };
9192
+ const done = (name) => {
9193
+ document.removeEventListener("keydown", onKeydown);
9194
+ closeTrashDialog();
9195
+ previousFocus?.focus?.();
9196
+ resolve(name);
9197
+ };
9198
+ const submit = () => {
9199
+ const name = normalizeNewDirectoryName(input.value);
9200
+ if (!name) {
9201
+ syncValidity();
9202
+ input.focus();
9203
+ return;
9204
+ }
9205
+ done(name);
9206
+ };
9207
+ const onKeydown = (event) => {
9208
+ if (event.key === "Escape") {
9209
+ event.preventDefault();
9210
+ event.stopPropagation();
9211
+ done(null);
9212
+ return;
9213
+ }
9214
+ if (event.isComposing || event.keyCode === 229)
9215
+ return;
9216
+ if (event.key === "Enter") {
9217
+ event.preventDefault();
9218
+ submit();
9219
+ return;
9220
+ }
9221
+ if (event.key !== "Tab")
9222
+ return;
9223
+ const focusables = [input, cancel, create];
9224
+ const index = focusables.indexOf(document.activeElement);
9225
+ if (index < 0) {
9226
+ event.preventDefault();
9227
+ focusables[0].focus();
9228
+ return;
9229
+ }
9230
+ if (event.shiftKey && index <= 0) {
9231
+ event.preventDefault();
9232
+ focusables[focusables.length - 1].focus();
9233
+ } else if (!event.shiftKey && index === focusables.length - 1) {
9234
+ event.preventDefault();
9235
+ focusables[0].focus();
9236
+ }
9237
+ };
9238
+ cancel.addEventListener("click", () => done(null));
9239
+ create.addEventListener("click", submit);
9240
+ input.addEventListener("input", syncValidity);
9241
+ create.disabled = true;
9242
+ const backdrop = createTrashDialog("New Folder", `Create a folder in "${path || PROJECT_NAME || "repository"}".`, [cancel, create]);
9243
+ const body = backdrop.querySelector(".gdp-trash-dialog-body");
9244
+ body?.append(input, error2);
9245
+ backdrop.addEventListener("pointerdown", (event) => {
9246
+ if (event.target === backdrop)
9247
+ done(null);
9248
+ });
9249
+ document.addEventListener("keydown", onKeydown);
9250
+ input.focus();
9251
+ });
9252
+ }
9253
+ async function moveRepoPathToTrash(path) {
9254
+ const res = await fetch("/_trash_path", {
9255
+ method: "POST",
9256
+ headers: {
9257
+ "Content-Type": "application/json",
9258
+ "X-Code-Viewer-Action": "1"
9259
+ },
9260
+ body: JSON.stringify({ path })
9261
+ });
9262
+ if (!res.ok) {
9263
+ showTrashError(`Failed to move "${path}" to Trash: ${await res.text()}`);
9264
+ return false;
9265
+ }
9266
+ const body = await res.json();
9267
+ if (body.undo)
9268
+ UNDO_STACK.unshift(body.undo);
9269
+ return true;
9270
+ }
9271
+ async function requestCreateDirectory(path, onCreated, options = {}) {
9272
+ if (creatingDirectory)
9273
+ return;
9274
+ const name = await askNewDirectoryName(path, options.focusReturnTarget);
9275
+ if (!name)
9276
+ return;
9277
+ creatingDirectory = true;
9278
+ try {
9279
+ const res = await fetch("/_create_directory", {
9280
+ method: "POST",
9281
+ headers: {
9282
+ "Content-Type": "application/json",
9283
+ "X-Code-Viewer-Action": "1"
9284
+ },
9285
+ body: JSON.stringify({ dir: path, name })
9286
+ });
9287
+ if (!res.ok) {
9288
+ showCreateDirectoryError(`Failed to create "${name}": ${await res.text()}`);
9289
+ return;
9290
+ }
9291
+ const body = await res.json();
9292
+ onCreated(body.path || (path ? `${path}/${name}` : name));
9293
+ } finally {
9294
+ creatingDirectory = false;
9295
+ }
9296
+ }
9297
+ async function runUndoAction(action) {
9298
+ if (action.type !== "trash")
9299
+ return false;
9300
+ const res = await fetch("/_restore_trash", {
9301
+ method: "POST",
9302
+ headers: {
9303
+ "Content-Type": "application/json",
9304
+ "X-Code-Viewer-Action": "1"
9305
+ },
9306
+ body: JSON.stringify(action.payload)
9307
+ });
9308
+ if (!res.ok) {
9309
+ showTrashError(`Failed to undo "${action.label}": ${await res.text()}`);
9310
+ return false;
9311
+ }
9312
+ return true;
9313
+ }
9314
+ async function undoLastAction() {
9315
+ const action = UNDO_STACK.shift();
9316
+ if (!action)
9317
+ return false;
9318
+ if (!await runUndoAction(action)) {
9319
+ UNDO_STACK.unshift(action);
9320
+ return true;
9321
+ }
9322
+ invalidateRepoSidebar();
9323
+ await load();
9324
+ return true;
9325
+ }
9326
+ async function requestMoveToTrash(path, onMoved, options = {}) {
9327
+ if (!await confirmMoveToTrash(path, options.focusReturnTarget))
9328
+ return;
9329
+ if (await moveRepoPathToTrash(path))
9330
+ onMoved();
9331
+ }
9332
+ function canTrashWorktreeRef(ref) {
9333
+ return ref === "worktree" || ref === "";
9334
+ }
9335
+ function parentRepoPath(path) {
9336
+ return path.split("/").slice(0, -1).join("/");
9337
+ }
9338
+ async function copyRepoContextText(text2) {
9339
+ if (!text2)
9340
+ return;
9341
+ await navigator.clipboard.writeText(text2);
9342
+ }
9343
+ function createCopyPathButton(path) {
9344
+ const button = document.createElement("button");
9345
+ button.type = "button";
9346
+ button.className = "gdp-file-header-icon gdp-copy-path";
9347
+ button.title = "copy folder path";
9348
+ button.setAttribute("aria-label", "copy folder path");
9349
+ button.innerHTML = iconSvg("octicon-copy", COPY_16_PATHS);
9350
+ button.addEventListener("click", async (event) => {
9351
+ event.stopPropagation();
9352
+ try {
9353
+ await navigator.clipboard.writeText(filePathClipboardText(path));
9354
+ button.classList.add("copied");
9355
+ setTimeout(() => {
9356
+ button.classList.remove("copied");
9357
+ }, 1200);
9358
+ } catch {
9359
+ button.classList.add("failed");
9360
+ setTimeout(() => {
9361
+ button.classList.remove("failed");
9362
+ }, 1200);
9363
+ }
9364
+ });
9365
+ return button;
9366
+ }
9367
+ function showRepoContextMenu(event, entry, ref, onChanged) {
9368
+ if (document.querySelector(".gdp-trash-dialog-backdrop"))
9369
+ return false;
9370
+ if (!canTrashWorktreeRef(ref))
9371
+ return false;
9372
+ if (entry.children_omitted_reason === "internal")
9373
+ return false;
9374
+ if (entry.type !== "tree" && entry.type !== "blob")
9375
+ return false;
9376
+ event.preventDefault();
9377
+ closeRepoContextMenu();
9378
+ const menu = document.createElement("div");
9379
+ menu.className = "gdp-context-menu";
9380
+ const anchor = event.target;
9381
+ const focusReturnTarget = anchor?.closest("li, .gdp-repo-row");
9382
+ const anchorRect = anchor?.closest("li, .gdp-repo-row")?.getBoundingClientRect();
9383
+ const anchorX = event.clientX > 0 ? event.clientX : anchorRect?.left || window.innerWidth / 2;
9384
+ const anchorY = event.clientY > 0 ? event.clientY : anchorRect?.bottom || window.innerHeight / 2;
9385
+ menu.style.left = `${anchorX}px`;
9386
+ menu.style.top = `${anchorY}px`;
9387
+ const copyPath = document.createElement("button");
9388
+ copyPath.type = "button";
9389
+ copyPath.textContent = "Copy Path";
9390
+ copyPath.addEventListener("click", async () => {
9391
+ closeRepoContextMenu();
9392
+ await copyRepoContextText(filePathClipboardText(entry.path));
9393
+ });
9394
+ const copyName = document.createElement("button");
9395
+ copyName.type = "button";
9396
+ copyName.textContent = "Copy Name";
9397
+ copyName.addEventListener("click", async () => {
9398
+ closeRepoContextMenu();
9399
+ await copyRepoContextText(fileNameClipboardText(entry.path));
9400
+ });
9401
+ const createDir = document.createElement("button");
9402
+ createDir.type = "button";
9403
+ createDir.textContent = "New Folder...";
9404
+ createDir.addEventListener("click", async () => {
9405
+ closeRepoContextMenu();
9406
+ const targetPath = entry.type === "blob" ? parentRepoPath(entry.path) : entry.path;
9407
+ await requestCreateDirectory(targetPath, onChanged, {
9408
+ focusReturnTarget
9409
+ });
9410
+ });
9411
+ const trash = document.createElement("button");
9412
+ trash.type = "button";
9413
+ trash.className = "danger";
9414
+ trash.textContent = "Move to Trash...";
9415
+ trash.addEventListener("click", async () => {
9416
+ closeRepoContextMenu();
9417
+ await requestMoveToTrash(entry.path, onChanged, { focusReturnTarget });
9418
+ });
9419
+ menu.append(copyPath, copyName, createDir, trash);
9420
+ document.body.appendChild(menu);
9421
+ const rect = menu.getBoundingClientRect();
9422
+ const left = Math.min(anchorX, window.innerWidth - rect.width - 8);
9423
+ const top = Math.min(anchorY, window.innerHeight - rect.height - 8);
9424
+ menu.style.left = `${Math.max(8, left)}px`;
9425
+ menu.style.top = `${Math.max(8, top)}px`;
9426
+ return true;
9427
+ }
9428
+ function sidebarTrashEntryFromEvent(event) {
9429
+ if (!isRepositorySidebarMode())
9430
+ return null;
9431
+ const row = event.target?.closest("#filelist li");
9432
+ if (!row)
9433
+ return null;
9434
+ const path = row.dataset.path || row.dataset.dirpath || "";
9435
+ if (!path)
9436
+ return null;
9437
+ return {
9438
+ path,
9439
+ type: row.dataset.type,
9440
+ children_omitted_reason: row.dataset.childrenOmittedReason
9441
+ };
9442
+ }
9443
+ function handleSidebarContextMenu(event) {
9444
+ const entry = sidebarTrashEntryFromEvent(event);
9445
+ if (!entry)
9446
+ return;
9447
+ if (showRepoContextMenu(event, entry, REPO_SIDEBAR_REF || "worktree", () => loadRepo()))
9448
+ markActive(entry.path);
9449
+ }
9450
+ function createMoveToTrashButton(path, onDeleted) {
9451
+ const button = document.createElement("button");
9452
+ button.type = "button";
9453
+ button.className = "gdp-file-header-icon gdp-trash-path";
9454
+ button.title = "move folder to Trash";
9455
+ button.setAttribute("aria-label", "move folder to Trash");
9456
+ button.innerHTML = iconSvg("octicon-trash", TRASH_16_PATH);
9457
+ button.addEventListener("click", async (event) => {
9458
+ event.stopPropagation();
9459
+ await requestMoveToTrash(path, onDeleted, { focusReturnTarget: button });
9460
+ });
9461
+ return button;
9462
+ }
9463
+ function createNewFolderButton(path, onCreated) {
9464
+ const button = document.createElement("button");
9465
+ button.type = "button";
9466
+ button.className = "gdp-file-header-icon gdp-create-dir";
9467
+ button.title = "new folder";
9468
+ button.setAttribute("aria-label", "new folder");
9469
+ button.innerHTML = iconSvg("octicon-plus", PLUS_16_PATH);
9470
+ button.addEventListener("click", async (event) => {
9471
+ event.stopPropagation();
9472
+ await requestCreateDirectory(path, onCreated, {
9473
+ focusReturnTarget: button
9474
+ });
9475
+ });
9476
+ return button;
9477
+ }
8947
9478
  function createOpenPathButton(path, kind, title = "open folder in OS") {
8948
9479
  const button = document.createElement("button");
8949
9480
  button.type = "button";
@@ -8994,7 +9525,10 @@ ${frontmatter.yaml}
8994
9525
  button.className = "gdp-btn gdp-btn-sm";
8995
9526
  button.textContent = "Upload files";
8996
9527
  button.addEventListener("click", () => input.click());
8997
- const fail = () => {
9528
+ const error2 = document.createElement("div");
9529
+ error2.className = "gdp-upload-error";
9530
+ const fail = (message = "Upload failed") => {
9531
+ error2.textContent = message;
8998
9532
  dropPanel.classList.add("failed");
8999
9533
  setTimeout(() => dropPanel.classList.remove("failed"), 1600);
9000
9534
  };
@@ -9002,8 +9536,9 @@ ${frontmatter.yaml}
9002
9536
  try {
9003
9537
  if (input.files?.length)
9004
9538
  await uploadFiles(path, input.files);
9005
- } catch {
9006
- fail();
9539
+ error2.textContent = "";
9540
+ } catch (uploadError) {
9541
+ fail(uploadError instanceof Error ? uploadError.message : "Upload failed");
9007
9542
  } finally {
9008
9543
  input.value = "";
9009
9544
  }
@@ -9020,11 +9555,12 @@ ${frontmatter.yaml}
9020
9555
  const files = event.dataTransfer?.files;
9021
9556
  if (files?.length)
9022
9557
  await uploadFiles(path, files);
9023
- } catch {
9024
- fail();
9558
+ error2.textContent = "";
9559
+ } catch (uploadError) {
9560
+ fail(uploadError instanceof Error ? uploadError.message : "Upload failed");
9025
9561
  }
9026
9562
  });
9027
- dropPanel.append(copy, button, input);
9563
+ dropPanel.append(copy, button, input, error2);
9028
9564
  return dropPanel;
9029
9565
  }
9030
9566
  function repoRoute(ref, path) {
@@ -9099,39 +9635,31 @@ ${frontmatter.yaml}
9099
9635
  const target = $("#diff");
9100
9636
  const shell = document.createElement("section");
9101
9637
  shell.className = "gdp-repo-shell";
9102
- const { wrap: targetPickerWrap, input: targetPicker } = createRefSelectorInput({
9103
- id: "repo-ref",
9104
- placeholder: "ref...",
9105
- title: "repository ref",
9106
- value: meta.ref || "worktree"
9107
- });
9108
- wireRefSelectorInput(targetPicker, (ref) => {
9109
- setRoute(repoRoute(ref, ""));
9110
- loadRepo();
9111
- });
9112
9638
  const toolbar = document.createElement("div");
9113
9639
  toolbar.className = "gdp-file-detail-header gdp-repo-toolbar";
9114
- toolbar.append(targetPickerWrap, createRepoBreadcrumb(meta.ref, meta.path || ""), createOpenPathButton(meta.path || "", "directory", "open this folder in OS"));
9640
+ const pathHeader = document.createElement("div");
9641
+ pathHeader.className = "gdp-file-detail-path";
9642
+ pathHeader.appendChild(createRepoBreadcrumb(meta.ref, meta.path || ""));
9643
+ if (meta.path)
9644
+ pathHeader.appendChild(createCopyPathButton(meta.path));
9645
+ pathHeader.appendChild(createOpenPathButton(meta.path || "", "directory", "open this folder in OS"));
9646
+ toolbar.appendChild(pathHeader);
9647
+ if (canTrashWorktreeRef(meta.ref)) {
9648
+ toolbar.appendChild(createNewFolderButton(meta.path || "", () => loadRepo()));
9649
+ if (meta.path) {
9650
+ toolbar.appendChild(createMoveToTrashButton(meta.path, () => {
9651
+ const parent = meta.path.split("/").slice(0, -1).join("/");
9652
+ setRoute(repoRoute(meta.ref, parent));
9653
+ loadRepo();
9654
+ }));
9655
+ }
9656
+ }
9115
9657
  shell.appendChild(toolbar);
9116
9658
  const listCard = document.createElement("section");
9117
9659
  listCard.className = "gdp-file-shell loaded gdp-repo-list-shell";
9118
9660
  const listWrapper = document.createElement("div");
9119
9661
  listWrapper.className = "d2h-file-wrapper";
9120
- const listHeader = document.createElement("div");
9121
- listHeader.className = "d2h-file-header";
9122
- const listName = document.createElement("div");
9123
- listName.className = "d2h-file-name-wrapper";
9124
- const listIcon = document.createElement("span");
9125
- listIcon.className = "dir-icon";
9126
- setFolderIcon(listIcon, false);
9127
- const listTitle = document.createElement("span");
9128
- listTitle.className = "d2h-file-name";
9129
- listTitle.textContent = meta.path || meta.project || "Files";
9130
- listName.append(listIcon, listTitle);
9131
- listHeader.appendChild(listName);
9132
- listHeader.appendChild(createOpenPathButton(meta.path || "", "directory", "open this folder in OS"));
9133
- listWrapper.appendChild(listHeader);
9134
- if (meta.upload_enabled && (meta.ref === "worktree" || meta.ref === "")) {
9662
+ if (meta.ref === "worktree" || meta.ref === "") {
9135
9663
  listWrapper.appendChild(createRepoUploadPanel(meta.path || ""));
9136
9664
  }
9137
9665
  const sortHost = document.createElement("div");
@@ -9198,6 +9726,7 @@ ${frontmatter.yaml}
9198
9726
  renderStandaloneSource({ path: entry.path, ref: meta.ref });
9199
9727
  }
9200
9728
  });
9729
+ row.addEventListener("contextmenu", (event) => showRepoContextMenu(event, entry, meta.ref, () => loadRepo()));
9201
9730
  list2.appendChild(row);
9202
9731
  });
9203
9732
  if (!meta.entries.length) {
@@ -9271,7 +9800,7 @@ ${frontmatter.yaml}
9271
9800
  const params = new URLSearchParams;
9272
9801
  params.set("ref", normalizedRef);
9273
9802
  params.set("recursive", "1");
9274
- appendScopeOmitDirsParam(params);
9803
+ appendScopeParams(params);
9275
9804
  REPO_SIDEBAR_LOAD_REF = normalizedRef;
9276
9805
  const load2 = trackLoad(fetch(`/_tree?${params.toString()}`).then((r2) => {
9277
9806
  if (!r2.ok)
@@ -9289,6 +9818,7 @@ ${frontmatter.yaml}
9289
9818
  children_omitted: entry.children_omitted,
9290
9819
  children_omitted_reason: entry.children_omitted_reason
9291
9820
  }));
9821
+ REPO_SIDEBAR_REF = normalizedRef;
9292
9822
  renderSidebar(files, (file) => {
9293
9823
  if (file.type === "tree") {
9294
9824
  setRoute(repoRoute(normalizedRef, file.path));
@@ -9304,7 +9834,6 @@ ${frontmatter.yaml}
9304
9834
  });
9305
9835
  renderStandaloneSource({ path: file.path, ref: normalizedRef });
9306
9836
  });
9307
- REPO_SIDEBAR_REF = normalizedRef;
9308
9837
  activateRepoSidebarPath(currentPath);
9309
9838
  }).catch(() => {
9310
9839
  REPO_SIDEBAR_REF = null;
@@ -11579,6 +12108,13 @@ ${frontmatter.yaml}
11579
12108
  name.appendChild(copy);
11580
12109
  name.appendChild(createOpenPathButton(target.path, "file-parent", "open parent folder in OS"));
11581
12110
  header.appendChild(name);
12111
+ if (repoTarget && canTrashWorktreeRef(repoTarget)) {
12112
+ header.appendChild(createMoveToTrashButton(target.path, () => {
12113
+ const parent = target.path.split("/").slice(0, -1).join("/");
12114
+ setRoute(repoRoute(repoTarget, parent));
12115
+ loadRepo();
12116
+ }));
12117
+ }
11582
12118
  loadRawFileInfo(target).then((meta) => {
11583
12119
  if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
11584
12120
  return;
@@ -12781,7 +13317,7 @@ ${frontmatter.yaml}
12781
13317
  return cached;
12782
13318
  const params = new URLSearchParams;
12783
13319
  params.set("ref", ref);
12784
- appendScopeOmitDirsParam(params);
13320
+ appendScopeParams(params);
12785
13321
  const res = await trackLoad(fetch(`/_files?${params.toString()}`).then((r2) => {
12786
13322
  if (!r2.ok)
12787
13323
  throw new Error("failed to load files");
@@ -12885,7 +13421,7 @@ ${frontmatter.yaml}
12885
13421
  params.set("max", "200");
12886
13422
  if (state.grepRegex)
12887
13423
  params.set("regex", "1");
12888
- appendScopeOmitDirsParam(params);
13424
+ appendScopeParams(params);
12889
13425
  if (source === "diff") {
12890
13426
  for (const file of state.diffSnapshot)
12891
13427
  params.append("path", file.path);
@@ -13176,10 +13712,19 @@ ${frontmatter.yaml}
13176
13712
  document.addEventListener("keydown", handleVirtualSourcePagingKeydown, {
13177
13713
  capture: true
13178
13714
  });
13179
- document.addEventListener("keydown", (e2) => {
13715
+ document.addEventListener("click", closeRepoContextMenu);
13716
+ $("#filelist").addEventListener("contextmenu", handleSidebarContextMenu);
13717
+ document.addEventListener("keydown", async (e2) => {
13718
+ if (e2.key === "Escape")
13719
+ closeRepoContextMenu();
13180
13720
  if (e2.__gdpVirtualSourcePagingHandled)
13181
13721
  return;
13182
13722
  const targetEl = e2.target;
13723
+ if ((e2.ctrlKey || e2.metaKey) && !e2.shiftKey && !e2.altKey && e2.key.toLowerCase() === "z" && !isEditableKeyTarget(targetEl)) {
13724
+ if (await undoLastAction())
13725
+ e2.preventDefault();
13726
+ return;
13727
+ }
13183
13728
  if ((e2.ctrlKey || e2.metaKey) && e2.key.toLowerCase() === "f" && !isEditableKeyTarget(targetEl)) {
13184
13729
  if (openVirtualSourceSearchFromKeyboard(targetEl)) {
13185
13730
  e2.preventDefault();
@@ -13214,7 +13759,7 @@ ${frontmatter.yaml}
13214
13759
  params.set("ref", STATE.route.ref || "worktree");
13215
13760
  if (STATE.route.path)
13216
13761
  params.set("path", STATE.route.path);
13217
- appendScopeOmitDirsParam(params);
13762
+ appendScopeParams(params);
13218
13763
  return trackLoad(fetch(`/_tree?${params.toString()}`).then((r2) => {
13219
13764
  if (!r2.ok)
13220
13765
  throw new Error("failed to load repository tree");