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 / FileTree.svelte
3.0 kB 95 lines
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>