Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit d6913e2

Browse files
committedApr 21, 2025·
handle newlines, prevent duplication in lists and blockquotes, and improve line break processing in rich text editor
1 parent c203af6 commit d6913e2

File tree

1 file changed

+134
-42
lines changed

1 file changed

+134
-42
lines changed
 

‎examples/rich-text-editor/rich_text_editor.js

Lines changed: 134 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/
396425
function 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
*/
428467
function 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) {
518566
function 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
*/
541600
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
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
*/
563634
function 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
*/
586668
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+
}
591683
}
592684

593685
// Main execution

0 commit comments

Comments
 (0)