@ckeditor/ckeditor5-list 35.4.0 → 36.0.1

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.
Files changed (54) hide show
  1. package/LICENSE.md +1 -1
  2. package/build/list.js +2 -2
  3. package/package.json +43 -39
  4. package/src/documentlist/converters.js +303 -419
  5. package/src/documentlist/documentlistcommand.js +136 -207
  6. package/src/documentlist/documentlistediting.js +538 -698
  7. package/src/documentlist/documentlistindentcommand.js +115 -168
  8. package/src/documentlist/documentlistmergecommand.js +161 -222
  9. package/src/documentlist/documentlistsplitcommand.js +59 -103
  10. package/src/documentlist/documentlistutils.js +31 -45
  11. package/src/documentlist/utils/listwalker.js +138 -236
  12. package/src/documentlist/utils/model.js +322 -421
  13. package/src/documentlist/utils/postfixers.js +98 -126
  14. package/src/documentlist/utils/view.js +74 -105
  15. package/src/documentlist.js +13 -19
  16. package/src/documentlistproperties/converters.js +33 -47
  17. package/src/documentlistproperties/documentlistpropertiesediting.js +265 -356
  18. package/src/documentlistproperties/documentlistpropertiesutils.js +32 -57
  19. package/src/documentlistproperties/documentlistreversedcommand.js +40 -61
  20. package/src/documentlistproperties/documentliststartcommand.js +42 -61
  21. package/src/documentlistproperties/documentliststylecommand.js +97 -147
  22. package/src/documentlistproperties/utils/style.js +27 -47
  23. package/src/documentlistproperties.js +13 -19
  24. package/src/index.js +1 -3
  25. package/src/list/converters.js +772 -929
  26. package/src/list/indentcommand.js +105 -140
  27. package/src/list/listcommand.js +262 -315
  28. package/src/list/listediting.js +141 -200
  29. package/src/list/listui.js +16 -25
  30. package/src/list/listutils.js +37 -59
  31. package/src/list/utils.js +295 -378
  32. package/src/list.js +13 -44
  33. package/src/listcommands.js +5 -0
  34. package/src/listconfig.js +5 -0
  35. package/src/listproperties/listpropertiesediting.js +656 -803
  36. package/src/listproperties/listpropertiesui.js +244 -296
  37. package/src/listproperties/listreversedcommand.js +37 -49
  38. package/src/listproperties/liststartcommand.js +37 -49
  39. package/src/listproperties/liststylecommand.js +82 -115
  40. package/src/listproperties/ui/collapsibleview.js +75 -138
  41. package/src/listproperties/ui/listpropertiesview.js +289 -415
  42. package/src/listproperties.js +13 -118
  43. package/src/liststyle.js +18 -24
  44. package/src/todolist/checktodolistcommand.js +60 -102
  45. package/src/todolist/todolistconverters.js +189 -271
  46. package/src/todolist/todolistediting.js +141 -206
  47. package/src/todolist/todolistui.js +14 -21
  48. package/src/todolist.js +13 -19
  49. package/theme/collapsible.css +1 -1
  50. package/theme/documentlist.css +1 -1
  51. package/theme/list.css +40 -0
  52. package/theme/listproperties.css +1 -1
  53. package/theme/liststyles.css +1 -37
  54. package/theme/todolist.css +1 -1
@@ -1,63 +1,49 @@
1
1
  /**
2
- * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
2
+ * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
-
6
- /**
7
- * @module list/documentlist/utils/model
8
- */
9
-
10
5
  import { uid, toArray } from 'ckeditor5/src/utils';
11
6
  import ListWalker, { iterateSiblingListBlocks } from './listwalker';
12
-
13
7
  /**
14
8
  * The list item ID generator.
15
9
  *
16
- * @protected
10
+ * @internal
17
11
  */
18
12
  export class ListItemUid {
19
- /**
20
- * Returns the next ID.
21
- *
22
- * @protected
23
- * @returns {String}
24
- */
25
- /* istanbul ignore next: static function definition */
26
- static next() {
27
- return uid();
28
- }
13
+ /**
14
+ * Returns the next ID.
15
+ *
16
+ * @internal
17
+ */
18
+ /* istanbul ignore next: static function definition */
19
+ static next() {
20
+ return uid();
21
+ }
29
22
  }
30
-
31
23
  /**
32
24
  * Returns true if the given model node is a list item block.
33
25
  *
34
- * @protected
35
- * @param {module:engine/model/node~Node} node A model node.
36
- * @returns {Boolean}
26
+ * @internal
37
27
  */
