mirror of
https://github.com/facebook/lexical.git
synced 2025-08-06 08:30:33 +08:00

committed by
acywatson

parent
d6ce7469b2
commit
3ebcd7c461
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user