@youtyan/code-viewer 0.1.42 → 0.1.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/code-viewer.js +146 -2
- package/package.json +1 -1
- package/web/app.js +677 -6
- package/web/index.html +24 -0
- package/web/style.css +260 -1
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ Requires Node.js 20 or newer when installed from npm. Development uses
|
|
|
9
9
|
|
|
10
10
|
- Browse repository files and folders in a persistent sidebar.
|
|
11
11
|
- View git diffs with unified or split layout, lazy loading, and viewed-file state.
|
|
12
|
+
- Browse commit history per branch and open any commit's changed files and diff, with shareable `/history?commit=<sha>` links.
|
|
12
13
|
- Open files directly from the repository or diff view, including large generated files.
|
|
13
14
|
- Preview Markdown with a table of contents, task lists, Mermaid diagrams, and Shiki code highlighting.
|
|
14
15
|
- Preview browser-safe media and show metadata for binary files that cannot be rendered.
|
package/dist/code-viewer.js
CHANGED
|
@@ -633,6 +633,127 @@ function refCommits(cwd, query = "", max = DEFAULT_REF_COMMIT_LIMIT) {
|
|
|
633
633
|
]);
|
|
634
634
|
return mergeCommitResults(limit, hashMatches, subjectMatches, authorMatches);
|
|
635
635
|
}
|
|
636
|
+
function parseRemoteWebUrl(remote) {
|
|
637
|
+
const raw = (remote || "").trim();
|
|
638
|
+
if (!raw)
|
|
639
|
+
return null;
|
|
640
|
+
const sshShorthand = /^[\w.-]+@([\w.-]+):(.+?)(?:\.git)?\/?$/.exec(raw);
|
|
641
|
+
if (sshShorthand)
|
|
642
|
+
return `https://${sshShorthand[1]}/${sshShorthand[2]}`;
|
|
643
|
+
const sshUrl = /^ssh:\/\/(?:[\w.-]+@)?([\w.-]+)(?::\d+)?\/(.+?)(?:\.git)?\/?$/.exec(raw);
|
|
644
|
+
if (sshUrl)
|
|
645
|
+
return `https://${sshUrl[1]}/${sshUrl[2]}`;
|
|
646
|
+
const httpUrl = /^https?:\/\/([\w.-]+)\/(.+?)(?:\.git)?\/?$/.exec(raw);
|
|
647
|
+
if (httpUrl)
|
|
648
|
+
return `https://${httpUrl[1]}/${httpUrl[2]}`;
|
|
649
|
+
return null;
|
|
650
|
+
}
|
|
651
|
+
function remoteWebUrl(cwd) {
|
|
652
|
+
const res = run(["git", "remote", "get-url", "origin"], cwd);
|
|
653
|
+
if (res.code !== 0)
|
|
654
|
+
return null;
|
|
655
|
+
return parseRemoteWebUrl(res.stdout.trim());
|
|
656
|
+
}
|
|
657
|
+
function parseHistoryLog(stdout) {
|
|
658
|
+
const parts = stdout.split("\x00");
|
|
659
|
+
const commits = [];
|
|
660
|
+
for (let index = 0;index < parts.length; ) {
|
|
661
|
+
if (!parts[index]) {
|
|
662
|
+
index++;
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
const sha = parts[index++] || "";
|
|
666
|
+
const subject = parts[index++] || "";
|
|
667
|
+
const author = parts[index++] || "";
|
|
668
|
+
const when = parts[index++] || "";
|
|
669
|
+
const parentsRaw = (parts[index++] || "").trim();
|
|
670
|
+
const body = (parts[index++] || "").trim();
|
|
671
|
+
if (sha)
|
|
672
|
+
commits.push({
|
|
673
|
+
sha,
|
|
674
|
+
subject,
|
|
675
|
+
author,
|
|
676
|
+
when,
|
|
677
|
+
parents: parentsRaw ? parentsRaw.split(/\s+/) : [],
|
|
678
|
+
body
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
return commits;
|
|
682
|
+
}
|
|
683
|
+
function historyQueryArgs(query) {
|
|
684
|
+
const trimmed = query.trim().slice(0, 200).replace(/\0/g, "");
|
|
685
|
+
if (!trimmed)
|
|
686
|
+
return { filterArgs: [], pathspec: [], shaTerm: "" };
|
|
687
|
+
const prefixed = /^(author|path):(.*)$/.exec(trimmed);
|
|
688
|
+
if (prefixed) {
|
|
689
|
+
const term = prefixed[2].trim();
|
|
690
|
+
if (!term)
|
|
691
|
+
return { filterArgs: [], pathspec: [], shaTerm: "" };
|
|
692
|
+
if (prefixed[1] === "author") {
|
|
693
|
+
return {
|
|
694
|
+
filterArgs: [
|
|
695
|
+
"--regexp-ignore-case",
|
|
696
|
+
"--fixed-strings",
|
|
697
|
+
`--author=${term}`
|
|
698
|
+
],
|
|
699
|
+
pathspec: [],
|
|
700
|
+
shaTerm: ""
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
return {
|
|
704
|
+
filterArgs: [],
|
|
705
|
+
pathspec: ["--", `:(icase)*${term}*`],
|
|
706
|
+
shaTerm: ""
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
return {
|
|
710
|
+
filterArgs: [
|
|
711
|
+
"--regexp-ignore-case",
|
|
712
|
+
"--fixed-strings",
|
|
713
|
+
`--grep=${trimmed}`
|
|
714
|
+
],
|
|
715
|
+
pathspec: [],
|
|
716
|
+
shaTerm: /^[0-9a-f]{4,40}$/i.test(trimmed) ? trimmed : ""
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
function commitHistory(cwd, options) {
|
|
720
|
+
const ref = (options.ref || "HEAD").trim();
|
|
721
|
+
if (!ref || ref.startsWith("-") || ref.includes("\x00"))
|
|
722
|
+
return { commits: [], hasMore: false, error: "invalid ref" };
|
|
723
|
+
const verified = run(["git", "rev-parse", "--verify", `${ref}^{commit}`], cwd);
|
|
724
|
+
if (verified.code !== 0)
|
|
725
|
+
return { commits: [], hasMore: false, error: "unknown ref" };
|
|
726
|
+
const skip = Math.max(0, Math.floor(options.skip) || 0);
|
|
727
|
+
const limit = Math.max(1, Math.min(Math.floor(options.limit) || 1, MAX_HISTORY_LIMIT));
|
|
728
|
+
const { filterArgs, pathspec, shaTerm } = historyQueryArgs(options.query || "");
|
|
729
|
+
const res = run([
|
|
730
|
+
"git",
|
|
731
|
+
"log",
|
|
732
|
+
"-z",
|
|
733
|
+
`--skip=${skip}`,
|
|
734
|
+
`--max-count=${limit + 1}`,
|
|
735
|
+
`--format=${HISTORY_FORMAT}`,
|
|
736
|
+
...filterArgs,
|
|
737
|
+
verified.stdout.trim(),
|
|
738
|
+
...pathspec
|
|
739
|
+
], cwd);
|
|
740
|
+
if (res.code !== 0)
|
|
741
|
+
return { commits: [], hasMore: false, error: "git log failed" };
|
|
742
|
+
let parsed = parseHistoryLog(res.stdout);
|
|
743
|
+
if (shaTerm && skip === 0) {
|
|
744
|
+
const bySha = run(["git", "rev-parse", "--verify", `${shaTerm}^{commit}`], cwd);
|
|
745
|
+
const sha = bySha.code === 0 ? bySha.stdout.trim() : "";
|
|
746
|
+
if (sha) {
|
|
747
|
+
const single = run(["git", "log", "-z", "-1", `--format=${HISTORY_FORMAT}`, sha], cwd);
|
|
748
|
+
if (single.code === 0) {
|
|
749
|
+
const hit = parseHistoryLog(single.stdout);
|
|
750
|
+
parsed = [...hit, ...parsed.filter((c) => c.sha !== sha)];
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
const hasMore = parsed.length > limit;
|
|
755
|
+
return { commits: hasMore ? parsed.slice(0, limit) : parsed, hasMore };
|
|
756
|
+
}
|
|
636
757
|
function nameStatus(args, cwd) {
|
|
637
758
|
const res = run([
|
|
638
759
|
"git",
|
|
@@ -1051,7 +1172,7 @@ function truncateToNHunks(diffText, n, maxLines = Number.POSITIVE_INFINITY) {
|
|
|
1051
1172
|
lineTruncated
|
|
1052
1173
|
};
|
|
1053
1174
|
}
|
|
1054
|
-
var WORKTREE_RECURSIVE_DEPTH_LIMIT = 32, WORKTREE_RECURSIVE_ENTRY_LIMIT = 50000, DEFAULT_REF_COMMIT_LIMIT = 100, MAX_REF_COMMIT_LIMIT = 500, COMMIT_FORMAT = "%H%x00%s%x00%an%x00%aI", DEFAULT_WORKTREE_OMIT_DIR_NAMES;
|
|
1175
|
+
var WORKTREE_RECURSIVE_DEPTH_LIMIT = 32, WORKTREE_RECURSIVE_ENTRY_LIMIT = 50000, DEFAULT_REF_COMMIT_LIMIT = 100, MAX_REF_COMMIT_LIMIT = 500, COMMIT_FORMAT = "%H%x00%s%x00%an%x00%aI", DEFAULT_WORKTREE_OMIT_DIR_NAMES, HISTORY_FORMAT = "%H%x00%s%x00%an%x00%aI%x00%P%x00%b", MAX_HISTORY_LIMIT = 200;
|
|
1055
1176
|
var init_git = __esm(() => {
|
|
1056
1177
|
init_runtime();
|
|
1057
1178
|
DEFAULT_WORKTREE_OMIT_DIR_NAMES = [
|
|
@@ -1814,7 +1935,13 @@ function normalizeNewDirectoryName(name) {
|
|
|
1814
1935
|
// web-src/core/routes.ts
|
|
1815
1936
|
var SPA_PATHS, APP_ENTRY_PATHS;
|
|
1816
1937
|
var init_routes = __esm(() => {
|
|
1817
|
-
SPA_PATHS = [
|
|
1938
|
+
SPA_PATHS = [
|
|
1939
|
+
"/todif",
|
|
1940
|
+
"/todiff",
|
|
1941
|
+
"/file",
|
|
1942
|
+
"/help",
|
|
1943
|
+
"/history"
|
|
1944
|
+
];
|
|
1818
1945
|
APP_ENTRY_PATHS = ["/", "/index.html"];
|
|
1819
1946
|
});
|
|
1820
1947
|
|
|
@@ -2998,6 +3125,7 @@ function handleTree(url) {
|
|
|
2998
3125
|
function handleSettings() {
|
|
2999
3126
|
return json({
|
|
3000
3127
|
project: basename2(cwd),
|
|
3128
|
+
repo_web_url: remoteWebUrl(cwd),
|
|
3001
3129
|
scope: {
|
|
3002
3130
|
omit_dirs_effective: scopeOmitDirNames,
|
|
3003
3131
|
omit_dirs_built_in: DEFAULT_WORKTREE_OMIT_DIR_NAMES,
|
|
@@ -3159,6 +3287,20 @@ function handleRefCommits(url) {
|
|
|
3159
3287
|
const max = Number.isFinite(parsedMax) && parsedMax > 0 ? parsedMax : undefined;
|
|
3160
3288
|
return json({ commits: refCommits(cwd, query, max) });
|
|
3161
3289
|
}
|
|
3290
|
+
function handleLog(url) {
|
|
3291
|
+
const ref = url.searchParams.get("ref") || "HEAD";
|
|
3292
|
+
const skip = Number(url.searchParams.get("skip") || "0");
|
|
3293
|
+
const limit = Number(url.searchParams.get("limit") || "50");
|
|
3294
|
+
const result = commitHistory(cwd, {
|
|
3295
|
+
ref,
|
|
3296
|
+
skip: Number.isFinite(skip) ? skip : 0,
|
|
3297
|
+
limit: Number.isFinite(limit) ? limit : 50,
|
|
3298
|
+
query: url.searchParams.get("q") || ""
|
|
3299
|
+
});
|
|
3300
|
+
if (result.error)
|
|
3301
|
+
return text(result.error, 400);
|
|
3302
|
+
return json({ commits: result.commits, hasMore: result.hasMore });
|
|
3303
|
+
}
|
|
3162
3304
|
function handleFileDiff(url) {
|
|
3163
3305
|
const path = url.searchParams.get("path") || "";
|
|
3164
3306
|
if (!safePath(path))
|
|
@@ -4201,6 +4343,8 @@ var init_preview = __esm(async () => {
|
|
|
4201
4343
|
return handleGrep(url);
|
|
4202
4344
|
if (url.pathname === "/_commits")
|
|
4203
4345
|
return handleRefCommits(url);
|
|
4346
|
+
if (url.pathname === "/_log")
|
|
4347
|
+
return handleLog(url);
|
|
4204
4348
|
if (url.pathname === "/file_diff")
|
|
4205
4349
|
return handleFileDiff(url);
|
|
4206
4350
|
if (url.pathname === "/file_range")
|
package/package.json
CHANGED
package/web/app.js
CHANGED
|
@@ -438,6 +438,15 @@
|
|
|
438
438
|
lang: params.get("lang") || "en",
|
|
439
439
|
section: params.get("section") || "keybindings"
|
|
440
440
|
};
|
|
441
|
+
case "/history": {
|
|
442
|
+
const commit = params.get("commit") || "";
|
|
443
|
+
return {
|
|
444
|
+
screen: "history",
|
|
445
|
+
ref: params.get("ref") || "HEAD",
|
|
446
|
+
...commit ? { commit } : {},
|
|
447
|
+
range
|
|
448
|
+
};
|
|
449
|
+
}
|
|
441
450
|
default:
|
|
442
451
|
return {
|
|
443
452
|
screen: "unknown",
|
|
@@ -475,6 +484,15 @@
|
|
|
475
484
|
const qs = params.toString();
|
|
476
485
|
return `/help${qs ? `?${qs}` : ""}`;
|
|
477
486
|
}
|
|
487
|
+
case "history": {
|
|
488
|
+
const params = new URLSearchParams;
|
|
489
|
+
if (route.ref && route.ref !== "HEAD")
|
|
490
|
+
params.set("ref", route.ref);
|
|
491
|
+
if (route.commit)
|
|
492
|
+
params.set("commit", route.commit);
|
|
493
|
+
const qs = params.toString();
|
|
494
|
+
return `/history${qs ? `?${qs}` : ""}`;
|
|
495
|
+
}
|
|
478
496
|
case "unknown":
|
|
479
497
|
return "/todif?from=" + encodeURIComponent(route.range.from || "") + "&to=" + encodeURIComponent(route.range.to || "worktree");
|
|
480
498
|
default:
|
|
@@ -7680,6 +7698,114 @@ ${frontmatter.yaml}
|
|
|
7680
7698
|
};
|
|
7681
7699
|
}
|
|
7682
7700
|
|
|
7701
|
+
// web-src/views/diff-line-select.ts
|
|
7702
|
+
var SELECTED_CLASS = "gdp-diff-line-selected";
|
|
7703
|
+
function cardPath(el) {
|
|
7704
|
+
return el.closest(".gdp-file-shell[data-path]")?.dataset.path || "";
|
|
7705
|
+
}
|
|
7706
|
+
function afterLineFromCell(cell) {
|
|
7707
|
+
const sideCell = cell.closest("td.d2h-code-side-linenumber");
|
|
7708
|
+
if (sideCell) {
|
|
7709
|
+
const side = sideCell.closest(".d2h-file-side-diff");
|
|
7710
|
+
const wrapper = sideCell.closest(".d2h-file-wrapper");
|
|
7711
|
+
if (!side || !wrapper)
|
|
7712
|
+
return null;
|
|
7713
|
+
const sides = wrapper.querySelectorAll(".d2h-file-side-diff");
|
|
7714
|
+
if (sides.length < 2 || side !== sides[1])
|
|
7715
|
+
return null;
|
|
7716
|
+
const line2 = Number((sideCell.textContent || "").trim());
|
|
7717
|
+
return Number.isInteger(line2) && line2 > 0 ? line2 : null;
|
|
7718
|
+
}
|
|
7719
|
+
const numCell = cell.closest("td.d2h-code-linenumber");
|
|
7720
|
+
if (!numCell)
|
|
7721
|
+
return null;
|
|
7722
|
+
const raw = (numCell.querySelector(".line-num2")?.textContent || "").trim();
|
|
7723
|
+
const line = Number(raw);
|
|
7724
|
+
return Number.isInteger(line) && line > 0 ? line : null;
|
|
7725
|
+
}
|
|
7726
|
+
function rowsWithAfterLines(card) {
|
|
7727
|
+
const out = [];
|
|
7728
|
+
card.querySelectorAll("table.d2h-diff-table tr").forEach((row) => {
|
|
7729
|
+
const cell = row.querySelector("td.d2h-code-linenumber, td.d2h-code-side-linenumber");
|
|
7730
|
+
if (!cell)
|
|
7731
|
+
return;
|
|
7732
|
+
const line = afterLineFromCell(cell);
|
|
7733
|
+
if (line !== null)
|
|
7734
|
+
out.push({ row, line });
|
|
7735
|
+
});
|
|
7736
|
+
return out;
|
|
7737
|
+
}
|
|
7738
|
+
function createDiffLineSelect(deps) {
|
|
7739
|
+
let drag = null;
|
|
7740
|
+
let selection = null;
|
|
7741
|
+
function clearHighlights() {
|
|
7742
|
+
document.querySelectorAll(`.${SELECTED_CLASS}`).forEach((row) => {
|
|
7743
|
+
row.classList.remove(SELECTED_CLASS);
|
|
7744
|
+
});
|
|
7745
|
+
}
|
|
7746
|
+
function applySelection(next) {
|
|
7747
|
+
selection = next;
|
|
7748
|
+
clearHighlights();
|
|
7749
|
+
if (!next) {
|
|
7750
|
+
deps.pill.hide();
|
|
7751
|
+
return;
|
|
7752
|
+
}
|
|
7753
|
+
const start = Math.min(next.start, next.end);
|
|
7754
|
+
const end = Math.max(next.start, next.end);
|
|
7755
|
+
const card = document.querySelector(`.gdp-file-shell[data-path="${CSS.escape(next.path)}"]`);
|
|
7756
|
+
if (card) {
|
|
7757
|
+
for (const item of rowsWithAfterLines(card)) {
|
|
7758
|
+
if (item.line >= start && item.line <= end)
|
|
7759
|
+
item.row.classList.add(SELECTED_CLASS);
|
|
7760
|
+
}
|
|
7761
|
+
}
|
|
7762
|
+
deps.pill.show(next.path, start, end);
|
|
7763
|
+
}
|
|
7764
|
+
function clear() {
|
|
7765
|
+
drag = null;
|
|
7766
|
+
applySelection(null);
|
|
7767
|
+
}
|
|
7768
|
+
const diff = document.querySelector("#diff");
|
|
7769
|
+
if (!diff)
|
|
7770
|
+
return { clear };
|
|
7771
|
+
diff.addEventListener("mousedown", (e2) => {
|
|
7772
|
+
const target = e2.target;
|
|
7773
|
+
const cell = target.closest("td.d2h-code-linenumber, td.d2h-code-side-linenumber");
|
|
7774
|
+
if (!cell)
|
|
7775
|
+
return;
|
|
7776
|
+
const line = afterLineFromCell(cell);
|
|
7777
|
+
const path = cardPath(cell);
|
|
7778
|
+
if (line === null || !path) {
|
|
7779
|
+
if (selection)
|
|
7780
|
+
clear();
|
|
7781
|
+
return;
|
|
7782
|
+
}
|
|
7783
|
+
e2.preventDefault();
|
|
7784
|
+
drag = { path, start: line };
|
|
7785
|
+
applySelection({ path, start: line, end: line });
|
|
7786
|
+
});
|
|
7787
|
+
diff.addEventListener("mouseover", (e2) => {
|
|
7788
|
+
if (!drag)
|
|
7789
|
+
return;
|
|
7790
|
+
const target = e2.target;
|
|
7791
|
+
const cell = target.closest("td.d2h-code-linenumber, td.d2h-code-side-linenumber");
|
|
7792
|
+
if (!cell || cardPath(cell) !== drag.path)
|
|
7793
|
+
return;
|
|
7794
|
+
const line = afterLineFromCell(cell);
|
|
7795
|
+
if (line === null)
|
|
7796
|
+
return;
|
|
7797
|
+
applySelection({ path: drag.path, start: drag.start, end: line });
|
|
7798
|
+
});
|
|
7799
|
+
document.addEventListener("mouseup", () => {
|
|
7800
|
+
drag = null;
|
|
7801
|
+
});
|
|
7802
|
+
document.addEventListener("keydown", (e2) => {
|
|
7803
|
+
if (e2.key === "Escape" && selection && !drag)
|
|
7804
|
+
clear();
|
|
7805
|
+
});
|
|
7806
|
+
return { clear };
|
|
7807
|
+
}
|
|
7808
|
+
|
|
7683
7809
|
// web-src/core/file-path-copy.ts
|
|
7684
7810
|
function filePathClipboardText(path) {
|
|
7685
7811
|
return path || "";
|
|
@@ -7690,6 +7816,13 @@ ${frontmatter.yaml}
|
|
|
7690
7816
|
const parts = path.split("/").filter(Boolean);
|
|
7691
7817
|
return parts[parts.length - 1] || "";
|
|
7692
7818
|
}
|
|
7819
|
+
function fileReferenceClipboardText(path, start, end) {
|
|
7820
|
+
if (!path)
|
|
7821
|
+
return "";
|
|
7822
|
+
const a2 = Math.max(1, Math.floor(Math.min(start, end)));
|
|
7823
|
+
const b2 = Math.max(1, Math.floor(Math.max(start, end)));
|
|
7824
|
+
return a2 === b2 ? `@${path}#${a2}` : `@${path}#${a2}-${b2}`;
|
|
7825
|
+
}
|
|
7693
7826
|
|
|
7694
7827
|
// web-src/core/ws-highlight.ts
|
|
7695
7828
|
function isWhitespaceOnlyInlineHighlight(text2) {
|
|
@@ -9013,6 +9146,356 @@ ${frontmatter.yaml}
|
|
|
9013
9146
|
return { renderHelpPage };
|
|
9014
9147
|
}
|
|
9015
9148
|
|
|
9149
|
+
// web-src/core/history.ts
|
|
9150
|
+
var EMPTY_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
9151
|
+
var HISTORY_PAGE_SIZE = 50;
|
|
9152
|
+
var HISTORY_AUTO_LOAD_MAX_PAGES = 20;
|
|
9153
|
+
function commitDiffRange(commit) {
|
|
9154
|
+
return { from: commit.parents[0] || EMPTY_TREE_SHA, to: commit.sha };
|
|
9155
|
+
}
|
|
9156
|
+
function shouldContinueAutoLoad(state) {
|
|
9157
|
+
if (state.found)
|
|
9158
|
+
return false;
|
|
9159
|
+
if (!state.hasMore)
|
|
9160
|
+
return false;
|
|
9161
|
+
return state.pagesLoaded < HISTORY_AUTO_LOAD_MAX_PAGES;
|
|
9162
|
+
}
|
|
9163
|
+
var MONTH_NAMES = [
|
|
9164
|
+
"January",
|
|
9165
|
+
"February",
|
|
9166
|
+
"March",
|
|
9167
|
+
"April",
|
|
9168
|
+
"May",
|
|
9169
|
+
"June",
|
|
9170
|
+
"July",
|
|
9171
|
+
"August",
|
|
9172
|
+
"September",
|
|
9173
|
+
"October",
|
|
9174
|
+
"November",
|
|
9175
|
+
"December"
|
|
9176
|
+
];
|
|
9177
|
+
var DAY_MS = 24 * 60 * 60 * 1000;
|
|
9178
|
+
function historyGroupLabel(whenIso, now) {
|
|
9179
|
+
const t2 = Date.parse(whenIso);
|
|
9180
|
+
if (!Number.isFinite(t2))
|
|
9181
|
+
return "Unknown date";
|
|
9182
|
+
const dayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
|
|
9183
|
+
if (t2 >= dayStart)
|
|
9184
|
+
return "Today";
|
|
9185
|
+
if (t2 >= dayStart - DAY_MS)
|
|
9186
|
+
return "Yesterday";
|
|
9187
|
+
if (t2 >= dayStart - 6 * DAY_MS)
|
|
9188
|
+
return "This week";
|
|
9189
|
+
const d2 = new Date(t2);
|
|
9190
|
+
if (d2.getFullYear() === now.getFullYear() && d2.getMonth() === now.getMonth())
|
|
9191
|
+
return "This month";
|
|
9192
|
+
return `${MONTH_NAMES[d2.getMonth()]} ${d2.getFullYear()}`;
|
|
9193
|
+
}
|
|
9194
|
+
|
|
9195
|
+
// web-src/views/history-view.ts
|
|
9196
|
+
function createHistoryView(deps) {
|
|
9197
|
+
const panel = deps.$("#history-panel");
|
|
9198
|
+
const list2 = deps.$("#history-list");
|
|
9199
|
+
const banner = deps.$("#history-banner");
|
|
9200
|
+
const statusEl = deps.$("#history-status");
|
|
9201
|
+
const sentinel = deps.$("#history-sentinel");
|
|
9202
|
+
let ref = "HEAD";
|
|
9203
|
+
let commits = [];
|
|
9204
|
+
let hasMore = false;
|
|
9205
|
+
let loading = false;
|
|
9206
|
+
let generation = 0;
|
|
9207
|
+
let selectedSha = "";
|
|
9208
|
+
let query = "";
|
|
9209
|
+
function setBanner(message) {
|
|
9210
|
+
banner.textContent = message;
|
|
9211
|
+
banner.hidden = !message;
|
|
9212
|
+
}
|
|
9213
|
+
function setStatusText(message) {
|
|
9214
|
+
statusEl.textContent = message;
|
|
9215
|
+
statusEl.hidden = !message;
|
|
9216
|
+
}
|
|
9217
|
+
function relativeWhen(iso) {
|
|
9218
|
+
const t2 = Date.parse(iso);
|
|
9219
|
+
if (!Number.isFinite(t2))
|
|
9220
|
+
return iso;
|
|
9221
|
+
const sec = Math.round((Date.now() - t2) / 1000);
|
|
9222
|
+
if (sec < 60)
|
|
9223
|
+
return "just now";
|
|
9224
|
+
const min = Math.round(sec / 60);
|
|
9225
|
+
if (min < 60)
|
|
9226
|
+
return `${min}m ago`;
|
|
9227
|
+
const hour = Math.round(min / 60);
|
|
9228
|
+
if (hour < 24)
|
|
9229
|
+
return `${hour}h ago`;
|
|
9230
|
+
const day = Math.round(hour / 24);
|
|
9231
|
+
if (day < 30)
|
|
9232
|
+
return `${day}d ago`;
|
|
9233
|
+
return iso.slice(0, 10);
|
|
9234
|
+
}
|
|
9235
|
+
function fetchPage(skip) {
|
|
9236
|
+
const url = `/_log?ref=${encodeURIComponent(ref)}&skip=${skip}&limit=${HISTORY_PAGE_SIZE}` + (query ? `&q=${encodeURIComponent(query)}` : "");
|
|
9237
|
+
return deps.trackLoad(fetch(url).then(async (r2) => {
|
|
9238
|
+
if (!r2.ok)
|
|
9239
|
+
throw new Error(await r2.text());
|
|
9240
|
+
return await r2.json();
|
|
9241
|
+
})).catch((err) => {
|
|
9242
|
+
setBanner(err instanceof Error ? err.message : "failed to load log");
|
|
9243
|
+
return null;
|
|
9244
|
+
});
|
|
9245
|
+
}
|
|
9246
|
+
function commitRow(commit) {
|
|
9247
|
+
const active = commit.sha === selectedSha ? " active" : "";
|
|
9248
|
+
return `<li class="history-item${active}" data-sha="${deps.escapeHtml(commit.sha)}">` + `<span class="subject" title="${deps.escapeHtml(commit.subject)}">${deps.escapeHtml(commit.subject)}</span>` + `<span class="meta2">` + `<span class="sha">${deps.escapeHtml(commit.sha.slice(0, 7))}</span>` + `<span class="author">${deps.escapeHtml(commit.author)}</span>` + `<span class="when">${deps.escapeHtml(relativeWhen(commit.when))}</span>` + `</span>` + `</li>`;
|
|
9249
|
+
}
|
|
9250
|
+
function renderList() {
|
|
9251
|
+
const now = new Date;
|
|
9252
|
+
const html = [];
|
|
9253
|
+
let lastGroup = "";
|
|
9254
|
+
for (const commit of commits) {
|
|
9255
|
+
const group = historyGroupLabel(commit.when, now);
|
|
9256
|
+
if (group !== lastGroup) {
|
|
9257
|
+
html.push(`<li class="history-group" aria-hidden="true">${deps.escapeHtml(group)}</li>`);
|
|
9258
|
+
lastGroup = group;
|
|
9259
|
+
}
|
|
9260
|
+
html.push(commitRow(commit));
|
|
9261
|
+
}
|
|
9262
|
+
list2.innerHTML = html.join("");
|
|
9263
|
+
setStatusText(loading ? "loading..." : commits.length ? "" : "no commits");
|
|
9264
|
+
}
|
|
9265
|
+
function updateCommitInfo(commit) {
|
|
9266
|
+
const info = document.querySelector("#history-commit-info");
|
|
9267
|
+
if (!info)
|
|
9268
|
+
return;
|
|
9269
|
+
if (!commit) {
|
|
9270
|
+
info.hidden = true;
|
|
9271
|
+
return;
|
|
9272
|
+
}
|
|
9273
|
+
const set2 = (sel, text2) => {
|
|
9274
|
+
const el = info.querySelector(sel);
|
|
9275
|
+
if (el)
|
|
9276
|
+
el.textContent = text2;
|
|
9277
|
+
};
|
|
9278
|
+
set2(".hci-sha", commit.sha);
|
|
9279
|
+
set2(".hci-author", commit.author);
|
|
9280
|
+
const t2 = Date.parse(commit.when);
|
|
9281
|
+
set2(".hci-date", Number.isFinite(t2) ? new Date(t2).toLocaleString() : commit.when);
|
|
9282
|
+
set2(".hci-subject", commit.subject);
|
|
9283
|
+
const body = info.querySelector(".hci-body");
|
|
9284
|
+
if (body) {
|
|
9285
|
+
body.textContent = commit.body;
|
|
9286
|
+
body.hidden = !commit.body;
|
|
9287
|
+
}
|
|
9288
|
+
info.hidden = false;
|
|
9289
|
+
}
|
|
9290
|
+
function updateActiveRow() {
|
|
9291
|
+
list2.querySelectorAll(".history-item").forEach((row) => {
|
|
9292
|
+
row.classList.toggle("active", row.dataset.sha === selectedSha);
|
|
9293
|
+
});
|
|
9294
|
+
}
|
|
9295
|
+
let inFlight = null;
|
|
9296
|
+
function loadNextPage() {
|
|
9297
|
+
if (inFlight)
|
|
9298
|
+
return inFlight;
|
|
9299
|
+
const started = doLoadNextPage().finally(() => {
|
|
9300
|
+
if (inFlight === started)
|
|
9301
|
+
inFlight = null;
|
|
9302
|
+
});
|
|
9303
|
+
inFlight = started;
|
|
9304
|
+
return started;
|
|
9305
|
+
}
|
|
9306
|
+
async function doLoadNextPage() {
|
|
9307
|
+
loading = true;
|
|
9308
|
+
const gen = generation;
|
|
9309
|
+
setStatusText("loading...");
|
|
9310
|
+
const page = await fetchPage(commits.length);
|
|
9311
|
+
if (gen !== generation)
|
|
9312
|
+
return false;
|
|
9313
|
+
loading = false;
|
|
9314
|
+
if (!page) {
|
|
9315
|
+
setStatusText("");
|
|
9316
|
+
return false;
|
|
9317
|
+
}
|
|
9318
|
+
commits = commits.concat(page.commits);
|
|
9319
|
+
hasMore = page.hasMore;
|
|
9320
|
+
renderList();
|
|
9321
|
+
return page.commits.length > 0;
|
|
9322
|
+
}
|
|
9323
|
+
async function selectCommit(commit, options = {}) {
|
|
9324
|
+
const gen = generation;
|
|
9325
|
+
selectedSha = commit.sha;
|
|
9326
|
+
updateActiveRow();
|
|
9327
|
+
updateCommitInfo(commit);
|
|
9328
|
+
if (gen !== generation)
|
|
9329
|
+
return;
|
|
9330
|
+
if (options.updateUrl !== false) {
|
|
9331
|
+
const range = commitDiffRange(commit);
|
|
9332
|
+
deps.setRoute({ screen: "history", ref, commit: commit.sha, range }, true);
|
|
9333
|
+
}
|
|
9334
|
+
if (gen !== generation)
|
|
9335
|
+
return;
|
|
9336
|
+
await deps.applyCommitRange(commitDiffRange(commit));
|
|
9337
|
+
if (gen !== generation)
|
|
9338
|
+
return;
|
|
9339
|
+
}
|
|
9340
|
+
let lookupFailed = false;
|
|
9341
|
+
async function fetchSingleCommit(sha) {
|
|
9342
|
+
const url = `/_log?ref=${encodeURIComponent(sha)}&skip=0&limit=1`;
|
|
9343
|
+
lookupFailed = false;
|
|
9344
|
+
try {
|
|
9345
|
+
const res = await deps.trackLoad(fetch(url).then(async (r2) => {
|
|
9346
|
+
if (r2.status === 400)
|
|
9347
|
+
return null;
|
|
9348
|
+
if (!r2.ok)
|
|
9349
|
+
throw new Error(await r2.text());
|
|
9350
|
+
return await r2.json();
|
|
9351
|
+
}));
|
|
9352
|
+
return res?.commits[0] || null;
|
|
9353
|
+
} catch {
|
|
9354
|
+
lookupFailed = true;
|
|
9355
|
+
return null;
|
|
9356
|
+
}
|
|
9357
|
+
}
|
|
9358
|
+
async function resolveDeepLink(sha) {
|
|
9359
|
+
const gen = generation;
|
|
9360
|
+
let pagesLoaded = 0;
|
|
9361
|
+
if (commits.length === 0) {
|
|
9362
|
+
await loadNextPage();
|
|
9363
|
+
if (gen !== generation)
|
|
9364
|
+
return;
|
|
9365
|
+
pagesLoaded = 1;
|
|
9366
|
+
} else {
|
|
9367
|
+
pagesLoaded = 1;
|
|
9368
|
+
}
|
|
9369
|
+
for (;; ) {
|
|
9370
|
+
const found = commits.find((c2) => c2.sha.startsWith(sha));
|
|
9371
|
+
if (found) {
|
|
9372
|
+
await selectCommit(found, { updateUrl: false });
|
|
9373
|
+
scrollToSelected();
|
|
9374
|
+
return;
|
|
9375
|
+
}
|
|
9376
|
+
if (!shouldContinueAutoLoad({ pagesLoaded, found: false, hasMore }))
|
|
9377
|
+
break;
|
|
9378
|
+
const got = await loadNextPage();
|
|
9379
|
+
if (gen !== generation)
|
|
9380
|
+
return;
|
|
9381
|
+
pagesLoaded++;
|
|
9382
|
+
if (!got && !hasMore)
|
|
9383
|
+
break;
|
|
9384
|
+
}
|
|
9385
|
+
const single = await fetchSingleCommit(sha);
|
|
9386
|
+
if (gen !== generation)
|
|
9387
|
+
return;
|
|
9388
|
+
if (!single) {
|
|
9389
|
+
setBanner(lookupFailed ? `failed to load commit: ${sha}` : `commit not found: ${sha}`);
|
|
9390
|
+
updateCommitInfo(null);
|
|
9391
|
+
deps.showEmptyDiffPane();
|
|
9392
|
+
return;
|
|
9393
|
+
}
|
|
9394
|
+
setBanner(`showing commit outside the loaded ${ref} log`);
|
|
9395
|
+
commits = [single, ...commits];
|
|
9396
|
+
renderList();
|
|
9397
|
+
await selectCommit(single, { updateUrl: false });
|
|
9398
|
+
scrollToSelected();
|
|
9399
|
+
}
|
|
9400
|
+
function scrollToSelected() {
|
|
9401
|
+
list2.querySelector(`.history-item[data-sha="${CSS.escape(selectedSha)}"]`)?.scrollIntoView({ block: "center" });
|
|
9402
|
+
}
|
|
9403
|
+
let entering = Promise.resolve();
|
|
9404
|
+
function enterHistory(force) {
|
|
9405
|
+
entering = entering.then(() => doEnterHistory(force === true)).catch(() => {});
|
|
9406
|
+
return entering;
|
|
9407
|
+
}
|
|
9408
|
+
async function doEnterHistory(force = false) {
|
|
9409
|
+
const route = deps.getRoute();
|
|
9410
|
+
if (route.screen !== "history")
|
|
9411
|
+
return;
|
|
9412
|
+
const nextRef = route.ref || "HEAD";
|
|
9413
|
+
const refChanged = nextRef !== ref;
|
|
9414
|
+
if (refChanged || force || commits.length === 0) {
|
|
9415
|
+
generation++;
|
|
9416
|
+
ref = nextRef;
|
|
9417
|
+
commits = [];
|
|
9418
|
+
hasMore = false;
|
|
9419
|
+
loading = false;
|
|
9420
|
+
inFlight = null;
|
|
9421
|
+
selectedSha = "";
|
|
9422
|
+
setBanner("");
|
|
9423
|
+
updateCommitInfo(null);
|
|
9424
|
+
renderList();
|
|
9425
|
+
await loadNextPage();
|
|
9426
|
+
}
|
|
9427
|
+
const route2 = deps.getRoute();
|
|
9428
|
+
if (route2.screen !== "history")
|
|
9429
|
+
return;
|
|
9430
|
+
if (route2.commit) {
|
|
9431
|
+
await resolveDeepLink(route2.commit);
|
|
9432
|
+
} else {
|
|
9433
|
+
selectedSha = "";
|
|
9434
|
+
updateActiveRow();
|
|
9435
|
+
updateCommitInfo(null);
|
|
9436
|
+
deps.showEmptyDiffPane();
|
|
9437
|
+
}
|
|
9438
|
+
}
|
|
9439
|
+
function onRefPicked(nextRef) {
|
|
9440
|
+
const value = nextRef && nextRef !== "worktree" ? nextRef : "HEAD";
|
|
9441
|
+
deps.setRoute({
|
|
9442
|
+
screen: "history",
|
|
9443
|
+
ref: value,
|
|
9444
|
+
range: { from: "HEAD", to: "worktree" }
|
|
9445
|
+
}, false);
|
|
9446
|
+
enterHistory(true);
|
|
9447
|
+
}
|
|
9448
|
+
list2.addEventListener("click", (e2) => {
|
|
9449
|
+
const row = e2.target.closest(".history-item");
|
|
9450
|
+
if (!row?.dataset.sha)
|
|
9451
|
+
return;
|
|
9452
|
+
const commit = commits.find((c2) => c2.sha === row.dataset.sha);
|
|
9453
|
+
if (commit)
|
|
9454
|
+
selectCommit(commit);
|
|
9455
|
+
});
|
|
9456
|
+
function applyFilter(next) {
|
|
9457
|
+
const value = next.trim();
|
|
9458
|
+
if (value === query)
|
|
9459
|
+
return;
|
|
9460
|
+
query = value;
|
|
9461
|
+
generation++;
|
|
9462
|
+
commits = [];
|
|
9463
|
+
hasMore = false;
|
|
9464
|
+
loading = false;
|
|
9465
|
+
inFlight = null;
|
|
9466
|
+
setBanner("");
|
|
9467
|
+
renderList();
|
|
9468
|
+
loadNextPage();
|
|
9469
|
+
}
|
|
9470
|
+
const filterInput = document.querySelector("#history-filter");
|
|
9471
|
+
let filterTimer = null;
|
|
9472
|
+
filterInput?.addEventListener("input", () => {
|
|
9473
|
+
if (filterTimer)
|
|
9474
|
+
clearTimeout(filterTimer);
|
|
9475
|
+
filterTimer = setTimeout(() => {
|
|
9476
|
+
filterTimer = null;
|
|
9477
|
+
applyFilter(filterInput.value);
|
|
9478
|
+
}, 250);
|
|
9479
|
+
});
|
|
9480
|
+
filterInput?.addEventListener("keydown", (e2) => {
|
|
9481
|
+
if (e2.key === "Escape" && filterInput.value) {
|
|
9482
|
+
filterInput.value = "";
|
|
9483
|
+
applyFilter("");
|
|
9484
|
+
e2.stopPropagation();
|
|
9485
|
+
}
|
|
9486
|
+
});
|
|
9487
|
+
const observer = new IntersectionObserver((entries) => {
|
|
9488
|
+
if (!entries.some((entry) => entry.isIntersecting))
|
|
9489
|
+
return;
|
|
9490
|
+
if (deps.getRoute().screen !== "history")
|
|
9491
|
+
return;
|
|
9492
|
+
if (hasMore && !loading)
|
|
9493
|
+
loadNextPage();
|
|
9494
|
+
}, { root: panel, rootMargin: "200px" });
|
|
9495
|
+
observer.observe(sentinel);
|
|
9496
|
+
return { enterHistory, onRefPicked };
|
|
9497
|
+
}
|
|
9498
|
+
|
|
9016
9499
|
// web-src/views/hunk-expand.ts
|
|
9017
9500
|
function createHunkExpand(deps) {
|
|
9018
9501
|
function parseHunkHeader(text2) {
|
|
@@ -9401,6 +9884,76 @@ ${frontmatter.yaml}
|
|
|
9401
9884
|
return { setupHunkExpand };
|
|
9402
9885
|
}
|
|
9403
9886
|
|
|
9887
|
+
// web-src/views/line-ref-pill.ts
|
|
9888
|
+
var COPY_ICON = '<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true" fill="currentColor">' + '<path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"/>' + '<path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"/>' + "</svg>";
|
|
9889
|
+
var CHECK_ICON = '<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true" fill="currentColor">' + '<path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.75.75 0 0 1 1.06-1.06L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"/>' + "</svg>";
|
|
9890
|
+
function escapeHtml2(value) {
|
|
9891
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
9892
|
+
}
|
|
9893
|
+
function createLineRefPill() {
|
|
9894
|
+
const pill = document.createElement("button");
|
|
9895
|
+
pill.id = "line-ref-pill";
|
|
9896
|
+
pill.type = "button";
|
|
9897
|
+
pill.title = "選択行の参照をコピー(Claude Code / Codex に貼り付け用)";
|
|
9898
|
+
pill.hidden = true;
|
|
9899
|
+
document.body.appendChild(pill);
|
|
9900
|
+
let refText = "";
|
|
9901
|
+
let feedbackTimer = null;
|
|
9902
|
+
function render(state) {
|
|
9903
|
+
pill.classList.toggle("copied", state === "copied");
|
|
9904
|
+
if (state === "copied") {
|
|
9905
|
+
pill.innerHTML = `${CHECK_ICON}<span class="lrp-label">Copied!</span>`;
|
|
9906
|
+
return;
|
|
9907
|
+
}
|
|
9908
|
+
if (state === "failed") {
|
|
9909
|
+
pill.innerHTML = `${COPY_ICON}<span class="lrp-label">copy failed</span>`;
|
|
9910
|
+
return;
|
|
9911
|
+
}
|
|
9912
|
+
pill.innerHTML = `${COPY_ICON}<span class="lrp-label">Copy</span>` + `<span class="lrp-ref">${escapeHtml2(refText)}</span>`;
|
|
9913
|
+
}
|
|
9914
|
+
pill.addEventListener("click", async () => {
|
|
9915
|
+
if (!refText)
|
|
9916
|
+
return;
|
|
9917
|
+
try {
|
|
9918
|
+
await navigator.clipboard.writeText(refText);
|
|
9919
|
+
render("copied");
|
|
9920
|
+
} catch {
|
|
9921
|
+
render("failed");
|
|
9922
|
+
}
|
|
9923
|
+
if (feedbackTimer)
|
|
9924
|
+
clearTimeout(feedbackTimer);
|
|
9925
|
+
feedbackTimer = setTimeout(() => {
|
|
9926
|
+
feedbackTimer = null;
|
|
9927
|
+
if (!pill.hidden)
|
|
9928
|
+
render("ready");
|
|
9929
|
+
}, 1200);
|
|
9930
|
+
});
|
|
9931
|
+
return {
|
|
9932
|
+
show(path, start, end) {
|
|
9933
|
+
const next = fileReferenceClipboardText(path, start, end);
|
|
9934
|
+
if (!next)
|
|
9935
|
+
return;
|
|
9936
|
+
const changed = next !== refText;
|
|
9937
|
+
refText = next;
|
|
9938
|
+
if (feedbackTimer) {
|
|
9939
|
+
clearTimeout(feedbackTimer);
|
|
9940
|
+
feedbackTimer = null;
|
|
9941
|
+
}
|
|
9942
|
+
render("ready");
|
|
9943
|
+
if (pill.hidden || changed) {
|
|
9944
|
+
pill.classList.remove("pop");
|
|
9945
|
+
pill.offsetWidth;
|
|
9946
|
+
pill.classList.add("pop");
|
|
9947
|
+
}
|
|
9948
|
+
pill.hidden = false;
|
|
9949
|
+
},
|
|
9950
|
+
hide() {
|
|
9951
|
+
refText = "";
|
|
9952
|
+
pill.hidden = true;
|
|
9953
|
+
}
|
|
9954
|
+
};
|
|
9955
|
+
}
|
|
9956
|
+
|
|
9404
9957
|
// web-src/views/ref-picker.ts
|
|
9405
9958
|
function createRefPicker(deps) {
|
|
9406
9959
|
function wireRefSelectorInput(input, onPick) {
|
|
@@ -9631,7 +10184,7 @@ ${frontmatter.yaml}
|
|
|
9631
10184
|
const target = e2.target;
|
|
9632
10185
|
if (popover.contains(target))
|
|
9633
10186
|
return;
|
|
9634
|
-
if (target.id === "ref-from" || target.id === "ref-to" || target.id === "repo-ref" || target.id === "repo-target")
|
|
10187
|
+
if (target.id === "ref-from" || target.id === "ref-to" || target.id === "repo-ref" || target.id === "repo-target" || target.id === "history-ref")
|
|
9635
10188
|
return;
|
|
9636
10189
|
closePopover();
|
|
9637
10190
|
});
|
|
@@ -14758,6 +15311,11 @@ ${frontmatter.yaml}
|
|
|
14758
15311
|
return null;
|
|
14759
15312
|
const settings = await res.json();
|
|
14760
15313
|
setProjectName(settings.project || "");
|
|
15314
|
+
const repoLink = document.querySelector("#repo-web-link");
|
|
15315
|
+
if (repoLink && settings.repo_web_url) {
|
|
15316
|
+
repoLink.href = settings.repo_web_url;
|
|
15317
|
+
repoLink.hidden = false;
|
|
15318
|
+
}
|
|
14761
15319
|
SERVER_SCOPE_OMIT_DIRS_DEFAULT = normalizeScopeOmitDirs(settings.scope.omit_dirs_effective);
|
|
14762
15320
|
SERVER_SCOPE_EXCLUDE_NAMES_DEFAULT = normalizeScopeExcludeNames(settings.scope.exclude_names_effective);
|
|
14763
15321
|
return settings;
|
|
@@ -14796,6 +15354,19 @@ ${frontmatter.yaml}
|
|
|
14796
15354
|
let highlightConfigured = false;
|
|
14797
15355
|
let PROJECT_NAME = "";
|
|
14798
15356
|
let REPO_SIDEBAR_REF = null;
|
|
15357
|
+
const LINE_REF_PILL = createLineRefPill();
|
|
15358
|
+
const DIFF_LINE_SELECT = createDiffLineSelect({ pill: LINE_REF_PILL });
|
|
15359
|
+
function syncLineRefPill() {
|
|
15360
|
+
const route = STATE.route;
|
|
15361
|
+
if (route.screen === "diff")
|
|
15362
|
+
return;
|
|
15363
|
+
DIFF_LINE_SELECT.clear();
|
|
15364
|
+
if (route.screen === "file" && route.line) {
|
|
15365
|
+
const start = typeof route.line === "number" ? route.line : route.line.start;
|
|
15366
|
+
const end = typeof route.line === "number" ? route.line : route.line.end;
|
|
15367
|
+
LINE_REF_PILL.show(route.path, start, end);
|
|
15368
|
+
}
|
|
15369
|
+
}
|
|
14799
15370
|
const SIDEBAR = createSidebar({
|
|
14800
15371
|
$,
|
|
14801
15372
|
$$,
|
|
@@ -15185,7 +15756,7 @@ ${frontmatter.yaml}
|
|
|
15185
15756
|
throw e2;
|
|
15186
15757
|
});
|
|
15187
15758
|
}
|
|
15188
|
-
function
|
|
15759
|
+
function escapeHtml3(s2) {
|
|
15189
15760
|
return String(s2 == null ? "" : s2).replace(/[&<>"']/g, (c2) => ({
|
|
15190
15761
|
"&": "&",
|
|
15191
15762
|
"<": "<",
|
|
@@ -15203,6 +15774,19 @@ ${frontmatter.yaml}
|
|
|
15203
15774
|
to: STATE.to || DEFAULT_RANGE.to
|
|
15204
15775
|
};
|
|
15205
15776
|
}
|
|
15777
|
+
let preHistoryRange = null;
|
|
15778
|
+
function parkRangeForHistory() {
|
|
15779
|
+
if (preHistoryRange === null)
|
|
15780
|
+
preHistoryRange = { from: STATE.from, to: STATE.to };
|
|
15781
|
+
}
|
|
15782
|
+
function restoreRangeAfterHistory() {
|
|
15783
|
+
if (!preHistoryRange)
|
|
15784
|
+
return;
|
|
15785
|
+
STATE.from = preHistoryRange.from;
|
|
15786
|
+
STATE.to = preHistoryRange.to;
|
|
15787
|
+
preHistoryRange = null;
|
|
15788
|
+
syncRefInputs();
|
|
15789
|
+
}
|
|
15206
15790
|
function repoFileTargetFromRoute() {
|
|
15207
15791
|
return STATE.route.screen === "file" && STATE.route.view === "blob" ? STATE.route.ref : null;
|
|
15208
15792
|
}
|
|
@@ -15233,12 +15817,22 @@ ${frontmatter.yaml}
|
|
|
15233
15817
|
else
|
|
15234
15818
|
history.pushState(state, "", url);
|
|
15235
15819
|
syncHeaderMenu();
|
|
15820
|
+
syncLineRefPill();
|
|
15236
15821
|
}
|
|
15237
15822
|
function setPageMode() {
|
|
15238
15823
|
document.body.classList.toggle("gdp-file-detail-page", STATE.route.screen === "file");
|
|
15239
15824
|
document.body.classList.toggle("gdp-repo-blob-page", STATE.route.screen === "file" && STATE.route.view === "blob");
|
|
15240
15825
|
document.body.classList.toggle("gdp-repo-page", STATE.route.screen === "repo");
|
|
15241
15826
|
document.body.classList.toggle("gdp-help-page", STATE.route.screen === "help");
|
|
15827
|
+
document.body.classList.toggle("gdp-history-page", STATE.route.screen === "history");
|
|
15828
|
+
const historyPanel = $("#history-panel");
|
|
15829
|
+
if (historyPanel)
|
|
15830
|
+
historyPanel.hidden = STATE.route.screen !== "history";
|
|
15831
|
+
if (STATE.route.screen === "history") {
|
|
15832
|
+
const historyRefInput = $("#history-ref");
|
|
15833
|
+
if (historyRefInput)
|
|
15834
|
+
historyRefInput.value = STATE.route.ref || "HEAD";
|
|
15835
|
+
}
|
|
15242
15836
|
syncRepoTargetInput(repoFileTargetFromRoute() || "worktree");
|
|
15243
15837
|
}
|
|
15244
15838
|
function syncHeaderMenu() {
|
|
@@ -15256,7 +15850,17 @@ ${frontmatter.yaml}
|
|
|
15256
15850
|
});
|
|
15257
15851
|
}
|
|
15258
15852
|
if (link2.dataset.route === "diff") {
|
|
15259
|
-
link2.href = buildRoute({
|
|
15853
|
+
link2.href = buildRoute({
|
|
15854
|
+
screen: "diff",
|
|
15855
|
+
range: preHistoryRange ?? currentRange()
|
|
15856
|
+
});
|
|
15857
|
+
}
|
|
15858
|
+
if (link2.dataset.route === "history") {
|
|
15859
|
+
link2.href = buildRoute({
|
|
15860
|
+
screen: "history",
|
|
15861
|
+
ref: "HEAD",
|
|
15862
|
+
range: currentRange()
|
|
15863
|
+
});
|
|
15260
15864
|
}
|
|
15261
15865
|
if (link2.dataset.route === "help") {
|
|
15262
15866
|
link2.href = buildRoute({
|
|
@@ -15379,7 +15983,7 @@ ${frontmatter.yaml}
|
|
|
15379
15983
|
STATE,
|
|
15380
15984
|
setRoute,
|
|
15381
15985
|
currentRange,
|
|
15382
|
-
escapeHtml:
|
|
15986
|
+
escapeHtml: escapeHtml3,
|
|
15383
15987
|
trackLoad,
|
|
15384
15988
|
diffCardSelector,
|
|
15385
15989
|
getHljs,
|
|
@@ -15747,6 +16351,18 @@ ${frontmatter.yaml}
|
|
|
15747
16351
|
}
|
|
15748
16352
|
if (STATE.route.screen === "repo")
|
|
15749
16353
|
return loadRepo();
|
|
16354
|
+
{
|
|
16355
|
+
const empty = $("#empty");
|
|
16356
|
+
if (empty) {
|
|
16357
|
+
const onHistory = STATE.route.screen === "history";
|
|
16358
|
+
const h2 = empty.querySelector("h2");
|
|
16359
|
+
if (h2)
|
|
16360
|
+
h2.textContent = onHistory ? "Empty diff" : "No changes";
|
|
16361
|
+
const p2 = empty.querySelector("p");
|
|
16362
|
+
if (p2)
|
|
16363
|
+
p2.textContent = onHistory ? "This commit has no changes against its first parent." : "The working tree is clean against this ref.";
|
|
16364
|
+
}
|
|
16365
|
+
}
|
|
15750
16366
|
setStatus("refreshing");
|
|
15751
16367
|
const params = new URLSearchParams;
|
|
15752
16368
|
if (STATE.ignoreWs)
|
|
@@ -15772,8 +16388,13 @@ ${frontmatter.yaml}
|
|
|
15772
16388
|
else if (STATE.route.screen === "file" && STATE.route.view === "blob") {
|
|
15773
16389
|
setStatus("live");
|
|
15774
16390
|
applySourceRouteToShell();
|
|
16391
|
+
} else if (STATE.route.screen === "history") {
|
|
16392
|
+
parkRangeForHistory();
|
|
16393
|
+
setStatus("live");
|
|
16394
|
+
HISTORY_VIEW.enterHistory();
|
|
15775
16395
|
} else
|
|
15776
16396
|
load();
|
|
16397
|
+
syncLineRefPill();
|
|
15777
16398
|
});
|
|
15778
16399
|
function syncRefInputs() {
|
|
15779
16400
|
const fi = $("#ref-from"), ti = $("#ref-to");
|
|
@@ -15783,6 +16404,7 @@ ${frontmatter.yaml}
|
|
|
15783
16404
|
ti.value = STATE.to;
|
|
15784
16405
|
}
|
|
15785
16406
|
function setRange(from, to) {
|
|
16407
|
+
preHistoryRange = null;
|
|
15786
16408
|
STATE.from = from || "";
|
|
15787
16409
|
STATE.to = to || "";
|
|
15788
16410
|
localStorage.setItem("gdp:from", STATE.from);
|
|
@@ -15801,14 +16423,44 @@ ${frontmatter.yaml}
|
|
|
15801
16423
|
renderHelpPage();
|
|
15802
16424
|
} else {
|
|
15803
16425
|
setRoute({ screen: "diff", range }, true);
|
|
16426
|
+
setPageMode();
|
|
15804
16427
|
load();
|
|
15805
16428
|
}
|
|
15806
16429
|
}
|
|
15807
16430
|
syncRefInputs();
|
|
15808
16431
|
syncHeaderMenu();
|
|
15809
|
-
|
|
16432
|
+
const HISTORY_VIEW = createHistoryView({
|
|
15810
16433
|
$,
|
|
15811
|
-
escapeHtml:
|
|
16434
|
+
escapeHtml: escapeHtml3,
|
|
16435
|
+
getRoute: () => STATE.route,
|
|
16436
|
+
setRoute,
|
|
16437
|
+
applyCommitRange: (range) => {
|
|
16438
|
+
STATE.from = range.from;
|
|
16439
|
+
STATE.to = range.to;
|
|
16440
|
+
syncRefInputs();
|
|
16441
|
+
return load();
|
|
16442
|
+
},
|
|
16443
|
+
showEmptyDiffPane: () => {
|
|
16444
|
+
const diff = $("#diff");
|
|
16445
|
+
if (diff)
|
|
16446
|
+
diff.innerHTML = "";
|
|
16447
|
+
const empty = $("#empty");
|
|
16448
|
+
if (empty) {
|
|
16449
|
+
empty.classList.remove("hidden");
|
|
16450
|
+
const h2 = empty.querySelector("h2");
|
|
16451
|
+
if (h2)
|
|
16452
|
+
h2.textContent = "No commit selected";
|
|
16453
|
+
const p2 = empty.querySelector("p");
|
|
16454
|
+
if (p2)
|
|
16455
|
+
p2.textContent = "Select a commit from the list to see its changes.";
|
|
16456
|
+
}
|
|
16457
|
+
setStatus("live");
|
|
16458
|
+
},
|
|
16459
|
+
trackLoad
|
|
16460
|
+
});
|
|
16461
|
+
const REF_PICKER = createRefPicker({
|
|
16462
|
+
$,
|
|
16463
|
+
escapeHtml: escapeHtml3,
|
|
15812
16464
|
currentRange,
|
|
15813
16465
|
setRange,
|
|
15814
16466
|
setRoute,
|
|
@@ -15818,8 +16470,18 @@ ${frontmatter.yaml}
|
|
|
15818
16470
|
getRepoRef: () => STATE.repoRef,
|
|
15819
16471
|
getRoute: () => STATE.route
|
|
15820
16472
|
});
|
|
16473
|
+
if (REF_PICKER) {
|
|
16474
|
+
const historyRefInput = document.querySelector("#history-ref");
|
|
16475
|
+
if (historyRefInput) {
|
|
16476
|
+
historyRefInput.value = "HEAD";
|
|
16477
|
+
REF_PICKER.wireRefSelectorInput(historyRefInput, (ref) => HISTORY_VIEW.onRefPicked(ref));
|
|
16478
|
+
}
|
|
16479
|
+
}
|
|
15821
16480
|
$("#ref-reset").addEventListener("click", () => setRange("HEAD", "worktree"));
|
|
15822
16481
|
function applyRouteFromLocation() {
|
|
16482
|
+
if (STATE.route.screen === "history" && window.location.pathname !== "/history") {
|
|
16483
|
+
restoreRangeAfterHistory();
|
|
16484
|
+
}
|
|
15823
16485
|
const parsedRoute = parseRoute(window.location.pathname, window.location.search, currentRange());
|
|
15824
16486
|
STATE.route = parsedRoute.screen === "unknown" ? { screen: "diff", range: parsedRoute.range } : parsedRoute;
|
|
15825
16487
|
STATE.from = STATE.route.range.from;
|
|
@@ -15829,6 +16491,7 @@ ${frontmatter.yaml}
|
|
|
15829
16491
|
ANNOTATIONS_UI?.restoreSessionFromUrl();
|
|
15830
16492
|
syncRefInputs();
|
|
15831
16493
|
syncHeaderMenu();
|
|
16494
|
+
syncLineRefPill();
|
|
15832
16495
|
if (STATE.route.screen === "help") {
|
|
15833
16496
|
cancelActiveSourceLoad("navigation");
|
|
15834
16497
|
setPageMode();
|
|
@@ -15843,6 +16506,14 @@ ${frontmatter.yaml}
|
|
|
15843
16506
|
loadRepo();
|
|
15844
16507
|
return;
|
|
15845
16508
|
}
|
|
16509
|
+
if (STATE.route.screen === "history") {
|
|
16510
|
+
parkRangeForHistory();
|
|
16511
|
+
cancelActiveSourceLoad("navigation");
|
|
16512
|
+
setPageMode();
|
|
16513
|
+
removeStandaloneSource();
|
|
16514
|
+
HISTORY_VIEW.enterHistory();
|
|
16515
|
+
return;
|
|
16516
|
+
}
|
|
15846
16517
|
if (STATE.route.screen !== "file") {
|
|
15847
16518
|
cancelActiveSourceLoad("navigation");
|
|
15848
16519
|
setPageMode();
|
package/web/index.html
CHANGED
|
@@ -26,12 +26,14 @@
|
|
|
26
26
|
<nav class="app-menu" aria-label="Views">
|
|
27
27
|
<a class="app-menu-item active" data-route="repo" href="/">Repository</a>
|
|
28
28
|
<a class="app-menu-item" data-route="diff" href="/todif?from=HEAD&to=worktree">Diff Viewer</a>
|
|
29
|
+
<a class="app-menu-item" data-route="history" href="/history">History</a>
|
|
29
30
|
</nav>
|
|
30
31
|
<div class="global-actions">
|
|
31
32
|
<button id="annotations-toggle" class="global-icon-action" title="code annotations" aria-label="code annotations">💬<span id="annotations-count" hidden></span></button>
|
|
32
33
|
<button id="viewer-settings" class="global-icon-action" title="viewer settings" aria-label="viewer settings"></button>
|
|
33
34
|
<button id="theme" title="toggle theme">🌗</button>
|
|
34
35
|
<span id="status" class="status"></span>
|
|
36
|
+
<a id="repo-web-link" class="global-help-link" href="#" target="_blank" rel="noopener" hidden>GitHub</a>
|
|
35
37
|
<a class="global-help-link" data-route="help" href="/help">Help</a>
|
|
36
38
|
<span class="product-label" aria-hidden="true">code viewer</span>
|
|
37
39
|
</div>
|
|
@@ -76,6 +78,19 @@
|
|
|
76
78
|
|
|
77
79
|
<div id="load-bar" aria-hidden="true"></div>
|
|
78
80
|
|
|
81
|
+
<aside id="history-panel" aria-label="Commit history" hidden>
|
|
82
|
+
<div class="history-head">
|
|
83
|
+
<span class="history-title">Commits</span>
|
|
84
|
+
<span data-ref-selector-mount data-ref-id="history-ref" data-placeholder="ref..." data-title="history ref"></span>
|
|
85
|
+
</div>
|
|
86
|
+
<div class="history-filter-wrap">
|
|
87
|
+
<input id="history-filter" type="search" placeholder="filter commits… (message, sha, author:name, path:file)" autocomplete="off" />
|
|
88
|
+
</div>
|
|
89
|
+
<div id="history-banner" class="history-banner" role="status" hidden></div>
|
|
90
|
+
<ol id="history-list"></ol>
|
|
91
|
+
<div id="history-sentinel" aria-hidden="true"></div>
|
|
92
|
+
<div id="history-status" class="history-status" role="status" hidden></div>
|
|
93
|
+
</aside>
|
|
79
94
|
<aside id="sidebar">
|
|
80
95
|
<div class="sb-head">
|
|
81
96
|
<span class="sb-title">Files</span>
|
|
@@ -180,6 +195,15 @@
|
|
|
180
195
|
</aside>
|
|
181
196
|
|
|
182
197
|
<main id="content">
|
|
198
|
+
<section id="history-commit-info" hidden aria-label="Selected commit">
|
|
199
|
+
<div class="hci-head">
|
|
200
|
+
<span id="hci-sha" class="hci-sha"></span>
|
|
201
|
+
<span id="hci-author" class="hci-author"></span>
|
|
202
|
+
<span id="hci-date" class="hci-date"></span>
|
|
203
|
+
</div>
|
|
204
|
+
<h2 id="hci-subject" class="hci-subject"></h2>
|
|
205
|
+
<pre id="hci-body" class="hci-body" hidden></pre>
|
|
206
|
+
</section>
|
|
183
207
|
<div id="empty" class="empty hidden">
|
|
184
208
|
<div class="emoji">✨</div>
|
|
185
209
|
<h2>No changes</h2>
|
package/web/style.css
CHANGED
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
--topbar-h: 56px;
|
|
51
51
|
--chrome-h: calc(var(--global-header-h) + var(--topbar-h));
|
|
52
52
|
--sidebar-w: 308px;
|
|
53
|
+
--history-w: 320px;
|
|
53
54
|
--sidebar-restore-rail-w: 44px;
|
|
54
55
|
/* shadows */
|
|
55
56
|
--shadow-sm: 0 1px 0 rgba(31,35,40,0.04);
|
|
@@ -900,6 +901,170 @@ body[data-code-font-size="xlarge"] { --code-font-size: 15px; }
|
|
|
900
901
|
100% { transform: translateX(450%); }
|
|
901
902
|
}
|
|
902
903
|
|
|
904
|
+
/* ===== Commit history panel ===== */
|
|
905
|
+
#history-panel {
|
|
906
|
+
display: none;
|
|
907
|
+
position: fixed;
|
|
908
|
+
left: 0;
|
|
909
|
+
top: var(--chrome-h);
|
|
910
|
+
bottom: 0;
|
|
911
|
+
width: var(--history-w);
|
|
912
|
+
background: var(--bg-soft);
|
|
913
|
+
border-right: 1px solid var(--border);
|
|
914
|
+
overflow-y: auto;
|
|
915
|
+
z-index: 31;
|
|
916
|
+
flex-direction: column;
|
|
917
|
+
}
|
|
918
|
+
body.gdp-history-page #history-panel { display: flex; }
|
|
919
|
+
body.gdp-history-page #sidebar { left: var(--history-w); }
|
|
920
|
+
body.gdp-history-page main#content {
|
|
921
|
+
margin-left: calc(var(--history-w) + var(--sidebar-w));
|
|
922
|
+
}
|
|
923
|
+
body.gdp-history-page.gdp-sidebar-hidden main#content {
|
|
924
|
+
margin-left: var(--history-w);
|
|
925
|
+
}
|
|
926
|
+
body.gdp-history-page #sidebar-resizer {
|
|
927
|
+
left: calc(var(--history-w) + var(--sidebar-w) - 4px);
|
|
928
|
+
}
|
|
929
|
+
/* The history screen drives its diff range from the commit list; the topbar
|
|
930
|
+
from/to pickers belong to the Diff Viewer and are hidden here. */
|
|
931
|
+
body.gdp-history-page #topbar .ref-pickers {
|
|
932
|
+
display: none;
|
|
933
|
+
}
|
|
934
|
+
#history-panel[hidden] {
|
|
935
|
+
display: none !important;
|
|
936
|
+
}
|
|
937
|
+
.history-head {
|
|
938
|
+
display: flex;
|
|
939
|
+
align-items: center;
|
|
940
|
+
gap: 8px;
|
|
941
|
+
padding: 13px 14px 10px;
|
|
942
|
+
position: sticky;
|
|
943
|
+
top: 0;
|
|
944
|
+
background: var(--bg-soft);
|
|
945
|
+
border-bottom: 1px solid var(--border);
|
|
946
|
+
z-index: 1;
|
|
947
|
+
}
|
|
948
|
+
.history-title { font-weight: 600; font-size: 13px; }
|
|
949
|
+
.history-filter-wrap {
|
|
950
|
+
padding: 0 12px 8px;
|
|
951
|
+
background: var(--bg-soft);
|
|
952
|
+
border-bottom: 1px solid var(--border);
|
|
953
|
+
}
|
|
954
|
+
#history-filter {
|
|
955
|
+
width: 100%;
|
|
956
|
+
background: var(--bg);
|
|
957
|
+
border: 1px solid var(--border);
|
|
958
|
+
color: var(--fg);
|
|
959
|
+
border-radius: 6px;
|
|
960
|
+
font: inherit;
|
|
961
|
+
font-size: 12px;
|
|
962
|
+
padding: 5px 8px;
|
|
963
|
+
}
|
|
964
|
+
.history-banner {
|
|
965
|
+
margin: 8px 10px 0;
|
|
966
|
+
padding: 6px 10px;
|
|
967
|
+
border: 1px solid var(--border);
|
|
968
|
+
border-radius: 6px;
|
|
969
|
+
background: var(--bg-mute);
|
|
970
|
+
color: var(--fg-muted);
|
|
971
|
+
font-size: 12px;
|
|
972
|
+
}
|
|
973
|
+
#history-list { list-style: none; margin: 0; padding: 0 0 24px; }
|
|
974
|
+
.history-group {
|
|
975
|
+
display: flex;
|
|
976
|
+
align-items: center;
|
|
977
|
+
gap: 8px;
|
|
978
|
+
padding: 14px 14px 6px;
|
|
979
|
+
background: var(--bg-mute);
|
|
980
|
+
border-top: 1px solid var(--border);
|
|
981
|
+
border-bottom: 1px solid var(--border);
|
|
982
|
+
color: var(--accent);
|
|
983
|
+
font-size: 11.5px;
|
|
984
|
+
font-weight: 700;
|
|
985
|
+
text-transform: uppercase;
|
|
986
|
+
letter-spacing: 0.06em;
|
|
987
|
+
}
|
|
988
|
+
.history-group::before {
|
|
989
|
+
content: "";
|
|
990
|
+
width: 4px;
|
|
991
|
+
height: 12px;
|
|
992
|
+
border-radius: 2px;
|
|
993
|
+
background: var(--accent);
|
|
994
|
+
}
|
|
995
|
+
#history-list > .history-group:first-child { border-top: 0; }
|
|
996
|
+
.history-item {
|
|
997
|
+
padding: 8px 14px;
|
|
998
|
+
cursor: pointer;
|
|
999
|
+
border-left: 3px solid transparent;
|
|
1000
|
+
border-bottom: 1px solid var(--border);
|
|
1001
|
+
}
|
|
1002
|
+
.history-item:hover { background: var(--bg-mute); }
|
|
1003
|
+
.history-item.active {
|
|
1004
|
+
background: var(--bg-mute);
|
|
1005
|
+
border-left-color: var(--accent);
|
|
1006
|
+
}
|
|
1007
|
+
.history-item .subject {
|
|
1008
|
+
display: block;
|
|
1009
|
+
font-size: 12.5px;
|
|
1010
|
+
overflow: hidden;
|
|
1011
|
+
text-overflow: ellipsis;
|
|
1012
|
+
white-space: nowrap;
|
|
1013
|
+
}
|
|
1014
|
+
.history-item .meta2 {
|
|
1015
|
+
display: flex;
|
|
1016
|
+
gap: 8px;
|
|
1017
|
+
color: var(--fg-muted);
|
|
1018
|
+
font-size: 11px;
|
|
1019
|
+
margin-top: 2px;
|
|
1020
|
+
}
|
|
1021
|
+
.history-item .sha { font-family: "Monaspace Neon", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; }
|
|
1022
|
+
.history-status {
|
|
1023
|
+
padding: 10px 14px;
|
|
1024
|
+
color: var(--fg-muted);
|
|
1025
|
+
font-size: 12px;
|
|
1026
|
+
}
|
|
1027
|
+
#history-sentinel { height: 1px; }
|
|
1028
|
+
#history-commit-info {
|
|
1029
|
+
display: none;
|
|
1030
|
+
margin: 12px 16px 4px;
|
|
1031
|
+
padding: 12px 16px;
|
|
1032
|
+
border: 1px solid var(--border);
|
|
1033
|
+
border-radius: 8px;
|
|
1034
|
+
background: var(--bg-soft);
|
|
1035
|
+
}
|
|
1036
|
+
body.gdp-history-page #history-commit-info:not([hidden]) { display: block; }
|
|
1037
|
+
#history-commit-info .hci-head {
|
|
1038
|
+
display: flex;
|
|
1039
|
+
flex-wrap: wrap;
|
|
1040
|
+
align-items: baseline;
|
|
1041
|
+
gap: 10px;
|
|
1042
|
+
color: var(--fg-muted);
|
|
1043
|
+
font-size: 12px;
|
|
1044
|
+
}
|
|
1045
|
+
#history-commit-info .hci-sha {
|
|
1046
|
+
font-family: "Monaspace Neon", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
|
|
1047
|
+
user-select: all;
|
|
1048
|
+
}
|
|
1049
|
+
#history-commit-info .hci-subject {
|
|
1050
|
+
margin: 6px 0 0;
|
|
1051
|
+
font-size: 15px;
|
|
1052
|
+
line-height: 1.4;
|
|
1053
|
+
overflow-wrap: anywhere;
|
|
1054
|
+
}
|
|
1055
|
+
#history-commit-info .hci-body {
|
|
1056
|
+
margin: 8px 0 0;
|
|
1057
|
+
padding: 0;
|
|
1058
|
+
background: transparent;
|
|
1059
|
+
border: 0;
|
|
1060
|
+
font-family: inherit;
|
|
1061
|
+
font-size: 12.5px;
|
|
1062
|
+
line-height: 1.6;
|
|
1063
|
+
color: var(--fg-muted);
|
|
1064
|
+
white-space: pre-wrap;
|
|
1065
|
+
overflow-wrap: anywhere;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
903
1068
|
/* ===== Sidebar ===== */
|
|
904
1069
|
#sidebar {
|
|
905
1070
|
position: fixed;
|
|
@@ -1641,7 +1806,10 @@ body.gdp-help-page #content {
|
|
|
1641
1806
|
|
|
1642
1807
|
#diff > *:first-child { margin-top: 0; }
|
|
1643
1808
|
|
|
1644
|
-
|
|
1809
|
+
/* Scroll-past-end spacer so the sidebar can align the last diff file to the
|
|
1810
|
+
top of the viewport. Not wanted on the history screen, where small
|
|
1811
|
+
single-commit diffs would gain a screenful of dead scroll space. */
|
|
1812
|
+
body:not(.gdp-file-detail-page):not(.gdp-history-page) #diff::after {
|
|
1645
1813
|
content: "";
|
|
1646
1814
|
display: block;
|
|
1647
1815
|
height: calc(100vh - var(--chrome-h) - 40px);
|
|
@@ -1911,6 +2079,86 @@ table.d2h-diff-table tr.gdp-diff-line-target .d2h-code-line-ctn {
|
|
|
1911
2079
|
table.d2h-diff-table tr.gdp-diff-line-target > td:first-child {
|
|
1912
2080
|
box-shadow: inset 3px 0 0 var(--line-hit-border), inset -1px 0 0 var(--border-muted);
|
|
1913
2081
|
}
|
|
2082
|
+
|
|
2083
|
+
/* After-side drag selection (diff screen) — same yellow as the file view. */
|
|
2084
|
+
table.d2h-diff-table tr.gdp-diff-line-selected > td,
|
|
2085
|
+
table.d2h-diff-table tr.gdp-diff-line-selected .d2h-code-line,
|
|
2086
|
+
table.d2h-diff-table tr.gdp-diff-line-selected .d2h-code-side-line,
|
|
2087
|
+
table.d2h-diff-table tr.gdp-diff-line-selected .d2h-code-line-ctn {
|
|
2088
|
+
background: var(--line-hit-bg) !important;
|
|
2089
|
+
background-color: var(--line-hit-bg) !important;
|
|
2090
|
+
}
|
|
2091
|
+
table.d2h-diff-table tr.gdp-diff-line-selected > td:first-child {
|
|
2092
|
+
box-shadow: inset 3px 0 0 var(--line-hit-border), inset -1px 0 0 var(--border-muted);
|
|
2093
|
+
}
|
|
2094
|
+
table.d2h-diff-table td.d2h-code-linenumber,
|
|
2095
|
+
table.d2h-diff-table td.d2h-code-side-linenumber {
|
|
2096
|
+
cursor: pointer;
|
|
2097
|
+
user-select: none;
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
/* Floating "copy @path#start-end" pill shown while lines are selected. */
|
|
2101
|
+
#line-ref-pill {
|
|
2102
|
+
position: fixed;
|
|
2103
|
+
left: 50%;
|
|
2104
|
+
bottom: 24px;
|
|
2105
|
+
transform: translateX(-50%);
|
|
2106
|
+
z-index: 400;
|
|
2107
|
+
display: inline-flex;
|
|
2108
|
+
align-items: center;
|
|
2109
|
+
gap: 8px;
|
|
2110
|
+
max-width: min(680px, calc(100vw - 48px));
|
|
2111
|
+
white-space: nowrap;
|
|
2112
|
+
padding: 9px 16px;
|
|
2113
|
+
border: 1px solid var(--accent);
|
|
2114
|
+
border-radius: 999px;
|
|
2115
|
+
background: var(--accent);
|
|
2116
|
+
color: #fff;
|
|
2117
|
+
font: inherit;
|
|
2118
|
+
font-size: 13px;
|
|
2119
|
+
font-weight: 600;
|
|
2120
|
+
cursor: pointer;
|
|
2121
|
+
box-shadow: 0 8px 28px rgba(31, 35, 40, 0.35);
|
|
2122
|
+
}
|
|
2123
|
+
#line-ref-pill:hover {
|
|
2124
|
+
filter: brightness(1.1);
|
|
2125
|
+
}
|
|
2126
|
+
#line-ref-pill svg {
|
|
2127
|
+
flex: 0 0 auto;
|
|
2128
|
+
}
|
|
2129
|
+
#line-ref-pill .lrp-label {
|
|
2130
|
+
flex: 0 0 auto;
|
|
2131
|
+
}
|
|
2132
|
+
#line-ref-pill .lrp-ref {
|
|
2133
|
+
overflow: hidden;
|
|
2134
|
+
text-overflow: ellipsis;
|
|
2135
|
+
padding: 2px 8px;
|
|
2136
|
+
border-radius: 6px;
|
|
2137
|
+
background: rgba(255, 255, 255, 0.18);
|
|
2138
|
+
font-family: "Monaspace Neon", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
|
|
2139
|
+
font-size: 12px;
|
|
2140
|
+
font-weight: 400;
|
|
2141
|
+
}
|
|
2142
|
+
#line-ref-pill.copied {
|
|
2143
|
+
background: var(--success, #2ea043);
|
|
2144
|
+
border-color: var(--success, #2ea043);
|
|
2145
|
+
}
|
|
2146
|
+
#line-ref-pill.pop {
|
|
2147
|
+
animation: lrp-pop 0.22s ease-out;
|
|
2148
|
+
}
|
|
2149
|
+
@keyframes lrp-pop {
|
|
2150
|
+
0% {
|
|
2151
|
+
transform: translateX(-50%) translateY(14px) scale(0.92);
|
|
2152
|
+
opacity: 0;
|
|
2153
|
+
}
|
|
2154
|
+
100% {
|
|
2155
|
+
transform: translateX(-50%) translateY(0) scale(1);
|
|
2156
|
+
opacity: 1;
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
#line-ref-pill[hidden] {
|
|
2160
|
+
display: none;
|
|
2161
|
+
}
|
|
1914
2162
|
/* Stack height = number of buttons * 20px. With 1 button = 20px row,
|
|
1915
2163
|
* with 2 (↑+↓) = 40px row, matching GitHub. */
|
|
1916
2164
|
.gdp-expand-stack {
|
|
@@ -3713,6 +3961,17 @@ body.gdp-file-detail-page #empty {
|
|
|
3713
3961
|
}
|
|
3714
3962
|
#content { margin-left: 0; max-width: none; padding-top: calc(var(--chrome-h) + 240px); }
|
|
3715
3963
|
.controls input[type="search"] { width: 130px; }
|
|
3964
|
+
body.gdp-history-page #history-panel {
|
|
3965
|
+
position: static;
|
|
3966
|
+
width: auto;
|
|
3967
|
+
border-right: 0;
|
|
3968
|
+
border-bottom: 1px solid var(--border);
|
|
3969
|
+
}
|
|
3970
|
+
body.gdp-history-page #sidebar { left: 0; }
|
|
3971
|
+
body.gdp-history-page main#content,
|
|
3972
|
+
body.gdp-history-page.gdp-sidebar-hidden main#content {
|
|
3973
|
+
margin-left: 0;
|
|
3974
|
+
}
|
|
3716
3975
|
}
|
|
3717
3976
|
|
|
3718
3977
|
/* ===== File shell (placeholder + loaded card) ===== */
|