Monorepo for Tangled tangled.org
1

Configure Feed

Select the types of activity you want to include in your feed.

core / web / src / routes / timeline / +page.svelte
4.1 kB 109 lines
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 &middot; 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>