@@ -108,6 +108,8 @@ function exportChildren(
108
108
) : string {
109
109
const output = [ ] ;
110
110
const children = node . getChildren ( ) ;
111
+ // keep track of unclosed tags from the very beginning
112
+ const unclosedTags : { format : TextFormatType ; tag : string } [ ] = [ ] ;
111
113
112
114
mainLoop: for ( const child of children ) {
113
115
for ( const transformer of textMatchTransformers ) {
@@ -124,7 +126,12 @@ function exportChildren(
124
126
textMatchTransformers ,
125
127
) ,
126
128
( textNode , textContent ) =>
127
- exportTextFormat ( textNode , textContent , textTransformersIndex ) ,
129
+ exportTextFormat (
130
+ textNode ,
131
+ textContent ,
132
+ textTransformersIndex ,
133
+ unclosedTags ,
134
+ ) ,
128
135
) ;
129
136
130
137
if ( result != null ) {
@@ -137,7 +144,12 @@ function exportChildren(
137
144
output . push ( '\n' ) ;
138
145
} else if ( $isTextNode ( child ) ) {
139
146
output . push (
140
- exportTextFormat ( child , child . getTextContent ( ) , textTransformersIndex ) ,
147
+ exportTextFormat (
148
+ child ,
149
+ child . getTextContent ( ) ,
150
+ textTransformersIndex ,
151
+ unclosedTags ,
152
+ ) ,
141
153
) ;
142
154
} else if ( $isElementNode ( child ) ) {
143
155
// empty paragraph returns ""
@@ -156,39 +168,63 @@ function exportTextFormat(
156
168
node : TextNode ,
157
169
textContent : string ,
158
170
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 } > ,
159
173
) : string {
160
174
// This function handles the case of a string looking like this: " foo "
161
175
// Where it would be invalid markdown to generate: "** foo **"
162
176
// We instead want to trim the whitespace out, apply formatting, and then
163
177
// bring the whitespace back. So our returned string looks like this: " **foo** "
164
178
const frozenString = textContent . trim ( ) ;
165
179
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 ) ;
166
187
167
188
const applied = new Set ( ) ;
168
189
169
190
for ( const transformer of textTransformers ) {
170
191
const format = transformer . format [ 0 ] ;
171
192
const tag = transformer . tag ;
172
193
194
+ // dedup applied formats
173
195
if ( hasFormat ( node , format ) && ! applied . has ( format ) ) {
174
196
// Multiple tags might be used for the same format (*, _)
175
197
applied . add ( format ) ;
176
- // Prevent adding opening tag is already opened by the previous sibling
177
- const previousNode = getTextSibling ( node , true ) ;
178
198
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 ;
181
207
}
208
+ }
209
+ }
182
210
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
+ }
185
217
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 ;
188
222
}
189
223
}
224
+ break ;
190
225
}
191
226
227
+ output = openingTags + output + closingTags ;
192
228
// Replace trimmed version of textContent ensuring surrounding whitespace is not modified
193
229
return textContent . replace ( frozenString , ( ) => output ) ;
194
230
}
0 commit comments