/** * The rich-text grammar, in one place. * * A `richtext` field is stored as a markdown string, and until now three * separate implementations parsed it: the block renderers (to React elements), * the editor overlay (to an HTML string) and the Puck adapter (to HTML, and * back). They had already drifted — the Puck adapter honoured heading depth * while the page hard-coded `

` — so the same document rendered a different * outline depending on which editor you had open. * * This module owns the grammar; the callers own presentation. Anything a * particular surface does differently (HTML-escaping its input, skipping the * paragraph wrapper for single-line values, choosing a heading tag) belongs in * that surface's serializer, not here. * * The grammar is a subset of CommonMark, sized to exactly what the property * panel's editor can produce — anything it can write must render, or the user * sees their own formatting come back as punctuation: * inline `**strong**`, `*em*`, `~~strike~~`, `` `code` ``, `[label](href)`, * hard breaks (`\` or two trailing spaces) * blocks ATX headings, bullet and ordered lists (nested), blockquotes, * fenced code, thematic breaks, paragraphs * Still outside it: tables, setext headings, reference links, footnotes, and * inline images — the editor has no image node and deletes them, so an * `![alt](src)` renders its alt text rather than promising an image the stack * cannot keep. See the rich-text characterisation tests for each case. */ /** A span of text inside a block, already resolved to its mark. */ export type InlineToken = { type: "text"; text: string; at: number; } | { type: "strong"; text: string; at: number; } | { type: "em"; text: string; at: number; } | { type: "strike"; text: string; at: number; } | { type: "code"; text: string; at: number; } | { type: "link"; text: string; href: string; at: number; } /** An explicit line break inside a block — markdown's `\` or two trailing spaces. */ | { type: "break"; at: number; }; /** One item in a list, with an optional nested list under it. */ export type RichTextListItem = { inline: InlineToken[]; children?: RichTextList; }; export type RichTextList = { type: "unordered-list" | "ordered-list"; items: RichTextListItem[]; }; /** * One top-level block. * * `index` is the block's position in the source document. Serializers use it to * key their output; it is stable for a given input. */ export type RichTextBlock = { type: "heading"; index: number; /** Hash count, 1–6, as written. Serializers decide which tag that maps to. */ level: number; inline: InlineToken[]; /** Text that followed the heading inside the same block, if any. */ trailing?: InlineToken[]; } | { type: "unordered-list"; index: number; items: RichTextListItem[]; } | { type: "ordered-list"; index: number; items: RichTextListItem[]; } | { type: "blockquote"; index: number; children: RichTextBlock[]; } | { type: "code"; index: number; code: string; language?: string; } | { type: "rule"; index: number; } | { type: "paragraph"; index: number; inline: InlineToken[]; raw: string; }; /** * Map an authored hash count onto the heading level a body actually renders. * * Every serializer must use this, or the same document gets a different outline * depending on which surface you are looking at — which is exactly the drift * that made the page emit `

` while Puck emitted `

`. */ export declare function resolveRichTextHeadingLevel(level: number): number; /** * Rewrite heading markers so the source agrees with what will be rendered. * * Only `#` moves (to `##`); 2-6 are already renderable. Use it on a value * entering an editor whose schema does not accept `h1`: without it the editor * silently degrades the heading to a paragraph and saves that back, so a `#` * that the page was rendering as `h2` disappears on the next edit. */ export declare function clampMarkdownHeadings(markdown: string): string; /** Undo the serialiser's escaping of a text span. */ export declare function unescapeMarkdownText(text: string): string; /** Split a line of text into marked spans. Unterminated markers stay literal. */ export declare function parseInline(text: string): InlineToken[]; /** * Whitespace clean-up applied to a body before it is parsed. * * This used to also apply `/([.!?])([A-Z])/g → "$1 $2"`, a band-aid for * planners that emitted `requested.Here's`. It fired on correct prose far more * often than on the malformed kind: every `U.S.A.` became `U. S. A.`, and any * URL with a capital after a dot (`/API.Reference`) gained a space and stopped * resolving. It ran on every render of every page, so the damage was permanent * from the reader's point of view even though the stored string was fine. * * If a planner starts emitting run-on sentences again, fix it in the prompt — * not by rewriting everyone's prose at render time. */ export declare function normalizeRichTextBody(input: string): string; /** * Parse a body that has already been through `normalizeRichTextBody`. * * Callers that need to inspect or transform the normalized string first (the * editor overlay checks it for newlines) use this; everyone else wants * `parseRichText`. */ export declare function parseRichTextBlocks(normalizedBody: string): RichTextBlock[]; /** Parse a rich-text string into its blocks. */ export declare function parseRichText(input: string): RichTextBlock[];