mirror of
https://github.com/facebook/lexical.git
synced 2025-05-20 16:48:04 +08:00
Fix indented list behavior when pressing Return (#779)
* Fix indented list behavior when pressing Return. * Fix test. Co-authored-by: Acy Watson <acy@fb.com>
This commit is contained in:
@ -316,7 +316,7 @@ describe('OutlineListItemNode tests', () => {
|
||||
listItemNode1.insertNewAfter();
|
||||
});
|
||||
expect(testEnv.outerHTML).toBe(
|
||||
'<div contenteditable="true" data-outline-editor="true"><p><br></p><ul><li><br></li><li><br></li></ul></div>',
|
||||
'<div contenteditable="true" data-outline-editor="true"><ul><li><br></li><li><br></li><li><br></li><li><br></li></ul></div>',
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -20,6 +20,7 @@ import {isBlockNode, BlockNode} from 'outline';
|
||||
import {createParagraphNode} from 'outline/ParagraphNode';
|
||||
import {createListNode, isListNode} from 'outline/ListNode';
|
||||
import invariant from 'shared/invariant';
|
||||
import {getTopListNode, isLastItemInList} from 'outline/nodes';
|
||||
|
||||
export class ListItemNode extends BlockNode {
|
||||
static clone(node: ListItemNode): ListItemNode {
|
||||
@ -118,13 +119,16 @@ export class ListItemNode extends BlockNode {
|
||||
insertNewAfter(): ListItemNode | ParagraphNode {
|
||||
const nextSibling = this.getNextSibling();
|
||||
const prevSibling = this.getPreviousSibling();
|
||||
const list = this.getParent();
|
||||
const list = getTopListNode(this);
|
||||
const isLast = isLastItemInList(this);
|
||||
|
||||
let newBlock;
|
||||
|
||||
if (
|
||||
isBlockNode(list) &&
|
||||
this.getTextContent() === '' &&
|
||||
(prevSibling === null || nextSibling === null)
|
||||
(prevSibling === null || nextSibling === null) &&
|
||||
isLast
|
||||
) {
|
||||
if (nextSibling === null) {
|
||||
newBlock = createParagraphNode();
|
||||
|
@ -7,8 +7,13 @@
|
||||
* @flow strict
|
||||
*/
|
||||
|
||||
import type {ListItemNode} from 'outline/ListItemNode';
|
||||
import type {OutlineNode, BlockNode} from 'outline';
|
||||
import type {ListNode} from 'outline/ListNode';
|
||||
|
||||
import {isListNode} from 'outline/ListNode';
|
||||
import {isListItemNode} from 'outline/ListItemNode';
|
||||
import invariant from 'shared/invariant';
|
||||
import {isBlockNode} from 'outline';
|
||||
|
||||
export function dfs(
|
||||
@ -53,3 +58,36 @@ export function getCommonAncestor(nodes: OutlineNode[]): null | BlockNode {
|
||||
}
|
||||
return commonAncestor;
|
||||
}
|
||||
|
||||
export function getTopListNode(listItem: ListItemNode): ListNode {
|
||||
let list = listItem.getParent();
|
||||
if (!isListNode(list)) {
|
||||
invariant(false, 'A ListItemNode must have a ListNode for a parent.');
|
||||
}
|
||||
let parent = list;
|
||||
while (parent !== null) {
|
||||
parent = parent.getParent();
|
||||
if (isListNode(parent)) {
|
||||
list = parent;
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
export function isLastItemInList(listItem: ListItemNode): boolean {
|
||||
let isLast = true;
|
||||
const firstChild = listItem.getFirstChild();
|
||||
if (isListNode(firstChild)) {
|
||||
return false;
|
||||
}
|
||||
let parent = listItem;
|
||||
while (parent !== null) {
|
||||
if (isListItemNode(parent)) {
|
||||
if (parent.getNextSiblings().length > 0) {
|
||||
isLast = false;
|
||||
}
|
||||
}
|
||||
parent = parent.getParent();
|
||||
}
|
||||
return isLast;
|
||||
}
|
||||
|
@ -13,9 +13,11 @@ import {
|
||||
initializeUnitTest,
|
||||
createTestBlockNode,
|
||||
} from '../../../__tests__/utils';
|
||||
import {dfs} from '../../OutlineNodeHelpers';
|
||||
import {dfs, getTopListNode, isLastItemInList} from 'outline/nodes';
|
||||
import {createParagraphNode, isParagraphNode} from 'outline/ParagraphNode';
|
||||
import {createTextNode} from 'outline';
|
||||
import {createListNode} from 'outline/ListNode';
|
||||
import {createListItemNode} from 'outline/ListItemNode';
|
||||
|
||||
describe('OutlineNodeHelpers tests', () => {
|
||||
initializeUnitTest((testEnv) => {
|
||||
@ -110,5 +112,186 @@ describe('OutlineNodeHelpers tests', () => {
|
||||
});
|
||||
expect(dfsKeys).toEqual(expectedKeys);
|
||||
});
|
||||
|
||||
test('getTopListNode should return the top list node when the list is a direct child of the RootNode', async () => {
|
||||
const editor: OutlineEditor = testEnv.editor;
|
||||
await editor.update((view: View) => {
|
||||
// Root
|
||||
// |- ListNode
|
||||
// |- ListItemNode
|
||||
// |- ListItemNode
|
||||
// |- ListNode
|
||||
// |- ListItemNode
|
||||
const root = view.getRoot();
|
||||
const topListNode = createListNode('ul');
|
||||
const secondLevelListNode = createListNode('ul');
|
||||
const listItem1 = createListItemNode();
|
||||
const listItem2 = createListItemNode();
|
||||
const listItem3 = createListItemNode();
|
||||
root.append(topListNode);
|
||||
topListNode.append(listItem1);
|
||||
topListNode.append(listItem2);
|
||||
topListNode.append(secondLevelListNode);
|
||||
secondLevelListNode.append(listItem3);
|
||||
const result = getTopListNode(listItem3);
|
||||
expect(result.getKey()).toEqual(topListNode.getKey());
|
||||
});
|
||||
});
|
||||
|
||||
test('getTopListNode should return the top list node when the list is not a direct child of the RootNode', async () => {
|
||||
const editor: OutlineEditor = testEnv.editor;
|
||||
await editor.update((view: View) => {
|
||||
// Root
|
||||
// |- ParagaphNode
|
||||
// |- ListNode
|
||||
// |- ListItemNode
|
||||
// |- ListItemNode
|
||||
// |- ListNode
|
||||
// |- ListItemNode
|
||||
const root = view.getRoot();
|
||||
const paragraphNode = createParagraphNode();
|
||||
const topListNode = createListNode('ul');
|
||||
const secondLevelListNode = createListNode('ul');
|
||||
const listItem1 = createListItemNode();
|
||||
const listItem2 = createListItemNode();
|
||||
const listItem3 = createListItemNode();
|
||||
root.append(paragraphNode);
|
||||
paragraphNode.append(topListNode);
|
||||
topListNode.append(listItem1);
|
||||
topListNode.append(listItem2);
|
||||
topListNode.append(secondLevelListNode);
|
||||
secondLevelListNode.append(listItem3);
|
||||
const result = getTopListNode(listItem3);
|
||||
expect(result.getKey()).toEqual(topListNode.getKey());
|
||||
});
|
||||
});
|
||||
|
||||
test('getTopListNode should return the top list node when the list item is deeply nested.', async () => {
|
||||
const editor: OutlineEditor = testEnv.editor;
|
||||
await editor.update((view: View) => {
|
||||
// Root
|
||||
// |- ParagaphNode
|
||||
// |- ListNode
|
||||
// |- ListItemNode
|
||||
// |- ListNode
|
||||
// |- ListItemNode
|
||||
// |- ListNode
|
||||
// |- ListItemNode
|
||||
// |- ListItemNode
|
||||
const root = view.getRoot();
|
||||
const paragraphNode = createParagraphNode();
|
||||
const topListNode = createListNode('ul');
|
||||
const secondLevelListNode = createListNode('ul');
|
||||
const thirdLevelListNode = createListNode('ul');
|
||||
const listItem1 = createListItemNode();
|
||||
const listItem2 = createListItemNode();
|
||||
const listItem3 = createListItemNode();
|
||||
const listItem4 = createListItemNode();
|
||||
root.append(paragraphNode);
|
||||
paragraphNode.append(topListNode);
|
||||
topListNode.append(listItem1);
|
||||
listItem1.append(secondLevelListNode);
|
||||
secondLevelListNode.append(listItem2);
|
||||
listItem2.append(thirdLevelListNode);
|
||||
thirdLevelListNode.append(listItem3);
|
||||
topListNode.append(listItem4);
|
||||
const result = getTopListNode(listItem4);
|
||||
expect(result.getKey()).toEqual(topListNode.getKey());
|
||||
});
|
||||
});
|
||||
|
||||
test('isLastItemInList should return true if the listItem is the last in a nested list.', async () => {
|
||||
const editor: OutlineEditor = testEnv.editor;
|
||||
await editor.update((view: View) => {
|
||||
// Root
|
||||
// |- ListNode
|
||||
// |- ListItemNode
|
||||
// |- ListNode
|
||||
// |- ListItemNode
|
||||
// |- ListNode
|
||||
// |- ListItemNode
|
||||
const root = view.getRoot();
|
||||
const topListNode = createListNode('ul');
|
||||
const secondLevelListNode = createListNode('ul');
|
||||
const thirdLevelListNode = createListNode('ul');
|
||||
const listItem1 = createListItemNode();
|
||||
const listItem2 = createListItemNode();
|
||||
const listItem3 = createListItemNode();
|
||||
root.append(topListNode);
|
||||
topListNode.append(listItem1);
|
||||
listItem1.append(secondLevelListNode);
|
||||
secondLevelListNode.append(listItem2);
|
||||
listItem2.append(thirdLevelListNode);
|
||||
thirdLevelListNode.append(listItem3);
|
||||
const result = isLastItemInList(listItem3);
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('isLastItemInList should return true if the listItem is the last in a non-nested list.', async () => {
|
||||
const editor: OutlineEditor = testEnv.editor;
|
||||
await editor.update((view: View) => {
|
||||
// Root
|
||||
// |- ListNode
|
||||
// |- ListItemNode
|
||||
// |- ListItemNode
|
||||
const root = view.getRoot();
|
||||
const topListNode = createListNode('ul');
|
||||
const listItem1 = createListItemNode();
|
||||
const listItem2 = createListItemNode();
|
||||
root.append(topListNode);
|
||||
topListNode.append(listItem1);
|
||||
topListNode.append(listItem2);
|
||||
const result = isLastItemInList(listItem2);
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('isLastItemInList should return false if the listItem is not the last in a nested list.', async () => {
|
||||
const editor: OutlineEditor = testEnv.editor;
|
||||
await editor.update((view: View) => {
|
||||
// Root
|
||||
// |- ListNode
|
||||
// |- ListItemNode
|
||||
// |- ListNode
|
||||
// |- ListItemNode
|
||||
// |- ListNode
|
||||
// |- ListItemNode
|
||||
const root = view.getRoot();
|
||||
const topListNode = createListNode('ul');
|
||||
const secondLevelListNode = createListNode('ul');
|
||||
const thirdLevelListNode = createListNode('ul');
|
||||
const listItem1 = createListItemNode();
|
||||
const listItem2 = createListItemNode();
|
||||
const listItem3 = createListItemNode();
|
||||
root.append(topListNode);
|
||||
topListNode.append(listItem1);
|
||||
listItem1.append(secondLevelListNode);
|
||||
secondLevelListNode.append(listItem2);
|
||||
listItem2.append(thirdLevelListNode);
|
||||
thirdLevelListNode.append(listItem3);
|
||||
const result = isLastItemInList(listItem2);
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
test('isLastItemInList should return true if the listItem is not the last in a non-nested list.', async () => {
|
||||
const editor: OutlineEditor = testEnv.editor;
|
||||
await editor.update((view: View) => {
|
||||
// Root
|
||||
// |- ListNode
|
||||
// |- ListItemNode
|
||||
// |- ListItemNode
|
||||
const root = view.getRoot();
|
||||
const topListNode = createListNode('ul');
|
||||
const listItem1 = createListItemNode();
|
||||
const listItem2 = createListItemNode();
|
||||
root.append(topListNode);
|
||||
topListNode.append(listItem1);
|
||||
topListNode.append(listItem2);
|
||||
const result = isLastItemInList(listItem1);
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -42,5 +42,6 @@
|
||||
"40": "createNode: node does not exist in nodeMap",
|
||||
"41": "reconcileNode: prevNode or nextNode does not exist in nodeMap",
|
||||
"42": "reconcileNode: parentDOM is null",
|
||||
"43": "Reconciliation: could not find DOM element for node key \"${key}\""
|
||||
"43": "Reconciliation: could not find DOM element for node key \"${key}\"",
|
||||
"44": "A ListItemNode must have a ListNode for a parent."
|
||||
}
|
||||
|
Reference in New Issue
Block a user