@@ -255,23 +255,52 @@ function deltaToMdast(delta) {
255
255
const text = op . insert ;
256
256
const attributes = op . attributes || { } ;
257
257
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
+
258
295
if ( text === "\n" ) {
259
- processLineBreak (
296
+ currentList = processLineBreak (
260
297
mdast ,
261
298
currentParagraph ,
262
299
attributes ,
263
300
textBuffer ,
264
- currentList ,
301
+ currentList
265
302
) ;
266
- if (
267
- ! attributes . list &&
268
- ! attributes . blockquote &&
269
- ! attributes [ "code-block" ] &&
270
- ! attributes . header
271
- ) {
272
- currentList = null ;
273
- }
274
-
303
+
275
304
// Reset paragraph and buffer after processing line break
276
305
currentParagraph = null ;
277
306
textBuffer = "" ;
@@ -391,7 +420,7 @@ function wrapNodesWith(children, type) {
391
420
* @param {Object } attributes - The attributes for the line.
392
421
* @param {string } textBuffer - The text buffer for the current line.
393
422
* @param {MdastNode|null } currentList - The current list being built.
394
- * @returns {void }
423
+ * @returns {MdastNode|null } - The updated current list.
395
424
*/
396
425
function processLineBreak (
397
426
mdast ,
@@ -401,39 +430,58 @@ function processLineBreak(
401
430
currentList ,
402
431
) {
403
432
if ( ! currentParagraph ) {
404
- handleEmptyLineWithAttributes ( mdast , attributes , currentList ) ;
405
- return ;
433
+ return handleEmptyLineWithAttributes ( mdast , attributes , currentList ) ;
406
434
}
407
435
408
436
if ( attributes . header ) {
409
437
processHeaderLineBreak ( mdast , textBuffer , attributes ) ;
410
- } else if ( attributes [ "code-block" ] ) {
438
+ return null ;
439
+ }
440
+
441
+ if ( attributes [ "code-block" ] ) {
411
442
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 ) {
415
451
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 ;
419
458
}
420
459
421
460
/**
422
461
* Handles an empty line with special attributes.
423
462
* @param {MdastNode } mdast - The root MDAST node.
424
463
* @param {Object } attributes - The attributes for the line.
425
464
* @param {MdastNode|null } currentList - The current list being built.
426
- * @returns {void }
465
+ * @returns {MdastNode|null } - The updated current list.
427
466
*/
428
467
function handleEmptyLineWithAttributes ( mdast , attributes , currentList ) {
429
468
if ( attributes [ "code-block" ] ) {
430
469
mdast . children . push ( createEmptyCodeBlock ( attributes ) ) ;
431
- } else if ( attributes . list ) {
470
+ return currentList ;
471
+ }
472
+
473
+ if ( attributes . list ) {
432
474
const list = ensureList ( mdast , attributes , currentList ) ;
433
475
list . children . push ( createEmptyListItem ( ) ) ;
434
- } else if ( attributes . blockquote ) {
476
+ return list ;
477
+ }
478
+
479
+ if ( attributes . blockquote ) {
435
480
mdast . children . push ( createEmptyBlockquote ( ) ) ;
481
+ return currentList ;
436
482
}
483
+
484
+ return null ;
437
485
}
438
486
439
487
/**
@@ -518,11 +566,22 @@ function processHeaderLineBreak(mdast, textBuffer, attributes) {
518
566
function processCodeBlockLineBreak ( mdast , textBuffer , attributes ) {
519
567
const lang =
520
568
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 } ` ;
525
583
} else {
584
+ // Create new code block
526
585
mdast . children . push ( {
527
586
type : "code" ,
528
587
value : textBuffer ,
@@ -539,16 +598,28 @@ function processCodeBlockLineBreak(mdast, textBuffer, attributes) {
539
598
* @returns {MdastNode } - The list node.
540
599
*/
541
600
function 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
543
613
const newList = {
544
614
type : "list" ,
545
- ordered : attributes . list === "ordered" ,
615
+ ordered : isOrderedList ,
546
616
spread : false ,
547
617
children : [ ] ,
548
618
} ;
549
619
mdast . children . push ( newList ) ;
550
620
return newList ;
551
621
}
622
+
552
623
return currentList ;
553
624
}
554
625
@@ -558,7 +629,7 @@ function ensureList(mdast, attributes, currentList) {
558
629
* @param {MdastNode } currentParagraph - The current paragraph being built.
559
630
* @param {Object } attributes - The attributes for the line.
560
631
* @param {MdastNode|null } currentList - The current list being built.
561
- * @returns {void }
632
+ * @returns {MdastNode } - The updated list node.
562
633
*/
563
634
function processListLineBreak (
564
635
mdast ,
@@ -568,13 +639,24 @@ function processListLineBreak(
568
639
) {
569
640
const list = ensureList ( mdast , attributes , currentList ) ;
570
641
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 ;
578
660
}
579
661
580
662
/**
@@ -584,10 +666,20 @@ function processListLineBreak(
584
666
* @returns {void }
585
667
*/
586
668
function 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
+ }
591
683
}
592
684
593
685
// Main execution
0 commit comments