Skip to content

feat: replace unified.js with custom markdown parser/serializer#2624

Open
nperez0111 wants to merge 4 commits intomainfrom
feat/custom-md-parser
Open

feat: replace unified.js with custom markdown parser/serializer#2624
nperez0111 wants to merge 4 commits intomainfrom
feat/custom-md-parser

Conversation

@nperez0111
Copy link
Copy Markdown
Contributor

@nperez0111 nperez0111 commented Apr 3, 2026

Summary

Replace the unified.js ecosystem (~89 transitive packages) with two custom, zero-dependency files for bidirectional markdown↔HTML conversion in BlockNote.

Rationale

The unified/remark/rehype pipeline was the largest single dependency ecosystem in BlockNote, yet was used in only 2 files with a narrow feature set. The pipeline built two unnecessary intermediate ASTs (MDAST + HAST) for what is fundamentally a direct markdown↔HTML conversion, and required 6 custom handlers/plugins to work around its abstractions.

Changes

  • New markdownToHtml.ts (~600 LOC): Custom markdown→HTML parser with two-pass architecture (block tokenizer + inline parser), recursive list handling, GFM table support, and video URL detection
  • New htmlToMarkdown.ts (~500 LOC): Custom HTML→markdown serializer using DOMParser with proper list nesting, table column padding, toggle item handling, and lossy conversion support
  • Removed 12 direct dependencies: unified, remark-parse, remark-stringify, remark-gfm, remark-rehype, rehype-parse, rehype-stringify, rehype-remark, rehype-format, hast-util-from-dom, unist-util-visit, @types/hast
  • Deleted 3 custom rehype plugins: convertVideoToMarkdownRehypePlugin, removeUnderlinesRehypePlugin, addSpacesToCheckboxesRehypePlugin (their logic is now built into the serializer)
  • Removed unified deps from @blocknote/xl-ai: unified, remark-parse, remark-stringify
  • Expanded test coverage: Added 100+ new test cases for markdown parsing (headings, code blocks, tables, task lists, nested lists, inline styles, etc.) and export/parse equality round-trip tests
  • Minor behavioral improvement: Trailing spaces in emphasis are moved outside delimiters (**Bold** Heading) instead of escaped with HTML entities (**Bold **Heading), producing cleaner, more readable markdown

Impact

  • Eliminates ~89 transitive packages from the dependency tree
  • All existing markdown features preserved: GFM tables, strikethrough, task lists, nested lists, code blocks with language, blockquotes, images, video URL detection, hard breaks
  • Lossy conversions (colors, underlines, alignment) behave identically
  • The nestedLists.test.ts no longer uses unified for pretty-printing (uses raw innerHTML comparison instead)

Testing

  • 672 core unit tests passing (0 failures)
  • 136 markdown parse tests (headings, lists, tables, emphasis, code blocks, blockquotes, links, images, videos, etc.)
  • 328 export tests (paragraphs, lists, code blocks, tables, media, inline styles, hard breaks, etc.)
  • 113 export/parse equality round-trip tests
  • Only 1 existing snapshot changed (complex/misc.md) due to the cleaner emphasis handling
  • TypeScript compilation passes with no errors

Screenshots/Video

N/A - internal serialization change with no UI impact.

Checklist

  • Code follows the project's coding standards.
  • Unit tests covering the new feature have been added.
  • All existing tests pass.
  • The documentation has been updated to reflect the new feature

Additional Notes

The custom parser handles all CommonMark + GFM features that BlockNote needs: ATX/setext headings, fenced code blocks, GFM pipe tables, nested lists (with recursive content parsing for wide markers like * ), task lists, blockquotes, emphasis (bold/italic/strikethrough), inline code, links, images with video URL detection, hard line breaks, and backslash escapes. Features not needed by BlockNote (footnotes, definition lists, math, frontmatter, reference-style links) are intentionally omitted.

Summary by CodeRabbit

  • New Features

    • Improved built-in Markdown↔HTML conversion with richer support for headings, nested lists and task items, tables, code blocks, blockquotes, media, and inline formatting.
  • Chores

    • Streamlined internal Markdown processing by removing several now-unused external processing dependencies.
  • Tests

    • Greatly expanded Markdown export/import round‑trip test coverage across headings, lists, tables, code blocks, media, and complex documents.

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
blocknote Ready Ready Preview Apr 3, 2026 0:33am
blocknote-website Ready Ready Preview Apr 3, 2026 0:33am

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 3, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 45fd4379-ed83-4aec-b71a-e474e365cae4

📥 Commits

Reviewing files that changed from the base of the PR and between 837e367 and 7c3e17d.

