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