repoview 0.3.1 → 0.5.0
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/public/app.css +60 -1
- package/public/app.js +98 -4
- package/src/server.js +13 -3
- package/src/views.js +13 -0
package/package.json
CHANGED
package/public/app.css
CHANGED
|
@@ -564,6 +564,36 @@ a {
|
|
|
564
564
|
text-size-adjust: 100%;
|
|
565
565
|
}
|
|
566
566
|
|
|
567
|
+
.diff-actions {
|
|
568
|
+
display: flex;
|
|
569
|
+
gap: 8px;
|
|
570
|
+
padding: 10px 14px;
|
|
571
|
+
position: sticky;
|
|
572
|
+
top: 0;
|
|
573
|
+
z-index: 5;
|
|
574
|
+
background: var(--panel);
|
|
575
|
+
border-bottom: 1px solid var(--border);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
.btn-sm {
|
|
579
|
+
font-weight: 600;
|
|
580
|
+
font-size: 12px;
|
|
581
|
+
padding: 3px 8px;
|
|
582
|
+
border-radius: 4px;
|
|
583
|
+
border: 1px solid var(--border);
|
|
584
|
+
background: var(--btn);
|
|
585
|
+
color: var(--text);
|
|
586
|
+
cursor: pointer;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
.btn-sm:hover {
|
|
590
|
+
background: var(--btnHover);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
.diff-truncated {
|
|
594
|
+
margin: 12px 14px;
|
|
595
|
+
}
|
|
596
|
+
|
|
567
597
|
.diff-empty {
|
|
568
598
|
margin: 12px;
|
|
569
599
|
}
|
|
@@ -701,6 +731,34 @@ a {
|
|
|
701
731
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
702
732
|
font-size: 13px;
|
|
703
733
|
font-weight: 600;
|
|
734
|
+
white-space: normal;
|
|
735
|
+
word-break: break-all;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
.diff-file-link {
|
|
739
|
+
color: var(--accent);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
.diff-file-link:hover {
|
|
743
|
+
text-decoration: underline;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
.diff-copy-btn {
|
|
747
|
+
display: inline-flex;
|
|
748
|
+
align-items: center;
|
|
749
|
+
justify-content: center;
|
|
750
|
+
margin-left: 6px;
|
|
751
|
+
padding: 2px;
|
|
752
|
+
border: none;
|
|
753
|
+
background: none;
|
|
754
|
+
color: var(--muted);
|
|
755
|
+
cursor: pointer;
|
|
756
|
+
border-radius: 3px;
|
|
757
|
+
vertical-align: middle;
|
|
758
|
+
}
|
|
759
|
+
.diff-copy-btn:hover {
|
|
760
|
+
color: var(--accent);
|
|
761
|
+
background: var(--btnHover);
|
|
704
762
|
}
|
|
705
763
|
|
|
706
764
|
.d2h-file-header .d2h-tag {
|
|
@@ -763,7 +821,8 @@ a {
|
|
|
763
821
|
transform: rotate(-90deg);
|
|
764
822
|
}
|
|
765
823
|
|
|
766
|
-
|
|
824
|
+
/* Diffs start hidden; JS adds .expanded to show them */
|
|
825
|
+
.d2h-file-diff:not(.expanded) {
|
|
767
826
|
display: none;
|
|
768
827
|
}
|
|
769
828
|
|
package/public/app.js
CHANGED
|
@@ -180,21 +180,93 @@ function initTimezoneToggle() {
|
|
|
180
180
|
update();
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
+
function fallbackCopy(text) {
|
|
184
|
+
const ta = document.createElement("textarea");
|
|
185
|
+
ta.value = text;
|
|
186
|
+
ta.style.position = "fixed";
|
|
187
|
+
ta.style.opacity = "0";
|
|
188
|
+
document.body.appendChild(ta);
|
|
189
|
+
ta.select();
|
|
190
|
+
document.execCommand("copy");
|
|
191
|
+
document.body.removeChild(ta);
|
|
192
|
+
}
|
|
193
|
+
|
|
183
194
|
function initDiffCollapse() {
|
|
184
195
|
const wrappers = document.querySelectorAll(".d2h-file-wrapper");
|
|
185
196
|
if (!wrappers.length) return;
|
|
186
197
|
|
|
198
|
+
// Diffs are hidden by CSS by default (.d2h-file-diff:not(.expanded)).
|
|
199
|
+
// Only expand when there are few files — avoids layout/paint cost for large diffs.
|
|
200
|
+
const shouldExpand = wrappers.length <= 8;
|
|
201
|
+
|
|
202
|
+
// Add expand/collapse all buttons
|
|
203
|
+
const diffWrap = document.querySelector(".diff-wrap");
|
|
204
|
+
if (diffWrap && wrappers.length > 1) {
|
|
205
|
+
const bar = document.createElement("div");
|
|
206
|
+
bar.className = "diff-actions";
|
|
207
|
+
bar.innerHTML = `<button type="button" class="btn btn-sm" id="expand-all">Expand all</button>
|
|
208
|
+
<button type="button" class="btn btn-sm" id="collapse-all">Collapse all</button>`;
|
|
209
|
+
diffWrap.insertBefore(bar, diffWrap.firstChild);
|
|
210
|
+
}
|
|
211
|
+
|
|
187
212
|
for (const wrapper of wrappers) {
|
|
188
213
|
const header = wrapper.querySelector(".d2h-file-header");
|
|
189
214
|
const diff = wrapper.querySelector(".d2h-file-diff");
|
|
190
215
|
if (!header || !diff) continue;
|
|
191
216
|
|
|
217
|
+
const fileNameEl = header.querySelector(".d2h-file-name");
|
|
218
|
+
if (fileNameEl && !fileNameEl.querySelector("a")) {
|
|
219
|
+
const rawName = fileNameEl.textContent.trim();
|
|
220
|
+
const name = rawName.includes(" → ") ? rawName.split(" → ").pop() : rawName;
|
|
221
|
+
const link = document.createElement("a");
|
|
222
|
+
link.className = "diff-file-link";
|
|
223
|
+
link.href = `/blob/${encodeURI(name)}`;
|
|
224
|
+
link.textContent = rawName;
|
|
225
|
+
fileNameEl.textContent = "";
|
|
226
|
+
fileNameEl.appendChild(link);
|
|
227
|
+
|
|
228
|
+
const copyBtn = document.createElement("button");
|
|
229
|
+
copyBtn.className = "diff-copy-btn";
|
|
230
|
+
copyBtn.type = "button";
|
|
231
|
+
copyBtn.setAttribute("aria-label", "Copy filename");
|
|
232
|
+
copyBtn.innerHTML =
|
|
233
|
+
'<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">' +
|
|
234
|
+
'<rect x="5" y="5" width="9" height="9" rx="1.5"/>' +
|
|
235
|
+
'<path d="M3 11V2.5A1.5 1.5 0 0 1 4.5 1H11"/>' +
|
|
236
|
+
"</svg>";
|
|
237
|
+
const svgIcon = copyBtn.innerHTML;
|
|
238
|
+
copyBtn.addEventListener("click", (e) => {
|
|
239
|
+
e.stopPropagation();
|
|
240
|
+
const showSuccess = () => {
|
|
241
|
+
copyBtn.textContent = "\u2713";
|
|
242
|
+
setTimeout(() => { copyBtn.innerHTML = svgIcon; }, 1500);
|
|
243
|
+
};
|
|
244
|
+
if (navigator.clipboard?.writeText) {
|
|
245
|
+
navigator.clipboard.writeText(name).then(showSuccess).catch(() => {
|
|
246
|
+
fallbackCopy(name);
|
|
247
|
+
showSuccess();
|
|
248
|
+
});
|
|
249
|
+
} else {
|
|
250
|
+
fallbackCopy(name);
|
|
251
|
+
showSuccess();
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
fileNameEl.appendChild(copyBtn);
|
|
255
|
+
}
|
|
256
|
+
|
|
192
257
|
const toggle = document.createElement("button");
|
|
193
258
|
toggle.className = "diff-toggle";
|
|
194
259
|
toggle.type = "button";
|
|
195
|
-
toggle.setAttribute("aria-expanded", "true");
|
|
196
260
|
toggle.setAttribute("aria-label", "Toggle file diff");
|
|
197
|
-
|
|
261
|
+
|
|
262
|
+
if (shouldExpand) {
|
|
263
|
+
toggle.setAttribute("aria-expanded", "true");
|
|
264
|
+
toggle.textContent = "\u25BE";
|
|
265
|
+
diff.classList.add("expanded");
|
|
266
|
+
} else {
|
|
267
|
+
toggle.setAttribute("aria-expanded", "false");
|
|
268
|
+
toggle.textContent = "\u25B8";
|
|
269
|
+
}
|
|
198
270
|
header.appendChild(toggle);
|
|
199
271
|
|
|
200
272
|
header.addEventListener("click", (e) => {
|
|
@@ -202,9 +274,31 @@ function initDiffCollapse() {
|
|
|
202
274
|
const expanded = toggle.getAttribute("aria-expanded") === "true";
|
|
203
275
|
toggle.setAttribute("aria-expanded", String(!expanded));
|
|
204
276
|
toggle.textContent = expanded ? "\u25B8" : "\u25BE";
|
|
205
|
-
diff.
|
|
277
|
+
diff.classList.toggle("expanded");
|
|
206
278
|
});
|
|
207
279
|
}
|
|
280
|
+
|
|
281
|
+
document.getElementById("expand-all")?.addEventListener("click", () => {
|
|
282
|
+
for (const wrapper of wrappers) {
|
|
283
|
+
const toggle = wrapper.querySelector(".diff-toggle");
|
|
284
|
+
const diff = wrapper.querySelector(".d2h-file-diff");
|
|
285
|
+
if (!toggle || !diff) continue;
|
|
286
|
+
toggle.setAttribute("aria-expanded", "true");
|
|
287
|
+
toggle.textContent = "\u25BE";
|
|
288
|
+
diff.classList.add("expanded");
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
document.getElementById("collapse-all")?.addEventListener("click", () => {
|
|
293
|
+
for (const wrapper of wrappers) {
|
|
294
|
+
const toggle = wrapper.querySelector(".diff-toggle");
|
|
295
|
+
const diff = wrapper.querySelector(".d2h-file-diff");
|
|
296
|
+
if (!toggle || !diff) continue;
|
|
297
|
+
toggle.setAttribute("aria-expanded", "false");
|
|
298
|
+
toggle.textContent = "\u25B8";
|
|
299
|
+
diff.classList.remove("expanded");
|
|
300
|
+
}
|
|
301
|
+
});
|
|
208
302
|
}
|
|
209
303
|
|
|
210
304
|
function initBaseSelector() {
|
|
@@ -222,7 +316,7 @@ function initBaseSelector() {
|
|
|
222
316
|
}
|
|
223
317
|
|
|
224
318
|
window.addEventListener("load", () => {
|
|
225
|
-
preserveQueryParamsOnInternalLinks(["ignored", "watch", "base"]);
|
|
319
|
+
preserveQueryParamsOnInternalLinks(["ignored", "watch", "base", "show_all"]);
|
|
226
320
|
renderMath();
|
|
227
321
|
renderMermaid();
|
|
228
322
|
initTimezoneToggle();
|
package/src/server.js
CHANGED
|
@@ -143,8 +143,7 @@ async function getGitDiffRaw(repoRootReal, base) {
|
|
|
143
143
|
async function getGitInfo(repoRootReal) {
|
|
144
144
|
const gitDir = path.join(repoRootReal, ".git");
|
|
145
145
|
try {
|
|
146
|
-
|
|
147
|
-
if (!stat.isDirectory()) return { branch: null, commit: null };
|
|
146
|
+
await fs.stat(gitDir);
|
|
148
147
|
} catch {
|
|
149
148
|
return { branch: null, commit: null };
|
|
150
149
|
}
|
|
@@ -387,6 +386,7 @@ export async function startServer({ repoRoot, host, port, watch }) {
|
|
|
387
386
|
if (req.query.watch === "0") query.set("watch", "0");
|
|
388
387
|
if (req.query.ignored === "1") query.set("ignored", "1");
|
|
389
388
|
if (base !== "HEAD") query.set("base", base);
|
|
389
|
+
if (req.query.show_all === "1") query.set("show_all", "1");
|
|
390
390
|
const querySuffix = query.toString() ? `?${query.toString()}` : "";
|
|
391
391
|
|
|
392
392
|
const [branches, tags, diffResult] = await Promise.all([
|
|
@@ -395,9 +395,17 @@ export async function startServer({ repoRoot, host, port, watch }) {
|
|
|
395
395
|
getGitDiffRaw(repoRootReal, base),
|
|
396
396
|
]);
|
|
397
397
|
|
|
398
|
+
const MAX_DIFF_FILES = 30;
|
|
398
399
|
let diffHtml = "";
|
|
400
|
+
let fileCount = 0;
|
|
401
|
+
const showAll = req.query.show_all === "1";
|
|
399
402
|
if (!diffResult.tooLarge && diffResult.raw) {
|
|
400
|
-
|
|
403
|
+
const parsed = diff2html.parse(diffResult.raw);
|
|
404
|
+
fileCount = parsed.length;
|
|
405
|
+
const toRender = (!showAll && parsed.length > MAX_DIFF_FILES)
|
|
406
|
+
? parsed.slice(0, MAX_DIFF_FILES)
|
|
407
|
+
: parsed;
|
|
408
|
+
diffHtml = diff2html.html(toRender, {
|
|
401
409
|
outputFormat: "line-by-line",
|
|
402
410
|
drawFileList: true,
|
|
403
411
|
});
|
|
@@ -416,6 +424,8 @@ export async function startServer({ repoRoot, host, port, watch }) {
|
|
|
416
424
|
diffHtml,
|
|
417
425
|
tooLarge: diffResult.tooLarge,
|
|
418
426
|
empty: !diffResult.raw,
|
|
427
|
+
fileCount,
|
|
428
|
+
showAll,
|
|
419
429
|
}),
|
|
420
430
|
);
|
|
421
431
|
} catch (e) {
|
package/src/views.js
CHANGED
|
@@ -293,6 +293,8 @@ export function renderDiffPage({
|
|
|
293
293
|
diffHtml,
|
|
294
294
|
tooLarge,
|
|
295
295
|
empty,
|
|
296
|
+
fileCount,
|
|
297
|
+
showAll,
|
|
296
298
|
}) {
|
|
297
299
|
const branchOptions = branches
|
|
298
300
|
.map((b) => {
|
|
@@ -323,6 +325,16 @@ export function renderDiffPage({
|
|
|
323
325
|
content = diffHtml;
|
|
324
326
|
}
|
|
325
327
|
|
|
328
|
+
const MAX_DIFF_FILES = 30;
|
|
329
|
+
let truncatedMsg = "";
|
|
330
|
+
if (fileCount > MAX_DIFF_FILES && !showAll) {
|
|
331
|
+
const hidden = fileCount - MAX_DIFF_FILES;
|
|
332
|
+
const showAllQuery = new URLSearchParams(querySuffix ? querySuffix.slice(1) : "");
|
|
333
|
+
showAllQuery.set("show_all", "1");
|
|
334
|
+
const showAllHref = `/diff?${showAllQuery.toString()}`;
|
|
335
|
+
truncatedMsg = `<div class="diff-truncated note">${hidden} more file${hidden === 1 ? "" : "s"} not shown. <a class="link" href="${showAllHref}">Show all ${fileCount} files</a></div>`;
|
|
336
|
+
}
|
|
337
|
+
|
|
326
338
|
const body = `<section class="panel">
|
|
327
339
|
<div class="panel-title">
|
|
328
340
|
<span>Compare working tree against</span>
|
|
@@ -332,6 +344,7 @@ export function renderDiffPage({
|
|
|
332
344
|
</div>
|
|
333
345
|
<div class="diff-wrap">
|
|
334
346
|
${content}
|
|
347
|
+
${truncatedMsg}
|
|
335
348
|
</div>
|
|
336
349
|
</section>`;
|
|
337
350
|
|