aboutsummaryrefslogtreecommitdiff
path: root/public/static/refs.js
diff options
context:
space:
mode:
authorJoe Mou <dev@mou.fo>2026-07-21 17:46:58 -0400
committerJoe Mou <dev@mou.fo>2026-07-22 23:25:26 -0400
commitecd01c4617ec0a6af05cfc4388bdc82986f25961 (patch)
tree89aa59db1c191af4ed6ccb4196fc800d74a195e6 /public/static/refs.js
parentfb16dc2b017e339e6a36a89c9b5d8a830ce3a541 (diff)
Add async branch/tag dropdown and latest-commit bar
Adds two lazily-fetched UI pieces to match vanilla GitHub while keeping initial page loads plain server-rendered HTML: a branch/tag selector (native <dialog>, CSS anchor-positioned to its button, light-dismissed via closedby="any") and a latest-commit bar above the file listing. Both are populated by new /api/:owner/:repo/refs/:type and /api/:owner/:repo/latest-commit/:branch/:path routes; the latter calls GitHub's own /latest-commit JSON endpoint (the same one the React UI uses) rather than scraping the full commit history page.
Diffstat (limited to 'public/static/refs.js')
-rw-r--r--public/static/refs.js60
1 files changed, 60 insertions, 0 deletions
diff --git a/public/static/refs.js b/public/static/refs.js
new file mode 100644
index 0000000..28b8373
--- /dev/null
+++ b/public/static/refs.js
@@ -0,0 +1,60 @@
+// Progressive enhancement for two bits of data GitHub itself loads lazily:
+// the branch/tag dropdown and the latest-commit bar above a file listing.
+// Both degrade gracefully without JS: the button's command/commandfor still
+// opens the (empty) dialog natively; the commit placeholder just never fills
+// in.
+document.addEventListener("DOMContentLoaded", () => {
+ const dialog = document.querySelector(".ref-selector-menu");
+ if (dialog) {
+ const list = dialog.querySelector(".ref-selector-list");
+ const { owner, repo } = dialog.dataset;
+ const cache = {};
+
+ async function load(type) {
+ if (Object.prototype.hasOwnProperty.call(cache, type)) {
+ list.innerHTML = cache[type];
+ return;
+ }
+ list.textContent = "Loading…";
+ try {
+ const res = await fetch(`/api/${owner}/${repo}/refs/${type}`);
+ const html = res.ok ? await res.text() : "";
+ cache[type] = html || "Failed to load.";
+ list.innerHTML = cache[type];
+ } catch {
+ list.textContent = "Failed to load.";
+ }
+ }
+
+ let opened = false;
+ dialog.addEventListener("toggle", () => {
+ if (dialog.open && !opened) {
+ opened = true;
+ load("branches");
+ }
+ });
+
+ for (const tab of dialog.querySelectorAll("[data-ref-type]")) {
+ tab.addEventListener("click", () => {
+ for (const other of dialog.querySelectorAll("[data-ref-type]")) {
+ other.classList.toggle("active", other === tab);
+ }
+ load(tab.dataset.refType);
+ });
+ }
+ }
+
+ const commitPlaceholder = document.querySelector(".latest-commit-placeholder");
+ if (commitPlaceholder) {
+ fetch(commitPlaceholder.dataset.src)
+ .then((res) => (res.ok ? res.text() : ""))
+ .then((html) => {
+ if (html.trim()) {
+ commitPlaceholder.outerHTML = html;
+ } else {
+ commitPlaceholder.remove();
+ }
+ })
+ .catch(() => commitPlaceholder.remove());
+ }
+});