Skip to content

[lexical-markdown] Bug Fix: nest heading inside quote when typing > # SOME HEADER#8240

Open
achaljhawar wants to merge 17 commits into
facebook:mainfrom
achaljhawar:fix/blockquote-heading-nesting
Open

[lexical-markdown] Bug Fix: nest heading inside quote when typing > # SOME HEADER#8240
achaljhawar wants to merge 17 commits into
facebook:mainfrom
achaljhawar:fix/blockquote-heading-nesting

Conversation

@achaljhawar

Copy link
Copy Markdown
Contributor

Description

Typing > # SOME HEADER created a heading but dropped the block quote. The heading transformer was replacing the QuoteNode instead of nesting inside it. Fixed createBlockNode to nest inside non-paragraph parents.

Closes #7407

Before

Lexical.Playground.10.mp4

After

Lexical.Playground.11.mp4

@vercel

vercel Bot commented Mar 19, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
lexical Ready Ready Preview, Comment Jun 10, 2026 9:06am
lexical-playground Ready Ready Preview, Comment Jun 10, 2026 9:06am

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Mar 19, 2026
@etrepum etrepum added the extended-tests Run extended e2e tests on a PR label Mar 19, 2026

@etrepum etrepum left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The current lexical document model (at least as implemented by the playground) doesn't really support blocks that contain blocks, except in cases where there's a shadow root in between or there's some special code to support it (ListItem -> ListItemNode). There might be other implications of allowing nesting here, it might be best to have this behavior as an option that defaults to false until all of the potential issues (unrelated to markdown export/import specifically) can be evaluated.

Comment thread packages/lexical-markdown/src/__tests__/unit/MarkdownTransformers.test.ts Outdated
Comment thread packages/lexical-markdown/src/MarkdownTransformers.ts Outdated

@etrepum etrepum left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is kind of an awkward way to do it, I would expect these to be distinct transformers that are exported separately and could be used to build an alternate transformers array. Ideally there would be some way to pass options down through to these transformers but that would require a larger refactor of the markdown API to use extensions.

I'm also not sure that it's worth adding this complexity just to support nesting these two types of nodes specifically, certainly not with this specific approach. There is also very likely going to be other problems to fix when using a document that nests these blocks because I believe that there are places in lexical's model that assume blocks aren't nested like this.

@achaljhawar

Copy link
Copy Markdown
Contributor Author

Hey @etrepum, I looked into the block nesting concern. Core selection/insertion algorithms find HeadingNode as the leaf block before reaching QuoteNode, so they all work correctly. The only issue is the playground toolbar showing "Quote" instead of the heading tag. Let me know if you'd rather I rework this or just close it.

@etrepum

etrepum commented Mar 28, 2026

Copy link
Copy Markdown
Collaborator

I'll take another look at this next week, my primary concern here is that it's only a partial solution because this structure isn't well supported by the editor and violates some of the assumptions about how elements can typically be nested (block nodes that are not shadow roots can typically only contain inline nodes).

@achaljhawar

Copy link
Copy Markdown
Contributor Author

Hey @etrepum, any chance you got to look at this? Happy to try a different approach.

@potatowagon

Copy link
Copy Markdown
Contributor

Review: Nest Heading Inside Quote When Typing > # SOME HEADER

Reviewed by: Navi (AI review assistant for @potatowagon)

Summary

Refactors HEADING and QUOTE transformers from static objects into factory functions ($createHeadingTransformer and $createQuoteTransformer) that accept options. Adds support for typing > # Header to create a heading nested inside a blockquote — a common Markdown pattern not previously supported.

What I Verified

  • Backward compatibility: The exported HEADING and QUOTE constants are still present (they call the factories with no options), so existing consumers are unaffected.
  • Factory pattern: $createHeadingTransformer({nestInBlockquote: true}) creates a heading transformer aware of quote context. $createQuoteTransformer({handleNestedHeadings: true}) creates a quote transformer that parses > # ... syntax.
  • Regex correctness: QUOTE_WITH_HEADING_REGEX = /^>\s(?:(#{1,6})\s)?/ correctly matches optional heading markers after the blockquote prefix.
  • Export roundtrip: When handleNestedHeadings is enabled, a QuoteNode containing a single HeadingNode exports as > # Text — proper roundtrip.
  • CI status: All tests pass across all platforms.

Concerns

  • API surface change: This changes two core transformer constants from plain objects to factory-generated objects. While backward-compatible, it shifts the architectural pattern. The default TRANSFORMERS array still uses the no-option versions, so the global behavior is unchanged.
  • Interaction complexity: Users who enable both nestInBlockquote on HEADING and handleNestedHeadings on QUOTE need to understand they work together. Could benefit from documentation.
  • Potential conflict: If a user has both the default HEADING transformer and a custom quote transformer with heading support, the heading regex might match before the quote regex gets a chance to parse > # ....

Verdict

⚠️ Needs maintainer sign-off on API direction — the refactor is correct and well-tested, but the factory pattern for core transformers is an architectural decision. The implementation quality is good. If the maintainers are okay with the pattern, it's safe to merge.

@potatowagon potatowagon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review — Nest Heading Inside Quote When Typing > # SOME HEADER

Assessment: Looks good to land

What I verified:

  1. Feature scope: Enables > # Heading markdown shortcut to create a heading nested inside a blockquote. Currently, > text creates a quote, and # text creates a heading, but the combination doesn't nest them — the heading replaces the quote.

  2. Implementation: Refactors the static HEADING transformer into a factory $createHeadingTransformer(options) with an opt-in nestInBlockquote flag. When the parent node is a QuoteNode and the flag is set, the heading appends into the quote instead of replacing it. The QUOTE transformer is similarly refactored into $createQuoteTransformer.

  3. Backward compatibility: The existing HEADING and QUOTE exports are preserved as instances of the factory with defaults — no breaking change for existing consumers.

  4. CI status: Full CI suite green (34 checks pass).

  5. Test coverage: Tests verify the nesting behavior for both import and shortcut scenarios.

— via Navi on behalf of potatowagon

…ing-nesting

# Conflicts:
#	packages/lexical-markdown/src/MarkdownTransformers.ts
#	packages/lexical-markdown/src/__tests__/unit/MarkdownTransformers.test.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. extended-tests Run extended e2e tests on a PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Markdown block quotes containing headers dropping quote

3 participants