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.1 kB 98 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 { 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>