Skip to content

calternal_notes_core::markdown

Block-level markdown (de)serialization for the editor boundary (DESIGN §2, §4.1). This is not a full CommonMark inline parser: the live editing tree lives in TS (TipTap/ProseMirror); Rust splits a note into coarse blocks and re-joins them verbatim so round-trip is byte-for-byte for untouched content.

Each Block holds the exact source span (text) and a classified BlockKind. Content blocks are separated in the source by blank-line boundaries.

BLANK-LINE PRESERVATION (#49). Authors use blank lines for deliberate vertical spacing; the editor expresses each as an EMPTY paragraph. We model that 1:1: between two content blocks a gap of K blank lines (K ≥ 1) is parsed into K - 1 empty Paragraph blocks (text=“”), and serialize_blocks reproduces exactly K blank lines. So:

  • K = 1 (the conventional single separator) → 0 empty blocks → re-emits one blank line: existing, untouched content round-trips BYTE-FOR-BYTE and is never rewritten.
  • K = 2 → 1 empty paragraph → re-emits 2 blank lines, and so on.

The mapping K ↔ (K-1) empties is a bijection, so the round-trip is byte-exact AND idempotent for any interior blank run (no normalization of spacing). Leading/trailing blank runs are dropped (no edge empty blocks).

A lazy ^block-id trailing anchor on a block’s last line is lifted into Block.id and re-emitted on serialize (DESIGN §4.1 “blocks carry lazy ^block-id anchors”). The block-ID character rule is shared with Task checkbox parsing so both projections keep the same stable identity.

Source: crates/calternal-notes-core/src/markdown.rs

pub struct Block

A lossless Markdown block with an optional trailing block identity.

Fields

  • pub id: Option<String>
  • pub kind: BlockKind
  • pub text: String
  • pub children: Vec<Block>

Implements: Debug, Clone, PartialEq, Eq

pub fn new(kind: BlockKind, text: impl Into<String>) -> Self

No doc comment.

Source: crates/calternal-notes-core/src/markdown.rs:44

pub enum BlockKind

Coarse editor block kinds used for Markdown canonicalization.

Variants

  • Heading
  • Paragraph
  • List
  • Task
  • Code
  • Table
  • Callout
  • Event

Implements: Debug, Clone, Copy, PartialEq, Eq

Source: crates/calternal-notes-core/src/markdown.rs:31

pub fn canonicalize_markdown(text: &str) -> String

Canonicalize one markdown body at the editor boundary.

This is intentionally the exact legacy oracle expressed as one Rust-side operation: serialize_blocks(parse_markdown(text)). Keeping the composition here means the editor pays for one WASM call and never materializes the intermediate Block[] in JavaScript. The API returns a UTF-8 Rust String because the editor already owns a JavaScript string: a Uint8Array or raw pointer would still require a full decode back to UTF-16 at the call site, while adding allocation/lifetime and unsafe-memory ownership rules. A plain string therefore minimises the boundary work for this text-in/text-out API.

Source: crates/calternal-notes-core/src/markdown.rs:148

pub fn parse_markdown(text: &str) -> Vec<Block>

Parse a markdown note into a flat list of blocks (paragraph-level granularity). Content blocks are delimited by blank lines; interior blank runs beyond the single separator become empty Paragraph blocks so the author’s vertical spacing survives (see module docs / #49).

Round-trip: serialize_blocks(parse_markdown(x)) equals x byte-for-byte for any x whose only blank-line irregularity is interior (no leading or trailing blank runs) — which covers everything the editor produces.

Source: crates/calternal-notes-core/src/markdown.rs:70

pub fn serialize_blocks(blocks: &[Block]) -> String

Re-join blocks into a markdown string. Each content block’s verbatim text is emitted; a trailing ^id anchor is appended when Block.id is set and the text doesn’t already carry it. Content blocks are separated by one blank line; each empty-paragraph block between them adds ONE additional blank line (the inverse of parse_markdown — see module docs / #49). Leading/trailing empty-paragraph blocks are dropped so there are no edge blank lines.

Source: crates/calternal-notes-core/src/markdown.rs:103