gabriel / musehub public
commits.ts typescript
146 lines 5.3 KB
Raw
sha256:3ff9c9863a9891bdcde71b4a43228f66d0493e38b7cc1d09fe9eb7de774046b2 feat: add repair-commit wire endpoint (API parity with repa… Opus 4.8 minor ⚠ breaking 1 day ago
1 /**
2 * commits.ts — Commits list page module.
3 *
4 * Responsibilities:
5 * 1. Branch selector → navigates with ?branch= param.
6 * 2. Filter form → let HTMX handle partial updates; no manual JS submit needed.
7 * 3. Compare mode — toggle, checkbox selection, compare strip link.
8 * Uses event delegation so it survives HTMX fragment swaps.
9 */
10
11 interface CommitsCfg {
12 repoId: string;
13 base: string;
14 page: number;
15 perPage: number;
16 totalPages: number;
17 branch: string;
18 }
19
20 let _commitsCfg: CommitsCfg | undefined;
21
22 // ── URL helpers ───────────────────────────────────────────────────────────
23
24 function buildUrl(overrides: Record<string, string | number | null>): string {
25 const url = new URL(window.location.href);
26 for (const [k, v] of Object.entries(overrides)) {
27 if (v === null || v === undefined || v === "") {
28 url.searchParams.delete(k);
29 } else {
30 url.searchParams.set(k, String(v));
31 }
32 }
33 return url.toString();
34 }
35
36 // ── Branch selector ───────────────────────────────────────────────────────
37
38 function bindBranchSelector(): void {
39 const sel = document.getElementById("branch-sel") as HTMLSelectElement | null;
40 if (!sel) return;
41 sel.addEventListener("change", () => {
42 window.location.href = buildUrl({ branch: sel.value || null, page: 1 });
43 });
44 }
45
46 // ── Compare mode ──────────────────────────────────────────────────────────
47
48 let compareMode = false;
49 const selected = new Set<string>();
50
51 function updateCompareStrip(): void {
52 const strip = document.getElementById("compare-strip");
53 const countEl = document.getElementById("compare-count");
54 const link = document.getElementById("compare-link") as HTMLAnchorElement | null;
55 const cfg = _commitsCfg;
56 if (!strip) return;
57
58 const n = selected.size;
59 if (countEl) countEl.textContent = `${n} selected`;
60
61 if (n === 2 && link && cfg) {
62 const [a, b] = [...selected];
63 link.href = `${cfg.base}/compare/${a}...${b}`;
64 link.style.display = "";
65 } else if (link) {
66 link.style.display = "none";
67 }
68 strip.classList.toggle("visible", compareMode);
69 }
70
71 function toggleCompareMode(): void {
72 compareMode = !compareMode;
73 document.body.classList.toggle("compare-mode", compareMode);
74 selected.clear();
75 document.querySelectorAll<HTMLInputElement>(".compare-check").forEach(cb => { cb.checked = false; });
76 document.querySelectorAll<HTMLElement>(".commit-list-row").forEach(r => r.classList.remove("compare-selected"));
77 updateCompareStrip();
78 const btn = document.getElementById("compare-toggle-btn");
79 if (btn) btn.textContent = compareMode ? "✕ Exit Compare" : "⊞ Compare";
80 }
81
82 function onCompareCheck(cb: HTMLInputElement, commitId: string): void {
83 const row = cb.closest<HTMLElement>(".commit-list-row");
84 if (cb.checked) {
85 if (selected.size >= 2) { cb.checked = false; return; }
86 selected.add(commitId);
87 row?.classList.add("compare-selected");
88 } else {
89 selected.delete(commitId);
90 row?.classList.remove("compare-selected");
91 }
92 updateCompareStrip();
93 }
94
95 function bindCompareMode(): void {
96 // Compare toggle button
97 document.getElementById("compare-toggle-btn")
98 ?.addEventListener("click", toggleCompareMode);
99
100 // Cancel button inside strip
101 document.getElementById("compare-cancel-btn")
102 ?.addEventListener("click", toggleCompareMode);
103
104 // Checkbox event delegation — survives HTMX fragment swaps
105 document.addEventListener("change", (e: Event) => {
106 const cb = (e.target as Element).closest<HTMLInputElement>(".compare-check");
107 if (!cb) return;
108 const commitId = cb.dataset.commitId ?? cb.closest<HTMLElement>(".commit-list-row")?.dataset.commitId;
109 if (commitId) onCompareCheck(cb, commitId);
110 });
111 }
112
113 // ── Re-apply compare state after HTMX swaps ──────────────────────────────
114
115 function bindHtmxSwap(): void {
116 document.body.addEventListener("htmx:afterSwap", () => {
117 // Re-check any previously selected commits after fragment swap
118 selected.forEach(id => {
119 const row = document.querySelector<HTMLElement>(`[data-commit-id="${id}"]`);
120 const cb = row?.querySelector<HTMLInputElement>(".compare-check");
121 if (row && cb) {
122 row.classList.add("compare-selected");
123 cb.checked = true;
124 }
125 });
126 if (compareMode) {
127 document.body.classList.add("compare-mode");
128 }
129 });
130 }
131
132 // ── Entry point ───────────────────────────────────────────────────────────
133
134 export function initCommits(data: Record<string, unknown> = {}): void {
135 _commitsCfg = {
136 repoId: String(data['repoId'] ?? ''),
137 base: String(data['base'] ?? ''),
138 page: Number(data['page'] ?? 1),
139 perPage: Number(data['perPage'] ?? 30),
140 totalPages: Number(data['totalPages'] ?? 1),
141 branch: String(data['branch'] ?? ''),
142 };
143 bindBranchSelector();
144 bindCompareMode();
145 bindHtmxSwap();
146 }
File History 1 commit
sha256:3ff9c9863a9891bdcde71b4a43228f66d0493e38b7cc1d09fe9eb7de774046b2 feat: add repair-commit wire endpoint (API parity with repa… Opus 4.8 minor 1 day ago