// c51 — UI: tree listing + blob viewer for the local bare repo. // Visited at /GIT/:repo/tree/:ref/ and /blob/:ref/. // Renders through tdd.md's chrome (renderPage with bodyHtml). Markdown // blobs get parsed via marked; everything else is rendered as // preformatted source. import { marked } from "marked"; import { renderPage, escape } from "./b51_render_layout.ts"; import type { TreeEntry } from "./a31_git_parse.ts"; const shortSha = (sha: string): string => sha.slice(0, 7); // Build a breadcrumb: "owner/repo · main · content/blog" with each // segment a clickable link to /GIT/.../tree//. const renderBreadcrumb = (params: { owner: string; repo: string; ref: string; path: string; asBlob?: boolean; }): string => { const { owner, repo, ref, path, asBlob } = params; const repoLink = `${escape(owner)}/${escape(repo)}`; const refLink = `${escape(ref)}`; if (path === "") return `

${repoLink} · ${refLink}

`; const segments = path.split("/"); const lastIdx = segments.length - 1; const links = segments .map((seg, i) => { const so_far = segments.slice(0, i + 1).join("/"); // For blob view, the last segment is the file itself — no link. // For tree view, every segment links to the tree at that depth. const isLastFile = asBlob && i === lastIdx; if (isLastFile) return `${escape(seg)}`; return `${escape(seg)}`; }) .join(" / "); return `

${repoLink} · ${refLink} · ${links}

`; }; // Sort: trees first, then blobs, alphabetically within each group. // Mirrors what GitHub / Forgejo's tree views do. const sortEntries = (entries: TreeEntry[]): TreeEntry[] => { return [...entries].sort((a, b) => { if (a.type !== b.type) return a.type === "tree" ? -1 : 1; return a.name.localeCompare(b.name); }); }; const renderTreeRow = (params: { entry: TreeEntry; owner: string; repo: string; ref: string; parentPath: string; }): string => { const { entry, owner, repo, ref, parentPath } = params; const childPath = parentPath === "" ? entry.name : `${parentPath}/${entry.name}`; const icon = entry.type === "tree" ? "📁" : entry.type === "commit" ? "🔗" : // submodule "📄"; const kind = entry.type === "tree" ? "tree" : "blob"; const href = `/GIT/${escape(repo)}/${kind}/${escape(ref)}/${escape(childPath)}`; return ` ${icon} ${escape(entry.name)} ${escape(shortSha(entry.sha))} `; }; export const renderRepoTree = async (params: { owner: string; repo: string; ref: string; path: string; entries: TreeEntry[]; }): Promise => { const { owner, repo, ref, path, entries } = params; const sorted = sortEntries(entries); const upRow = path === "" ? "" : (() => { const parentPath = path.includes("/") ? path.slice(0, path.lastIndexOf("/")) : ""; const upHref = parentPath === "" ? `/GIT/${escape(repo)}/tree/${escape(ref)}` : `/GIT/${escape(repo)}/tree/${escape(ref)}/${escape(parentPath)}`; return `⬆..`; })(); const rows = entries.length === 0 ? `empty tree` : upRow + sorted.map((entry) => renderTreeRow({ entry, owner, repo, ref, parentPath: path })).join(""); const titlePath = path === "" ? "" : ` · ${path}`; const inner = `
${renderBreadcrumb({ owner, repo, ref, path })}

${escape(path === "" ? `${owner}/${repo}` : path)}

${entries.length} entr${entries.length === 1 ? "y" : "ies"} at ${escape(ref)}

${rows}
`; return renderPage({ title: `${owner}/${repo}${titlePath} — tdd.md`, bodyHtml: inner, description: `Repository tree at ${ref}${path ? "/" + path : ""} on tdd.md.`, noindex: true, bodyClass: "commit-body-page", hideNav: true, }); }; const isMarkdown = (path: string): boolean => path.endsWith(".md"); export const renderRepoBlob = async (params: { owner: string; repo: string; ref: string; path: string; content: string; }): Promise => { const { owner, repo, ref, path, content } = params; const filename = path.split("/").pop() ?? path; // Markdown gets rendered through marked; code files get a

  // block; everything else also 
 (we don't try to syntax-highlight,
  // just render readable monospace).
  const bodyHtml = isMarkdown(path)
    ? `
${await marked.parse(content, { gfm: true, breaks: false })}
` : `
${escape(content)}
`; const inner = `
${renderBreadcrumb({ owner, repo, ref, path, asBlob: true })}
${escape(filename)} ${content.split("\n").length} lines · ${content.length} bytes raw ${isMarkdown(path) ? `· source` : ""}
${bodyHtml}
`; return renderPage({ title: `${path} · ${owner}/${repo} — tdd.md`, bodyHtml: inner, description: `${path} at ${ref} on tdd.md.`, noindex: true, bodyClass: "commit-body-page", hideNav: true, }); };