Skip to content

Markdown Format

RelationalText implements CommonMark 0.31 (652/652 spec tests passing) with GFM extensions. Features are stored under the org.commonmark.facet namespace for CommonMark elements and org.gfm.facet for GFM-specific extensions.

Functions

ts
import { from, to } from 'relational-text/registry'
import { normalizeMarkdown } from 'relational-text/markdown'

from('markdown', input: string): Document

Parse a Markdown string into a Document.

ts
const doc = from('markdown', '**Hello**, _world_!\n\n- item one\n- item two')

Supports:

  • CommonMark 0.31 (full spec compliance)
  • GFM strikethrough (~~text~~)
  • GFM pipe tables
  • Footnotes (via markdown-it-footnote)
  • Definition lists (via markdown-it-deflist)

to('markdown', doc: Document | DocumentJSON): string

Render a Document to a Markdown string.

ts
const md = to('markdown', doc)
  • Automatically applies any registered lenses targeting org.commonmark.facet via lensGraph.autoTransform()
  • Documents from other formats (HTML, Mastodon, Quill, ProseMirror) convert automatically

normalizeMarkdown(input: string): string

Normalize insignificant whitespace in a Markdown string without re-parsing.

Removes:

  • CRLF line endings (normalized to LF)
  • Trailing whitespace on every line
  • Leading blank lines
  • Trailing blank lines (keeps exactly one trailing newline)
  • Runs of 3+ consecutive newlines (collapsed to one blank line)

Useful for round-trip testing: normalizeMarkdown(x) === normalizeMarkdown(to('markdown', from('markdown', x))).

ensureMarkdownLexicon(): void

Explicitly register the CommonMark lexicon (org.commonmark.facet#* types) and the GFM lexicon (org.gfm.facet#* types). Called automatically by from('markdown', ...) / to('markdown', ...) on first use. Safe to call multiple times — subsequent calls are no-ops.

Feature Mapping

Inline Marks (CommonMark)

SyntaxFeature nameNamespace
**bold** / __bold__strongorg.commonmark.facet
_italic_ / *italic*emphasisorg.commonmark.facet
`code`code-spanorg.commonmark.facet
[text](url)link (attrs: uri, title)org.commonmark.facet
![alt](src)image (attrs: src, alt, title)org.commonmark.facet
\n (hard break)line-breakorg.commonmark.facet
~~text~~strikethroughorg.gfm.facet

Block Elements (CommonMark)

SyntaxFeature nameattrs
Paragraphparagraph
# Headingheading{ level: 1 }
## Headingheading{ level: 2 }
Bullet list itemcontainer: ul, block: list-item-text
Ordered list itemcontainer: ol, block: list-item-text
> quotecontainer: blockquote
``` fenced codecode-block{ language?: string }
Indented codecode-block
---horizontal-rule

Block Elements (GFM)

SyntaxFeature nameNamespaceattrs
| col | col | pipe tabletableorg.gfm.facet{ headers: string[], rows: string[][] }

Examples

Import

ts
import { from } from 'relational-text/registry'

const doc = from('markdown', `
# Hello

A paragraph with **bold** and a [link](https://example.com).

- Item one
- Item two
`)

console.log(doc.text)
// "\uFFFCHello\n..."

Export

ts
import { from, to } from 'relational-text/registry'

const doc = from('html', '<h2>Hello</h2><p><strong>bold</strong> text</p>')
const md = to('markdown', doc)
// '## Hello\n\n**bold** text\n\n'

Round-Trip

ts
import { from, to } from 'relational-text/registry'
import { normalizeMarkdown } from 'relational-text/markdown'

const input = '**Hello**, _world_!\n\n- a\n- b\n'
const normalized = normalizeMarkdown(input)
const roundTripped = normalizeMarkdown(to('markdown', from('markdown', input)))

console.log(normalized === roundTripped)  // true

Hub Lenses

ensureMarkdownLexicon() registers the following hub lenses on demand. RelationalText uses org.relationaltext.facet as its hub format — all format-to-format paths go through RT, not directly between source formats.

LensautoApply
org.gfm.facetorg.relationaltext.facettrue
org.relationaltext.facetorg.commonmark.facettrue

Because all 37 registered formats have a path to the RT hub, to('markdown', ...) can accept documents from any of them directly — the lens graph resolves multi-hop paths automatically.