This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

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