web/app/%5Bowner%5D/%5Brepo%5D/tree/%5B...path%5D/page.tsxblame
View source
1da98741import type { Metadata } from "next";
3e3af552import Link from "next/link";
bf5fc333import { FileIcon } from "@/app/components/file-icon";
bc1b2ba4import { encodePath } from "@/lib/utils";
bc1b2ba5import { getRepoTree } from "@/lib/grove-api";
3e3af556
3e3af557interface Props {
3e3af558 params: Promise<{ owner: string; repo: string; path: string[] }>;
3e3af559}
3e3af5510
1da987411export async function generateMetadata({ params }: Props): Promise<Metadata> {
86450dc12 const { repo, path: pathParts } = await params;
1da987413 const path = pathParts.slice(1).join("/");
86450dc14 return { title: `${path || "/"} · ${repo}` };
1da987415}
1da987416
3e3af5517
bf5fc3318function sortEntries(entries: any[]) {
bf5fc3319 return [...entries].sort((a, b) => {
bf5fc3320 if (a.type === b.type) return a.name.localeCompare(b.name);
bf5fc3321 return a.type === "tree" ? -1 : 1;
bf5fc3322 });
bf5fc3323}
bf5fc3324
12ffdd425export default async function TreePage({ params }: Props) {
3e3af5526 const { owner, repo, path: pathParts } = await params;
12ffdd427 const ref = pathParts[0] ?? "main";
12ffdd428 const path = pathParts.slice(1).join("/");
3e3af5529
bc1b2ba30 const tree = await getRepoTree(owner, repo, ref, path);
3e3af5531
3e3af5532 if (!tree) {
3e3af5533 return (
135dfe534 <div className="max-w-3xl mx-auto px-4 py-16">
cf89d3c35 <h1 className="text-lg" style={{ color: "var(--text-secondary)" }}>
135dfe536 Path not found
135dfe537 </h1>
3e3af5538 </div>
3e3af5539 );
3e3af5540 }
3e3af5541
3e3af5542 const parts = path.split("/");
3e3af5543
3e3af5544 return (
4dfd09b45 <div className="px-4 py-6">
bf5fc3346 <div className="text-sm" style={{ border: "1px solid var(--border-subtle)" }}>
bf5fc3347 <Link
bf5fc3348 href={
bf5fc3349 parts.length > 1
d744b8250 ? `/${owner}/${repo}/tree/${ref}/${encodePath(parts.slice(0, -1).join("/"))}`
bf5fc3351 : `/${owner}/${repo}`
bf5fc3352 }
bf5fc3353 className="flex items-center gap-2 py-1.5 pl-3 pr-3 hover-row"
bf5fc3354 style={{
bf5fc3355 borderBottom: "1px solid var(--divide)",
bf5fc3356 color: "var(--text-muted)",
bf5fc3357 }}
bf5fc3358 >
bf5fc3359 <FileIcon type="tree" />
bf5fc3360 <span className="hover:underline">..</span>
bf5fc3361 </Link>
bf5fc3362 {sortEntries(tree.entries).map((entry: any) => (
bf5fc3363 <Link
bf5fc3364 key={entry.name}
bf5fc3365 href={
bf5fc3366 entry.type === "tree"
d744b8267 ? `/${owner}/${repo}/tree/${ref}/${encodePath(path)}/${encodePath(entry.name)}`
d744b8268 : `/${owner}/${repo}/blob/${ref}/${encodePath(path)}/${encodePath(entry.name)}`
bf5fc3369 }
bf5fc3370 className="flex items-center gap-2 py-1.5 pl-3 pr-3 hover-row"
bf5fc3371 style={{
bf5fc3372 borderTop: "1px solid var(--divide)",
bf5fc3373 color: "var(--accent)",
bf5fc3374 }}
bf5fc3375 >
bf5fc3376 <FileIcon type={entry.type} name={entry.name} />
bf5fc3377 <span className="hover:underline">{entry.name}</span>
bf5fc3378 </Link>
bf5fc3379 ))}
bf5fc3380 </div>
3e3af5581 </div>
3e3af5582 );
3e3af5583}