Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 7 additions & 15 deletions packages/lexical-markdown/src/MarkdownExport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,11 @@ function exportTextFormat(
): string {
// This function handles the case of a string looking like this: " foo "
// Where it would be invalid markdown to generate: "** foo **"
// If the node has no format, we use the original text.
// Otherwise, we escape leading and trailing whitespaces to their corresponding code points,
// ensuring the returned string maintains its original formatting, e.g., "**   foo   **".
let output =
node.getFormat() === 0
? textContent
: escapeLeadingAndTrailingWhitespaces(textContent);
// We instead want to trim the whitespace out, apply formatting, and then
// bring the whitespace back. So our returned string looks like this: " **foo** "
// However, we do not want to export any formatting if the string is just whitespace: " "
const frozenString = textContent.trim();
let output = frozenString;

if (!node.hasFormat('code')) {
// Escape any markdown characters in the text content
Expand Down Expand Up @@ -288,9 +286,9 @@ function exportTextFormat(
break;
}

output = openingTags + output + closingTagsAfter;
output = output ? openingTags + output + closingTagsAfter : output;
// Replace trimmed version of textContent ensuring surrounding whitespace is not modified
return closingTagsBefore + output;
return closingTagsBefore + textContent.replace(frozenString, () => output);
}

function getTextSibling(node: TextNode, backward: boolean): TextNode | null {
Expand All @@ -309,9 +307,3 @@ function hasFormat(
): boolean {
return $isTextNode(node) && node.hasFormat(format);
}

function escapeLeadingAndTrailingWhitespaces(textContent: string) {
return textContent.replace(/^\s+|\s+$/g, (match) => {
return [...match].map((char) => '&#' + char.codePointAt(0) + ';').join('');
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -409,17 +409,14 @@ describe('Markdown', () => {
{
html: '<p><b><strong style="white-space: pre-wrap;">Hello </strong></b><s><b><strong style="white-space: pre-wrap;">world</strong></b></s><span style="white-space: pre-wrap;">!</span></p>',
md: '**Hello ~~world~~**!',
mdAfterExport: '**Hello&#32;~~world~~**!',
},
{
html: '<p><s><b><strong style="white-space: pre-wrap;">Hello </strong></b></s><s><i><b><strong style="white-space: pre-wrap;">world</strong></b></i></s><s><span style="white-space: pre-wrap;">!</span></s></p>',
md: '**~~Hello *world*~~**~~!~~',
mdAfterExport: '**~~Hello&#32;*world*~~**~~!~~',
},
{
html: '<p><i><em style="white-space: pre-wrap;">Hello </em></i><i><b><strong style="white-space: pre-wrap;">world</strong></b></i><i><em style="white-space: pre-wrap;">!</em></i></p>',
md: '*Hello **world**!*',
mdAfterExport: '*Hello&#32;**world**!*',
},
{
html: '<p><span style="white-space: pre-wrap;">hello world</span></p>',
Expand Down Expand Up @@ -602,28 +599,24 @@ describe('Markdown', () => {
md: '**Bold** `[text](https://lexical.dev)` **Bold 3**',
},
{
html: '<p><span style="white-space: pre-wrap;">Text </span><b><strong style="white-space: pre-wrap;">boldstart </strong></b><a href="https://lexical.dev"><b><strong style="white-space: pre-wrap;">text</strong></b></a><b><strong style="white-space: pre-wrap;"> boldend</strong></b><span style="white-space: pre-wrap;"> text</span></p>',
md: 'Text **boldstart [text](https://lexical.dev) boldend** text',
mdAfterExport:
'Text **boldstart&#32;**[**text**](https://lexical.dev)**&#32;boldend** text',
html: '<p><span style="white-space: pre-wrap;">Text </span><b><strong style="white-space: pre-wrap;">boldstart</strong></b><span style="white-space: pre-wrap;"> </span><a href="https://lexical.dev"><b><strong style="white-space: pre-wrap;">text</strong></b></a><span style="white-space: pre-wrap;"> </span><b><strong style="white-space: pre-wrap;">boldend</strong></b><span style="white-space: pre-wrap;"> text</span></p>',
md: 'Text **boldstart** [**text**](https://lexical.dev) **boldend** text',
},
{
html: '<p><span style="white-space: pre-wrap;">Text </span><b><strong style="white-space: pre-wrap;">boldstart </strong></b><a href="https://lexical.dev"><b><code spellcheck="false" style="white-space: pre-wrap;"><strong>text</strong></code></b></a><b><strong style="white-space: pre-wrap;"> boldend</strong></b><span style="white-space: pre-wrap;"> text</span></p>',
md: 'Text **boldstart [`text`](https://lexical.dev) boldend** text',
mdAfterExport:
'Text **boldstart&#32;**[**`text`**](https://lexical.dev)**&#32;boldend** text',
html: '<p><span style="white-space: pre-wrap;">Text </span><b><strong style="white-space: pre-wrap;">boldstart</strong></b><span style="white-space: pre-wrap;"> </span><a href="https://lexical.dev"><b><code spellcheck="false" style="white-space: pre-wrap;"><strong>text</strong></code></b></a><span style="white-space: pre-wrap;"> </span><b><strong style="white-space: pre-wrap;">boldend</strong></b><span style="white-space: pre-wrap;"> text</span></p>',
md: 'Text **boldstart** [**`text`**](https://lexical.dev) **boldend** text',
},
{
html: '<p><span style="white-space: pre-wrap;">It </span><s><i><b><strong style="white-space: pre-wrap;">works </strong></b></i></s><a href="https://lexical.io"><s><i><b><strong style="white-space: pre-wrap;">with links</strong></b></i></s></a><span style="white-space: pre-wrap;"> too</span></p>',
md: 'It ~~___works [with links](https://lexical.io)___~~ too',
html: '<p><span style="white-space: pre-wrap;">It </span><s><i><b><strong style="white-space: pre-wrap;">works</strong></b></i></s><span style="white-space: pre-wrap;"> </span><a href="https://lexical.io"><s><i><b><strong style="white-space: pre-wrap;">with links</strong></b></i></s></a><span style="white-space: pre-wrap;"> too</span></p>',
md: 'It ~~___works___~~ [~~___with links___~~](https://lexical.io) too',
mdAfterExport:
'It ***~~works&#32;~~***[***~~with links~~***](https://lexical.io) too',
'It ***~~works~~*** [***~~with links~~***](https://lexical.io) too',
},
{
html: '<p><span style="white-space: pre-wrap;">It </span><s><i><b><strong style="white-space: pre-wrap;">works </strong></b></i></s><a href="https://lexical.io"><s><i><b><strong style="white-space: pre-wrap;">with links</strong></b></i></s></a><s><i><b><strong style="white-space: pre-wrap;"> too</strong></b></i></s><span style="white-space: pre-wrap;">!</span></p>',
md: 'It ~~___works [with links](https://lexical.io) too___~~!',
html: '<p><span style="white-space: pre-wrap;">It </span><s><i><b><strong style="white-space: pre-wrap;">works</strong></b></i></s><span style="white-space: pre-wrap;"> </span><a href="https://lexical.io"><s><i><b><strong style="white-space: pre-wrap;">with links</strong></b></i></s></a><span style="white-space: pre-wrap;"> </span><s><i><b><strong style="white-space: pre-wrap;">too</strong></b></i></s><span style="white-space: pre-wrap;">!</span></p>',
md: 'It ~~___works___~~ [~~___with links___~~](https://lexical.io) ~~___too___~~!',
mdAfterExport:
'It ***~~works&#32;~~***[***~~with links~~***](https://lexical.io)***~~&#32;too~~***!',
'It ***~~works~~*** [***~~with links~~***](https://lexical.io) ***~~too~~***!',
},
{
html: '<p><a href="https://lexical.dev"><span style="white-space: pre-wrap;">link</span></a><a href="https://lexical.dev"><span style="white-space: pre-wrap;">link2</span></a></p>',
Expand Down Expand Up @@ -688,10 +681,6 @@ describe('Markdown', () => {
html: '<p><span style="white-space: pre-wrap;">*Hello* world</span></p>',
md: '\\*Hello\\* world',
},
{
html: '<p><b><strong style="white-space: pre-wrap;">&nbsp;</strong></b></p>',
md: '**&#160;**',
},
{
html: '<p><a href="https://lexical.dev"><span style="white-space: pre-wrap;">[h]ello</span></a><a href="https://lexical.dev"><span style="white-space: pre-wrap;">h[e]llo</span></a></p>',
md: '[[h]ello](https://lexical.dev)[h[e]llo](https://lexical.dev)',
Expand Down Expand Up @@ -720,7 +709,7 @@ describe('Markdown', () => {
{
html: '<p><b><strong style="white-space: pre-wrap;">text </strong></b><a href="https://lexical.dev"><b><strong style="white-space: pre-wrap;">link</strong></b></a></p>',
md: '**text [link](https://lexical.dev)**',
mdAfterExport: '**text&#32;**[**link**](https://lexical.dev)',
mdAfterExport: '**text** [**link**](https://lexical.dev)',
},
{
html: '<p><i><em style="white-space: pre-wrap;">text</em></i><i><b><strong style="white-space: pre-wrap;">text</strong></b></i></p>',
Expand All @@ -734,7 +723,7 @@ describe('Markdown', () => {
{
html: '<p><b><strong style="white-space: pre-wrap;">foo </strong></b><a href="/url"><i><b><strong style="white-space: pre-wrap;">bar</strong></b></i></a></p>',
md: '**foo [*bar*](/url)**',
mdAfterExport: '**foo&#32;**[***bar***](/url)',
mdAfterExport: '**foo** [***bar***](/url)',
},
{
html: '<p><span style="white-space: pre-wrap;">*foo </span><i><em style="white-space: pre-wrap;">bar baz</em></i></p>',
Expand All @@ -744,13 +733,18 @@ describe('Markdown', () => {
{
html: '<p><i><em style="white-space: pre-wrap;">a </em></i><i><code spellcheck="false" style="white-space: pre-wrap;"><em>*</em></code></i><i><em style="white-space: pre-wrap;"> b </em></i><i><code spellcheck="false" style="white-space: pre-wrap;"><em>x</em></code></i></p>',
md: '*a `*` b `x`*',
mdAfterExport: '*a&#32;`*`&#32;b&#32;`x`*',
mdAfterExport: '*a `*` b `x`*',
},
{
html: '<p><span style="white-space: pre-wrap;">_foo_bar</span></p>',
md: '_foo_bar',
mdAfterExport: '\\_foo\\_bar',
},
{
html: '<p><b><strong style="white-space: pre-wrap;"> </strong></b></p>',
md: ' ',
skipImport: true,
},
];

for (const {
Expand Down
6 changes: 1 addition & 5 deletions packages/lexical-markdown/src/importTextTransformers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,6 @@ export function importTextTransformers(

// Handle escape characters
const textContent = textNode.getTextContent();
const escapedText = textContent
.replace(/\\([*_`~\\])/g, '$1')
.replace(/&#(\d+);/g, (_, codePoint) => {
return String.fromCodePoint(codePoint);
});
const escapedText = textContent.replace(/\\([*_`~\\])/g, '$1');
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not the cause of the current failure but this change may make it parse previously generated markdown incorrectly

Copy link
Author

Choose a reason for hiding this comment

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

That's a good point. I'll restore this.

textNode.setTextContent(escapedText);
}
Loading