Something went wrong. Try again.
Monorepo for Tangled forked from tangled.org/core
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102import { loadBlogFeed, type BlogFeedItem } from "#lib/blog.ts";import type { BobbinContext } from "#lib/api/client.ts";import { resolveMiniDoc } from "#lib/api/identity.ts";import type { Fetch } from "#lib/fetch.ts";
interface Image { src: string; alt: string;}
interface Base { id: string; href: string; at: string;}
interface Author { name: string; handle?: string; did?: string;}
export interface BlogUpdate extends Base { kind: "blog"; title: string; authors: Author[]; summary?: string; image?: Image;}
export interface ChangelogUpdate extends Base { kind: "changelog"; text: string;}
export interface AnnouncementUpdate extends Base { kind: "announcement"; title: string; /** Paragraphs; `backticks` set a word in mono. */ body: string[];}
export type Update = BlogUpdate | ChangelogUpdate | AnnouncementUpdate;
export const KIND_LABEL: Record<Exclude<Update["kind"], "blog">, string> = { changelog: "Changelog", announcement: "Announcement"};
const DATE = new Intl.DateTimeFormat("en-US", { month: "short", day: "2-digit", year: "numeric", timeZone: "UTC"});
/** Jul 13, 2026. */export const date = (at: string) => DATE.format(new Date(at));
/** How many updates the section shows. */const LIVE_COUNT = 10;
const PROFILE_ORIGIN = "https://tangled.org";
const handleOf = (profileUrl: string | undefined) => { const url = URL.parse(profileUrl ?? ""); return url?.origin === PROFILE_ORIGIN ? url.pathname.slice(1) || undefined : undefined;};
export const resolveAuthorDids = (ctx: BobbinContext, updates: Update[]): void => { for (const update of updates) { if (update.kind !== "blog") continue; for (const author of update.authors) { if (!author.handle) continue; void resolveMiniDoc(ctx, author.handle).then( (doc) => (author.did = doc.did), () => {} ); } }};
export const fromBlogFeed = (items: BlogFeedItem[], count = LIVE_COUNT): Update[] => items.slice(0, count).map((item) => ({ kind: "blog", id: item.url, href: item.url, at: item.date_published, title: item.title, authors: (item.authors ?? []).map((author) => ({ name: author.name, handle: handleOf(author.url) })), summary: item.summary || undefined, image: item.image ? { src: item.image, alt: "" } : undefined }));
export async function loadLiveUpdates(fetch: Fetch, timeout = 2500): Promise<Update[] | null> { const items = await loadBlogFeed(fetch, timeout); return items && fromBlogFeed(items);}