Rich text / foldkit-richtext-markdown

Edit the Markdown itself

A source session lets a person edit the document as Markdown and switch back. It is a value, not a store: the application keeps it in its Model while source mode is on, and the rich document stands still meanwhile, so only one representation is ever being edited.

ts
import { closeSource, openSource } from 'foldkit-richtext-markdown'

// Entering source mode: the draft starts as the printed document.
const session = openSource(model.document)

// Each keystroke in the source editor replaces the draft.
const edited = { ...session, draft: '# Title\n\nNew text\n' }

// Leaving it: a pure read, so it serves as a preview too.
const closed = closeSource(edited, model.document, { mint: () => crypto.randomUUID() })

openSource prints the document and keeps what the printer could not show — a mark Markdown has no syntax for — as unprintable. closeSource compares the draft with what was printed: unedited, it returns the caller's own document untouched (changed: false), identities and unprintable marks included, so switching modes without typing loses nothing. Edited, it parses the draft into a new document and reports, in diagnostics, the unprintable content the edit loses followed by what parsing refused. Nothing is committed until the application puts closed.document in its Model, so it can warn first. SourceSession is a schema, so the session fits in a Model as it is.

closed.style is how the edited draft spelled its constructs, over the style the session opened with; its blocks are the edited draft's alone, since the blocks it names are new. Keep it beside the document and pass it to the next openSource(document, { style }), and a writer who typed _hello_ and * item sees them spelled that way when they come back to source mode, rather than in the canonical *hello* and - item.

The caret comes along both ways. openSource(document, { selection }) sets the session's caret, an offset in the draft, to where the selection's focus printed; a node selection, or none, starts it at 0. Replace caret as it moves in the source editor, and closed.selection is that place in the document closeSource returns: in the parsed draft when it was edited, and in the caller's own document when it was not. It is null when the draft holds no text, or when an unedited draft does not have as many text blocks as the caller's document. Keep the old selection then.