diff --git a/packages/lexical-markdown/src/MarkdownTransformers.ts b/packages/lexical-markdown/src/MarkdownTransformers.ts index 0fc245c81b5..994b691bd4c 100644 --- a/packages/lexical-markdown/src/MarkdownTransformers.ts +++ b/packages/lexical-markdown/src/MarkdownTransformers.ts @@ -222,6 +222,7 @@ const ORDERED_LIST_REGEX = /^(\s*)(\d{1,})\.\s/; const UNORDERED_LIST_REGEX = /^(\s*)[-*+]\s/; const CHECK_LIST_REGEX = /^(\s*)(?:[-*+]\s)?\s?(\[(\s|x)?\])\s/i; const HEADING_REGEX = /^(#{1,6})\s/; +const HEADING_CLOSING_SEQUENCE_REGEX = /(?:^|[ \t]+)#+[ \t]*$/; const QUOTE_REGEX = /^>\s/; const CODE_START_REGEX = /^([ \t]*`{3,})([\w-]+)?[ \t]?/; const CODE_END_REGEX = /^[ \t]*`{3,}$/; @@ -807,6 +808,20 @@ export const HEADING: ElementTransformer = { if (!isImport && $isUnreplaceableBlock(parentNode)) { return false; } + if (isImport) { + // An ATX heading may end with a closing sequence of `#`s, which is not + // part of the content: `## Hello ##` is a heading reading "Hello". + // The sequence must be preceded by a space or tab (or be the whole + // content); `# C#` keeps its `#`. + const lastChild = children[children.length - 1]; + if ($isTextNode(lastChild)) { + const text = lastChild.getTextContent(); + const stripped = text.replace(HEADING_CLOSING_SEQUENCE_REGEX, ''); + if (stripped !== text) { + lastChild.setTextContent(stripped); + } + } + } return $replaceWithHeading(parentNode, children, match, isImport); }, triggerOnEnter: true, diff --git a/packages/lexical-markdown/src/__tests__/unit/LexicalMarkdown.test.ts b/packages/lexical-markdown/src/__tests__/unit/LexicalMarkdown.test.ts index b7b6051268f..08123f5134a 100644 --- a/packages/lexical-markdown/src/__tests__/unit/LexicalMarkdown.test.ts +++ b/packages/lexical-markdown/src/__tests__/unit/LexicalMarkdown.test.ts @@ -342,6 +342,23 @@ describe('Markdown', () => { html: '
Hello world
', md: '###### Hello world', }, + { + // ATX closing sequence: https://spec.commonmark.org/0.31.2/#example-71 + html: '

Hello world

', + md: '## Hello world ##', + skipExport: true, + }, + { + // The closing sequence may be any length and followed by spaces + html: '

Hello world

', + md: '# Hello world ##### ', + skipExport: true, + }, + { + // A `#` not preceded by a space is content, not a closing sequence + html: '

Hello C#

', + md: '# Hello C#', + }, { // Multiline paragraphs: https://spec.commonmark.org/dingus/?text=Hello%0Aworld%0A! html: '

Hello world !

',