Skip to content
QuireKit MSFE
Developer docs
v0.0.0 · Experimental

Markdown plugins

Connect new syntax to incremental parsing, semantic queries and source presentation.

Distinguish three plugin responsibilities

CapabilityMSFE entryMutates source?
Recognize syntaxLezer extensions through addMarkdownExtensions()No
Render recognized syntaxaddSource(), addNodeView(), addProjectionLayers()Rendering itself does not
Normalize typed or pasted inputInputRule, PasteRuleYes, through transactions and history

markdown-it plugins consume a token stream, rule chain and HTML renderer. MSFE uses Lezer's incremental tree. A plugin intended for md.use(plugin) cannot be installed directly as an MSFE extension. A third-party renderer may run inside an isolated non-editable preview, but source ranges, parsing and writes still need integration.

A working parser plugin

Optional document properties

Frontmatter recognizes source only. Explicitly install DocumentProperties to display a leading YAML header as a property table. MD Editor does not install it by default. Vue consumers can reuse the public form:

vue

MD Editor includes Frontmatter; direct MSFE Core consumers must install it alongside DocumentProperties. Omitting render uses a native property display and a source entry point.

setDocumentPropertiesEnabled(editor, false) restores the literal Frontmatter; true enables the table again. It preserves the instance, undo history, body and selection. Read the applied preference with documentPropertiesEnabled(editor.state). The example page uses this public API for its Document properties switch.

defaultCollapsed: true initially shows only the heading and count, keeping the document body prominent. Omit it or use false to start expanded. Both the Vue panel and native renderer honor this setting. Manual choices survive edits, mode changes, projection toggles and viewport remounts within the same document. replaceDocument reapplies the default for the next document. enabled: false shows literal YAML instead of a collapsed property panel. Hosts can persist a preference across sessions and pass it as the next default.

Read the current state with documentPropertiesCollapsed(editor.state). Call setDocumentPropertiesCollapsed(editor, true) to collapse or pass false to expand. Custom renderers use view.collapsed and view.setCollapsed(). Readonly mode still allows folding without changing edit permissions.

MdDocumentProperties initially previews six fields; long text and tag lists expand individually. Click a value to edit it with confirmation and cancellation on the same row. Enter saves a single-line field, Escape cancels, and ⌘/Ctrl + Enter saves multiline fields. Collapsing retains unsaved drafts; expansion state is never written to Markdown.

Strings, numbers, booleans and string lists are editable through source transactions and undo. Other values remain literal. Invalid YAML, duplicate keys, aliases, anchors, explicit tags, flow or indented root maps, explicit or multiline keys, TOML, unclosed headers and headers over 20,000 UTF-16 units fall back to source. New property names are always encoded as string keys. Edits replace only the target range, retaining other fields, comments and blank lines. Obsolete forms are rejected after a document or header change. Source mode always shows the original header; readonly editors expose no modification controls.

A custom badge

The example recognizes :badge[label]:, retains delimiters and adds theme color. Unclosed, oversized or nested-bracket forms remain ordinary source. The same spelling inside a code span is not a badge.

Preparing the editor…

1. Contribute syntax

Depend on @quirekit/lezer-markdown and return a MarkdownExtension. plugins/syntax.ts registers ExampleBadge through defineNodes. Its inline parser checks the exact prefix at a colon and examines at most 82 following units for the closing form.

context.elt() uses canonical source coordinates, and addElement() returns the consumed endpoint. Return -1 when not applicable. before / after controls parser ordering, not visual priority. Nesting is intentionally unsupported here. More complex syntax needs explicit block/inline rules and fallback behavior.

For simple, non-nested, single-line literal delimiters, use defineSource({ syntax }) as in the status node. Avoid maintaining a second whole-document parser for a simple token.

2. Add semantic queries

addSource() reads confirmed ExampleBadge nodes as { label }. Read only the matched range. Add attribute validation and a precise patch if fields become editable. The semantic layer does not need HTML rendering.

ts

Queries default to the current selections or accept an explicit bounded range. pending means parsing is unconfirmed, not that syntax is absent; unsupported means the semantic entry is unavailable. Use requestMarkdownParse(editor.state) to register demand, then retry on accepted-state notification. Do not synchronously parse the whole document during input.

3. Choose presentation and editing

The example's inline layer returns marks and uses a theme. It does not hide delimiters. Use a NodeView for interactive controls or a block layer for multiline replacements. A display plugin should not silently normalize source when loaded.

Porting a markdown-it plugin

  1. Describe syntax, input/output, nesting, escapes and references.
  2. Implement Lezer handling for incomplete input, code contexts and nesting.
  3. Describe fields in a source codec; keep fields readonly until exact encoding exists.
  4. Render through projections or components; implement HTML export independently.
  5. Create source fixtures for unchanged reads, precise writes, undo, failures, readonly and incomplete parsing.

Many common plugins already have built-in equivalents. Check Features and installation, then Input rules and Projection layers.

MSFE · QuireKitYour Markdown source stays yours.