@youtyan/code-viewer 0.1.26 → 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 +4 -13
- package/dist/code-viewer.js +105 -22
- package/package.json +1 -1
- package/web/app.js +304 -39
- package/web/index.html +2 -1
- package/web/style.css +61 -10
package/README.md
CHANGED
|
@@ -80,30 +80,21 @@ the full non-virtual view.
|
|
|
80
80
|
|
|
81
81
|
## Uploads
|
|
82
82
|
|
|
83
|
-
File uploads are
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
```sh
|
|
87
|
-
code-viewer --cwd /path/to/repo --allow-upload
|
|
88
|
-
```
|
|
83
|
+
File uploads are available for the local worktree target. Git tree views remain
|
|
84
|
+
read-only.
|
|
89
85
|
|
|
90
|
-
|
|
86
|
+
Place `.code-viewer.json` at the repository root to configure repository scope
|
|
87
|
+
defaults:
|
|
91
88
|
|
|
92
89
|
```json
|
|
93
90
|
{
|
|
94
91
|
"version": 1,
|
|
95
|
-
"upload": {
|
|
96
|
-
"enabled": true
|
|
97
|
-
},
|
|
98
92
|
"scope": {
|
|
99
93
|
"omitDirs": ["node_modules", "dist", "build"]
|
|
100
94
|
}
|
|
101
95
|
}
|
|
102
96
|
```
|
|
103
97
|
|
|
104
|
-
Uploads are accepted only for the worktree target. Git tree views remain
|
|
105
|
-
read-only.
|
|
106
|
-
|
|
107
98
|
Repository scope settings control recursive repository browsing and search scope
|
|
108
99
|
for the left tree, Ctrl+K file palette, and Ctrl+G grep palette. The in-app Scope
|
|
109
100
|
Settings popover stores only a browser-local override in localStorage; edit
|
package/dist/code-viewer.js
CHANGED
|
@@ -19,6 +19,23 @@ import {
|
|
|
19
19
|
import { homedir } from "node:os";
|
|
20
20
|
import { basename as basename2, dirname as dirname2, extname, join as join4, relative } from "node:path";
|
|
21
21
|
|
|
22
|
+
// web-src/directory-name.ts
|
|
23
|
+
function normalizeNewDirectoryName(name) {
|
|
24
|
+
if (typeof name !== "string")
|
|
25
|
+
return null;
|
|
26
|
+
const trimmed = name.trim();
|
|
27
|
+
if (!trimmed || trimmed.length > 180)
|
|
28
|
+
return null;
|
|
29
|
+
if (trimmed.includes("/") || trimmed.includes("\\") || trimmed.includes("\x00") || Array.from(trimmed).some((char) => {
|
|
30
|
+
const code = char.charCodeAt(0);
|
|
31
|
+
return code < 32 || code === 127;
|
|
32
|
+
}))
|
|
33
|
+
return null;
|
|
34
|
+
if (trimmed === "." || trimmed === ".." || trimmed.toLowerCase() === ".git")
|
|
35
|
+
return null;
|
|
36
|
+
return trimmed;
|
|
37
|
+
}
|
|
38
|
+
|
|
22
39
|
// web-src/routes.ts
|
|
23
40
|
var SPA_PATHS = ["/todif", "/todiff", "/file", "/help"];
|
|
24
41
|
var APP_ENTRY_PATHS = ["/", "/index.html"];
|
|
@@ -1236,8 +1253,8 @@ var SIZE_LARGE = 20000;
|
|
|
1236
1253
|
var LINE_INDEX_MIN_START = 1e4;
|
|
1237
1254
|
var LINE_INDEX_MAX_FILE_BYTES = 256 * 1024 * 1024;
|
|
1238
1255
|
var BLOB_LINE_CACHE_MAX_BYTES = 128 * 1024 * 1024;
|
|
1239
|
-
var MAX_UPLOAD_FILE_BYTES =
|
|
1240
|
-
var MAX_UPLOAD_TOTAL_BYTES =
|
|
1256
|
+
var MAX_UPLOAD_FILE_BYTES = 512 * 1024 * 1024;
|
|
1257
|
+
var MAX_UPLOAD_TOTAL_BYTES = 1024 * 1024 * 1024;
|
|
1241
1258
|
var MAX_UPLOAD_BODY_BYTES = MAX_UPLOAD_TOTAL_BYTES + 1024 * 1024;
|
|
1242
1259
|
var MAX_UPLOAD_FILES = 50;
|
|
1243
1260
|
var SAFE_UPLOAD_EXTENSIONS = new Set([
|
|
@@ -1255,7 +1272,19 @@ var SAFE_UPLOAD_EXTENSIONS = new Set([
|
|
|
1255
1272
|
".jpeg",
|
|
1256
1273
|
".gif",
|
|
1257
1274
|
".webp",
|
|
1275
|
+
".svg",
|
|
1258
1276
|
".pdf",
|
|
1277
|
+
".mp4",
|
|
1278
|
+
".mov",
|
|
1279
|
+
".m4v",
|
|
1280
|
+
".webm",
|
|
1281
|
+
".mp3",
|
|
1282
|
+
".wav",
|
|
1283
|
+
".m4a",
|
|
1284
|
+
".aac",
|
|
1285
|
+
".flac",
|
|
1286
|
+
".ogg",
|
|
1287
|
+
".zip",
|
|
1259
1288
|
".ts",
|
|
1260
1289
|
".tsx",
|
|
1261
1290
|
".js",
|
|
@@ -1268,12 +1297,11 @@ var generation = 1;
|
|
|
1268
1297
|
var cwd = repoRoot(process.cwd()) || process.cwd();
|
|
1269
1298
|
var cliArgs = DEFAULT_ARGS;
|
|
1270
1299
|
var listenPort = 0;
|
|
1271
|
-
var allowUpload = false;
|
|
1272
|
-
var uploadAllowedByCli = false;
|
|
1273
1300
|
var openAfterStart = false;
|
|
1274
1301
|
var scopeOmitDirNames = DEFAULT_WORKTREE_OMIT_DIR_NAMES;
|
|
1275
1302
|
var scopeOmitDirCliOverride = null;
|
|
1276
1303
|
var scopeExcludeNames = DEFAULT_EXCLUDE_NAMES;
|
|
1304
|
+
var uploadDisabledByConfig = false;
|
|
1277
1305
|
var rgAvailableCache = null;
|
|
1278
1306
|
var enc = new TextEncoder;
|
|
1279
1307
|
var sseClients = new Set;
|
|
@@ -1326,10 +1354,7 @@ Examples:
|
|
|
1326
1354
|
listenPort = parsed;
|
|
1327
1355
|
} else if (arg === "--open") {
|
|
1328
1356
|
openAfterStart = true;
|
|
1329
|
-
} else if (arg === "--allow-upload") {
|
|
1330
|
-
allowUpload = true;
|
|
1331
|
-
uploadAllowedByCli = true;
|
|
1332
|
-
} else if (arg === "--scope-omit-dir") {
|
|
1357
|
+
} else if (arg === "--allow-upload") {} else if (arg === "--scope-omit-dir") {
|
|
1333
1358
|
const next = process.argv[++i];
|
|
1334
1359
|
if (!next) {
|
|
1335
1360
|
console.error("--scope-omit-dir requires a directory name");
|
|
@@ -1345,10 +1370,9 @@ Examples:
|
|
|
1345
1370
|
}
|
|
1346
1371
|
if (rest.length)
|
|
1347
1372
|
cliArgs = rest;
|
|
1348
|
-
if (!uploadAllowedByCli)
|
|
1349
|
-
allowUpload = loadProjectConfigUploadEnabled();
|
|
1350
1373
|
const configScopeOmitDirs = loadProjectConfigScopeOmitDirs();
|
|
1351
1374
|
const configScopeExcludeNames = loadProjectConfigScopeExcludeNames();
|
|
1375
|
+
uploadDisabledByConfig = loadProjectConfigUploadDisabled();
|
|
1352
1376
|
if (scopeOmitDirCliOverride) {
|
|
1353
1377
|
scopeOmitDirNames = scopeOmitDirCliOverride;
|
|
1354
1378
|
} else if (configScopeOmitDirs) {
|
|
@@ -1680,9 +1704,9 @@ function loadProjectConfig() {
|
|
|
1680
1704
|
return null;
|
|
1681
1705
|
}
|
|
1682
1706
|
}
|
|
1683
|
-
function
|
|
1707
|
+
function loadProjectConfigUploadDisabled() {
|
|
1684
1708
|
const config = loadProjectConfig();
|
|
1685
|
-
return config?.upload?.enabled ===
|
|
1709
|
+
return config?.upload?.enabled === false;
|
|
1686
1710
|
}
|
|
1687
1711
|
function loadProjectConfigScopeOmitDirs() {
|
|
1688
1712
|
const config = loadProjectConfig();
|
|
@@ -1873,7 +1897,7 @@ function handleTree(url) {
|
|
|
1873
1897
|
branch: currentBranch(cwd) || undefined,
|
|
1874
1898
|
entries: recursive ? entries : entries.map((entry) => attachTreeEntryMetadata(target, entry)),
|
|
1875
1899
|
readme: readReadme(target, path),
|
|
1876
|
-
upload_enabled:
|
|
1900
|
+
upload_enabled: !uploadDisabledByConfig && (target === "worktree" || target === "")
|
|
1877
1901
|
});
|
|
1878
1902
|
}
|
|
1879
1903
|
function handleSettings() {
|
|
@@ -2430,24 +2454,26 @@ function isForbiddenUploadName(name) {
|
|
|
2430
2454
|
return lower.startsWith(".") || lower === "package.json" || lower === "package-lock.json" || lower === "bun.lock" || lower === "bun.lockb" || lower === "yarn.lock" || lower === "pnpm-lock.yaml" || lower === "makefile" || lower === "dockerfile" || lower.endsWith(".dockerfile") || /^(tsconfig|jsconfig|bunfig|vercel|netlify|wrangler|next|vite|webpack|rollup|esbuild|astro|svelte|tailwind|postcss|babel|prettier|eslint)\./.test(lower) || lower.endsWith(".config.js") || lower.endsWith(".config.jsx") || lower.endsWith(".config.ts") || lower.endsWith(".config.tsx") || lower.endsWith(".config.mjs") || lower.endsWith(".config.cjs") || lower.includes("credential") || lower.includes("secret") || lower.endsWith(".exe") || lower.endsWith(".dll") || lower.endsWith(".dylib") || lower.endsWith(".so") || lower.endsWith(".sh") || lower.endsWith(".bash") || lower.endsWith(".zsh") || lower.endsWith(".fish") || lower.endsWith(".ps1") || lower.endsWith(".bat") || lower.endsWith(".cmd");
|
|
2431
2455
|
}
|
|
2432
2456
|
function safeUploadFileName(name) {
|
|
2433
|
-
|
|
2457
|
+
const trimmed = name.trim();
|
|
2458
|
+
if (!trimmed || trimmed.length > 180 || trimmed.includes("\x00") || trimmed.includes("/") || trimmed.includes("\\") || Array.from(trimmed).some((char) => {
|
|
2459
|
+
const code = char.charCodeAt(0);
|
|
2460
|
+
return code < 32 || code === 127;
|
|
2461
|
+
}))
|
|
2434
2462
|
return null;
|
|
2435
|
-
if (
|
|
2463
|
+
if (trimmed === "." || trimmed === "..")
|
|
2436
2464
|
return null;
|
|
2437
|
-
if (
|
|
2465
|
+
if (isGitInternalPath(trimmed) || isForbiddenUploadName(trimmed))
|
|
2438
2466
|
return null;
|
|
2439
|
-
if (
|
|
2467
|
+
if (!SAFE_UPLOAD_EXTENSIONS.has(extname(trimmed).toLowerCase()))
|
|
2440
2468
|
return null;
|
|
2441
|
-
|
|
2442
|
-
return null;
|
|
2443
|
-
return name;
|
|
2469
|
+
return trimmed;
|
|
2444
2470
|
}
|
|
2445
2471
|
function uploadOpenFlags() {
|
|
2446
2472
|
return constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW || 0);
|
|
2447
2473
|
}
|
|
2448
2474
|
async function handleUploadFiles(req) {
|
|
2449
|
-
if (
|
|
2450
|
-
return text("upload disabled", 403);
|
|
2475
|
+
if (uploadDisabledByConfig)
|
|
2476
|
+
return text("upload disabled by project config", 403);
|
|
2451
2477
|
if (req.method !== "POST")
|
|
2452
2478
|
return text("method not allowed", 405);
|
|
2453
2479
|
if (!sideEffectRequestAllowed(req))
|
|
@@ -2778,6 +2804,61 @@ async function handleTrashPath(req) {
|
|
|
2778
2804
|
sendSse("update");
|
|
2779
2805
|
return json({ ok: true, generation, undo });
|
|
2780
2806
|
}
|
|
2807
|
+
async function handleCreateDirectory(req) {
|
|
2808
|
+
if (req.method !== "POST")
|
|
2809
|
+
return text("method not allowed", 405);
|
|
2810
|
+
if (!sideEffectRequestAllowed(req))
|
|
2811
|
+
return text("forbidden", 403);
|
|
2812
|
+
const contentType = req.headers.get("content-type") || "";
|
|
2813
|
+
if (!/^application\/json(?:;|$)/i.test(contentType))
|
|
2814
|
+
return text("unsupported media type", 415);
|
|
2815
|
+
const lengthHeader = req.headers.get("content-length");
|
|
2816
|
+
const length = Number(lengthHeader || "0");
|
|
2817
|
+
if (lengthHeader && (!Number.isFinite(length) || length < 0))
|
|
2818
|
+
return text("invalid content length", 400);
|
|
2819
|
+
if (length > 2048)
|
|
2820
|
+
return text("payload too large", 413);
|
|
2821
|
+
let body = {};
|
|
2822
|
+
try {
|
|
2823
|
+
const raw = await req.text();
|
|
2824
|
+
if (raw.length > 2048)
|
|
2825
|
+
return text("payload too large", 413);
|
|
2826
|
+
body = JSON.parse(raw);
|
|
2827
|
+
} catch {
|
|
2828
|
+
return text("invalid json", 400);
|
|
2829
|
+
}
|
|
2830
|
+
const dir = typeof body.dir === "string" ? body.dir.trim().replace(/^\/+|\/+$/g, "") : "";
|
|
2831
|
+
const name = normalizeNewDirectoryName(body.name);
|
|
2832
|
+
if (!safeRepoPath(dir))
|
|
2833
|
+
return text("invalid dir", 400);
|
|
2834
|
+
if (dir && isGitInternalPath(dir))
|
|
2835
|
+
return text("forbidden", 403);
|
|
2836
|
+
if (!name)
|
|
2837
|
+
return text("invalid name", 400);
|
|
2838
|
+
const parent = safeOpenWorktreePath(dir);
|
|
2839
|
+
if (!parent)
|
|
2840
|
+
return text("not found", 404);
|
|
2841
|
+
const stats = statSync(parent);
|
|
2842
|
+
if (!stats.isDirectory())
|
|
2843
|
+
return text("not a directory", 400);
|
|
2844
|
+
const targetPath = dir ? `${dir}/${name}` : name;
|
|
2845
|
+
if (!safeRepoPath(targetPath) || isGitInternalPath(targetPath))
|
|
2846
|
+
return text("invalid target", 400);
|
|
2847
|
+
const target = join4(parent, name);
|
|
2848
|
+
if (existsSync3(target))
|
|
2849
|
+
return text("already exists", 409);
|
|
2850
|
+
try {
|
|
2851
|
+
mkdirSync(target, { recursive: false });
|
|
2852
|
+
} catch (error) {
|
|
2853
|
+
if (error.code === "EEXIST")
|
|
2854
|
+
return text("already exists", 409);
|
|
2855
|
+
return text("create failed", 500);
|
|
2856
|
+
}
|
|
2857
|
+
generation++;
|
|
2858
|
+
clearMutableCaches();
|
|
2859
|
+
sendSse("update");
|
|
2860
|
+
return json({ ok: true, path: targetPath, generation });
|
|
2861
|
+
}
|
|
2781
2862
|
async function handleRestoreTrash(req) {
|
|
2782
2863
|
if (req.method !== "POST")
|
|
2783
2864
|
return text("method not allowed", 405);
|
|
@@ -2864,6 +2945,8 @@ var server = await startServer({
|
|
|
2864
2945
|
return handleTrashPath(req);
|
|
2865
2946
|
if (url.pathname === "/_restore_trash")
|
|
2866
2947
|
return handleRestoreTrash(req);
|
|
2948
|
+
if (url.pathname === "/_create_directory")
|
|
2949
|
+
return handleCreateDirectory(req);
|
|
2867
2950
|
if (url.pathname === "/_upload_files")
|
|
2868
2951
|
return handleUploadFiles(req);
|
|
2869
2952
|
if (url.pathname === "/_refs")
|
package/package.json
CHANGED
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;
|
|
@@ -7276,6 +7337,11 @@ ${frontmatter.yaml}
|
|
|
7276
7337
|
return;
|
|
7277
7338
|
PROJECT_NAME = project;
|
|
7278
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
|
+
}
|
|
7279
7345
|
}
|
|
7280
7346
|
function savedScopeOmitDirs() {
|
|
7281
7347
|
const raw = localStorage.getItem(scopeOmitDirsStorageKey());
|
|
@@ -7850,6 +7916,7 @@ ${frontmatter.yaml}
|
|
|
7850
7916
|
li.className = "tree-dir";
|
|
7851
7917
|
li.tabIndex = -1;
|
|
7852
7918
|
li.dataset.dirpath = dir.path;
|
|
7919
|
+
li.dataset.type = "tree";
|
|
7853
7920
|
if (dir.children_omitted_reason)
|
|
7854
7921
|
li.dataset.childrenOmittedReason = dir.children_omitted_reason;
|
|
7855
7922
|
if (dir.explicit)
|
|
@@ -7937,6 +8004,7 @@ ${frontmatter.yaml}
|
|
|
7937
8004
|
li.className = "tree-file";
|
|
7938
8005
|
li.tabIndex = -1;
|
|
7939
8006
|
li.dataset.path = f2.path;
|
|
8007
|
+
li.dataset.type = "blob";
|
|
7940
8008
|
li.classList.toggle("viewed", !onFileClick && STATE.viewedFiles.has(f2.path));
|
|
7941
8009
|
li.style.setProperty("--lvl-pad", `${12 + depth * 14}px`);
|
|
7942
8010
|
const spacer = document.createElement("span");
|
|
@@ -7995,6 +8063,7 @@ ${frontmatter.yaml}
|
|
|
7995
8063
|
li.className = "tree-dir";
|
|
7996
8064
|
li.tabIndex = -1;
|
|
7997
8065
|
li.dataset.dirpath = dir.path;
|
|
8066
|
+
li.dataset.type = "tree";
|
|
7998
8067
|
if (dir.children_omitted_reason)
|
|
7999
8068
|
li.dataset.childrenOmittedReason = dir.children_omitted_reason;
|
|
8000
8069
|
if (dir.explicit)
|
|
@@ -8077,6 +8146,7 @@ ${frontmatter.yaml}
|
|
|
8077
8146
|
li.className = "tree-file";
|
|
8078
8147
|
li.tabIndex = -1;
|
|
8079
8148
|
li.dataset.path = f2.path;
|
|
8149
|
+
li.dataset.type = "blob";
|
|
8080
8150
|
li.classList.toggle("viewed", !onFileClick && STATE.viewedFiles.has(f2.path));
|
|
8081
8151
|
li.classList.toggle("hidden-by-tests", STATE.hideTests && TEST_RE.test(f2.path || ""));
|
|
8082
8152
|
li.style.setProperty("--lvl-pad", `${12 + depth * 14}px`);
|
|
@@ -9084,6 +9154,102 @@ ${frontmatter.yaml}
|
|
|
9084
9154
|
createTrashDialog("Trash failed", message, [ok]);
|
|
9085
9155
|
ok.focus();
|
|
9086
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
|
+
}
|
|
9087
9253
|
async function moveRepoPathToTrash(path) {
|
|
9088
9254
|
const res = await fetch("/_trash_path", {
|
|
9089
9255
|
method: "POST",
|
|
@@ -9102,6 +9268,32 @@ ${frontmatter.yaml}
|
|
|
9102
9268
|
UNDO_STACK.unshift(body.undo);
|
|
9103
9269
|
return true;
|
|
9104
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
|
+
}
|
|
9105
9297
|
async function runUndoAction(action) {
|
|
9106
9298
|
if (action.type !== "trash")
|
|
9107
9299
|
return false;
|
|
@@ -9140,13 +9332,47 @@ ${frontmatter.yaml}
|
|
|
9140
9332
|
function canTrashWorktreeRef(ref) {
|
|
9141
9333
|
return ref === "worktree" || ref === "";
|
|
9142
9334
|
}
|
|
9143
|
-
function
|
|
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) {
|
|
9144
9368
|
if (document.querySelector(".gdp-trash-dialog-backdrop"))
|
|
9145
9369
|
return false;
|
|
9146
9370
|
if (!canTrashWorktreeRef(ref))
|
|
9147
9371
|
return false;
|
|
9148
9372
|
if (entry.children_omitted_reason === "internal")
|
|
9149
9373
|
return false;
|
|
9374
|
+
if (entry.type !== "tree" && entry.type !== "blob")
|
|
9375
|
+
return false;
|
|
9150
9376
|
event.preventDefault();
|
|
9151
9377
|
closeRepoContextMenu();
|
|
9152
9378
|
const menu = document.createElement("div");
|
|
@@ -9158,15 +9384,39 @@ ${frontmatter.yaml}
|
|
|
9158
9384
|
const anchorY = event.clientY > 0 ? event.clientY : anchorRect?.bottom || window.innerHeight / 2;
|
|
9159
9385
|
menu.style.left = `${anchorX}px`;
|
|
9160
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
|
+
});
|
|
9161
9411
|
const trash = document.createElement("button");
|
|
9162
9412
|
trash.type = "button";
|
|
9163
9413
|
trash.className = "danger";
|
|
9164
9414
|
trash.textContent = "Move to Trash...";
|
|
9165
9415
|
trash.addEventListener("click", async () => {
|
|
9166
9416
|
closeRepoContextMenu();
|
|
9167
|
-
await requestMoveToTrash(entry.path,
|
|
9417
|
+
await requestMoveToTrash(entry.path, onChanged, { focusReturnTarget });
|
|
9168
9418
|
});
|
|
9169
|
-
menu.
|
|
9419
|
+
menu.append(copyPath, copyName, createDir, trash);
|
|
9170
9420
|
document.body.appendChild(menu);
|
|
9171
9421
|
const rect = menu.getBoundingClientRect();
|
|
9172
9422
|
const left = Math.min(anchorX, window.innerWidth - rect.width - 8);
|
|
@@ -9186,6 +9436,7 @@ ${frontmatter.yaml}
|
|
|
9186
9436
|
return null;
|
|
9187
9437
|
return {
|
|
9188
9438
|
path,
|
|
9439
|
+
type: row.dataset.type,
|
|
9189
9440
|
children_omitted_reason: row.dataset.childrenOmittedReason
|
|
9190
9441
|
};
|
|
9191
9442
|
}
|
|
@@ -9199,14 +9450,31 @@ ${frontmatter.yaml}
|
|
|
9199
9450
|
function createMoveToTrashButton(path, onDeleted) {
|
|
9200
9451
|
const button = document.createElement("button");
|
|
9201
9452
|
button.type = "button";
|
|
9202
|
-
button.className = "gdp-
|
|
9203
|
-
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);
|
|
9204
9457
|
button.addEventListener("click", async (event) => {
|
|
9205
9458
|
event.stopPropagation();
|
|
9206
9459
|
await requestMoveToTrash(path, onDeleted, { focusReturnTarget: button });
|
|
9207
9460
|
});
|
|
9208
9461
|
return button;
|
|
9209
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
|
+
}
|
|
9210
9478
|
function createOpenPathButton(path, kind, title = "open folder in OS") {
|
|
9211
9479
|
const button = document.createElement("button");
|
|
9212
9480
|
button.type = "button";
|
|
@@ -9257,7 +9525,10 @@ ${frontmatter.yaml}
|
|
|
9257
9525
|
button.className = "gdp-btn gdp-btn-sm";
|
|
9258
9526
|
button.textContent = "Upload files";
|
|
9259
9527
|
button.addEventListener("click", () => input.click());
|
|
9260
|
-
const
|
|
9528
|
+
const error2 = document.createElement("div");
|
|
9529
|
+
error2.className = "gdp-upload-error";
|
|
9530
|
+
const fail = (message = "Upload failed") => {
|
|
9531
|
+
error2.textContent = message;
|
|
9261
9532
|
dropPanel.classList.add("failed");
|
|
9262
9533
|
setTimeout(() => dropPanel.classList.remove("failed"), 1600);
|
|
9263
9534
|
};
|
|
@@ -9265,8 +9536,9 @@ ${frontmatter.yaml}
|
|
|
9265
9536
|
try {
|
|
9266
9537
|
if (input.files?.length)
|
|
9267
9538
|
await uploadFiles(path, input.files);
|
|
9268
|
-
|
|
9269
|
-
|
|
9539
|
+
error2.textContent = "";
|
|
9540
|
+
} catch (uploadError) {
|
|
9541
|
+
fail(uploadError instanceof Error ? uploadError.message : "Upload failed");
|
|
9270
9542
|
} finally {
|
|
9271
9543
|
input.value = "";
|
|
9272
9544
|
}
|
|
@@ -9283,11 +9555,12 @@ ${frontmatter.yaml}
|
|
|
9283
9555
|
const files = event.dataTransfer?.files;
|
|
9284
9556
|
if (files?.length)
|
|
9285
9557
|
await uploadFiles(path, files);
|
|
9286
|
-
|
|
9287
|
-
|
|
9558
|
+
error2.textContent = "";
|
|
9559
|
+
} catch (uploadError) {
|
|
9560
|
+
fail(uploadError instanceof Error ? uploadError.message : "Upload failed");
|
|
9288
9561
|
}
|
|
9289
9562
|
});
|
|
9290
|
-
dropPanel.append(copy, button, input);
|
|
9563
|
+
dropPanel.append(copy, button, input, error2);
|
|
9291
9564
|
return dropPanel;
|
|
9292
9565
|
}
|
|
9293
9566
|
function repoRoute(ref, path) {
|
|
@@ -9362,39 +9635,31 @@ ${frontmatter.yaml}
|
|
|
9362
9635
|
const target = $("#diff");
|
|
9363
9636
|
const shell = document.createElement("section");
|
|
9364
9637
|
shell.className = "gdp-repo-shell";
|
|
9365
|
-
const { wrap: targetPickerWrap, input: targetPicker } = createRefSelectorInput({
|
|
9366
|
-
id: "repo-ref",
|
|
9367
|
-
placeholder: "ref...",
|
|
9368
|
-
title: "repository ref",
|
|
9369
|
-
value: meta.ref || "worktree"
|
|
9370
|
-
});
|
|
9371
|
-
wireRefSelectorInput(targetPicker, (ref) => {
|
|
9372
|
-
setRoute(repoRoute(ref, ""));
|
|
9373
|
-
loadRepo();
|
|
9374
|
-
});
|
|
9375
9638
|
const toolbar = document.createElement("div");
|
|
9376
9639
|
toolbar.className = "gdp-file-detail-header gdp-repo-toolbar";
|
|
9377
|
-
|
|
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
|
+
}
|
|
9378
9657
|
shell.appendChild(toolbar);
|
|
9379
9658
|
const listCard = document.createElement("section");
|
|
9380
9659
|
listCard.className = "gdp-file-shell loaded gdp-repo-list-shell";
|
|
9381
9660
|
const listWrapper = document.createElement("div");
|
|
9382
9661
|
listWrapper.className = "d2h-file-wrapper";
|
|
9383
|
-
|
|
9384
|
-
listHeader.className = "d2h-file-header";
|
|
9385
|
-
const listName = document.createElement("div");
|
|
9386
|
-
listName.className = "d2h-file-name-wrapper";
|
|
9387
|
-
const listIcon = document.createElement("span");
|
|
9388
|
-
listIcon.className = "dir-icon";
|
|
9389
|
-
setFolderIcon(listIcon, false);
|
|
9390
|
-
const listTitle = document.createElement("span");
|
|
9391
|
-
listTitle.className = "d2h-file-name";
|
|
9392
|
-
listTitle.textContent = meta.path || meta.project || "Files";
|
|
9393
|
-
listName.append(listIcon, listTitle);
|
|
9394
|
-
listHeader.appendChild(listName);
|
|
9395
|
-
listHeader.appendChild(createOpenPathButton(meta.path || "", "directory", "open this folder in OS"));
|
|
9396
|
-
listWrapper.appendChild(listHeader);
|
|
9397
|
-
if (meta.upload_enabled && (meta.ref === "worktree" || meta.ref === "")) {
|
|
9662
|
+
if (meta.ref === "worktree" || meta.ref === "") {
|
|
9398
9663
|
listWrapper.appendChild(createRepoUploadPanel(meta.path || ""));
|
|
9399
9664
|
}
|
|
9400
9665
|
const sortHost = document.createElement("div");
|
package/web/index.html
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
<header id="global-header">
|
|
22
22
|
<a class="brand" href="/" aria-label="Repository home">
|
|
23
23
|
<img class="brand-icon" src="/favicon.png" alt="" width="20" height="20" />
|
|
24
|
-
<span class="title">
|
|
24
|
+
<span class="title" id="project-title">repository</span>
|
|
25
25
|
</a>
|
|
26
26
|
<nav class="app-menu" aria-label="Views">
|
|
27
27
|
<a class="app-menu-item active" data-route="repo" href="/">Repository</a>
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
<button id="theme" title="toggle theme">🌗</button>
|
|
33
33
|
<span id="status" class="status"></span>
|
|
34
34
|
<a class="global-help-link" data-route="help" href="/help">Help</a>
|
|
35
|
+
<span class="product-label" aria-hidden="true">code viewer</span>
|
|
35
36
|
</div>
|
|
36
37
|
</header>
|
|
37
38
|
<header id="topbar">
|
package/web/style.css
CHANGED
|
@@ -101,6 +101,7 @@ html, body {
|
|
|
101
101
|
margin: 0; padding: 0;
|
|
102
102
|
background: var(--bg); color: var(--fg);
|
|
103
103
|
--code-font-size: 12px;
|
|
104
|
+
--markdown-font-size: calc(var(--code-font-size) + 2px);
|
|
104
105
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans",
|
|
105
106
|
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
|
106
107
|
font-size: 14px;
|
|
@@ -282,9 +283,11 @@ body[data-code-font-size="xlarge"] { --code-font-size: 15px; }
|
|
|
282
283
|
.brand {
|
|
283
284
|
display: flex; align-items: center; gap: 9px;
|
|
284
285
|
font-weight: 600;
|
|
285
|
-
font-size:
|
|
286
|
+
font-size: 15px;
|
|
286
287
|
color: var(--fg);
|
|
287
|
-
|
|
288
|
+
min-width: 0;
|
|
289
|
+
max-width: min(34vw, 420px);
|
|
290
|
+
flex: 0 1 auto;
|
|
288
291
|
text-decoration: none;
|
|
289
292
|
}
|
|
290
293
|
.brand:hover { color: var(--fg); text-decoration: none; }
|
|
@@ -294,7 +297,12 @@ body[data-code-font-size="xlarge"] { --code-font-size: 15px; }
|
|
|
294
297
|
display: block;
|
|
295
298
|
border-radius: 5px;
|
|
296
299
|
}
|
|
297
|
-
.brand .title {
|
|
300
|
+
.brand .title {
|
|
301
|
+
min-width: 0;
|
|
302
|
+
overflow: hidden;
|
|
303
|
+
text-overflow: ellipsis;
|
|
304
|
+
white-space: nowrap;
|
|
305
|
+
}
|
|
298
306
|
|
|
299
307
|
.app-menu {
|
|
300
308
|
display: flex;
|
|
@@ -366,6 +374,13 @@ body[data-code-font-size="xlarge"] { --code-font-size: 15px; }
|
|
|
366
374
|
gap: 8px;
|
|
367
375
|
flex: 0 0 auto;
|
|
368
376
|
}
|
|
377
|
+
.product-label {
|
|
378
|
+
color: var(--fg-muted);
|
|
379
|
+
font-size: 12px;
|
|
380
|
+
font-weight: 600;
|
|
381
|
+
line-height: 1;
|
|
382
|
+
white-space: nowrap;
|
|
383
|
+
}
|
|
369
384
|
.global-actions #theme,
|
|
370
385
|
.global-icon-action {
|
|
371
386
|
display: inline-flex;
|
|
@@ -2298,6 +2313,12 @@ table.d2h-diff-table tr.gdp-diff-line-target > td:first-child {
|
|
|
2298
2313
|
.gdp-upload-panel.failed {
|
|
2299
2314
|
border-color: var(--danger);
|
|
2300
2315
|
}
|
|
2316
|
+
.gdp-upload-error {
|
|
2317
|
+
min-height: 18px;
|
|
2318
|
+
color: var(--danger);
|
|
2319
|
+
font-size: 12px;
|
|
2320
|
+
line-height: 18px;
|
|
2321
|
+
}
|
|
2301
2322
|
.gdp-upload-copy {
|
|
2302
2323
|
min-width: 0;
|
|
2303
2324
|
color: var(--fg-muted);
|
|
@@ -2542,6 +2563,7 @@ table.d2h-diff-table tr.gdp-diff-line-target > td:first-child {
|
|
|
2542
2563
|
display: flex;
|
|
2543
2564
|
align-items: center;
|
|
2544
2565
|
min-width: 0;
|
|
2566
|
+
flex: 0 1 auto;
|
|
2545
2567
|
gap: 4px;
|
|
2546
2568
|
padding-top: 2px;
|
|
2547
2569
|
overflow: hidden;
|
|
@@ -2595,6 +2617,9 @@ table.d2h-diff-table tr.gdp-diff-line-target > td:first-child {
|
|
|
2595
2617
|
flex-shrink: 0;
|
|
2596
2618
|
color: var(--danger);
|
|
2597
2619
|
}
|
|
2620
|
+
.gdp-file-detail-header .gdp-create-dir {
|
|
2621
|
+
flex-shrink: 0;
|
|
2622
|
+
}
|
|
2598
2623
|
.gdp-file-detail-meta {
|
|
2599
2624
|
display: flex;
|
|
2600
2625
|
align-items: center;
|
|
@@ -2634,7 +2659,7 @@ table.d2h-diff-table tr.gdp-diff-line-target > td:first-child {
|
|
|
2634
2659
|
padding: 24px 32px 40px;
|
|
2635
2660
|
color: var(--fg);
|
|
2636
2661
|
overflow-wrap: break-word;
|
|
2637
|
-
font-size:
|
|
2662
|
+
font-size: var(--markdown-font-size);
|
|
2638
2663
|
line-height: 1.75;
|
|
2639
2664
|
}
|
|
2640
2665
|
.gdp-html-preview {
|
|
@@ -2697,7 +2722,7 @@ table.d2h-diff-table tr.gdp-diff-line-target > td:first-child {
|
|
|
2697
2722
|
margin: 0;
|
|
2698
2723
|
}
|
|
2699
2724
|
.gdp-markdown-toc a {
|
|
2700
|
-
display:
|
|
2725
|
+
display: block;
|
|
2701
2726
|
min-height: 28px;
|
|
2702
2727
|
padding: 5px 10px 5px 12px;
|
|
2703
2728
|
border-left: 3px solid transparent;
|
|
@@ -2706,8 +2731,6 @@ table.d2h-diff-table tr.gdp-diff-line-target > td:first-child {
|
|
|
2706
2731
|
overflow: hidden;
|
|
2707
2732
|
overflow-wrap: anywhere;
|
|
2708
2733
|
text-decoration: none;
|
|
2709
|
-
-webkit-box-orient: vertical;
|
|
2710
|
-
-webkit-line-clamp: 2;
|
|
2711
2734
|
transition:
|
|
2712
2735
|
background-color 0.06s ease,
|
|
2713
2736
|
border-color 0.06s ease,
|
|
@@ -2741,6 +2764,14 @@ table.d2h-diff-table tr.gdp-diff-line-target > td:first-child {
|
|
|
2741
2764
|
font-weight: 600;
|
|
2742
2765
|
}
|
|
2743
2766
|
.gdp-markdown-preview h1,
|
|
2767
|
+
.gdp-markdown-preview h2,
|
|
2768
|
+
.gdp-markdown-preview h3,
|
|
2769
|
+
.gdp-markdown-preview h4,
|
|
2770
|
+
.gdp-markdown-preview h5,
|
|
2771
|
+
.gdp-markdown-preview h6 {
|
|
2772
|
+
scroll-margin-top: calc(var(--global-header-h) + 120px);
|
|
2773
|
+
}
|
|
2774
|
+
.gdp-markdown-preview h1,
|
|
2744
2775
|
.gdp-markdown-preview h2 {
|
|
2745
2776
|
padding-bottom: 0.3em;
|
|
2746
2777
|
border-bottom: 1px solid var(--border-muted);
|
|
@@ -3141,9 +3172,6 @@ body.gdp-file-detail-page #empty {
|
|
|
3141
3172
|
padding-left: 0;
|
|
3142
3173
|
padding-right: 0;
|
|
3143
3174
|
}
|
|
3144
|
-
.gdp-repo-breadcrumb {
|
|
3145
|
-
flex: 1;
|
|
3146
|
-
}
|
|
3147
3175
|
.gdp-repo-breadcrumb button {
|
|
3148
3176
|
max-width: 260px;
|
|
3149
3177
|
border: 0;
|
|
@@ -3324,6 +3352,29 @@ body.gdp-file-detail-page #empty {
|
|
|
3324
3352
|
line-height: 1.5;
|
|
3325
3353
|
overflow-wrap: anywhere;
|
|
3326
3354
|
}
|
|
3355
|
+
.gdp-create-dir-input {
|
|
3356
|
+
display: block;
|
|
3357
|
+
width: 100%;
|
|
3358
|
+
height: 32px;
|
|
3359
|
+
margin-top: 10px;
|
|
3360
|
+
padding: 0 10px;
|
|
3361
|
+
border: 1px solid var(--border);
|
|
3362
|
+
border-radius: 6px;
|
|
3363
|
+
background: var(--bg);
|
|
3364
|
+
color: var(--fg);
|
|
3365
|
+
font: inherit;
|
|
3366
|
+
}
|
|
3367
|
+
.gdp-create-dir-input:focus {
|
|
3368
|
+
outline: 2px solid var(--accent-subtle);
|
|
3369
|
+
border-color: var(--accent);
|
|
3370
|
+
}
|
|
3371
|
+
.gdp-create-dir-error {
|
|
3372
|
+
min-height: 18px;
|
|
3373
|
+
margin-top: 6px;
|
|
3374
|
+
color: var(--danger);
|
|
3375
|
+
font-size: 12px;
|
|
3376
|
+
line-height: 18px;
|
|
3377
|
+
}
|
|
3327
3378
|
.gdp-trash-dialog-actions {
|
|
3328
3379
|
display: flex;
|
|
3329
3380
|
justify-content: flex-end;
|