diff --git a/packages/lexical-markdown/src/MarkdownExport.ts b/packages/lexical-markdown/src/MarkdownExport.ts index c0215cdc626..31f84ee178e 100644 --- a/packages/lexical-markdown/src/MarkdownExport.ts +++ b/packages/lexical-markdown/src/MarkdownExport.ts @@ -14,6 +14,7 @@ import type { } from '@lexical/markdown'; import type {ElementNode, LexicalNode, TextFormatType, TextNode} from 'lexical'; +import {$isLinkNode} from '@lexical/link'; import { $getRoot, $isDecoratorNode, @@ -176,7 +177,7 @@ function getTextSibling(node: TextNode, backward: boolean): TextNode | null { if (!sibling) { const parent = node.getParentOrThrow(); - if (parent.isInline()) { + if (parent.isInline() && !$isLinkNode(parent)) { sibling = backward ? parent.getPreviousSibling() : parent.getNextSibling(); @@ -185,7 +186,7 @@ function getTextSibling(node: TextNode, backward: boolean): TextNode | null { while (sibling) { if ($isElementNode(sibling)) { - if (!sibling.isInline()) { + if (!sibling.isInline() || $isLinkNode(sibling)) { break; } diff --git a/packages/lexical-markdown/src/MarkdownImport.ts b/packages/lexical-markdown/src/MarkdownImport.ts index 76e40e2ca5e..357920aa58b 100644 --- a/packages/lexical-markdown/src/MarkdownImport.ts +++ b/packages/lexical-markdown/src/MarkdownImport.ts @@ -16,7 +16,9 @@ import type { import type {LexicalNode, TextNode} from 'lexical'; import {$createCodeNode} from '@lexical/code'; +import {$createLinkNode} from '@lexical/link'; import {$isListItemNode, $isListNode, ListItemNode} from '@lexical/list'; +import {LINK} from '@lexical/markdown'; import {$isQuoteNode} from '@lexical/rich-text'; import {$findMatchingParent} from '@lexical/utils'; import { @@ -129,7 +131,7 @@ function $importBlocks( } } - importTextFormatTransformers( + $importTextFormatTransformers( textNode, textFormatTransformersIndex, textMatchTransformers, @@ -203,12 +205,46 @@ function $importCodeBlock( // E.g. for "*Hello **world**!*" string it will create text node with // "Hello **world**!" content and italic format and run recursively over // its content to transform "**world**" part -function importTextFormatTransformers( +function $importTextFormatTransformers( textNode: TextNode, textFormatTransformersIndex: TextFormatTransformersIndex, textMatchTransformers: Array, ) { const textContent = textNode.getTextContent(); + + // Look for links first. + const linkMatch = LINK.importRegExp.exec(textContent); + if (linkMatch) { + // If the link is the whole text node, then replace it and return. + // Else, split the match from previous and subsequent text nodes + // (if any) and recurse. + if (linkMatch.index === 0 && linkMatch[0].length === textContent.length) { + const [, linkText, linkUrl, linkTitle] = linkMatch; + const linkNode = $createLinkNode(linkUrl, {title: linkTitle}); + const linkTextNode = $createTextNode(linkText); + linkNode.append(linkTextNode); + textNode.replace(linkNode); + $importTextFormatTransformers( + linkTextNode, + textFormatTransformersIndex, + textMatchTransformers, + ); + } else { + const nodes = textNode.splitText( + linkMatch.index, + linkMatch.index + linkMatch[0].length, + ); + for (const node of nodes) { + $importTextFormatTransformers( + node, + textFormatTransformersIndex, + textMatchTransformers, + ); + } + } + return; + } + const match = findOutermostMatch(textContent, textFormatTransformersIndex); if (!match) { @@ -252,7 +288,7 @@ function importTextFormatTransformers( // Recursively run over inner text if it's not inline code if (!currentNode.hasFormat('code')) { - importTextFormatTransformers( + $importTextFormatTransformers( currentNode, textFormatTransformersIndex, textMatchTransformers, @@ -261,7 +297,7 @@ function importTextFormatTransformers( // Run over leading/remaining text if any if (leadingNode) { - importTextFormatTransformers( + $importTextFormatTransformers( leadingNode, textFormatTransformersIndex, textMatchTransformers, @@ -269,7 +305,7 @@ function importTextFormatTransformers( } if (remainderNode) { - importTextFormatTransformers( + $importTextFormatTransformers( remainderNode, textFormatTransformersIndex, textMatchTransformers, diff --git a/packages/lexical-markdown/src/MarkdownTransformers.ts b/packages/lexical-markdown/src/MarkdownTransformers.ts index ebc1b448dbc..438ca3c38f4 100644 --- a/packages/lexical-markdown/src/MarkdownTransformers.ts +++ b/packages/lexical-markdown/src/MarkdownTransformers.ts @@ -361,16 +361,17 @@ export const LINK: TextMatchTransformer = { return null; } const title = node.getTitle(); - const linkContent = title - ? `[${node.getTextContent()}](${node.getURL()} "${title}")` - : `[${node.getTextContent()}](${node.getURL()})`; + const linkHref = title ? `${node.getURL()} "${title}"` : node.getURL(); const firstChild = node.getFirstChild(); // Add text styles only if link has single text node inside. If it's more // then one we ignore it as markdown does not support nested styles for links if (node.getChildrenSize() === 1 && $isTextNode(firstChild)) { - return exportFormat(firstChild, linkContent); + return `[${exportFormat( + firstChild, + node.getTextContent(), + )}](${linkHref})`; } else { - return linkContent; + return `[${node.getTextContent()}](${linkHref})`; } }, importRegExp: diff --git a/packages/lexical-markdown/src/__tests__/unit/LexicalMarkdown.test.ts b/packages/lexical-markdown/src/__tests__/unit/LexicalMarkdown.test.ts index fcc3f20f075..93661828249 100644 --- a/packages/lexical-markdown/src/__tests__/unit/LexicalMarkdown.test.ts +++ b/packages/lexical-markdown/src/__tests__/unit/LexicalMarkdown.test.ts @@ -115,6 +115,18 @@ describe('Markdown', () => { html: '

Hello world

', md: '`Hello` world', }, + { + html: '

XXX world

', + md: '[`XXX`](https://lexical.dev) world', + }, + { + html: '

Hello XXX world

', + md: 'Hello [`XXX`](https://lexical.dev) world', + }, + { + html: '

Hello XXY

', + md: '`Hello` [`XXY`](https://lexical.dev)', + }, { html: '

Hello world

', md: '~~Hello~~ world',