Skip to content

Commit ddd0903

Browse files
yhw5yhw5etrepum
authored
[lexical-markdown] Bug Fix: preserve the order of markdown tags for markdown combinations, and close the tags when the outmost tag is closed (#5758)
Co-authored-by: yhw5 <[email protected]> Co-authored-by: Bob Ippolito <[email protected]>
1 parent bf44530 commit ddd0903

File tree

2 files changed

+55
-11
lines changed

2 files changed

+55
-11
lines changed

packages/lexical-markdown/src/MarkdownExport.ts

+46-10
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ function exportChildren(
108108
): string {
109109
const output = [];
110110
const children = node.getChildren();
111+
// keep track of unclosed tags from the very beginning
112+
const unclosedTags: {format: TextFormatType; tag: string}[] = [];
111113

112114
mainLoop: for (const child of children) {
113115
for (const transformer of textMatchTransformers) {
@@ -124,7 +126,12 @@ function exportChildren(
124126
textMatchTransformers,
125127
),
126128
(textNode, textContent) =>
127-
exportTextFormat(textNode, textContent, textTransformersIndex),
129+
exportTextFormat(
130+
textNode,
131+
textContent,
132+
textTransformersIndex,
133+
unclosedTags,
134+
),
128135
);
129136

130137
if (result != null) {
@@ -137,7 +144,12 @@ function exportChildren(
137144
output.push('\n');
138145
} else if ($isTextNode(child)) {
139146
output.push(
140-
exportTextFormat(child, child.getTextContent(), textTransformersIndex),
147+
exportTextFormat(
148+
child,
149+
child.getTextContent(),
150+
textTransformersIndex,
151+
unclosedTags,
152+
),
141153
);
142154
} else if ($isElementNode(child)) {
143155
// empty paragraph returns ""
@@ -156,39 +168,63 @@ function exportTextFormat(
156168
node: TextNode,
157169
textContent: string,
158170
textTransformers: Array<TextFormatTransformer>,
171+
// unclosed tags include the markdown tags that haven't been closed yet, and their associated formats
172+
unclosedTags: Array<{format: TextFormatType; tag: string}>,
159173
): string {
160174
// This function handles the case of a string looking like this: " foo "
161175
// Where it would be invalid markdown to generate: "** foo **"
162176
// We instead want to trim the whitespace out, apply formatting, and then
163177
// bring the whitespace back. So our returned string looks like this: " **foo** "
164178
const frozenString = textContent.trim();
165179
let output = frozenString;
180+
// the opening tags to be added to the result
181+
let openingTags = '';
182+
// the closing tags to be added to the result
183+
let closingTags = '';
184+
185+
const prevNode = getTextSibling(node, true);
186+
const nextNode = getTextSibling(node, false);
166187

167188
const applied = new Set();
168189

169190
for (const transformer of textTransformers) {
170191
const format = transformer.format[0];
171192
const tag = transformer.tag;
172193

194+
// dedup applied formats
173195
if (hasFormat(node, format) && !applied.has(format)) {
174196
// Multiple tags might be used for the same format (*, _)
175197
applied.add(format);
176-
// Prevent adding opening tag is already opened by the previous sibling
177-
const previousNode = getTextSibling(node, true);
178198

179-
if (!hasFormat(previousNode, format)) {
180-
output = tag + output;
199+
// append the tag to openningTags, if it's not applied to the previous nodes,
200+
// or the nodes before that (which would result in an unclosed tag)
201+
if (
202+
!hasFormat(prevNode, format) ||
203+
!unclosedTags.find((element) => element.tag === tag)
204+
) {
205+
unclosedTags.push({format, tag});
206+
openingTags += tag;
181207
}
208+
}
209+
}
182210

183-
// Prevent adding closing tag if next sibling will do it
184-
const nextNode = getTextSibling(node, false);
211+
// close any tags in the same order they were applied, if necessary
212+
for (let i = 0; i < unclosedTags.length; i++) {
213+
// prevent adding closing tag if next sibling will do it
214+
if (hasFormat(nextNode, unclosedTags[i].format)) {
215+
continue;
216+
}
185217

186-
if (!hasFormat(nextNode, format)) {
187-
output += tag;
218+
while (unclosedTags.length > i) {
219+
const unclosedTag = unclosedTags.pop();
220+
if (unclosedTag && typeof unclosedTag.tag === 'string') {
221+
closingTags += unclosedTag.tag;
188222
}
189223
}
224+
break;
190225
}
191226

227+
output = openingTags + output + closingTags;
192228
// Replace trimmed version of textContent ensuring surrounding whitespace is not modified
193229
return textContent.replace(frozenString, () => output);
194230
}

packages/lexical-markdown/src/__tests__/unit/LexicalMarkdown.test.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,15 @@ describe('Markdown', () => {
354354
},
355355
{
356356
html: '<p><span style="white-space: pre-wrap;">Hello </span><s><i><b><strong style="white-space: pre-wrap;">world</strong></b></i></s><span style="white-space: pre-wrap;">!</span></p>',
357-
md: 'Hello ~~***world***~~!',
357+
md: 'Hello ***~~world~~***!',
358+
},
359+
{
360+
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>',
361+
md: '**Hello ~~world~~**!',
362+
},
363+
{
364+
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>',
365+
md: '**~~Hello *world*~~**~~!~~',
358366
},
359367
{
360368
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>',

0 commit comments

Comments
 (0)