website
git clone https://git.pyrossh.dev/website
木 Personal website of pyrossh. Built with astrojs, shiki, vite.
986738a
— pyrossh
2026-11-06T15:29:20+05:30
Show loading feedback for repo page navigation
- assets/css/repos.css +45 -0
- tests/pages.test.js +30 -0
assets/css/repos.css
CHANGED
|
@@ -247,6 +247,14 @@
|
|
|
247
247
|
border-radius: 6px;
|
|
248
248
|
font-size: 0.9rem;
|
|
249
249
|
|
|
250
|
+
/* The tree is fetched separately (see treeSidebar in file-tree.njk). htmx
|
|
251
|
+
puts .htmx-request on this element only for that fetch, so a failed
|
|
252
|
+
request doesn't leave this showing forever. */
|
|
253
|
+
&.htmx-request::before {
|
|
254
|
+
content: "Loading files…";
|
|
255
|
+
opacity: 0.6;
|
|
256
|
+
}
|
|
257
|
+
|
|
250
258
|
ul {
|
|
251
259
|
list-style: none;
|
|
252
260
|
margin: 0 0 0 1.1rem;
|
|
@@ -475,3 +483,40 @@ html:root {
|
|
|
475
483
|
.d2h-wrapper {
|
|
476
484
|
color: var(--color-text);
|
|
477
485
|
}
|
|
486
|
+
|
|
487
|
+
/* Top progress bar for htmx navigation (repo tabs, the file tree, opening a
|
|
488
|
+
file), driven by the .htmx-request class htmx puts on whatever is fetching.
|
|
489
|
+
Showing is delayed so fast or cached responses don't flicker. Hiding is
|
|
490
|
+
delayed too, to bridge the gap between the Code tab's page request and the
|
|
491
|
+
tree request it triggers, which would otherwise blink the bar. */
|
|
492
|
+
body::before {
|
|
493
|
+
content: "";
|
|
494
|
+
position: fixed;
|
|
495
|
+
top: 0;
|
|
496
|
+
left: 0;
|
|
497
|
+
z-index: 100;
|
|
498
|
+
width: 40%;
|
|
499
|
+
height: 3px;
|
|
500
|
+
pointer-events: none;
|
|
501
|
+
background: var(--color-accent);
|
|
502
|
+
opacity: 0;
|
|
503
|
+
transition: opacity 100ms linear 100ms;
|
|
504
|
+
animation: htmx-progress 1.1s ease-in-out infinite;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
body:has(.htmx-request)::before {
|
|
508
|
+
opacity: 1;
|
|
509
|
+
transition-delay: 150ms;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
@keyframes htmx-progress {
|
|
513
|
+
from { transform: translateX(-100%); }
|
|
514
|
+
to { transform: translateX(250%); }
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
@media (prefers-reduced-motion: reduce) {
|
|
518
|
+
body::before {
|
|
519
|
+
width: 100%;
|
|
520
|
+
animation: none;
|
|
521
|
+
}
|
|
522
|
+
}
|
tests/pages.test.js
CHANGED
|
@@ -207,6 +207,36 @@ describe("repo pages", () => {
|
|
|
207
207
|
}
|
|
208
208
|
});
|
|
209
209
|
|
|
210
|
+
test("the Code tab shows loading feedback until the tree arrives", async () => {
|
|
211
|
+
const treeUrl = `**/repos/${repoId}/tree/`;
|
|
212
|
+
let releaseTree;
|
|
213
|
+
const treeHeld = new Promise((resolve) => (releaseTree = resolve));
|
|
214
|
+
await page.route(treeUrl, async (route) => {
|
|
215
|
+
await treeHeld;
|
|
216
|
+
await route.continue();
|
|
217
|
+
});
|
|
218
|
+
const barOpacity = () => getComputedStyle(document.body, "::before").opacity;
|
|
219
|
+
try {
|
|
220
|
+
await goto(`/repos/${repoId}/`);
|
|
221
|
+
await page.waitForFunction(() => htmx);
|
|
222
|
+
await page.locator(".nav a", { hasText: "Code" }).click();
|
|
223
|
+
|
|
224
|
+
await page.locator(".file-explorer-tree.htmx-request").waitFor();
|
|
225
|
+
const placeholder = await page
|
|
226
|
+
.locator(".file-explorer-tree")
|
|
227
|
+
.evaluate((el) => getComputedStyle(el, "::before").content);
|
|
228
|
+
assert.equal(placeholder, '"Loading files…"');
|
|
229
|
+
await page.waitForFunction(`(${barOpacity})() === "1"`);
|
|
230
|
+
|
|
231
|
+
releaseTree();
|
|
232
|
+
await page.locator(".file-explorer-tree li a").first().waitFor({ state: "attached" });
|
|
233
|
+
await page.waitForFunction(`(${barOpacity})() === "0"`);
|
|
234
|
+
} finally {
|
|
235
|
+
releaseTree();
|
|
236
|
+
await page.unroute(treeUrl);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
210
240
|
test("repo files index renders the tree and a default file", async () => {
|
|
211
241
|
const response = await goto(`/repos/${repoId}/files/`);
|
|
212
242
|
assert.equal(response.status(), 200);
|