Something went wrong. Try again.
Monorepo for Tangled forked from tangled.org/core
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990import { existsSync } from "node:fs";import { getCollection, type CollectionEntry } from "astro:content";
export type Post = CollectionEntry<"posts">;
const BASE = import.meta.env.BASE_URL.replace(/\/$/, "");
export const withBase = (path: string): string => `${BASE}${path}`;
// drafts show up in `astro dev` and nowhere elseexport const getPosts = async (): Promise<Post[]> => { const posts = await getCollection("posts", (post) => import.meta.env.DEV || !post.data.draft); return posts.sort((a, b) => b.data.date.getTime() - a.data.date.getTime());};
// "5 October 2026", set in capitals by .datelineconst dateFormat = new Intl.DateTimeFormat("en-GB", { year: "numeric", month: "long", day: "numeric", timeZone: "UTC",});
export const formatDate = (date: Date): string => dateFormat.format(date);
export const profileUrl = (handle: string): string => `https://tangled.org/${handle}`;
// fetched once from bluesky's public api into public/avatars: the build is// offline and tangled's avatar service needs signed urlsexport const avatarSrc = (handle: string): string | undefined => existsSync(`public/avatars/${handle}.jpg`) ? withBase(`/avatars/${handle}.jpg`) : undefined;
export const byline = ({ data }: Post): string => data.authors.map((author) => author.name).join(", ");
// pages build to <slug>/index.html, and cloudflare redirects /<slug> to /<slug>/, so// links and canonical urls take the slash (the feed keeps its old ids, without it)export const postPath = ({ data }: Post): string => withBase(`/${data.slug}/`);
// a post as plain markdown, for /<slug>.md and llms-full.txt: the source body, with// root-relative links made absolute so they still resolve once the text is lifted// out of the site. an .mdx post's imports and component lines (its diagrams) have// no markdown form, so they're left out; an inline component that only dresses up// some text (<Ripple text="…" />) is left as that textconst MDX_ONLY = /^(import|export) .*$|^<[A-Z][\w.]*\b[^>]*\/>$/gm;const MDX_TEXT = /<[A-Z][\w.]*\s+text="([^"]*)"\s*\/>/g;export const postMarkdown = (post: Post, site: URL): string => { const { title, subtitle, date } = post.data; const body = (post.body ?? "") .replace(MDX_ONLY, "") .replace(MDX_TEXT, "$1") .replace(/\n{3,}/g, "\n\n") .replace(/\]\(\//g, `](${new URL("/", site).href}`); const lede = subtitle ? `\n> ${subtitle}\n` : ""; return `# ${title}\n${lede}\n${byline(post)} · ${formatDate(date)} · ${new URL(postPath(post), site).href}\n\n${body.trim()}\n`;};
// the standard.site records `blog publish` (cmd/blog, blog/standard.go) keeps on// the tangled.org pds; the pages link to them for clients that verify the siteconst REPO_DID = "did:plc:wshs7t2adsemcrrd4snkeqli";export const PUBLICATION_URI = `at://${REPO_DID}/site.standard.publication/3myiuoozs2222`;export const documentUri = ({ data }: Post): string | undefined => data.rkey && `at://${REPO_DID}/site.standard.document/${data.rkey}`;
const publisher = { "@type": "Organization", name: "Tangled", url: "https://tangled.org", logo: "https://tangled.org/blog/logos/dolly.png",};
export const postAuthors = ({ data }: Post) => data.authors.map((author) => ({ name: author.name, url: profileUrl(author.handle) }));
// schema.org BlogPosting, for the post page's json-ld and the index's blogPost listexport const postLd = (post: Post, site: URL) => { const { title, subtitle, date, image } = post.data; const url = new URL(postPath(post), site).href; return { "@type": "BlogPosting", headline: title, description: subtitle || undefined, datePublished: date.toISOString(), url, mainEntityOfPage: url, image, author: postAuthors(post).map((author) => ({ "@type": "Person", ...author })), publisher, };};