Skip to content

Commit e4547c5

Browse files
committed
feat(Lists): updated logic for nested
1 parent 3ba4923 commit e4547c5

File tree

2 files changed

+20
-16
lines changed

2 files changed

+20
-16
lines changed

src/extensions/markdown/Lists/plugins/CollapseListsPlugin.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ describe('CollapseListsPlugin', () => {
6464
view.state.tr.replaceWith(0, view.state.doc.nodeSize - 2, initialDoc.content),
6565
);
6666

67-
expect(view.state.doc).toMatchNode(doc(ul(li(ul(li(p('Deep nested item')))))));
67+
expect(view.state.doc).toMatchNode(doc(ul(li(p('Deep nested item')))));
6868
});
6969

7070
it('should collapse multiple nested lists in a single document', () => {

src/extensions/markdown/Lists/plugins/CollapseListsPlugin.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import {Fragment, type Node, Slice} from 'prosemirror-model';
1+
import {Fragment, type Node} from 'prosemirror-model';
22
import {Plugin, TextSelection, type Transaction} from 'prosemirror-state';
33
import {findChildren, hasParentNode} from 'prosemirror-utils';
44

55
import {getChildrenOfNode} from '../../../../utils';
6-
import {isListItemNode, isListNode} from '../utils';
6+
import {isListItemNode, isListNode, liType} from '../utils';
77

88
export const collapseListsPlugin = () =>
99
new Plugin({
@@ -17,9 +17,15 @@ export const collapseListsPlugin = () =>
1717
if (!hasParentList) return null;
1818

1919
const {tr} = newState;
20-
const listNodes = findChildren(tr.doc, isListNode, true);
20+
let prevStepsCount = -1;
21+
let currentStepsCount = 0;
2122

22-
collapseEmptyListItems(tr, listNodes);
23+
// execute until there are no nested lists.
24+
while (prevStepsCount !== currentStepsCount) {
25+
const listNodes = findChildren(tr.doc, isListNode, true);
26+
prevStepsCount = currentStepsCount;
27+
currentStepsCount = collapseEmptyListItems(tr, listNodes);
28+
}
2329

2430
return tr.docChanged ? tr : null;
2531
},
@@ -28,13 +34,14 @@ export const collapseListsPlugin = () =>
2834
export function collapseEmptyListItems(
2935
tr: Transaction,
3036
nodes: ReturnType<typeof findChildren>,
31-
): void {
37+
): number {
38+
const stepsCountBefore = tr.steps.length;
3239
nodes.reverse().forEach((list) => {
3340
const listNode = list.node;
3441
const listPos = list.pos;
35-
const childrenOfNodes = getChildrenOfNode(listNode).reverse();
42+
const childrenOfList = getChildrenOfNode(listNode).reverse();
3643

37-
childrenOfNodes.forEach(({node: itemNode, offset}) => {
44+
childrenOfList.forEach(({node: itemNode, offset}) => {
3845
if (isListItemNode(itemNode)) {
3946
const {firstChild} = itemNode;
4047
const listItemNodePos = listPos + 1 + offset;
@@ -45,25 +52,20 @@ export function collapseEmptyListItems(
4552
const nestedList = firstChild.content;
4653

4754
// nodes at the same level as the list
48-
const remainingNodes: Node[] = [];
49-
itemNode.forEach((child, _pos, index) => {
50-
if (index > 0) {
51-
remainingNodes.push(child);
52-
}
53-
});
55+
const remainingNodes = itemNode.content.content.slice(1);
5456

5557
const listItems = remainingNodes.length
5658
? nestedList.append(
5759
Fragment.from(
58-
tr.doc.type.schema.nodes.list_item.create(null, remainingNodes),
60+
liType(tr.doc.type.schema).create(null, remainingNodes),
5961
),
6062
)
6163
: nestedList;
6264

6365
const mappedStart = tr.mapping.map(listItemNodePos);
6466
const mappedEnd = tr.mapping.map(listItemNodePos + itemNode.nodeSize);
6567

66-
tr.replace(mappedStart, mappedEnd, new Slice(listItems, 0, 0));
68+
tr.replaceWith(mappedStart, mappedEnd, listItems);
6769

6870
const closestTextNodePos = findClosestTextNodePos(
6971
tr.doc,
@@ -76,6 +78,8 @@ export function collapseEmptyListItems(
7678
}
7779
});
7880
});
81+
82+
return tr.steps.length - stepsCountBefore;
7983
}
8084

8185
function findClosestTextNodePos(doc: Node, pos: number): number | null {

0 commit comments

Comments
 (0)