aboutsummaryrefslogtreecommitdiff
path: root/src/app.ts
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 /src/app.ts
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 'src/app.ts')
-rw-r--r--src/app.ts46
1 files changed, 46 insertions, 0 deletions
diff --git a/src/app.ts b/src/app.ts
index 78b59ae..ca79b1f 100644
--- a/src/app.ts
+++ b/src/app.ts
@@ -5,8 +5,10 @@ import {
getGitHubBlob,
getGitHubCommits,
getGitHubIssues,
+ getGitHubLatestCommit,
getGitHubOwner,
getGitHubPulls,
+ getGitHubRefs,
getGitHubRelease,
getGitHubReleases,
getGitHubRepo,
@@ -44,6 +46,29 @@ export function createApp(eta: Eta) {
return c.html(eta.render(template, data));
}
+ // For fragments fetched asynchronously by client-side JS (see public/static/refs.js):
+ // errors render as an empty body rather than the full error.eta page.
+ async function tryRenderFragment<T extends object>(
+ c: Context,
+ template: string,
+ promise: Promise<T>,
+ ) {
+ let data: T;
+ try {
+ data = await promise;
+ } catch (e) {
+ const status =
+ e instanceof GitHubHTTPError
+ ? (e.status as StatusCode)
+ : e instanceof RedirectError
+ ? 404
+ : 500;
+ c.status(status);
+ return c.body(null);
+ }
+ return c.html(eta.render(template, data));
+ }
+
app.get("/:owner", async (c) => {
const { owner } = c.req.param();
return tryRender(c, "owner.eta", getGitHubOwner(owner));
@@ -112,6 +137,27 @@ export function createApp(eta: Eta) {
return tryRender(c, "commits.eta", getGitHubCommits(owner, repo, branch, path));
});
+ // Internal endpoints fetched asynchronously by client-side JS, not meant to
+ // 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();
+ if (type !== "branches" && type !== "tags") {
+ return c.notFound();
+ }
+ return tryRenderFragment(c, "_refs.eta", getGitHubRefs(owner, repo, type));
+ });
+
+ app.get("/api/:owner/:repo/latest-commit/:branch/:path{.*}?", async (c) => {
+ const { owner, repo, branch, path = "" } = c.req.param();
+ return tryRenderFragment(
+ c,
+ "_latestCommit.eta",
+ getGitHubLatestCommit(owner, repo, branch, path),
+ );
+ });
+
app.get("/:owner/:repo/releases/tag/:tag", async (c) => {
const { owner, repo, tag } = c.req.param();
return tryRender(c, "release.eta", getGitHubRelease(owner, repo, tag));