@youtyan/code-viewer 0.1.11 → 0.1.13
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/package.json +1 -1
- package/web/app.js +1416 -56
- package/web/index.html +1 -0
- package/web/style.css +373 -0
- package/web-src/routes.ts +59 -8
- package/web-src/server/preview.ts +109 -2
- package/web-src/server/search.ts +101 -0
- package/web-src/types.ts +24 -0
package/web/app.js
CHANGED
|
@@ -131,6 +131,309 @@
|
|
|
131
131
|
};
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
// web-src/focus-scope.ts
|
|
135
|
+
function isEditableKeyTarget(target) {
|
|
136
|
+
if (!target)
|
|
137
|
+
return false;
|
|
138
|
+
const tag = target.tagName;
|
|
139
|
+
return tag === "INPUT" || tag === "TEXTAREA" || target.closest('[contenteditable="true"]') != null;
|
|
140
|
+
}
|
|
141
|
+
function keymapScope(target) {
|
|
142
|
+
if (target?.closest("#content"))
|
|
143
|
+
return "main";
|
|
144
|
+
if (target?.closest("#sidebar"))
|
|
145
|
+
return "sidebar";
|
|
146
|
+
return "global";
|
|
147
|
+
}
|
|
148
|
+
function prepareKeyboardPanels(doc = document) {
|
|
149
|
+
const sidebar = doc.querySelector("#sidebar");
|
|
150
|
+
const content = doc.querySelector("#content");
|
|
151
|
+
if (sidebar)
|
|
152
|
+
sidebar.tabIndex = -1;
|
|
153
|
+
if (content)
|
|
154
|
+
content.tabIndex = -1;
|
|
155
|
+
}
|
|
156
|
+
function getPanelFocusScope(doc = document) {
|
|
157
|
+
const scope = doc.body?.dataset.focusScope;
|
|
158
|
+
return scope === "sidebar" || scope === "main" ? scope : null;
|
|
159
|
+
}
|
|
160
|
+
function setPanelFocusScope(scope, doc = document) {
|
|
161
|
+
if (!doc.body)
|
|
162
|
+
return;
|
|
163
|
+
if (scope)
|
|
164
|
+
doc.body.dataset.focusScope = scope;
|
|
165
|
+
else
|
|
166
|
+
delete doc.body.dataset.focusScope;
|
|
167
|
+
}
|
|
168
|
+
function restorePanelFocusScope(scope, doc = document) {
|
|
169
|
+
if (scope === "sidebar")
|
|
170
|
+
focusSidebarPanel(doc);
|
|
171
|
+
else if (scope === "main")
|
|
172
|
+
focusMainPanel(doc);
|
|
173
|
+
else
|
|
174
|
+
setPanelFocusScope(null, doc);
|
|
175
|
+
}
|
|
176
|
+
function focusSidebarPanel(doc = document) {
|
|
177
|
+
const active = doc.querySelector("#filelist li.active[data-path], #filelist .tree-dir.active[data-dirpath]");
|
|
178
|
+
const sidebar = doc.querySelector("#sidebar");
|
|
179
|
+
(active || sidebar)?.focus({ preventScroll: true });
|
|
180
|
+
setPanelFocusScope("sidebar", doc);
|
|
181
|
+
}
|
|
182
|
+
function focusMainPanel(doc = document) {
|
|
183
|
+
doc.querySelector("#content")?.focus({ preventScroll: true });
|
|
184
|
+
setPanelFocusScope("main", doc);
|
|
185
|
+
}
|
|
186
|
+
function findMainScrollTarget(doc = document) {
|
|
187
|
+
const active = doc.activeElement;
|
|
188
|
+
const activeScroller = active?.closest("#content .gdp-source-virtual-scroller");
|
|
189
|
+
if (activeScroller && activeScroller.offsetParent !== null)
|
|
190
|
+
return activeScroller;
|
|
191
|
+
const sourceScroller = doc.querySelector("#content .gdp-source-virtual-scroller");
|
|
192
|
+
if (sourceScroller && sourceScroller.offsetParent !== null)
|
|
193
|
+
return sourceScroller;
|
|
194
|
+
const content = doc.querySelector("#content");
|
|
195
|
+
if (!content || content.offsetParent === null)
|
|
196
|
+
return null;
|
|
197
|
+
const isScrollable = (item) => {
|
|
198
|
+
if (item.offsetParent === null)
|
|
199
|
+
return false;
|
|
200
|
+
const style = doc.defaultView?.getComputedStyle(item);
|
|
201
|
+
return !!style && /(auto|scroll)/.test(style.overflowY) && item.scrollHeight > item.clientHeight;
|
|
202
|
+
};
|
|
203
|
+
const preferred = Array.from(content.querySelectorAll(".gdp-source-viewer, .gdp-markdown-layout, .gdp-markdown-preview, .d2h-files-diff, .d2h-file-diff"));
|
|
204
|
+
const scrollable = preferred.find(isScrollable) || (isScrollable(content) ? content : null) || Array.from(content.querySelectorAll("*")).find(isScrollable);
|
|
205
|
+
return scrollable || doc.scrollingElement;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// web-src/fuzzy-search.ts
|
|
209
|
+
function basenameStart(path) {
|
|
210
|
+
const slash = path.lastIndexOf("/");
|
|
211
|
+
return slash < 0 ? 0 : slash + 1;
|
|
212
|
+
}
|
|
213
|
+
function isBoundary(path, index) {
|
|
214
|
+
if (index <= 0)
|
|
215
|
+
return true;
|
|
216
|
+
const prev = path[index - 1];
|
|
217
|
+
return prev === "/" || prev === "-" || prev === "_" || prev === "." || prev === " ";
|
|
218
|
+
}
|
|
219
|
+
function toRanges(indices) {
|
|
220
|
+
const ranges = [];
|
|
221
|
+
for (const index of indices) {
|
|
222
|
+
const last = ranges[ranges.length - 1];
|
|
223
|
+
if (last && last.end === index) {
|
|
224
|
+
last.end = index + 1;
|
|
225
|
+
} else {
|
|
226
|
+
ranges.push({ start: index, end: index + 1 });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return ranges;
|
|
230
|
+
}
|
|
231
|
+
function fuzzyMatchPath(query, path) {
|
|
232
|
+
const q = query.trim().toLowerCase();
|
|
233
|
+
if (!q)
|
|
234
|
+
return { score: 0, ranges: [] };
|
|
235
|
+
const lowerPath = path.toLowerCase();
|
|
236
|
+
const baseStart = basenameStart(path);
|
|
237
|
+
const indices = [];
|
|
238
|
+
let from = 0;
|
|
239
|
+
let score = 0;
|
|
240
|
+
for (const ch of q) {
|
|
241
|
+
const index = lowerPath.indexOf(ch, from);
|
|
242
|
+
if (index < 0)
|
|
243
|
+
return null;
|
|
244
|
+
indices.push(index);
|
|
245
|
+
score += 10;
|
|
246
|
+
if (index >= baseStart)
|
|
247
|
+
score += 8;
|
|
248
|
+
if (isBoundary(path, index))
|
|
249
|
+
score += 6;
|
|
250
|
+
const prev = indices[indices.length - 2];
|
|
251
|
+
if (prev != null && prev + 1 === index)
|
|
252
|
+
score += 12;
|
|
253
|
+
from = index + 1;
|
|
254
|
+
}
|
|
255
|
+
const first = indices[0] || 0;
|
|
256
|
+
score -= Math.min(first, 40);
|
|
257
|
+
if (indices[0] >= baseStart)
|
|
258
|
+
score += 20;
|
|
259
|
+
const basename = lowerPath.slice(baseStart);
|
|
260
|
+
if (basename.startsWith(q))
|
|
261
|
+
score += 30;
|
|
262
|
+
if (basename === q || basename.startsWith(q + "."))
|
|
263
|
+
score += 25;
|
|
264
|
+
if (lowerPath.endsWith(q))
|
|
265
|
+
score += 15;
|
|
266
|
+
return { score, ranges: toRanges(indices) };
|
|
267
|
+
}
|
|
268
|
+
function rankFuzzyPaths(query, items) {
|
|
269
|
+
return items.map((item) => {
|
|
270
|
+
const match = fuzzyMatchPath(query, item.path);
|
|
271
|
+
return match ? { item, score: match.score, ranges: match.ranges } : null;
|
|
272
|
+
}).filter((item) => item !== null).sort((a, b) => b.score - a.score || a.item.path.localeCompare(b.item.path));
|
|
273
|
+
}
|
|
274
|
+
function isGlobPathQuery(query) {
|
|
275
|
+
return /[*?]/.test(query.trim());
|
|
276
|
+
}
|
|
277
|
+
function escapeRegexChar(ch) {
|
|
278
|
+
return /[\\^$+?.()|{}]/.test(ch) ? "\\" + ch : ch;
|
|
279
|
+
}
|
|
280
|
+
function globToRegExp(query) {
|
|
281
|
+
const pattern = query.trim();
|
|
282
|
+
if (!pattern)
|
|
283
|
+
return null;
|
|
284
|
+
let source = "^";
|
|
285
|
+
for (let i = 0;i < pattern.length; i++) {
|
|
286
|
+
const ch = pattern[i];
|
|
287
|
+
if (ch === "*") {
|
|
288
|
+
if (pattern[i + 1] === "*") {
|
|
289
|
+
source += ".*";
|
|
290
|
+
i++;
|
|
291
|
+
} else {
|
|
292
|
+
source += "[^/]*";
|
|
293
|
+
}
|
|
294
|
+
} else if (ch === "?") {
|
|
295
|
+
source += "[^/]";
|
|
296
|
+
} else if (ch === "[") {
|
|
297
|
+
const close = pattern.indexOf("]", i + 1);
|
|
298
|
+
if (close < 0) {
|
|
299
|
+
source += "\\[";
|
|
300
|
+
} else {
|
|
301
|
+
const body = pattern.slice(i + 1, close).replace(/\\/g, "\\\\");
|
|
302
|
+
source += "[" + body + "]";
|
|
303
|
+
i = close;
|
|
304
|
+
}
|
|
305
|
+
} else {
|
|
306
|
+
source += escapeRegexChar(ch);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
source += "$";
|
|
310
|
+
try {
|
|
311
|
+
return new RegExp(source, "i");
|
|
312
|
+
} catch {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function globMatchPath(query, path) {
|
|
317
|
+
const regex = globToRegExp(query);
|
|
318
|
+
const baseStart = basenameStart(path);
|
|
319
|
+
const basename = path.slice(baseStart);
|
|
320
|
+
if (!regex || !regex.test(path) && (query.includes("/") || !regex.test(basename)))
|
|
321
|
+
return null;
|
|
322
|
+
const literal = query.replace(/[*?[\]]+/g, " ").trim().split(/\s+/).filter(Boolean);
|
|
323
|
+
const ranges = [];
|
|
324
|
+
const lowerPath = path.toLowerCase();
|
|
325
|
+
for (const part of literal) {
|
|
326
|
+
const start = lowerPath.indexOf(part.toLowerCase());
|
|
327
|
+
if (start >= 0)
|
|
328
|
+
ranges.push({ start, end: start + part.length });
|
|
329
|
+
}
|
|
330
|
+
ranges.sort((a, b) => a.start - b.start || a.end - b.end);
|
|
331
|
+
const mergedRanges = [];
|
|
332
|
+
for (const range of ranges) {
|
|
333
|
+
const last = mergedRanges[mergedRanges.length - 1];
|
|
334
|
+
if (last && last.end >= range.start) {
|
|
335
|
+
last.end = Math.max(last.end, range.end);
|
|
336
|
+
} else {
|
|
337
|
+
mergedRanges.push({ ...range });
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const score = 1000 - Math.min(path.length, 200) + (path.slice(baseStart).toLowerCase().endsWith(query.replace(/^\*+/, "").toLowerCase()) ? 50 : 0);
|
|
341
|
+
return { score, ranges: mergedRanges };
|
|
342
|
+
}
|
|
343
|
+
function rankPathMatches(query, items) {
|
|
344
|
+
if (isGlobPathQuery(query)) {
|
|
345
|
+
return items.map((item) => {
|
|
346
|
+
const match = globMatchPath(query, item.path);
|
|
347
|
+
return match ? { item, score: match.score, ranges: match.ranges, mode: "glob" } : null;
|
|
348
|
+
}).filter((item) => item !== null).sort((a, b) => b.score - a.score || a.item.path.localeCompare(b.item.path));
|
|
349
|
+
}
|
|
350
|
+
return rankFuzzyPaths(query, items).map((item) => ({ ...item, mode: "fuzzy" }));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// web-src/keymap.ts
|
|
354
|
+
var DEFAULT_KEY_BINDINGS = [
|
|
355
|
+
{ action: "open-file-palette", key: "k", ctrl: true, allowEditable: true, allowPaletteOpen: true },
|
|
356
|
+
{ action: "open-file-palette", key: "k", meta: true, allowEditable: true, allowPaletteOpen: true },
|
|
357
|
+
{ action: "open-grep-palette", key: "g", ctrl: true, allowEditable: true, allowPaletteOpen: true },
|
|
358
|
+
{ action: "open-grep-palette", key: "g", meta: true, allowEditable: true, allowPaletteOpen: true },
|
|
359
|
+
{ action: "focus-file-filter", key: "/" },
|
|
360
|
+
{ action: "focus-sidebar", key: "h", ctrl: true },
|
|
361
|
+
{ action: "focus-main", key: "l", ctrl: true },
|
|
362
|
+
{ action: "cancel-source-load", key: "escape", requires: { lightboxClosed: true } },
|
|
363
|
+
{ action: "open-sidebar-item", key: "enter", scope: "sidebar" },
|
|
364
|
+
{ action: "open-sidebar-item", key: "enter", scope: "global" },
|
|
365
|
+
{ action: "sidebar-next", key: "j", scope: "sidebar" },
|
|
366
|
+
{ action: "sidebar-next", key: "j", scope: "global" },
|
|
367
|
+
{ action: "sidebar-previous", key: "k", scope: "sidebar" },
|
|
368
|
+
{ action: "sidebar-previous", key: "k", scope: "global" },
|
|
369
|
+
{ action: "sidebar-page-down", key: "d", scope: "sidebar", ctrl: true },
|
|
370
|
+
{ action: "sidebar-page-down", key: "d", scope: "global", ctrl: true },
|
|
371
|
+
{ action: "sidebar-page-up", key: "u", scope: "sidebar", ctrl: true },
|
|
372
|
+
{ action: "sidebar-page-up", key: "u", scope: "global", ctrl: true },
|
|
373
|
+
{ action: "sidebar-expand", key: "l", scope: "sidebar" },
|
|
374
|
+
{ action: "sidebar-expand", key: "l", scope: "global" },
|
|
375
|
+
{ action: "sidebar-collapse", key: "h", scope: "sidebar" },
|
|
376
|
+
{ action: "sidebar-collapse", key: "h", scope: "global" },
|
|
377
|
+
{ action: "scroll-main-down", key: "j", scope: "main" },
|
|
378
|
+
{ action: "scroll-main-up", key: "k", scope: "main" },
|
|
379
|
+
{ action: "scroll-main-page-down", key: "d", scope: "main", ctrl: true },
|
|
380
|
+
{ action: "scroll-main-page-up", key: "u", scope: "main", ctrl: true },
|
|
381
|
+
{ action: "tab-preview", key: "p", scope: "main", pendingG: true },
|
|
382
|
+
{ action: "tab-code", key: "c", scope: "main", pendingG: true },
|
|
383
|
+
{ action: "goto-top", key: "g", pendingG: true },
|
|
384
|
+
{ action: "goto-bottom", key: "g", shift: true, pendingG: true },
|
|
385
|
+
{ action: "goto-bottom", key: "g", shift: true },
|
|
386
|
+
{ action: "start-g-sequence", key: "g", scope: "sidebar" },
|
|
387
|
+
{ action: "start-g-sequence", key: "g", scope: "main" },
|
|
388
|
+
{ action: "layout-unified", key: "u" },
|
|
389
|
+
{ action: "layout-split", key: "s" },
|
|
390
|
+
{ action: "toggle-theme", key: "t" }
|
|
391
|
+
];
|
|
392
|
+
function resolveKeymapAction(event, context) {
|
|
393
|
+
const key = event.key.toLowerCase();
|
|
394
|
+
if (context.composing)
|
|
395
|
+
return null;
|
|
396
|
+
for (const binding of DEFAULT_KEY_BINDINGS) {
|
|
397
|
+
if (binding.key !== key)
|
|
398
|
+
continue;
|
|
399
|
+
if (binding.requires?.lightboxClosed && context.lightboxOpen)
|
|
400
|
+
continue;
|
|
401
|
+
if (binding.scope && binding.scope !== context.scope)
|
|
402
|
+
continue;
|
|
403
|
+
if (!!binding.pendingG !== !!context.pendingG)
|
|
404
|
+
continue;
|
|
405
|
+
if (context.paletteOpen && !binding.allowPaletteOpen)
|
|
406
|
+
continue;
|
|
407
|
+
if (context.editable && !binding.allowEditable)
|
|
408
|
+
continue;
|
|
409
|
+
if (!!binding.ctrl !== !!event.ctrlKey)
|
|
410
|
+
continue;
|
|
411
|
+
if (!!binding.meta !== !!event.metaKey)
|
|
412
|
+
continue;
|
|
413
|
+
if (!!binding.alt !== !!event.altKey)
|
|
414
|
+
continue;
|
|
415
|
+
if (!!binding.shift !== !!event.shiftKey)
|
|
416
|
+
continue;
|
|
417
|
+
if (!binding.ctrl && !binding.meta && !binding.alt && !binding.shift && (event.ctrlKey || event.metaKey || event.altKey || event.shiftKey))
|
|
418
|
+
continue;
|
|
419
|
+
return binding.action;
|
|
420
|
+
}
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// web-src/search-palette.ts
|
|
425
|
+
var PALETTE_RESULT_LIMIT = 50;
|
|
426
|
+
function limitPaletteResults(items) {
|
|
427
|
+
return items.slice(0, PALETTE_RESULT_LIMIT);
|
|
428
|
+
}
|
|
429
|
+
function movePaletteSelection(index, count, direction) {
|
|
430
|
+
if (count <= 0)
|
|
431
|
+
return -1;
|
|
432
|
+
if (index < 0)
|
|
433
|
+
return direction > 0 ? 0 : count - 1;
|
|
434
|
+
return (index + direction + count) % count;
|
|
435
|
+
}
|
|
436
|
+
|
|
134
437
|
// web-src/catch-up.ts
|
|
135
438
|
function shouldCatchUpDiff(route) {
|
|
136
439
|
return route.screen !== "repo" && !(route.screen === "file" && route.view === "blob");
|
|
@@ -160,6 +463,24 @@
|
|
|
160
463
|
to: raw.slice(sep + 2) || fallback.to
|
|
161
464
|
};
|
|
162
465
|
}
|
|
466
|
+
function parseLineTarget(value) {
|
|
467
|
+
const raw = value || "";
|
|
468
|
+
const range = /^(\d+)-(\d+)$/.exec(raw);
|
|
469
|
+
if (range) {
|
|
470
|
+
const a = Number(range[1]);
|
|
471
|
+
const b = Number(range[2]);
|
|
472
|
+
const start = Math.min(a, b);
|
|
473
|
+
const end = Math.max(a, b);
|
|
474
|
+
if (start > 0)
|
|
475
|
+
return { start, end };
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
const line = Number(raw);
|
|
479
|
+
return Number.isInteger(line) && line > 0 ? line : undefined;
|
|
480
|
+
}
|
|
481
|
+
function formatLineTarget(line) {
|
|
482
|
+
return typeof line === "number" ? String(line) : line.start + "-" + line.end;
|
|
483
|
+
}
|
|
163
484
|
function parseRoute(pathname, search, fallbackRange) {
|
|
164
485
|
const params = new URLSearchParams(search);
|
|
165
486
|
const legacyRange = parseLegacyRange(params.get("range"), fallbackRange);
|
|
@@ -178,15 +499,28 @@
|
|
|
178
499
|
};
|
|
179
500
|
case "/todif":
|
|
180
501
|
case "/todiff":
|
|
181
|
-
return {
|
|
502
|
+
return {
|
|
503
|
+
screen: "diff",
|
|
504
|
+
range,
|
|
505
|
+
...params.get("path") ? { path: params.get("path") || "" } : {},
|
|
506
|
+
...parseLineTarget(params.get("line")) ? { line: parseLineTarget(params.get("line")) } : {}
|
|
507
|
+
};
|
|
182
508
|
case "/file": {
|
|
183
509
|
const path = params.get("path") || "";
|
|
184
510
|
const target = params.get("target") || "";
|
|
185
511
|
const ref = target || params.get("ref") || "worktree";
|
|
512
|
+
const line = parseLineTarget(params.get("line"));
|
|
186
513
|
if (!path)
|
|
187
514
|
return { screen: "unknown", reason: "missing-path", rawPathname: pathname, rawSearch: search, range };
|
|
188
|
-
return { screen: "file", path, ref, range, view: target ? "blob" : "detail" };
|
|
515
|
+
return { screen: "file", path, ref, range, view: target ? "blob" : "detail", ...line ? { line } : {} };
|
|
189
516
|
}
|
|
517
|
+
case "/help":
|
|
518
|
+
return {
|
|
519
|
+
screen: "help",
|
|
520
|
+
range,
|
|
521
|
+
lang: params.get("lang") || "en",
|
|
522
|
+
section: params.get("section") || "keybindings"
|
|
523
|
+
};
|
|
190
524
|
default:
|
|
191
525
|
return { screen: "unknown", reason: "unknown-pathname", rawPathname: pathname, rawSearch: search, range };
|
|
192
526
|
}
|
|
@@ -204,11 +538,20 @@
|
|
|
204
538
|
}
|
|
205
539
|
case "file":
|
|
206
540
|
if (route.view === "blob") {
|
|
207
|
-
return "/file?path=" + encodeURIComponent(route.path) + "&target=" + encodeURIComponent(route.ref || "worktree");
|
|
541
|
+
return "/file?path=" + encodeURIComponent(route.path) + "&target=" + encodeURIComponent(route.ref || "worktree") + (route.line ? "&line=" + encodeURIComponent(formatLineTarget(route.line)) : "");
|
|
208
542
|
}
|
|
209
|
-
return "/file?path=" + encodeURIComponent(route.path) + "&ref=" + encodeURIComponent(route.ref || "worktree") + "&from=" + encodeURIComponent(route.range.from || "") + "&to=" + encodeURIComponent(route.range.to || "worktree");
|
|
543
|
+
return "/file?path=" + encodeURIComponent(route.path) + "&ref=" + encodeURIComponent(route.ref || "worktree") + "&from=" + encodeURIComponent(route.range.from || "") + "&to=" + encodeURIComponent(route.range.to || "worktree") + (route.line ? "&line=" + encodeURIComponent(formatLineTarget(route.line)) : "");
|
|
210
544
|
case "diff":
|
|
211
|
-
return "/todif?from=" + encodeURIComponent(route.range.from || "") + "&to=" + encodeURIComponent(route.range.to || "worktree");
|
|
545
|
+
return "/todif?from=" + encodeURIComponent(route.range.from || "") + "&to=" + encodeURIComponent(route.range.to || "worktree") + (route.path ? "&path=" + encodeURIComponent(route.path) : "") + (route.line ? "&line=" + encodeURIComponent(formatLineTarget(route.line)) : "");
|
|
546
|
+
case "help": {
|
|
547
|
+
const params = new URLSearchParams;
|
|
548
|
+
if (route.lang && route.lang !== "en")
|
|
549
|
+
params.set("lang", route.lang);
|
|
550
|
+
if (route.section && route.section !== "keybindings")
|
|
551
|
+
params.set("section", route.section);
|
|
552
|
+
const qs = params.toString();
|
|
553
|
+
return "/help" + (qs ? "?" + qs : "");
|
|
554
|
+
}
|
|
212
555
|
case "unknown":
|
|
213
556
|
return "/todif?from=" + encodeURIComponent(route.range.from || "") + "&to=" + encodeURIComponent(route.range.to || "worktree");
|
|
214
557
|
default:
|
|
@@ -6002,7 +6345,29 @@
|
|
|
6002
6345
|
return markdown;
|
|
6003
6346
|
}
|
|
6004
6347
|
function renderMarkdownHtml(textValue, target, highlighter, signal) {
|
|
6005
|
-
|
|
6348
|
+
const md = createMarkdownIt(target, highlighter, signal);
|
|
6349
|
+
const frontmatter = splitYamlFrontmatter(textValue);
|
|
6350
|
+
if (!frontmatter)
|
|
6351
|
+
return md.render(textValue);
|
|
6352
|
+
return '<div class="gdp-markdown-frontmatter" data-gdp-frontmatter="yaml">' + md.render("```yaml\n" + frontmatter.yaml + "\n```\n") + "</div>" + md.render(frontmatter.body);
|
|
6353
|
+
}
|
|
6354
|
+
function splitYamlFrontmatter(textValue) {
|
|
6355
|
+
if (!textValue.startsWith(`---
|
|
6356
|
+
`) && !textValue.startsWith(`---\r
|
|
6357
|
+
`))
|
|
6358
|
+
return null;
|
|
6359
|
+
const newline2 = textValue.startsWith(`---\r
|
|
6360
|
+
`) ? `\r
|
|
6361
|
+
` : `
|
|
6362
|
+
`;
|
|
6363
|
+
const start = 3 + newline2.length;
|
|
6364
|
+
const closing = textValue.indexOf(newline2 + "---" + newline2, start);
|
|
6365
|
+
if (closing < 0)
|
|
6366
|
+
return null;
|
|
6367
|
+
return {
|
|
6368
|
+
yaml: textValue.slice(start, closing),
|
|
6369
|
+
body: textValue.slice(closing + newline2.length + 3 + newline2.length)
|
|
6370
|
+
};
|
|
6006
6371
|
}
|
|
6007
6372
|
async function loadMarkdownHighlighter() {
|
|
6008
6373
|
if (!shikiPromise) {
|
|
@@ -6399,6 +6764,177 @@
|
|
|
6399
6764
|
let REPO_SIDEBAR_REF = null;
|
|
6400
6765
|
let REPO_SIDEBAR_LOAD_REF = null;
|
|
6401
6766
|
let REPO_SIDEBAR_LOAD = null;
|
|
6767
|
+
let PENDING_G_SCOPE = null;
|
|
6768
|
+
let PENDING_G_UNTIL = 0;
|
|
6769
|
+
let SOURCE_CURSOR = null;
|
|
6770
|
+
const SOURCE_CURSOR_TOTALS = new Map;
|
|
6771
|
+
const HELP_LANGUAGES = ["en", "ja"];
|
|
6772
|
+
const HELP_SECTIONS = ["keybindings"];
|
|
6773
|
+
const HELP_CONTENT = {
|
|
6774
|
+
en: {
|
|
6775
|
+
languageLabel: "Language",
|
|
6776
|
+
title: "Help",
|
|
6777
|
+
sections: {
|
|
6778
|
+
keybindings: {
|
|
6779
|
+
nav: "Keybindings",
|
|
6780
|
+
title: "Keyboard Shortcuts",
|
|
6781
|
+
intro: "Use these shortcuts to move between panels and navigate files without leaving the keyboard.",
|
|
6782
|
+
groups: [
|
|
6783
|
+
{ title: "Global", rows: [["Ctrl+K", "Open file palette"], ["Ctrl+G", "Open grep palette"], ["/", "Focus file filter"], ["t", "Toggle theme"]] },
|
|
6784
|
+
{ title: "Panels", rows: [["Ctrl+H", "Focus sidebar"], ["Ctrl+L", "Focus main panel"]] },
|
|
6785
|
+
{ title: "Sidebar", rows: [["j / k", "Move selection down / up"], ["Ctrl+D / Ctrl+U", "Move selection by half a page"], ["gg / Shift+G", "Move to top / bottom"], ["Enter", "Open selected item"], ["h / l", "Collapse / expand directory"]] },
|
|
6786
|
+
{ title: "Main Panel", rows: [["j / k", "Move code cursor down / up"], ["Ctrl+D / Ctrl+U", "Move code cursor by half a page"], ["gg / Shift+G", "Move code cursor to top / bottom"], ["gp / gc", "Switch to Preview / Code tab"]] }
|
|
6787
|
+
]
|
|
6788
|
+
}
|
|
6789
|
+
}
|
|
6790
|
+
},
|
|
6791
|
+
ja: {
|
|
6792
|
+
languageLabel: "言語",
|
|
6793
|
+
title: "ヘルプ",
|
|
6794
|
+
sections: {
|
|
6795
|
+
keybindings: {
|
|
6796
|
+
nav: "キーバインド",
|
|
6797
|
+
title: "キーバインド",
|
|
6798
|
+
intro: "キーボードだけでパネル移動、ファイル選択、スクロールを行うためのショートカットです。",
|
|
6799
|
+
groups: [
|
|
6800
|
+
{ title: "グローバル", rows: [["Ctrl+K", "ファイルパレットを開く"], ["Ctrl+G", "grep パレットを開く"], ["/", "ファイルフィルターへフォーカス"], ["t", "テーマ切り替え"]] },
|
|
6801
|
+
{ title: "パネル", rows: [["Ctrl+H", "サイドバーへフォーカス"], ["Ctrl+L", "メインパネルへフォーカス"]] },
|
|
6802
|
+
{ title: "サイドバー", rows: [["j / k", "選択を下 / 上へ移動"], ["Ctrl+D / Ctrl+U", "半ページ分選択を移動"], ["gg / Shift+G", "先頭 / 末尾へ移動"], ["Enter", "選択項目を開く"], ["h / l", "ディレクトリを閉じる / 開く"]] },
|
|
6803
|
+
{ title: "メインパネル", rows: [["j / k", "コードカーソルを下 / 上へ移動"], ["Ctrl+D / Ctrl+U", "コードカーソルを半ページ分移動"], ["gg / Shift+G", "コードカーソルを先頭 / 末尾へ移動"], ["gp / gc", "Preview / Code タブへ切り替え"]] }
|
|
6804
|
+
]
|
|
6805
|
+
}
|
|
6806
|
+
}
|
|
6807
|
+
}
|
|
6808
|
+
};
|
|
6809
|
+
function sourceLineScrollAmount() {
|
|
6810
|
+
const virtualRow = Array.from(document.querySelectorAll("#content .gdp-source-virtual-row")).find((item) => item.offsetParent !== null);
|
|
6811
|
+
if (virtualRow)
|
|
6812
|
+
return virtualRow.getBoundingClientRect().height || VIRTUAL_SOURCE_ROW_HEIGHT;
|
|
6813
|
+
const sourceRow = Array.from(document.querySelectorAll("#content .gdp-source-table tr")).find((item) => item.offsetParent !== null);
|
|
6814
|
+
if (sourceRow)
|
|
6815
|
+
return sourceRow.getBoundingClientRect().height || 20;
|
|
6816
|
+
const preview = document.querySelector("#content .gdp-markdown-preview:not([hidden])");
|
|
6817
|
+
const lineHeight = Number.parseFloat(getComputedStyle(preview || document.body).lineHeight);
|
|
6818
|
+
return Number.isFinite(lineHeight) && lineHeight > 0 ? lineHeight : 20;
|
|
6819
|
+
}
|
|
6820
|
+
function hasVisibleSourceCodeSurface() {
|
|
6821
|
+
return Array.from(document.querySelectorAll("#content .gdp-source-virtual-scroller, #content .gdp-source-table")).some((item) => item.offsetParent !== null);
|
|
6822
|
+
}
|
|
6823
|
+
function sourceCursorKey(target) {
|
|
6824
|
+
return target.ref + "\x00" + target.path;
|
|
6825
|
+
}
|
|
6826
|
+
function sourceCursorMatches(target, line) {
|
|
6827
|
+
return !!SOURCE_CURSOR && sourceTargetsEqual(SOURCE_CURSOR.target, target) && SOURCE_CURSOR.line === line;
|
|
6828
|
+
}
|
|
6829
|
+
function syncSourceCursorRows(target) {
|
|
6830
|
+
document.querySelectorAll("#content [data-line]").forEach((row) => {
|
|
6831
|
+
const line = Number(row.dataset.line || "0");
|
|
6832
|
+
row.classList.toggle("gdp-source-cursor", sourceCursorMatches(target, line));
|
|
6833
|
+
});
|
|
6834
|
+
}
|
|
6835
|
+
function visibleSourceLineFallback() {
|
|
6836
|
+
const scroller = findMainScrollTarget();
|
|
6837
|
+
if (scroller)
|
|
6838
|
+
return Math.max(1, Math.floor(scroller.scrollTop / VIRTUAL_SOURCE_ROW_HEIGHT) + 1);
|
|
6839
|
+
const rows = $$("#content .gdp-source-table tr[data-line]");
|
|
6840
|
+
const contentTop = document.querySelector("#content")?.getBoundingClientRect().top ?? 0;
|
|
6841
|
+
const row = rows.find((item) => item.getBoundingClientRect().bottom >= Math.max(0, contentTop));
|
|
6842
|
+
return Math.max(1, Number(row?.dataset.line || "1"));
|
|
6843
|
+
}
|
|
6844
|
+
function ensureSourceCursor(target) {
|
|
6845
|
+
if (SOURCE_CURSOR && sourceTargetsEqual(SOURCE_CURSOR.target, target))
|
|
6846
|
+
return SOURCE_CURSOR;
|
|
6847
|
+
const routeLine = lineTargetStart(currentSourceLineTarget(target));
|
|
6848
|
+
SOURCE_CURSOR = { target, line: routeLine || visibleSourceLineFallback() };
|
|
6849
|
+
syncSourceCursorRows(target);
|
|
6850
|
+
return SOURCE_CURSOR;
|
|
6851
|
+
}
|
|
6852
|
+
function resetSourceCursorForTarget(target, totalLines) {
|
|
6853
|
+
const routeLine = lineTargetStart(currentSourceLineTarget(target));
|
|
6854
|
+
SOURCE_CURSOR = { target, line: Math.max(1, Math.min(totalLines, routeLine || 1)) };
|
|
6855
|
+
}
|
|
6856
|
+
function scrollSourceCursorIntoView(cursor, edge = "nearest") {
|
|
6857
|
+
const scroller = findMainScrollTarget();
|
|
6858
|
+
if (scroller) {
|
|
6859
|
+
const top = (cursor.line - 1) * VIRTUAL_SOURCE_ROW_HEIGHT;
|
|
6860
|
+
const bottom = top + VIRTUAL_SOURCE_ROW_HEIGHT;
|
|
6861
|
+
const before = scroller.scrollTop;
|
|
6862
|
+
if (edge === "center")
|
|
6863
|
+
scroller.scrollTop = Math.max(0, top - Math.round(scroller.clientHeight / 2));
|
|
6864
|
+
else if (top < scroller.scrollTop)
|
|
6865
|
+
scroller.scrollTop = top;
|
|
6866
|
+
else if (bottom > scroller.scrollTop + scroller.clientHeight)
|
|
6867
|
+
scroller.scrollTop = bottom - scroller.clientHeight;
|
|
6868
|
+
if (scroller.scrollTop !== before)
|
|
6869
|
+
scroller.dispatchEvent(new Event("scroll"));
|
|
6870
|
+
scroller.__gdpRenderVirtualSource?.();
|
|
6871
|
+
syncSourceCursorRows(cursor.target);
|
|
6872
|
+
return;
|
|
6873
|
+
}
|
|
6874
|
+
document.querySelector('#content [data-line="' + cursor.line + '"]')?.scrollIntoView({ block: edge });
|
|
6875
|
+
}
|
|
6876
|
+
function moveSourceCursor(direction, unit, edge) {
|
|
6877
|
+
if (!hasVisibleSourceCodeSurface())
|
|
6878
|
+
return false;
|
|
6879
|
+
const target = sourceTargetFromRoute();
|
|
6880
|
+
if (!target)
|
|
6881
|
+
return false;
|
|
6882
|
+
const total = SOURCE_CURSOR_TOTALS.get(sourceCursorKey(target));
|
|
6883
|
+
if (!total)
|
|
6884
|
+
return false;
|
|
6885
|
+
const cursor = ensureSourceCursor(target);
|
|
6886
|
+
if (unit === "edge") {
|
|
6887
|
+
cursor.line = edge === "bottom" ? total : 1;
|
|
6888
|
+
syncSourceCursorRows(target);
|
|
6889
|
+
scrollSourceCursorIntoView(cursor, "center");
|
|
6890
|
+
return true;
|
|
6891
|
+
}
|
|
6892
|
+
const pageRows = Math.max(1, Math.floor((findMainScrollTarget()?.clientHeight || window.innerHeight) * 0.55 / (sourceLineScrollAmount() || VIRTUAL_SOURCE_ROW_HEIGHT)));
|
|
6893
|
+
const delta = unit === "page" ? pageRows : 1;
|
|
6894
|
+
cursor.line = Math.max(1, Math.min(total, cursor.line + direction * delta));
|
|
6895
|
+
syncSourceCursorRows(target);
|
|
6896
|
+
scrollSourceCursorIntoView(cursor);
|
|
6897
|
+
return true;
|
|
6898
|
+
}
|
|
6899
|
+
function scrollMainPanel(direction, repeated = false, unit = "line") {
|
|
6900
|
+
if (moveSourceCursor(direction, unit))
|
|
6901
|
+
return;
|
|
6902
|
+
const target = findMainScrollTarget();
|
|
6903
|
+
const viewportHeight = target?.clientHeight || document.scrollingElement?.clientHeight || window.innerHeight;
|
|
6904
|
+
const top = direction * (unit === "line" ? Math.round(sourceLineScrollAmount() || 32) : Math.round(viewportHeight * 0.55));
|
|
6905
|
+
const behavior = repeated ? "auto" : "smooth";
|
|
6906
|
+
if (target)
|
|
6907
|
+
target.scrollBy({ top, behavior });
|
|
6908
|
+
else
|
|
6909
|
+
window.scrollBy({ top, behavior });
|
|
6910
|
+
}
|
|
6911
|
+
function scrollMainToEdge(edge) {
|
|
6912
|
+
if (moveSourceCursor(edge === "bottom" ? 1 : -1, "edge", edge))
|
|
6913
|
+
return;
|
|
6914
|
+
const target = findMainScrollTarget();
|
|
6915
|
+
if (target) {
|
|
6916
|
+
target.scrollTo({ top: edge === "top" ? 0 : target.scrollHeight, behavior: "auto" });
|
|
6917
|
+
return;
|
|
6918
|
+
}
|
|
6919
|
+
const top = edge === "top" ? 0 : Math.max(document.documentElement.scrollHeight, document.body.scrollHeight);
|
|
6920
|
+
window.scrollTo({ top, behavior: "auto" });
|
|
6921
|
+
}
|
|
6922
|
+
function switchSourceTab(tab) {
|
|
6923
|
+
const tabs = document.querySelector("#content .gdp-source-tabs");
|
|
6924
|
+
if (!tabs)
|
|
6925
|
+
return false;
|
|
6926
|
+
const button = tabs.querySelector('button[data-source-tab="' + tab + '"]');
|
|
6927
|
+
if (!button || button.hidden || button.disabled)
|
|
6928
|
+
return false;
|
|
6929
|
+
button.click();
|
|
6930
|
+
focusMainPanel();
|
|
6931
|
+
return true;
|
|
6932
|
+
}
|
|
6933
|
+
function isFocusableClickTarget(target) {
|
|
6934
|
+
if (!(target instanceof Element))
|
|
6935
|
+
return false;
|
|
6936
|
+
return !!target.closest('a, button, input, textarea, select, summary, [tabindex]:not([tabindex="-1"]), [contenteditable="true"]');
|
|
6937
|
+
}
|
|
6402
6938
|
function invalidateRepoSidebar() {
|
|
6403
6939
|
REPO_SIDEBAR_REF = null;
|
|
6404
6940
|
REPO_SIDEBAR_LOAD_REF = null;
|
|
@@ -6741,6 +7277,7 @@
|
|
|
6741
7277
|
const dir = item.dir;
|
|
6742
7278
|
const li = document.createElement("li");
|
|
6743
7279
|
li.className = "tree-dir";
|
|
7280
|
+
li.tabIndex = -1;
|
|
6744
7281
|
li.dataset.dirpath = dir.path;
|
|
6745
7282
|
if (dir.explicit)
|
|
6746
7283
|
li.dataset.explicit = "true";
|
|
@@ -6798,6 +7335,7 @@
|
|
|
6798
7335
|
li.addEventListener("click", (e2) => {
|
|
6799
7336
|
e2.stopPropagation();
|
|
6800
7337
|
onFileClick({ path: dir.path, display_path: dir.path, type: "tree", children_omitted: dir.children_omitted });
|
|
7338
|
+
focusSidebarPanel();
|
|
6801
7339
|
});
|
|
6802
7340
|
} else {
|
|
6803
7341
|
li.addEventListener("click", toggleDir);
|
|
@@ -6808,6 +7346,7 @@
|
|
|
6808
7346
|
const f2 = item.file;
|
|
6809
7347
|
const li = document.createElement("li");
|
|
6810
7348
|
li.className = "tree-file";
|
|
7349
|
+
li.tabIndex = -1;
|
|
6811
7350
|
li.dataset.path = f2.path;
|
|
6812
7351
|
li.classList.toggle("viewed", !onFileClick && STATE.viewedFiles.has(f2.path));
|
|
6813
7352
|
li.style.setProperty("--lvl-pad", 12 + depth * 14 + "px");
|
|
@@ -6832,6 +7371,7 @@
|
|
|
6832
7371
|
onFileClick(f2);
|
|
6833
7372
|
else
|
|
6834
7373
|
scrollToFile(f2.path);
|
|
7374
|
+
focusSidebarPanel();
|
|
6835
7375
|
});
|
|
6836
7376
|
if (!onFileClick)
|
|
6837
7377
|
li.addEventListener("mouseenter", () => prefetchByPath(f2.path), { passive: true });
|
|
@@ -6842,6 +7382,7 @@
|
|
|
6842
7382
|
function renderFlat(files, ul, onFileClick) {
|
|
6843
7383
|
files.forEach((f2, i2) => {
|
|
6844
7384
|
const li = document.createElement("li");
|
|
7385
|
+
li.tabIndex = -1;
|
|
6845
7386
|
li.dataset.index = String(i2);
|
|
6846
7387
|
li.dataset.path = f2.path;
|
|
6847
7388
|
li.classList.toggle("viewed", !onFileClick && STATE.viewedFiles.has(f2.path));
|
|
@@ -6863,6 +7404,7 @@
|
|
|
6863
7404
|
onFileClick(f2);
|
|
6864
7405
|
else
|
|
6865
7406
|
scrollToFile(f2.path);
|
|
7407
|
+
focusSidebarPanel();
|
|
6866
7408
|
});
|
|
6867
7409
|
if (!onFileClick)
|
|
6868
7410
|
li.addEventListener("mouseenter", () => prefetchByPath(f2.path), { passive: true });
|
|
@@ -6954,7 +7496,41 @@
|
|
|
6954
7496
|
return;
|
|
6955
7497
|
enqueueLoad(f2, card, 5);
|
|
6956
7498
|
}
|
|
6957
|
-
function
|
|
7499
|
+
function clearDiffLineFocus() {
|
|
7500
|
+
document.querySelectorAll(".gdp-diff-line-target").forEach((row) => {
|
|
7501
|
+
row.classList.remove("gdp-diff-line-target");
|
|
7502
|
+
});
|
|
7503
|
+
}
|
|
7504
|
+
function diffRowLineNumber(row) {
|
|
7505
|
+
const newLine = row.querySelector(".line-num2, td.d2h-code-side-linenumber");
|
|
7506
|
+
const raw = (newLine?.textContent || "").trim();
|
|
7507
|
+
const line = Number(raw);
|
|
7508
|
+
return Number.isInteger(line) && line > 0 ? line : null;
|
|
7509
|
+
}
|
|
7510
|
+
function focusDiffLine(card, line) {
|
|
7511
|
+
const start = lineTargetStart(line);
|
|
7512
|
+
if (!start)
|
|
7513
|
+
return false;
|
|
7514
|
+
const rows = Array.from(card.querySelectorAll("table.d2h-diff-table tr"));
|
|
7515
|
+
const row = rows.find((candidate) => diffRowLineNumber(candidate) === start);
|
|
7516
|
+
if (!row)
|
|
7517
|
+
return false;
|
|
7518
|
+
clearDiffLineFocus();
|
|
7519
|
+
row.classList.add("gdp-diff-line-target");
|
|
7520
|
+
row.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
7521
|
+
return true;
|
|
7522
|
+
}
|
|
7523
|
+
function applyDiffRouteFocus(card) {
|
|
7524
|
+
if (STATE.route.screen !== "diff" || !STATE.route.path || !STATE.route.line)
|
|
7525
|
+
return false;
|
|
7526
|
+
if (card && card.dataset.path !== STATE.route.path)
|
|
7527
|
+
return false;
|
|
7528
|
+
const targetCard = card || document.querySelector(diffCardSelector(STATE.route.path));
|
|
7529
|
+
if (!targetCard)
|
|
7530
|
+
return false;
|
|
7531
|
+
return focusDiffLine(targetCard, STATE.route.line);
|
|
7532
|
+
}
|
|
7533
|
+
function scrollToFile(path, line) {
|
|
6958
7534
|
const card = document.querySelector(diffCardSelector(path));
|
|
6959
7535
|
if (!card)
|
|
6960
7536
|
return;
|
|
@@ -6970,7 +7546,9 @@
|
|
|
6970
7546
|
if (f2)
|
|
6971
7547
|
enqueueLoad(f2, card, 10);
|
|
6972
7548
|
}
|
|
6973
|
-
|
|
7549
|
+
if (!line || !focusDiffLine(card, line)) {
|
|
7550
|
+
card.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
7551
|
+
}
|
|
6974
7552
|
}
|
|
6975
7553
|
function markActive(path) {
|
|
6976
7554
|
STATE.activeFile = path;
|
|
@@ -7095,6 +7673,12 @@
|
|
|
7095
7673
|
function repoFileTargetFromRoute() {
|
|
7096
7674
|
return STATE.route.screen === "file" && STATE.route.view === "blob" ? STATE.route.ref : null;
|
|
7097
7675
|
}
|
|
7676
|
+
function helpLanguageFromRoute() {
|
|
7677
|
+
return STATE.route.screen === "help" && HELP_LANGUAGES.includes(STATE.route.lang) ? STATE.route.lang : "en";
|
|
7678
|
+
}
|
|
7679
|
+
function helpSectionFromRoute() {
|
|
7680
|
+
return STATE.route.screen === "help" && HELP_SECTIONS.includes(STATE.route.section) ? STATE.route.section : "keybindings";
|
|
7681
|
+
}
|
|
7098
7682
|
function setRoute(route, replace2 = false) {
|
|
7099
7683
|
const nextRoute = route.screen === "unknown" ? { screen: "diff", range: route.range } : route;
|
|
7100
7684
|
STATE.route = nextRoute;
|
|
@@ -7115,6 +7699,7 @@
|
|
|
7115
7699
|
document.body.classList.toggle("gdp-file-detail-page", STATE.route.screen === "file");
|
|
7116
7700
|
document.body.classList.toggle("gdp-repo-blob-page", STATE.route.screen === "file" && STATE.route.view === "blob");
|
|
7117
7701
|
document.body.classList.toggle("gdp-repo-page", STATE.route.screen === "repo");
|
|
7702
|
+
document.body.classList.toggle("gdp-help-page", STATE.route.screen === "help");
|
|
7118
7703
|
syncRepoTargetInput(repoFileTargetFromRoute() || "worktree");
|
|
7119
7704
|
}
|
|
7120
7705
|
function syncHeaderMenu() {
|
|
@@ -7129,12 +7714,104 @@
|
|
|
7129
7714
|
if (link2.dataset.route === "diff") {
|
|
7130
7715
|
link2.href = buildRoute({ screen: "diff", range: currentRange() });
|
|
7131
7716
|
}
|
|
7717
|
+
if (link2.dataset.route === "help") {
|
|
7718
|
+
link2.href = buildRoute({ screen: "help", lang: helpLanguageFromRoute(), section: helpSectionFromRoute(), range: currentRange() });
|
|
7719
|
+
}
|
|
7132
7720
|
});
|
|
7133
7721
|
}
|
|
7134
7722
|
function removeStandaloneSource() {
|
|
7135
7723
|
document.querySelectorAll(".gdp-standalone-source").forEach((el) => el.remove());
|
|
7136
7724
|
document.querySelectorAll(".gdp-repo-blob-layout").forEach((el) => el.remove());
|
|
7137
7725
|
}
|
|
7726
|
+
function renderHelpPage() {
|
|
7727
|
+
cancelActiveSourceLoad("navigation");
|
|
7728
|
+
removeStandaloneSource();
|
|
7729
|
+
LOAD_QUEUE.length = 0;
|
|
7730
|
+
const target = $("#diff");
|
|
7731
|
+
const empty = $("#empty");
|
|
7732
|
+
empty.classList.add("hidden");
|
|
7733
|
+
$("#meta").textContent = "";
|
|
7734
|
+
$("#totals").textContent = "";
|
|
7735
|
+
$("#filelist").textContent = "";
|
|
7736
|
+
const lang = helpLanguageFromRoute();
|
|
7737
|
+
const section = helpSectionFromRoute();
|
|
7738
|
+
const content = HELP_CONTENT[lang];
|
|
7739
|
+
const sectionContent = content.sections[section];
|
|
7740
|
+
const shell = document.createElement("section");
|
|
7741
|
+
shell.className = "gdp-help-shell";
|
|
7742
|
+
const header = document.createElement("header");
|
|
7743
|
+
header.className = "gdp-help-header";
|
|
7744
|
+
const title = document.createElement("h1");
|
|
7745
|
+
title.textContent = content.title;
|
|
7746
|
+
const langSelect = document.createElement("select");
|
|
7747
|
+
langSelect.className = "gdp-help-language";
|
|
7748
|
+
langSelect.setAttribute("aria-label", content.languageLabel);
|
|
7749
|
+
HELP_LANGUAGES.forEach((optionLang) => {
|
|
7750
|
+
const option = document.createElement("option");
|
|
7751
|
+
option.value = optionLang;
|
|
7752
|
+
option.textContent = optionLang.toUpperCase();
|
|
7753
|
+
option.selected = optionLang === lang;
|
|
7754
|
+
langSelect.appendChild(option);
|
|
7755
|
+
});
|
|
7756
|
+
langSelect.addEventListener("change", () => {
|
|
7757
|
+
setRoute({ screen: "help", lang: langSelect.value, section, range: currentRange() });
|
|
7758
|
+
setPageMode();
|
|
7759
|
+
renderHelpPage();
|
|
7760
|
+
syncHeaderMenu();
|
|
7761
|
+
});
|
|
7762
|
+
header.append(title, langSelect);
|
|
7763
|
+
const layout = document.createElement("div");
|
|
7764
|
+
layout.className = "gdp-help-layout";
|
|
7765
|
+
const helpNav = document.createElement("nav");
|
|
7766
|
+
helpNav.className = "gdp-help-nav";
|
|
7767
|
+
HELP_SECTIONS.forEach((helpSection) => {
|
|
7768
|
+
const button = document.createElement("button");
|
|
7769
|
+
button.type = "button";
|
|
7770
|
+
button.className = helpSection === section ? "active" : "";
|
|
7771
|
+
button.textContent = content.sections[helpSection].nav;
|
|
7772
|
+
button.addEventListener("click", () => {
|
|
7773
|
+
setRoute({ screen: "help", lang, section: helpSection, range: currentRange() });
|
|
7774
|
+
renderHelpPage();
|
|
7775
|
+
syncHeaderMenu();
|
|
7776
|
+
});
|
|
7777
|
+
helpNav.appendChild(button);
|
|
7778
|
+
});
|
|
7779
|
+
const article = document.createElement("article");
|
|
7780
|
+
article.className = "gdp-help-content";
|
|
7781
|
+
const h2 = document.createElement("h2");
|
|
7782
|
+
h2.textContent = sectionContent.title;
|
|
7783
|
+
const intro = document.createElement("p");
|
|
7784
|
+
intro.textContent = sectionContent.intro;
|
|
7785
|
+
article.append(h2, intro);
|
|
7786
|
+
sectionContent.groups.forEach((group) => {
|
|
7787
|
+
const groupSection = document.createElement("section");
|
|
7788
|
+
groupSection.className = "gdp-help-group";
|
|
7789
|
+
const groupTitle = document.createElement("h3");
|
|
7790
|
+
groupTitle.textContent = group.title;
|
|
7791
|
+
const table2 = document.createElement("table");
|
|
7792
|
+
group.rows.forEach(([keys, description]) => {
|
|
7793
|
+
const tr = document.createElement("tr");
|
|
7794
|
+
const keyCell = document.createElement("th");
|
|
7795
|
+
keyCell.scope = "row";
|
|
7796
|
+
keys.split(" / ").forEach((key, index) => {
|
|
7797
|
+
if (index > 0)
|
|
7798
|
+
keyCell.append(" / ");
|
|
7799
|
+
const kbd = document.createElement("kbd");
|
|
7800
|
+
kbd.textContent = key;
|
|
7801
|
+
keyCell.appendChild(kbd);
|
|
7802
|
+
});
|
|
7803
|
+
const desc = document.createElement("td");
|
|
7804
|
+
desc.textContent = description;
|
|
7805
|
+
tr.append(keyCell, desc);
|
|
7806
|
+
table2.appendChild(tr);
|
|
7807
|
+
});
|
|
7808
|
+
groupSection.append(groupTitle, table2);
|
|
7809
|
+
article.appendChild(groupSection);
|
|
7810
|
+
});
|
|
7811
|
+
layout.append(helpNav, article);
|
|
7812
|
+
shell.append(header, layout);
|
|
7813
|
+
target.replaceChildren(shell);
|
|
7814
|
+
}
|
|
7138
7815
|
function renderShell(meta) {
|
|
7139
7816
|
const newFiles = meta.files || [];
|
|
7140
7817
|
STATE.files = newFiles;
|
|
@@ -8624,11 +9301,35 @@
|
|
|
8624
9301
|
});
|
|
8625
9302
|
return info;
|
|
8626
9303
|
}
|
|
8627
|
-
function
|
|
9304
|
+
function createSourceCopyButton(textValue) {
|
|
9305
|
+
const copy = document.createElement("button");
|
|
9306
|
+
copy.type = "button";
|
|
9307
|
+
copy.className = "gdp-file-header-icon gdp-copy-source";
|
|
9308
|
+
copy.title = "Copy source";
|
|
9309
|
+
copy.setAttribute("aria-label", "Copy source");
|
|
9310
|
+
copy.innerHTML = iconSvg("octicon-copy", COPY_16_PATHS);
|
|
9311
|
+
copy.addEventListener("click", async () => {
|
|
9312
|
+
try {
|
|
9313
|
+
await navigator.clipboard.writeText(textValue);
|
|
9314
|
+
copy.classList.add("copied");
|
|
9315
|
+
setTimeout(() => {
|
|
9316
|
+
copy.classList.remove("copied");
|
|
9317
|
+
}, 1200);
|
|
9318
|
+
} catch {
|
|
9319
|
+
copy.classList.add("failed");
|
|
9320
|
+
setTimeout(() => {
|
|
9321
|
+
copy.classList.remove("failed");
|
|
9322
|
+
}, 1200);
|
|
9323
|
+
}
|
|
9324
|
+
});
|
|
9325
|
+
return copy;
|
|
9326
|
+
}
|
|
9327
|
+
function createSourceTabs(active, textValue) {
|
|
8628
9328
|
const tabs = document.createElement("div");
|
|
8629
9329
|
tabs.className = "gdp-source-tabs";
|
|
8630
9330
|
const codeButton = document.createElement("button");
|
|
8631
9331
|
codeButton.type = "button";
|
|
9332
|
+
codeButton.dataset.sourceTab = "code";
|
|
8632
9333
|
codeButton.textContent = "Code";
|
|
8633
9334
|
codeButton.classList.toggle("active", active === "code");
|
|
8634
9335
|
tabs.appendChild(codeButton);
|
|
@@ -8636,10 +9337,13 @@
|
|
|
8636
9337
|
if (active === "preview") {
|
|
8637
9338
|
previewButton = document.createElement("button");
|
|
8638
9339
|
previewButton.type = "button";
|
|
9340
|
+
previewButton.dataset.sourceTab = "preview";
|
|
8639
9341
|
previewButton.className = "active";
|
|
8640
9342
|
previewButton.textContent = "Preview";
|
|
8641
9343
|
tabs.prepend(previewButton);
|
|
8642
9344
|
}
|
|
9345
|
+
if (textValue != null)
|
|
9346
|
+
tabs.appendChild(createSourceCopyButton(textValue));
|
|
8643
9347
|
return { tabs, codeButton, previewButton };
|
|
8644
9348
|
}
|
|
8645
9349
|
async function renderSourceText(card, target, textValue, signal) {
|
|
@@ -8647,6 +9351,8 @@
|
|
|
8647
9351
|
`).replace(/\r/g, `
|
|
8648
9352
|
`).split(`
|
|
8649
9353
|
`) : [""];
|
|
9354
|
+
SOURCE_CURSOR_TOTALS.set(sourceCursorKey(target), lines.length);
|
|
9355
|
+
resetSourceCursorForTarget(target, lines.length);
|
|
8650
9356
|
const body = card.querySelector(".gdp-file-detail-body, .d2h-files-diff, .d2h-file-diff, .gdp-media, .gdp-source-viewer");
|
|
8651
9357
|
const isStandalone = card.classList.contains("gdp-standalone-source");
|
|
8652
9358
|
const view = document.createElement("div");
|
|
@@ -8667,7 +9373,7 @@
|
|
|
8667
9373
|
if (usesVirtualSource) {
|
|
8668
9374
|
const virtualCode = renderVirtualSource(target, textValue, lines, hljsRef, lang);
|
|
8669
9375
|
if (previewable) {
|
|
8670
|
-
const { tabs: tabs2, codeButton: codeButton2, previewButton: previewButton2 } = createSourceTabs("preview");
|
|
9376
|
+
const { tabs: tabs2, codeButton: codeButton2, previewButton: previewButton2 } = createSourceTabs("preview", textValue);
|
|
8671
9377
|
if (tabsHost) {
|
|
8672
9378
|
tabsHost.hidden = false;
|
|
8673
9379
|
tabsHost.replaceChildren(tabs2);
|
|
@@ -8727,9 +9433,13 @@
|
|
|
8727
9433
|
return false;
|
|
8728
9434
|
const line = lines[index];
|
|
8729
9435
|
const tr = document.createElement("tr");
|
|
9436
|
+
tr.dataset.line = String(index + 1);
|
|
9437
|
+
tr.classList.toggle("gdp-source-line-target", lineInSourceTarget(index + 1, currentSourceLineTarget(target)));
|
|
9438
|
+
tr.classList.toggle("gdp-source-cursor", sourceCursorMatches(target, index + 1));
|
|
8730
9439
|
const num = document.createElement("td");
|
|
8731
9440
|
num.className = "gdp-source-line-number";
|
|
8732
9441
|
num.textContent = String(index + 1);
|
|
9442
|
+
bindSourceLineNumber(num, card, target, index + 1);
|
|
8733
9443
|
const code2 = document.createElement("td");
|
|
8734
9444
|
code2.className = "gdp-source-line-code";
|
|
8735
9445
|
if (shikiLines && shikiLines[index] != null) {
|
|
@@ -8748,7 +9458,7 @@
|
|
|
8748
9458
|
}
|
|
8749
9459
|
}
|
|
8750
9460
|
table2.appendChild(tbody);
|
|
8751
|
-
const { tabs, codeButton, previewButton } = createSourceTabs(previewable ? "preview" : "code");
|
|
9461
|
+
const { tabs, codeButton, previewButton } = createSourceTabs(previewable ? "preview" : "code", textValue);
|
|
8752
9462
|
if (tabsHost) {
|
|
8753
9463
|
tabsHost.hidden = false;
|
|
8754
9464
|
tabsHost.replaceChildren(tabs);
|
|
@@ -8821,6 +9531,69 @@
|
|
|
8821
9531
|
url.searchParams.delete("virtual");
|
|
8822
9532
|
return url.pathname + url.search;
|
|
8823
9533
|
}
|
|
9534
|
+
function currentSourceLineTarget(target) {
|
|
9535
|
+
const routeTarget = sourceTargetFromRoute();
|
|
9536
|
+
return sourceTargetsEqual(routeTarget, target) && STATE.route.screen === "file" ? STATE.route.line : undefined;
|
|
9537
|
+
}
|
|
9538
|
+
function lineTargetStart(line) {
|
|
9539
|
+
if (!line)
|
|
9540
|
+
return;
|
|
9541
|
+
return typeof line === "number" ? line : line.start;
|
|
9542
|
+
}
|
|
9543
|
+
function lineInSourceTarget(lineNumber, target) {
|
|
9544
|
+
if (!target)
|
|
9545
|
+
return false;
|
|
9546
|
+
if (typeof target === "number")
|
|
9547
|
+
return lineNumber === target;
|
|
9548
|
+
return lineNumber >= target.start && lineNumber <= target.end;
|
|
9549
|
+
}
|
|
9550
|
+
let SOURCE_LINE_DRAG = null;
|
|
9551
|
+
function normalizeSourceLineSelection(start, end) {
|
|
9552
|
+
const a2 = Math.max(1, Math.floor(start));
|
|
9553
|
+
const b2 = Math.max(1, Math.floor(end));
|
|
9554
|
+
const from = Math.min(a2, b2);
|
|
9555
|
+
const to = Math.max(a2, b2);
|
|
9556
|
+
return from === to ? from : { start: from, end: to };
|
|
9557
|
+
}
|
|
9558
|
+
function setSourceLineRoute(target, line) {
|
|
9559
|
+
if (STATE.route.screen !== "file")
|
|
9560
|
+
return;
|
|
9561
|
+
setRoute({
|
|
9562
|
+
screen: "file",
|
|
9563
|
+
path: target.path,
|
|
9564
|
+
ref: target.ref,
|
|
9565
|
+
view: STATE.route.view,
|
|
9566
|
+
range: currentRange(),
|
|
9567
|
+
line
|
|
9568
|
+
}, true);
|
|
9569
|
+
}
|
|
9570
|
+
function syncRenderedSourceLineHighlights(card, target) {
|
|
9571
|
+
const lineTarget = currentSourceLineTarget(target);
|
|
9572
|
+
card.querySelectorAll("[data-line]").forEach((row) => {
|
|
9573
|
+
const line = Number(row.dataset.line || "0");
|
|
9574
|
+
row.classList.toggle("gdp-source-line-target", lineInSourceTarget(line, lineTarget));
|
|
9575
|
+
});
|
|
9576
|
+
}
|
|
9577
|
+
function updateSourceLineSelection(card, target, start, end) {
|
|
9578
|
+
setSourceLineRoute(target, normalizeSourceLineSelection(start, end));
|
|
9579
|
+
syncRenderedSourceLineHighlights(card, target);
|
|
9580
|
+
}
|
|
9581
|
+
function beginSourceLineSelection(event, card, target, line) {
|
|
9582
|
+
event.preventDefault();
|
|
9583
|
+
SOURCE_LINE_DRAG = { target, start: line };
|
|
9584
|
+
updateSourceLineSelection(card, target, line, line);
|
|
9585
|
+
}
|
|
9586
|
+
function bindSourceLineNumber(num, card, target, line) {
|
|
9587
|
+
num.addEventListener("mousedown", (e2) => beginSourceLineSelection(e2, card, target, line));
|
|
9588
|
+
num.addEventListener("mouseenter", () => {
|
|
9589
|
+
if (!SOURCE_LINE_DRAG || !sourceTargetsEqual(SOURCE_LINE_DRAG.target, target))
|
|
9590
|
+
return;
|
|
9591
|
+
updateSourceLineSelection(card, target, SOURCE_LINE_DRAG.start, line);
|
|
9592
|
+
});
|
|
9593
|
+
}
|
|
9594
|
+
document.addEventListener("mouseup", () => {
|
|
9595
|
+
SOURCE_LINE_DRAG = null;
|
|
9596
|
+
});
|
|
8824
9597
|
function renderVirtualSource(target, textValue, lines, hljsRef, lang) {
|
|
8825
9598
|
const wrap = document.createElement("div");
|
|
8826
9599
|
wrap.className = "gdp-source-virtual";
|
|
@@ -8836,19 +9609,21 @@
|
|
|
8836
9609
|
actions.className = "gdp-source-virtual-actions";
|
|
8837
9610
|
const copy = document.createElement("button");
|
|
8838
9611
|
copy.type = "button";
|
|
8839
|
-
copy.className = "gdp-source-virtual-
|
|
8840
|
-
copy.
|
|
9612
|
+
copy.className = "gdp-file-header-icon gdp-copy-source gdp-source-virtual-copy";
|
|
9613
|
+
copy.title = "Copy source";
|
|
9614
|
+
copy.setAttribute("aria-label", "Copy source");
|
|
9615
|
+
copy.innerHTML = iconSvg("octicon-copy", COPY_16_PATHS);
|
|
8841
9616
|
copy.addEventListener("click", async () => {
|
|
8842
9617
|
try {
|
|
8843
9618
|
await navigator.clipboard.writeText(textValue);
|
|
8844
|
-
copy.
|
|
9619
|
+
copy.classList.add("copied");
|
|
8845
9620
|
setTimeout(() => {
|
|
8846
|
-
copy.
|
|
9621
|
+
copy.classList.remove("copied");
|
|
8847
9622
|
}, 1200);
|
|
8848
9623
|
} catch {
|
|
8849
|
-
copy.
|
|
9624
|
+
copy.classList.add("failed");
|
|
8850
9625
|
setTimeout(() => {
|
|
8851
|
-
copy.
|
|
9626
|
+
copy.classList.remove("failed");
|
|
8852
9627
|
}, 1600);
|
|
8853
9628
|
}
|
|
8854
9629
|
});
|
|
@@ -8859,7 +9634,8 @@
|
|
|
8859
9634
|
full.title = "Render every line without virtualization. This can be slow for large files.";
|
|
8860
9635
|
full.addEventListener("click", (e2) => {
|
|
8861
9636
|
e2.preventDefault();
|
|
8862
|
-
|
|
9637
|
+
const url = new URL(full.href, window.location.origin);
|
|
9638
|
+
setRoute(parseRoute(url.pathname, url.search, currentRange()), true);
|
|
8863
9639
|
renderStandaloneSource(target);
|
|
8864
9640
|
});
|
|
8865
9641
|
actions.append(copy, full);
|
|
@@ -8893,9 +9669,13 @@
|
|
|
8893
9669
|
for (let index = start;index < end; index++) {
|
|
8894
9670
|
const row = document.createElement("div");
|
|
8895
9671
|
row.className = "gdp-source-virtual-row";
|
|
9672
|
+
row.dataset.line = String(index + 1);
|
|
9673
|
+
row.classList.toggle("gdp-source-line-target", lineInSourceTarget(index + 1, currentSourceLineTarget(target)));
|
|
9674
|
+
row.classList.toggle("gdp-source-cursor", sourceCursorMatches(target, index + 1));
|
|
8896
9675
|
const num = document.createElement("span");
|
|
8897
9676
|
num.className = "gdp-source-virtual-line-number";
|
|
8898
9677
|
num.textContent = String(index + 1);
|
|
9678
|
+
bindSourceLineNumber(num, wrap, target, index + 1);
|
|
8899
9679
|
const code2 = document.createElement("span");
|
|
8900
9680
|
code2.className = "gdp-source-virtual-line-code";
|
|
8901
9681
|
const line = lines[index] ?? "";
|
|
@@ -8918,6 +9698,7 @@
|
|
|
8918
9698
|
if (!raf)
|
|
8919
9699
|
raf = requestAnimationFrame(render);
|
|
8920
9700
|
};
|
|
9701
|
+
scroller.__gdpRenderVirtualSource = render;
|
|
8921
9702
|
scroller.addEventListener("scroll", schedule, { passive: true });
|
|
8922
9703
|
let resizeObserver = null;
|
|
8923
9704
|
resizeObserver = typeof ResizeObserver === "function" ? new ResizeObserver(() => {
|
|
@@ -9143,6 +9924,7 @@
|
|
|
9143
9924
|
return;
|
|
9144
9925
|
if (!rendered)
|
|
9145
9926
|
return;
|
|
9927
|
+
scrollStandaloneSourceLine(card, lineTargetStart(STATE.route.screen === "file" ? STATE.route.line : undefined));
|
|
9146
9928
|
finishSourceLoad(req);
|
|
9147
9929
|
}
|
|
9148
9930
|
} catch (err) {
|
|
@@ -9156,6 +9938,19 @@
|
|
|
9156
9938
|
renderSourceError(card, target, "Cannot load " + target.path + " at " + target.ref);
|
|
9157
9939
|
}
|
|
9158
9940
|
}
|
|
9941
|
+
function scrollStandaloneSourceLine(card, line) {
|
|
9942
|
+
if (!line || line < 1)
|
|
9943
|
+
return;
|
|
9944
|
+
const virtualScroller = card.querySelector(".gdp-source-virtual-scroller");
|
|
9945
|
+
if (virtualScroller) {
|
|
9946
|
+
const centeredOffset = virtualScroller.clientHeight / 2 - VIRTUAL_SOURCE_ROW_HEIGHT / 2;
|
|
9947
|
+
virtualScroller.scrollTop = Math.max(0, (line - 1) * VIRTUAL_SOURCE_ROW_HEIGHT - Math.max(0, centeredOffset));
|
|
9948
|
+
return;
|
|
9949
|
+
}
|
|
9950
|
+
const row = card.querySelector('.gdp-source-table tr[data-line="' + String(line) + '"]');
|
|
9951
|
+
if (row)
|
|
9952
|
+
row.scrollIntoView({ block: "center" });
|
|
9953
|
+
}
|
|
9159
9954
|
function applySourceRouteToShell() {
|
|
9160
9955
|
const target = sourceTargetFromRoute();
|
|
9161
9956
|
setPageMode();
|
|
@@ -9344,6 +10139,7 @@
|
|
|
9344
10139
|
card.classList.add("loaded");
|
|
9345
10140
|
card.style.minHeight = "";
|
|
9346
10141
|
mountDiff(card, file, data);
|
|
10142
|
+
applyDiffRouteFocus(card);
|
|
9347
10143
|
card.style.containIntrinsicSize = Math.max(card.offsetHeight, file.estimated_height_px || 200) + "px";
|
|
9348
10144
|
applyViewedToCard(card, STATE.viewedFiles.has(file.path), true);
|
|
9349
10145
|
if (data.truncated && data.mode === "preview") {
|
|
@@ -9619,6 +10415,15 @@
|
|
|
9619
10415
|
});
|
|
9620
10416
|
$("#sb-expand-all").addEventListener("click", () => setAllSidebarDirsCollapsed(false));
|
|
9621
10417
|
$("#sb-collapse-all").addEventListener("click", () => setAllSidebarDirsCollapsed(true));
|
|
10418
|
+
prepareKeyboardPanels();
|
|
10419
|
+
const contentPanel = document.querySelector("#content");
|
|
10420
|
+
contentPanel?.addEventListener("focusin", () => setPanelFocusScope("main"));
|
|
10421
|
+
contentPanel?.addEventListener("mousedown", (event) => {
|
|
10422
|
+
if (isFocusableClickTarget(event.target))
|
|
10423
|
+
setPanelFocusScope("main");
|
|
10424
|
+
else
|
|
10425
|
+
focusMainPanel();
|
|
10426
|
+
});
|
|
9622
10427
|
function applySidebarWidth(w) {
|
|
9623
10428
|
const cw = Math.max(180, Math.min(900, w));
|
|
9624
10429
|
document.documentElement.style.setProperty("--sidebar-w", cw + "px");
|
|
@@ -9637,6 +10442,13 @@
|
|
|
9637
10442
|
sb.addEventListener("mousedown", mark);
|
|
9638
10443
|
sb.addEventListener("touchstart", mark, { passive: true });
|
|
9639
10444
|
sb.addEventListener("scroll", mark, { passive: true });
|
|
10445
|
+
sb.addEventListener("focusin", () => setPanelFocusScope("sidebar"));
|
|
10446
|
+
sb.addEventListener("mousedown", (event) => {
|
|
10447
|
+
if (isFocusableClickTarget(event.target))
|
|
10448
|
+
setPanelFocusScope("sidebar");
|
|
10449
|
+
else
|
|
10450
|
+
focusSidebarPanel();
|
|
10451
|
+
});
|
|
9640
10452
|
})();
|
|
9641
10453
|
(function setupResizer() {
|
|
9642
10454
|
const handle = $("#sidebar-resizer");
|
|
@@ -9699,6 +10511,32 @@
|
|
|
9699
10511
|
function visibleSidebarItems() {
|
|
9700
10512
|
return $$("#filelist li[data-path], #filelist .tree-dir[data-dirpath]").filter(isSidebarRowVisible);
|
|
9701
10513
|
}
|
|
10514
|
+
function scrollSidebarItemIntoView(item, block2 = "nearest") {
|
|
10515
|
+
const sidebar = document.querySelector("#sidebar");
|
|
10516
|
+
if (!sidebar) {
|
|
10517
|
+
item.scrollIntoView({ block: block2 });
|
|
10518
|
+
return;
|
|
10519
|
+
}
|
|
10520
|
+
const sidebarRect = sidebar.getBoundingClientRect();
|
|
10521
|
+
const itemRect = item.getBoundingClientRect();
|
|
10522
|
+
const stickyBottom = Math.max(sidebarRect.top, document.querySelector(".sb-head")?.getBoundingClientRect().bottom || sidebarRect.top, document.querySelector(".sb-filter-wrap")?.getBoundingClientRect().bottom || sidebarRect.top);
|
|
10523
|
+
const topPadding = Math.max(8, stickyBottom - sidebarRect.top + 8);
|
|
10524
|
+
const bottomPadding = 14;
|
|
10525
|
+
const visibleTop = sidebarRect.top + topPadding;
|
|
10526
|
+
const visibleBottom = sidebarRect.bottom - bottomPadding;
|
|
10527
|
+
if (block2 === "start") {
|
|
10528
|
+
sidebar.scrollTop += itemRect.top - visibleTop;
|
|
10529
|
+
return;
|
|
10530
|
+
}
|
|
10531
|
+
if (block2 === "end") {
|
|
10532
|
+
sidebar.scrollTop += itemRect.bottom - visibleBottom;
|
|
10533
|
+
return;
|
|
10534
|
+
}
|
|
10535
|
+
if (itemRect.top < visibleTop)
|
|
10536
|
+
sidebar.scrollTop += itemRect.top - visibleTop;
|
|
10537
|
+
else if (itemRect.bottom > visibleBottom)
|
|
10538
|
+
sidebar.scrollTop += itemRect.bottom - visibleBottom;
|
|
10539
|
+
}
|
|
9702
10540
|
function isRepositorySidebarMode() {
|
|
9703
10541
|
return document.body.classList.contains("gdp-repo-page") || document.body.classList.contains("gdp-repo-blob-page");
|
|
9704
10542
|
}
|
|
@@ -9714,7 +10552,44 @@
|
|
|
9714
10552
|
const path = target.dataset.path || target.dataset.dirpath;
|
|
9715
10553
|
if (path)
|
|
9716
10554
|
markActive(path);
|
|
9717
|
-
target
|
|
10555
|
+
scrollSidebarItemIntoView(target);
|
|
10556
|
+
if (target.dataset.path)
|
|
10557
|
+
prefetchByPath(target.dataset.path);
|
|
10558
|
+
}
|
|
10559
|
+
function moveActiveSidebarPage(direction) {
|
|
10560
|
+
const items = visibleSidebarItems();
|
|
10561
|
+
if (!items.length)
|
|
10562
|
+
return;
|
|
10563
|
+
const repoSidebar = isRepositorySidebarMode();
|
|
10564
|
+
const sidebar = document.querySelector("#sidebar");
|
|
10565
|
+
const sample = items.find((item) => item.getBoundingClientRect().height > 0);
|
|
10566
|
+
const rowHeight = sample ? sample.getBoundingClientRect().height : 28;
|
|
10567
|
+
const halfPageRows = Math.max(1, Math.floor((sidebar?.clientHeight || window.innerHeight) / 2 / rowHeight));
|
|
10568
|
+
const current = items.findIndex((li) => li.classList.contains("active"));
|
|
10569
|
+
const start = current < 0 ? 0 : current;
|
|
10570
|
+
const idx = Math.max(0, Math.min(items.length - 1, start + direction * halfPageRows));
|
|
10571
|
+
const target = items[idx];
|
|
10572
|
+
const path = target.dataset.path || target.dataset.dirpath;
|
|
10573
|
+
if (!repoSidebar && target.dataset.path)
|
|
10574
|
+
target.click();
|
|
10575
|
+
else if (path)
|
|
10576
|
+
markActive(path);
|
|
10577
|
+
scrollSidebarItemIntoView(target);
|
|
10578
|
+
if (target.dataset.path)
|
|
10579
|
+
prefetchByPath(target.dataset.path);
|
|
10580
|
+
}
|
|
10581
|
+
function moveActiveSidebarToEdge(edge) {
|
|
10582
|
+
const items = visibleSidebarItems();
|
|
10583
|
+
const repoSidebar = isRepositorySidebarMode();
|
|
10584
|
+
const target = edge === "top" ? items[0] : items[items.length - 1];
|
|
10585
|
+
if (!target)
|
|
10586
|
+
return;
|
|
10587
|
+
const path = target.dataset.path || target.dataset.dirpath;
|
|
10588
|
+
if (!repoSidebar && target.dataset.path)
|
|
10589
|
+
target.click();
|
|
10590
|
+
else if (path)
|
|
10591
|
+
markActive(path);
|
|
10592
|
+
scrollSidebarItemIntoView(target, edge === "top" ? "start" : "end");
|
|
9718
10593
|
if (target.dataset.path)
|
|
9719
10594
|
prefetchByPath(target.dataset.path);
|
|
9720
10595
|
}
|
|
@@ -9775,69 +10650,535 @@
|
|
|
9775
10650
|
input.focus();
|
|
9776
10651
|
input.select();
|
|
9777
10652
|
}
|
|
9778
|
-
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
|
|
10653
|
+
let PALETTE = null;
|
|
10654
|
+
const REPO_FILE_CACHE = new Map;
|
|
10655
|
+
function paletteSource() {
|
|
10656
|
+
if (STATE.route.screen === "diff")
|
|
10657
|
+
return "diff";
|
|
10658
|
+
if (STATE.route.screen === "file" && STATE.route.view !== "blob")
|
|
10659
|
+
return "diff";
|
|
10660
|
+
return "repo";
|
|
10661
|
+
}
|
|
10662
|
+
function paletteRef(source) {
|
|
10663
|
+
if (source === "diff")
|
|
10664
|
+
return STATE.to && STATE.to !== "worktree" ? STATE.to : "worktree";
|
|
10665
|
+
if (STATE.route.screen === "repo")
|
|
10666
|
+
return STATE.route.ref || "worktree";
|
|
10667
|
+
if (STATE.route.screen === "file")
|
|
10668
|
+
return STATE.route.ref || "worktree";
|
|
10669
|
+
return STATE.repoRef || "worktree";
|
|
10670
|
+
}
|
|
10671
|
+
function closeSearchPalette() {
|
|
10672
|
+
if (!PALETTE)
|
|
9782
10673
|
return;
|
|
9783
|
-
|
|
9784
|
-
|
|
9785
|
-
if (
|
|
10674
|
+
const previousFocusScope = PALETTE.previousFocusScope;
|
|
10675
|
+
PALETTE.controller?.abort();
|
|
10676
|
+
if (PALETTE.debounce)
|
|
10677
|
+
window.clearTimeout(PALETTE.debounce);
|
|
10678
|
+
PALETTE.root.remove();
|
|
10679
|
+
PALETTE = null;
|
|
10680
|
+
restorePanelFocusScope(previousFocusScope);
|
|
10681
|
+
}
|
|
10682
|
+
function createPalette(mode) {
|
|
10683
|
+
const previousFocusScope = PALETTE ? PALETTE.previousFocusScope : getPanelFocusScope();
|
|
10684
|
+
closeSearchPalette();
|
|
10685
|
+
const root = document.createElement("div");
|
|
10686
|
+
root.className = "gdp-palette-backdrop";
|
|
10687
|
+
const dialog = document.createElement("div");
|
|
10688
|
+
dialog.className = "gdp-palette";
|
|
10689
|
+
dialog.setAttribute("role", "dialog");
|
|
10690
|
+
dialog.setAttribute("aria-modal", "true");
|
|
10691
|
+
const label = document.createElement("div");
|
|
10692
|
+
label.className = "gdp-palette-label";
|
|
10693
|
+
label.textContent = mode === "file" ? "Files" : "Grep";
|
|
10694
|
+
const input = document.createElement("input");
|
|
10695
|
+
input.className = "gdp-palette-input";
|
|
10696
|
+
input.type = "search";
|
|
10697
|
+
input.autocomplete = "off";
|
|
10698
|
+
input.spellcheck = false;
|
|
10699
|
+
input.placeholder = mode === "file" ? "Search files" : "Search text";
|
|
10700
|
+
input.setAttribute("role", "combobox");
|
|
10701
|
+
input.setAttribute("aria-expanded", "true");
|
|
10702
|
+
input.setAttribute("aria-controls", "gdp-palette-list");
|
|
10703
|
+
const status = document.createElement("div");
|
|
10704
|
+
status.className = "gdp-palette-status";
|
|
10705
|
+
const controls = document.createElement("div");
|
|
10706
|
+
controls.className = "gdp-palette-controls";
|
|
10707
|
+
const list2 = document.createElement("div");
|
|
10708
|
+
list2.id = "gdp-palette-list";
|
|
10709
|
+
list2.className = "gdp-palette-list";
|
|
10710
|
+
list2.setAttribute("role", "listbox");
|
|
10711
|
+
dialog.append(label, input, controls, status, list2);
|
|
10712
|
+
root.appendChild(dialog);
|
|
10713
|
+
document.body.appendChild(root);
|
|
10714
|
+
const state = {
|
|
10715
|
+
root,
|
|
10716
|
+
input,
|
|
10717
|
+
controls,
|
|
10718
|
+
list: list2,
|
|
10719
|
+
status,
|
|
10720
|
+
mode,
|
|
10721
|
+
grepRegex: false,
|
|
10722
|
+
selected: -1,
|
|
10723
|
+
items: [],
|
|
10724
|
+
composing: false,
|
|
10725
|
+
diffSnapshot: [...STATE.files],
|
|
10726
|
+
previousFocusScope
|
|
10727
|
+
};
|
|
10728
|
+
PALETTE = state;
|
|
10729
|
+
setPanelFocusScope(null);
|
|
10730
|
+
root.addEventListener("mousedown", (e2) => {
|
|
10731
|
+
if (e2.target === root)
|
|
10732
|
+
closeSearchPalette();
|
|
10733
|
+
});
|
|
10734
|
+
input.addEventListener("compositionstart", () => {
|
|
10735
|
+
state.composing = true;
|
|
10736
|
+
});
|
|
10737
|
+
input.addEventListener("compositionend", () => {
|
|
10738
|
+
state.composing = false;
|
|
10739
|
+
});
|
|
10740
|
+
input.addEventListener("input", () => updatePaletteResults(state));
|
|
10741
|
+
input.addEventListener("keydown", (e2) => handlePaletteKeydown(e2, state));
|
|
10742
|
+
input.focus();
|
|
10743
|
+
updatePaletteResults(state);
|
|
10744
|
+
return state;
|
|
10745
|
+
}
|
|
10746
|
+
function renderPaletteControls(state) {
|
|
10747
|
+
state.controls.innerHTML = "";
|
|
10748
|
+
if (state.mode === "file") {
|
|
10749
|
+
const hint2 = document.createElement("span");
|
|
10750
|
+
hint2.className = "gdp-palette-mode-hint";
|
|
10751
|
+
hint2.textContent = isGlobPathQuery(state.input.value) ? "Glob: * ? []" : "Fuzzy path search";
|
|
10752
|
+
state.controls.appendChild(hint2);
|
|
9786
10753
|
return;
|
|
9787
|
-
|
|
9788
|
-
|
|
10754
|
+
}
|
|
10755
|
+
const plain = document.createElement("button");
|
|
10756
|
+
plain.type = "button";
|
|
10757
|
+
plain.className = "gdp-palette-mode-button";
|
|
10758
|
+
plain.setAttribute("aria-pressed", String(!state.grepRegex));
|
|
10759
|
+
plain.textContent = "Plain";
|
|
10760
|
+
plain.addEventListener("mousedown", (e2) => {
|
|
10761
|
+
e2.preventDefault();
|
|
10762
|
+
state.grepRegex = false;
|
|
10763
|
+
renderPaletteControls(state);
|
|
10764
|
+
updatePaletteResults(state);
|
|
10765
|
+
state.input.focus();
|
|
10766
|
+
});
|
|
10767
|
+
const regex = document.createElement("button");
|
|
10768
|
+
regex.type = "button";
|
|
10769
|
+
regex.className = "gdp-palette-mode-button";
|
|
10770
|
+
regex.setAttribute("aria-pressed", String(state.grepRegex));
|
|
10771
|
+
regex.textContent = ".* Regex";
|
|
10772
|
+
regex.title = "Alt+R";
|
|
10773
|
+
regex.addEventListener("mousedown", (e2) => {
|
|
10774
|
+
e2.preventDefault();
|
|
10775
|
+
state.grepRegex = true;
|
|
10776
|
+
renderPaletteControls(state);
|
|
10777
|
+
updatePaletteResults(state);
|
|
10778
|
+
state.input.focus();
|
|
10779
|
+
});
|
|
10780
|
+
const hint = document.createElement("span");
|
|
10781
|
+
hint.className = "gdp-palette-mode-hint";
|
|
10782
|
+
hint.textContent = "Alt+R toggles regex";
|
|
10783
|
+
state.controls.append(plain, regex, hint);
|
|
10784
|
+
}
|
|
10785
|
+
function regexQueryIsValid(query) {
|
|
10786
|
+
try {
|
|
10787
|
+
new RegExp(query);
|
|
10788
|
+
return true;
|
|
10789
|
+
} catch {
|
|
10790
|
+
return false;
|
|
10791
|
+
}
|
|
10792
|
+
}
|
|
10793
|
+
function appendHighlightedPath(parent, path, ranges) {
|
|
10794
|
+
let cursor = 0;
|
|
10795
|
+
for (const range of ranges) {
|
|
10796
|
+
if (range.start > cursor)
|
|
10797
|
+
parent.appendChild(document.createTextNode(path.slice(cursor, range.start)));
|
|
10798
|
+
const mark = document.createElement("mark");
|
|
10799
|
+
mark.textContent = path.slice(range.start, range.end);
|
|
10800
|
+
parent.appendChild(mark);
|
|
10801
|
+
cursor = range.end;
|
|
10802
|
+
}
|
|
10803
|
+
if (cursor < path.length)
|
|
10804
|
+
parent.appendChild(document.createTextNode(path.slice(cursor)));
|
|
10805
|
+
}
|
|
10806
|
+
function renderPalette(state) {
|
|
10807
|
+
state.list.innerHTML = "";
|
|
10808
|
+
state.items.forEach((item, index) => {
|
|
10809
|
+
const row = document.createElement("button");
|
|
10810
|
+
row.type = "button";
|
|
10811
|
+
row.id = "gdp-palette-item-" + index;
|
|
10812
|
+
row.className = "gdp-palette-row";
|
|
10813
|
+
row.setAttribute("role", "option");
|
|
10814
|
+
row.setAttribute("aria-selected", index === state.selected ? "true" : "false");
|
|
10815
|
+
const title = document.createElement("span");
|
|
10816
|
+
title.className = "gdp-palette-row-title";
|
|
10817
|
+
const detail = document.createElement("span");
|
|
10818
|
+
detail.className = "gdp-palette-row-detail";
|
|
10819
|
+
if (item.kind === "file") {
|
|
10820
|
+
title.textContent = item.path.split("/").pop() || item.path;
|
|
10821
|
+
appendHighlightedPath(detail, item.displayPath, item.ranges);
|
|
10822
|
+
if (item.old_path && item.displayPath !== item.old_path) {
|
|
10823
|
+
detail.appendChild(document.createTextNode(" " + item.old_path));
|
|
10824
|
+
}
|
|
10825
|
+
} else {
|
|
10826
|
+
title.textContent = item.path + ":" + item.line;
|
|
10827
|
+
detail.textContent = item.preview;
|
|
10828
|
+
}
|
|
10829
|
+
row.append(title, detail);
|
|
10830
|
+
row.addEventListener("mouseenter", () => {
|
|
10831
|
+
state.selected = index;
|
|
10832
|
+
syncPaletteSelection(state);
|
|
10833
|
+
});
|
|
10834
|
+
row.addEventListener("mousedown", (e2) => {
|
|
9789
10835
|
e2.preventDefault();
|
|
10836
|
+
state.selected = index;
|
|
10837
|
+
selectPaletteItem(state);
|
|
10838
|
+
});
|
|
10839
|
+
state.list.appendChild(row);
|
|
10840
|
+
});
|
|
10841
|
+
syncPaletteSelection(state);
|
|
10842
|
+
}
|
|
10843
|
+
function syncPaletteSelection(state) {
|
|
10844
|
+
state.input.setAttribute("aria-activedescendant", state.selected >= 0 ? "gdp-palette-item-" + state.selected : "");
|
|
10845
|
+
state.list.querySelectorAll(".gdp-palette-row").forEach((row, index) => {
|
|
10846
|
+
row.setAttribute("aria-selected", index === state.selected ? "true" : "false");
|
|
10847
|
+
if (index === state.selected)
|
|
10848
|
+
row.scrollIntoView({ block: "nearest" });
|
|
10849
|
+
});
|
|
10850
|
+
}
|
|
10851
|
+
async function repoPaletteFiles(ref) {
|
|
10852
|
+
const cached = REPO_FILE_CACHE.get(ref);
|
|
10853
|
+
if (cached && cached.generation === SERVER_GENERATION)
|
|
10854
|
+
return cached;
|
|
10855
|
+
const params = new URLSearchParams;
|
|
10856
|
+
params.set("ref", ref);
|
|
10857
|
+
const res = await trackLoad(fetch("/_files?" + params.toString()).then((r2) => {
|
|
10858
|
+
if (!r2.ok)
|
|
10859
|
+
throw new Error("failed to load files");
|
|
10860
|
+
return r2.json();
|
|
10861
|
+
}));
|
|
10862
|
+
REPO_FILE_CACHE.set(ref, res);
|
|
10863
|
+
return res;
|
|
10864
|
+
}
|
|
10865
|
+
function diffFilePaletteItems(state, query) {
|
|
10866
|
+
const matchPath = isGlobPathQuery(query) ? globMatchPath : fuzzyMatchPath;
|
|
10867
|
+
const candidates = state.diffSnapshot.map((file) => {
|
|
10868
|
+
const current = matchPath(query, file.path);
|
|
10869
|
+
const old = file.old_path ? matchPath(query, file.old_path) : null;
|
|
10870
|
+
const best = old && (!current || old.score > current.score) ? { match: old, displayPath: file.old_path || file.path } : current ? { match: current, displayPath: file.path } : null;
|
|
10871
|
+
return best ? { file, ...best } : null;
|
|
10872
|
+
}).filter((item) => item !== null).sort((a2, b2) => b2.match.score - a2.match.score || a2.file.path.localeCompare(b2.file.path));
|
|
10873
|
+
return limitPaletteResults(candidates).map((candidate) => ({
|
|
10874
|
+
kind: "file",
|
|
10875
|
+
path: candidate.file.path,
|
|
10876
|
+
old_path: candidate.file.old_path,
|
|
10877
|
+
displayPath: candidate.displayPath,
|
|
10878
|
+
ref: paletteRef("diff"),
|
|
10879
|
+
targetPath: fileSourceTarget(candidate.file).path,
|
|
10880
|
+
targetRef: fileSourceTarget(candidate.file).ref,
|
|
10881
|
+
source: "diff",
|
|
10882
|
+
ranges: candidate.match.ranges
|
|
10883
|
+
}));
|
|
10884
|
+
}
|
|
10885
|
+
async function updateFilePalette(state, query) {
|
|
10886
|
+
renderPaletteControls(state);
|
|
10887
|
+
const source = paletteSource();
|
|
10888
|
+
if (!query.trim()) {
|
|
10889
|
+
const base2 = source === "diff" ? state.diffSnapshot.map((file) => {
|
|
10890
|
+
const target = fileSourceTarget(file);
|
|
10891
|
+
return { kind: "file", path: file.path, old_path: file.old_path, displayPath: file.path, ref: paletteRef(source), targetPath: target.path, targetRef: target.ref, source, ranges: [] };
|
|
10892
|
+
}) : [];
|
|
10893
|
+
state.items = limitPaletteResults(base2);
|
|
10894
|
+
state.selected = state.items.length ? 0 : -1;
|
|
10895
|
+
state.status.textContent = source === "diff" ? state.diffSnapshot.length + " diff files" : "Type to search repository files";
|
|
10896
|
+
renderPalette(state);
|
|
10897
|
+
return;
|
|
10898
|
+
}
|
|
10899
|
+
if (source === "diff") {
|
|
10900
|
+
state.items = diffFilePaletteItems(state, query);
|
|
10901
|
+
} else {
|
|
10902
|
+
state.status.textContent = "Loading files...";
|
|
10903
|
+
const ref = paletteRef(source);
|
|
10904
|
+
const response = await repoPaletteFiles(ref);
|
|
10905
|
+
if (PALETTE !== state || state.input.value !== query)
|
|
9790
10906
|
return;
|
|
10907
|
+
state.items = limitPaletteResults(rankPathMatches(query, response.files)).map((match2) => ({
|
|
10908
|
+
kind: "file",
|
|
10909
|
+
path: match2.item.path,
|
|
10910
|
+
displayPath: match2.item.path,
|
|
10911
|
+
ref,
|
|
10912
|
+
source,
|
|
10913
|
+
ranges: match2.ranges
|
|
10914
|
+
}));
|
|
10915
|
+
}
|
|
10916
|
+
state.selected = state.items.length ? 0 : -1;
|
|
10917
|
+
state.status.textContent = state.items.length ? state.items.length + " results" : "No results";
|
|
10918
|
+
renderPalette(state);
|
|
10919
|
+
}
|
|
10920
|
+
function updateGrepPalette(state, query) {
|
|
10921
|
+
renderPaletteControls(state);
|
|
10922
|
+
state.controller?.abort();
|
|
10923
|
+
if (state.debounce)
|
|
10924
|
+
window.clearTimeout(state.debounce);
|
|
10925
|
+
if (!query.trim()) {
|
|
10926
|
+
state.items = [];
|
|
10927
|
+
state.selected = -1;
|
|
10928
|
+
state.status.textContent = "Type to grep";
|
|
10929
|
+
renderPalette(state);
|
|
10930
|
+
return;
|
|
10931
|
+
}
|
|
10932
|
+
if (state.grepRegex && !regexQueryIsValid(query)) {
|
|
10933
|
+
state.controller?.abort();
|
|
10934
|
+
state.items = [];
|
|
10935
|
+
state.selected = -1;
|
|
10936
|
+
state.status.textContent = "Invalid regular expression";
|
|
10937
|
+
renderPalette(state);
|
|
10938
|
+
return;
|
|
10939
|
+
}
|
|
10940
|
+
state.status.textContent = "Searching...";
|
|
10941
|
+
state.debounce = window.setTimeout(() => {
|
|
10942
|
+
const source = paletteSource();
|
|
10943
|
+
const ref = paletteRef(source);
|
|
10944
|
+
const params = new URLSearchParams;
|
|
10945
|
+
params.set("ref", ref);
|
|
10946
|
+
params.set("q", query);
|
|
10947
|
+
params.set("max", "200");
|
|
10948
|
+
if (state.grepRegex)
|
|
10949
|
+
params.set("regex", "1");
|
|
10950
|
+
if (source === "diff") {
|
|
10951
|
+
for (const file of state.diffSnapshot)
|
|
10952
|
+
params.append("path", file.path);
|
|
10953
|
+
}
|
|
10954
|
+
const controller = new AbortController;
|
|
10955
|
+
state.controller = controller;
|
|
10956
|
+
trackLoad(fetch("/_grep?" + params.toString(), { signal: controller.signal }).then((r2) => {
|
|
10957
|
+
if (!r2.ok)
|
|
10958
|
+
throw new Error("grep failed");
|
|
10959
|
+
return r2.json();
|
|
10960
|
+
})).then((response) => {
|
|
10961
|
+
if (PALETTE !== state || controller.signal.aborted)
|
|
10962
|
+
return;
|
|
10963
|
+
state.items = limitPaletteResults(response.matches.map((match2) => ({
|
|
10964
|
+
kind: "grep",
|
|
10965
|
+
path: match2.path,
|
|
10966
|
+
line: match2.line,
|
|
10967
|
+
column: match2.column,
|
|
10968
|
+
preview: match2.preview,
|
|
10969
|
+
ref,
|
|
10970
|
+
source
|
|
10971
|
+
})));
|
|
10972
|
+
state.selected = state.items.length ? 0 : -1;
|
|
10973
|
+
state.status.textContent = response.engine + (state.grepRegex ? " regex" : " plain") + (response.truncated ? " truncated" : "") + " - " + state.items.length + " results";
|
|
10974
|
+
renderPalette(state);
|
|
10975
|
+
}).catch((err) => {
|
|
10976
|
+
if (isAbortError(err))
|
|
10977
|
+
return;
|
|
10978
|
+
state.status.textContent = "Search failed";
|
|
10979
|
+
});
|
|
10980
|
+
}, 80);
|
|
10981
|
+
}
|
|
10982
|
+
function updatePaletteResults(state) {
|
|
10983
|
+
const query = state.input.value;
|
|
10984
|
+
if (state.mode === "file") {
|
|
10985
|
+
updateFilePalette(state, query).catch(() => {
|
|
10986
|
+
state.status.textContent = "Search failed";
|
|
10987
|
+
});
|
|
10988
|
+
} else {
|
|
10989
|
+
updateGrepPalette(state, query);
|
|
10990
|
+
}
|
|
10991
|
+
}
|
|
10992
|
+
function selectPaletteItem(state) {
|
|
10993
|
+
const item = state.items[state.selected];
|
|
10994
|
+
if (!item)
|
|
10995
|
+
return;
|
|
10996
|
+
closeSearchPalette();
|
|
10997
|
+
if (item.kind === "file") {
|
|
10998
|
+
if (item.source === "diff") {
|
|
10999
|
+
if (STATE.route.screen === "file") {
|
|
11000
|
+
setRoute({ screen: "file", path: item.targetPath || item.path, ref: item.targetRef || item.ref, range: currentRange() });
|
|
11001
|
+
applySourceRouteToShell();
|
|
11002
|
+
} else {
|
|
11003
|
+
scrollToFile(item.path);
|
|
11004
|
+
}
|
|
11005
|
+
} else {
|
|
11006
|
+
setRoute({ screen: "file", path: item.path, ref: item.ref, view: "blob", range: currentRange() });
|
|
11007
|
+
renderStandaloneSource({ path: item.path, ref: item.ref });
|
|
9791
11008
|
}
|
|
11009
|
+
return;
|
|
11010
|
+
}
|
|
11011
|
+
if (item.source === "diff") {
|
|
11012
|
+
setRoute({ screen: "diff", range: currentRange(), path: item.path, line: item.line });
|
|
11013
|
+
scrollToFile(item.path, item.line);
|
|
11014
|
+
} else {
|
|
11015
|
+
setRoute({ screen: "file", path: item.path, ref: item.ref, view: "blob", line: item.line, range: currentRange() });
|
|
11016
|
+
renderStandaloneSource({ path: item.path, ref: item.ref });
|
|
9792
11017
|
}
|
|
9793
|
-
|
|
11018
|
+
}
|
|
11019
|
+
function handlePaletteKeydown(e2, state) {
|
|
11020
|
+
if (e2.key === "Escape") {
|
|
9794
11021
|
e2.preventDefault();
|
|
9795
|
-
|
|
9796
|
-
|
|
9797
|
-
|
|
9798
|
-
|
|
9799
|
-
|
|
9800
|
-
|
|
9801
|
-
|
|
11022
|
+
closeSearchPalette();
|
|
11023
|
+
return;
|
|
11024
|
+
}
|
|
11025
|
+
if (e2.key === "Enter") {
|
|
11026
|
+
if (state.composing)
|
|
11027
|
+
return;
|
|
11028
|
+
e2.preventDefault();
|
|
11029
|
+
selectPaletteItem(state);
|
|
11030
|
+
return;
|
|
11031
|
+
}
|
|
11032
|
+
if (state.mode === "grep" && e2.altKey && e2.key.toLowerCase() === "r") {
|
|
9802
11033
|
e2.preventDefault();
|
|
11034
|
+
state.grepRegex = !state.grepRegex;
|
|
11035
|
+
updatePaletteResults(state);
|
|
11036
|
+
return;
|
|
11037
|
+
}
|
|
11038
|
+
const direction = e2.key === "ArrowDown" || e2.ctrlKey && e2.key.toLowerCase() === "n" ? 1 : e2.key === "ArrowUp" || e2.ctrlKey && e2.key.toLowerCase() === "p" ? -1 : 0;
|
|
11039
|
+
if (direction) {
|
|
11040
|
+
e2.preventDefault();
|
|
11041
|
+
state.selected = movePaletteSelection(state.selected, state.items.length, direction);
|
|
11042
|
+
syncPaletteSelection(state);
|
|
11043
|
+
}
|
|
11044
|
+
}
|
|
11045
|
+
function openSearchPalette(mode) {
|
|
11046
|
+
createPalette(mode);
|
|
11047
|
+
}
|
|
11048
|
+
function dispatchKeymapAction(action, scope, repeated = false) {
|
|
11049
|
+
if (action !== "start-g-sequence") {
|
|
11050
|
+
PENDING_G_SCOPE = null;
|
|
11051
|
+
PENDING_G_UNTIL = 0;
|
|
11052
|
+
}
|
|
11053
|
+
if (action === "open-file-palette") {
|
|
11054
|
+
if (PALETTE?.mode !== "file")
|
|
11055
|
+
openSearchPalette("file");
|
|
11056
|
+
return true;
|
|
11057
|
+
}
|
|
11058
|
+
if (action === "open-grep-palette") {
|
|
11059
|
+
if (PALETTE?.mode !== "grep")
|
|
11060
|
+
openSearchPalette("grep");
|
|
11061
|
+
return true;
|
|
11062
|
+
}
|
|
11063
|
+
if (action === "focus-file-filter") {
|
|
11064
|
+
focusFileFilter();
|
|
11065
|
+
return true;
|
|
11066
|
+
}
|
|
11067
|
+
if (action === "focus-sidebar") {
|
|
11068
|
+
focusSidebarPanel();
|
|
11069
|
+
return true;
|
|
11070
|
+
}
|
|
11071
|
+
if (action === "focus-main") {
|
|
11072
|
+
focusMainPanel();
|
|
11073
|
+
return true;
|
|
11074
|
+
}
|
|
11075
|
+
if (action === "cancel-source-load") {
|
|
11076
|
+
cancelActiveSourceLoad("esc");
|
|
11077
|
+
return true;
|
|
11078
|
+
}
|
|
11079
|
+
if (action === "open-sidebar-item") {
|
|
11080
|
+
if (!isRepositorySidebarMode())
|
|
11081
|
+
return false;
|
|
11082
|
+
openActiveSidebarItem();
|
|
11083
|
+
focusMainPanel();
|
|
11084
|
+
return true;
|
|
11085
|
+
}
|
|
11086
|
+
if (action === "sidebar-next" || action === "sidebar-previous") {
|
|
9803
11087
|
const repoSidebar = isRepositorySidebarMode();
|
|
9804
11088
|
const items = repoSidebar ? visibleSidebarItems() : $$("#filelist li[data-path]:not(.hidden):not(.hidden-by-tests)");
|
|
9805
11089
|
if (!items.length)
|
|
9806
|
-
return;
|
|
11090
|
+
return true;
|
|
9807
11091
|
let idx = items.findIndex((li) => li.classList.contains("active"));
|
|
9808
11092
|
if (idx < 0)
|
|
9809
11093
|
idx = 0;
|
|
9810
11094
|
else
|
|
9811
|
-
idx =
|
|
11095
|
+
idx = action === "sidebar-next" ? Math.min(items.length - 1, idx + 1) : Math.max(0, idx - 1);
|
|
9812
11096
|
const target = items[idx];
|
|
9813
11097
|
const path = target?.dataset.path || target?.dataset.dirpath;
|
|
9814
11098
|
if (!repoSidebar && target) {
|
|
9815
11099
|
target.click();
|
|
9816
|
-
target
|
|
11100
|
+
scrollSidebarItemIntoView(target);
|
|
9817
11101
|
} else if (path) {
|
|
9818
11102
|
markActive(path);
|
|
9819
|
-
target
|
|
11103
|
+
scrollSidebarItemIntoView(target);
|
|
9820
11104
|
}
|
|
9821
|
-
const nextIdx =
|
|
11105
|
+
const nextIdx = action === "sidebar-next" ? Math.min(items.length - 1, idx + 1) : Math.max(0, idx - 1);
|
|
9822
11106
|
const nextItem = items[nextIdx];
|
|
9823
11107
|
if (nextItem && nextItem !== target && nextItem.dataset.path)
|
|
9824
11108
|
prefetchByPath(nextItem.dataset.path);
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
|
|
9829
|
-
|
|
9830
|
-
}
|
|
9831
|
-
|
|
9832
|
-
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
|
|
11109
|
+
return true;
|
|
11110
|
+
}
|
|
11111
|
+
if (action === "sidebar-page-down" || action === "sidebar-page-up") {
|
|
11112
|
+
moveActiveSidebarPage(action === "sidebar-page-down" ? 1 : -1);
|
|
11113
|
+
return true;
|
|
11114
|
+
}
|
|
11115
|
+
if (action === "sidebar-expand") {
|
|
11116
|
+
if (!isRepositorySidebarMode())
|
|
11117
|
+
return false;
|
|
11118
|
+
toggleActiveSidebarDirectoryCollapsed();
|
|
11119
|
+
return true;
|
|
11120
|
+
}
|
|
11121
|
+
if (action === "sidebar-collapse") {
|
|
11122
|
+
if (!isRepositorySidebarMode())
|
|
11123
|
+
return false;
|
|
11124
|
+
setActiveSidebarDirectoryCollapsed(true);
|
|
11125
|
+
return true;
|
|
11126
|
+
}
|
|
11127
|
+
if (action === "scroll-main-down" || action === "scroll-main-up") {
|
|
11128
|
+
scrollMainPanel(action === "scroll-main-down" ? 1 : -1, repeated);
|
|
11129
|
+
return true;
|
|
11130
|
+
}
|
|
11131
|
+
if (action === "scroll-main-page-down" || action === "scroll-main-page-up") {
|
|
11132
|
+
scrollMainPanel(action === "scroll-main-page-down" ? 1 : -1, repeated, "page");
|
|
11133
|
+
return true;
|
|
11134
|
+
}
|
|
11135
|
+
if (action === "tab-preview" || action === "tab-code") {
|
|
11136
|
+
return switchSourceTab(action === "tab-preview" ? "preview" : "code");
|
|
11137
|
+
}
|
|
11138
|
+
if (action === "start-g-sequence") {
|
|
11139
|
+
PENDING_G_SCOPE = scope;
|
|
11140
|
+
PENDING_G_UNTIL = performance.now() + 900;
|
|
11141
|
+
return true;
|
|
11142
|
+
}
|
|
11143
|
+
if (action === "goto-top" || action === "goto-bottom") {
|
|
11144
|
+
const edge = action === "goto-top" ? "top" : "bottom";
|
|
11145
|
+
if (scope === "main")
|
|
11146
|
+
scrollMainToEdge(edge);
|
|
11147
|
+
else if (scope === "sidebar")
|
|
11148
|
+
moveActiveSidebarToEdge(edge);
|
|
11149
|
+
else
|
|
11150
|
+
window.scrollTo({ top: edge === "top" ? 0 : Math.max(document.documentElement.scrollHeight, document.body.scrollHeight), behavior: "auto" });
|
|
11151
|
+
return true;
|
|
11152
|
+
}
|
|
11153
|
+
if (action === "layout-unified") {
|
|
9836
11154
|
setLayout("line-by-line");
|
|
9837
|
-
|
|
11155
|
+
return true;
|
|
11156
|
+
}
|
|
11157
|
+
if (action === "layout-split") {
|
|
9838
11158
|
setLayout("side-by-side");
|
|
9839
|
-
|
|
11159
|
+
return true;
|
|
11160
|
+
}
|
|
11161
|
+
if (action === "toggle-theme") {
|
|
9840
11162
|
$("#theme").click();
|
|
11163
|
+
return true;
|
|
11164
|
+
}
|
|
11165
|
+
return false;
|
|
11166
|
+
}
|
|
11167
|
+
document.addEventListener("keydown", (e2) => {
|
|
11168
|
+
const targetEl = e2.target;
|
|
11169
|
+
const scope = keymapScope(targetEl);
|
|
11170
|
+
const action = resolveKeymapAction(e2, {
|
|
11171
|
+
scope,
|
|
11172
|
+
editable: isEditableKeyTarget(targetEl),
|
|
11173
|
+
composing: e2.isComposing,
|
|
11174
|
+
paletteOpen: !!PALETTE,
|
|
11175
|
+
pendingG: PENDING_G_SCOPE === scope && performance.now() <= PENDING_G_UNTIL,
|
|
11176
|
+
lightboxOpen: !!document.querySelector(".mkdp-lightbox")
|
|
11177
|
+
});
|
|
11178
|
+
if (!action)
|
|
11179
|
+
return;
|
|
11180
|
+
if (dispatchKeymapAction(action, scope, e2.repeat))
|
|
11181
|
+
e2.preventDefault();
|
|
9841
11182
|
});
|
|
9842
11183
|
applyTheme();
|
|
9843
11184
|
setLayout(STATE.layout);
|
|
@@ -9864,6 +11205,12 @@
|
|
|
9864
11205
|
}).catch(() => setStatus("error"));
|
|
9865
11206
|
}
|
|
9866
11207
|
function load(options = {}) {
|
|
11208
|
+
if (STATE.route.screen === "help") {
|
|
11209
|
+
setStatus("live");
|
|
11210
|
+
renderHelpPage();
|
|
11211
|
+
syncHeaderMenu();
|
|
11212
|
+
return Promise.resolve();
|
|
11213
|
+
}
|
|
9867
11214
|
if (STATE.route.screen === "repo")
|
|
9868
11215
|
return loadRepo();
|
|
9869
11216
|
setStatus("refreshing");
|
|
@@ -9882,7 +11229,10 @@
|
|
|
9882
11229
|
setStatus("live");
|
|
9883
11230
|
}).catch(() => setStatus("error"));
|
|
9884
11231
|
}
|
|
9885
|
-
if (STATE.route.screen === "
|
|
11232
|
+
if (STATE.route.screen === "help") {
|
|
11233
|
+
setStatus("live");
|
|
11234
|
+
renderHelpPage();
|
|
11235
|
+
} else if (STATE.route.screen === "repo")
|
|
9886
11236
|
loadRepo();
|
|
9887
11237
|
else if (STATE.route.screen === "file" && STATE.route.view === "blob") {
|
|
9888
11238
|
setStatus("live");
|
|
@@ -9905,10 +11255,13 @@
|
|
|
9905
11255
|
const range = currentRange();
|
|
9906
11256
|
if (STATE.route.screen === "file") {
|
|
9907
11257
|
setRoute({ screen: "file", path: STATE.route.path, ref: STATE.route.ref, range }, true);
|
|
11258
|
+
} else if (STATE.route.screen === "help") {
|
|
11259
|
+
setRoute({ screen: "help", lang: helpLanguageFromRoute(), section: helpSectionFromRoute(), range }, true);
|
|
11260
|
+
renderHelpPage();
|
|
9908
11261
|
} else {
|
|
9909
11262
|
setRoute({ screen: "diff", range }, true);
|
|
11263
|
+
load();
|
|
9910
11264
|
}
|
|
9911
|
-
load();
|
|
9912
11265
|
}
|
|
9913
11266
|
syncRefInputs();
|
|
9914
11267
|
syncHeaderMenu();
|
|
@@ -10109,6 +11462,13 @@
|
|
|
10109
11462
|
STATE.repoRef = STATE.route.ref || "worktree";
|
|
10110
11463
|
syncRefInputs();
|
|
10111
11464
|
syncHeaderMenu();
|
|
11465
|
+
if (STATE.route.screen === "help") {
|
|
11466
|
+
cancelActiveSourceLoad("navigation");
|
|
11467
|
+
setPageMode();
|
|
11468
|
+
renderHelpPage();
|
|
11469
|
+
setStatus("live");
|
|
11470
|
+
return;
|
|
11471
|
+
}
|
|
10112
11472
|
if (STATE.route.screen === "repo") {
|
|
10113
11473
|
cancelActiveSourceLoad("navigation");
|
|
10114
11474
|
setPageMode();
|