38
- export function isListItemBlock( node ) {
39
- return !!node && node.is( 'element' ) && node.hasAttribute( 'listItemId' );
28
+ export function isListItemBlock(node) {
29
+ return !!node && node.is('element') && node.hasAttribute('listItemId');
40
30
  }
41
-
42
31
  /**
43
32
  * Returns an array with all elements that represents the same list item.
44
33
  *
45
34
  * It means that values for `listIndent`, and `listItemId` for all items are equal.
46
35
  *
47
- * @protected
48
- * @param {module:engine/model/element~Element} listItem Starting list item element.
49
- * @param {Object} [options]
50
- * @param {Boolean} [options.higherIndent=false] Whether blocks with a higher indent level than the start block should be included
36
+ * @internal
37
+ * @param listItem Starting list item element.
38
+ * @param options.higherIndent Whether blocks with a higher indent level than the start block should be included
51
39
  * in the result.
52
- * @return {Array.<module:engine/model/element~Element>}
53
40
  */
54
- export function getAllListItemBlocks( listItem, options = {} ) {
55
- return [
56
- ...getListItemBlocks( listItem, { ...options, direction: 'backward' } ),
57
- ...getListItemBlocks( listItem, { ...options, direction: 'forward' } )
58
- ];
41
+ export function getAllListItemBlocks(listItem, options = {}) {
42
+ return [
43
+ ...getListItemBlocks(listItem, { ...options, direction: 'backward' }),
44
+ ...getListItemBlocks(listItem, { ...options, direction: 'forward' })
45
+ ];
59
46
  }
60
-
61
47
  /**
62
48
  * Returns an array with elements that represents the same list item in the specified direction.
63
49
  *
@@ -65,470 +51,385 @@ export function getAllListItemBlocks( listItem, options = {} ) {
65
51
  *
66
52
  * **Note**: For backward search the provided item is not included, but for forward search it is included in the result.
67
53
  *
68
- * @protected
69
- * @param {module:engine/model/element~Element} listItem Starting list item element.
70
- * @param {Object} [options]
71
- * @param {'forward'|'backward'} [options.direction='backward'] Walking direction.
72
- * @param {Boolean} [options.higherIndent=false] Whether blocks with a higher indent level than the start block should be included
73
- * in the result.
74
- * @returns {Array.<module:engine/model/element~Element>}
54
+ * @internal
55
+ * @param listItem Starting list item element.
56
+ * @param options.direction Walking direction.
57
+ * @param options.higherIndent Whether blocks with a higher indent level than the start block should be included in the result.
75
58
  */
76
- export function getListItemBlocks( listItem, options = {} ) {
77
- const isForward = options.direction == 'forward';
78
-
79
- const items = Array.from( new ListWalker( listItem, {
80
- ...options,
81
- includeSelf: isForward,
82
- sameIndent: true,
83
- sameAttributes: 'listItemId'
84
- } ) );
85
-
86
- return isForward ? items : items.reverse();
59
+ export function getListItemBlocks(listItem, options = {}) {
60
+ const isForward = options.direction == 'forward';
61
+ const items = Array.from(new ListWalker(listItem, {
62
+ ...options,
63
+ includeSelf: isForward,
64
+ sameIndent: true,
65
+ sameAttributes: 'listItemId'
66
+ }));
67
+ return isForward ? items : items.reverse();
87
68
  }
88
-
89
69
  /**
90
70
  * Returns a list items nested inside the given list item.
91
71
  *
92
- * @protected
93
- * @param {module:engine/model/element~Element} listItem Starting list item element.
94
- * @returns {Array.<module:engine/model/element~Element>}
72
+ * @internal
95
73
  */
96
- export function getNestedListBlocks( listItem ) {
97
- return Array.from( new ListWalker( listItem, {
98
- direction: 'forward',
99
- higherIndent: true
100
- } ) );
74
+ export function getNestedListBlocks(listItem) {
75
+ return Array.from(new ListWalker(listItem, {
76
+ direction: 'forward',
77
+ higherIndent: true
78
+ }));
101
79
  }
102
-
103
80
  /**
104
81
  * Returns array of all blocks/items of the same list as given block (same indent, same type and properties).
105
82
  *
106
- * @protected
107
- * @param {module:engine/model/element~Element} listItem Starting list item element.
108
- * @returns {Array.<module:engine/model/element~Element>}
83
+ * @internal
84
+ * @param listItem Starting list item element.
109
85
  */
110
- export function getListItems( listItem ) {
111
- const backwardBlocks = new ListWalker( listItem, {
112
- sameIndent: true,
113
- sameAttributes: 'listType'
114
- } );
115
-
116
- const forwardBlocks = new ListWalker( listItem, {
117
- sameIndent: true,
118
- sameAttributes: 'listType',
119
- includeSelf: true,
120
- direction: 'forward'
121
- } );
122
-
123
- return [
124
- ...Array.from( backwardBlocks ).reverse(),
125
- ...forwardBlocks
126
- ];
86
+ export function getListItems(listItem) {
87
+ const backwardBlocks = new ListWalker(listItem, {
88
+ sameIndent: true,
89
+ sameAttributes: 'listType'
90
+ });
91
+ const forwardBlocks = new ListWalker(listItem, {
92
+ sameIndent: true,
93
+ sameAttributes: 'listType',
94
+ includeSelf: true,
95
+ direction: 'forward'
96
+ });
97
+ return [
98
+ ...Array.from(backwardBlocks).reverse(),
99
+ ...forwardBlocks
100
+ ];
127
101
  }
128
-
129
102
  /**
130
103
  * Check if the given block is the first in the list item.
131
104
  *
132
- * @protected
133
- * @param {module:engine/model/element~Element} listBlock The list block element.
134
- * @returns {Boolean}
105
+ * @internal
106
+ * @param listBlock The list block element.
135
107
  */
136
- export function isFirstBlockOfListItem( listBlock ) {
137
- const previousSibling = ListWalker.first( listBlock, {
138
- sameIndent: true,
139
- sameAttributes: 'listItemId'
140
- } );
141
-
142
- if ( !previousSibling ) {
143
- return true;
144
- }
145
-
146
- return false;
108
+ export function isFirstBlockOfListItem(listBlock) {
109
+ const previousSibling = ListWalker.first(listBlock, {
110
+ sameIndent: true,
111
+ sameAttributes: 'listItemId'
112
+ });
113
+ if (!previousSibling) {
114
+ return true;
115
+ }
116
+ return false;
147
117
  }
148
-
149
118
  /**
150
119
  * Check if the given block is the last in the list item.
151
120
  *
152
- * @protected
153
- * @param {module:engine/model/element~Element} listBlock The list block element.
154
- * @returns {Boolean}
121
+ * @internal
155
122
  */
156
- export function isLastBlockOfListItem( listBlock ) {
157
- const nextSibling = ListWalker.first( listBlock, {
158
- direction: 'forward',
159
- sameIndent: true,
160
- sameAttributes: 'listItemId'
161
- } );
162
-
163
- if ( !nextSibling ) {
164
- return true;
165
- }
166
-
167
- return false;
123
+ export function isLastBlockOfListItem(listBlock) {
124
+ const nextSibling = ListWalker.first(listBlock, {
125
+ direction: 'forward',
126
+ sameIndent: true,
127
+ sameAttributes: 'listItemId'
128
+ });
129
+ if (!nextSibling) {
130
+ return true;
131
+ }
132
+ return false;
168
133
  }
169
-
170
134
  /**
171
135
  * Expands the given list of selected blocks to include the leading and tailing blocks of partially selected list items.
172
136
  *
173
- * @protected
174
- * @param {module:engine/model/element~Element|Array.<module:engine/model/element~Element>} blocks The list of selected blocks.
175
- * @param {Object} [options]
176
- * @param {Boolean} [options.withNested=true] Whether should include nested list items.
177
- * @returns {Array.<module:engine/model/element~Element>}
137
+ * @internal
138
+ * @param blocks The list of selected blocks.
139
+ * @param options.withNested Whether should include nested list items.
178
140
  */
179
- export function expandListBlocksToCompleteItems( blocks, options = {} ) {
180
- blocks = toArray( blocks );
181
-
182
- const higherIndent = options.withNested !== false;
183
- const allBlocks = new Set();
184
-
185
- for ( const block of blocks ) {
186
- for ( const itemBlock of getAllListItemBlocks( block, { higherIndent } ) ) {
187
- allBlocks.add( itemBlock );
188
- }
189
- }
190
-
191
- return sortBlocks( allBlocks );
141
+ export function expandListBlocksToCompleteItems(blocks, options = {}) {
142
+ blocks = toArray(blocks);
143
+ const higherIndent = options.withNested !== false;
144
+ const allBlocks = new Set();
145
+ for (const block of blocks) {
146
+ for (const itemBlock of getAllListItemBlocks(block, { higherIndent })) {
147
+ allBlocks.add(itemBlock);
148
+ }
149
+ }
150
+ return sortBlocks(allBlocks);
192
151
  }
193
-
194
152
  /**
195
153
  * Expands the given list of selected blocks to include all the items of the lists they're in.
196
154
  *
197
- * @protected
198
- * @param {module:engine/model/element~Element|Array.<module:engine/model/element~Element>} blocks The list of selected blocks.
199
- * @returns {Array.<module:engine/model/element~Element>}
155
+ * @internal
156
+ * @param blocks The list of selected blocks.
200
157
  */
201
- export function expandListBlocksToCompleteList( blocks ) {
202
- blocks = toArray( blocks );
203
-
204
- const allBlocks = new Set();
205
-
206
- for ( const block of blocks ) {
207
- for ( const itemBlock of getListItems( block ) ) {
208
- allBlocks.add( itemBlock );
209
- }
210
- }
211
-
212
- return sortBlocks( allBlocks );
158
+ export function expandListBlocksToCompleteList(blocks) {
159
+ blocks = toArray(blocks);
160
+ const allBlocks = new Set();
161
+ for (const block of blocks) {
162
+ for (const itemBlock of getListItems(block)) {
163
+ allBlocks.add(itemBlock);
164
+ }
165
+ }
166
+ return sortBlocks(allBlocks);
213
167
  }
214
-
215
168
  /**
216
169
  * Splits the list item just before the provided list block.
217
170
  *
218
- * @protected
219
- * @param {module:engine/model/element~Element} listBlock The list block element.
220
- * @param {module:engine/model/writer~Writer} writer The model writer.
221
- * @returns {Array.<module:engine/model/element~Element>} The array of updated blocks.
171
+ * @internal
172
+ * @param listBlock The list block element.
173
+ * @param writer The model writer.
174
+ * @returns The array of updated blocks.
222
175
  */
223
- export function splitListItemBefore( listBlock, writer ) {
224
- const blocks = getListItemBlocks( listBlock, { direction: 'forward' } );
225
- const id = ListItemUid.next();
226
-
227
- for ( const block of blocks ) {
228
- writer.setAttribute( 'listItemId', id, block );
229
- }
230
-
231
- return blocks;
176
+ export function splitListItemBefore(listBlock, writer) {
177
+ const blocks = getListItemBlocks(listBlock, { direction: 'forward' });
178
+ const id = ListItemUid.next();
179
+ for (const block of blocks) {
180
+ writer.setAttribute('listItemId', id, block);
181
+ }
182
+ return blocks;
232
183
  }
233
-
234
184
  /**
235
185
  * Merges the list item with the parent list item.
236
186
  *
237
- * @protected
238
- * @param {module:engine/model/element~Element} listBlock The list block element.
239
- * @param {module:engine/model/element~Element} parentBlock The list block element to merge with.
240
- * @param {module:engine/model/writer~Writer} writer The model writer.
241
- * @returns {Array.<module:engine/model/element~Element>} The array of updated blocks.
187
+ * @internal
188
+ * @param listBlock The list block element.
189
+ * @param parentBlock The list block element to merge with.
190
+ * @param writer The model writer.
191
+ * @returns The array of updated blocks.
242
192
  */
243
- export function mergeListItemBefore( listBlock, parentBlock, writer ) {
244
- const attributes = {};
245
-
246
- for ( const [ key, value ] of parentBlock.getAttributes() ) {
247
- if ( key.startsWith( 'list' ) ) {
248
- attributes[ key ] = value;
249
- }
250
- }
251
-
252
- const blocks = getListItemBlocks( listBlock, { direction: 'forward' } );
253
-
254
- for ( const block of blocks ) {
255
- writer.setAttributes( attributes, block );
256
- }
257
-
258
- return blocks;
193
+ export function mergeListItemBefore(listBlock, parentBlock, writer) {
194
+ const attributes = {};
195
+ for (const [key, value] of parentBlock.getAttributes()) {
196
+ if (key.startsWith('list')) {
197
+ attributes[key] = value;
198
+ }
199
+ }
200
+ const blocks = getListItemBlocks(listBlock, { direction: 'forward' });
201
+ for (const block of blocks) {
202
+ writer.setAttributes(attributes, block);
203
+ }
204
+ return blocks;
259
205
  }
260
-
261
206
  /**
262
207
  * Increases indentation of given list blocks.
263
208
  *
264
- * @protected
265
- * @param {module:engine/model/element~Element|Iterable.<module:engine/model/element~Element>} blocks The block or iterable of blocks.
266
- * @param {module:engine/model/writer~Writer} writer The model writer.
267
- * @param {Object} [options]
268
- * @param {Boolean} [options.expand=false] Whether should expand the list of blocks to include complete list items.
269
- * @param {Number} [options.indentBy=1] The number of levels the indentation should change (could be negative).
209
+ * @internal
210
+ * @param blocks The block or iterable of blocks.
211
+ * @param writer The model writer.
212
+ * @param options.expand Whether should expand the list of blocks to include complete list items.
213
+ * @param options.indentBy The number of levels the indentation should change (could be negative).
270
214
  */
271
- export function indentBlocks( blocks, writer, { expand, indentBy = 1 } = {} ) {
272
- blocks = toArray( blocks );
273
-
274
- // Expand the selected blocks to contain the whole list items.
275
- const allBlocks = expand ? expandListBlocksToCompleteItems( blocks ) : blocks;
276
-
277
- for ( const block of allBlocks ) {
278
- const blockIndent = block.getAttribute( 'listIndent' ) + indentBy;
279
-
280
- if ( blockIndent < 0 ) {
281
- removeListAttributes( block, writer );
282
- } else {
283
- writer.setAttribute( 'listIndent', blockIndent, block );
284
- }
285
- }
286
-
287
- return allBlocks;
215
+ export function indentBlocks(blocks, writer, { expand, indentBy = 1 } = {}) {
216
+ blocks = toArray(blocks);
217
+ // Expand the selected blocks to contain the whole list items.
218
+ const allBlocks = expand ? expandListBlocksToCompleteItems(blocks) : blocks;
219
+ for (const block of allBlocks) {
220
+ const blockIndent = block.getAttribute('listIndent') + indentBy;
221
+ if (blockIndent < 0) {
222
+ removeListAttributes(block, writer);
223
+ }
224
+ else {
225
+ writer.setAttribute('listIndent', blockIndent, block);
226
+ }
227
+ }
228
+ return allBlocks;
288
229
  }
289
-
290
230
  /**
291
231
  * Decreases indentation of given list of blocks. If the indentation of some blocks matches the indentation
292
232
  * of surrounding blocks, they get merged together.
293
233
  *
294
- * @protected
295
- * @param {module:engine/model/element~Element|Iterable.<module:engine/model/element~Element>} blocks The block or iterable of blocks.
296
- * @param {module:engine/model/writer~Writer} writer The model writer.
234
+ * @internal
235
+ * @param blocks The block or iterable of blocks.
236
+ * @param writer The model writer.
297
237
  */
298
- export function outdentBlocksWithMerge( blocks, writer ) {
299
- blocks = toArray( blocks );
300
-
301
- // Expand the selected blocks to contain the whole list items.
302
- const allBlocks = expandListBlocksToCompleteItems( blocks );
303
- const visited = new Set();
304
-
305
- const referenceIndent = Math.min( ...allBlocks.map( block => block.getAttribute( 'listIndent' ) ) );
306
- const parentBlocks = new Map();
307
-
308
- // Collect parent blocks before the list structure gets altered.
309
- for ( const block of allBlocks ) {
310
- parentBlocks.set( block, ListWalker.first( block, { lowerIndent: true } ) );
311
- }
312
-
313
- for ( const block of allBlocks ) {
314
- if ( visited.has( block ) ) {
315
- continue;
316
- }
317
-
318
- visited.add( block );
319
-
320
- const blockIndent = block.getAttribute( 'listIndent' ) - 1;
321
-
322
- if ( blockIndent < 0 ) {
323
- removeListAttributes( block, writer );
324
-
325
- continue;
326
- }
327
-
328
- // Merge with parent list item while outdenting and indent matches reference indent.
329
- if ( block.getAttribute( 'listIndent' ) == referenceIndent ) {
330
- const mergedBlocks = mergeListItemIfNotLast( block, parentBlocks.get( block ), writer );
331
-
332
- // All list item blocks are updated while merging so add those to visited set.
333
- for ( const mergedBlock of mergedBlocks ) {
334
- visited.add( mergedBlock );
335
- }
336
-
337
- // The indent level was updated while merging so continue to next block.
338
- if ( mergedBlocks.length ) {
339
- continue;
340
- }
341
- }
342
-
343
- writer.setAttribute( 'listIndent', blockIndent, block );
344
- }
345
-
346
- return sortBlocks( visited );
238
+ export function outdentBlocksWithMerge(blocks, writer) {
239
+ blocks = toArray(blocks);
240
+ // Expand the selected blocks to contain the whole list items.
241
+ const allBlocks = expandListBlocksToCompleteItems(blocks);
242
+ const visited = new Set();
243
+ const referenceIndent = Math.min(...allBlocks.map(block => block.getAttribute('listIndent')));
244
+ const parentBlocks = new Map();
245
+ // Collect parent blocks before the list structure gets altered.
246
+ for (const block of allBlocks) {
247
+ parentBlocks.set(block, ListWalker.first(block, { lowerIndent: true }));
248
+ }
249
+ for (const block of allBlocks) {
250
+ if (visited.has(block)) {
251
+ continue;
252
+ }
253
+ visited.add(block);
254
+ const blockIndent = block.getAttribute('listIndent') - 1;
255
+ if (blockIndent < 0) {
256
+ removeListAttributes(block, writer);
257
+ continue;
258
+ }
259
+ // Merge with parent list item while outdenting and indent matches reference indent.
260
+ if (block.getAttribute('listIndent') == referenceIndent) {
261
+ const mergedBlocks = mergeListItemIfNotLast(block, parentBlocks.get(block), writer);
262
+ // All list item blocks are updated while merging so add those to visited set.
263
+ for (const mergedBlock of mergedBlocks) {
264
+ visited.add(mergedBlock);
265
+ }
266
+ // The indent level was updated while merging so continue to next block.
267
+ if (mergedBlocks.length) {
268
+ continue;
269
+ }
270
+ }
271
+ writer.setAttribute('listIndent', blockIndent, block);
272
+ }
273
+ return sortBlocks(visited);
347
274
  }
348
-
349
275
  /**
350
276
  * Removes all list attributes from the given blocks.
351
277
  *
352
- * @protected
353
- * @param {module:engine/model/element~Element|Iterable.<module:engine/model/element~Element>} blocks The block or iterable of blocks.
354
- * @param {module:engine/model/writer~Writer} writer The model writer.
355
- * @returns {Array.<module:engine/model/element~Element>} Array of altered blocks.
278
+ * @internal
279
+ * @param blocks The block or iterable of blocks.
280
+ * @param writer The model writer.
281
+ * @returns Array of altered blocks.
356
282
  */
357
- export function removeListAttributes( blocks, writer ) {
358
- blocks = toArray( blocks );
359
-
360
- for ( const block of blocks ) {
361
- for ( const attributeKey of block.getAttributeKeys() ) {
362
- if ( attributeKey.startsWith( 'list' ) ) {
363
- writer.removeAttribute( attributeKey, block );
364
- }
365
- }
366
- }
367
-
368
- return blocks;
283
+ export function removeListAttributes(blocks, writer) {
284
+ blocks = toArray(blocks);
285
+ for (const block of blocks) {
286
+ for (const attributeKey of block.getAttributeKeys()) {
287
+ if (attributeKey.startsWith('list')) {
288
+ writer.removeAttribute(attributeKey, block);
289
+ }
290
+ }
291
+ }
292
+ return blocks;
369
293
  }
370
-
371
294
  /**
372
295
  * Checks whether the given blocks are related to a single list item.
373
296
  *
374
- * @protected
375
- * @param {Array.<module:engine/model/element~Element>} blocks The list block elements.
376
- * @returns {Boolean}
297
+ * @internal
298
+ * @param blocks The list block elements.
377
299
  */
378
- export function isSingleListItem( blocks ) {
379
- if ( !blocks.length ) {
380
- return false;
381
- }
382
-
383
- const firstItemId = blocks[ 0 ].getAttribute( 'listItemId' );
384
-
385
- if ( !firstItemId ) {
386
- return false;
387
- }
388
-
389
- return !blocks.some( item => item.getAttribute( 'listItemId' ) != firstItemId );
300
+ export function isSingleListItem(blocks) {
301
+ if (!blocks.length) {
302
+ return false;
303
+ }
304
+ const firstItemId = blocks[0].getAttribute('listItemId');
305
+ if (!firstItemId) {
306
+ return false;
307
+ }
308
+ return !blocks.some(item => item.getAttribute('listItemId') != firstItemId);
390
309
  }
391
-
392
310
  /**
393
311
  * Modifies the indents of list blocks following the given list block so the indentation is valid after
394
312
  * the given block is no longer a list item.
395
313
  *
396
- * @protected
397
- * @param {module:engine/model/element~Element} lastBlock The last list block that has become a non-list element.
398
- * @param {module:engine/model/writer~Writer} writer The model writer.
399
- * @returns {Array.<module:engine/model/element~Element>} Array of altered blocks.
314
+ * @internal
315
+ * @param lastBlock The last list block that has become a non-list element.
316
+ * @param writer The model writer.
317
+ * @returns Array of altered blocks.
400
318
  */
401
- export function outdentFollowingItems( lastBlock, writer ) {
402
- const changedBlocks = [];
403
-
404
- // Start from the model item that is just after the last turned-off item.
405
- let currentIndent = Number.POSITIVE_INFINITY;
406
-
407
- // Correct indent of all items after the last turned off item.
408
- // Rules that should be followed:
409
- // 1. All direct sub-items of turned-off item should become indent 0, because the first item after it
410
- // will be the first item of a new list. Other items are at the same level, so should have same 0 index.
411
- // 2. All items with indent lower than indent of turned-off item should become indent 0, because they
412
- // should not end up as a child of any of list items that they were not children of before.
413
- // 3. All other items should have their indent changed relatively to it's parent.
414
- //
415
- // For example:
416
- // 1 * --------
417
- // 2 * --------
418
- // 3 * -------- <-- this is turned off.
419
- // 4 * -------- <-- this has to become indent = 0, because it will be first item on a new list.
420
- // 5 * -------- <-- this should be still be a child of item above, so indent = 1.
421
- // 6 * -------- <-- this has to become indent = 0, because it should not be a child of any of items above.
422
- // 7 * -------- <-- this should be still be a child of item above, so indent = 1.
423
- // 8 * -------- <-- this has to become indent = 0.
424
- // 9 * -------- <-- this should still be a child of item above, so indent = 1.
425
- // 10 * -------- <-- this should still be a child of item above, so indent = 2.
426
- // 11 * -------- <-- this should still be at the same level as item above, so indent = 2.
427
- // 12 * -------- <-- this and all below are left unchanged.
428
- // 13 * --------
429
- // 14 * --------
430
- //
431
- // After turning off 3 the list becomes:
432
- //
433
- // 1 * --------
434
- // 2 * --------
435
- //
436
- // 3 --------
437
- //
438
- // 4 * --------
439
- // 5 * --------
440
- // 6 * --------
441
- // 7 * --------
442
- // 8 * --------
443
- // 9 * --------
444
- // 10 * --------
445
- // 11 * --------
446
- // 12 * --------
447
- // 13 * --------
448
- // 14 * --------
449
- //
450
- // Thanks to this algorithm no lists are mismatched and no items get unexpected children/parent, while
451
- // those parent-child connection which are possible to maintain are still maintained. It's worth noting
452
- // that this is the same effect that we would be get by multiple use of outdent command. However doing
453
- // it like this is much more efficient because it's less operation (less memory usage, easier OT) and
454
- // less conversion (faster).
455
- for ( const { node } of iterateSiblingListBlocks( lastBlock.nextSibling, 'forward' ) ) {
456
- // Check each next list item, as long as its indent is higher than 0.
457
- const indent = node.getAttribute( 'listIndent' );
458
-
459
- // If the indent is 0 we are not going to change anything anyway.
460
- if ( indent == 0 ) {
461
- break;
462
- }
463
-
464
- // We check if that's item indent is lower than current relative indent.
465
- if ( indent < currentIndent ) {
466
- // If it is, current relative indent becomes that indent.
467
- currentIndent = indent;
468
- }
469
-
470
- // Fix indent relatively to current relative indent.
471
- // Note, that if we just changed the current relative indent, the newIndent will be equal to 0.
472
- const newIndent = indent - currentIndent;
473
-
474
- writer.setAttribute( 'listIndent', newIndent, node );
475
- changedBlocks.push( node );
476
- }
477
-
478
- return changedBlocks;
319
+ export function outdentFollowingItems(lastBlock, writer) {
320
+ const changedBlocks = [];
321
+ // Start from the model item that is just after the last turned-off item.
322
+ let currentIndent = Number.POSITIVE_INFINITY;
323
+ // Correct indent of all items after the last turned off item.
324
+ // Rules that should be followed:
325
+ // 1. All direct sub-items of turned-off item should become indent 0, because the first item after it
326
+ // will be the first item of a new list. Other items are at the same level, so should have same 0 index.
327
+ // 2. All items with indent lower than indent of turned-off item should become indent 0, because they
328
+ // should not end up as a child of any of list items that they were not children of before.
329
+ // 3. All other items should have their indent changed relatively to it's parent.
330
+ //
331
+ // For example:
332
+ // 1 * --------
333
+ // 2 * --------
334
+ // 3 * -------- <-- this is turned off.
335
+ // 4 * -------- <-- this has to become indent = 0, because it will be first item on a new list.
336
+ // 5 * -------- <-- this should be still be a child of item above, so indent = 1.
337
+ // 6 * -------- <-- this has to become indent = 0, because it should not be a child of any of items above.
338
+ // 7 * -------- <-- this should be still be a child of item above, so indent = 1.
339
+ // 8 * -------- <-- this has to become indent = 0.
340
+ // 9 * -------- <-- this should still be a child of item above, so indent = 1.
341
+ // 10 * -------- <-- this should still be a child of item above, so indent = 2.
342
+ // 11 * -------- <-- this should still be at the same level as item above, so indent = 2.
343
+ // 12 * -------- <-- this and all below are left unchanged.
344
+ // 13 * --------
345
+ // 14 * --------
346
+ //
347
+ // After turning off 3 the list becomes:
348
+ //
349
+ // 1 * --------
350
+ // 2 * --------
351
+ //
352
+ // 3 --------
353
+ //
354
+ // 4 * --------
355
+ // 5 * --------
356
+ // 6 * --------
357
+ // 7 * --------
358
+ // 8 * --------
359
+ // 9 * --------
360
+ // 10 * --------
361
+ // 11 * --------
362
+ // 12 * --------
363
+ // 13 * --------
364
+ // 14 * --------
365
+ //
366
+ // Thanks to this algorithm no lists are mismatched and no items get unexpected children/parent, while
367
+ // those parent-child connection which are possible to maintain are still maintained. It's worth noting
368
+ // that this is the same effect that we would be get by multiple use of outdent command. However doing
369
+ // it like this is much more efficient because it's less operation (less memory usage, easier OT) and
370
+ // less conversion (faster).
371
+ for (const { node } of iterateSiblingListBlocks(lastBlock.nextSibling, 'forward')) {
372
+ // Check each next list item, as long as its indent is higher than 0.
373
+ const indent = node.getAttribute('listIndent');
374
+ // If the indent is 0 we are not going to change anything anyway.
375
+ if (indent == 0) {
376
+ break;
377
+ }
378
+ // We check if that's item indent is lower than current relative indent.
379
+ if (indent < currentIndent) {
380
+ // If it is, current relative indent becomes that indent.
381
+ currentIndent = indent;
382
+ }
383
+ // Fix indent relatively to current relative indent.
384
+ // Note, that if we just changed the current relative indent, the newIndent will be equal to 0.
385
+ const newIndent = indent - currentIndent;
386
+ writer.setAttribute('listIndent', newIndent, node);
387
+ changedBlocks.push(node);
388
+ }
389
+ return changedBlocks;
479
390
  }
480
-
481
391
  /**
482
392
  * Returns the array of given blocks sorted by model indexes (document order).
483
393
  *
484
- * @protected
485
- * @param {Iterable.<module:engine/model/element~Element>} blocks The array of blocks.
486
- * @returns {Array.<module:engine/model/element~Element>} The sorted array of blocks.
394
+ * @internal
487
395
  */
488
- export function sortBlocks( blocks ) {
489
- return Array.from( blocks )
490
- .filter( block => block.root.rootName !== '$graveyard' )
491
- .sort( ( a, b ) => a.index - b.index );
396
+ export function sortBlocks(blocks) {
397
+ return Array.from(blocks)
398
+ .filter(block => block.root.rootName !== '$graveyard')
399
+ .sort((a, b) => a.index - b.index);
492
400
  }
493
-
494
401
  /**
495
402
  * Returns a selected block object. If a selected object is inline or when there is no selected
496
403
  * object, `null` is returned.
497
404
  *
498
- * @protected
499
- * @param {module:engine/model/model~Model} model The instance of editor model.
500
- * @returns {module:engine/model/element~Element|null} Selected block object or `null`.
405
+ * @internal
406
+ * @param model The instance of editor model.
407
+ * @returns Selected block object or `null`.
501
408
  */
502
- export function getSelectedBlockObject( model ) {
503
- const selectedElement = model.document.selection.getSelectedElement();
504
-
505
- if ( !selectedElement ) {
506
- return null;
507
- }
508
-
509
- if ( model.schema.isObject( selectedElement ) && model.schema.isBlock( selectedElement ) ) {
510
- return selectedElement;
511
- }
512
-
513
- return null;
409
+ export function getSelectedBlockObject(model) {
410
+ const selectedElement = model.document.selection.getSelectedElement();
411
+ if (!selectedElement) {
412
+ return null;
413
+ }
414
+ if (model.schema.isObject(selectedElement) && model.schema.isBlock(selectedElement)) {
415
+ return selectedElement;
416
+ }
417
+ return null;
514
418
  }
515
-
516
419
  // Merges a given block to the given parent block if parent is a list item and there is no more blocks in the same item.
517
- function mergeListItemIfNotLast( block, parentBlock, writer ) {
518
- const parentItemBlocks = getListItemBlocks( parentBlock, { direction: 'forward' } );
519
-
520
- // Merge with parent only if outdented item wasn't the last one in its parent.
521
- // Merge:
522
- // * a -> * a
523
- // * [b] -> b
524
- // c -> c
525
- // Don't merge:
526
- // * a -> * a
527
- // * [b] -> * b
528
- // * c -> * c
529
- if ( parentItemBlocks.pop().index > block.index ) {
530
- return mergeListItemBefore( block, parentBlock, writer );
531
- }
532
-
533
- return [];
420
+ function mergeListItemIfNotLast(block, parentBlock, writer) {
421
+ const parentItemBlocks = getListItemBlocks(parentBlock, { direction: 'forward' });
422
+ // Merge with parent only if outdented item wasn't the last one in its parent.
423
+ // Merge:
424
+ // * a -> * a
425
+ // * [b] -> b
426
+ // c -> c
427
+ // Don't merge:
428
+ // * a -> * a
429
+ // * [b] -> * b
430
+ // * c -> * c
431
+ if (parentItemBlocks.pop().index > block.index) {
432
+ return mergeListItemBefore(block, parentBlock, writer);
433
+ }
434
+ return [];
534
435
  }