This repository has no description
0

Configure Feed

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

web/components: add timeline component

just a list of timeline events, but with the vertical line passing
through them. extra sauce

Signed-off-by: oppiliappan <me@oppi.li>

author
oppiliappan
committer
dawn
date (Jul 31, 2026, 10:57 PM +0300) commit 65902b59 parent 4b3b9c14 change-id msnsswrn
+123 -18
+83
web/src/lib/components/timeline/Timeline.stories.svelte
··· 1 + <script module lang="ts"> 2 + import { defineMeta } from "@storybook/addon-svelte-csf"; 3 + import MockAuthProvider from "$lib/components/testing/MockAuthProvider.svelte"; 4 + import Timeline from "./Timeline.svelte"; 5 + import type * as ShTangledFeedGetTimeline from "$lib/api/lexicons/types/sh/tangled/feed/getTimeline"; 6 + 7 + const owner = { did: "did:plc:3p7ejjxnufohsygt5vbyxb2i", handle: "tangled.org" }; 8 + const alice = { did: "did:plc:oky5czdrnfjpqslsw2a5iclo", handle: "alice.tngl.sh" }; 9 + 10 + const repo = { 11 + did: "did:plc:3p7ejjxnufohsygt5vbyxb2i", 12 + slug: "core", 13 + owner, 14 + createdAt: "2025-06-01T10:30:00Z", 15 + description: "The knot server, appview, and everything that makes tangled tick.", 16 + starCount: 42 17 + }; 18 + 19 + const feed = [ 20 + { 21 + event: { 22 + $type: "sh.tangled.feed.getTimeline#repoEvent", 23 + cid: "bafyreib1", 24 + uri: "at://did:plc:3p7ejjxnufohsygt5vbyxb2i/sh.tangled.repo/core", 25 + repo 26 + }, 27 + eventAt: "2025-06-04T09:00:00Z" 28 + }, 29 + { 30 + event: { 31 + $type: "sh.tangled.feed.getTimeline#starEvent", 32 + cid: "bafyreib2", 33 + uri: "at://did:plc:oky5czdrnfjpqslsw2a5iclo/sh.tangled.feed.star/3l", 34 + actor: alice, 35 + repo 36 + }, 37 + eventAt: "2025-06-03T18:20:00Z" 38 + }, 39 + { 40 + event: { 41 + $type: "sh.tangled.feed.getTimeline#followEvent", 42 + cid: "bafyreib3", 43 + uri: "at://did:plc:oky5czdrnfjpqslsw2a5iclo/sh.tangled.graph.follow/3l", 44 + actor: alice, 45 + subject: { 46 + did: "did:plc:3p7ejjxnufohsygt5vbyxb2i", 47 + handle: "joshka.net", 48 + description: "Rust, TUIs, and the occasional git plumbing yak-shave.", 49 + followersCount: 128, 50 + followsCount: 87 51 + } 52 + }, 53 + eventAt: "2025-06-02T12:00:00Z" 54 + }, 55 + { 56 + event: { 57 + $type: "sh.tangled.feed.getTimeline#repoEvent", 58 + cid: "bafyreib4", 59 + uri: "at://did:plc:3p7ejjxnufohsygt5vbyxb2i/sh.tangled.repo/core-fork", 60 + repo: { ...repo, slug: "core-fork" }, 61 + source: repo 62 + }, 63 + eventAt: "2025-06-01T08:00:00Z" 64 + } 65 + ] as unknown as ShTangledFeedGetTimeline.$output["feed"]; 66 + 67 + const { Story } = defineMeta({ 68 + title: "Timeline/Timeline", 69 + component: Timeline, 70 + tags: ["autodocs"], 71 + args: { feed } 72 + }); 73 + </script> 74 + 75 + <Story name="Default"> 76 + {#snippet template(args)} 77 + <MockAuthProvider> 78 + <Timeline {...args} /> 79 + </MockAuthProvider> 80 + {/snippet} 81 + </Story> 82 + 83 + <Story name="Empty" args={{ feed: [], emptyMessage: "Nothing here yet." }} />
+35
web/src/lib/components/timeline/Timeline.svelte
··· 1 + <script lang="ts"> 2 + import EmptyState from "$lib/components/ui/EmptyState.svelte"; 3 + import TimelineRepoEvent from "./TimelineRepoEvent.svelte"; 4 + import TimelineStarEvent from "./TimelineStarEvent.svelte"; 5 + import TimelineFollowEvent from "./TimelineFollowEvent.svelte"; 6 + import type * as ShTangledFeedGetTimeline from "$lib/api/lexicons/types/sh/tangled/feed/getTimeline"; 7 + 8 + let { 9 + feed, 10 + emptyMessage = "Nothing here yet." 11 + }: { feed: ShTangledFeedGetTimeline.$output["feed"]; emptyMessage?: string } = $props(); 12 + </script> 13 + 14 + {#if feed.length > 0} 15 + <div class="flex flex-col gap-4"> 16 + {#each feed as item, i (item.event.uri)} 17 + {@const event = item.event} 18 + <div class="relative"> 19 + <!-- connector bridging the gap up to the previous card --> 20 + {#if i !== 0} 21 + <div class="absolute -top-4 left-8 h-4 w-0.5 bg-border-default" aria-hidden="true"></div> 22 + {/if} 23 + {#if event.$type === "sh.tangled.feed.getTimeline#repoEvent"} 24 + <TimelineRepoEvent {event} eventAt={item.eventAt} /> 25 + {:else if event.$type === "sh.tangled.feed.getTimeline#starEvent"} 26 + <TimelineStarEvent {event} eventAt={item.eventAt} /> 27 + {:else if event.$type === "sh.tangled.feed.getTimeline#followEvent"} 28 + <TimelineFollowEvent {event} eventAt={item.eventAt} /> 29 + {/if} 30 + </div> 31 + {/each} 32 + </div> 33 + {:else} 34 + <EmptyState message={emptyMessage} /> 35 + {/if}
+5 -18
web/src/routes/timeline/+page.svelte
··· 3 3 import GalleryVertical from "$icon/gallery-vertical"; 4 4 import Button from "$lib/components/ui/Button.svelte"; 5 5 import ButtonGroup from "$lib/components/ui/ButtonGroup.svelte"; 6 - import EmptyState from "$lib/components/ui/EmptyState.svelte"; 7 - import TimelineRepoEvent from "$lib/components/timeline/TimelineRepoEvent.svelte"; 8 - import TimelineStarEvent from "$lib/components/timeline/TimelineStarEvent.svelte"; 9 - import TimelineFollowEvent from "$lib/components/timeline/TimelineFollowEvent.svelte"; 6 + import Timeline from "$lib/components/timeline/Timeline.svelte"; 10 7 import type { PageData } from "./$types"; 11 8 12 9 let { data }: { data: PageData } = $props(); ··· 36 33 {/if} 37 34 </div> 38 35 39 - {#each data.feed as item (item.event.uri)} 40 - {@const event = item.event} 41 - {#if event.$type === "sh.tangled.feed.getTimeline#repoEvent"} 42 - <TimelineRepoEvent {event} eventAt={item.eventAt} /> 43 - {:else if event.$type === "sh.tangled.feed.getTimeline#starEvent"} 44 - <TimelineStarEvent {event} eventAt={item.eventAt} /> 45 - {:else if event.$type === "sh.tangled.feed.getTimeline#followEvent"} 46 - <TimelineFollowEvent {event} eventAt={item.eventAt} /> 47 - {/if} 48 - {:else} 49 - <EmptyState 50 - message={data.followingOnly ? "Nobody you follow has been busy." : "Nothing here yet."} 51 - /> 52 - {/each} 36 + <Timeline 37 + feed={data.feed ?? []} 38 + emptyMessage={data.followingOnly ? "Nobody you follow has been busy." : "Nothing here yet."} 39 + /> 53 40 </section>