This repository has no description
1<script lang="ts">
2 import { resolve } from "$app/paths";
3 import File from "$icon/file";
4 import FileSymlink from "$icon/file-symlink";
5 import Folder from "$icon/folder";
6 import FolderInput from "$icon/folder-input";
7 import { splitMessage } from "$lib/api/repo";
8 import TimeAgo from "$lib/components/ui/TimeAgo.svelte";
9 import type { TreeEntrySummary } from "./types";
10 import { encodePathSegments } from "./urls";
11
12 interface Props {
13 ownerHandle: string;
14 repoName: string;
15 ref: string;
16 entries: TreeEntrySummary[];
17 path?: string;
18 withMessage?: boolean;
19 }
20
21 let { ownerHandle, repoName, ref, entries, path = "", withMessage = false }: Props = $props();
22
23 const base = $derived(`/${ownerHandle}/${repoName}`);
24 const encodedRef = $derived(encodeURIComponent(ref));
25
26 const linkFor = (entry: TreeEntrySummary) => {
27 const target = path ? `${path}/${entry.name}` : entry.name;
28 const kind = entry.kind === "directory" ? "tree" : "blob";
29 return `${base}/${kind}/${encodedRef}/${encodePathSegments(target)}`;
30 };
31
32 // lucide puts fill="none" on the path itself, which beats anything inherited
33 // from the svg, so the class has to reach the path
34 const iconClassFor = (entry: TreeEntrySummary) =>
35 entry.kind === "directory" ? "size-4 shrink-0 [&_path]:fill-current" : "size-4 shrink-0";
36
37 const iconFor = (entry: TreeEntrySummary) => {
38 switch (entry.kind) {
39 case "directory":
40 return Folder;
41 case "submodule":
42 return FolderInput;
43 case "symlink":
44 return FileSymlink;
45 default:
46 return File;
47 }
48 };
49</script>
50
51<!-- the divider belongs to whatever column the tree is sat in -->
52<div class="min-w-0">
53 {#each entries as entry (entry.name)}
54 {@const Glyph = iconFor(entry)}
55 <div class={`grid items-center gap-4 py-1 ${withMessage ? "grid-cols-12" : "grid-cols-3"}`}>
56 <a
57 href={resolve(linkFor(entry) as "/")}
58 class={`flex min-w-0 items-center gap-2 text-foreground-default no-underline hover:underline ${
59 withMessage ? "col-span-8 md:col-span-4" : "col-span-2"
60 }`}
61 >
62 <span class="text-foreground-muted">
63 <Glyph class={iconClassFor(entry)} aria-hidden="true" />
64 </span>
65 <span class="truncate">{entry.name}</span>
66 </a>
67
68 {#if withMessage}
69 <div class="hidden min-w-0 md:col-span-6 md:block">
70 {#if entry.lastCommitHash && entry.lastCommitMessage}
71 <a
72 href={resolve(`${base}/commit/${entry.lastCommitHash}` as "/")}
73 class="block truncate text-foreground-subtle no-underline hover:underline"
74 >
75 {splitMessage(entry.lastCommitMessage)[0]}
76 </a>
77 {/if}
78 </div>
79 {/if}
80
81 <div
82 class={`text-right typography-paragraph-small text-foreground-subtle ${
83 withMessage ? "col-span-4 md:col-span-2" : "col-span-1"
84 }`}
85 >
86 {#if entry.lastCommitHash && entry.lastCommitWhen}
87 <a
88 href={resolve(`${base}/commit/${entry.lastCommitHash}` as "/")}
89 class="text-foreground-subtle no-underline hover:underline"
90 title={entry.lastCommitMessage}
91 >
92 <TimeAgo value={entry.lastCommitWhen} />
93 </a>
94 {/if}
95 </div>
96 </div>
97 {/each}
98</div>