This repository has no description
1<script lang="ts">
2 import { resolve } from "$app/paths";
3 import Copy from "$icon/copy";
4 import CopyCheck from "$icon/copy-check";
5 import Ellipsis from "$icon/ellipsis";
6 import FolderCode from "$icon/folder-code";
7 import Avatar from "$lib/components/ui/Avatar.svelte";
8 import Pagination from "$lib/components/ui/Pagination.svelte";
9 import Tag from "$lib/components/ui/Tag.svelte";
10 import TimeAgo from "$lib/components/ui/TimeAgo.svelte";
11 import { createCopyFeedback } from "$lib/copy.svelte";
12 import type { CommitSummary } from "./types";
13
14 interface Props {
15 ownerHandle: string;
16 repoName: string;
17 ref: string;
18 commits: CommitSummary[];
19 tagsByCommit?: Record<string, string[]>;
20 page: number;
21 pageCount: number;
22 onPageChange?: (page: number) => void;
23 }
24
25 let {
26 ownerHandle,
27 repoName,
28 ref,
29 commits,
30 tagsByCommit = {},
31 page,
32 pageCount,
33 onPageChange
34 }: Props = $props();
35
36 const base = $derived(`/${ownerHandle}/${repoName}`);
37
38 let expanded = $state<Record<string, boolean>>({});
39 const copyFeedback = createCopyFeedback();
40</script>
41
42{#snippet authorCell(commit: CommitSummary)}
43 <span class="flex items-center gap-1">
44 <!-- no did/handle on a commit summary, always the placeholder -->
45 <Avatar size="large" />
46 {#if commit.authorEmail}
47 <a href="mailto:{commit.authorEmail}" class="no-underline hover:underline">
48 {commit.authorName}
49 </a>
50 {:else}
51 <span>{commit.authorName}</span>
52 {/if}
53 </span>
54{/snippet}
55
56{#snippet shaChip(commit: CommitSummary, mobile: boolean)}
57 <a
58 href={resolve(`${base}/commit/${commit.hash}` as "/")}
59 title={commit.changeId ? `jj change id: ${commit.changeId}` : undefined}
60 class="flex items-center gap-2 rounded bg-background-inset px-2 {mobile
61 ? 'py-1'
62 : 'py-0.5'} text-foreground-muted no-underline hover:underline"
63 >
64 {commit.shortHash}
65 </a>
66{/snippet}
67
68{#snippet treeLink(commit: CommitSummary)}
69 <a
70 href={resolve(`${base}/tree/${commit.hash}` as "/")}
71 class="rounded p-1 hover:bg-background-inset"
72 title="Browse repository at this commit"
73 aria-label="Browse repository at this commit"
74 >
75 <FolderCode class="size-4" aria-hidden="true" />
76 </a>
77{/snippet}
78
79{#snippet copyButton(commit: CommitSummary)}
80 <button
81 type="button"
82 class="cursor-pointer rounded p-1 hover:bg-background-inset"
83 title="Copy SHA"
84 aria-label="Copy SHA"
85 onclick={() => void copyFeedback.copy(commit.hash)}
86 >
87 {#if copyFeedback.copied === commit.hash}
88 <CopyCheck class="size-4" aria-hidden="true" />
89 {:else}
90 <Copy class="size-4" aria-hidden="true" />
91 {/if}
92 </button>
93{/snippet}
94
95{#snippet messageCell(commit: CommitSummary)}
96 <div>
97 <a
98 href={resolve(`${base}/commit/${commit.hash}` as "/")}
99 class="text-foreground-default no-underline hover:underline"
100 >
101 {commit.subject}
102 </a>
103 {#if commit.body}
104 <button
105 type="button"
106 class="cursor-pointer rounded bg-background-inset px-1 py-0.5 hover:bg-background-muted"
107 aria-expanded={expanded[commit.hash] === true}
108 aria-label="Toggle commit body"
109 onclick={() => (expanded[commit.hash] = !expanded[commit.hash])}
110 >
111 <Ellipsis class="size-3" aria-hidden="true" />
112 </button>
113 {/if}
114 {#each tagsByCommit[commit.hash] ?? [] as name (name)}
115 <Tag color="gray" class="ml-2 font-mono">{name}</Tag>
116 {/each}
117 </div>
118 {#if commit.body && expanded[commit.hash]}
119 <p class="mt-1 typography-paragraph-regular whitespace-pre-wrap text-foreground-muted">
120 {commit.body}
121 </p>
122 {/if}
123{/snippet}
124
125<section id="commit-table" class="overflow-x-auto rounded bg-background-default px-6 py-4">
126 <h2 class="mb-4 typography-paragraph-regular font-bold">Commits</h2>
127
128 {#if commits.length === 0}
129 <p class="py-6 text-center text-foreground-subtle">No commits at {ref}.</p>
130 {:else}
131 <div class="hidden divide-y divide-border-default md:flex md:flex-col">
132 <div class="grid grid-cols-14 gap-4">
133 <div
134 class="col-span-3 py-2 text-left typography-paragraph-regular font-bold text-foreground-muted"
135 >
136 Author
137 </div>
138 <div
139 class="col-span-3 py-2 text-left typography-paragraph-regular font-bold text-foreground-muted"
140 >
141 Commit
142 </div>
143 <div
144 class="col-span-6 py-2 text-left typography-paragraph-regular font-bold text-foreground-muted"
145 >
146 Message
147 </div>
148 <div
149 class="col-span-2 justify-self-end py-2 text-left typography-paragraph-regular font-bold text-foreground-muted"
150 >
151 Date
152 </div>
153 </div>
154 {#each commits as commit (commit.hash)}
155 <div class="grid grid-cols-14 gap-4 py-3">
156 <div class="col-span-3 align-top">
157 {@render authorCell(commit)}
158 </div>
159 <div class="col-span-3 flex items-start align-top font-mono">
160 {@render shaChip(commit, false)}
161 <!-- TODO: unverified rows indent the actions by a verified-shield
162 width, but no shield renders yet. drop the ml-6 or render the
163 shield once commit verification lands -->
164 <div class="ml-6 inline-flex">
165 {@render copyButton(commit)}
166 {@render treeLink(commit)}
167 </div>
168 </div>
169 <div class="col-span-6 align-top">
170 {@render messageCell(commit)}
171 </div>
172 <div class="col-span-2 justify-self-end align-top text-foreground-muted">
173 <TimeAgo value={commit.when} />
174 </div>
175 </div>
176 {/each}
177 </div>
178
179 <div class="md:hidden">
180 {#each commits as commit, index (commit.hash)}
181 <div
182 class="relative mb-2 p-2 {index < commits.length - 1
183 ? 'border-b border-border-default'
184 : ''}"
185 >
186 <div class="flex items-center justify-between">
187 <div class="flex-1">
188 {@render messageCell(commit)}
189 </div>
190 {@render treeLink(commit)}
191 </div>
192 <div
193 class="mt-2 flex items-center gap-1 typography-paragraph-small text-foreground-muted"
194 >
195 <span class="font-mono">
196 {@render shaChip(commit, true)}
197 </span>
198 <span aria-hidden="true">·</span>
199 {@render authorCell(commit)}
200 {#if commit.when}
201 <span aria-hidden="true">·</span>
202 <TimeAgo value={commit.when} />
203 {/if}
204 </div>
205 </div>
206 {/each}
207 </div>
208 {/if}
209</section>
210
211{#if pageCount > 1}
212 <div class="mt-4 flex justify-center">
213 <Pagination {page} total={pageCount} labels onchange={onPageChange} class="gap-5" />
214 </div>
215{/if}