~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
190 lines · 6.9 KB Raw History
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>
  );
}