@lexical/list 0.12.2 → 0.12.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LexicalList.dev.js +69 -272
- package/LexicalList.prod.js +8 -8
- package/LexicalListItemNode.d.ts +3 -2
- package/package.json +3 -3
package/LexicalList.dev.js
CHANGED
|
@@ -16,63 +16,54 @@ var utils = require('@lexical/utils');
|
|
|
16
16
|
* LICENSE file in the root directory of this source tree.
|
|
17
17
|
*
|
|
18
18
|
*/
|
|
19
|
+
|
|
19
20
|
/**
|
|
20
21
|
* Checks the depth of listNode from the root node.
|
|
21
22
|
* @param listNode - The ListNode to be checked.
|
|
22
23
|
* @returns The depth of the ListNode.
|
|
23
24
|
*/
|
|
24
|
-
|
|
25
25
|
function $getListDepth(listNode) {
|
|
26
26
|
let depth = 1;
|
|
27
27
|
let parent = listNode.getParent();
|
|
28
|
-
|
|
29
28
|
while (parent != null) {
|
|
30
29
|
if ($isListItemNode(parent)) {
|
|
31
30
|
const parentList = parent.getParent();
|
|
32
|
-
|
|
33
31
|
if ($isListNode(parentList)) {
|
|
34
32
|
depth++;
|
|
35
33
|
parent = parentList.getParent();
|
|
36
34
|
continue;
|
|
37
35
|
}
|
|
38
|
-
|
|
39
36
|
{
|
|
40
37
|
throw Error(`A ListItemNode must have a ListNode for a parent.`);
|
|
41
38
|
}
|
|
42
39
|
}
|
|
43
|
-
|
|
44
40
|
return depth;
|
|
45
41
|
}
|
|
46
|
-
|
|
47
42
|
return depth;
|
|
48
43
|
}
|
|
44
|
+
|
|
49
45
|
/**
|
|
50
46
|
* Finds the nearest ancestral ListNode and returns it, throws an invariant if listItem is not a ListItemNode.
|
|
51
47
|
* @param listItem - The node to be checked.
|
|
52
48
|
* @returns The ListNode found.
|
|
53
49
|
*/
|
|
54
|
-
|
|
55
50
|
function $getTopListNode(listItem) {
|
|
56
51
|
let list = listItem.getParent();
|
|
57
|
-
|
|
58
52
|
if (!$isListNode(list)) {
|
|
59
53
|
{
|
|
60
54
|
throw Error(`A ListItemNode must have a ListNode for a parent.`);
|
|
61
55
|
}
|
|
62
56
|
}
|
|
63
|
-
|
|
64
57
|
let parent = list;
|
|
65
|
-
|
|
66
58
|
while (parent !== null) {
|
|
67
59
|
parent = parent.getParent();
|
|
68
|
-
|
|
69
60
|
if ($isListNode(parent)) {
|
|
70
61
|
list = parent;
|
|
71
62
|
}
|
|
72
63
|
}
|
|
73
|
-
|
|
74
64
|
return list;
|
|
75
65
|
}
|
|
66
|
+
|
|
76
67
|
/**
|
|
77
68
|
* A recursive Depth-First Search (Postorder Traversal) that finds all of a node's children
|
|
78
69
|
* that are of type ListItemNode and returns them in an array.
|
|
@@ -80,33 +71,30 @@ function $getTopListNode(listItem) {
|
|
|
80
71
|
* @returns An array containing all nodes of type ListItemNode found.
|
|
81
72
|
*/
|
|
82
73
|
// This should probably be $getAllChildrenOfType
|
|
83
|
-
|
|
84
74
|
function $getAllListItems(node) {
|
|
85
75
|
let listItemNodes = [];
|
|
86
76
|
const listChildren = node.getChildren().filter($isListItemNode);
|
|
87
|
-
|
|
88
77
|
for (let i = 0; i < listChildren.length; i++) {
|
|
89
78
|
const listItemNode = listChildren[i];
|
|
90
79
|
const firstChild = listItemNode.getFirstChild();
|
|
91
|
-
|
|
92
80
|
if ($isListNode(firstChild)) {
|
|
93
81
|
listItemNodes = listItemNodes.concat($getAllListItems(firstChild));
|
|
94
82
|
} else {
|
|
95
83
|
listItemNodes.push(listItemNode);
|
|
96
84
|
}
|
|
97
85
|
}
|
|
98
|
-
|
|
99
86
|
return listItemNodes;
|
|
100
87
|
}
|
|
88
|
+
|
|
101
89
|
/**
|
|
102
90
|
* Checks to see if the passed node is a ListItemNode and has a ListNode as a child.
|
|
103
91
|
* @param node - The node to be checked.
|
|
104
92
|
* @returns true if the node is a ListItemNode and has a ListNode child, false otherwise.
|
|
105
93
|
*/
|
|
106
|
-
|
|
107
94
|
function isNestedListNode(node) {
|
|
108
95
|
return $isListItemNode(node) && $isListNode(node.getFirstChild());
|
|
109
96
|
}
|
|
97
|
+
|
|
110
98
|
/**
|
|
111
99
|
* Takes a deeply nested ListNode or ListItemNode and traverses up the branch to delete the first
|
|
112
100
|
* ancestral ListNode (which could be the root ListNode) or ListItemNode with siblings, essentially
|
|
@@ -114,7 +102,6 @@ function isNestedListNode(node) {
|
|
|
114
102
|
* Should not break ListItem -> List -> ListItem chain as empty List/ItemNodes should be removed on .remove().
|
|
115
103
|
* @param sublist - The nested ListNode or ListItemNode to be brought up the branch.
|
|
116
104
|
*/
|
|
117
|
-
|
|
118
105
|
function $removeHighestEmptyListParent(sublist) {
|
|
119
106
|
// Nodes may be repeatedly indented, to create deeply nested lists that each
|
|
120
107
|
// contain just one bullet.
|
|
@@ -123,25 +110,21 @@ function $removeHighestEmptyListParent(sublist) {
|
|
|
123
110
|
// (e.g. is actually part of the list contents) and delete that, or delete
|
|
124
111
|
// the root of the list (if no list nodes have siblings.)
|
|
125
112
|
let emptyListPtr = sublist;
|
|
126
|
-
|
|
127
113
|
while (emptyListPtr.getNextSibling() == null && emptyListPtr.getPreviousSibling() == null) {
|
|
128
114
|
const parent = emptyListPtr.getParent();
|
|
129
|
-
|
|
130
115
|
if (parent == null || !($isListItemNode(emptyListPtr) || $isListNode(emptyListPtr))) {
|
|
131
116
|
break;
|
|
132
117
|
}
|
|
133
|
-
|
|
134
118
|
emptyListPtr = parent;
|
|
135
119
|
}
|
|
136
|
-
|
|
137
120
|
emptyListPtr.remove();
|
|
138
121
|
}
|
|
122
|
+
|
|
139
123
|
/**
|
|
140
124
|
* Wraps a node into a ListItemNode.
|
|
141
125
|
* @param node - The node to be wrapped into a ListItemNode
|
|
142
126
|
* @returns The ListItemNode which the passed node is wrapped in.
|
|
143
127
|
*/
|
|
144
|
-
|
|
145
128
|
function wrapInListItem(node) {
|
|
146
129
|
const listItemWrapper = $createListItemNode();
|
|
147
130
|
return listItemWrapper.append(node);
|
|
@@ -154,15 +137,12 @@ function wrapInListItem(node) {
|
|
|
154
137
|
* LICENSE file in the root directory of this source tree.
|
|
155
138
|
*
|
|
156
139
|
*/
|
|
157
|
-
|
|
158
140
|
function $isSelectingEmptyListItem(anchorNode, nodes) {
|
|
159
141
|
return $isListItemNode(anchorNode) && (nodes.length === 0 || nodes.length === 1 && anchorNode.is(nodes[0]) && anchorNode.getChildrenSize() === 0);
|
|
160
142
|
}
|
|
161
|
-
|
|
162
143
|
function $getListItemValue(listItem) {
|
|
163
144
|
const list = listItem.getParent();
|
|
164
145
|
let value = 1;
|
|
165
|
-
|
|
166
146
|
if (list != null) {
|
|
167
147
|
if (!$isListNode(list)) {
|
|
168
148
|
{
|
|
@@ -172,19 +152,16 @@ function $getListItemValue(listItem) {
|
|
|
172
152
|
value = list.getStart();
|
|
173
153
|
}
|
|
174
154
|
}
|
|
175
|
-
|
|
176
155
|
const siblings = listItem.getPreviousSiblings();
|
|
177
|
-
|
|
178
156
|
for (let i = 0; i < siblings.length; i++) {
|
|
179
157
|
const sibling = siblings[i];
|
|
180
|
-
|
|
181
158
|
if ($isListItemNode(sibling) && !$isListNode(sibling.getFirstChild())) {
|
|
182
159
|
value++;
|
|
183
160
|
}
|
|
184
161
|
}
|
|
185
|
-
|
|
186
162
|
return value;
|
|
187
163
|
}
|
|
164
|
+
|
|
188
165
|
/**
|
|
189
166
|
* Inserts a new ListNode. If the selection's anchor node is an empty ListItemNode and is a child of
|
|
190
167
|
* the root/shadow root, it will replace the ListItemNode with a ListNode and the old ListItemNode.
|
|
@@ -195,55 +172,42 @@ function $getListItemValue(listItem) {
|
|
|
195
172
|
* @param editor - The lexical editor.
|
|
196
173
|
* @param listType - The type of list, "number" | "bullet" | "check".
|
|
197
174
|
*/
|
|
198
|
-
|
|
199
|
-
|
|
200
175
|
function insertList(editor, listType) {
|
|
201
176
|
editor.update(() => {
|
|
202
177
|
const selection = lexical.$getSelection();
|
|
203
|
-
|
|
204
178
|
if (lexical.$isRangeSelection(selection) || lexical.DEPRECATED_$isGridSelection(selection)) {
|
|
205
179
|
const nodes = selection.getNodes();
|
|
206
180
|
const anchor = selection.anchor;
|
|
207
181
|
const anchorNode = anchor.getNode();
|
|
208
182
|
const anchorNodeParent = anchorNode.getParent();
|
|
209
|
-
|
|
210
183
|
if ($isSelectingEmptyListItem(anchorNode, nodes)) {
|
|
211
184
|
const list = $createListNode(listType);
|
|
212
|
-
|
|
213
185
|
if (lexical.$isRootOrShadowRoot(anchorNodeParent)) {
|
|
214
186
|
anchorNode.replace(list);
|
|
215
187
|
const listItem = $createListItemNode();
|
|
216
|
-
|
|
217
188
|
if (lexical.$isElementNode(anchorNode)) {
|
|
218
189
|
listItem.setFormat(anchorNode.getFormatType());
|
|
219
190
|
listItem.setIndent(anchorNode.getIndent());
|
|
220
191
|
}
|
|
221
|
-
|
|
222
192
|
list.append(listItem);
|
|
223
193
|
} else if ($isListItemNode(anchorNode)) {
|
|
224
194
|
const parent = anchorNode.getParentOrThrow();
|
|
225
195
|
append(list, parent.getChildren());
|
|
226
196
|
parent.replace(list);
|
|
227
197
|
}
|
|
228
|
-
|
|
229
198
|
return;
|
|
230
199
|
} else {
|
|
231
200
|
const handled = new Set();
|
|
232
|
-
|
|
233
201
|
for (let i = 0; i < nodes.length; i++) {
|
|
234
202
|
const node = nodes[i];
|
|
235
|
-
|
|
236
203
|
if (lexical.$isElementNode(node) && node.isEmpty() && !handled.has(node.getKey())) {
|
|
237
204
|
createListOrMerge(node, listType);
|
|
238
205
|
continue;
|
|
239
206
|
}
|
|
240
|
-
|
|
241
207
|
if (lexical.$isLeafNode(node)) {
|
|
242
208
|
let parent = node.getParent();
|
|
243
|
-
|
|
244
209
|
while (parent != null) {
|
|
245
210
|
const parentKey = parent.getKey();
|
|
246
|
-
|
|
247
211
|
if ($isListNode(parent)) {
|
|
248
212
|
if (!handled.has(parentKey)) {
|
|
249
213
|
const newListNode = $createListNode(listType);
|
|
@@ -252,17 +216,14 @@ function insertList(editor, listType) {
|
|
|
252
216
|
updateChildrenListItemValue(newListNode);
|
|
253
217
|
handled.add(parentKey);
|
|
254
218
|
}
|
|
255
|
-
|
|
256
219
|
break;
|
|
257
220
|
} else {
|
|
258
221
|
const nextParent = parent.getParent();
|
|
259
|
-
|
|
260
222
|
if (lexical.$isRootOrShadowRoot(nextParent) && !handled.has(parentKey)) {
|
|
261
223
|
handled.add(parentKey);
|
|
262
224
|
createListOrMerge(parent, listType);
|
|
263
225
|
break;
|
|
264
226
|
}
|
|
265
|
-
|
|
266
227
|
parent = nextParent;
|
|
267
228
|
}
|
|
268
229
|
}
|
|
@@ -272,32 +233,28 @@ function insertList(editor, listType) {
|
|
|
272
233
|
}
|
|
273
234
|
});
|
|
274
235
|
}
|
|
275
|
-
|
|
276
236
|
function append(node, nodesToAppend) {
|
|
277
237
|
node.splice(node.getChildrenSize(), 0, nodesToAppend);
|
|
278
238
|
}
|
|
279
|
-
|
|
280
239
|
function createListOrMerge(node, listType) {
|
|
281
240
|
if ($isListNode(node)) {
|
|
282
241
|
return node;
|
|
283
242
|
}
|
|
284
|
-
|
|
285
243
|
const previousSibling = node.getPreviousSibling();
|
|
286
244
|
const nextSibling = node.getNextSibling();
|
|
287
245
|
const listItem = $createListItemNode();
|
|
288
246
|
listItem.setFormat(node.getFormatType());
|
|
289
247
|
listItem.setIndent(node.getIndent());
|
|
290
248
|
append(listItem, node.getChildren());
|
|
291
|
-
|
|
292
249
|
if ($isListNode(previousSibling) && listType === previousSibling.getListType()) {
|
|
293
250
|
previousSibling.append(listItem);
|
|
294
|
-
node.remove();
|
|
251
|
+
node.remove();
|
|
252
|
+
// if the same type of list is on both sides, merge them.
|
|
295
253
|
|
|
296
254
|
if ($isListNode(nextSibling) && listType === nextSibling.getListType()) {
|
|
297
255
|
append(previousSibling, nextSibling.getChildren());
|
|
298
256
|
nextSibling.remove();
|
|
299
257
|
}
|
|
300
|
-
|
|
301
258
|
return previousSibling;
|
|
302
259
|
} else if ($isListNode(nextSibling) && listType === nextSibling.getListType()) {
|
|
303
260
|
nextSibling.getFirstChildOrThrow().insertBefore(listItem);
|
|
@@ -311,32 +268,28 @@ function createListOrMerge(node, listType) {
|
|
|
311
268
|
return list;
|
|
312
269
|
}
|
|
313
270
|
}
|
|
271
|
+
|
|
314
272
|
/**
|
|
315
273
|
* A recursive function that goes through each list and their children, including nested lists,
|
|
316
274
|
* appending list2 children after list1 children and updating ListItemNode values.
|
|
317
275
|
* @param list1 - The first list to be merged.
|
|
318
276
|
* @param list2 - The second list to be merged.
|
|
319
277
|
*/
|
|
320
|
-
|
|
321
|
-
|
|
322
278
|
function mergeLists(list1, list2) {
|
|
323
279
|
const listItem1 = list1.getLastChild();
|
|
324
280
|
const listItem2 = list2.getFirstChild();
|
|
325
|
-
|
|
326
281
|
if (listItem1 && listItem2 && isNestedListNode(listItem1) && isNestedListNode(listItem2)) {
|
|
327
282
|
mergeLists(listItem1.getFirstChild(), listItem2.getFirstChild());
|
|
328
283
|
listItem2.remove();
|
|
329
284
|
}
|
|
330
|
-
|
|
331
285
|
const toMerge = list2.getChildren();
|
|
332
|
-
|
|
333
286
|
if (toMerge.length > 0) {
|
|
334
287
|
list1.append(...toMerge);
|
|
335
288
|
updateChildrenListItemValue(list1);
|
|
336
289
|
}
|
|
337
|
-
|
|
338
290
|
list2.remove();
|
|
339
291
|
}
|
|
292
|
+
|
|
340
293
|
/**
|
|
341
294
|
* Searches for the nearest ancestral ListNode and removes it. If selection is an empty ListItemNode
|
|
342
295
|
* it will remove the whole list, including the ListItemNode. For each ListItemNode in the ListNode,
|
|
@@ -344,63 +297,55 @@ function mergeLists(list1, list2) {
|
|
|
344
297
|
* inside a ListItemNode will be appended to the new ParagraphNodes.
|
|
345
298
|
* @param editor - The lexical editor.
|
|
346
299
|
*/
|
|
347
|
-
|
|
348
300
|
function removeList(editor) {
|
|
349
301
|
editor.update(() => {
|
|
350
302
|
const selection = lexical.$getSelection();
|
|
351
|
-
|
|
352
303
|
if (lexical.$isRangeSelection(selection)) {
|
|
353
304
|
const listNodes = new Set();
|
|
354
305
|
const nodes = selection.getNodes();
|
|
355
306
|
const anchorNode = selection.anchor.getNode();
|
|
356
|
-
|
|
357
307
|
if ($isSelectingEmptyListItem(anchorNode, nodes)) {
|
|
358
308
|
listNodes.add($getTopListNode(anchorNode));
|
|
359
309
|
} else {
|
|
360
310
|
for (let i = 0; i < nodes.length; i++) {
|
|
361
311
|
const node = nodes[i];
|
|
362
|
-
|
|
363
312
|
if (lexical.$isLeafNode(node)) {
|
|
364
313
|
const listItemNode = utils.$getNearestNodeOfType(node, ListItemNode);
|
|
365
|
-
|
|
366
314
|
if (listItemNode != null) {
|
|
367
315
|
listNodes.add($getTopListNode(listItemNode));
|
|
368
316
|
}
|
|
369
317
|
}
|
|
370
318
|
}
|
|
371
319
|
}
|
|
372
|
-
|
|
373
320
|
for (const listNode of listNodes) {
|
|
374
321
|
let insertionPoint = listNode;
|
|
375
322
|
const listItems = $getAllListItems(listNode);
|
|
376
|
-
|
|
377
323
|
for (const listItemNode of listItems) {
|
|
378
324
|
const paragraph = lexical.$createParagraphNode();
|
|
379
325
|
append(paragraph, listItemNode.getChildren());
|
|
380
326
|
insertionPoint.insertAfter(paragraph);
|
|
381
|
-
insertionPoint = paragraph;
|
|
327
|
+
insertionPoint = paragraph;
|
|
328
|
+
|
|
329
|
+
// When the anchor and focus fall on the textNode
|
|
382
330
|
// we don't have to change the selection because the textNode will be appended to
|
|
383
331
|
// the newly generated paragraph.
|
|
384
332
|
// When selection is in empty nested list item, selection is actually on the listItemNode.
|
|
385
333
|
// When the corresponding listItemNode is deleted and replaced by the newly generated paragraph
|
|
386
334
|
// we should manually set the selection's focus and anchor to the newly generated paragraph.
|
|
387
|
-
|
|
388
335
|
if (listItemNode.__key === selection.anchor.key) {
|
|
389
336
|
selection.anchor.set(paragraph.getKey(), 0, 'element');
|
|
390
337
|
}
|
|
391
|
-
|
|
392
338
|
if (listItemNode.__key === selection.focus.key) {
|
|
393
339
|
selection.focus.set(paragraph.getKey(), 0, 'element');
|
|
394
340
|
}
|
|
395
|
-
|
|
396
341
|
listItemNode.remove();
|
|
397
342
|
}
|
|
398
|
-
|
|
399
343
|
listNode.remove();
|
|
400
344
|
}
|
|
401
345
|
}
|
|
402
346
|
});
|
|
403
347
|
}
|
|
348
|
+
|
|
404
349
|
/**
|
|
405
350
|
* Takes the value of a child ListItemNode and makes it the value the ListItemNode
|
|
406
351
|
* should be if it isn't already. If only certain children should be updated, they
|
|
@@ -408,18 +353,14 @@ function removeList(editor) {
|
|
|
408
353
|
* @param list - The list whose children are updated.
|
|
409
354
|
* @param children - An array of the children to be updated.
|
|
410
355
|
*/
|
|
411
|
-
|
|
412
356
|
function updateChildrenListItemValue(list, children) {
|
|
413
357
|
const childrenOrExisting = children || list.getChildren();
|
|
414
|
-
|
|
415
358
|
if (childrenOrExisting !== undefined) {
|
|
416
359
|
for (let i = 0; i < childrenOrExisting.length; i++) {
|
|
417
360
|
const child = childrenOrExisting[i];
|
|
418
|
-
|
|
419
361
|
if ($isListItemNode(child)) {
|
|
420
362
|
const prevValue = child.getValue();
|
|
421
363
|
const nextValue = $getListItemValue(child);
|
|
422
|
-
|
|
423
364
|
if (prevValue !== nextValue) {
|
|
424
365
|
child.setValue(nextValue);
|
|
425
366
|
}
|
|
@@ -427,70 +368,63 @@ function updateChildrenListItemValue(list, children) {
|
|
|
427
368
|
}
|
|
428
369
|
}
|
|
429
370
|
}
|
|
371
|
+
|
|
430
372
|
/**
|
|
431
373
|
* Adds an empty ListNode/ListItemNode chain at listItemNode, so as to
|
|
432
374
|
* create an indent effect. Won't indent ListItemNodes that have a ListNode as
|
|
433
375
|
* a child, but does merge sibling ListItemNodes if one has a nested ListNode.
|
|
434
376
|
* @param listItemNode - The ListItemNode to be indented.
|
|
435
377
|
*/
|
|
436
|
-
|
|
437
378
|
function $handleIndent(listItemNode) {
|
|
438
379
|
// go through each node and decide where to move it.
|
|
439
380
|
const removed = new Set();
|
|
440
|
-
|
|
441
381
|
if (isNestedListNode(listItemNode) || removed.has(listItemNode.getKey())) {
|
|
442
382
|
return;
|
|
443
383
|
}
|
|
384
|
+
const parent = listItemNode.getParent();
|
|
444
385
|
|
|
445
|
-
|
|
446
|
-
|
|
386
|
+
// We can cast both of the below `isNestedListNode` only returns a boolean type instead of a user-defined type guards
|
|
447
387
|
const nextSibling = listItemNode.getNextSibling();
|
|
448
|
-
const previousSibling = listItemNode.getPreviousSibling();
|
|
388
|
+
const previousSibling = listItemNode.getPreviousSibling();
|
|
389
|
+
// if there are nested lists on either side, merge them all together.
|
|
449
390
|
|
|
450
391
|
if (isNestedListNode(nextSibling) && isNestedListNode(previousSibling)) {
|
|
451
392
|
const innerList = previousSibling.getFirstChild();
|
|
452
|
-
|
|
453
393
|
if ($isListNode(innerList)) {
|
|
454
394
|
innerList.append(listItemNode);
|
|
455
395
|
const nextInnerList = nextSibling.getFirstChild();
|
|
456
|
-
|
|
457
396
|
if ($isListNode(nextInnerList)) {
|
|
458
397
|
const children = nextInnerList.getChildren();
|
|
459
398
|
append(innerList, children);
|
|
460
399
|
nextSibling.remove();
|
|
461
400
|
removed.add(nextSibling.getKey());
|
|
462
401
|
}
|
|
463
|
-
|
|
464
402
|
updateChildrenListItemValue(innerList);
|
|
465
403
|
}
|
|
466
404
|
} else if (isNestedListNode(nextSibling)) {
|
|
467
405
|
// if the ListItemNode is next to a nested ListNode, merge them
|
|
468
406
|
const innerList = nextSibling.getFirstChild();
|
|
469
|
-
|
|
470
407
|
if ($isListNode(innerList)) {
|
|
471
408
|
const firstChild = innerList.getFirstChild();
|
|
472
|
-
|
|
473
409
|
if (firstChild !== null) {
|
|
474
410
|
firstChild.insertBefore(listItemNode);
|
|
475
411
|
}
|
|
476
|
-
|
|
477
412
|
updateChildrenListItemValue(innerList);
|
|
478
413
|
}
|
|
479
414
|
} else if (isNestedListNode(previousSibling)) {
|
|
480
415
|
const innerList = previousSibling.getFirstChild();
|
|
481
|
-
|
|
482
416
|
if ($isListNode(innerList)) {
|
|
483
417
|
innerList.append(listItemNode);
|
|
484
418
|
updateChildrenListItemValue(innerList);
|
|
485
419
|
}
|
|
486
420
|
} else {
|
|
487
421
|
// otherwise, we need to create a new nested ListNode
|
|
422
|
+
|
|
488
423
|
if ($isListNode(parent)) {
|
|
489
424
|
const newListItem = $createListItemNode();
|
|
490
425
|
const newList = $createListNode(parent.getListType());
|
|
491
426
|
newListItem.append(newList);
|
|
492
427
|
newList.append(listItemNode);
|
|
493
|
-
|
|
494
428
|
if (previousSibling) {
|
|
495
429
|
previousSibling.insertAfter(newListItem);
|
|
496
430
|
} else if (nextSibling) {
|
|
@@ -498,49 +432,45 @@ function $handleIndent(listItemNode) {
|
|
|
498
432
|
} else {
|
|
499
433
|
parent.append(newListItem);
|
|
500
434
|
}
|
|
501
|
-
|
|
502
435
|
updateChildrenListItemValue(newList);
|
|
503
436
|
}
|
|
504
437
|
}
|
|
505
|
-
|
|
506
438
|
if ($isListNode(parent)) {
|
|
507
439
|
updateChildrenListItemValue(parent);
|
|
508
440
|
}
|
|
509
441
|
}
|
|
442
|
+
|
|
510
443
|
/**
|
|
511
444
|
* Removes an indent by removing an empty ListNode/ListItemNode chain. An indented ListItemNode
|
|
512
445
|
* has a great grandparent node of type ListNode, which is where the ListItemNode will reside
|
|
513
446
|
* within as a child.
|
|
514
447
|
* @param listItemNode - The ListItemNode to remove the indent (outdent).
|
|
515
448
|
*/
|
|
516
|
-
|
|
517
449
|
function $handleOutdent(listItemNode) {
|
|
518
450
|
// go through each node and decide where to move it.
|
|
451
|
+
|
|
519
452
|
if (isNestedListNode(listItemNode)) {
|
|
520
453
|
return;
|
|
521
454
|
}
|
|
522
|
-
|
|
523
455
|
const parentList = listItemNode.getParent();
|
|
524
456
|
const grandparentListItem = parentList ? parentList.getParent() : undefined;
|
|
525
|
-
const greatGrandparentList = grandparentListItem ? grandparentListItem.getParent() : undefined;
|
|
457
|
+
const greatGrandparentList = grandparentListItem ? grandparentListItem.getParent() : undefined;
|
|
458
|
+
// If it doesn't have these ancestors, it's not indented.
|
|
526
459
|
|
|
527
460
|
if ($isListNode(greatGrandparentList) && $isListItemNode(grandparentListItem) && $isListNode(parentList)) {
|
|
528
461
|
// if it's the first child in it's parent list, insert it into the
|
|
529
462
|
// great grandparent list before the grandparent
|
|
530
463
|
const firstChild = parentList ? parentList.getFirstChild() : undefined;
|
|
531
464
|
const lastChild = parentList ? parentList.getLastChild() : undefined;
|
|
532
|
-
|
|
533
465
|
if (listItemNode.is(firstChild)) {
|
|
534
466
|
grandparentListItem.insertBefore(listItemNode);
|
|
535
|
-
|
|
536
467
|
if (parentList.isEmpty()) {
|
|
537
468
|
grandparentListItem.remove();
|
|
538
|
-
}
|
|
469
|
+
}
|
|
470
|
+
// if it's the last child in it's parent list, insert it into the
|
|
539
471
|
// great grandparent list after the grandparent.
|
|
540
|
-
|
|
541
472
|
} else if (listItemNode.is(lastChild)) {
|
|
542
473
|
grandparentListItem.insertAfter(listItemNode);
|
|
543
|
-
|
|
544
474
|
if (parentList.isEmpty()) {
|
|
545
475
|
grandparentListItem.remove();
|
|
546
476
|
}
|
|
@@ -554,18 +484,18 @@ function $handleOutdent(listItemNode) {
|
|
|
554
484
|
const nextSiblingsListItem = $createListItemNode();
|
|
555
485
|
const nextSiblingsList = $createListNode(listType);
|
|
556
486
|
nextSiblingsListItem.append(nextSiblingsList);
|
|
557
|
-
append(nextSiblingsList, listItemNode.getNextSiblings());
|
|
558
|
-
|
|
487
|
+
append(nextSiblingsList, listItemNode.getNextSiblings());
|
|
488
|
+
// put the sibling nested lists on either side of the grandparent list item in the great grandparent.
|
|
559
489
|
grandparentListItem.insertBefore(previousSiblingsListItem);
|
|
560
|
-
grandparentListItem.insertAfter(nextSiblingsListItem);
|
|
561
|
-
|
|
490
|
+
grandparentListItem.insertAfter(nextSiblingsListItem);
|
|
491
|
+
// replace the grandparent list item (now between the siblings) with the outdented list item.
|
|
562
492
|
grandparentListItem.replace(listItemNode);
|
|
563
493
|
}
|
|
564
|
-
|
|
565
494
|
updateChildrenListItemValue(parentList);
|
|
566
495
|
updateChildrenListItemValue(greatGrandparentList);
|
|
567
496
|
}
|
|
568
497
|
}
|
|
498
|
+
|
|
569
499
|
/**
|
|
570
500
|
* Attempts to insert a ParagraphNode at selection and selects the new node. The selection must contain a ListItemNode
|
|
571
501
|
* or a node that does not already contain text. If its grandparent is the root/shadow root, it will get the ListNode
|
|
@@ -575,31 +505,23 @@ function $handleOutdent(listItemNode) {
|
|
|
575
505
|
* @returns true if a ParagraphNode was inserted succesfully, false if there is no selection
|
|
576
506
|
* or the selection does not contain a ListItemNode or the node already holds text.
|
|
577
507
|
*/
|
|
578
|
-
|
|
579
508
|
function $handleListInsertParagraph() {
|
|
580
509
|
const selection = lexical.$getSelection();
|
|
581
|
-
|
|
582
510
|
if (!lexical.$isRangeSelection(selection) || !selection.isCollapsed()) {
|
|
583
511
|
return false;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
|
|
512
|
+
}
|
|
513
|
+
// Only run this code on empty list items
|
|
587
514
|
const anchor = selection.anchor.getNode();
|
|
588
|
-
|
|
589
515
|
if (!$isListItemNode(anchor) || anchor.getChildrenSize() !== 0) {
|
|
590
516
|
return false;
|
|
591
517
|
}
|
|
592
|
-
|
|
593
518
|
const topListNode = $getTopListNode(anchor);
|
|
594
519
|
const parent = anchor.getParent();
|
|
595
|
-
|
|
596
520
|
if (!$isListNode(parent)) {
|
|
597
521
|
throw Error(`A ListItemNode must have a ListNode for a parent.`);
|
|
598
522
|
}
|
|
599
|
-
|
|
600
523
|
const grandparent = parent.getParent();
|
|
601
524
|
let replacementNode;
|
|
602
|
-
|
|
603
525
|
if (lexical.$isRootOrShadowRoot(grandparent)) {
|
|
604
526
|
replacementNode = lexical.$createParagraphNode();
|
|
605
527
|
topListNode.insertAfter(replacementNode);
|
|
@@ -609,13 +531,10 @@ function $handleListInsertParagraph() {
|
|
|
609
531
|
} else {
|
|
610
532
|
return false;
|
|
611
533
|
}
|
|
612
|
-
|
|
613
534
|
replacementNode.select();
|
|
614
535
|
const nextSiblings = anchor.getNextSiblings();
|
|
615
|
-
|
|
616
536
|
if (nextSiblings.length > 0) {
|
|
617
537
|
const newList = $createListNode(parent.getListType());
|
|
618
|
-
|
|
619
538
|
if (lexical.$isParagraphNode(replacementNode)) {
|
|
620
539
|
replacementNode.insertAfter(newList);
|
|
621
540
|
} else {
|
|
@@ -623,14 +542,13 @@ function $handleListInsertParagraph() {
|
|
|
623
542
|
newListItem.append(newList);
|
|
624
543
|
replacementNode.insertAfter(newListItem);
|
|
625
544
|
}
|
|
626
|
-
|
|
627
545
|
nextSiblings.forEach(sibling => {
|
|
628
546
|
sibling.remove();
|
|
629
547
|
newList.append(sibling);
|
|
630
548
|
});
|
|
631
|
-
}
|
|
632
|
-
|
|
549
|
+
}
|
|
633
550
|
|
|
551
|
+
// Don't leave hanging nested empty lists
|
|
634
552
|
$removeHighestEmptyListParent(anchor);
|
|
635
553
|
return true;
|
|
636
554
|
}
|
|
@@ -642,66 +560,54 @@ function $handleListInsertParagraph() {
|
|
|
642
560
|
* LICENSE file in the root directory of this source tree.
|
|
643
561
|
*
|
|
644
562
|
*/
|
|
645
|
-
|
|
646
563
|
/** @noInheritDoc */
|
|
647
564
|
class ListItemNode extends lexical.ElementNode {
|
|
648
565
|
/** @internal */
|
|
649
566
|
|
|
650
567
|
/** @internal */
|
|
568
|
+
|
|
651
569
|
static getType() {
|
|
652
570
|
return 'listitem';
|
|
653
571
|
}
|
|
654
|
-
|
|
655
572
|
static clone(node) {
|
|
656
573
|
return new ListItemNode(node.__value, node.__checked, node.__key);
|
|
657
574
|
}
|
|
658
|
-
|
|
659
575
|
constructor(value, checked, key) {
|
|
660
576
|
super(key);
|
|
661
577
|
this.__value = value === undefined ? 1 : value;
|
|
662
578
|
this.__checked = checked;
|
|
663
579
|
}
|
|
664
|
-
|
|
665
580
|
createDOM(config) {
|
|
666
581
|
const element = document.createElement('li');
|
|
667
582
|
const parent = this.getParent();
|
|
668
|
-
|
|
669
583
|
if ($isListNode(parent) && parent.getListType() === 'check') {
|
|
670
584
|
updateListItemChecked(element, this, null);
|
|
671
585
|
}
|
|
672
|
-
|
|
673
586
|
element.value = this.__value;
|
|
674
587
|
$setListItemThemeClassNames(element, config.theme, this);
|
|
675
588
|
return element;
|
|
676
589
|
}
|
|
677
|
-
|
|
678
590
|
updateDOM(prevNode, dom, config) {
|
|
679
591
|
const parent = this.getParent();
|
|
680
|
-
|
|
681
592
|
if ($isListNode(parent) && parent.getListType() === 'check') {
|
|
682
593
|
updateListItemChecked(dom, this, prevNode);
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
|
|
594
|
+
}
|
|
595
|
+
// @ts-expect-error - this is always HTMLListItemElement
|
|
686
596
|
dom.value = this.__value;
|
|
687
597
|
$setListItemThemeClassNames(dom, config.theme, this);
|
|
688
598
|
return false;
|
|
689
599
|
}
|
|
690
|
-
|
|
691
600
|
static transform() {
|
|
692
601
|
return node => {
|
|
693
602
|
const parent = node.getParent();
|
|
694
|
-
|
|
695
603
|
if ($isListNode(parent)) {
|
|
696
604
|
updateChildrenListItemValue(parent);
|
|
697
|
-
|
|
698
605
|
if (parent.getListType() !== 'check' && node.getChecked() != null) {
|
|
699
606
|
node.setChecked(undefined);
|
|
700
607
|
}
|
|
701
608
|
}
|
|
702
609
|
};
|
|
703
610
|
}
|
|
704
|
-
|
|
705
611
|
static importDOM() {
|
|
706
612
|
return {
|
|
707
613
|
li: node => ({
|
|
@@ -710,7 +616,6 @@ class ListItemNode extends lexical.ElementNode {
|
|
|
710
616
|
})
|
|
711
617
|
};
|
|
712
618
|
}
|
|
713
|
-
|
|
714
619
|
static importJSON(serializedNode) {
|
|
715
620
|
const node = $createListItemNode();
|
|
716
621
|
node.setChecked(serializedNode.checked);
|
|
@@ -719,20 +624,25 @@ class ListItemNode extends lexical.ElementNode {
|
|
|
719
624
|
node.setDirection(serializedNode.direction);
|
|
720
625
|
return node;
|
|
721
626
|
}
|
|
722
|
-
|
|
627
|
+
exportDOM(editor) {
|
|
628
|
+
const element = this.createDOM(editor._config);
|
|
629
|
+
element.style.textAlign = this.getFormatType();
|
|
630
|
+
return {
|
|
631
|
+
element
|
|
632
|
+
};
|
|
633
|
+
}
|
|
723
634
|
exportJSON() {
|
|
724
|
-
return {
|
|
635
|
+
return {
|
|
636
|
+
...super.exportJSON(),
|
|
725
637
|
checked: this.getChecked(),
|
|
726
638
|
type: 'listitem',
|
|
727
639
|
value: this.getValue(),
|
|
728
640
|
version: 1
|
|
729
641
|
};
|
|
730
642
|
}
|
|
731
|
-
|
|
732
643
|
append(...nodes) {
|
|
733
644
|
for (let i = 0; i < nodes.length; i++) {
|
|
734
645
|
const node = nodes[i];
|
|
735
|
-
|
|
736
646
|
if (lexical.$isElementNode(node) && this.canMergeWith(node)) {
|
|
737
647
|
const children = node.getChildren();
|
|
738
648
|
this.append(...children);
|
|
@@ -741,19 +651,15 @@ class ListItemNode extends lexical.ElementNode {
|
|
|
741
651
|
super.append(node);
|
|
742
652
|
}
|
|
743
653
|
}
|
|
744
|
-
|
|
745
654
|
return this;
|
|
746
655
|
}
|
|
747
|
-
|
|
748
656
|
replace(replaceWithNode, includeChildren) {
|
|
749
657
|
if ($isListItemNode(replaceWithNode)) {
|
|
750
658
|
return super.replace(replaceWithNode);
|
|
751
659
|
}
|
|
752
|
-
|
|
753
660
|
this.setIndent(0);
|
|
754
661
|
const list = this.getParentOrThrow();
|
|
755
662
|
if (!$isListNode(list)) return replaceWithNode;
|
|
756
|
-
|
|
757
663
|
if (list.__first === this.getKey()) {
|
|
758
664
|
list.insertBefore(replaceWithNode);
|
|
759
665
|
} else if (list.__last === this.getKey()) {
|
|
@@ -762,103 +668,83 @@ class ListItemNode extends lexical.ElementNode {
|
|
|
762
668
|
// Split the list
|
|
763
669
|
const newList = $createListNode(list.getListType());
|
|
764
670
|
let nextSibling = this.getNextSibling();
|
|
765
|
-
|
|
766
671
|
while (nextSibling) {
|
|
767
672
|
const nodeToAppend = nextSibling;
|
|
768
673
|
nextSibling = nextSibling.getNextSibling();
|
|
769
674
|
newList.append(nodeToAppend);
|
|
770
675
|
}
|
|
771
|
-
|
|
772
676
|
list.insertAfter(replaceWithNode);
|
|
773
677
|
replaceWithNode.insertAfter(newList);
|
|
774
678
|
}
|
|
775
|
-
|
|
776
679
|
if (includeChildren) {
|
|
777
680
|
this.getChildren().forEach(child => {
|
|
778
681
|
replaceWithNode.append(child);
|
|
779
682
|
});
|
|
780
683
|
}
|
|
781
|
-
|
|
782
684
|
this.remove();
|
|
783
|
-
|
|
784
685
|
if (list.getChildrenSize() === 0) {
|
|
785
686
|
list.remove();
|
|
786
687
|
}
|
|
787
|
-
|
|
788
688
|
return replaceWithNode;
|
|
789
689
|
}
|
|
790
|
-
|
|
791
690
|
insertAfter(node, restoreSelection = true) {
|
|
792
691
|
const listNode = this.getParentOrThrow();
|
|
793
|
-
|
|
794
692
|
if (!$isListNode(listNode)) {
|
|
795
693
|
{
|
|
796
694
|
throw Error(`insertAfter: list node is not parent of list item node`);
|
|
797
695
|
}
|
|
798
696
|
}
|
|
799
|
-
|
|
800
697
|
const siblings = this.getNextSiblings();
|
|
801
|
-
|
|
802
698
|
if ($isListItemNode(node)) {
|
|
803
699
|
const after = super.insertAfter(node, restoreSelection);
|
|
804
700
|
const afterListNode = node.getParentOrThrow();
|
|
805
|
-
|
|
806
701
|
if ($isListNode(afterListNode)) {
|
|
807
702
|
updateChildrenListItemValue(afterListNode);
|
|
808
703
|
}
|
|
809
|
-
|
|
810
704
|
return after;
|
|
811
|
-
}
|
|
705
|
+
}
|
|
812
706
|
|
|
707
|
+
// Attempt to merge if the list is of the same type.
|
|
813
708
|
|
|
814
709
|
if ($isListNode(node)) {
|
|
815
710
|
let child = node;
|
|
816
711
|
const children = node.getChildren();
|
|
817
|
-
|
|
818
712
|
for (let i = children.length - 1; i >= 0; i--) {
|
|
819
713
|
child = children[i];
|
|
820
714
|
this.insertAfter(child, restoreSelection);
|
|
821
715
|
}
|
|
822
|
-
|
|
823
716
|
return child;
|
|
824
|
-
}
|
|
825
|
-
// Split the lists and insert the node in between them
|
|
826
|
-
|
|
717
|
+
}
|
|
827
718
|
|
|
719
|
+
// Otherwise, split the list
|
|
720
|
+
// Split the lists and insert the node in between them
|
|
828
721
|
listNode.insertAfter(node, restoreSelection);
|
|
829
|
-
|
|
830
722
|
if (siblings.length !== 0) {
|
|
831
723
|
const newListNode = $createListNode(listNode.getListType());
|
|
832
724
|
siblings.forEach(sibling => newListNode.append(sibling));
|
|
833
725
|
node.insertAfter(newListNode, restoreSelection);
|
|
834
726
|
}
|
|
835
|
-
|
|
836
727
|
return node;
|
|
837
728
|
}
|
|
838
|
-
|
|
839
729
|
remove(preserveEmptyParent) {
|
|
840
730
|
const prevSibling = this.getPreviousSibling();
|
|
841
731
|
const nextSibling = this.getNextSibling();
|
|
842
732
|
super.remove(preserveEmptyParent);
|
|
843
|
-
|
|
844
733
|
if (prevSibling && nextSibling && isNestedListNode(prevSibling) && isNestedListNode(nextSibling)) {
|
|
845
734
|
mergeLists(prevSibling.getFirstChild(), nextSibling.getFirstChild());
|
|
846
735
|
nextSibling.remove();
|
|
847
736
|
} else if (nextSibling) {
|
|
848
737
|
const parent = nextSibling.getParent();
|
|
849
|
-
|
|
850
738
|
if ($isListNode(parent)) {
|
|
851
739
|
updateChildrenListItemValue(parent);
|
|
852
740
|
}
|
|
853
741
|
}
|
|
854
742
|
}
|
|
855
|
-
|
|
856
743
|
insertNewAfter(_, restoreSelection = true) {
|
|
857
744
|
const newElement = $createListItemNode(this.__checked == null ? undefined : false);
|
|
858
745
|
this.insertAfter(newElement, restoreSelection);
|
|
859
746
|
return newElement;
|
|
860
747
|
}
|
|
861
|
-
|
|
862
748
|
collapseAtStart(selection) {
|
|
863
749
|
const paragraph = lexical.$createParagraphNode();
|
|
864
750
|
const children = this.getChildren();
|
|
@@ -866,7 +752,6 @@ class ListItemNode extends lexical.ElementNode {
|
|
|
866
752
|
const listNode = this.getParentOrThrow();
|
|
867
753
|
const listNodeParent = listNode.getParentOrThrow();
|
|
868
754
|
const isIndented = $isListItemNode(listNodeParent);
|
|
869
|
-
|
|
870
755
|
if (listNode.getChildrenSize() === 1) {
|
|
871
756
|
if (isIndented) {
|
|
872
757
|
// if the list node is nested, we just want to remove it,
|
|
@@ -875,17 +760,15 @@ class ListItemNode extends lexical.ElementNode {
|
|
|
875
760
|
listNodeParent.select();
|
|
876
761
|
} else {
|
|
877
762
|
listNode.insertBefore(paragraph);
|
|
878
|
-
listNode.remove();
|
|
763
|
+
listNode.remove();
|
|
764
|
+
// If we have selection on the list item, we'll need to move it
|
|
879
765
|
// to the paragraph
|
|
880
|
-
|
|
881
766
|
const anchor = selection.anchor;
|
|
882
767
|
const focus = selection.focus;
|
|
883
768
|
const key = paragraph.getKey();
|
|
884
|
-
|
|
885
769
|
if (anchor.type === 'element' && anchor.getNode().is(this)) {
|
|
886
770
|
anchor.set(key, anchor.offset, 'element');
|
|
887
771
|
}
|
|
888
|
-
|
|
889
772
|
if (focus.type === 'element' && focus.getNode().is(this)) {
|
|
890
773
|
focus.set(key, focus.offset, 'element');
|
|
891
774
|
}
|
|
@@ -894,61 +777,47 @@ class ListItemNode extends lexical.ElementNode {
|
|
|
894
777
|
listNode.insertBefore(paragraph);
|
|
895
778
|
this.remove();
|
|
896
779
|
}
|
|
897
|
-
|
|
898
780
|
return true;
|
|
899
781
|
}
|
|
900
|
-
|
|
901
782
|
getValue() {
|
|
902
783
|
const self = this.getLatest();
|
|
903
784
|
return self.__value;
|
|
904
785
|
}
|
|
905
|
-
|
|
906
786
|
setValue(value) {
|
|
907
787
|
const self = this.getWritable();
|
|
908
788
|
self.__value = value;
|
|
909
789
|
}
|
|
910
|
-
|
|
911
790
|
getChecked() {
|
|
912
791
|
const self = this.getLatest();
|
|
913
792
|
return self.__checked;
|
|
914
793
|
}
|
|
915
|
-
|
|
916
794
|
setChecked(checked) {
|
|
917
795
|
const self = this.getWritable();
|
|
918
796
|
self.__checked = checked;
|
|
919
797
|
}
|
|
920
|
-
|
|
921
798
|
toggleChecked() {
|
|
922
799
|
this.setChecked(!this.__checked);
|
|
923
800
|
}
|
|
924
|
-
|
|
925
801
|
getIndent() {
|
|
926
802
|
// If we don't have a parent, we are likely serializing
|
|
927
803
|
const parent = this.getParent();
|
|
928
|
-
|
|
929
804
|
if (parent === null) {
|
|
930
805
|
return this.getLatest().__indent;
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
|
|
806
|
+
}
|
|
807
|
+
// ListItemNode should always have a ListNode for a parent.
|
|
934
808
|
let listNodeParent = parent.getParentOrThrow();
|
|
935
809
|
let indentLevel = 0;
|
|
936
|
-
|
|
937
810
|
while ($isListItemNode(listNodeParent)) {
|
|
938
811
|
listNodeParent = listNodeParent.getParentOrThrow().getParentOrThrow();
|
|
939
812
|
indentLevel++;
|
|
940
813
|
}
|
|
941
|
-
|
|
942
814
|
return indentLevel;
|
|
943
815
|
}
|
|
944
|
-
|
|
945
816
|
setIndent(indent) {
|
|
946
817
|
if (!(typeof indent === 'number' && indent > -1)) {
|
|
947
818
|
throw Error(`Invalid indent value.`);
|
|
948
819
|
}
|
|
949
|
-
|
|
950
820
|
let currentIndent = this.getIndent();
|
|
951
|
-
|
|
952
821
|
while (currentIndent !== indent) {
|
|
953
822
|
if (currentIndent < indent) {
|
|
954
823
|
$handleIndent(this);
|
|
@@ -958,108 +827,84 @@ class ListItemNode extends lexical.ElementNode {
|
|
|
958
827
|
currentIndent--;
|
|
959
828
|
}
|
|
960
829
|
}
|
|
961
|
-
|
|
962
830
|
return this;
|
|
963
831
|
}
|
|
964
|
-
|
|
965
832
|
insertBefore(nodeToInsert) {
|
|
966
833
|
if ($isListItemNode(nodeToInsert)) {
|
|
967
834
|
const parent = this.getParentOrThrow();
|
|
968
|
-
|
|
969
835
|
if ($isListNode(parent)) {
|
|
970
836
|
const siblings = this.getNextSiblings();
|
|
971
837
|
updateChildrenListItemValue(parent, siblings);
|
|
972
838
|
}
|
|
973
839
|
}
|
|
974
|
-
|
|
975
840
|
return super.insertBefore(nodeToInsert);
|
|
976
841
|
}
|
|
977
|
-
|
|
978
842
|
canInsertAfter(node) {
|
|
979
843
|
return $isListItemNode(node);
|
|
980
844
|
}
|
|
981
|
-
|
|
982
845
|
canReplaceWith(replacement) {
|
|
983
846
|
return $isListItemNode(replacement);
|
|
984
847
|
}
|
|
985
|
-
|
|
986
848
|
canMergeWith(node) {
|
|
987
849
|
return lexical.$isParagraphNode(node) || $isListItemNode(node);
|
|
988
850
|
}
|
|
989
|
-
|
|
990
851
|
extractWithChild(child, selection) {
|
|
991
852
|
if (!lexical.$isRangeSelection(selection)) {
|
|
992
853
|
return false;
|
|
993
854
|
}
|
|
994
|
-
|
|
995
855
|
const anchorNode = selection.anchor.getNode();
|
|
996
856
|
const focusNode = selection.focus.getNode();
|
|
997
857
|
return this.isParentOf(anchorNode) && this.isParentOf(focusNode) && this.getTextContent().length === selection.getTextContent().length;
|
|
998
858
|
}
|
|
999
|
-
|
|
1000
859
|
isParentRequired() {
|
|
1001
860
|
return true;
|
|
1002
861
|
}
|
|
1003
|
-
|
|
1004
862
|
createParentElementNode() {
|
|
1005
863
|
return $createListNode('bullet');
|
|
1006
864
|
}
|
|
1007
|
-
|
|
1008
865
|
}
|
|
1009
|
-
|
|
1010
866
|
function $setListItemThemeClassNames(dom, editorThemeClasses, node) {
|
|
1011
867
|
const classesToAdd = [];
|
|
1012
868
|
const classesToRemove = [];
|
|
1013
869
|
const listTheme = editorThemeClasses.list;
|
|
1014
870
|
const listItemClassName = listTheme ? listTheme.listitem : undefined;
|
|
1015
871
|
let nestedListItemClassName;
|
|
1016
|
-
|
|
1017
872
|
if (listTheme && listTheme.nested) {
|
|
1018
873
|
nestedListItemClassName = listTheme.nested.listitem;
|
|
1019
874
|
}
|
|
1020
|
-
|
|
1021
875
|
if (listItemClassName !== undefined) {
|
|
1022
876
|
const listItemClasses = listItemClassName.split(' ');
|
|
1023
877
|
classesToAdd.push(...listItemClasses);
|
|
1024
878
|
}
|
|
1025
|
-
|
|
1026
879
|
if (listTheme) {
|
|
1027
880
|
const parentNode = node.getParent();
|
|
1028
881
|
const isCheckList = $isListNode(parentNode) && parentNode.getListType() === 'check';
|
|
1029
882
|
const checked = node.getChecked();
|
|
1030
|
-
|
|
1031
883
|
if (!isCheckList || checked) {
|
|
1032
884
|
classesToRemove.push(listTheme.listitemUnchecked);
|
|
1033
885
|
}
|
|
1034
|
-
|
|
1035
886
|
if (!isCheckList || !checked) {
|
|
1036
887
|
classesToRemove.push(listTheme.listitemChecked);
|
|
1037
888
|
}
|
|
1038
|
-
|
|
1039
889
|
if (isCheckList) {
|
|
1040
890
|
classesToAdd.push(checked ? listTheme.listitemChecked : listTheme.listitemUnchecked);
|
|
1041
891
|
}
|
|
1042
892
|
}
|
|
1043
|
-
|
|
1044
893
|
if (nestedListItemClassName !== undefined) {
|
|
1045
894
|
const nestedListItemClasses = nestedListItemClassName.split(' ');
|
|
1046
|
-
|
|
1047
895
|
if (node.getChildren().some(child => $isListNode(child))) {
|
|
1048
896
|
classesToAdd.push(...nestedListItemClasses);
|
|
1049
897
|
} else {
|
|
1050
898
|
classesToRemove.push(...nestedListItemClasses);
|
|
1051
899
|
}
|
|
1052
900
|
}
|
|
1053
|
-
|
|
1054
901
|
if (classesToRemove.length > 0) {
|
|
1055
902
|
utils.removeClassNamesFromElement(dom, ...classesToRemove);
|
|
1056
903
|
}
|
|
1057
|
-
|
|
1058
904
|
if (classesToAdd.length > 0) {
|
|
1059
905
|
utils.addClassNamesToElement(dom, ...classesToAdd);
|
|
1060
906
|
}
|
|
1061
907
|
}
|
|
1062
|
-
|
|
1063
908
|
function updateListItemChecked(dom, listItemNode, prevListItemNode, listNode) {
|
|
1064
909
|
// Only add attributes for leaf list items
|
|
1065
910
|
if ($isListNode(listItemNode.getFirstChild())) {
|
|
@@ -1069,35 +914,32 @@ function updateListItemChecked(dom, listItemNode, prevListItemNode, listNode) {
|
|
|
1069
914
|
} else {
|
|
1070
915
|
dom.setAttribute('role', 'checkbox');
|
|
1071
916
|
dom.setAttribute('tabIndex', '-1');
|
|
1072
|
-
|
|
1073
917
|
if (!prevListItemNode || listItemNode.__checked !== prevListItemNode.__checked) {
|
|
1074
918
|
dom.setAttribute('aria-checked', listItemNode.getChecked() ? 'true' : 'false');
|
|
1075
919
|
}
|
|
1076
920
|
}
|
|
1077
921
|
}
|
|
1078
|
-
|
|
1079
922
|
function convertListItemElement(domNode) {
|
|
1080
923
|
const checked = utils.isHTMLElement(domNode) && domNode.getAttribute('aria-checked') === 'true';
|
|
1081
924
|
return {
|
|
1082
925
|
node: $createListItemNode(checked)
|
|
1083
926
|
};
|
|
1084
927
|
}
|
|
928
|
+
|
|
1085
929
|
/**
|
|
1086
930
|
* Creates a new List Item node, passing true/false will convert it to a checkbox input.
|
|
1087
931
|
* @param checked - Is the List Item a checkbox and, if so, is it checked? undefined/null: not a checkbox, true/false is a checkbox and checked/unchecked, respectively.
|
|
1088
932
|
* @returns The new List Item.
|
|
1089
933
|
*/
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
934
|
function $createListItemNode(checked) {
|
|
1093
935
|
return lexical.$applyNodeReplacement(new ListItemNode(undefined, checked));
|
|
1094
936
|
}
|
|
937
|
+
|
|
1095
938
|
/**
|
|
1096
939
|
* Checks to see if the node is a ListItemNode.
|
|
1097
940
|
* @param node - The node to be checked.
|
|
1098
941
|
* @returns true if the node is a ListItemNode, false otherwise.
|
|
1099
942
|
*/
|
|
1100
|
-
|
|
1101
943
|
function $isListItemNode(node) {
|
|
1102
944
|
return node instanceof ListItemNode;
|
|
1103
945
|
}
|
|
@@ -1109,7 +951,6 @@ function $isListItemNode(node) {
|
|
|
1109
951
|
* LICENSE file in the root directory of this source tree.
|
|
1110
952
|
*
|
|
1111
953
|
*/
|
|
1112
|
-
|
|
1113
954
|
/** @noInheritDoc */
|
|
1114
955
|
class ListNode extends lexical.ElementNode {
|
|
1115
956
|
/** @internal */
|
|
@@ -1117,67 +958,56 @@ class ListNode extends lexical.ElementNode {
|
|
|
1117
958
|
/** @internal */
|
|
1118
959
|
|
|
1119
960
|
/** @internal */
|
|
961
|
+
|
|
1120
962
|
static getType() {
|
|
1121
963
|
return 'list';
|
|
1122
964
|
}
|
|
1123
|
-
|
|
1124
965
|
static clone(node) {
|
|
1125
966
|
const listType = node.__listType || TAG_TO_LIST_TYPE[node.__tag];
|
|
1126
967
|
return new ListNode(listType, node.__start, node.__key);
|
|
1127
968
|
}
|
|
1128
|
-
|
|
1129
969
|
constructor(listType, start, key) {
|
|
1130
970
|
super(key);
|
|
1131
|
-
|
|
1132
971
|
const _listType = TAG_TO_LIST_TYPE[listType] || listType;
|
|
1133
|
-
|
|
1134
972
|
this.__listType = _listType;
|
|
1135
973
|
this.__tag = _listType === 'number' ? 'ol' : 'ul';
|
|
1136
974
|
this.__start = start;
|
|
1137
975
|
}
|
|
1138
|
-
|
|
1139
976
|
getTag() {
|
|
1140
977
|
return this.__tag;
|
|
1141
978
|
}
|
|
1142
|
-
|
|
1143
979
|
setListType(type) {
|
|
1144
980
|
const writable = this.getWritable();
|
|
1145
981
|
writable.__listType = type;
|
|
1146
982
|
writable.__tag = type === 'number' ? 'ol' : 'ul';
|
|
1147
983
|
}
|
|
1148
|
-
|
|
1149
984
|
getListType() {
|
|
1150
985
|
return this.__listType;
|
|
1151
986
|
}
|
|
1152
|
-
|
|
1153
987
|
getStart() {
|
|
1154
988
|
return this.__start;
|
|
1155
|
-
}
|
|
989
|
+
}
|
|
1156
990
|
|
|
991
|
+
// View
|
|
1157
992
|
|
|
1158
993
|
createDOM(config, _editor) {
|
|
1159
994
|
const tag = this.__tag;
|
|
1160
995
|
const dom = document.createElement(tag);
|
|
1161
|
-
|
|
1162
996
|
if (this.__start !== 1) {
|
|
1163
997
|
dom.setAttribute('start', String(this.__start));
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
|
|
998
|
+
}
|
|
999
|
+
// @ts-expect-error Internal field.
|
|
1167
1000
|
dom.__lexicalListType = this.__listType;
|
|
1168
1001
|
setListThemeClassNames(dom, config.theme, this);
|
|
1169
1002
|
return dom;
|
|
1170
1003
|
}
|
|
1171
|
-
|
|
1172
1004
|
updateDOM(prevNode, dom, config) {
|
|
1173
1005
|
if (prevNode.__tag !== this.__tag) {
|
|
1174
1006
|
return true;
|
|
1175
1007
|
}
|
|
1176
|
-
|
|
1177
1008
|
setListThemeClassNames(dom, config.theme, this);
|
|
1178
1009
|
return false;
|
|
1179
1010
|
}
|
|
1180
|
-
|
|
1181
1011
|
static importDOM() {
|
|
1182
1012
|
return {
|
|
1183
1013
|
ol: node => ({
|
|
@@ -1190,7 +1020,6 @@ class ListNode extends lexical.ElementNode {
|
|
|
1190
1020
|
})
|
|
1191
1021
|
};
|
|
1192
1022
|
}
|
|
1193
|
-
|
|
1194
1023
|
static importJSON(serializedNode) {
|
|
1195
1024
|
const node = $createListNode(serializedNode.listType, serializedNode.start);
|
|
1196
1025
|
node.setFormat(serializedNode.format);
|
|
@@ -1198,29 +1027,25 @@ class ListNode extends lexical.ElementNode {
|
|
|
1198
1027
|
node.setDirection(serializedNode.direction);
|
|
1199
1028
|
return node;
|
|
1200
1029
|
}
|
|
1201
|
-
|
|
1202
1030
|
exportDOM(editor) {
|
|
1203
1031
|
const {
|
|
1204
1032
|
element
|
|
1205
1033
|
} = super.exportDOM(editor);
|
|
1206
|
-
|
|
1207
1034
|
if (element && utils.isHTMLElement(element)) {
|
|
1208
1035
|
if (this.__start !== 1) {
|
|
1209
1036
|
element.setAttribute('start', String(this.__start));
|
|
1210
1037
|
}
|
|
1211
|
-
|
|
1212
1038
|
if (this.__listType === 'check') {
|
|
1213
1039
|
element.setAttribute('__lexicalListType', 'check');
|
|
1214
1040
|
}
|
|
1215
1041
|
}
|
|
1216
|
-
|
|
1217
1042
|
return {
|
|
1218
1043
|
element
|
|
1219
1044
|
};
|
|
1220
1045
|
}
|
|
1221
|
-
|
|
1222
1046
|
exportJSON() {
|
|
1223
|
-
return {
|
|
1047
|
+
return {
|
|
1048
|
+
...super.exportJSON(),
|
|
1224
1049
|
listType: this.getListType(),
|
|
1225
1050
|
start: this.getStart(),
|
|
1226
1051
|
tag: this.getTag(),
|
|
@@ -1228,24 +1053,19 @@ class ListNode extends lexical.ElementNode {
|
|
|
1228
1053
|
version: 1
|
|
1229
1054
|
};
|
|
1230
1055
|
}
|
|
1231
|
-
|
|
1232
1056
|
canBeEmpty() {
|
|
1233
1057
|
return false;
|
|
1234
1058
|
}
|
|
1235
|
-
|
|
1236
1059
|
canIndent() {
|
|
1237
1060
|
return false;
|
|
1238
1061
|
}
|
|
1239
|
-
|
|
1240
1062
|
append(...nodesToAppend) {
|
|
1241
1063
|
for (let i = 0; i < nodesToAppend.length; i++) {
|
|
1242
1064
|
const currentNode = nodesToAppend[i];
|
|
1243
|
-
|
|
1244
1065
|
if ($isListItemNode(currentNode)) {
|
|
1245
1066
|
super.append(currentNode);
|
|
1246
1067
|
} else {
|
|
1247
1068
|
const listItemNode = $createListItemNode();
|
|
1248
|
-
|
|
1249
1069
|
if ($isListNode(currentNode)) {
|
|
1250
1070
|
listItemNode.append(currentNode);
|
|
1251
1071
|
} else if (lexical.$isElementNode(currentNode)) {
|
|
@@ -1254,26 +1074,20 @@ class ListNode extends lexical.ElementNode {
|
|
|
1254
1074
|
} else {
|
|
1255
1075
|
listItemNode.append(currentNode);
|
|
1256
1076
|
}
|
|
1257
|
-
|
|
1258
1077
|
super.append(listItemNode);
|
|
1259
1078
|
}
|
|
1260
1079
|
}
|
|
1261
|
-
|
|
1262
1080
|
updateChildrenListItemValue(this);
|
|
1263
1081
|
return this;
|
|
1264
1082
|
}
|
|
1265
|
-
|
|
1266
1083
|
extractWithChild(child) {
|
|
1267
1084
|
return $isListItemNode(child);
|
|
1268
1085
|
}
|
|
1269
|
-
|
|
1270
1086
|
}
|
|
1271
|
-
|
|
1272
1087
|
function setListThemeClassNames(dom, editorThemeClasses, node) {
|
|
1273
1088
|
const classesToAdd = [];
|
|
1274
1089
|
const classesToRemove = [];
|
|
1275
1090
|
const listTheme = editorThemeClasses.list;
|
|
1276
|
-
|
|
1277
1091
|
if (listTheme !== undefined) {
|
|
1278
1092
|
const listLevelsClassNames = listTheme[`${node.__tag}Depth`] || [];
|
|
1279
1093
|
const listDepth = $getListDepth(node) - 1;
|
|
@@ -1282,29 +1096,23 @@ function setListThemeClassNames(dom, editorThemeClasses, node) {
|
|
|
1282
1096
|
const listClassName = listTheme[node.__tag];
|
|
1283
1097
|
let nestedListClassName;
|
|
1284
1098
|
const nestedListTheme = listTheme.nested;
|
|
1285
|
-
|
|
1286
1099
|
if (nestedListTheme !== undefined && nestedListTheme.list) {
|
|
1287
1100
|
nestedListClassName = nestedListTheme.list;
|
|
1288
1101
|
}
|
|
1289
|
-
|
|
1290
1102
|
if (listClassName !== undefined) {
|
|
1291
1103
|
classesToAdd.push(listClassName);
|
|
1292
1104
|
}
|
|
1293
|
-
|
|
1294
1105
|
if (listLevelClassName !== undefined) {
|
|
1295
1106
|
const listItemClasses = listLevelClassName.split(' ');
|
|
1296
1107
|
classesToAdd.push(...listItemClasses);
|
|
1297
|
-
|
|
1298
1108
|
for (let i = 0; i < listLevelsClassNames.length; i++) {
|
|
1299
1109
|
if (i !== normalizedListDepth) {
|
|
1300
1110
|
classesToRemove.push(node.__tag + i);
|
|
1301
1111
|
}
|
|
1302
1112
|
}
|
|
1303
1113
|
}
|
|
1304
|
-
|
|
1305
1114
|
if (nestedListClassName !== undefined) {
|
|
1306
1115
|
const nestedListItemClasses = nestedListClassName.split(' ');
|
|
1307
|
-
|
|
1308
1116
|
if (listDepth > 1) {
|
|
1309
1117
|
classesToAdd.push(...nestedListItemClasses);
|
|
1310
1118
|
} else {
|
|
@@ -1312,32 +1120,26 @@ function setListThemeClassNames(dom, editorThemeClasses, node) {
|
|
|
1312
1120
|
}
|
|
1313
1121
|
}
|
|
1314
1122
|
}
|
|
1315
|
-
|
|
1316
1123
|
if (classesToRemove.length > 0) {
|
|
1317
1124
|
utils.removeClassNamesFromElement(dom, ...classesToRemove);
|
|
1318
1125
|
}
|
|
1319
|
-
|
|
1320
1126
|
if (classesToAdd.length > 0) {
|
|
1321
1127
|
utils.addClassNamesToElement(dom, ...classesToAdd);
|
|
1322
1128
|
}
|
|
1323
1129
|
}
|
|
1130
|
+
|
|
1324
1131
|
/*
|
|
1325
1132
|
* This function normalizes the children of a ListNode after the conversion from HTML,
|
|
1326
1133
|
* ensuring that they are all ListItemNodes and contain either a single nested ListNode
|
|
1327
1134
|
* or some other inline content.
|
|
1328
1135
|
*/
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
1136
|
function normalizeChildren(nodes) {
|
|
1332
1137
|
const normalizedListItems = [];
|
|
1333
|
-
|
|
1334
1138
|
for (let i = 0; i < nodes.length; i++) {
|
|
1335
1139
|
const node = nodes[i];
|
|
1336
|
-
|
|
1337
1140
|
if ($isListItemNode(node)) {
|
|
1338
1141
|
normalizedListItems.push(node);
|
|
1339
1142
|
const children = node.getChildren();
|
|
1340
|
-
|
|
1341
1143
|
if (children.length > 1) {
|
|
1342
1144
|
children.forEach(child => {
|
|
1343
1145
|
if ($isListNode(child)) {
|
|
@@ -1349,14 +1151,11 @@ function normalizeChildren(nodes) {
|
|
|
1349
1151
|
normalizedListItems.push(wrapInListItem(node));
|
|
1350
1152
|
}
|
|
1351
1153
|
}
|
|
1352
|
-
|
|
1353
1154
|
return normalizedListItems;
|
|
1354
1155
|
}
|
|
1355
|
-
|
|
1356
1156
|
function convertListNode(domNode) {
|
|
1357
1157
|
const nodeName = domNode.nodeName.toLowerCase();
|
|
1358
1158
|
let node = null;
|
|
1359
|
-
|
|
1360
1159
|
if (nodeName === 'ol') {
|
|
1361
1160
|
// @ts-ignore
|
|
1362
1161
|
const start = domNode.start;
|
|
@@ -1368,33 +1167,31 @@ function convertListNode(domNode) {
|
|
|
1368
1167
|
node = $createListNode('bullet');
|
|
1369
1168
|
}
|
|
1370
1169
|
}
|
|
1371
|
-
|
|
1372
1170
|
return {
|
|
1373
1171
|
after: normalizeChildren,
|
|
1374
1172
|
node
|
|
1375
1173
|
};
|
|
1376
1174
|
}
|
|
1377
|
-
|
|
1378
1175
|
const TAG_TO_LIST_TYPE = {
|
|
1379
1176
|
ol: 'number',
|
|
1380
1177
|
ul: 'bullet'
|
|
1381
1178
|
};
|
|
1179
|
+
|
|
1382
1180
|
/**
|
|
1383
1181
|
* Creates a ListNode of listType.
|
|
1384
1182
|
* @param listType - The type of list to be created. Can be 'number', 'bullet', or 'check'.
|
|
1385
1183
|
* @param start - Where an ordered list starts its count, start = 1 if left undefined.
|
|
1386
1184
|
* @returns The new ListNode
|
|
1387
1185
|
*/
|
|
1388
|
-
|
|
1389
1186
|
function $createListNode(listType, start = 1) {
|
|
1390
1187
|
return lexical.$applyNodeReplacement(new ListNode(listType, start));
|
|
1391
1188
|
}
|
|
1189
|
+
|
|
1392
1190
|
/**
|
|
1393
1191
|
* Checks to see if the node is a ListNode.
|
|
1394
1192
|
* @param node - The node to be checked.
|
|
1395
1193
|
* @returns true if the node is a ListNode, false otherwise.
|
|
1396
1194
|
*/
|
|
1397
|
-
|
|
1398
1195
|
function $isListNode(node) {
|
|
1399
1196
|
return node instanceof ListNode;
|
|
1400
1197
|
}
|