Detailed changes
@@ -15,8 +15,7 @@ interface BugRowProps {
author: { humanId: string; displayName: string; avatarUrl?: string | null };
createdAt: string;
commentCount: number;
- /** Current repo slug, used to build /:repo/issues/:id and /:repo/user/:id links. */
- repo: string | null;
+ repo: string;
onLabelClick?: (name: string) => void;
}
@@ -36,9 +35,6 @@ export function BugRow({
const isOpen = status === Status.Open;
const StatusIcon = isOpen ? CircleDot : CircleCheck;
- const issueHref = repo ? `/${repo}/issues/${humanId}` : `/issues/${humanId}`;
- const authorHref = repo ? `/${repo}/user/${author.humanId}` : `/user/${author.humanId}`;
-
return (
<div className="border-border hover:bg-muted/30 flex items-start gap-3 border-b px-4 py-3 last:border-0">
<StatusIcon
@@ -52,7 +48,8 @@ export function BugRow({
<div className="min-w-0 flex-1">
<div className="flex flex-wrap items-baseline gap-2">
<Link
- to={issueHref}
+ to="/$repo/issues/$id"
+ params={{ repo, id: humanId }}
className="text-foreground hover:text-primary font-medium hover:underline"
>
{title}
@@ -68,7 +65,11 @@ export function BugRow({
</div>
<p className="text-muted-foreground mt-0.5 text-xs">
#{humanId} opened {formatDistanceToNow(new Date(createdAt), { addSuffix: true })} by{" "}
- <Link to={authorHref} className="hover:underline">
+ <Link
+ to="/$repo/user/$id"
+ params={{ repo, id: author.humanId }}
+ className="hover:underline"
+ >
{author.displayName}
</Link>
</p>
@@ -15,24 +15,16 @@ export interface TreeEntryWithCommit extends GitTreeEntry {
}
interface FileTreeProps {
- repo: string | null;
+ repo: string;
+ currentRef: string;
+ currentPath: string;
entries: TreeEntryWithCommit[];
- path: string;
loading?: boolean;
- onNavigate: (entry: TreeEntryWithCommit) => void;
- onNavigateUp: () => void;
}
// Directory listing table for the code browser. Shows each entry's icon,
// name, last-commit message (linked to commit detail), and relative date.
-export function FileTree({
- repo,
- entries,
- path,
- loading,
- onNavigate,
- onNavigateUp,
-}: FileTreeProps) {
+export function FileTree({ repo, currentRef, currentPath, entries, loading }: FileTreeProps) {
// Directories first, then files — each group alphabetical
const sorted = entries.toSorted((a, b) => {
if (a.type !== b.type) return a.type === GitObjectType.Tree ? -1 : 1;
@@ -45,18 +37,32 @@ export function FileTree({
<div className="border-border overflow-hidden rounded-md border">
<table className="w-full text-sm">
<tbody className="divide-border divide-y">
- {path && (
- <tr className="hover:bg-muted/40 cursor-pointer" onClick={onNavigateUp}>
- <td className="w-6 py-2 pl-4">
- <Folder className="size-4 text-blue-500 dark:text-blue-400" />
+ {currentPath && (
+ <tr className="hover:bg-muted/40">
+ <td colSpan={4}>
+ <Link
+ to="/$repo/tree/$ref/$"
+ params={{
+ repo,
+ ref: currentRef,
+ _splat: currentPath.split("/").slice(0, -1).join("/"),
+ }}
+ className="flex items-center gap-3 py-2 pl-4"
+ >
+ <Folder className="size-4 text-blue-500 dark:text-blue-400" />
+ <span className="text-muted-foreground font-mono">..</span>
+ </Link>
</td>
- <td className="text-muted-foreground px-3 py-2 font-mono">..</td>
- <td className="text-muted-foreground hidden px-3 py-2 md:table-cell" />
- <td className="text-muted-foreground hidden px-4 py-2 text-right md:table-cell" />
</tr>
)}
{sorted.map((entry) => (
- <FileTreeRow key={entry.name} entry={entry} repo={repo} onNavigate={onNavigate} />
+ <FileTreeRow
+ key={entry.name}
+ entry={entry}
+ repo={repo}
+ currentRef={currentRef}
+ currentPath={currentPath}
+ />
))}
</tbody>
</table>
@@ -67,16 +73,23 @@ export function FileTree({
function FileTreeRow({
entry,
repo,
- onNavigate,
+ currentRef,
+ currentPath,
}: {
entry: TreeEntryWithCommit;
- repo: string | null;
- onNavigate: (entry: TreeEntryWithCommit) => void;
+ repo: string;
+ currentRef: string;
+ currentPath: string;
}) {
const isDir = entry.type === GitObjectType.Tree;
+ const entryPath = currentPath ? `${currentPath}/${entry.name}` : entry.name;
+
+ const entryLink = isDir
+ ? { to: "/$repo/tree/$ref/$" as const, params: { repo, ref: currentRef, _splat: entryPath } }
+ : { to: "/$repo/blob/$ref/$" as const, params: { repo, ref: currentRef, _splat: entryPath } };
return (
- <tr className="hover:bg-muted/40 cursor-pointer" onClick={() => onNavigate(entry)}>
+ <tr className="hover:bg-muted/40">
<td className="w-6 py-2 pl-4">
{isDir ? (
<Folder className="size-4 text-blue-500 dark:text-blue-400" />
@@ -85,17 +98,16 @@ function FileTreeRow({
)}
</td>
<td className="px-3 py-2">
- <span className={`font-mono ${isDir ? "text-foreground font-medium" : "text-foreground"}`}>
+ <Link {...entryLink} className={`font-mono ${isDir ? "font-medium" : ""} hover:underline`}>
{entry.name}
- </span>
+ </Link>
</td>
<td className="text-muted-foreground hidden max-w-xs truncate px-3 py-2 md:table-cell">
{entry.lastCommit && (
<Link
to="/$repo/commit/$hash"
- params={{ repo: repo!, hash: entry.lastCommit.hash }}
+ params={{ repo, hash: entry.lastCommit.hash }}
className="hover:text-foreground hover:underline"
- onClick={(e) => e.stopPropagation()}
>
{entry.lastCommit.message}
</Link>
@@ -2,7 +2,7 @@
import { gql } from "@apollo/client";
import { useQuery } from "@apollo/client/react";
-import { createFileRoute, useNavigate } from "@tanstack/react-router";
+import { createFileRoute } from "@tanstack/react-router";
import {
GitObjectType,
@@ -70,7 +70,6 @@ export const Route = createFileRoute("/$repo/_code/tree/$ref/$")({
function TreeView() {
const { repo, ref: currentRef, _splat: currentPath = "" } = Route.useParams();
const { ref: repoRef } = Route.useRouteContext();
- const navigate = useNavigate();
const { data: treeData, loading: treeLoading } = useQuery<TreeQueryData>(TREE_QUERY, {
variables: { repo: repoRef, ref: currentRef, path: currentPath || null },
@@ -104,39 +103,14 @@ function TreeView() {
});
const readme: string | null = readmeBlobData?.repository?.blob?.text ?? null;
- function handleEntryClick(entry: TreeEntryWithCommit) {
- const newPath = currentPath ? `${currentPath}/${entry.name}` : entry.name;
- if (entry.type === GitObjectType.Blob) {
- void navigate({
- to: "/$repo/blob/$ref/$",
- params: { repo, ref: currentRef, _splat: newPath },
- });
- } else {
- void navigate({
- to: "/$repo/tree/$ref/$",
- params: { repo, ref: currentRef, _splat: newPath },
- });
- }
- }
-
- function handleNavigateUp() {
- const parts = currentPath.split("/").filter(Boolean);
- parts.pop();
- void navigate({
- to: "/$repo/tree/$ref/$",
- params: { repo, ref: currentRef, _splat: parts.join("/") },
- });
- }
-
return (
<>
<FileTree
repo={repo}
+ currentRef={currentRef}
+ currentPath={currentPath}
entries={entriesWithCommits}
- path={currentPath}
loading={treeLoading}
- onNavigate={handleEntryClick}
- onNavigateUp={handleNavigateUp}
/>
{readme && (
<div className="rounded-md border">
@@ -3,7 +3,7 @@
import { gql } from "@apollo/client";
import { useReadQuery } from "@apollo/client/react";
-import { createFileRoute, Link } from "@tanstack/react-router";
+import { createFileRoute, Link, useCanGoBack, useRouter } from "@tanstack/react-router";
import { format } from "date-fns";
import { ArrowLeft, GitCommit } from "lucide-react";
@@ -68,6 +68,8 @@ function RouteComponent() {
const { repo } = Route.useParams();
const { commitRef } = Route.useLoaderData();
const { data } = useReadQuery(commitRef);
+ const canGoBack = useCanGoBack();
+ const router = useRouter();
const commit = data?.repository?.commit;
if (!commit) return null;
@@ -77,15 +79,15 @@ function RouteComponent() {
return (
<div>
- <button
- onClick={() => {
- window.history.back();
- }}
- className="text-muted-foreground hover:text-foreground mb-6 flex items-center gap-1.5 text-sm"
- >
- <ArrowLeft className="size-3.5" />
- Back
- </button>
+ {canGoBack && (
+ <button
+ onClick={() => router.history.back()}
+ className="text-muted-foreground hover:text-foreground mb-6 flex items-center gap-1.5 text-sm"
+ >
+ <ArrowLeft className="size-3.5" />
+ Back
+ </button>
+ )}
<div className="border-border mb-6 rounded-md border p-5">
<div className="mb-1 flex items-start gap-3">