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 106 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 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 &middot; 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>