Skip to content

Commit

Permalink
Make insertNodes append children to the first element
Browse files Browse the repository at this point in the history
This restores the behavior that changed in facebook#5002 particularly when
pasting into links and other inline elements.

Fixes facebook#5251.
  • Loading branch information
birtles committed Dec 27, 2023
1 parent f38bd6a commit 0ddffa0
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/

import {
moveToLineBeginning,
moveToNextWord,
moveToPrevWord,
selectCharacters,
} from '../keyboardShortcuts/index.mjs';
import {
assertHTML,
click,
copyToClipboard,
focusEditor,
html,
initialize,
IS_WINDOWS,
pasteFromClipboard,
pressToggleBold,
test,
} from '../utils/index.mjs';

test.describe('Regression test #5251', () => {
test.beforeEach(({isCollab, page}) => initialize({isCollab, page}));
test('Correctly pastes rich content inside an inline element', async ({
isPlainText,
page,
}) => {
test.skip(isPlainText);
await focusEditor(page);

// Root
// |- Paragraph
// |- Text "Hello "
// |- Text "bold" { format: bold }
// |- Text " "
// |- Link
// |- Text "World"
await page.keyboard.type('Hello ');
await pressToggleBold(page);
await page.keyboard.type('bold');
await pressToggleBold(page);
await page.keyboard.type(' World');
await moveToPrevWord(page);
await selectCharacters(page, 'right', 'World'.length);
await click(page, '.link');

// Copy "Hello bold"
await moveToLineBeginning(page);
await selectCharacters(page, 'right', 'Hello bold'.length);
const clipboard = await copyToClipboard(page);

// Drop "bold"
await page.keyboard.press('ArrowLeft');
await moveToNextWord(page);
await selectCharacters(page, 'right', 'bold '.length);
await page.keyboard.press('Delete');

// Check our current state
await assertHTML(
page,
html`
<p
class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr"
dir="ltr">
<span data-lexical-text="true">Hello</span>
<a
href="https://"
rel="noreferrer"
class="PlaygroundEditorTheme__link PlaygroundEditorTheme__ltr"
dir="ltr">
<span data-lexical-text="true">World</span>
</a>
</p>
`,
);

// Replace "Wor" with the contents of the clipboard
if (!IS_WINDOWS) {
await page.keyboard.press('ArrowRight');
}
await selectCharacters(page, 'right', 'Wor'.length);
await pasteFromClipboard(page, clipboard);

await assertHTML(
page,
html`
<p
class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr"
dir="ltr">
<span data-lexical-text="true">Hello</span>
<a
href="https://"
rel="noreferrer"
class="PlaygroundEditorTheme__link PlaygroundEditorTheme__ltr"
dir="ltr">
<span data-lexical-text="true">Hello</span>
<strong
class="PlaygroundEditorTheme__textBold"
data-lexical-text="true">
bold
</strong>
<span data-lexical-text="true">ld</span>
</a>
</p>
`,
);
});
});
35 changes: 12 additions & 23 deletions packages/lexical/src/LexicalSelection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1625,16 +1625,16 @@ export class RangeSelection extends INTERNAL_PointSelection {
);
return selection.insertNodes(nodes);
}
const firstBlock = $getAncestor(this.anchor.getNode(), INTERNAL_$isBlock)!;
const firstElement = $getAncestor(this.anchor.getNode(), $isElementNode)!;
const last = nodes[nodes.length - 1]!;

// CASE 1: insert inside a code block
if ('__language' in firstBlock && $isElementNode(firstBlock)) {
if ('__language' in firstElement) {
if ('__language' in nodes[0]) {
this.insertText(nodes[0].getTextContent());
} else {
const index = removeTextAndSplitBlock(this);
firstBlock.splice(index, 0, nodes);
firstElement.splice(index, 0, nodes);
last.selectEnd();
}
return;
Expand All @@ -1645,12 +1645,8 @@ export class RangeSelection extends INTERNAL_PointSelection {
($isElementNode(node) || $isDecoratorNode(node)) && !node.isInline();

if (!nodes.some(notInline)) {
invariant(
$isElementNode(firstBlock),
"Expected 'firstBlock' to be an ElementNode",
);
const index = removeTextAndSplitBlock(this);
firstBlock.splice(index, 0, nodes);
firstElement.splice(index, 0, nodes);
last.selectEnd();
return;
}
Expand All @@ -1665,23 +1661,18 @@ export class RangeSelection extends INTERNAL_PointSelection {
$isElementNode(node) &&
INTERNAL_$isBlock(node) &&
!node.isEmpty() &&
$isElementNode(firstBlock) &&
(!firstBlock.isEmpty() || isLI(firstBlock));
(!firstElement.isEmpty() || isLI(firstElement));

const shouldInsert = !$isElementNode(firstBlock) || !firstBlock.isEmpty();
const shouldInsert = !firstElement.isEmpty();
const insertedParagraph = shouldInsert ? this.insertParagraph() : null;
const lastToInsert = blocks[blocks.length - 1];
let firstToInsert = blocks[0];
if (isMergeable(firstToInsert)) {
invariant(
$isElementNode(firstBlock),
"Expected 'firstBlock' to be an ElementNode",
);
firstBlock.append(...firstToInsert.getChildren());
firstElement.append(...firstToInsert.getChildren());
firstToInsert = blocks[1];
}
if (firstToInsert) {
insertRangeAfter(firstBlock, firstToInsert);
insertRangeAfter(firstElement, firstToInsert);
}
const lastInsertedBlock = $getAncestor(nodeToSelect, INTERNAL_$isBlock)!;

Expand All @@ -1693,17 +1684,15 @@ export class RangeSelection extends INTERNAL_PointSelection {
lastInsertedBlock.append(...insertedParagraph.getChildren());
insertedParagraph.remove();
}
if ($isElementNode(firstBlock) && firstBlock.isEmpty()) {
firstBlock.remove();
if (firstElement.isEmpty()) {
firstElement.remove();
}

nodeToSelect.selectEnd();

// To understand this take a look at the test "can wrap post-linebreak nodes into new element"
const lastChild = $isElementNode(firstBlock)
? firstBlock.getLastChild()
: null;
if ($isLineBreakNode(lastChild) && lastInsertedBlock !== firstBlock) {
const lastChild = firstElement.getLastChild();
if ($isLineBreakNode(lastChild) && lastInsertedBlock !== firstElement) {
lastChild.remove();
}
}
Expand Down

0 comments on commit 0ddffa0

Please sign in to comment.