/** The posts folder: `/content/blog`. */ export declare function blogContentDir(): string; /** Whether a string can name a post (letters, digits, `-`, `_`; no dots or slashes). */ export declare function isPostSlug(slug: string): boolean; /** * `key: value` lines between `---` fences, quotes optional. A value may * contain colons; only the first one separates the key. */ export declare function parseFrontmatter(markdown: string): { data: Record, body: string }; /** * Whether frontmatter describes a published post: a title and a date, and no * `draft: true`. Files without both (a STRATEGY.md, notes) are not posts. */ export declare function isPublished(data: Record): boolean; /** Whole minutes to read a markdown body at 230 words a minute, at least one. Code blocks are skipped. */ export declare function readingMinutes(markdown: string): number; /** * The authors a post's frontmatter names: `authors: chris, pawel` (brackets * and quotes allowed), each with its `author..*` fallbacks, or the * single-author keys. */ export declare function authorsOf(data: Record): AuthorRef[]; /** Featured first, then newest first. */ export declare function sortPosts>(posts: T[]): T[]; /** Every published post, featured first and then newest first, without bodies. */ export declare function loadPosts(options?: BlogDirOptions): PostMeta[]; /** One published post with its body, or null for a draft, an unsafe slug or no such file. */ export declare function loadPost(slug: unknown, options?: BlogDirOptions): Post | null; /** * A post body as HTML: Bun's markdown parser, then `renderPostHtml`, which * turns a lone image into a captioned figure, consecutive images into a grid * and a bare video link into an embed. Pair it with `articleHead()` for the * figure and grid styles. */ export declare function renderPostBody(markdown: string, options?: { publicDir?: string }): string; /** "October 11, 2026". A post date is a calendar day, so it is read as UTC. */ export declare function formatPostDate(date: string, locale?: string): string; /** "CB" for "Chris Breuer", first and last word. */ export declare function initialsOf(name: string): string; /** "A", "A and B", "A, B and C". */ export declare function joinNames(names: string[]): string; /** * Resolves bylines through the app's own profiles. * * Each author is looked up by username; a profile's name, initials, picture * and link win, and the post's own frontmatter fills whatever it does not * have. A `bio` in the frontmatter wins over the profile's, so a post can say * what it means to about its writer. A lookup that throws, or answers null, * leaves the frontmatter standing, without a link. * * Create it once at module scope so its cache outlives a render: * * ```ts * export const resolveAuthors = createAuthorResolver({ * lookup: async (username) => { * const res = await fetch(`${api}/api/u/${username}`) * const { profile } = res.ok ? await res.json() : {} * return profile ? { ...profile, url: `/${profile.username}` } : null * }, * }) * ``` */ export declare function createAuthorResolver(options?: AuthorResolverOptions): AuthorResolver; /** * The byline as a template draws it: each author with the words before their * name (`''`, `', '` or `' and '`) and an inline style that paints their * picture as a background. Lay that over their initials and a picture that * fails to load leaves the initials showing rather than an empty circle. */ export declare function bylineOf(authors: PostAuthor[]): Array; export declare interface PostMeta { slug: string title: string date: string description: string author: string authors: AuthorRef[] image: string imageAlt: string featured: boolean minutes: number frontmatter: Record } export declare interface Post extends PostMeta { body: string } /** An author as a post's frontmatter names them. */ export declare interface AuthorRef { username: string name: string avatar: string bio: string } /** What an app knows about an author, from its `AuthorLookup`. */ export declare interface AuthorProfile { name?: string initials?: string avatar?: string bio?: string url?: string } /** An author as a byline shows them. */ export declare interface PostAuthor { name: string initials: string avatar: string bio: string url: string } export declare interface BlogDirOptions { dir?: string } export declare interface AuthorResolverOptions { lookup?: AuthorLookup ttlMs?: number fallbackName?: string } export declare interface AuthorResolver { (post: Pick): Promise one: (ref: AuthorRef) => Promise clear: () => void } /** Finds an author by username: their profile, or null when there is none to show. */ export type AuthorLookup = (username: string) => Promise | AuthorProfile | null | undefined;