import { TopicList } from "~/components/TopicList"; import { DropdownMenu } from "@cloudflare/kumo/components/dropdown"; import { Popover } from "@cloudflare/kumo/components/popover"; import { createFileRoute, Link, Outlet, redirect, useMatchRoute, useNavigate } from "@tanstack/react-router"; import { useEffect, useState } from "react"; import { setRepoVisibility } from "~/lib/analytics"; import { forkRepo, getRepoHeader, toggleStar, transferableOwners } from "~/fns/repos"; import { WatchMenu } from "~/components/WatchMenu"; import { useAction } from "~/hooks/use-action"; import { useOptimisticMutation } from "~/hooks/use-optimistic-mutation"; import { useViewer } from "~/hooks/use-viewer"; import { Badge } from "~/ui/Badge"; import { Button } from "~/ui/Button"; import { inputClass } from "~/ui/Field"; import { cn } from "~/utils/cn"; import { RepoProvisioning } from "~/components/RepoProvisioning"; import { usePollWhile } from "~/hooks/use-poll-while"; import { useEditorHotkey } from "~/hooks/use-editor-hotkey"; import { TextLink } from "~/ui/TextLink"; import { CountBadge } from "~/ui/CountBadge"; import { RouteTabs } from "~/ui/Nav"; import { SignInLink } from "~/components/SignInPrompt"; import { ReportLink } from "~/components/ReportLink"; import { seo } from "~/utils/seo"; export const Route = createFileRoute("/$owner/$repo")({ loader: async ({ params, location }) => { const header = await getRepoHeader({ data: { owner: params.owner, repo: params.repo } }); const canonical = `/${header.repo.owner}/${header.repo.name}`; if (canonical.toLowerCase() !== `/${params.owner}/${params.repo}`.toLowerCase()) { const rest = location.pathname.split("/").slice(3).join("/"); throw redirect({ href: `${canonical}${rest ? `/${rest}` : ""}${location.searchStr}` }); } // Before the navigation resolves, so the pageview (sent on resolve) already knows whether the path is public. if (typeof window !== "undefined") { setRepoVisibility(header.repo.owner, header.repo.name, header.repo.visibility !== "public"); } return header; }, head: ({ params, loaderData, match }) => { const title = `${params.owner}/${params.repo} · gitfor`; if (!loaderData?.og.shareable) { return { meta: seo({ origin: match.context.publicUrl, title: "gitfor", documentTitle: title }) }; } const { repo } = loaderData; return { meta: seo({ origin: match.context.publicUrl, path: `/${repo.owner}/${repo.name}`, title: `${repo.owner}/${repo.name}`, documentTitle: title, description: repo.description ?? `${repo.owner}/${repo.name} on gitfor`, image: loaderData.og.image, imageAlt: `${repo.owner}/${repo.name}${repo.description ? `: ${repo.description}` : ""}`, }), }; }, component: RepoLayout, }); function CloneBox({ url }: { url: string }) { return ( }>Clone ▾ HTTPS event.currentTarget.select()} className={cn(inputClass, "h-8 font-mono text-xs")} /> Authenticate with your username and a personal access token. ); } function ForkButton({ owner, repo, count }: { owner: string; repo: string; count: number }) { const navigate = useNavigate(); const [targets, setTargets] = useState(null); const fork = useAction(forkRepo, { invalidate: false }); async function openChange(open: boolean) { if (!open) return setTargets(null); fork.reset(); setTargets([]); const owners = await transferableOwners(); setTargets((current) => (current === null ? null : owners)); } async function choose(target: string) { const choices = targets; setTargets(null); const result = await fork.run({ data: { owner, repo, targetOwner: target } }); if (result) await navigate({ to: "/$owner/$repo", params: { owner: result.owner, repo: result.name } }); else setTargets(choices); } return ( void openChange(open)}> }> ⑂ {fork.pending ? "Forking…" : "Fork"}{" "} {fork.pending ? count + 1 : count} Fork into… {targets?.length === 0 && Loading accounts…} {targets?.map((target) => ( choose(target)}> {target} ))} {fork.error && {fork.error}} ); } const BACKFILL_POLL_MS = 10_000; function RepoLayout() { const header = Route.useLoaderData(); useEffect(() => { setRepoVisibility(header.repo.owner, header.repo.name, header.repo.visibility !== "public"); }, [header.repo.owner, header.repo.name, header.repo.visibility]); const params = Route.useParams(); const viewer = useViewer(); const star = useOptimisticMutation( { starred: header.starred, count: header.repo.starsCount }, (state, starred: boolean) => ({ starred, count: Math.max(0, state.count + (starred ? 1 : -1)) }), (starred: boolean) => toggleStar({ data: { ...params, star: starred } }), ); const { repo, permission } = header; usePollWhile(repo.status === "ready" && repo.backfilling, BACKFILL_POLL_MS); const canAdmin = permission === "admin"; const repoParams = { owner: repo.owner, repo: repo.name }; const matchRoute = useMatchRoute(); useEditorHotkey(repo.owner, repo.name, repo.defaultBranch, viewer !== null); const tabs = [ { to: "/$owner/$repo", label: "Code", exact: true, show: true, count: null }, { to: "/$owner/$repo/issues", label: "Issues", exact: false, show: repo.hasIssues, count: repo.openIssuesCount }, { to: "/$owner/$repo/pulls", label: "Pull requests", exact: false, show: true, count: repo.openPullsCount }, { to: "/$owner/$repo/discussions", label: "Discussions", exact: false, show: repo.hasIssues, count: null }, { to: "/$owner/$repo/actions", label: "Actions", exact: false, show: repo.hasActions, count: null }, { to: "/$owner/$repo/releases", label: "Releases", exact: false, show: true, count: null }, { to: "/$owner/$repo/security", label: "Security", exact: false, show: true, count: null }, { to: "/$owner/$repo/insights", label: "Insights", exact: false, show: true, count: null }, { to: "/$owner/$repo/settings", label: "Settings", exact: false, show: canAdmin, count: null }, ] as const; return ( {repo.owner} / {repo.name} {repo.visibility} {repo.isArchived && Archived} {header.hidden && Hidden by site admin} {header.ownerDisabled && Account disabled} {repo.status !== "ready" && {repo.status}…} {repo.status === "ready" && repo.backfilling && importing branches…} {!viewer && ( <> > )} {viewer && ( <> star.run(!star.state.starred)} > {star.state.starred ? "★ Starred" : "☆ Star"} {star.state.count} > )} {repo.forkOf && ( forked from{" "} {repo.forkOf.owner}/{repo.forkOf.name} )} {repo.description && {repo.description}} tab.show) .map((tab) => ({ value: tab.to, active: Boolean(matchRoute({ to: tab.to, params: repoParams, fuzzy: !tab.exact })), link: , label: ( {tab.label} {tab.count !== null && tab.count > 0 && } ), }))} /> {repo.status === "ready" ? ( ) : ( )} ); }
Authenticate with your username and a personal access token.
{fork.error}
forked from{" "} {repo.forkOf.owner}/{repo.forkOf.name}
{repo.description}