📒 Files selected for processing (1)
  • tests/src/unit/core/formatConversion/exportParseEquality/exportParseEqualityTestInstances.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/src/unit/core/formatConversion/exportParseEquality/exportParseEqualityTestInstances.ts

📝 Walkthrough

Walkthrough

Replaces the unified/remark/rehype pipeline with custom TypeScript Markdown↔HTML converters, removes related rehype/remark plugins and dependencies, updates exporter/parser calls, and expands Markdown/HTML round‑trip tests and fixtures.

Changes

Cohort / File(s) Summary
Dependency Removals
packages/core/package.json, packages/xl-ai/package.json
Removed unified, remark-*, rehype-*, and related HAST types from dependencies/devDependencies.
New Markdown→HTML
packages/core/src/api/parsers/markdown/markdownToHtml.ts
Added custom tokenizer + HTML emitter: supports headings (ATX/setext), paragraphs, blockquotes, fenced code (with language), lists (ordered/bullet/task with nesting), tables, inline syntax, images/videos, and exports markdownToHtml(markdown:string).
New HTML→Markdown
packages/core/src/api/exporters/markdown/htmlToMarkdown.ts
Added DOM-based HTML parser and recursive serializer producing GFM-like Markdown for block and inline elements; exports htmlToMarkdown(html:string).
Removed Rehype Plugins
packages/core/src/api/exporters/markdown/util/addSpacesToCheckboxesRehypePlugin.ts, .../convertVideoToMarkdownRehypePlugin.ts, .../removeUnderlinesRehypePlugin.ts
Deleted custom rehype plugins; their behavior has been reimplemented inside the new converters.
Exporter/Parser Integrations
packages/core/src/api/exporters/markdown/markdownExporter.ts, packages/core/src/api/parsers/markdown/parseMarkdown.ts
Replaced unified pipelines with calls to htmlToMarkdown() and markdownToHtml(), removing inline rehype/remark pipeline logic.
Tests — Fixtures & Executors
tests/src/unit/core/formatConversion/export/exportTestInstances.ts, tests/src/unit/core/formatConversion/parse/parseTestInstances.ts, tests/src/unit/core/formatConversion/exportParseEquality/exportParseEqualityTestInstances.ts, tests/src/unit/shared/.../exportParseEqualityTestExecutors.ts
Expanded HTML export fixtures, added comprehensive Markdown parse tests and Markdown round‑trip test instances, and added testExportParseEqualityMarkdown executor (file-based snapshotting for lossy round-trips).
Test Registration / Adjustments
tests/src/unit/core/formatConversion/exportParseEquality/runTests.test.ts, tests/src/unit/core/formatConversion/parse/nestedLists.test.ts
Added new Vitest suite for Markdown export/parse equality; simplified nested lists test to snapshot raw innerHTML (removed unified formatting step).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 I hopped through tags and tokens bright,\
Swapped tangled plugins for code that’s light,\
From markdown hills to HTML glade,\
I stitched the paths that tests parade,\
A tiny rabbit, converters made.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.65% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: replace unified.js with custom markdown parser/serializer' accurately and concisely summarizes the main change: replacing the unified.js library with custom implementations.
Description check ✅ Passed The PR description is comprehensive and well-structured. It follows the template with Summary, Rationale, Changes, Impact, Testing, Screenshots/Video, and Checklist sections, providing detailed information about the changes and testing performed.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/custom-md-parser

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

nperez0111 and others added 2 commits April 3, 2026 11:12
…izer

Replace the remark/rehype/unified pipeline (~89 transitive packages) with two
custom, zero-dependency files for markdown↔HTML conversion. The unified ecosystem
was overkill for BlockNote's bounded feature set and required 6 custom plugins
to work around its abstractions.

New files:
- markdownToHtml.ts: block tokenizer + inline parser with recursive list handling
- htmlToMarkdown.ts: DOM-based serializer with proper list nesting and table support

Removes 12 direct dependencies: unified, remark-parse, remark-stringify, remark-gfm,
remark-rehype, rehype-parse, rehype-stringify, rehype-remark, rehype-format,
hast-util-from-dom, unist-util-visit, @types/hast

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add curly braces to single-line if/while statements and convert inner
function declaration to arrow function to fix no-inner-declarations error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
tests/src/unit/core/formatConversion/export/exportTestInstances.ts (1)

1148-2630: Consider extracting a helper for table cell creation.

The table tests provide excellent coverage of headers, spans, colors, alignment, and inline content. However, the default cell props are repeated extensively. A helper function could reduce boilerplate while maintaining readability.

