pub enum Block {
Show 13 variants
Heading {
level: u8,
children: Vec<Inline>,
id: Option<String>,
},
Paragraph(Vec<Inline>),
Callout {
kind: CalloutKind,
fold: Option<Fold>,
title: Option<String>,
children: Vec<Block>,
},
List {
ordered: bool,
start: Option<u64>,
items: Vec<Vec<Block>>,
item_source_lines: Vec<Option<usize>>,
},
CodeBlock {
lang: Option<String>,
value: String,
},
Table {
header: Vec<Vec<Inline>>,
rows: Vec<Vec<Vec<Inline>>>,
alignments: Vec<ColumnAlignment>,
header_source_line: Option<usize>,
row_source_lines: Vec<Option<usize>>,
},
BlockQuote(Vec<Block>),
Shortcode(Shortcode),
ThematicBreak,
Figure {
image: Inline,
caption: Option<Vec<Inline>>,
width: Option<String>,
align: Option<String>,
class_names: Vec<String>,
img_style: Option<String>,
},
LinkCard {
url: Url,
children: Vec<Block>,
},
FootnoteDefinition {
label: String,
children: Vec<Block>,
},
Other(String),
}Expand description
A block-level AST node.
Variants§
Heading
# Heading (level 1) through ###### Heading (level 6).
Fields
id: Option<String>Heading anchor id (slug). Computed by the parser via
crate::heading::anchor::obsidian_heading_anchor.
Paragraph(Vec<Inline>)
A paragraph of inline content.
Callout
> [!type] body — typed callouts. The kind is canonicalized
via CalloutKind::from_raw (Obsidian-dialect aliases collapse
to the canonical 16-kind set). Foldable callouts (> [!type]+
open by default, > [!type]- closed) carry the Fold state;
non-foldable callouts have fold: None.
Phase 4 PR4 extended the shape from kind: String to
kind: CalloutKind + added fold: Option<Fold> and title: Option<String>.
Title is the optional inline text following the marker
(> [!note] My title → title: Some("My title")).
List
- item / 1. item. Each item is a list of blocks (so list items
can carry paragraphs, sub-lists, etc).
item_source_lines is a parallel-to-items vec of 1-based source
line numbers, populated by the parser only when
crate::ast::ParseConfig::emit_source_lines is true. When tracking
is off (production publish builds, the ~40 in-crate parse() callers
that use the default config), the vec is empty (vec![]) and the
renderer treats every item as None — no data-source-line on the
emitted <li>. When tracking is on, length matches items.len()
exactly; individual entries may still be None for synthesized
items that have no faithful source position (none today, but kept
for symmetry with crate::ast::document::BlockMeta::source_line).
Phase 4 source-lines followup (2026-05-28): added because the
preview’s scroll-sync (cm-scroll-sync via
frontend/bridge/iframe-bridge.ts) interpolates editor positions
proportionally between annotated DOM elements. A 30-item list
spanning 50 source lines without per-<li> annotations forces
interpolation between the outer <ul> and the next top-level
block — potentially 100 lines away. Legacy transform_events
(commit f91aca8fa, 2026-04-01) emitted on <li> and <tr> for
this reason; the typed-AST renderer now matches.
Fields
start: Option<u64>Explicit ordered-list start number (pulldown-cmark’s
Tag::List(Option<u64>) payload). Some(N) when the source
is N. item and the renderer should emit <ol start="N">;
None for unordered lists and for ordered lists where N is
the implicit default 1. CommonMark only honors the FIRST
item’s number as the list start; subsequent numbers are
re-derived. Phase 4 followup B (2026-05-28): added because
<ol> was previously emitted for any ordered list,
silently dropping the explicit start number — 3. foo
rendered as <ol><li>foo</li></ol> instead of
<ol start="3"><li>foo</li></ol>.
CodeBlock
A fenced code block.
Table
Markdown table.
header_source_line and row_source_lines are populated by the
parser only when crate::ast::ParseConfig::emit_source_lines is
true. When tracking is off, header_source_line is None and
row_source_lines is empty (vec![]); the renderer emits no
data-source-line attributes. When tracking is on,
row_source_lines.len() == rows.len().
Phase 4 source-lines followup (2026-05-28): see the corresponding
doc comment on Block::List for the scroll-sync interpolation
rationale.
Fields
alignments: Vec<ColumnAlignment>Per-column GFM alignment, parallel to header. Empty (the common
case) means the author declared no alignment on any column.
skip_serializing_if keeps previously-serialized ASTs and snapshot
fixtures byte-stable when there is no alignment to record.
BlockQuote(Vec<Block>)
> blockquote
Shortcode(Shortcode)
A typed shortcode block (:::name ...args\n body :::).
ThematicBreak
<hr> thematic break.
Figure
Image-only paragraph promoted to a typed figure.
Detected by the parser’s Tag::Paragraph arm (Phase 4 PR3,
2026-05-27): a paragraph that contains exactly one
Inline::Image modulo whitespace text and line breaks. The
renderer emits <figure class="moss-image">…<figcaption>…</figcaption></figure>,
wrapping the image hook’s output and appending the caption when
present.
image is constrained by the parser to be an Inline::Image;
the renderer pattern-matches and falls back gracefully if the
variant is anything else.
caption defaults to the image’s alt text at parse time. None
means “figure wrap but no <figcaption>” — reserved for the
empty-alt case (omit caption when there is nothing to read).
The figure-level display params (width, align, class_names,
img_style) are populated only when a figure originates from a
parameterized wikilink embed (![[photo.jpg|wide cover]]) — the
image-embed synth-collapse routes such embeds through this typed
node so width/fit/position/align survive (previously dropped by the
markdown round-trip). The CommonMark  promotion path
(try_promote_to_figure) leaves them at their defaults, so its
rendered output is byte-identical to before the collapse.
Fields
width: Option<String>Canonical width token (body | wide | page | screen) emitted as
data-width="…" on the <figure>. None omits the attribute.
String (not &'static str) so Block keeps its Deserialize
derive; the value is always one of the canonical tokens.
align: Option<String>Figure-level align class (moss-align-left / moss-align-right),
appended to the <figure> class list. None omits it.
LinkCard
Compound-link grid cell: the entire cell is a single markdown
link [inner](url) whose inner is parsed as block-level content
(images, headings, paragraphs, emphasis). The SoCiviC Theatre
pattern: [![[poster]] ### Title *date* description](/url).
Phase 4 PR4.5 (2026-05-28): added because CommonMark restricts
Inline::Link.children to inline-level content; a markdown link
wrapping ### Heading + paragraphs cannot round-trip through
pulldown-cmark’s inline parser. The cell-string-level shape
([...](url) with multi-paragraph inner content) is detected by
crate::ast::shortcode_extract::parse_grid BEFORE the cell flows
through crate::ast::parser::parse; the matched cell yields a
single-element vec![Block::LinkCard { url, children }] with the
inner markdown parsed into typed blocks.
Render shape (matches today’s render_compound_link_cell byte
shape):
- External URL (
http(s)://...):<a href=URL class="moss-grid-card link-preview" target="_blank" rel="noopener">children</a>. - Internal URL:
<a href=URL class="moss-grid-card" data-kind="link">children</a>.
FootnoteDefinition
[^label]: body — a GFM footnote definition, wherever the author
wrote it (pulldown-cmark nests one written inside a blockquote or a
list item under that container). The renderer hoists it out to the
document’s endnote section; see ADR-035.
Other(String)
Raw HTML passthrough: a Tag::HtmlBlock the author wrote, or a
payload moss synthesized itself (the shortcode sentinel pass in
dispatch_wikilink_embeds). Emitted verbatim. NOT a fallback for
unmodeled pulldown constructs — see the module doc.
Trait Implementations§
Source§impl<'de> Deserialize<'de> for Block
impl<'de> Deserialize<'de> for Block
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
impl Eq for Block
impl StructuralPartialEq for Block
Auto Trait Implementations§
impl Freeze for Block
impl RefUnwindSafe for Block
impl Send for Block
impl Sync for Block
impl Unpin for Block
impl UnsafeUnpin for Block
impl UnwindSafe for Block
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.