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