Monorepo for Tangled
tangled.org
1<script lang="ts">
2 import { resolve } from "$app/paths";
3 import GalleryVertical from "$icon/gallery-vertical";
4 import Button from "$lib/components/ui/Button.svelte";
5 import ButtonGroup from "$lib/components/ui/ButtonGroup.svelte";
6 import EmptyState from "$lib/components/ui/EmptyState.svelte";
7 import User from "$lib/components/ui/User.svelte";
8 import RepoCard from "$lib/components/repo/RepoCard.svelte";
9 import FollowCard from "$lib/components/profile/FollowCard.svelte";
10 import { rkeyFromUri } from "$lib/api/uri";
11 import { relativeTime } from "$lib/format";
12 import type { RepoCardData, PersonData } from "$lib/components/profile/types";
13 import type * as ShTangledRepoDefs from "$lib/api/lexicons/types/sh/tangled/repo/defs";
14 import type * as ShTangledActorDefs from "$lib/api/lexicons/types/sh/tangled/actor/defs";
15 import type { PageData } from "./$types";
16
17 let { data }: { data: PageData } = $props();
18
19 const toRepoCard = (repo: ShTangledRepoDefs.RepoViewBasic): RepoCardData => ({
20 name: repo.slug,
21 repoDid: repo.did,
22 ownerHandle: repo.owner.handle,
23 description: repo.description,
24 createdAt: repo.createdAt,
25 stars: repo.starCount,
26 viewerStarRkey: repo.viewer?.star ? rkeyFromUri(repo.viewer.star) : null
27 });
28
29 const toPerson = (profile: ShTangledActorDefs.ProfileViewDetailed): PersonData => ({
30 did: profile.did,
31 handle: profile.handle,
32 avatar: profile.avatar,
33 followers: profile.followersCount,
34 following: profile.followsCount,
35 viewerFollowRkey: profile.viewer?.following ? rkeyFromUri(profile.viewer.following) : null
36 });
37
38 const repoHref = (repo: ShTangledRepoDefs.RepoViewBasic) =>
39 resolve(`/${repo.owner.handle}/${repo.slug}` as "/");
40</script>
41
42<svelte:head>
43 <title>Timeline · Tangled</title>
44</svelte:head>
45
46<section class="mx-auto flex w-full max-w-screen-md flex-col gap-4 px-6 py-8">
47 <div class="flex items-center justify-between">
48 <h1 class="flex items-center gap-2 text-2xl font-semibold">
49 <GalleryVertical class="size-5 text-foreground-subtle" aria-hidden="true" />
50 Timeline
51 </h1>
52 {#if data.signedIn}
53 <ButtonGroup>
54 <Button
55 href={resolve("/timeline" as "/")}
56 variant={data.followingOnly ? "ghost" : "default"}>Global</Button
57 >
58 <Button
59 href={resolve("/timeline?following=true" as "/")}
60 variant={data.followingOnly ? "default" : "ghost"}>Following</Button
61 >
62 </ButtonGroup>
63 {/if}
64 </div>
65
66 {#each data.feed as item (item.event.uri)}
67 {@const event = item.event}
68 <div class="flex flex-col gap-2">
69 <div class="flex flex-wrap items-center gap-2 text-sm text-foreground-muted">
70 {#if event.$type === "sh.tangled.feed.getTimeline#repoEvent"}
71 <User handle={event.repo.owner.handle} src={event.repo.owner.avatar} size="sm" />
72 {#if event.source}
73 <span>forked</span>
74 <a href={repoHref(event.source)} class="text-foreground-default"
75 >{event.source.owner.handle}/{event.source.slug}</a
76 >
77 <span>to {event.repo.slug}</span>
78 {:else}
79 <span>created {event.repo.slug}</span>
80 {/if}
81 {:else if event.$type === "sh.tangled.feed.getTimeline#starEvent"}
82 <User handle={event.actor.handle} src={event.actor.avatar} size="sm" />
83 <span>starred</span>
84 <a href={repoHref(event.repo)} class="text-foreground-default"
85 >{event.repo.owner.handle}/{event.repo.slug}</a
86 >
87 {:else if event.$type === "sh.tangled.feed.getTimeline#followEvent"}
88 <User handle={event.actor.handle} src={event.actor.avatar} size="sm" />
89 <span>followed</span>
90 <User handle={event.subject.handle} src={event.subject.avatar} size="sm" />
91 {/if}
92 <span class="text-foreground-subtle">{relativeTime(item.eventAt)}</span>
93 </div>
94
95 {#if event.$type === "sh.tangled.feed.getTimeline#followEvent"}
96 <FollowCard person={toPerson(event.subject)} />
97 {:else}
98 <RepoCard repo={toRepoCard(event.repo)} />
99 {/if}
100 </div>
101 {:else}
102 <EmptyState
103 message={data.followingOnly ? "Nobody you follow has been busy." : "Nothing here yet."}
104 />
105 {/each}
106</section>