@youtyan/code-viewer 0.1.10 → 0.1.11
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 +115 -20
- package/web/shiki.js +2 -1
- package/web/style.css +8 -0
- package/web-src/server/cache.ts +51 -0
- package/web-src/server/preview.ts +10 -7
package/package.json
CHANGED
package/web/app.js
CHANGED
|
@@ -6393,6 +6393,7 @@
|
|
|
6393
6393
|
const VIRTUAL_SOURCE_ROW_HEIGHT = 20;
|
|
6394
6394
|
const VIRTUAL_SOURCE_HIGHLIGHT_MAX_LINE_LENGTH = 2000;
|
|
6395
6395
|
let highlightLoadPromise = null;
|
|
6396
|
+
let sourceShikiLoadPromise = null;
|
|
6396
6397
|
let highlightConfigured = false;
|
|
6397
6398
|
let PROJECT_NAME = "";
|
|
6398
6399
|
let REPO_SIDEBAR_REF = null;
|
|
@@ -6498,6 +6499,100 @@
|
|
|
6498
6499
|
});
|
|
6499
6500
|
return highlightLoadPromise;
|
|
6500
6501
|
}
|
|
6502
|
+
const SOURCE_SHIKI_LANGS = Array.from(new Set([
|
|
6503
|
+
"bash",
|
|
6504
|
+
"bibtex",
|
|
6505
|
+
"c",
|
|
6506
|
+
"clojure",
|
|
6507
|
+
"cmake",
|
|
6508
|
+
"cpp",
|
|
6509
|
+
"csharp",
|
|
6510
|
+
"css",
|
|
6511
|
+
"dart",
|
|
6512
|
+
"diff",
|
|
6513
|
+
"dockerfile",
|
|
6514
|
+
"elixir",
|
|
6515
|
+
"erlang",
|
|
6516
|
+
"fortran",
|
|
6517
|
+
"go",
|
|
6518
|
+
"gradle",
|
|
6519
|
+
"graphql",
|
|
6520
|
+
"haskell",
|
|
6521
|
+
"html",
|
|
6522
|
+
"java",
|
|
6523
|
+
"javascript",
|
|
6524
|
+
"json",
|
|
6525
|
+
"julia",
|
|
6526
|
+
"kotlin",
|
|
6527
|
+
"lua",
|
|
6528
|
+
"make",
|
|
6529
|
+
"markdown",
|
|
6530
|
+
"nix",
|
|
6531
|
+
"ocaml",
|
|
6532
|
+
"perl",
|
|
6533
|
+
"php",
|
|
6534
|
+
"properties",
|
|
6535
|
+
"protobuf",
|
|
6536
|
+
"python",
|
|
6537
|
+
"r",
|
|
6538
|
+
"rst",
|
|
6539
|
+
"ruby",
|
|
6540
|
+
"rust",
|
|
6541
|
+
"scala",
|
|
6542
|
+
"scss",
|
|
6543
|
+
"sql",
|
|
6544
|
+
"swift",
|
|
6545
|
+
"terraform",
|
|
6546
|
+
"tex",
|
|
6547
|
+
"toml",
|
|
6548
|
+
"typescript",
|
|
6549
|
+
"vim",
|
|
6550
|
+
"vue",
|
|
6551
|
+
"xml",
|
|
6552
|
+
"yaml"
|
|
6553
|
+
]));
|
|
6554
|
+
const SOURCE_SHIKI_LANG_ALIASES = {
|
|
6555
|
+
makefile: "make",
|
|
6556
|
+
objectivec: "c",
|
|
6557
|
+
"objective-c": "c",
|
|
6558
|
+
"objective-cpp": "cpp",
|
|
6559
|
+
starlark: "python"
|
|
6560
|
+
};
|
|
6561
|
+
function normalizeSourceShikiLang(lang) {
|
|
6562
|
+
if (!lang)
|
|
6563
|
+
return null;
|
|
6564
|
+
return SOURCE_SHIKI_LANG_ALIASES[lang] || lang;
|
|
6565
|
+
}
|
|
6566
|
+
function loadSourceShikiHighlighter() {
|
|
6567
|
+
if (!sourceShikiLoadPromise) {
|
|
6568
|
+
sourceShikiLoadPromise = import("/shiki.js").then((mod) => {
|
|
6569
|
+
const typed = mod;
|
|
6570
|
+
const langs = typed.bundledLanguages ? SOURCE_SHIKI_LANGS.filter((lang) => !!typed.bundledLanguages?.[lang]) : SOURCE_SHIKI_LANGS;
|
|
6571
|
+
return typed.createHighlighter({
|
|
6572
|
+
themes: ["github-light", "github-dark"],
|
|
6573
|
+
langs
|
|
6574
|
+
});
|
|
6575
|
+
}).catch(() => null);
|
|
6576
|
+
}
|
|
6577
|
+
return sourceShikiLoadPromise;
|
|
6578
|
+
}
|
|
6579
|
+
function sourceShikiLines(textValue, lang, highlighter) {
|
|
6580
|
+
try {
|
|
6581
|
+
const html = highlighter.codeToHtml(textValue || " ", {
|
|
6582
|
+
lang,
|
|
6583
|
+
themes: { light: "github-light", dark: "github-dark" },
|
|
6584
|
+
defaultColor: false
|
|
6585
|
+
});
|
|
6586
|
+
const template = document.createElement("template");
|
|
6587
|
+
template.innerHTML = html;
|
|
6588
|
+
const renderedLines = Array.from(template.content.querySelectorAll(".line"));
|
|
6589
|
+
if (!renderedLines.length)
|
|
6590
|
+
return null;
|
|
6591
|
+
return renderedLines.map((line) => line.innerHTML || " ");
|
|
6592
|
+
} catch {
|
|
6593
|
+
return null;
|
|
6594
|
+
}
|
|
6595
|
+
}
|
|
6501
6596
|
function rerenderLoadedDiffs() {
|
|
6502
6597
|
document.querySelectorAll(".gdp-file-shell.loaded").forEach((card) => {
|
|
6503
6598
|
const data = card._diffData;
|
|
@@ -6565,10 +6660,10 @@
|
|
|
6565
6660
|
}
|
|
6566
6661
|
function setFolderIcon(el, collapsed) {
|
|
6567
6662
|
const path = collapsed ? FOLDER_ICON_PATHS.closed : FOLDER_ICON_PATHS.open;
|
|
6568
|
-
el.innerHTML = '<svg class="octicon octicon-file-directory-' + (collapsed ? "fill" : "open-fill") + '" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" aria-hidden="true"
|
|
6663
|
+
el.innerHTML = '<svg class="octicon octicon-file-directory-' + (collapsed ? "fill" : "open-fill") + '" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" aria-hidden="true"><path fill="currentColor" d="' + path + '"></path></svg>';
|
|
6569
6664
|
}
|
|
6570
6665
|
function setChevronIcon(el) {
|
|
6571
|
-
el.innerHTML = '<svg class="octicon octicon-chevron-down" viewBox="0 0 12 12" width="12" height="12" fill="currentColor" aria-hidden="true"
|
|
6666
|
+
el.innerHTML = '<svg class="octicon octicon-chevron-down" viewBox="0 0 12 12" width="12" height="12" fill="currentColor" aria-hidden="true"><path fill="currentColor" d="' + CHEVRON_DOWN_12_PATH + '"></path></svg>';
|
|
6572
6667
|
}
|
|
6573
6668
|
function iconSvg(className, paths) {
|
|
6574
6669
|
const pathList = Array.isArray(paths) ? paths : [paths];
|
|
@@ -6840,7 +6935,7 @@
|
|
|
6840
6935
|
if (meta.totals) {
|
|
6841
6936
|
const t2 = document.createElement("span");
|
|
6842
6937
|
t2.className = "num";
|
|
6843
|
-
t2.innerHTML = '<span class="add">+' + meta.totals.additions + "</span> " + '<span class="del">−' + meta.totals.deletions + "</span>
|
|
6938
|
+
t2.innerHTML = '<span class="add">+' + meta.totals.additions + "</span> " + '<span class="del">−' + meta.totals.deletions + "</span> <span>" + meta.totals.files + " files</span>";
|
|
6844
6939
|
el.appendChild(t2);
|
|
6845
6940
|
}
|
|
6846
6941
|
const u2 = document.createElement("span");
|
|
@@ -7510,7 +7605,7 @@
|
|
|
7510
7605
|
}
|
|
7511
7606
|
const head = document.createElement("div");
|
|
7512
7607
|
head.className = "gdp-shell-header";
|
|
7513
|
-
head.innerHTML = '<span class="status-pill ' + escapeHtml2(f2.status || "M") + '">' + escapeHtml2(f2.status || "M") +
|
|
7608
|
+
head.innerHTML = '<span class="status-pill ' + escapeHtml2(f2.status || "M") + '">' + escapeHtml2(f2.status || "M") + '</span><span class="path">' + escapeHtml2(f2.display_path || f2.path) + '</span><span class="stats"><span class="a">+' + (f2.additions || 0) + "</span>" + '<span class="d">−' + (f2.deletions || 0) + '</span></span><span class="size-tag ' + escapeHtml2(f2.size_class || "") + '">' + escapeHtml2(f2.size_class || "") + "</span>" + '<span class="loading-indicator" hidden>loading…</span>';
|
|
7514
7609
|
card.appendChild(head);
|
|
7515
7610
|
const body = document.createElement("div");
|
|
7516
7611
|
body.className = "gdp-shell-body";
|
|
@@ -7948,7 +8043,7 @@
|
|
|
7948
8043
|
const button = document.createElement("button");
|
|
7949
8044
|
button.className = "gdp-expand-btn";
|
|
7950
8045
|
button.title = spec.title;
|
|
7951
|
-
button.innerHTML = '<svg viewBox="0 0 16 16" width="12" height="12" aria-hidden="true"
|
|
8046
|
+
button.innerHTML = '<svg viewBox="0 0 16 16" width="12" height="12" aria-hidden="true"><path fill="currentColor" d="' + EXPAND_ICON_PATHS[spec.direction] + '"/></svg>';
|
|
7952
8047
|
button.addEventListener("click", (e2) => {
|
|
7953
8048
|
e2.stopPropagation();
|
|
7954
8049
|
if (button.disabled)
|
|
@@ -8046,9 +8141,9 @@
|
|
|
8046
8141
|
const num = sideIndex === 0 ? oldStart + i2 : newStart + i2;
|
|
8047
8142
|
lnHtml = '<td class="d2h-code-side-linenumber d2h-cntx">' + num + "</td>";
|
|
8048
8143
|
} else {
|
|
8049
|
-
lnHtml = '<td class="d2h-code-linenumber d2h-cntx"
|
|
8144
|
+
lnHtml = '<td class="d2h-code-linenumber d2h-cntx"><div class="line-num1">' + (oldStart + i2) + '</div><div class="line-num2">' + (newStart + i2) + "</div></td>";
|
|
8050
8145
|
}
|
|
8051
|
-
tr.innerHTML = lnHtml + '<td class="d2h-cntx"
|
|
8146
|
+
tr.innerHTML = lnHtml + '<td class="d2h-cntx"><div class="' + (isSplit ? "d2h-code-side-line" : "d2h-code-line") + '"><span class="d2h-code-line-prefix"> </span><span class="d2h-code-line-ctn">' + escapeHtmlText(lines[i2]) + "</span></div></td>";
|
|
8052
8147
|
frag.appendChild(tr);
|
|
8053
8148
|
}
|
|
8054
8149
|
tbody.insertBefore(frag, anchor);
|
|
@@ -8562,12 +8657,14 @@
|
|
|
8562
8657
|
header.textContent = target.path + " @ " + target.ref;
|
|
8563
8658
|
}
|
|
8564
8659
|
const lang = inferLang(target.path);
|
|
8565
|
-
const
|
|
8660
|
+
const usesVirtualSource = shouldVirtualizeSource(textValue, lines) && !isVirtualSourceDisabled();
|
|
8661
|
+
const hljsRef = STATE.syntaxHighlight && usesVirtualSource ? await loadSyntaxHighlighter() : null;
|
|
8662
|
+
const sourceShikiRef = STATE.syntaxHighlight && !usesVirtualSource ? await loadSourceShikiHighlighter() : null;
|
|
8566
8663
|
if (signal?.aborted)
|
|
8567
8664
|
return false;
|
|
8568
8665
|
const previewable = isPreviewableSource(target.path);
|
|
8569
8666
|
const tabsHost = card.querySelector(".gdp-file-detail-tabs");
|
|
8570
|
-
if (
|
|
8667
|
+
if (usesVirtualSource) {
|
|
8571
8668
|
const virtualCode = renderVirtualSource(target, textValue, lines, hljsRef, lang);
|
|
8572
8669
|
if (previewable) {
|
|
8573
8670
|
const { tabs: tabs2, codeButton: codeButton2, previewButton: previewButton2 } = createSourceTabs("preview");
|
|
@@ -8623,6 +8720,8 @@
|
|
|
8623
8720
|
const table2 = document.createElement("table");
|
|
8624
8721
|
table2.className = "gdp-source-table";
|
|
8625
8722
|
const tbody = document.createElement("tbody");
|
|
8723
|
+
const sourceShikiLang = normalizeSourceShikiLang(lang);
|
|
8724
|
+
const shikiLines = sourceShikiRef && sourceShikiLang ? sourceShikiLines(textValue, sourceShikiLang, sourceShikiRef) : null;
|
|
8626
8725
|
for (let index = 0;index < lines.length; index++) {
|
|
8627
8726
|
if (signal?.aborted)
|
|
8628
8727
|
return false;
|
|
@@ -8633,13 +8732,9 @@
|
|
|
8633
8732
|
num.textContent = String(index + 1);
|
|
8634
8733
|
const code2 = document.createElement("td");
|
|
8635
8734
|
code2.className = "gdp-source-line-code";
|
|
8636
|
-
if (
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
code2.classList.add("hljs");
|
|
8640
|
-
} catch {
|
|
8641
|
-
code2.textContent = line || " ";
|
|
8642
|
-
}
|
|
8735
|
+
if (shikiLines && shikiLines[index] != null) {
|
|
8736
|
+
code2.innerHTML = shikiLines[index] || " ";
|
|
8737
|
+
code2.classList.add("shiki");
|
|
8643
8738
|
} else {
|
|
8644
8739
|
code2.textContent = line || " ";
|
|
8645
8740
|
}
|
|
@@ -9450,7 +9545,7 @@
|
|
|
9450
9545
|
leftHTML = mediaTag(path, "HEAD");
|
|
9451
9546
|
rightHTML = mediaTag(path, "worktree");
|
|
9452
9547
|
}
|
|
9453
|
-
container.innerHTML = '<div class="media-side"><div class="media-label del">Before</div>' + leftHTML +
|
|
9548
|
+
container.innerHTML = '<div class="media-side"><div class="media-label del">Before</div>' + leftHTML + '</div><div class="media-side"><div class="media-label add">After</div>' + rightHTML + "</div>";
|
|
9454
9549
|
body.replaceWith(container);
|
|
9455
9550
|
}
|
|
9456
9551
|
function setupScrollSpy() {
|
|
@@ -9842,7 +9937,7 @@
|
|
|
9842
9937
|
const [sha, subject, author, when] = c2.split("\t");
|
|
9843
9938
|
if (!sha)
|
|
9844
9939
|
continue;
|
|
9845
|
-
html.push('<div class="rp-item-commit" data-val="' + escapeAttr(sha) + '"
|
|
9940
|
+
html.push('<div class="rp-item-commit" data-val="' + escapeAttr(sha) + '"><div class="row1"><span class="sha">' + escapeHtml2(sha) + '</span><span class="subject" title="' + escapeAttr(subject || "") + '">' + escapeHtml2(subject || "") + '</span></div><div class="row2"><span class="author">' + escapeHtml2(author || "") + '</span><span class="when">' + escapeHtml2(when || "") + "</span></div></div>");
|
|
9846
9941
|
}
|
|
9847
9942
|
} else if (popTab === "branches") {
|
|
9848
9943
|
const branches = (REFS.branches || []).filter(m);
|
|
@@ -9851,7 +9946,7 @@
|
|
|
9851
9946
|
}
|
|
9852
9947
|
for (const b2 of branches) {
|
|
9853
9948
|
const cur = b2 === REFS.current;
|
|
9854
|
-
html.push('<div class="rp-item-ref" data-val="' + escapeAttr(b2) + '"
|
|
9949
|
+
html.push('<div class="rp-item-ref" data-val="' + escapeAttr(b2) + '"><span class="name">' + escapeHtml2(b2) + "</span>" + (cur ? '<span class="badge cur">current</span>' : '<span class="badge">branch</span>') + "</div>");
|
|
9855
9950
|
}
|
|
9856
9951
|
} else if (popTab === "tags") {
|
|
9857
9952
|
const tags = (REFS.tags || []).filter(m);
|
|
@@ -9859,7 +9954,7 @@
|
|
|
9859
9954
|
html.push('<div class="rp-empty">no tags</div>');
|
|
9860
9955
|
}
|
|
9861
9956
|
for (const t2 of tags) {
|
|
9862
|
-
html.push('<div class="rp-item-ref" data-val="' + escapeAttr(t2) + '"
|
|
9957
|
+
html.push('<div class="rp-item-ref" data-val="' + escapeAttr(t2) + '"><span class="name">' + escapeHtml2(t2) + '</span><span class="badge">tag</span></div>');
|
|
9863
9958
|
}
|
|
9864
9959
|
}
|
|
9865
9960
|
popBody.innerHTML = html.join("");
|
package/web/shiki.js
CHANGED
|
@@ -13178,5 +13178,6 @@ var createHighlighter = /* @__PURE__ */ createBundledHighlighter({
|
|
|
13178
13178
|
engine: () => (0, engine_oniguruma_exports.createOnigurumaEngine)(Promise.resolve().then(() => (init_wasm2(), exports_wasm2)))
|
|
13179
13179
|
});
|
|
13180
13180
|
export {
|
|
13181
|
-
createHighlighter
|
|
13181
|
+
createHighlighter,
|
|
13182
|
+
bundledLanguages
|
|
13182
13183
|
};
|
package/web/style.css
CHANGED
|
@@ -1660,6 +1660,14 @@ table.d2h-diff-table .d2h-code-line-prefix {
|
|
|
1660
1660
|
.gdp-source-line-code.hljs {
|
|
1661
1661
|
background: var(--bg);
|
|
1662
1662
|
}
|
|
1663
|
+
.gdp-source-line-code.shiki,
|
|
1664
|
+
.gdp-source-line-code.shiki span {
|
|
1665
|
+
color: var(--shiki-light) !important;
|
|
1666
|
+
}
|
|
1667
|
+
[data-theme="dark"] .gdp-source-line-code.shiki,
|
|
1668
|
+
[data-theme="dark"] .gdp-source-line-code.shiki span {
|
|
1669
|
+
color: var(--shiki-dark) !important;
|
|
1670
|
+
}
|
|
1663
1671
|
.gdp-source-virtual {
|
|
1664
1672
|
display: grid;
|
|
1665
1673
|
grid-template-rows: auto minmax(0, 1fr);
|
package/web-src/server/cache.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { lstatSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
1
4
|
// Short enough that a browser reload self-heals stale git data, while still
|
|
2
5
|
// coalescing bursts from one render pass.
|
|
3
6
|
export const CACHE_TTL_MS = 1500;
|
|
7
|
+
export const MAX_TIMED_CACHE_ENTRIES = 200;
|
|
4
8
|
|
|
5
9
|
export type TimedCacheEntry<T> = T & { storedAt: number };
|
|
6
10
|
|
|
@@ -11,3 +15,50 @@ export function cacheFresh<T>(
|
|
|
11
15
|
): cached is TimedCacheEntry<T> {
|
|
12
16
|
return !!cached && now - cached.storedAt <= ttlMs;
|
|
13
17
|
}
|
|
18
|
+
|
|
19
|
+
export function setTimedCacheEntry<T>(
|
|
20
|
+
cache: Map<string, TimedCacheEntry<T>>,
|
|
21
|
+
key: string,
|
|
22
|
+
value: T,
|
|
23
|
+
now = Date.now(),
|
|
24
|
+
maxEntries = MAX_TIMED_CACHE_ENTRIES,
|
|
25
|
+
): void {
|
|
26
|
+
cache.set(key, { ...value, storedAt: now });
|
|
27
|
+
while (cache.size > maxEntries) {
|
|
28
|
+
const oldest = cache.keys().next().value;
|
|
29
|
+
if (oldest === undefined) break;
|
|
30
|
+
cache.delete(oldest);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function worktreeFileSignature(path: string, cwd: string): string {
|
|
35
|
+
try {
|
|
36
|
+
const stats = lstatSync(join(cwd, path));
|
|
37
|
+
const inode = 'ino' in stats ? stats.ino : 0;
|
|
38
|
+
return `state:file|size:${stats.size}|mtime:${stats.mtimeMs}|ctime:${stats.ctimeMs}|ino:${inode}`;
|
|
39
|
+
} catch {
|
|
40
|
+
return 'state:missing';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function fileDiffCacheKey(options: {
|
|
45
|
+
path: string;
|
|
46
|
+
oldPath?: string | null;
|
|
47
|
+
isUntracked: boolean;
|
|
48
|
+
range: { from?: string; to?: string };
|
|
49
|
+
extras: string[];
|
|
50
|
+
args: string[];
|
|
51
|
+
cwd: string;
|
|
52
|
+
}): string {
|
|
53
|
+
const worktreeTarget = options.range.from === 'worktree' || !options.range.to || options.range.to === 'worktree';
|
|
54
|
+
if (options.isUntracked && !worktreeTarget) {
|
|
55
|
+
throw new Error('untracked file diffs require a worktree range');
|
|
56
|
+
}
|
|
57
|
+
const signature = worktreeTarget
|
|
58
|
+
? `\0${worktreeFileSignature(options.path, options.cwd)}`
|
|
59
|
+
: '';
|
|
60
|
+
if (options.isUntracked) {
|
|
61
|
+
return `u\0${options.path}${signature}\0${options.extras.join('\0')}`;
|
|
62
|
+
}
|
|
63
|
+
return `t\0${options.path}\0${options.oldPath || ''}${signature}\0${[...options.extras, ...options.args].join('\0')}`;
|
|
64
|
+
}
|
|
@@ -4,7 +4,7 @@ import { closeSync, constants, existsSync, openSync, readFileSync, realpathSync,
|
|
|
4
4
|
import { basename, dirname, extname, join, normalize, relative } from 'node:path';
|
|
5
5
|
import { APP_ENTRY_PATHS, SPA_PATHS } from '../routes';
|
|
6
6
|
import type { DiffMeta, FileDiffResponse, FileMeta, FileRangeResponse, RepoTreeResponse } from '../types';
|
|
7
|
-
import { cacheFresh, type TimedCacheEntry } from './cache';
|
|
7
|
+
import { cacheFresh, fileDiffCacheKey, setTimedCacheEntry, type TimedCacheEntry } from './cache';
|
|
8
8
|
import { startDevAssetReload } from './dev-assets';
|
|
9
9
|
import * as git from './git';
|
|
10
10
|
import { isSameWorktreeRange } from './range';
|
|
@@ -273,14 +273,14 @@ function handleDiffJson(url: URL) {
|
|
|
273
273
|
fileCache.clear();
|
|
274
274
|
}
|
|
275
275
|
const body = JSON.stringify(payload);
|
|
276
|
-
metaCache
|
|
276
|
+
setTimedCacheEntry(metaCache, key, { body, sig });
|
|
277
277
|
return new Response(body, { headers: { 'Content-Type': 'application/json; charset=utf-8', 'Cache-Control': 'no-store' } });
|
|
278
278
|
}
|
|
279
279
|
const cached = metaCache.get(key);
|
|
280
280
|
if (cacheFresh(cached)) return new Response(cached.body, { headers: { 'Content-Type': 'application/json; charset=utf-8', 'Cache-Control': 'no-store' } });
|
|
281
281
|
const payload = computePayload(extras, range);
|
|
282
282
|
const body = JSON.stringify(payload);
|
|
283
|
-
metaCache
|
|
283
|
+
setTimedCacheEntry(metaCache, key, { body, sig: JSON.stringify({ ...payload, generation: undefined }) });
|
|
284
284
|
return new Response(body, { headers: { 'Content-Type': 'application/json; charset=utf-8', 'Cache-Control': 'no-store' } });
|
|
285
285
|
}
|
|
286
286
|
|
|
@@ -420,9 +420,12 @@ function handleFileDiff(url: URL) {
|
|
|
420
420
|
}
|
|
421
421
|
const { args } = buildRangeArgs(range);
|
|
422
422
|
const oldPath = url.searchParams.get('old_path');
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
423
|
+
let cacheKey: string;
|
|
424
|
+
try {
|
|
425
|
+
cacheKey = fileDiffCacheKey({ path, oldPath, isUntracked, range, extras, args, cwd });
|
|
426
|
+
} catch {
|
|
427
|
+
return text('invalid diff range', 400);
|
|
428
|
+
}
|
|
426
429
|
const cached = fileCache.get(cacheKey);
|
|
427
430
|
let diffText: string;
|
|
428
431
|
let errText = '';
|
|
@@ -436,7 +439,7 @@ function handleFileDiff(url: URL) {
|
|
|
436
439
|
diffText = res.stdout || '';
|
|
437
440
|
if (res.code !== 0) errText = res.stderr;
|
|
438
441
|
}
|
|
439
|
-
fileCache
|
|
442
|
+
setTimedCacheEntry(fileCache, cacheKey, { diffText });
|
|
440
443
|
}
|
|
441
444
|
const mode = url.searchParams.get('mode') || 'full';
|
|
442
445
|
const truncated = mode === 'preview'
|