diff --git a/web/src/lib/api/profile.ts b/web/src/lib/api/profile.ts index 215e4c0fa..e918caf4f 100644 --- a/web/src/lib/api/profile.ts +++ b/web/src/lib/api/profile.ts @@ -6,6 +6,7 @@ import type { OAuthUserAgent } from "@atcute/oauth-browser-client"; import { createClient } from "$lib/auth/agent"; import { FOLLOW_VIEWER, STAR_VIEWER } from "./descriptors"; import { enrich, target } from "./enrich"; +import { jsonGet } from "./_request"; import type { BobbinContext, XrpcRequestInit } from "./client"; import type * as ShTangledActorGetTrending from "./lexicons/types/sh/tangled/actor/getTrending"; import type { ProfileRecord } from "./records"; @@ -15,6 +16,9 @@ const PROFILE_COLLECTION = "sh.tangled.actor.profile" as Nsid; const AVATAR_TYPES: Record = { "image/png": true, "image/jpeg": true }; const MAX_AVATAR_BYTES = 1_000_000; +export const getTrending = (ctx: BobbinContext, init?: XrpcRequestInit) => + jsonGet(ctx, "sh.tangled.actor.getTrending", {}, init); + export const getTrendingWithViewerState = ( ctx: BobbinContext, viewer: string, diff --git a/web/src/lib/components/timeline/PreviewCard.svelte b/web/src/lib/components/timeline/PreviewCard.svelte new file mode 100644 index 000000000..cd48bd74b --- /dev/null +++ b/web/src/lib/components/timeline/PreviewCard.svelte @@ -0,0 +1,47 @@ + + +{#snippet heading()} +

+

+{/snippet} + + +
+ {#if href} + + {@render heading()} + + {:else} +
{@render heading()}
+ {/if} + {@render action?.()} +
+ {@render children()} +
diff --git a/web/src/lib/components/timeline/TrendingRepos.stories.svelte b/web/src/lib/components/timeline/TrendingRepos.stories.svelte new file mode 100644 index 000000000..e7e9721d9 --- /dev/null +++ b/web/src/lib/components/timeline/TrendingRepos.stories.svelte @@ -0,0 +1,42 @@ + + + + + diff --git a/web/src/lib/components/timeline/TrendingRepos.svelte b/web/src/lib/components/timeline/TrendingRepos.svelte new file mode 100644 index 000000000..4baf56a25 --- /dev/null +++ b/web/src/lib/components/timeline/TrendingRepos.svelte @@ -0,0 +1,50 @@ + + + + {#if repos.length > 0} + + {:else} +

+ No trending repositories this week +

+ {/if} +
diff --git a/web/src/routes/timeline/+page.svelte b/web/src/routes/timeline/+page.svelte index c483d8356..0b978193c 100644 --- a/web/src/routes/timeline/+page.svelte +++ b/web/src/routes/timeline/+page.svelte @@ -4,6 +4,7 @@ import Button from "$lib/components/ui/Button.svelte"; import ButtonGroup from "$lib/components/ui/ButtonGroup.svelte"; import Timeline from "$lib/components/timeline/Timeline.svelte"; + import TrendingRepos from "$lib/components/timeline/TrendingRepos.svelte"; import type { PageData } from "./$types"; let { data }: { data: PageData } = $props(); @@ -13,28 +14,43 @@ Timeline · Tangled -
-
-

-

- {#if data.signedIn} - - - - - {/if} +
+
+
+
+

+

+ {#if data.signedIn} + + + + + {/if} +
+ + +
- -
+ +
+ +
+ diff --git a/web/src/routes/timeline/+page.ts b/web/src/routes/timeline/+page.ts index 2cdea02d3..ef219e90d 100644 --- a/web/src/routes/timeline/+page.ts +++ b/web/src/routes/timeline/+page.ts @@ -1,6 +1,8 @@ import { createBobbinClient } from "$lib/api/client"; import { jsonGet } from "$lib/api/_request"; import { toHttpError } from "$lib/api/load"; +import { getTrending } from "$lib/api/profile"; +import { toRepoCard } from "$lib/components/timeline/mappers"; import type * as ShTangledFeedGetTimeline from "$lib/api/lexicons/types/sh/tangled/feed/getTimeline"; import type { PageLoad } from "./$types"; @@ -8,18 +10,31 @@ const PAGE_LIMIT = 50; export const load: PageLoad = async (event) => { const parent = await event.parent(); - const ctx = createBobbinClient({ serviceUrl: parent.publicConfig.bobbinUrl, fetch: event.fetch }); + const ctx = createBobbinClient({ + serviceUrl: parent.publicConfig.bobbinUrl, + fetch: event.fetch + }); const viewer = parent.auth?.did; - const followingOnly = event.url.searchParams.get("following") === "true" && viewer !== undefined; + const followingOnly = + event.url.searchParams.get("following") === "true" && viewer !== undefined; try { - const { feed } = await jsonGet( - ctx, - "sh.tangled.feed.getTimeline", - { viewer, followingOnly, limit: PAGE_LIMIT } - ); - return { feed, followingOnly, signedIn: viewer !== undefined }; + const [{ feed }, trending] = await Promise.all([ + jsonGet(ctx, "sh.tangled.feed.getTimeline", { + viewer, + followingOnly, + limit: PAGE_LIMIT + }), + // the rail is decoration: an empty one beats failing the whole timeline + getTrending(ctx).catch(() => ({ people: [], repos: [] })) + ]); + return { + feed, + trending: trending.repos.map(toRepoCard), + followingOnly, + signedIn: viewer !== undefined + }; } catch (cause) { toHttpError(cause, "Could not load timeline"); }