chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { GitCommitVerticalIcon } from 'lucide-react';
import { ButtonLink } from '#/components/atoms/button-link.jsx';
import { Link } from '#/components/atoms/link.jsx';
import { AuthorFacepile } from '#/components/molecules/author-facepile.jsx';
import { Breadcrumbs } from '#/components/molecules/breadcrumbs.jsx';
import { CommitCheckStatus } from '#/components/molecules/commit-check-status.jsx';
import { EmptyState } from '#/components/molecules/empty-state.jsx';
import { ListBox } from '#/components/molecules/list-box.jsx';
import { ListSkeleton } from '#/components/molecules/list-skeleton.jsx';
import { PageHead } from '#/components/molecules/page-head.jsx';
import { PageNotice } from '#/components/molecules/page-notice.jsx';
import { ReadingRepo } from '#/components/molecules/reading-repo.jsx';
import { RefSelect } from '#/components/molecules/ref-select.jsx';
import { Screen } from '#/components/molecules/screen.jsx';
import { loadAuthors } from '#/lib/authors.js';
import { PULL_COMMIT_LIMIT, pullsHref } from '#/lib/collab.js';
import { timeAgo } from '#/lib/format.js';
import { isEmpty, repoRecord, repoSize } from '#/lib/git.js';
import { useNavigate } from '#/lib/navigation.jsx';
import { logKey, readLog } from '#/lib/source.js';
const LIMIT = PULL_COMMIT_LIMIT;
/**
* The commits on a ref, or the commits one pull request carries.
*
* A pull request's log is not its branch's: a branch reaches back through
* everything it was cut from, and what somebody reading a pull request wants
* is the commits it added. `base..tip` asks for those, from the copy that
* holds them.
*/
export function CommitsPage({ repo, refName, path, range, from, space }) {
const navigate = useNavigate();
const record = repoRecord(repo, space);
// The tip of a range, which the way to its pull request names.
const tip = range ? range.split('..')[1] : null;
const pulls = useQueryClient().getQueryData(['pulls', repo, space ?? '']);
const { data, error } = useQuery({
queryKey: logKey(repo, refName, range, from, space, path),
enabled: Boolean(record) && !isEmpty(record) && !range,
queryFn: () =>
readLog(repo, { refName, range, from, limit: LIMIT, space, path }),
});
// The rows wait for the authors, so a face never lands after its row.
const authors = useQuery({
queryKey: ['authors'],
queryFn: loadAuthors,
staleTime: Number.POSITIVE_INFINITY,
});
const listed = data && (authors.isSuccess || authors.isError);
if (!record) {
return <PageNotice>No such repository.</PageNotice>;
}
const at = from ? `/${encodeURIComponent(from)}` : '';
const commitHref = (sha) => `/${encodeURIComponent(repo)}/commit${at}/${sha}`;
// An empty repository has no ref for the log to read, so the listing
// says so rather than asking the server for a ref that cannot exist.
if (isEmpty(record) && !range) {
return (
<Screen>
<EmptyState
icon={<GitCommitVerticalIcon />}
title="No commits yet"
description="This repository has no commits. Push the first one and it appears here."
/>
</Screen>
);
}
// The page heads itself the way a forge heads a history: a title, and for
// one path's history the path it is for. A pull request's commits carry
// the way back to the pull request, which no tab above names.
const refChip = (
<code className="rounded bg-muted px-1.5 py-0.5 font-mono text-[12px] text-foreground">
{refName}
</code>
);
const head = range ? (
<PageHead
rule
title="Commits"
back={{ href: pullsHref(repo, pulls, tip), label: 'Pull requests' }}
actions={
<ButtonLink
href={`/${encodeURIComponent(repo)}/commit${at}/${range}`}
variant="ghost"
>
The whole pull request as one diff
</ButtonLink>
}
meta={<span>Every commit the pull request carries.</span>}
/>
) : (
<PageHead
rule
title="Commits"
meta={
path ? (
<span className="flex min-w-0 flex-wrap items-center gap-1.5">
<span className="shrink-0">History for</span>
<Breadcrumbs repo={repo} refName={refName} path={path} />
<span>on</span>
{refChip}
</span>
) : (
<span className="flex items-center gap-1.5">
<span>on</span>
{refChip}
</span>
)
}
/>
);
return (
<Screen bar={head}>
{!range && (
<div className="px-(--gutter) pt-4">
<RefSelect
repo={record}
value={refName}
onSelect={(next) =>
navigate(
`/${encodeURIComponent(repo)}/commits/${encodeURI(next)}${path ? `/${encodeURI(path)}` : ''}`,
)
}
/>
</div>
)}
{error && <PageNotice tone="error">{error.message}</PageNotice>}
{!listed && !error && (
<>
<div className="empty:hidden px-(--gutter) pt-4">
<ReadingRepo size={repoSize(record)} />
</div>
<ListBox>
<ListSkeleton />
</ListBox>
</>
)}
{listed && (
<ListBox>
<ul>
{data.commits.map((commit) => (
<li key={commit.sha} className="border-border border-b">
<Link
href={commitHref(commit.sha)}
className="flex items-start gap-3 px-4 py-3.5 text-foreground no-underline transition-colors hover:bg-accent hover:no-underline"
>
<GitCommitVerticalIcon className="mt-0.5 size-4 shrink-0 text-faint" />
<span className="min-w-0 flex-1">
<span className="block break-words font-medium text-[14px]">
{commit.message.split('\n')[0]}
</span>
<span className="mt-1 flex flex-wrap items-center gap-x-2.5 gap-y-1 text-[12.5px] text-faint">
<AuthorFacepile
ident={commit.author}
message={commit.message}
/>
<code className="font-mono text-key">
{commit.sha.slice(0, 8)}
</code>
<span>{timeAgo(commit.committedAt)}</span>
</span>
</span>
<CommitCheckStatus
repo={repo}
sha={commit.sha}
className="mt-1"
space={space}
/>
</Link>
</li>
))}
{data.truncated && (
<li className="px-4 py-3 text-[12.5px] text-faint">
Showing the newest {LIMIT} commits.
</li>
)}
</ul>
</ListBox>
)}
</Screen>
);
}