Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 40 additions & 3 deletions packages/lexical-html/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,9 +309,46 @@ function $createNodesFromDOM(
}
} else {
if ($isElementNode(currentLexicalNode)) {
// If the current node is a ElementNode after conversion,
// we can append all the children to it.
currentLexicalNode.append(...childLexicalNodes);
// If the current node is a ElementNode after conversion, we should be able to append all the
// children to it. However, we still need to ensure that children requiring specific parents
// have the correct parent type. Group children by their required parent type.
let requiredParent: ElementNode | null = null;
for (const child of childLexicalNodes) {
// console.log(requiredParent, child, child.isParentRequired());
const desiredParent = child.createParentElementNode();
if (
child.isParentRequired() &&
!(currentLexicalNode instanceof desiredParent.constructor)
) {
if (
requiredParent === null ||
!(requiredParent instanceof desiredParent.constructor)
) {
// CASE 1: We have a child that needs a parent, but the existing parent either doesn't
// exist or can't house this child. So, create a new parent to house this child.
requiredParent = desiredParent;
requiredParent.append(child);
// Typescript gets a little confused here, so help it along
(currentLexicalNode as ElementNode).append(requiredParent);
} else {
// CASE 2: We have a child in need of a parent, and the parent we've already been
// adding to can do the job.
requiredParent.append(child);
}
} else {
if (requiredParent === null) {
// CASE 3: We have a child that doesn't need a parent, and no existing parent, so we can
// add like normal.
currentLexicalNode.append(child);
} else {
// CASE 4: We have a child that doesn't need a parent, but there is an existing parent.
// To make it so that children always appear in the correct order, we should remove the
// parent before continuing.
requiredParent = null;
currentLexicalNode.append(child);
}
}
}
}
}

Expand Down
24 changes: 24 additions & 0 deletions packages/lexical/src/LexicalNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1328,6 +1328,18 @@ export class LexicalNode {
writableNodeToInsert.__next = nextKey;
writableNodeToInsert.__prev = writableSelf.__key;
writableNodeToInsert.__parent = writableSelf.__parent;
// Check invariant: if node requires a parent, verify it has the correct parent type
if (writableNodeToInsert.isParentRequired()) {
const requiredParentType =
writableNodeToInsert.createParentElementNode().constructor;
invariant(
writableParent instanceof requiredParentType,
'insertAfter: node %s requires a parent of type %s, but got %s',
writableNodeToInsert.constructor.name,
requiredParentType.name,
writableParent.constructor.name,
);
}
if (restoreSelection && $isRangeSelection(selection)) {
const index = this.getIndexWithinParent();
$updateElementSelectionOnCreateDeleteNode(
Expand Down Expand Up @@ -1379,6 +1391,18 @@ export class LexicalNode {
writableNodeToInsert.__prev = prevKey;
writableNodeToInsert.__next = writableSelf.__key;
writableNodeToInsert.__parent = writableSelf.__parent;
// Check invariant: if node requires a parent, verify it has the correct parent type
if (writableNodeToInsert.isParentRequired()) {
const requiredParentType =
writableNodeToInsert.createParentElementNode().constructor;
invariant(
writableParent instanceof requiredParentType,
'insertAfter: node %s requires a parent of type %s, but got %s',
writableNodeToInsert.constructor.name,
requiredParentType.name,
writableParent.constructor.name,
);
}
const selection = $getSelection();
if (restoreSelection && $isRangeSelection(selection)) {
const parent = this.getParentOrThrow();
Expand Down
11 changes: 11 additions & 0 deletions packages/lexical/src/__tests__/unit/HTMLCopyAndPaste.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,17 @@ describe('HTMLCopyAndPaste tests', () => {
pastedHTML: `<strong>hello</strong>`,
plainTextInsert: ' world',
},
{
expectedHTML: `<blockquote dir="auto"><ul><li value="1"><span data-lexical-text="true">Item A</span></li><li value="2"><span data-lexical-text="true">Item B</span></li><li value="3"><span data-lexical-text="true">Item C</span></li></ul></blockquote>`,
name: 'invalid list node correction',
pastedHTML: `
<blockquote>
<li>Item A</li>
<li>Item B</li>
<li>Item C</li>
</blockquote>
`,
},
];

HTML_COPY_PASTING_TESTS.forEach((testCase, i) => {
Expand Down