summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoe Mou <dev@mou.fo>2026-08-11 13:32:44 -0400
committerJoe Mou <dev@mou.fo>2026-08-11 17:37:02 -0400
commit866f99c414cb3d102483ce21c68a0e62b394baaa (patch)
tree973b8e526bf51e8a05f6daab262d84035b7e2901
parent10f923b51e2844486651493b44974e418a2be6d9 (diff)
Keep the ref picker on the current view and path
-rw-r--r--cgithub/public/static/refs.js6
-rw-r--r--cgithub/src/app.test.ts52
-rw-r--r--cgithub/src/app.ts12
-rw-r--r--cgithub/views/_nav.eta4
-rw-r--r--cgithub/views/_refs.eta6
-rw-r--r--cgithub/views/_tree.eta2
-rw-r--r--cgithub/views/blob.eta2
-rw-r--r--cgithub/views/commits.eta2
8 files changed, 74 insertions, 12 deletions
diff --git a/cgithub/public/static/refs.js b/cgithub/public/static/refs.js
index ffff505..f3e07e8 100644
--- a/cgithub/public/static/refs.js
+++ b/cgithub/public/static/refs.js
@@ -7,7 +7,9 @@ document.addEventListener("DOMContentLoaded", () => {
const dialog = document.querySelector(".ref-selector-menu");
if (dialog) {
const list = dialog.querySelector(".ref-selector-list");
- const { owner, repo } = dialog.dataset;
+ // The view and path let the picker link to the same place on another ref
+ // rather than to the repository root.
+ const { owner, repo, view, path = "" } = dialog.dataset;
const cache = {};
async function load(type) {
@@ -17,7 +19,7 @@ document.addEventListener("DOMContentLoaded", () => {
}
list.textContent = "Loading…";
try {
- const res = await fetch(`/api/${owner}/${repo}/refs/${type}`);
+ const res = await fetch(`/api/${owner}/${repo}/refs/${type}/${view}/${path}`);
const html = res.ok ? await res.text() : "";
cache[type] = html || "Failed to load.";
list.innerHTML = cache[type];
diff --git a/cgithub/src/app.test.ts b/cgithub/src/app.test.ts
index 498d8a6..6544f03 100644
--- a/cgithub/src/app.test.ts
+++ b/cgithub/src/app.test.ts
@@ -8,6 +8,56 @@ import { createApp } from "./app.ts";
const eta = new Eta({ views: path.join(import.meta.dirname, "..", "views") });
const app = createApp(eta);
+describe("branch picker", () => {
+ const repo = { owner: "actions", name: "deploy-pages" };
+ const refs = [{ name: "main", isDefault: true }, { name: "v4" }];
+
+ it("should keep the current view and path", () => {
+ const blob = eta.render("_refs.eta", {
+ repo,
+ refs,
+ type: "branches",
+ view: "blob",
+ path: "src/main.ts",
+ });
+ const tree = eta.render("_refs.eta", {
+ repo,
+ refs,
+ type: "branches",
+ view: "tree",
+ path: "src",
+ });
+
+ assert.match(blob, /href="\/actions\/deploy-pages\/blob\/main\/src\/main.ts"/);
+ assert.match(blob, /href="\/actions\/deploy-pages\/blob\/v4\/src\/main.ts"/);
+ assert.match(tree, /href="\/actions\/deploy-pages\/tree\/v4\/src"/);
+ });
+
+ it("should link to the root tree without a path", () => {
+ const html = eta.render("_refs.eta", { repo, refs, type: "branches", view: "tree", path: "" });
+
+ assert.match(html, /href="\/actions\/deploy-pages\/tree\/main"/);
+ });
+
+ it("should not build ref links for views it does not serve", async () => {
+ const res = await app.request("http://cgithub.example/api/a/b/refs/branches/raw/README.md");
+
+ assert.strictEqual(res.status, 404);
+ });
+
+ it("should tell the refs endpoint where the picker was opened", () => {
+ const nav = { repo, branch: "main", path: "src" };
+
+ const blob = eta.render("_nav.eta", { ...nav, view: "blob", path: "src/main.ts" });
+ assert.match(blob, /data-view="blob" data-path="src\/main.ts"/);
+ assert.match(
+ eta.render("_nav.eta", { ...nav, view: "tree" }),
+ /data-view="tree" data-path="src"/,
+ );
+ assert.doesNotMatch(eta.render("_nav.eta", { ...nav, view: "tree", path: "" }), /data-path=/);
+ });
+});
+
describe("redirects to GitHub", () => {
// An HTTP redirect would be caught by the extension's declarativeNetRequest
// rules and bounced straight back here.
@@ -21,7 +71,7 @@ describe("redirects to GitHub", () => {
const body = await res.text();
assert.match(
body,
- /<meta http-equiv="refresh" content="2; url=https:\/\/github.com\/a\/b\/c\?x=1">/,
+ /<meta http-equiv="refresh" content="1; url=https:\/\/github.com\/a\/b\/c\?x=1">/,
);
assert.doesNotMatch(body, /<script/);
});
diff --git a/cgithub/src/app.ts b/cgithub/src/app.ts
index 9a7ee8e..81e47fd 100644
--- a/cgithub/src/app.ts
+++ b/cgithub/src/app.ts
@@ -155,12 +155,18 @@ export function createApp(eta: Eta) {
// be visited directly. Keep the site's initial page loads plain server-
// rendered HTML while still surfacing data (branches/tags, latest commit)
// that would otherwise require an extra scrape on every page view.
- app.get("/api/:owner/:repo/refs/:type", async (c) => {
- const { owner, repo, type } = c.req.param();
+ // The view and path are where the picker was opened, so each ref can link to
+ // the same page on another ref.
+ app.get("/api/:owner/:repo/refs/:type/:view/:path{.*}?", async (c) => {
+ const { owner, repo, type, view, path = "" } = c.req.param();
if (type !== "branches" && type !== "tags") {
return c.notFound();
}
- return tryRenderFragment(c, "_refs.eta", getGitHubRefs(owner, repo, type));
+ if (view !== "tree" && view !== "blob" && view !== "commits") {
+ return c.notFound();
+ }
+ const refs = getGitHubRefs(owner, repo, type).then((data) => ({ ...data, view, path }));
+ return tryRenderFragment(c, "_refs.eta", refs);
});
app.get("/api/:owner/:repo/latest-commit/:branch/:path{.*}?", async (c) => {
diff --git a/cgithub/views/_nav.eta b/cgithub/views/_nav.eta
index eb7ed42..a20fbbc 100644
--- a/cgithub/views/_nav.eta
+++ b/cgithub/views/_nav.eta
@@ -6,7 +6,7 @@
<button type="button" class="ref-selector-button branch" command="show-modal" commandfor="ref-selector-menu">
<img src="/static/icons/git-branch.svg" alt="branch" class="icon"> <%= refLabel %> ▾
</button>
- <dialog class="ref-selector-menu" id="ref-selector-menu" closedby="any" data-owner="<%= it.repo.owner %>" data-repo="<%= it.repo.name %>">
+ <dialog class="ref-selector-menu" id="ref-selector-menu" closedby="any" data-owner="<%= it.repo.owner %>" data-repo="<%= it.repo.name %>" data-view="<%= it.view %>"<% if (it.path && it.path !== "/") { %> data-path="<%= it.path %>"<% } %>>
<div class="ref-selector-tabs">
<button type="button" class="active" data-ref-type="branches">Branches</button>
<button type="button" data-ref-type="tags">Tags</button>
@@ -22,7 +22,7 @@
currentPath += (currentPath ? '/' : '') + part;
const isLast = i === pathParts.length - 1;
if (isLast) {
- %>/<%= part %><%= it.isTree ? "/" : "" %><%
+ %>/<%= part %><%= it.view === "tree" ? "/" : "" %><%
} else {
%>/<a href="/<%= it.repo.owner %>/<%= it.repo.name %>/tree/<%= it.branch %>/<%= currentPath %>"><%= part %></a><%
}
diff --git a/cgithub/views/_refs.eta b/cgithub/views/_refs.eta
index 2450c4b..9d951cc 100644
--- a/cgithub/views/_refs.eta
+++ b/cgithub/views/_refs.eta
@@ -1,10 +1,14 @@
+<%
+const linkPrefix = `/${it.repo.owner}/${it.repo.name}/${it.view}/`;
+const linkSuffix = it.path ? `/${it.path}` : "";
+%>
<% if (it.refs.length === 0) { %>
<div class="ref-selector-empty">No <%= it.type %> found.</div>
<% } else { %>
<ul class="ref-selector-items">
<% for (const ref of it.refs) { %>
<li>
- <a href="/<%= it.repo.owner %>/<%= it.repo.name %>/tree/<%= ref.name %>"><%= ref.name %></a>
+ <a href="<%= linkPrefix %><%= ref.name %><%= linkSuffix %>"><%= ref.name %></a>
<% if (ref.isDefault) { %><span class="ref-selector-badge">default</span><% } %>
</li>
<% } %>
diff --git a/cgithub/views/_tree.eta b/cgithub/views/_tree.eta
index fc7707b..a3c943d 100644
--- a/cgithub/views/_tree.eta
+++ b/cgithub/views/_tree.eta
@@ -1,4 +1,4 @@
-<%~ include("./_nav.eta", {...it, isTree: true}) %>
+<%~ include("./_nav.eta", {...it, view: "tree"}) %>
<% const commitPath = it.path && it.path !== "/" ? it.path : ""; %>
<div class="placeholder commit box" data-src="/api/<%= it.repo.owner %>/<%= it.repo.name %>/latest-commit/<%= it.branch %>/<%= commitPath %>">
diff --git a/cgithub/views/blob.eta b/cgithub/views/blob.eta
index 50f92a9..ed1e84b 100644
--- a/cgithub/views/blob.eta
+++ b/cgithub/views/blob.eta
@@ -1,6 +1,6 @@
<% layout("./layout.eta"); it.searchType = "code" %>
-<%~ include("./_nav.eta", it) %>
+<%~ include("./_nav.eta", {...it, view: "blob"}) %>
<div>
<% if (it.language) { %>
diff --git a/cgithub/views/commits.eta b/cgithub/views/commits.eta
index 2ddcba3..5cf0beb 100644
--- a/cgithub/views/commits.eta
+++ b/cgithub/views/commits.eta
@@ -1,6 +1,6 @@
<% layout("./layout.eta") %>
-<%~ include("./_nav.eta", {...it, isTree: false}) %>
+<%~ include("./_nav.eta", {...it, view: "commits"}) %>
<% for (const group of it.commitGroups) { %>
<h3><%= group.title %></h3>