Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128import { jsonGet } from "#lib/api/_request.ts";import { FOLLOW_VIEWER, STAR_VIEWER } from "#lib/api/descriptors.ts";import { enrich, target, viewerUriOf, type Sidecar } from "#lib/api/enrich.ts";import { getTrending } from "#lib/api/profile.ts";import type { Auth } from "#lib/auth.svelte.ts";import { toRepoCard } from "#lib/components/timeline/mappers.ts";import type { BlogPost } from "#lib/components/timeline/types.ts";import { loadBlogFeed } from "#lib/blog.ts";import { createBobbinClient, type BobbinContext } from "#lib/api/client.ts";import type { ResourceUri } from "@atcute/lexicons/syntax";import type * as OrgTangledFeedGetTimeline from "#lib/api/lexicons/types/org/tangled/feed/getTimeline.ts";import type * as OrgTangledRepoDefs from "#lib/api/lexicons/types/org/tangled/repo/defs.ts";import type * as OrgTangledActorDefs from "#lib/api/lexicons/types/org/tangled/actor/defs.ts";
const PAGE_LIMIT = 50;
type Feed = OrgTangledFeedGetTimeline.$output["feed"];
export interface LoadFeedOptions { ctx: BobbinContext; followingOnly: boolean; viewer?: string;}
const withStar = ( repo: OrgTangledRepoDefs.RepoViewBasic, data: Sidecar): OrgTangledRepoDefs.RepoViewBasic => { const star = viewerUriOf(data, repo.did, STAR_VIEWER) as ResourceUri | null | undefined; return { ...repo, viewer: { ...repo.viewer, star: star ?? undefined } };};
const withFollow = ( profile: OrgTangledActorDefs.ProfileViewDetailed, data: Sidecar): OrgTangledActorDefs.ProfileViewDetailed => { const following = viewerUriOf(data, profile.did, FOLLOW_VIEWER) as ResourceUri | null | undefined; return { ...profile, viewer: { ...profile.viewer, following: following ?? undefined } };};
const withViewerState = (feed: Feed, data: Sidecar): Feed => feed.map((item) => { const event = item.event; switch (event.$type) { case "org.tangled.feed.getTimeline#repoEvent": return { ...item, event: { ...event, repo: withStar(event.repo, data), ...(event.source ? { source: withStar(event.source, data) } : {}) } }; case "org.tangled.feed.getTimeline#starEvent": return { ...item, event: { ...event, repo: withStar(event.repo, data) } }; case "org.tangled.feed.getTimeline#followEvent": return { ...item, event: { ...event, subject: withFollow(event.subject, data) } }; default: return item; } });
const fetchFeed = async ({ ctx, followingOnly, viewer }: LoadFeedOptions): Promise<Feed> => { const params = { followingOnly, limit: PAGE_LIMIT }; if (!viewer || followingOnly) { const { feed } = await jsonGet<OrgTangledFeedGetTimeline.$output>( ctx, "org.tangled.feed.getTimeline", params ); return feed; } const { output, data } = await enrich<OrgTangledFeedGetTimeline.$output>(ctx, { xrpc: "org.tangled.feed.getTimeline", params, viewer, enrich: [ target(STAR_VIEWER, ["feed[].event.repo.did", "feed[].event.source.did"]), target(FOLLOW_VIEWER, ["feed[].event.subject.did"]) ] }); return withViewerState(output.feed, data);};
// shared by /timeline and the signed-in homescreen at /export const loadFeed = (options: LoadFeedOptions) => fetchFeed(options);
// the agent only exists in the browser, so this can't run in a server loadexport const loadFollowingFeed = async (auth: Auth, bobbinUrl: string) => { const did = auth.currentDid; if (!did) return []; const agent = auth.agent ?? (await auth.agentFor(did, { relogin: false })); return loadFeed({ ctx: createBobbinClient({ serviceUrl: bobbinUrl, agent }), followingOnly: true });};
export const loadTrendingRepos = async (ctx: BobbinContext) => { try { const trending = await getTrending(ctx); return trending.repos.map(toRepoCard); } catch { return []; }};
const ANNOUNCEMENT_TAG = "announcement";const ANNOUNCEMENT_COUNT = 3;
export const loadAnnouncements = async ( fetch: typeof globalThis.fetch, timeout = 2500): Promise<BlogPost[]> => { const items = await loadBlogFeed(fetch, timeout); if (!items) return []; return items .filter((item) => item.tags?.includes(ANNOUNCEMENT_TAG)) .slice(0, ANNOUNCEMENT_COUNT) .map((item) => ({ url: item.url, title: item.title, subtitle: item.summary, date: item.date_published }));};