Skip to content

Commit 94917ee

Browse files
authored
fix(lists): fixed merge list (#373)
1 parent f8ac7e4 commit 94917ee

File tree

5 files changed

+87
-71
lines changed

5 files changed

+87
-71
lines changed

src/extensions/markdown/Lists/commands.test.ts

Lines changed: 0 additions & 43 deletions
This file was deleted.

src/extensions/markdown/Lists/commands.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import type {NodeType} from 'prosemirror-model';
2-
import {liftListItem, wrapInList} from 'prosemirror-schema-list';
2+
import {wrapInList} from 'prosemirror-schema-list';
33
import type {Command} from 'prosemirror-state';
44

55
import {joinPreviousBlock} from '../../../commands/join';
6-
import {get$CursorAtBlockStart} from '../../../utils/selection';
76

8-
import {findAnyParentList, isListItemNode, isListNode, isListOrItemNode, liType} from './utils';
7+
import {findAnyParentList, isListNode, isListOrItemNode} from './utils';
98

109
export function toList(listType: NodeType): Command {
1110
return (state, dispatch) => {
@@ -20,17 +19,6 @@ export function toList(listType: NodeType): Command {
2019
};
2120
}
2221

23-
export const liftIfCursorIsAtBeginningOfItem: Command = (state, dispatch) => {
24-
const $cursor = get$CursorAtBlockStart(state.selection);
25-
if (!$cursor) return false;
26-
const {schema} = state;
27-
const parentBlock = $cursor.node($cursor.depth - 1);
28-
if (parentBlock.firstChild === $cursor.parent && isListItemNode(parentBlock)) {
29-
return liftListItem(liType(schema))(state, dispatch);
30-
}
31-
return false;
32-
};
33-
3422
export const joinPrevList = joinPreviousBlock({
3523
checkPrevNode: isListNode,
3624
skipNode: isListOrItemNode,

src/extensions/markdown/Lists/index.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
import {chainCommands} from 'prosemirror-commands';
21
import {liftListItem, sinkListItem, splitListItem} from 'prosemirror-schema-list';
32

43
import type {Action, ExtensionAuto, Keymap} from '../../../core';
54
import {withLogAction} from '../../../utils/keymap';
65

76
import {ListsSpecs, blType, liType, olType} from './ListsSpecs';
87
import {actions} from './actions';
9-
import {joinPrevList, liftIfCursorIsAtBeginningOfItem, toList} from './commands';
8+
import {joinPrevList, toList} from './commands';
109
import {ListAction} from './const';
1110
import {ListsInputRulesExtension, ListsInputRulesOptions} from './inputrules';
1211
import {mergeListsPlugin} from './plugins/MergeListsPlugin';
@@ -42,7 +41,7 @@ export const Lists: ExtensionAuto<ListsOptions> = (builder, opts) => {
4241
builder.addKeymap(
4342
({schema}) => ({
4443
Enter: splitListItem(liType(schema)),
45-
Backspace: chainCommands(liftIfCursorIsAtBeginningOfItem, joinPrevList),
44+
Backspace: joinPrevList,
4645
}),
4746
builder.Priority.Low,
4847
);

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

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const {schema, markupParser} = new ExtensionsManager({
1313
extensions: (builder) => builder.use(BaseSchemaSpecs, {}).use(ListsSpecs).use(CodeSpecs),
1414
}).buildDeps();
1515

16-
const {doc, p, li, ul, c} = builders<'doc' | 'p' | 'li' | 'ul' | 'ol', 'c'>(schema, {
16+
const {doc, p, li, ul, c, ol} = builders<'doc' | 'p' | 'li' | 'ul' | 'ol', 'c'>(schema, {
1717
doc: {nodeType: BaseNode.Doc},
1818
p: {nodeType: BaseNode.Paragraph},
1919
li: {nodeType: ListNode.ListItem},
@@ -32,9 +32,21 @@ const nestedListsMarkup = `
3232
* Very easy!
3333
`;
3434

35+
const orderedListMarkup = `
36+
1. Create a list by starting a line with \`+\`, \`-\`, or \`*\`
37+
2. Sub-lists are made by indenting 2 spaces:
38+
1. Marker character change forces new list start:
39+
2. Ac tristique libero volutpat at
40+
3. Facilisis in pretium nisl aliquet
41+
4. Nulla volutpat aliquam velit
42+
43+
1. Very easy!
44+
2. Very easy!
45+
`;
46+
3547
describe('Lists extension', () => {
3648
describe('MergeListsPlugin', () => {
37-
it('should merge lists', () => {
49+
it('should merge bullet lists', () => {
3850
const view = new EditorView(null, {
3951
state: EditorState.create({schema, plugins: [mergeListsPlugin()]}),
4052
});
@@ -83,13 +95,66 @@ describe('Lists extension', () => {
8395
li({[ListsAttr.Markup]: '-'}, p('Nulla volutpat aliquam velit')),
8496
),
8597
),
98+
li({[ListsAttr.Markup]: '*'}, p('Very easy!')),
8699
),
87-
ul(
100+
),
101+
);
102+
});
103+
104+
it('should merge ordered lists', () => {
105+
const view = new EditorView(null, {
106+
state: EditorState.create({schema, plugins: [mergeListsPlugin()]}),
107+
});
108+
view.dispatch(
109+
view.state.tr.replaceWith(
110+
0,
111+
view.state.doc.nodeSize - 2,
112+
markupParser.parse(orderedListMarkup).content,
113+
),
114+
);
115+
expect(view.state.doc).toMatchNode(
116+
doc(
117+
ol(
88118
{
89-
[ListsAttr.Tight]: true,
90-
[ListsAttr.Bullet]: '*',
119+
[ListsAttr.Bullet]: '.',
91120
},
92-
li({[ListsAttr.Markup]: '*'}, p('Very easy!')),
121+
li(
122+
{
123+
[ListsAttr.Markup]: '.',
124+
},
125+
p(
126+
'Create a list by starting a line with ',
127+
c('+'),
128+
', ',
129+
c('-'),
130+
', or ',
131+
c('*'),
132+
),
133+
),
134+
li(
135+
{
136+
[ListsAttr.Markup]: '.',
137+
},
138+
p('Sub-lists are made by indenting 2 spaces:'),
139+
ol(
140+
{
141+
[ListsAttr.Tight]: true,
142+
[ListsAttr.Bullet]: '.',
143+
},
144+
li(
145+
{[ListsAttr.Markup]: '.'},
146+
p('Marker character change forces new list start:'),
147+
),
148+
li({[ListsAttr.Markup]: '.'}, p('Ac tristique libero volutpat at')),
149+
li(
150+
{[ListsAttr.Markup]: '.'},
151+
p('Facilisis in pretium nisl aliquet'),
152+
),
153+
li({[ListsAttr.Markup]: '.'}, p('Nulla volutpat aliquam velit')),
154+
),
155+
),
156+
li({[ListsAttr.Markup]: '.'}, p('Very easy!')),
157+
li({[ListsAttr.Markup]: '.'}, p('Very easy!')),
93158
),
94159
),
95160
);

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type {Node} from 'prosemirror-model';
12
import {Plugin, Transaction} from 'prosemirror-state';
23
import {findChildren, hasParentNode} from 'prosemirror-utils';
34

@@ -27,12 +28,18 @@ function mergeAdjacentNodesWithSameType(
2728
tr: Transaction,
2829
nodes: ReturnType<typeof findChildren>,
2930
): void {
30-
for (let i = nodes.length - 1; i > 0; i--) {
31-
const prev = nodes[i - 1];
32-
const next = nodes[i];
31+
const posAfterMap: Partial<Record<number, Node>> = {};
3332

34-
if (prev.node.type === next.node.type && prev.pos + prev.node.nodeSize === next.pos) {
35-
tr.join(next.pos);
33+
for (const item of nodes) {
34+
const posBefore = item.pos;
35+
const posAfter = posBefore + item.node.nodeSize;
36+
37+
posAfterMap[posAfter] = item.node;
38+
39+
const nodeBefore = posAfterMap[posBefore];
40+
if (nodeBefore?.type === item.node.type) {
41+
tr.join(tr.mapping.map(posBefore));
42+
posAfterMap[posBefore] = undefined;
3643
}
3744
}
3845
}

0 commit comments

Comments
 (0)