@@ -254,89 +254,115 @@ function renderBlock(block, index, rawDraftObject, options) {
254254 }
255255 }
256256
257- // Render text within content, along with any inline styles/entities
258- Array . from ( block . text ) . some ( function ( character , characterIndex ) {
259- // Close any entity tags that need closing
260- block . entityRanges . forEach ( function ( range , rangeIndex ) {
261- if ( range . offset + range . length === characterIndex ) {
262- var entity = rawDraftObject . entityMap [ range . key ] ;
263- if ( customEntityItems [ entity . type ] || EntityItems [ entity . type ] ) {
264- markdownString += ( customEntityItems [ entity . type ] || EntityItems [ entity . type ] ) . close ( entity ) ;
265- }
257+ // A stack to keep track of open tags
258+ var openTags = [ ] ;
259+
260+ function openTag ( tag ) {
261+ openTags . push ( tag ) ;
262+
263+ if ( tag . style ) {
264+ // Open inline tag
265+
266+ if ( customStyleItems [ tag . style ] || StyleItems [ tag . style ] ) {
267+ var styleToAdd = (
268+ customStyleItems [ tag . style ] || StyleItems [ tag . style ]
269+ ) . open ( ) ;
270+ markdownToAdd . push ( {
271+ type : 'style' ,
272+ style : tag ,
273+ value : styleToAdd
274+ } ) ;
266275 }
267- } ) ;
276+ } else {
277+ // Open entity tag
268278
269- // Close any inline tags that need closing
270- openInlineStyles . forEach ( function ( style , styleIndex ) {
271- if ( style . offset + style . length === characterIndex ) {
272- if ( ( customStyleItems [ style . style ] || StyleItems [ style . style ] ) ) {
273- var styleIndex = openInlineStyles . indexOf ( style ) ;
274- // Handle nested case - close any open inline styles before closing the parent
275- if ( styleIndex > - 1 && styleIndex !== openInlineStyles . length - 1 ) {
276- for ( var i = openInlineStyles . length - 1 ; i !== styleIndex ; i -- ) {
277- var styleItem = ( customStyleItems [ openInlineStyles [ i ] . style ] || StyleItems [ openInlineStyles [ i ] . style ] ) ;
278- if ( styleItem ) {
279- var trailingWhitespace = TRAILING_WHITESPACE . exec ( markdownString ) ;
280- markdownString = markdownString . slice ( 0 , markdownString . length - trailingWhitespace [ 0 ] . length ) ;
281- markdownString += styleItem . close ( ) ;
282- markdownString += trailingWhitespace [ 0 ] ;
283- }
284- }
285- }
279+ var entity = rawDraftObject . entityMap [ tag . key ] ;
280+ if ( customEntityItems [ entity . type ] || EntityItems [ entity . type ] ) {
281+ var entityToAdd = (
282+ customEntityItems [ entity . type ] || EntityItems [ entity . type ]
283+ ) . open ( entity ) ;
284+ markdownToAdd . push ( {
285+ type : 'entity' ,
286+ value : entityToAdd
287+ } ) ;
288+ }
289+ }
290+ }
286291
287- // Close the actual inline style being closed
288- // Have to trim whitespace first and then re-add after because markdown can't handle leading/trailing whitespace
289- var trailingWhitespace = TRAILING_WHITESPACE . exec ( markdownString ) ;
290- markdownString = markdownString . slice ( 0 , markdownString . length - trailingWhitespace [ 0 ] . length ) ;
291-
292- markdownString += ( customStyleItems [ style . style ] || StyleItems [ style . style ] ) . close ( ) ;
293- markdownString += trailingWhitespace [ 0 ] ;
294-
295- // Handle nested case - reopen any inline styles after closing the parent
296- if ( styleIndex > - 1 && styleIndex !== openInlineStyles . length - 1 ) {
297- for ( var i = openInlineStyles . length - 1 ; i !== styleIndex ; i -- ) {
298- var styleItem = ( customStyleItems [ openInlineStyles [ i ] . style ] || StyleItems [ openInlineStyles [ i ] . style ] ) ;
299- if ( styleItem && openInlineStyles [ i ] . offset + openInlineStyles [ i ] . length > characterIndex ) {
300- markdownString += styleItem . open ( ) ;
301- } else {
302- openInlineStyles . splice ( i , 1 ) ;
303- }
304- }
305- }
292+ function closeTag ( tag ) {
293+ const popped = openTags . pop ( ) ;
294+ if ( tag !== popped ) {
295+ throw new Error (
296+ 'Invariant violation: Cannot close a tag before all inner tags have been closed'
297+ ) ;
298+ }
306299
307- openInlineStyles . splice ( styleIndex , 1 ) ;
308- }
300+ if ( tag . style ) {
301+ // Close inline tag
302+
303+ if ( customStyleItems [ tag . style ] || StyleItems [ tag . style ] ) {
304+ // Have to trim whitespace first and then re-add after because markdown can't handle leading/trailing whitespace
305+ var trailingWhitespace = TRAILING_WHITESPACE . exec ( markdownString ) ;
306+ markdownString = markdownString . slice (
307+ 0 ,
308+ markdownString . length - trailingWhitespace [ 0 ] . length
309+ ) ;
310+
311+ markdownString += (
312+ customStyleItems [ tag . style ] || StyleItems [ tag . style ]
313+ ) . close ( ) ;
314+ markdownString += trailingWhitespace [ 0 ] ;
309315 }
310- } ) ;
316+ } else {
317+ // Close entity tag
311318
312- // Open any inline tags that need opening
313- block . inlineStyleRanges . forEach ( function ( style , styleIndex ) {
314- if ( style . offset === characterIndex ) {
315- if ( ( customStyleItems [ style . style ] || StyleItems [ style . style ] ) ) {
316- var styleToAdd = ( customStyleItems [ style . style ] || StyleItems [ style . style ] ) . open ( ) ;
317- markdownToAdd . push ( {
318- type : 'style' ,
319- style : style ,
320- value : styleToAdd
321- } ) ;
322- }
319+ var entity = rawDraftObject . entityMap [ tag . key ] ;
320+ if ( customEntityItems [ entity . type ] || EntityItems [ entity . type ] ) {
321+ markdownString += (
322+ customEntityItems [ entity . type ] || EntityItems [ entity . type ]
323+ ) . close ( entity ) ;
323324 }
324- } ) ;
325+ }
326+ }
325327
326- // Open any entity tags that need opening
327- block . entityRanges . forEach ( function ( range , rangeIndex ) {
328- if ( range . offset === characterIndex ) {
329- var entity = rawDraftObject . entityMap [ range . key ] ;
330- if ( customEntityItems [ entity . type ] || EntityItems [ entity . type ] ) {
331- var entityToAdd = ( customEntityItems [ entity . type ] || EntityItems [ entity . type ] ) . open ( entity ) ;
332- markdownToAdd . push ( {
333- type : 'entity' ,
334- value : entityToAdd
335- } ) ;
336- }
328+ const compareTagsLastCloseFirst = ( a , b ) =>
329+ b . offset + b . length - ( a . offset + a . length ) ;
330+
331+ // reverse array without mutating the original
332+ const reverse = ( array ) => array . concat ( ) . reverse ( ) ;
333+
334+ // Render text within content, along with any inline styles/entities
335+ Array . from ( block . text ) . some ( function ( character , characterIndex ) {
336+ // Close any tags that need closing, starting from top of the stack
337+ reverse ( openTags ) . forEach ( function ( tag ) {
338+ if ( tag . offset + tag . length === characterIndex ) {
339+ // Take all tags stacked on top of the current one, meaning they opened after it.
340+ // Since they have not been popped, they'll close only later. So we need to split them.
341+ var tagsToSplit = openTags . slice ( openTags . indexOf ( tag ) + 1 ) ;
342+
343+ // Close in reverse order as they were opened
344+ reverse ( tagsToSplit ) . forEach ( closeTag ) ;
345+
346+ // Now we can close the current tag
347+ closeTag ( tag ) ;
348+
349+ // Reopen split tags, ordered so that tags that close last open first
350+ tagsToSplit . sort ( compareTagsLastCloseFirst ) . forEach ( openTag ) ;
337351 }
338352 } ) ;
339353
354+ // Open any tags that need opening, using the correct nesting order.
355+ var inlineTagsToOpen = block . inlineStyleRanges . filter (
356+ ( tag ) => tag . offset === characterIndex
357+ ) ;
358+ var entityTagsToOpen = block . entityRanges . filter (
359+ ( tag ) => tag . offset === characterIndex
360+ ) ;
361+ inlineTagsToOpen
362+ . concat ( entityTagsToOpen )
363+ . sort ( compareTagsLastCloseFirst )
364+ . forEach ( openTag ) ;
365+
340366 // These are all the opening entity and style types being added to the markdown string for this loop
341367 // we store in an array and add here because if the character is WS character, we want to hang onto it and not apply it until the next non-whitespace
342368 // character before adding the markdown, since markdown doesn’t play nice with leading whitespace (eg '** bold**' is no good, whereas ' **bold**' is good.)
@@ -345,18 +371,11 @@ function renderBlock(block, index, rawDraftObject, options) {
345371 return item . value ;
346372 } ) . join ( '' ) ;
347373
348- markdownToAdd . forEach ( function ( item ) {
349- if ( item . type === 'style' ) {
350- // We hang on to this because we may need to close it early and then re-open if there are nested styles being opened and closed.
351- openInlineStyles . push ( item . style ) ;
352- }
353- } ) ;
354-
355374 markdownToAdd = [ ] ;
356375 }
357376
358377 if ( block . type !== 'code-block' && escapeMarkdownCharacters ) {
359- let insideInlineCodeStyle = openInlineStyles . find ( ( style ) => style . style === 'CODE' ) ;
378+ let insideInlineCodeStyle = openTags . find ( ( style ) => style . style === 'CODE' ) ;
360379
361380 if ( insideInlineCodeStyle ) {
362381 // Todo - The syntax to escape backtics when inside backtic code already is to use MORE backtics wrapping.
@@ -434,23 +453,8 @@ function renderBlock(block, index, rawDraftObject, options) {
434453 }
435454 } ) ;
436455
437- // Close any remaining entity tags
438- block . entityRanges . forEach ( function ( range , rangeIndex ) {
439- if ( range . offset + range . length === Array . from ( block . text ) . length ) {
440- var entity = rawDraftObject . entityMap [ range . key ] ;
441- if ( customEntityItems [ entity . type ] || EntityItems [ entity . type ] ) {
442- markdownString += ( customEntityItems [ entity . type ] || EntityItems [ entity . type ] ) . close ( entity ) ;
443- }
444- }
445- } ) ;
446-
447- // Close any remaining inline tags (if an inline tag ends at the very last char, we won't catch it inside the loop)
448- openInlineStyles . reverse ( ) . forEach ( function ( style ) {
449- var trailingWhitespace = TRAILING_WHITESPACE . exec ( markdownString ) ;
450- markdownString = markdownString . slice ( 0 , markdownString . length - trailingWhitespace [ 0 ] . length ) ;
451- markdownString += ( customStyleItems [ style . style ] || StyleItems [ style . style ] ) . close ( ) ;
452- markdownString += trailingWhitespace [ 0 ] ;
453- } ) ;
456+ // Finally, close all remaining open tags
457+ reverse ( openTags ) . forEach ( closeTag ) ;
454458
455459 // Close block level item
456460 if ( customStyleItems [ type ] || StyleItems [ type ] ) {
0 commit comments