@@ -255,23 +255,52 @@ function deltaToMdast(delta) {
255255 const text = op . insert ;
256256 const attributes = op . attributes || { } ;
257257
258+ // Handle newlines within text content
259+ if ( text . includes ( "\n" ) && text !== "\n" ) {
260+ const lines = text . split ( "\n" ) ;
261+
262+ // Process all lines except the last one as complete lines
263+ for ( let i = 0 ; i < lines . length - 1 ; i ++ ) {
264+ const line = lines [ i ] ;
265+ if ( line . length > 0 ) {
266+ // Add text to current paragraph
267+ if ( ! currentParagraph ) {
268+ currentParagraph = createParagraphNode ( ) ;
269+ }
270+ const nodes = createTextNodes ( line , attributes ) ;
271+ currentParagraph . children . push ( ...nodes ) ;
272+ textBuffer = line ;
273+ }
274+
275+ // Process line break with empty attributes (regular paragraph break)
276+ processLineBreak ( mdast , currentParagraph , { } , textBuffer , currentList ) ;
277+ currentParagraph = null ;
278+ textBuffer = "" ;
279+ }
280+
281+ // Add the last line to the buffer without processing the line break yet
282+ const lastLine = lines [ lines . length - 1 ] ;
283+ if ( lastLine . length > 0 ) {
284+ if ( ! currentParagraph ) {
285+ currentParagraph = createParagraphNode ( ) ;
286+ }
287+ const nodes = createTextNodes ( lastLine , attributes ) ;
288+ currentParagraph . children . push ( ...nodes ) ;
289+ textBuffer = lastLine ;
290+ }
291+
292+ continue ;
293+ }
294+
258295 if ( text === "\n" ) {
259- processLineBreak (
296+ currentList = processLineBreak (
260297 mdast ,
261298 currentParagraph ,
262299 attributes ,
263300 textBuffer ,
264- currentList ,
301+ currentList
265302 ) ;
266- if (
267- ! attributes . list &&
268- ! attributes . blockquote &&
269- ! attributes [ "code-block" ] &&
270- ! attributes . header
271- ) {
272- currentList = null ;
273- }
274-
303+
275304 // Reset paragraph and buffer after processing line break
276305 currentParagraph = null ;
277306 textBuffer = "" ;
@@ -391,7 +420,7 @@ function wrapNodesWith(children, type) {
391420 * @param {Object } attributes - The attributes for the line.
392421 * @param {string } textBuffer - The text buffer for the current line.
393422 * @param {MdastNode|null } currentList - The current list being built.
394- * @returns {void }
423+ * @returns {MdastNode|null } - The updated current list.
395424 */
396425function processLineBreak (
397426 mdast ,
@@ -401,39 +430,58 @@ function processLineBreak(
401430 currentList ,
402431) {
403432 if ( ! currentParagraph ) {
404- handleEmptyLineWithAttributes ( mdast , attributes , currentList ) ;
405- return ;
433+ return handleEmptyLineWithAttributes ( mdast , attributes , currentList ) ;
406434 }
407435
408436 if ( attributes . header ) {
409437 processHeaderLineBreak ( mdast , textBuffer , attributes ) ;
410- } else if ( attributes [ "code-block" ] ) {
438+ return null ;
439+ }
440+
441+ if ( attributes [ "code-block" ] ) {
411442 processCodeBlockLineBreak ( mdast , textBuffer , attributes ) ;
412- } else if ( attributes . list ) {
413- processListLineBreak ( mdast , currentParagraph , attributes , currentList ) ;
414- } else if ( attributes . blockquote ) {
443+ return currentList ;
444+ }
445+
446+ if ( attributes . list ) {
447+ return processListLineBreak ( mdast , currentParagraph , attributes , currentList ) ;
448+ }
449+
450+ if ( attributes . blockquote ) {
415451 processBlockquoteLineBreak ( mdast , currentParagraph ) ;
416- } else {
417- mdast . children . push ( currentParagraph ) ;
418- }
452+ return currentList ;
453+ }
454+
455+ // Default case: regular paragraph
456+ mdast . children . push ( currentParagraph ) ;
457+ return null ;
419458}
420459
421460/**
422461 * Handles an empty line with special attributes.
423462 * @param {MdastNode } mdast - The root MDAST node.
424463 * @param {Object } attributes - The attributes for the line.
425464 * @param {MdastNode|null } currentList - The current list being built.
426- * @returns {void }
465+ * @returns {MdastNode|null } - The updated current list.
427466 */
428467function handleEmptyLineWithAttributes ( mdast , attributes , currentList ) {
429468 if ( attributes [ "code-block" ] ) {
430469 mdast . children . push ( createEmptyCodeBlock ( attributes ) ) ;
431- } else if ( attributes . list ) {
470+ return currentList ;
471+ }
472+
473+ if ( attributes . list ) {
432474 const list = ensureList ( mdast , attributes , currentList ) ;
433475 list . children . push ( createEmptyListItem ( ) ) ;
434- } else if ( attributes . blockquote ) {
476+ return list ;
477+ }
478+
479+ if ( attributes . blockquote ) {
435480 mdast . children . push ( createEmptyBlockquote ( ) ) ;
481+ return currentList ;
436482 }
483+
484+ return null ;
437485}
438486
439487/**
@@ -518,11 +566,22 @@ function processHeaderLineBreak(mdast, textBuffer, attributes) {
518566function processCodeBlockLineBreak ( mdast , textBuffer , attributes ) {
519567 const lang =
520568 attributes [ "code-block" ] === "plain" ? null : attributes [ "code-block" ] ;
521- // Two code blocks in a row are merged into one
522- const lastChild = mdast . children [ mdast . children . length - 1 ] ;
523- if ( lastChild && lastChild . type === "code" && lastChild . lang === lang ) {
524- lastChild . value += `\n${ textBuffer } ` ;
569+
570+ // Find the last code block with the same language
571+ let lastCodeBlock = null ;
572+ for ( let i = mdast . children . length - 1 ; i >= 0 ; i -- ) {
573+ const child = mdast . children [ i ] ;
574+ if ( child . type === "code" && child . lang === lang ) {
575+ lastCodeBlock = child ;
576+ break ;
577+ }
578+ }
579+
580+ if ( lastCodeBlock ) {
581+ // Append to existing code block with same language
582+ lastCodeBlock . value += `\n${ textBuffer } ` ;
525583 } else {
584+ // Create new code block
526585 mdast . children . push ( {
527586 type : "code" ,
528587 value : textBuffer ,
@@ -539,16 +598,28 @@ function processCodeBlockLineBreak(mdast, textBuffer, attributes) {
539598 * @returns {MdastNode } - The list node.
540599 */
541600function ensureList ( mdast , attributes , currentList ) {
542- if ( ! currentList || currentList . ordered !== ( attributes . list === "ordered" ) ) {
601+ const isOrderedList = attributes . list === "ordered" ;
602+
603+ // If there's no current list or the list type doesn't match
604+ if ( ! currentList || currentList . ordered !== isOrderedList ) {
605+ // Check if the last child is a list of the correct type
606+ const lastChild = mdast . children [ mdast . children . length - 1 ] ;
607+ if ( lastChild && lastChild . type === "list" && lastChild . ordered === isOrderedList ) {
608+ // Use the last list if it matches the type
609+ return lastChild ;
610+ }
611+
612+ // Create a new list
543613 const newList = {
544614 type : "list" ,
545- ordered : attributes . list === "ordered" ,
615+ ordered : isOrderedList ,
546616 spread : false ,
547617 children : [ ] ,
548618 } ;
549619 mdast . children . push ( newList ) ;
550620 return newList ;
551621 }
622+
552623 return currentList ;
553624}
554625
@@ -558,7 +629,7 @@ function ensureList(mdast, attributes, currentList) {
558629 * @param {MdastNode } currentParagraph - The current paragraph being built.
559630 * @param {Object } attributes - The attributes for the line.
560631 * @param {MdastNode|null } currentList - The current list being built.
561- * @returns {void }
632+ * @returns {MdastNode } - The updated list node.
562633 */
563634function processListLineBreak (
564635 mdast ,
@@ -568,13 +639,24 @@ function processListLineBreak(
568639) {
569640 const list = ensureList ( mdast , attributes , currentList ) ;
570641
571- const listItem = {
572- type : "listItem" ,
573- spread : false ,
574- children : [ currentParagraph ] ,
575- } ;
576-
577- list . children . push ( listItem ) ;
642+ // Check if this list item already exists to avoid duplication
643+ const paragraphContent = JSON . stringify ( currentParagraph . children ) ;
644+ const isDuplicate = list . children . some (
645+ item => item . children ?. length === 1 &&
646+ JSON . stringify ( item . children [ 0 ] . children ) === paragraphContent
647+ ) ;
648+
649+ if ( ! isDuplicate ) {
650+ const listItem = {
651+ type : "listItem" ,
652+ spread : false ,
653+ children : [ currentParagraph ] ,
654+ } ;
655+
656+ list . children . push ( listItem ) ;
657+ }
658+
659+ return list ;
578660}
579661
580662/**
@@ -584,10 +666,20 @@ function processListLineBreak(
584666 * @returns {void }
585667 */
586668function processBlockquoteLineBreak ( mdast , currentParagraph ) {
587- mdast . children . push ( {
588- type : "blockquote" ,
589- children : [ currentParagraph ] ,
590- } ) ;
669+ // Look for an existing blockquote with identical content to avoid duplication
670+ const paragraphContent = JSON . stringify ( currentParagraph . children ) ;
671+ const existingBlockquote = mdast . children . find (
672+ child => child . type === "blockquote" &&
673+ child . children ?. length === 1 &&
674+ JSON . stringify ( child . children [ 0 ] . children ) === paragraphContent
675+ ) ;
676+
677+ if ( ! existingBlockquote ) {
678+ mdast . children . push ( {
679+ type : "blockquote" ,
680+ children : [ currentParagraph ] ,
681+ } ) ;
682+ }
591683}
592684
593685// Main execution
0 commit comments