@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/README.md +7 -13
- package/dist/code-viewer.js +449 -60
- package/package.json +1 -1
- package/web/app.js +594 -49
- package/web/index.html +5 -2
- package/web/style.css +144 -12
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 <=
|
|
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
|
|
7285
|
-
|
|
7286
|
-
|
|
7287
|
-
|
|
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
|
|
7323
|
-
|
|
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
|
|
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
|
-
|
|
9006
|
-
|
|
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
|
-
|
|
9024
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
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
|
-
|
|
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");
|