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 / CloneDropdown.svelte
4.0 kB 122 lines
1<script lang="ts"> 2 import { page } from "$app/state"; 3 import Check from "$icon/check"; 4 import Copy from "$icon/copy"; 5 import Download from "$icon/download"; 6 import Checkbox from "$lib/components/ui/Checkbox.svelte"; 7 import Dropdown from "$lib/components/ui/Dropdown.svelte"; 8 import { createCopyFeedback } from "$lib/copy.svelte"; 9 import type { RepoInfo } from "./types"; 10 11 interface Props { 12 repo: RepoInfo; 13 ref: string; 14 bobbinUrl: string; 15 } 16 17 let { repo, ref, bobbinUrl }: Props = $props(); 18 19 // ssh for knot1 goes through the main domain, other knots serve their own. the 20 // knot field is usually a bare host but a scheme and port are both allowed 21 const sshHost = $derived( 22 (repo.knot === "knot1.tangled.sh" ? "tangled.org" : repo.knot) 23 .replace(/^[a-z]+:\/\//, "") 24 .split(/[:/]/)[0] 25 ); 26 const origin = $derived(page.url.origin); 27 28 let permalink = $state(false); 29 const copyFeedback = createCopyFeedback(); 30 31 const urls = $derived([ 32 { 33 label: "HTTPS", 34 handle: `${origin}/${repo.ownerHandle}/${repo.name}`, 35 perma: repo.repoDid ? `${origin}/${repo.repoDid}` : null 36 }, 37 { 38 label: "SSH", 39 handle: `git@${sshHost}:${repo.ownerHandle}/${repo.name}`, 40 perma: repo.repoDid ? `git@${sshHost}:${repo.repoDid}` : null 41 } 42 ]); 43 44 const archiveUrl = (format: "tar.gz" | "zip") => { 45 const url = new URL("/xrpc/sh.tangled.repo.archive", `${bobbinUrl}/`); 46 url.searchParams.set("repo", repo.uri); 47 url.searchParams.set("ref", ref); 48 url.searchParams.set("format", format); 49 return url.toString(); 50 }; 51 52</script> 53 54<Dropdown 55 variant="button" 56 buttonVariant="primary" 57 align="right" 58 label="Clone this repository" 59 menuClass="w-96 divide-y-0 p-4" 60> 61 {#snippet trigger()} 62 <Download class="size-4" aria-hidden="true" /> 63 <span class="hidden md:inline">Code</span> 64 {/snippet} 65 66 <div class="flex items-center justify-between"> 67 <h3 class="typography-paragraph-small font-medium text-foreground-default">Clone this repository</h3> 68 {#if repo.repoDid} 69 <Checkbox bind:checked={permalink} class="typography-paragraph-small text-foreground-muted"> 70 Use permalink 71 </Checkbox> 72 {/if} 73 </div> 74 75 {#each urls as entry (entry.label)} 76 {@const value = permalink && entry.perma ? entry.perma : entry.handle} 77 <div class="mt-4"> 78 <span class="mb-1 block typography-paragraph-small font-medium text-foreground-muted" 79 >{entry.label}</span 80 > 81 <div 82 class="flex items-stretch divide-x divide-border-default rounded-sm border border-border-default" 83 > 84 <span 85 class="flex-1 overflow-x-auto bg-background-inset px-3 py-2 font-mono typography-paragraph-small whitespace-nowrap text-foreground-default select-all" 86 >{value}</span 87 > 88 <button 89 type="button" 90 class="cursor-pointer px-3 py-2 text-foreground-subtle hover:text-foreground-default" 91 title="Copy to clipboard" 92 aria-label={`Copy ${entry.label} clone url`} 93 onclick={() => void copyFeedback.copy(entry.label, value)} 94 > 95 {#if copyFeedback.copied === entry.label} 96 <Check class="size-4" aria-hidden="true" /> 97 {:else} 98 <Copy class="size-4" aria-hidden="true" /> 99 {/if} 100 </button> 101 </div> 102 </div> 103 {/each} 104 105 <p class="mt-2 typography-paragraph-small text-foreground-subtle"> 106 For self-hosted knots, clone URLs may differ based on your setup. 107 </p> 108 109 <!-- archives come from bobbin, so these are external links --> 110 <div class="mt-4 flex gap-2"> 111 {#each [{ format: "tar.gz" }, { format: "zip" }] as const as archive (archive.format)} 112 <a 113 href={archiveUrl(archive.format)} 114 rel="external noopener noreferrer" 115 class="flex flex-1 items-center justify-center gap-2 rounded-sm border border-border-default px-2 py-1.5 typography-paragraph-small text-foreground-default no-underline hover:bg-background-subtle hover:no-underline" 116 > 117 <Download class="size-4" aria-hidden="true" /> 118 {archive.format === "zip" ? ".zip" : archive.format} 119 </a> 120 {/each} 121 </div> 122</Dropdown>