docspec-blocknote-writer
Converts a DocSpec event stream into BlockNote JSON. Implements the EventSink trait and emits JSON tokens directly to any Write target as events arrive — no intermediate document representation, constant memory regardless of file size.
Supported Features
| Block type | BlockNote type |
|---|---|
| Paragraph | paragraph |
| Heading (levels 1–6) | heading |
| Block quote | quote |
| Preformatted / code block | codeBlock |
| Image | image |
| Table (with header and data cells) | table |
| Thematic break | divider |
| Unordered list item | bulletListItem |
| Ordered list item | numberedListItem |
| Inline link | link inline type with href (title is dropped) |
Inline styles supported within text content: bold, italic, code, strikethrough, underline.
Inline links (StartLink/EndLink) emit a link inline type with the href. The optional title field is dropped (BlockNote's default link schema has no title).
List items support arbitrary nesting via BlockNote's native children: Block[] arrays. The start prop on numberedListItem is preserved when the first item in a sequence carries an explicit start number.
Not Yet Supported
- Footnotes —
StartFootnote/EndFootnote/FootnoteRefare silently ignored; BlockNote's default schema has no equivalent - Definition lists —
StartDefinitionList/StartDefinitionTerm/StartDefinitionDetail(and theirEnd*pairs) are silently ignored; BlockNote's default schema has no equivalent - Captions —
StartCaption/EndCaptionare silently ignored checkListItem— requires upstream DocSpec event support not yet definedtoggleListItem— no DocSpec event equivalent- Custom list style markers — the
style_typefield onStartOrderedListItem/StartUnorderedListItemis always dropped (BlockNote's default schema has no equivalent); list kind is determined entirely by the event variant (ordered vs unordered)
Usage
Wrap BlockNoteWriter in StackTrackingSink before feeding list events. The writer opens the list item's content[] array directly at Start*ListItem, and StartParagraph for the first paragraph of an item is a no-op. StackTrackingSink is still required because it normalizes paragraph boundaries (especially from sources that don't always emit an explicit StartParagraph inside list items), which is what lets the writer detect multi-paragraph items and transition emission into children[] for subsequent paragraphs.
use BlockNoteWriter;
use ;
let mut buf = Vec::new;
let mut writer = new;
writer.handle_event?;
// Plain paragraph
writer.handle_event?;
writer.handle_event?;
writer.handle_event?;
// Bullet list item
writer.handle_event?;
writer.handle_event?;
writer.handle_event?;
// Numbered list item
writer.handle_event?;
writer.handle_event?;
writer.handle_event?;
writer.handle_event?;
writer.finish?;
let json = Stringfrom_utf8?;
# Ok::
Limitations
Table cell content: BlockNote's tableCell.content is InlineContent[] and cannot hold block-level types. Block-level events inside a cell are handled as follows:
StartParagraph/EndParagraphboundaries are absorbed silently (adjacent paragraphs concatenate without separator)TextandLineBreakare preserved- Everything else (images, headings, code blocks, blockquotes, list items, nested tables) is dropped silently
Non-paragraph children inside list items: headings, images, code blocks, and blockquotes that appear as children of a list item are dropped. The first paragraph's inline content populates the item's content[] array; each subsequent paragraph becomes a child paragraph block in children[].
Blockquote + list interaction: a list item encountered while inside a blockquote force-closes the blockquote and emits the list item at the top level as a sibling.
Image-in-link: BlockNote does not allow block-level images inside inline links. The reader closes the link before emitting the image as a sibling block, and the writer serialises that sequence directly. Content preceding the image stays inside the link; the image becomes a sibling block after the link closes; content following the image appears outside the link, losing its link wrapper. The link is empty only when the image is the sole link label, e.g. [](url). All variants are lossy mappings of the original "image-inside-link" structure.
API Documentation
See the module-level docs for the full API surface, including BlockNoteWriter and its EventSink implementation.
License
See the repository LICENSE.