💡 Optional helper function example
// Could be added at the top of the file or in a shared test utilities module
const createTableCell = (
  content: string | any[],
  overrides: Partial<{
    backgroundColor: string;
    colspan: number;
    rowspan: number;
    textAlignment: string;
    textColor: string;
  }> = {}
) => ({
  type: "tableCell" as const,
  content: typeof content === "string" ? [content] : content,
  props: {
    backgroundColor: "default",
    colspan: 1,
    rowspan: 1,
    textAlignment: "left",
    textColor: "default",
    ...overrides,
  },
});

// Usage example:
// createTableCell("Table Cell")
// createTableCell("Bold", { backgroundColor: "red", textColor: "blue" })

This is purely optional - the explicit definitions are also valid for test clarity and snapshot stability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/src/unit/core/formatConversion/export/exportTestInstances.ts` around
lines 1148 - 2630, Tests repeat the same default table cell object structure
many times; extract a small helper (e.g., createTableCell) and replace repeated
tableCell objects with calls to it to reduce boilerplate. Implement a helper
that accepts content (string or node array) and an overrides partial for props
(backgroundColor, colspan, rowspan, textAlignment, textColor), returns an object
with type: "tableCell", content normalized to an array, and props merged with
defaults, then update table test cases (used by testExportBlockNoteHTML) to call
createTableCell(...) instead of inlining identical props.
tests/src/unit/core/formatConversion/exportParseEquality/exportParseEqualityTestInstances.ts (1)

391-827: Comprehensive test coverage for the new markdown parser/serializer.

The test cases cover the key markdown features well: block types, inline styles, nested structures, and a complex integration test. The structure is consistent and follows established patterns.

Optional: Consider adding edge case tests for deeper nesting (3+ levels) and special character escaping (e.g., *asterisks* within content, backticks in code) if the custom parser handles these scenarios. These could be added in a follow-up to ensure robustness.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tests/src/unit/core/formatConversion/exportParseEquality/exportParseEqualityTestInstances.ts`
around lines 391 - 827, Add tests for deeper nesting and special-character
escaping to the existing markdown test suite by appending new testCase entries
(executed with testExportParseEqualityMarkdown) to the array in
exportParseEqualityTestInstances.ts; specifically add a
"markdown/deeplyNestedLists" case with 3+ nested list items (use types like
bulletListItem and numberedListItem and children arrays) and a
"markdown/specialCharEscaping" case containing inline text nodes with literal
asterisks, backticks, and other markdown-significant characters (and a codeBlock
containing backticks) so the parser/serializer round-trip covers escaping and
nested-depth edge cases referenced by testExportParseEqualityMarkdown.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/src/api/parsers/markdown/markdownToHtml.ts`:
- Around line 128-143: The underscore delimiter is being treated the same as `*`
in markdownToHtml, causing intraword emphasis like `snake_case` to be rendered
incorrectly; before calling parseDelimited(text, i, delimiter, "<em>", "</em>")
when delimiter === "_" check the left- and right-flanking status (i.e., inspect
the previous and next characters and determine if both sides are alphanumeric)
and skip parsing if the delimiter is intraword (both left- and right-flanking).
Implement this by using or adding an isAlphanumeric(char) helper and computing
leftFlanking/rightFlanking from text[i-1] and text[i+1] (being careful at
bounds), and only call parseDelimited for "_" when it is not intraword; keep
behavior for "*" unchanged.

---

Nitpick comments:
In `@tests/src/unit/core/formatConversion/export/exportTestInstances.ts`:
- Around line 1148-2630: Tests repeat the same default table cell object
structure many times; extract a small helper (e.g., createTableCell) and replace
repeated tableCell objects with calls to it to reduce boilerplate. Implement a
helper that accepts content (string or node array) and an overrides partial for
props (backgroundColor, colspan, rowspan, textAlignment, textColor), returns an
object with type: "tableCell", content normalized to an array, and props merged
with defaults, then update table test cases (used by testExportBlockNoteHTML) to
call createTableCell(...) instead of inlining identical props.

In
`@tests/src/unit/core/formatConversion/exportParseEquality/exportParseEqualityTestInstances.ts`:
- Around line 391-827: Add tests for deeper nesting and special-character
escaping to the existing markdown test suite by appending new testCase entries
(executed with testExportParseEqualityMarkdown) to the array in
exportParseEqualityTestInstances.ts; specifically add a
"markdown/deeplyNestedLists" case with 3+ nested list items (use types like
bulletListItem and numberedListItem and children arrays) and a
"markdown/specialCharEscaping" case containing inline text nodes with literal
asterisks, backticks, and other markdown-significant characters (and a codeBlock
containing backticks) so the parser/serializer round-trip covers escaping and
nested-depth edge cases referenced by testExportParseEqualityMarkdown.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d6a99fa6-56ef-4eec-b935-b34f2a74e929

📥 Commits

Reviewing files that changed from the base of the PR and between 38be5fd and f9da2df.

⛔ Files ignored due to path filters (248)
  • packages/core/src/api/parsers/html/util/__snapshots__/nestedLists.test.ts.snap is excluded by !**/*.snap, !**/__snapshots__/**
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/audio/basic.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/audio/button.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/audio/noName.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/complex/document.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h1.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h2.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h3.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h4.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h5.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h6.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/styled.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/toggleable.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/withCaption.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/withCode.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/lists/numberedListStart.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/paragraph/multiple.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/basic.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/multiple.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/nested.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/styled.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/withLink.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/backgroundColor.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/bold.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/boldItalicStrike.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/code.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/combined.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/italic.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/mixedInParagraph.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/strike.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/textColor.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/underline.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/advancedExample.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/cellTextAlignment.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/emptyCells.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/hardBreakInCell.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/headerRowsAndCols.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/linksInCells.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/singleCell.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/styledCellContent.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/video/withCaption.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/audio/basic.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/audio/button.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/audio/noName.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/complex/document.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h1.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h2.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h3.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h4.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h5.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h6.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/styled.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/toggleable.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/image/withCaption.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/link/withCode.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/numberedListStart.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/multiple.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/basic.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/multiple.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/nested.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/styled.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/withLink.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/style/backgroundColor.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/style/bold.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/style/boldItalicStrike.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/style/code.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/style/combined.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/style/italic.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/style/mixedInParagraph.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/style/strike.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/style/textColor.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/style/underline.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/table/advancedExample.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/table/cellTextAlignment.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/table/emptyCells.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/table/hardBreakInCell.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/table/headerRowsAndCols.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/table/linksInCells.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/table/singleCell.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/table/styledCellContent.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/video/withCaption.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/audio/basic.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/audio/button.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/audio/noName.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/complex/document.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/complex/misc.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h1.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h2.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h3.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h4.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h5.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h6.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/styled.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/toggleable.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/withCaption.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/link/withCode.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/numberedListStart.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/paragraph/multiple.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/basic.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/multiple.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/nested.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/styled.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/withLink.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/backgroundColor.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/bold.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/boldItalicStrike.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/code.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/combined.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/italic.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/mixedInParagraph.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/strike.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/textColor.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/underline.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/advancedExample.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/cellTextAlignment.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/emptyCells.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/hardBreakInCell.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/headerRowsAndCols.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/linksInCells.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/singleCell.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/styledCellContent.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/markdown/video/withCaption.md is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/audio/basic.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/audio/button.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/audio/noName.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/complex/document.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h1.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h2.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h3.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h4.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h5.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h6.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/styled.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/toggleable.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/image/withCaption.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/withCode.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/lists/numberedListStart.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/paragraph/multiple.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/basic.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/multiple.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/nested.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/styled.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/withLink.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/backgroundColor.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/bold.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/boldItalicStrike.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/code.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/combined.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/italic.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/mixedInParagraph.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/strike.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/textColor.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/underline.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/advancedExample.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/cellTextAlignment.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/emptyCells.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/hardBreakInCell.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/headerRowsAndCols.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/linksInCells.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/singleCell.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/styledCellContent.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/nodes/video/withCaption.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/bold.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/boldItalic.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/bulletList.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/checkList.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/codeBlock.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/complexDocument.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/divider.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/hardBreak.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/headingLevels.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/image.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/inlineCode.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/italic.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/link.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/mixedStyles.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/multipleParagraphs.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/nestedLists.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/numberedList.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/paragraph.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/quote.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/strike.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/table.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/video.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/adjacentFormattedRuns.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/adjacentLinks.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/backslashEscapes.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteMultiline.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteWithCode.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteWithLink.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/boldOnly.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/boldUnderscore.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/checkListBasic.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/checkListMixed.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/checkListNested.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockBasic.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockPython.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockTildes.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockWithLanguage.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockWithSpecialChars.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/complexDocument.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/deeplyNestedLists.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/emptyString.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/hardBreakBackslash.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/hardBreakMultiple.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH1.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH2.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH3.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH4.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH5.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH6.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingThenCode.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingWithInlineStyles.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/horizontalRuleAsterisks.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/horizontalRuleDashes.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/horizontalRuleUnderscores.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/imageWithAlt.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineCode.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineCodeWithSpecialChars.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineImage.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/italicOnly.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/italicUnderscore.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkAndText.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkBasic.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkInParagraph.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkWithStyledContent.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/listWithStyledItems.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/mixedInlineContent.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/mixedListTypes.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/multipleImages.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/multipleParagraphs.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedBulletLists.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedEmphasis.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedEmphasisComplex.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedOrderedLists.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/onlyWhitespace.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/orderedListStart.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/paragraphContinuation.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/setextH1.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/setextH2.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/strikethroughOnly.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableAlignment.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableBasic.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableFollowedByParagraph.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableThreeColumns.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableWithInlineFormatting.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableWithLinks.json is excluded by !**/__snapshots__/**
📒 Files selected for processing (15)
  • packages/core/package.json
  • packages/core/src/api/exporters/markdown/htmlToMarkdown.ts
  • packages/core/src/api/exporters/markdown/markdownExporter.ts
  • packages/core/src/api/exporters/markdown/util/addSpacesToCheckboxesRehypePlugin.ts
  • packages/core/src/api/exporters/markdown/util/convertVideoToMarkdownRehypePlugin.ts
  • packages/core/src/api/exporters/markdown/util/removeUnderlinesRehypePlugin.ts
  • packages/core/src/api/parsers/html/util/nestedLists.test.ts
  • packages/core/src/api/parsers/markdown/markdownToHtml.ts
  • packages/core/src/api/parsers/markdown/parseMarkdown.ts
  • packages/xl-ai/package.json
  • tests/src/unit/core/formatConversion/export/exportTestInstances.ts
  • tests/src/unit/core/formatConversion/exportParseEquality/exportParseEqualityTestInstances.ts
  • tests/src/unit/core/formatConversion/exportParseEquality/runTests.test.ts
  • tests/src/unit/core/formatConversion/parse/parseTestInstances.ts
  • tests/src/unit/shared/formatConversion/exportParseEquality/exportParseEqualityTestExecutors.ts
💤 Files with no reviewable changes (5)
  • packages/xl-ai/package.json
  • packages/core/package.json
  • packages/core/src/api/exporters/markdown/util/removeUnderlinesRehypePlugin.ts
  • packages/core/src/api/exporters/markdown/util/convertVideoToMarkdownRehypePlugin.ts
  • packages/core/src/api/exporters/markdown/util/addSpacesToCheckboxesRehypePlugin.ts

…ments

Use document.createElement instead of DOMParser and numeric constants
instead of Node.TEXT_NODE/ELEMENT_NODE for server-side compatibility.
Update snapshots to match new serializer output.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/src/api/exporters/markdown/htmlToMarkdown.ts`:
- Around line 368-370: The table cell content is inserted verbatim into the
Markdown row, so characters like "|" and embedded newlines break GFM tables;
update the logic around serializeInlineContent(cell as HTMLElement) to escape
pipe characters and replace or encode newlines (e.g., convert "\n" to "<br>" or
"\n" to "\\n") before using colspan/rowspan to lay out the row; modify the code
that reads content, colspan, and rowspan so the escaped content is used for
building the row string (also apply the same escaping fix in the similar block
around the serializeInlineContent usage at the other location mentioned).
- Around line 460-467: The serializer currently injects raw alt/src/text/href
into markdown (see serializeImage and other serializers handling textContent,
alt, href, src), which can produce accidental headings, lists or break
link/image syntax; add and use escaping helpers (e.g., escapeLabel for
link/image labels, escapeDestination for URLs/targets, and escapeText for
paragraph/text runs) that 1) escape characters like ] and ) in
labels/destinations, 2) percent-encode or backslash-escape problematic
characters in destinations, and 3) prefix or escape leading markdown markers
(like "# ", "1. ") in textContent; then update serializeImage to call
escapeLabel(alt) and escapeDestination(src) before building `![...]()` and apply
the same helpers in the other affected serializers referenced (the branches
around textContent, href handling and the ranges you noted).
- Around line 593-595: The inline code handler in htmlToMarkdown produces
malformed markdown when the code contains backticks; update the "code" case in
htmlToMarkdown to compute the longest run of backticks in childEl.textContent
(e.g., scan with /`+/g), pick a delimiter of backticks one longer than that run,
and if the content begins or ends with a backtick or whitespace pad the content
with a single space inside the delimiters; then join delimiter + paddedContent +
delimiter instead of always a single backtick. Ensure you operate on
childEl.textContent (or "" fallback) and preserve original content otherwise.
- Around line 674-680: trimHardBreaks currently strips any trailing backslash
with result = result.replace(/\\$/, ""), which removes legitimate trailing
backslashes (e.g., paths/regex). Remove that final replacement and only trim
leading/trailing backslash+newline sequences (the existing /^(\\\n)+/ and
/(\\\n)+$/ patterns) so real trailing "\" characters are preserved; update tests
for trimHardBreaks to cover strings that legitimately end with a backslash.
- Around line 147-172: serializeCodeBlock always emits triple backticks which
breaks when the code contains ```; modify serializeCodeBlock to compute a fence
string longer than any run of backticks inside the extracted code (e.g., find
the longest consecutive sequence of '`' in code and create fence as that
length+1 backticks), then use that fence variable in place of the hardcoded
"```" when opening and closing the block and when returning the empty-block
case, keeping language and indent handling the same; update references to
code.endsWith("\n") logic to still append a single newline inside the fence
before the closing fence.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 460294a2-949b-4d1a-a661-02f96ad629c6

📥 Commits

Reviewing files that changed from the base of the PR and between f9da2df and 837e367.

⛔ Files ignored due to path filters (3)
  • packages/server-util/src/context/__snapshots__/ServerBlockNoteEditor.test.ts.snap is excluded by !**/*.snap, !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/deeplyNestedLists.json is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/specialCharEscaping.json is excluded by !**/__snapshots__/**
📒 Files selected for processing (3)
  • packages/core/src/api/exporters/markdown/htmlToMarkdown.ts
  • packages/core/src/api/parsers/markdown/markdownToHtml.ts
  • tests/src/unit/core/formatConversion/exportParseEquality/exportParseEqualityTestInstances.ts

Comment on lines +147 to +172
function serializeCodeBlock(el: HTMLElement, ctx: SerializeContext): string {
const codeEl = el.querySelector("code");
if (!codeEl) {return "";}

const language =
codeEl.getAttribute("data-language") ||
extractLanguageFromClass(codeEl.className) ||
"";

// Extract code content, handling <br> elements as newlines
const code = extractCodeContent(codeEl);

// For empty code blocks, don't add a newline between the fences
if (!code) {
return ctx.indent + "```" + language + "\n```\n\n";
}

return (
ctx.indent +
"```" +
language +
"\n" +
code +
(code.endsWith("\n") ? "" : "\n") +
"```\n\n"
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Choose a fence that is longer than the code payload.

Always emitting triple backticks breaks as soon as the code block itself contains ```; the fence closes early and the remainder is serialized as normal markdown.

Possible fix
 function serializeCodeBlock(el: HTMLElement, ctx: SerializeContext): string {
   const codeEl = el.querySelector("code");
   if (!codeEl) {return "";}
@@
   // Extract code content, handling <br> elements as newlines
   const code = extractCodeContent(codeEl);
+  const longestRun = Math.max(
+    0,
+    ...((code.match(/`+/g) ?? []).map((run) => run.length))
+  );
+  const fence = "`".repeat(Math.max(3, longestRun + 1));
@@
   // For empty code blocks, don't add a newline between the fences
   if (!code) {
-    return ctx.indent + "```" + language + "\n```\n\n";
+    return ctx.indent + fence + language + "\n" + fence + "\n\n";
   }
@@
     ctx.indent +
-    "```" +
+    fence +
     language +
     "\n" +
     code +
     (code.endsWith("\n") ? "" : "\n") +
-    "```\n\n"
+    fence +
+    "\n\n"
   );
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function serializeCodeBlock(el: HTMLElement, ctx: SerializeContext): string {
const codeEl = el.querySelector("code");
if (!codeEl) {return "";}
const language =
codeEl.getAttribute("data-language") ||
extractLanguageFromClass(codeEl.className) ||
"";
// Extract code content, handling <br> elements as newlines
const code = extractCodeContent(codeEl);
// For empty code blocks, don't add a newline between the fences
if (!code) {
return ctx.indent + "```" + language + "\n```\n\n";
}
return (
ctx.indent +
"```" +
language +
"\n" +
code +
(code.endsWith("\n") ? "" : "\n") +
"```\n\n"
);
function serializeCodeBlock(el: HTMLElement, ctx: SerializeContext): string {
const codeEl = el.querySelector("code");
if (!codeEl) {return "";}
const language =
codeEl.getAttribute("data-language") ||
extractLanguageFromClass(codeEl.className) ||
"";
// Extract code content, handling <br> elements as newlines
const code = extractCodeContent(codeEl);
const longestRun = Math.max(
0,
...((code.match(/`+/g) ?? []).map((run) => run.length))
);
const fence = "`".repeat(Math.max(3, longestRun + 1));
// For empty code blocks, don't add a newline between the fences
if (!code) {
return ctx.indent + fence + language + "\n" + fence + "\n\n";
}
return (
ctx.indent +
fence +
language +
"\n" +
code +
(code.endsWith("\n") ? "" : "\n") +
fence +
"\n\n"
);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/api/exporters/markdown/htmlToMarkdown.ts` around lines 147
- 172, serializeCodeBlock always emits triple backticks which breaks when the
code contains ```; modify serializeCodeBlock to compute a fence string longer
than any run of backticks inside the extracted code (e.g., find the longest
consecutive sequence of '`' in code and create fence as that length+1
backticks), then use that fence variable in place of the hardcoded "```" when
opening and closing the block and when returning the empty-block case, keeping
language and indent handling the same; update references to code.endsWith("\n")
logic to still append a single newline inside the fence before the closing
fence.

Comment on lines +368 to +370
const content = serializeInlineContent(cell as HTMLElement).trim();
const colspan = parseInt(cell.getAttribute("colspan") || "1", 10);
const rowspan = parseInt(cell.getAttribute("rowspan") || "1", 10);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Escape table-cell content before laying out the row.

Cell text is inserted verbatim, so | creates extra columns and embedded newlines split the row. That makes otherwise valid table content serialize into invalid GFM.

Possible fix
-      const content = serializeInlineContent(cell as HTMLElement).trim();
+      const content = escapeTableCell(
+        serializeInlineContent(cell as HTMLElement).trim()
+      );
@@
 function formatTableRow(
   cells: string[],
   colWidths: number[],
   colCount: number
 ): string {
@@
   return "|" + parts.join("|") + "|";
 }
+
+function escapeTableCell(text: string): string {
+  return text
+    .replace(/\\/g, "\\\\")
+    .replace(/\|/g, "\\|")
+    .replace(/\r?\n/g, "<br>");
+}

Also applies to: 437-447

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/api/exporters/markdown/htmlToMarkdown.ts` around lines 368
- 370, The table cell content is inserted verbatim into the Markdown row, so
characters like "|" and embedded newlines break GFM tables; update the logic
around serializeInlineContent(cell as HTMLElement) to escape pipe characters and
replace or encode newlines (e.g., convert "\n" to "<br>" or "\n" to "\\n")
before using colspan/rowspan to lay out the row; modify the code that reads
content, colspan, and rowspan so the escaped content is used for building the
row string (also apply the same escaping fix in the similar block around the
serializeInlineContent usage at the other location mentioned).

Comment on lines +460 to +467
function serializeImage(el: HTMLElement, ctx: SerializeContext): string {
const src = el.getAttribute("src") || "";
const alt = el.getAttribute("alt") || "";
if (!src) {
return ctx.indent + "Add image\n\n";
}
return ctx.indent + `![${alt}](${src})\n\n`;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Escape raw text, labels, and destinations before interpolating markdown.

These branches concatenate textContent, alt, href, and src directly into markdown syntax. A literal paragraph that starts with # or 1. becomes a heading/list item on export, and ] / ) in labels or URLs will break link and image syntax instead of round-tripping literally.

Also applies to: 524-529, 555-645

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/api/exporters/markdown/htmlToMarkdown.ts` around lines 460
- 467, The serializer currently injects raw alt/src/text/href into markdown (see
serializeImage and other serializers handling textContent, alt, href, src),
which can produce accidental headings, lists or break link/image syntax; add and
use escaping helpers (e.g., escapeLabel for link/image labels, escapeDestination
for URLs/targets, and escapeText for paragraph/text runs) that 1) escape
characters like ] and ) in labels/destinations, 2) percent-encode or
backslash-escape problematic characters in destinations, and 3) prefix or escape
leading markdown markers (like "# ", "1. ") in textContent; then update
serializeImage to call escapeLabel(alt) and escapeDestination(src) before
building `![...]()` and apply the same helpers in the other affected serializers
referenced (the branches around textContent, href handling and the ranges you
noted).

Comment on lines +593 to +595
case "code":
result += "`" + (childEl.textContent || "") + "`";
break;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Inline code spans need dynamic delimiters too.

A literal backtick inside <code> currently produces malformed markdown. Inline code needs a fence longer than the longest backtick run in the payload, plus padding when the content starts or ends with backticks or whitespace.

Possible fix
-        case "code":
-          result += "`" + (childEl.textContent || "") + "`";
-          break;
+        case "code": {
+          const text = childEl.textContent || "";
+          const longestRun = Math.max(
+            0,
+            ...((text.match(/`+/g) ?? []).map((run) => run.length))
+          );
+          const fence = "`".repeat(longestRun + 1);
+          const needsPadding =
+            text.startsWith("`") ||
+            text.endsWith("`") ||
+            /^\s/.test(text) ||
+            /\s$/.test(text);
+          result += fence + (needsPadding ? ` ${text} ` : text) + fence;
+          break;
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case "code":
result += "`" + (childEl.textContent || "") + "`";
break;
case "code": {
const text = childEl.textContent || "";
const longestRun = Math.max(
0,
...((text.match(/`+/g) ?? []).map((run) => run.length))
);
const fence = "`".repeat(longestRun + 1);
const needsPadding =
text.startsWith("`") ||
text.endsWith("`") ||
/^\s/.test(text) ||
/\s$/.test(text);
result += fence + (needsPadding ? ` ${text} ` : text) + fence;
break;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/api/exporters/markdown/htmlToMarkdown.ts` around lines 593
- 595, The inline code handler in htmlToMarkdown produces malformed markdown
when the code contains backticks; update the "code" case in htmlToMarkdown to
compute the longest run of backticks in childEl.textContent (e.g., scan with
/`+/g), pick a delimiter of backticks one longer than that run, and if the
content begins or ends with a backtick or whitespace pad the content with a
single space inside the delimiters; then join delimiter + paddedContent +
delimiter instead of always a single backtick. Ensure you operate on
childEl.textContent (or "" fallback) and preserve original content otherwise.

Comment on lines +674 to +680
function trimHardBreaks(content: string): string {
// Remove leading hard breaks
let result = content.replace(/^(\\\n)+/, "");
// Remove trailing hard breaks (including trailing backslash)
result = result.replace(/(\\\n)+$/, "");
result = result.replace(/\\$/, "");
return result;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Don’t drop a real trailing backslash.

The final replacement removes any paragraph that legitimately ends with \, not just a serialized <br>. That corrupts content like Windows paths or regex snippets.

Possible fix
 function trimHardBreaks(content: string): string {
   // Remove leading hard breaks
   let result = content.replace(/^(\\\n)+/, "");
-  // Remove trailing hard breaks (including trailing backslash)
+  // Remove trailing hard breaks produced by `<br>`
   result = result.replace(/(\\\n)+$/, "");
-  result = result.replace(/\\$/, "");
   return result;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function trimHardBreaks(content: string): string {
// Remove leading hard breaks
let result = content.replace(/^(\\\n)+/, "");
// Remove trailing hard breaks (including trailing backslash)
result = result.replace(/(\\\n)+$/, "");
result = result.replace(/\\$/, "");
return result;
function trimHardBreaks(content: string): string {
// Remove leading hard breaks
let result = content.replace(/^(\\\n)+/, "");
// Remove trailing hard breaks produced by `<br>`
result = result.replace(/(\\\n)+$/, "");
return result;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/api/exporters/markdown/htmlToMarkdown.ts` around lines 674
- 680, trimHardBreaks currently strips any trailing backslash with result =
result.replace(/\\$/, ""), which removes legitimate trailing backslashes (e.g.,
paths/regex). Remove that final replacement and only trim leading/trailing
backslash+newline sequences (the existing /^(\\\n)+/ and /(\\\n)+$/ patterns) so
real trailing "\" characters are preserved; update tests for trimHardBreaks to
cover strings that legitimately end with a backslash.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 3, 2026

Open in StackBlitz

@blocknote/ariakit

npm i https://pkg.pr.new/@blocknote/ariakit@2624

@blocknote/code-block

npm i https://pkg.pr.new/@blocknote/code-block@2624

@blocknote/core

npm i https://pkg.pr.new/@blocknote/core@2624

@blocknote/mantine

npm i https://pkg.pr.new/@blocknote/mantine@2624

@blocknote/react

npm i https://pkg.pr.new/@blocknote/react@2624

@blocknote/server-util

npm i https://pkg.pr.new/@blocknote/server-util@2624

@blocknote/shadcn

npm i https://pkg.pr.new/@blocknote/shadcn@2624

@blocknote/xl-ai

npm i https://pkg.pr.new/@blocknote/xl-ai@2624

@blocknote/xl-docx-exporter

npm i https://pkg.pr.new/@blocknote/xl-docx-exporter@2624

@blocknote/xl-email-exporter

npm i https://pkg.pr.new/@blocknote/xl-email-exporter@2624

@blocknote/xl-multi-column

npm i https://pkg.pr.new/@blocknote/xl-multi-column@2624

@blocknote/xl-odt-exporter

npm i https://pkg.pr.new/@blocknote/xl-odt-exporter@2624

@blocknote/xl-pdf-exporter

npm i https://pkg.pr.new/@blocknote/xl-pdf-exporter@2624

commit: 7c3e17d

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant