This repository has no description
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>