Rich text / foldkit-richtext-markdown

Parse Markdown

ts
import { parse } from 'foldkit-richtext-markdown'

let n = 0
const { document, diagnostics } = parse('> quoted\n', { mint: () => `id-${++n}` })

mint supplies every identity, as it does everywhere else in the library, and the document is decoded through the codec, so a bad construction fails loudly instead of reaching an editor.

It reads CommonMark, plus GFM's task lists, strikethrough, and tables, and with footnotes: true its footnotes, as foldkit-richtext's FootnoteRef and FootnoteDefinition for a kit that declares them; without it a footnote is reported and skipped. It reports instead of guessing: raw HTML, a link definition (a reference to one keeps its text, unlinked), a hard line break (which becomes its own paragraph, because a block holds no line break), and an image inside a paragraph (Image is a block, so a paragraph holding only one is hoisted to it). Markdown is as untrusted as pasted HTML, so a link's or an image's URL passes the same safeUrl policy HTML import uses: a refused link keeps its text unlinked, a refused image is dropped, and each is reported as UnsafeUrl.

print(parse(markdown)) returns the same Markdown and parse(print(document)) a document that prints the same, which is how the two directions are tested against each other.

The same Markdown means the canonical spellings: * for emphasis, - for a bullet, backtick fences, # headings. Text written as _hello_, * item, or a heading underlined with === is kept that way by the style parse also returns — which spelling each construct took, first occurrence each, and never part of the document — handed back to print:

ts
const parsed = parse('_hello_\n\n* item\n', { mint: () => `id-${++n}` })
print(parsed.document, { style: parsed.style }).markdown // '_hello_\n\n* item\n'

Blocks also keep their own spelling. style.blocks maps the id parse gave each list, heading, fence, and rule to how that one was written, so a text with a - list and a * list, or a setext title over ## sections, prints back as it was. A block the text did not have, such as a list added in the rich editor, takes its construct's spelling. A list's spacing is kept the same way: a loose list, one with a blank line between its items or inside one, prints with a blank line between its items and their blocks, and a tight one with none, so a nested list sits right under its item's text. Without a style, lists print tight. Emphasis stays per construct, because runs split and merge as they are edited and their ids do not last.

A spelling that would change what the text means somewhere — _ does not open emphasis inside a word — is not used: print reads its styled text back, and when that reads as a different document than the canonical text, the canonical text is what it returns.