~james/core

core/web/src/lib/components/repo/RepoHeader.svelte -rw-r--r-- 4.8 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 16 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
<script lang="ts">
	import InlineMarkdown from "#lib/components/ui/InlineMarkdown.svelte";
	import { resolve } from "$app/paths";
	import GitFork from "$icon/git-fork";
	import Globe from "$icon/globe";
	import Button from "#lib/components/ui/Button.svelte";
	import ButtonGroup from "#lib/components/ui/ButtonGroup.svelte";
	import OwnerTitle from "#lib/components/repo/OwnerTitle.svelte";
	import KnotUpgradeTag from "#lib/components/repo/KnotUpgradeTag.svelte";
	import TopicList from "#lib/components/repo/TopicList.svelte";
	import Pending from "#lib/components/ui/Pending.svelte";
	import Bones from "#lib/components/ui/Bones.svelte";
	import bonesLoadingprimitivesCount from "#lib/bones/bones-loadingprimitives--count.bones.json";
	import StarButton from "#lib/components/repo/StarButton.svelte";
	import type { KnotInfo, RepoCounts, RepoInfo, RepoSource } from "#lib/components/repo/types.ts";
	import SubscribeButton from "#lib/components/repo/SubscribeButton.svelte";
	import { formatCount } from "#lib/format.ts";
	import { safeHref } from "#lib/safeHref.ts";
	interface Props {
		repo: RepoInfo;
		counts: RepoCounts | Promise<RepoCounts>;
		viewerStarRkey?: string | null | Promise<string | null>;
		viewerSubscriptionRkey?: string | null | Promise<string | null>;
		source?: RepoSource | null | Promise<RepoSource | null>;
		knotInfo?: KnotInfo | Promise<KnotInfo>;
	}

	let { repo, counts, viewerStarRkey, viewerSubscriptionRkey, source, knotInfo }: Props =
		$props();

	const base = $derived(`/${repo.ownerHandle}/${repo.name}`);
	const website = $derived(safeHref(repo.website));
	const trimScheme = (url: string) => url.replace(/^https?:\/\//, "").replace(/\/$/, "");
	const topics = $derived([...new Set(repo.topics ?? [])]);
</script>

{#snippet repoActions(className: string, itemClass: string | undefined)}
	<div class={className}>
		<StarButton
			subject={{
				kind: "repo",
				did: repo.repoDid ?? "",
				ownerHandle: repo.ownerHandle,
				name: repo.name
			}}
			initialCount={counts}
			initialRkey={viewerStarRkey}
			class={itemClass ?? "flex-1"}
		/>
		<ButtonGroup class={itemClass ?? "flex-1"}>
			<Button
				href={resolve(`${base}/fork` as "/")}
				variant="default"
				icon={GitFork}
				disabled={!repo.git}
				title={repo.git ? undefined : "This repository doesn't support git"}
				class="flex-1 px-md!"
			>
				Fork
			</Button>
			<Button href={resolve(`${base}/forks` as "/")} variant="ghost" title="Forked by">
				{#if typeof counts === "object" && counts !== null && "then" in counts}
					<Pending>
						{#snippet skeleton()}
							<Bones
								capture={bonesLoadingprimitivesCount}
								class="inline-block w-8 align-middle"
							/>
						{/snippet}
						{formatCount((await counts).forks)}
					</Pending>
				{:else}
					{formatCount(counts.forks)}
				{/if}
			</Button>
		</ButtonGroup>
		<SubscribeButton
			repoDid={repo.repoDid ?? ""}
			initialRkey={viewerSubscriptionRkey}
			class={itemClass}
		/>
	</div>
{/snippet}

<section class="mb-xs px-md py-xs">
	<div class="mb-xs flex flex-col items-start justify-between gap-md sm:flex-row sm:items-center">
		<div class="flex w-full min-w-0 flex-col gap-xs sm:flex-1">
			<OwnerTitle
				ownerDid={repo.ownerDid}
				ownerHandle={repo.ownerHandle}
				name={repo.name}
				href={resolve(`/${repo.ownerHandle}/${repo.name}` as "/")}
			>
				{#await knotInfo then info}
					{#if info?.needsUpgrade}
						<KnotUpgradeTag reason={info.needsUpgrade} knot={repo.knot} />
					{/if}
				{/await}
			</OwnerTitle>
		</div>

		{@render repoActions("hidden shrink-0 items-start gap-xs sm:flex", undefined)}
	</div>

	<div class="flex flex-col gap-xs">
		<div class="flex flex-wrap items-center gap-x-md gap-y-2xs text-foreground-muted">
			{#if repo.description}
				<span data-testid="repoDescription" class="wrap-anywhere"
					><InlineMarkdown text={repo.description} /></span
				>
			{/if}

			<Pending>
				{@const resolvedSource = await (source ?? repo.source)}
				{#if resolvedSource}
					<span class="flex items-center gap-2xs">
						<GitFork class="size-3.5 shrink-0" aria-hidden="true" />
						<span data-testid="forkedFromPrefix">forked from</span>
						<a
							href={resolve(
								`/${resolvedSource.ownerHandle}/${resolvedSource.name}` as "/"
							)}
							class="min-w-0 wrap-anywhere underline"
						>
							{resolvedSource.ownerHandle}/{resolvedSource.name}
						</a>
					</span>
				{/if}
			</Pending>

			{#if website}
				<span class="flex items-center gap-2xs">
					<Globe class="size-3.5 shrink-0" aria-hidden="true" />
					<a
						href={website}
						rel="external ugc nofollow noopener noreferrer"
						class="min-w-0 wrap-anywhere">{trimScheme(website)}</a
					>
				</span>
			{/if}
		</div>

		<TopicList {topics} />
	</div>

	{@render repoActions("mt-xs flex gap-xs sm:hidden", "flex-auto")}
</section>