<script lang="ts">
import { resolve } from "$app/paths";
import Copy from "$icon/copy";
import FolderCode from "$icon/folder-code";
import CommitAuthor from "#lib/components/repo/CommitAuthor.svelte";
import CommitBodyToggle from "#lib/components/repo/CommitBodyToggle.svelte";
import CommitShaButton from "#lib/components/repo/CommitShaButton.svelte";
import { verifiedSignature } from "#lib/components/repo/commitSignature.ts";
import Button, { type ButtonVariants } from "#lib/components/ui/Button.svelte";
import DropdownItem from "#lib/components/ui/DropdownItem.svelte";
import MoreMenu from "#lib/components/ui/MoreMenu.svelte";
import Pending from "#lib/components/ui/Pending.svelte";
import Tag from "#lib/components/ui/Tag.svelte";
import TimeAgo from "#lib/components/ui/TimeAgo.svelte";
import type { CommitStatuses } from "#lib/api/commitStatuses.ts";
import PipelineWorkflows from "#lib/components/repo/pipelines/PipelineWorkflows.svelte";
import type { CommitSummary } from "#lib/components/repo/types.ts";
import { cn, tv } from "tailwind-variants";
const row = tv({
base: "relative grid grid-cols-[minmax(0,max-content)_max-content_max-content_1fr] items-center gap-1 p-4 hover:bg-background-subtle max-md:text-foreground-muted md:col-span-full md:grid-cols-subgrid md:grid-rows-[minmax(3rem,auto)] md:gap-x-[normal] md:gap-y-0 md:py-0"
});
interface Props {
ownerHandle: string;
repoName: string;
commit: CommitSummary;
changeIds?: string[];
tagsByCommit?: Record<string, string[]> | Promise<Record<string, string[]>>;
pipelineStatuses?: Promise<CommitStatuses>;
hasCi?: boolean;
class?: string;
}
let {
ownerHandle,
repoName,
commit,
changeIds,
tagsByCommit,
pipelineStatuses,
hasCi = false,
class: className
}: Props = $props();
const base = $derived(`/${ownerHandle}/${repoName}`);
const commitHref = $derived(resolve(`${base}/commit/${commit.hash}` as "/"));
const treeHref = $derived(resolve(`${base}/tree/${commit.hash}` as "/"));
let expanded = $state(false);
const hasBody = $derived(commit.body.trim() !== "");
const lead = $derived(commit.subject.slice(0, commit.subject.lastIndexOf(" ") + 1));
const last = $derived(commit.subject.slice(lead.length));
</script>
{#snippet author(extra = "")}
<span class="relative z-1 flex min-h-7 min-w-0 items-center gap-xs {extra}">
<CommitAuthor
name={commit.authorName}
email={commit.authorEmail}
did={commit.authorDid}
handle={commit.authorHandle}
/>
</span>
{/snippet}
{#snippet actions()}
<span class="flex items-center gap-xs">
<div class="flex flex-row gap-none">
<Button
variant="ghost"
size="small"
href={treeHref}
title="Browse repository at this commit"
aria-label="Browse repository at this commit"
icon={FolderCode}
class="fill-foreground-muted stroke-foreground-muted text-foreground-muted"
/>
</div>
</span>
{/snippet}
{#snippet subject()}
<span class="min-w-0 space-x-xs md:flex md:items-center md:gap-xs md:space-x-0">
{#if hasBody}
<a
href={commitHref}
class="wrap-break-word text-foreground-default no-underline after:absolute after:inset-0 hover:no-underline max-md:me-0 md:truncate"
title={commit.subject}>{lead}<span class="max-md:sr-only">{last}</span></a
><span class="whitespace-nowrap"
><span class="text-foreground-default md:hidden" aria-hidden="true">{last}</span>
<CommitBodyToggle
{expanded}
onclick={() => (expanded = !expanded)}
class="relative z-1 ml-xs align-middle md:ml-0"
/></span
>
{:else}
<a
href={commitHref}
class="wrap-break-word text-foreground-default no-underline after:absolute after:inset-0 hover:no-underline md:truncate"
title={commit.subject}
>
{commit.subject}
</a>
{/if}
{#if tagsByCommit}
<Pending>
{#each (await tagsByCommit)[commit.hash] ?? [] as name (name)}
<Tag
color="inset"
class="max-w-40 shrink-0 typography-monospace-regular font-normal"
truncate
title={name}>{name}</Tag
>
{/each}
</Pending>
{/if}
{@render pipeline("min-h-0 inline-flex align-middle md:hidden", "small")}
</span>
{/snippet}
{#snippet pipeline(extra = "", size: ButtonVariants["size"] = "regular")}
{#if pipelineStatuses}
<span class={cn("relative z-1 flex min-h-9 items-center", extra)}>
<Pending>
{@const status = (await pipelineStatuses)[commit.hash]}
{#if status}
<PipelineWorkflows {ownerHandle} {repoName} pipeline={status} {size} />
{/if}
</Pending>
</span>
{/if}
{/snippet}
{#snippet mobileMenu()}
<MoreMenu label="More actions for commit {commit.shortHash}" size="small">
<DropdownItem icon={Copy} onclick={() => navigator.clipboard.writeText(commit.hash)}>
Copy SHA
</DropdownItem>
<DropdownItem icon={FolderCode} href={treeHref}>Browse repository</DropdownItem>
</MoreMenu>
{/snippet}
{#snippet when()}
<TimeAgo
value={commit.when}
class="relative z-1 order-5 shrink-0 whitespace-nowrap text-foreground-muted md:order-0"
/>
{/snippet}
<!--
only the columns come from CommitRows; the rows are the row's own, so the cells
keep a comfortable height and an expanded body drops underneath them.
-->
<div class={row({ class: className })}>
{@render author("order-3 md:order-none md:col-start-1 md:row-start-1")}
<span
class="relative z-1 hidden items-center justify-between gap-xs md:col-start-2 md:row-start-1 md:flex"
>
{@render actions()}
<CommitShaButton
hash={commit.hash}
shortHash={commit.shortHash}
changeId={commit.changeId}
{changeIds}
signature={verifiedSignature(commit)}
/>
</span>
<div class="order-1 col-span-full flex min-w-0 items-start md:order-0 md:contents">
{@render subject()}
<span class="relative z-1 ml-auto shrink-0 md:hidden">{@render mobileMenu()}</span>
</div>
<span class="order-4 shrink-0 md:hidden">committed</span>
<span
class="contents items-center gap-xs md:col-start-4 md:row-start-1 md:flex md:justify-self-end"
>
{@render pipeline(hasCi ? "max-md:hidden min-w-24 justify-end" : "max-md:hidden")}
{@render when()}
</span>
{#if hasBody && expanded}
<p
class="relative z-1 order-2 col-span-full pb-xs typography-paragraph-small wrap-anywhere whitespace-pre-wrap text-foreground-muted md:order-0 md:col-span-1 md:col-start-3 md:row-start-2"
>
{commit.body}
</p>
{/if}
</div>