Skip to content

Commit

Permalink
[lexical] Bug Fix: Merge pasted paragraph into empty quote (#6367)
Browse files Browse the repository at this point in the history
Co-authored-by: Bob Ippolito <bob@redivi.com>
  • Loading branch information
2wheeh and etrepum authored Aug 2, 2024
1 parent f373759 commit 3c53e64
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 4 deletions.
4 changes: 4 additions & 0 deletions packages/lexical-list/src/LexicalListItemNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,10 @@ export class ListItemNode extends ElementNode {
createParentElementNode(): ElementNode {
return $createListNode('bullet');
}

canMergeWhenEmpty(): true {
return true;
}
}

function $setListItemThemeClassNames(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -914,4 +914,37 @@ test.describe('CopyAndPaste', () => {
`,
);
});

test('Copy and paste paragraph into quote', async ({page, isPlainText}) => {
test.skip(isPlainText);
await focusEditor(page);

await page.keyboard.type('Hello world');
await page.keyboard.press('Enter');
await page.keyboard.type('Some text');

await selectAll(page);

const clipboard = await copyToClipboard(page);

await page.keyboard.type('> ');

await pasteFromClipboard(page, clipboard);

await assertHTML(
page,
html`
<blockquote
class="PlaygroundEditorTheme__quote PlaygroundEditorTheme__ltr"
dir="ltr">
<span data-lexical-text="true">Hello world</span>
</blockquote>
<p
class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr"
dir="ltr">
<span data-lexical-text="true">Some text</span>
</p>
`,
);
});
});
4 changes: 4 additions & 0 deletions packages/lexical-rich-text/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,10 @@ export class QuoteNode extends ElementNode {
this.replace(paragraph);
return true;
}

canMergeWhenEmpty(): true {
return true;
}
}

export function $createQuoteNode(): QuoteNode {
Expand Down
6 changes: 2 additions & 4 deletions packages/lexical/src/LexicalSelection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1255,14 +1255,12 @@ export class RangeSelection implements BaseSelection {
const blocksParent = $wrapInlineNodes(nodes);
const nodeToSelect = blocksParent.getLastDescendant()!;
const blocks = blocksParent.getChildren();
const isLI = (node: LexicalNode) =>
'__value' in node && '__checked' in node;
const isMergeable = (node: LexicalNode): node is ElementNode =>
$isElementNode(node) &&
INTERNAL_$isBlock(node) &&
!node.isEmpty() &&
$isElementNode(firstBlock) &&
(!firstBlock.isEmpty() || isLI(firstBlock));
(!firstBlock.isEmpty() || firstBlock.canMergeWhenEmpty());

const shouldInsert = !$isElementNode(firstBlock) || !firstBlock.isEmpty();
const insertedParagraph = shouldInsert ? this.insertParagraph() : null;
Expand All @@ -1284,7 +1282,7 @@ export class RangeSelection implements BaseSelection {
if (
insertedParagraph &&
$isElementNode(lastInsertedBlock) &&
(isLI(insertedParagraph) || INTERNAL_$isBlock(lastToInsert))
(insertedParagraph.canMergeWhenEmpty() || INTERNAL_$isBlock(lastToInsert))
) {
lastInsertedBlock.append(...insertedParagraph.getChildren());
insertedParagraph.remove();
Expand Down
17 changes: 17 additions & 0 deletions packages/lexical/src/nodes/LexicalElementNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,23 @@ export class ElementNode extends LexicalNode {
): boolean {
return false;
}

/**
* Determines whether this node, when empty, can merge with a first block
* of nodes being inserted.
*
* This method is specifically called in {@link RangeSelection.insertNodes}
* to determine merging behavior during nodes insertion.
*
* @example
* // In a ListItemNode or QuoteNode implementation:
* canMergeWhenEmpty(): true {
* return true;
* }
*/
canMergeWhenEmpty(): boolean {
return false;
}
}

export function $isElementNode(
Expand Down

0 comments on commit 3c53e64

Please sign in to comment.