More insertNode fixes (#715)

* More insertNode fixes
This commit is contained in:
Dominic Gannaway
2021-10-14 18:21:53 +01:00
committed by acywatson
parent d6ce7469b2
commit 3ebcd7c461
5 changed files with 139 additions and 37 deletions

View File

@ -20,6 +20,7 @@ import {
pasteFromClipboard, pasteFromClipboard,
E2E_BROWSER, E2E_BROWSER,
IS_LINUX, IS_LINUX,
IS_WINDOWS,
} from '../utils'; } from '../utils';
describe('CopyAndPaste', () => { describe('CopyAndPaste', () => {
@ -301,7 +302,7 @@ describe('CopyAndPaste', () => {
}); });
}); });
it('Copy and paste of partial list items', async () => { it('Copy and paste of partial list items into an empty editor', async () => {
const {isRichText, page} = e2e; const {isRichText, page} = e2e;
if (!isRichText) { if (!isRichText) {
@ -380,6 +381,91 @@ describe('CopyAndPaste', () => {
}); });
}); });
it('Copy and paste of partial list items into the list', async () => {
const {isRichText, page} = e2e;
if (!isRichText) {
return;
}
await page.focus('div.editor');
// Add three list items
await page.keyboard.type('- One');
await page.keyboard.press('Enter');
await page.keyboard.type('Two');
await page.keyboard.press('Enter');
await page.keyboard.type('Three');
await page.keyboard.press('Enter');
await page.keyboard.press('Enter');
// Add a paragraph
await page.keyboard.type('Some text.');
await assertHTML(
page,
'<ul class="editor-list-ul" dir="ltr"><li class="editor-listitem"><span data-outline-text="true">One</span></li><li class="editor-listitem"><span data-outline-text="true">Two</span></li><li class="editor-listitem"><span data-outline-text="true">Three</span></li></ul><p class="editor-paragraph" dir="ltr"><span data-outline-text="true">Some text.</span></p>',
);
await assertSelection(page, {
anchorPath: [1, 0, 0],
anchorOffset: 10,
focusPath: [1, 0, 0],
focusOffset: 10,
});
await page.keyboard.down('Shift');
await moveToLineBeginning(page);
await page.keyboard.press('ArrowLeft');
await page.keyboard.press('ArrowLeft');
await page.keyboard.press('ArrowLeft');
await page.keyboard.up('Shift');
await assertSelection(page, {
anchorPath: [1, 0, 0],
anchorOffset: 10,
focusPath: [0, 2, 0, 0],
focusOffset: 3,
});
// Copy the partial list item and paragraph
const clipboard = await copyToClipboard(page);
// Select all and remove content
await page.keyboard.press('ArrowUp');
await page.keyboard.press('ArrowUp');
if (!IS_WINDOWS && E2E_BROWSER === 'firefox') {
await page.keyboard.press('ArrowUp');
}
await moveToLineEnd(page);
await page.keyboard.down('Enter');
await assertHTML(
page,
'<ul class="editor-list-ul" dir="ltr"><li class="editor-listitem"><span data-outline-text="true">One</span></li><li class="editor-listitem"><br></li><li class="editor-listitem"><span data-outline-text="true">Two</span></li><li class="editor-listitem"><span data-outline-text="true">Three</span></li></ul><p class="editor-paragraph" dir="ltr"><span data-outline-text="true">Some text.</span></p>',
);
await assertSelection(page, {
anchorPath: [0, 1],
anchorOffset: 0,
focusPath: [0, 1],
focusOffset: 0,
});
await pasteFromClipboard(page, clipboard);
await assertHTML(
page,
'<ul class="editor-list-ul" dir="ltr"><li class="editor-listitem"><span data-outline-text="true">One</span></li><li class="editor-listitem"><span data-outline-text="true">ee</span></li></ul><p class="editor-paragraph" dir="ltr"><span data-outline-text="true">Some text.</span></p><ul class="editor-list-ul"><li class="editor-listitem"><span data-outline-text="true">Three</span></li><li class="editor-listitem"><span data-outline-text="true">Two</span></li></ul><p class="editor-paragraph" dir="ltr"><span data-outline-text="true">Some text.</span></p>',
);
await assertSelection(page, {
anchorPath: [1, 0, 0],
anchorOffset: 10,
focusPath: [1, 0, 0],
focusOffset: 10,
});
});
it('Copy and paste of list items and paste back into list', async () => { it('Copy and paste of list items and paste back into list', async () => {
const {isRichText, page} = e2e; const {isRichText, page} = e2e;

View File

@ -291,6 +291,9 @@ export class BlockNode extends OutlineNode {
canReplaceWith(replacement: OutlineNode): boolean { canReplaceWith(replacement: OutlineNode): boolean {
return true; return true;
} }
canInsertAfter(node: OutlineNode): boolean {
return true;
}
canBeEmpty(): boolean { canBeEmpty(): boolean {
return true; return true;
} }

View File

@ -171,6 +171,10 @@ export class ListItemNode extends BlockNode {
return true; return true;
} }
canInsertAfter(node: OutlineNode): boolean {
return isListItemNode(node);
}
canReplaceWith(replacement: OutlineNode): boolean { canReplaceWith(replacement: OutlineNode): boolean {
return isListItemNode(replacement); return isListItemNode(replacement);
} }

View File

@ -1037,21 +1037,29 @@ export function insertNodes(
if (siblings.length !== 0) { if (siblings.length !== 0) {
for (let i = siblings.length - 1; i >= 0; i--) { for (let i = siblings.length - 1; i >= 0; i--) {
const sibling = siblings[i]; const sibling = siblings[i];
const prevParent = sibling.getParent(); const prevParent = sibling.getParentOrThrow();
if (isBlockNode(target) && !isBlockNode(sibling)) { if (isBlockNode(target) && !isBlockNode(sibling)) {
target.append(sibling); target.append(sibling);
target = sibling; target = sibling;
} else { } else {
target.insertAfter(sibling); if (isBlockNode(sibling) && !sibling.canInsertAfter(target)) {
const prevParentClone = prevParent.constructor.clone(prevParent);
if (!isBlockNode(prevParentClone)) {
invariant(
false,
'insertNodes: cloned parent clone is not a block',
);
}
prevParentClone.append(sibling);
target.insertAfter(prevParentClone);
} else {
target.insertAfter(sibling);
}
} }
// Check if the prev parent is empty, as it might need // Check if the prev parent is empty, as it might need
// removing. // removing.
if ( if (prevParent.isEmpty() && !prevParent.canBeEmpty()) {
isBlockNode(prevParent) &&
prevParent.isEmpty() &&
!prevParent.canBeEmpty()
) {
prevParent.remove(); prevParent.remove();
} }
} }

View File

@ -3,36 +3,36 @@
"1": "clearEditor expected plain text root first child to be a ParagraphNode", "1": "clearEditor expected plain text root first child to be a ParagraphNode",
"2": "Expected rich text root first child to be a ParagraphNode", "2": "Expected rich text root first child to be a ParagraphNode",
"3": "insertAfter: list node is not parent of list item node", "3": "insertAfter: list node is not parent of list item node",
"4": "createOffsetModel: could not find node by key", "4": "insertText: first node is not a text node",
"5": "insertText: first node is not a text node", "5": "createOffsetModel: could not find node by key",
"6": "Editor.getLatestTextContent() can be asynchronous and cannot be used within Editor.update()", "6": "Editor.getLatestTextContent() can be asynchronous and cannot be used within Editor.update()",
"7": "setViewModel: the view model is empty. Ensure the view model's root node never becomes empty.", "7": "setViewModel: the view model is empty. Ensure the view model's root node never becomes empty.",
"8": "decorate: base method not extended", "8": "select: cannot be called on root nodes",
"9": "updateDOM: prevInnerDOM is null or undefined", "9": "remove: cannot be called on root nodes",
"10": "updateDOM: innerDOM is null or undefined", "10": "replace: cannot be called on root nodes",
"11": "setFormat: can only be used on non-immutable nodes", "11": "insertBefore: cannot be called on root nodes",
"12": "setStyle: can only be used on non-immutable nodes", "12": "insertAfter: cannot be called on root nodes",
"13": "setTextContent: can only be used on non-immutable text nodes", "13": "rootNode.append: Only block nodes can be appended to the root node",
"14": "spliceText: can only be used on non-immutable text nodes", "14": "updateDOM: prevInnerDOM is null or undefined",
"15": "spliceText: selection not found", "15": "updateDOM: innerDOM is null or undefined",
"16": "splitText: can only be used on non-immutable text nodes", "16": "setFormat: can only be used on non-immutable nodes",
"17": "OutlineNode: Node type %s does not implement .clone().", "17": "setStyle: can only be used on non-immutable nodes",
"18": "Expected node %s to have a parent.", "18": "setTextContent: can only be used on non-immutable text nodes",
"19": "Expected node %s to have a parent block.", "19": "spliceText: can only be used on non-immutable text nodes",
"20": "Expected node %s to have a top parent block.", "20": "spliceText: selection not found",
"21": "getNodesBetween: ancestor is null", "21": "splitText: can only be used on non-immutable text nodes",
"22": "getLatest: node not found", "22": "decorate: base method not extended",
"23": "createDOM: base method not extended", "23": "OutlineNode: Node type %s does not implement .clone().",
"24": "updateDOM: base method not extended", "24": "Expected node %s to have a parent.",
"25": "setFlags: can only be used on non-immutable nodes", "25": "Expected node %s to have a parent block.",
"26": "Expected node with key %s to exist but it's not in the nodeMap.", "26": "Expected node %s to have a top parent block.",
"27": "createNodeFromParse: type \"%s\" + not found", "27": "getNodesBetween: ancestor is null",
"28": "select: cannot be called on root nodes", "28": "getLatest: node not found",
"29": "remove: cannot be called on root nodes", "29": "createDOM: base method not extended",
"30": "replace: cannot be called on root nodes", "30": "updateDOM: base method not extended",
"31": "insertBefore: cannot be called on root nodes", "31": "setFlags: can only be used on non-immutable nodes",
"32": "insertAfter: cannot be called on root nodes", "32": "Expected node with key %s to exist but it's not in the nodeMap.",
"33": "rootNode.append: Only block nodes can be appended to the root node", "33": "createNodeFromParse: type \"%s\" + not found",
"34": "Editor.update() cannot be used within a text node transform.", "34": "Editor.update() cannot be used within a text node transform.",
"35": "Cannot use method in read-only mode.", "35": "Cannot use method in read-only mode.",
"36": "Unable to find an active view model. View methods or node methods can only be used synchronously during the callback of editor.update() or viewModel.read().", "36": "Unable to find an active view model. View methods or node methods can only be used synchronously during the callback of editor.update() or viewModel.read().",
@ -43,5 +43,6 @@
"41": "createNode: node does not exist in nodeMap", "41": "createNode: node does not exist in nodeMap",
"42": "reconcileNode: prevNode or nextNode does not exist in nodeMap", "42": "reconcileNode: prevNode or nextNode does not exist in nodeMap",
"43": "reconcileNode: parentDOM is null", "43": "reconcileNode: parentDOM is null",
"44": "Reconciliation: could not find DOM element for node key \"${key}\"" "44": "Reconciliation: could not find DOM element for node key \"${key}\"",
"45": "insertNodes: cloned parent clone is not a block"
} }