diff --git a/packages/lexical-markdown/src/MarkdownImport.ts b/packages/lexical-markdown/src/MarkdownImport.ts index cc7bd324294..11a30adb23c 100644 --- a/packages/lexical-markdown/src/MarkdownImport.ts +++ b/packages/lexical-markdown/src/MarkdownImport.ts @@ -20,12 +20,15 @@ import {$findMatchingParent} from '@lexical/utils'; import { $createLineBreakNode, $createParagraphNode, + $createTabNode, $createTextNode, $getRoot, $getSelection, + $isElementNode, $isParagraphNode, $isTextNode, ElementNode, + TextNode, } from 'lexical'; import {importTextTransformers} from './importTextTransformers'; @@ -84,17 +87,24 @@ export function createMarkdownImport( ); } - // By default, removing empty paragraphs as md does not really - // allow empty lines and uses them as delimiter. - // If you need empty lines set shouldPreserveNewLines = true. const children = root.getChildren(); for (const child of children) { + // By default, removing empty paragraphs as md does not really + // allow empty lines and uses them as delimiter. + // If you need empty lines set shouldPreserveNewLines = true. if ( !shouldPreserveNewLines && isEmptyParagraph(child) && root.getChildrenSize() > 1 ) { child.remove(); + continue; + } + // Convert all '\t' into TabNode. + if ($isElementNode(child)) { + for (const textNode of child.getAllTextNodes()) { + $normalizeMarkdownTextNode(textNode); + } } } @@ -291,6 +301,28 @@ function $importBlocks( } } +// Look in node for '\t' and create a TabNode for each occurrence. +function $normalizeMarkdownTextNode(textNode: TextNode): void { + const tabOffsets: Set = new Set(); + const text = textNode.getTextContent(); + let index = text.indexOf('\t'); + + // Find all tab occurrences + while (index !== -1) { + tabOffsets.add(index); + tabOffsets.add(index + 1); + index = text.indexOf('\t', index + 1); + } + + // Split node to isolate each tab then replace '\t' into TabNode + const splitNodes = textNode.splitText(...tabOffsets); + splitNodes.forEach((node) => { + if (node.getTextContent() === '\t') { + node.replace($createTabNode()); + } + }); +} + function createTextFormatTransformersIndex( textTransformers: Array, ): TextFormatTransformersIndex { diff --git a/packages/lexical-markdown/src/__tests__/unit/LexicalMarkdown.test.ts b/packages/lexical-markdown/src/__tests__/unit/LexicalMarkdown.test.ts index e7e2991611c..d9d6879c9dd 100644 --- a/packages/lexical-markdown/src/__tests__/unit/LexicalMarkdown.test.ts +++ b/packages/lexical-markdown/src/__tests__/unit/LexicalMarkdown.test.ts @@ -781,6 +781,21 @@ describe('Markdown', () => { md: 'foo\\\nbar', mdAfterExport: 'foo\nbar', }, + { + html: '

foo bar\tbaz

', + md: 'foo [bar](https://lexical.dev)\tbaz', + skipExport: true, + }, + { + html: '

foo\t \tbar
\tbaz

', + md: 'foo\t \tbar\n\tbaz', + skipExport: true, + }, + { + html: '

Hello\t\tWorld

', + md: 'Hello\t\tWorld', + skipExport: true, + }, ]; for (const {