Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186<script lang="ts"> import { resolve } from "$app/paths"; import { navigating, page } from "$app/state"; import GalleryVertical from "$icon/gallery-vertical"; import Globe from "$icon/globe"; import UserRoundGroup from "$icon/user-round-group"; import Button from "#lib/components/ui/Button.svelte"; import ErrorAlert from "#lib/components/ui/Error.svelte"; import RefreshCw from "$icon/refresh-cw"; import ButtonGroup from "#lib/components/ui/ButtonGroup.svelte"; import Announcements from "#lib/components/timeline/Announcements.svelte"; import NewsletterCard from "#lib/components/timeline/NewsletterCard.svelte"; import NotificationsPreview from "#lib/components/timeline/NotificationsPreview.svelte"; import Pending from "#lib/components/ui/Pending.svelte"; import RecentsPreview from "#lib/components/timeline/RecentsPreview.svelte"; import Timeline from "#lib/components/timeline/Timeline.svelte"; import TrendingRepos from "#lib/components/timeline/TrendingRepos.svelte"; import Bones from "#lib/components/ui/Bones.svelte"; import timelineTimelineSkeletonFixture from "#lib/bones/timeline-timeline--skeleton-fixture.bones.json"; import type { RepoCardData } from "#lib/components/profile/types.ts"; import OnboardingNudge from "#lib/components/welcome/OnboardingNudge.svelte"; import type { NotificationSummary } from "#lib/components/notifications/types.ts"; import type { BlogPost } from "#lib/components/timeline/types.ts"; import type * as OrgTangledFeedGetTimeline from "#lib/api/lexicons/types/org/tangled/feed/getTimeline.ts"; import { loadRecents } from "#lib/components/timeline/recents.svelte.ts";
let { feed, trending = [], announcements = [], notifications, followingOnly = false, signedIn = false, onboardingNudge = false, basePath, onRefresh }: { feed?: | OrgTangledFeedGetTimeline.$output["feed"] | Promise<OrgTangledFeedGetTimeline.$output["feed"]>; trending?: RepoCardData[] | Promise<RepoCardData[]>; announcements?: BlogPost[] | Promise<BlogPost[]>; notifications?: NotificationSummary[] | Promise<NotificationSummary[]>; followingOnly?: boolean; signedIn?: boolean; onboardingNudge?: boolean; /** route the global/following toggle stays on, e.g. "/" or "/timeline" */ basePath: string; onRefresh?: () => void; } = $props();
// loads after hydration, the server can't see this browser's storage const recents = loadRecents();
const pendingFollowing = $derived( navigating.to && navigating.to.url.pathname === page.url.pathname ? navigating.to.url.searchParams.get("following") === "true" : undefined ); const shownFollowing = $derived(pendingFollowing ?? followingOnly); const switching = $derived( pendingFollowing !== undefined && pendingFollowing !== followingOnly );
const toggleHref = (following: boolean) => resolve((following ? `${basePath}?following=true` : basePath) as "/");
const refreshIfActive = (following: boolean) => (event: MouseEvent) => { if (following !== followingOnly || !onRefresh) return; if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return; event.preventDefault(); onRefresh(); };</script>
{#snippet findPeople()} <Button href={toggleHref(false)} icon={Globe}>Find people on the Global timeline</Button>{/snippet}
<div class="mx-auto flex w-full max-w-[1920px] flex-col md:grid md:grid-cols-[minmax(0,25rem)_minmax(0,37.5rem)_minmax(0,25rem)] md:items-start md:justify-center"> <div class="flex flex-col md:sticky md:top-0 md:col-start-1 md:row-start-2 md:h-screen md:w-full md:max-w-100 md:gap-md md:overflow-y-auto md:p-md" > {#if signedIn && onboardingNudge} <OnboardingNudge class="max-md:px-xs max-md:pt-md" /> {/if} <div class="contents max-md:hidden"> <NotificationsPreview {signedIn} {notifications} /> <RecentsPreview items={recents.items} /> </div> </div>
<div class="order-2 flex flex-col gap-md px-xs pt-2xl md:order-0 md:col-start-2 md:row-start-1 md:mx-auto md:w-full md:max-w-150 md:px-none" > <div class="flex items-center justify-between"> <h1 class="flex items-center gap-xs pl-xs typography-heading-4"> <GalleryVertical class="size-lg" aria-hidden="true" /> <span class="max-[24rem]:sr-only">Timeline</span> </h1> {#if signedIn} <ButtonGroup> <Button href={toggleHref(false)} onclick={refreshIfActive(false)} variant={shownFollowing ? "ghost" : "default"} icon={Globe} > Global </Button> <Button href={toggleHref(true)} onclick={refreshIfActive(true)} variant={shownFollowing ? "default" : "ghost"} icon={UserRoundGroup} > Following </Button> </ButtonGroup> {/if} </div> </div>
<div class="order-3 flex flex-col px-xs pt-md pb-2xl md:order-0 md:col-start-2 md:row-start-2 md:mx-auto md:w-full md:max-w-150 md:px-none" > {#if feed === undefined || switching} <Bones capture={timelineTimelineSkeletonFixture} /> {:else if feed instanceof Promise} <Pending key={feed}> {#snippet skeleton()} <Bones capture={timelineTimelineSkeletonFixture} /> {/snippet} {#snippet failed()} <div class="flex flex-col items-start gap-sm" data-testid="timelineError"> <ErrorAlert class="self-stretch" label={followingOnly ? "Could not load the timeline of people you follow." : "Could not load the timeline."} /> {#if onRefresh} <Button icon={RefreshCw} onclick={onRefresh}>Try again</Button> {/if} </div> {/snippet} {@const resolvedFeed = await feed} <Timeline feed={resolvedFeed} {signedIn} emptyMessage={followingOnly ? "Activity from people you follow shows up here." : "Nothing here yet."} emptyAction={followingOnly ? findPeople : undefined} /> </Pending> {:else} <Timeline {feed} {signedIn} emptyMessage={followingOnly ? "Activity from people you follow shows up here." : "Nothing here yet."} emptyAction={followingOnly ? findPeople : undefined} /> {/if} </div>
<!-- right rail: leads on mobile, where only trending shows --> <div class="order-1 flex flex-col gap-md px-xs pt-md md:sticky md:top-0 md:order-0 md:col-start-3 md:row-start-2 md:ml-auto md:h-screen md:w-full md:max-w-100 md:overflow-y-auto md:p-md" > <TrendingRepos repos={trending} /> {#if signedIn} <NewsletterCard /> {/if}
<div class="hidden md:block"> <Announcements posts={announcements} /> </div> </div></div>