@ckeditor/ckeditor5-list 40.1.0 → 41.0.0

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 (199) hide show
  1. package/CHANGELOG.md +50 -50
  2. package/LICENSE.md +1 -1
  3. package/build/list.js +2 -2
  4. package/build/translations/ug.js +1 -1
  5. package/ckeditor5-metadata.json +34 -34
  6. package/lang/translations/ar.po +1 -1
  7. package/lang/translations/ast.po +1 -1
  8. package/lang/translations/az.po +1 -1
  9. package/lang/translations/bg.po +1 -1
  10. package/lang/translations/bn.po +1 -1
  11. package/lang/translations/ca.po +1 -1
  12. package/lang/translations/cs.po +1 -1
  13. package/lang/translations/da.po +1 -1
  14. package/lang/translations/de-ch.po +1 -1
  15. package/lang/translations/de.po +1 -1
  16. package/lang/translations/el.po +1 -1
  17. package/lang/translations/en-au.po +1 -1
  18. package/lang/translations/en-gb.po +1 -1
  19. package/lang/translations/en.po +1 -1
  20. package/lang/translations/eo.po +1 -1
  21. package/lang/translations/es.po +1 -1
  22. package/lang/translations/et.po +1 -1
  23. package/lang/translations/eu.po +1 -1
  24. package/lang/translations/fa.po +1 -1
  25. package/lang/translations/fi.po +1 -1
  26. package/lang/translations/fr.po +1 -1
  27. package/lang/translations/gl.po +1 -1
  28. package/lang/translations/he.po +1 -1
  29. package/lang/translations/hi.po +1 -1
  30. package/lang/translations/hr.po +1 -1
  31. package/lang/translations/hu.po +1 -1
  32. package/lang/translations/id.po +1 -1
  33. package/lang/translations/it.po +1 -1
  34. package/lang/translations/ja.po +1 -1
  35. package/lang/translations/jv.po +1 -1
  36. package/lang/translations/km.po +1 -1
  37. package/lang/translations/kn.po +1 -1
  38. package/lang/translations/ko.po +1 -1
  39. package/lang/translations/ku.po +1 -1
  40. package/lang/translations/lt.po +1 -1
  41. package/lang/translations/lv.po +1 -1
  42. package/lang/translations/ms.po +1 -1
  43. package/lang/translations/nb.po +1 -1
  44. package/lang/translations/ne.po +1 -1
  45. package/lang/translations/nl.po +1 -1
  46. package/lang/translations/no.po +1 -1
  47. package/lang/translations/pl.po +1 -1
  48. package/lang/translations/pt-br.po +1 -1
  49. package/lang/translations/pt.po +1 -1
  50. package/lang/translations/ro.po +1 -1
  51. package/lang/translations/ru.po +1 -1
  52. package/lang/translations/si.po +1 -1
  53. package/lang/translations/sk.po +1 -1
  54. package/lang/translations/sq.po +1 -1
  55. package/lang/translations/sr-latn.po +1 -1
  56. package/lang/translations/sr.po +1 -1
  57. package/lang/translations/sv.po +1 -1
  58. package/lang/translations/th.po +1 -1
  59. package/lang/translations/tk.po +1 -1
  60. package/lang/translations/tr.po +1 -1
  61. package/lang/translations/tt.po +1 -1
  62. package/lang/translations/ug.po +11 -11
  63. package/lang/translations/uk.po +1 -1
  64. package/lang/translations/ur.po +1 -1
  65. package/lang/translations/uz.po +1 -1
  66. package/lang/translations/vi.po +1 -1
  67. package/lang/translations/zh-cn.po +1 -1
  68. package/lang/translations/zh.po +1 -1
  69. package/package.json +3 -3
  70. package/src/augmentation.d.ts +29 -28
  71. package/src/augmentation.js +1 -1
  72. package/src/documentlist.d.ts +9 -7
  73. package/src/documentlist.js +18 -7
  74. package/src/documentlistproperties.d.ts +9 -8
  75. package/src/documentlistproperties.js +18 -8
  76. package/src/index.d.ts +39 -37
  77. package/src/index.js +32 -23
  78. package/src/legacylist/legacyconverters.d.ts +196 -0
  79. package/src/legacylist/legacyconverters.js +905 -0
  80. package/src/{list/indentcommand.d.ts → legacylist/legacyindentcommand.d.ts} +4 -4
  81. package/src/{list/indentcommand.js → legacylist/legacyindentcommand.js} +5 -5
  82. package/src/{documentlist/documentlistcommand.d.ts → legacylist/legacylistcommand.d.ts} +5 -30
  83. package/src/legacylist/legacylistcommand.js +274 -0
  84. package/src/legacylist/legacylistediting.d.ts +32 -0
  85. package/src/legacylist/legacylistediting.js +161 -0
  86. package/src/legacylist/legacylistutils.d.ts +41 -0
  87. package/src/legacylist/legacylistutils.js +46 -0
  88. package/src/legacylist/legacyutils.d.ts +101 -0
  89. package/src/legacylist/legacyutils.js +347 -0
  90. package/src/legacylist.d.ts +26 -0
  91. package/src/legacylist.js +30 -0
  92. package/src/legacylistproperties/legacylistpropertiesediting.d.ts +72 -0
  93. package/src/legacylistproperties/legacylistpropertiesediting.js +696 -0
  94. package/src/legacylistproperties/legacylistreversedcommand.d.ts +38 -0
  95. package/src/legacylistproperties/legacylistreversedcommand.js +52 -0
  96. package/src/{documentlistproperties/documentliststartcommand.d.ts → legacylistproperties/legacyliststartcommand.d.ts} +6 -7
  97. package/src/legacylistproperties/legacyliststartcommand.js +51 -0
  98. package/src/{documentlistproperties/documentliststylecommand.d.ts → legacylistproperties/legacyliststylecommand.d.ts} +14 -19
  99. package/src/{documentlistproperties/documentliststylecommand.js → legacylistproperties/legacyliststylecommand.js} +22 -36
  100. package/src/legacylistproperties.d.ts +27 -0
  101. package/src/legacylistproperties.js +31 -0
  102. package/src/{tododocumentlist/checktododocumentlistcommand.d.ts → legacytodolist/legacychecktodolistcommand.d.ts} +17 -14
  103. package/src/{tododocumentlist/checktododocumentlistcommand.js → legacytodolist/legacychecktodolistcommand.js} +34 -40
  104. package/src/{todolist/todolistconverters.d.ts → legacytodolist/legacytodolistconverters.d.ts} +9 -8
  105. package/src/{todolist/todolistconverters.js → legacytodolist/legacytodolistconverters.js} +6 -5
  106. package/src/{tododocumentlist/tododocumentlistediting.d.ts → legacytodolist/legacytodolistediting.d.ts} +9 -8
  107. package/src/legacytodolist/legacytodolistediting.js +161 -0
  108. package/src/legacytodolist.d.ts +27 -0
  109. package/src/legacytodolist.js +31 -0
  110. package/src/{documentlist → list}/adjacentlistssupport.d.ts +2 -2
  111. package/src/{documentlist → list}/adjacentlistssupport.js +9 -9
  112. package/src/list/converters.d.ts +41 -172
  113. package/src/list/converters.js +357 -821
  114. package/src/list/listcommand.d.ts +28 -3
  115. package/src/list/listcommand.js +81 -205
  116. package/src/list/listediting.d.ts +189 -9
  117. package/src/list/listediting.js +592 -107
  118. package/src/{documentlist/documentlistindentcommand.d.ts → list/listindentcommand.d.ts} +10 -10
  119. package/src/{documentlist/documentlistindentcommand.js → list/listindentcommand.js} +7 -7
  120. package/src/{documentlist/documentlistmergecommand.d.ts → list/listmergecommand.d.ts} +10 -10
  121. package/src/{documentlist/documentlistmergecommand.js → list/listmergecommand.js} +7 -7
  122. package/src/{documentlist/documentlistsplitcommand.d.ts → list/listsplitcommand.d.ts} +10 -10
  123. package/src/{documentlist/documentlistsplitcommand.js → list/listsplitcommand.js} +5 -5
  124. package/src/list/listui.d.ts +2 -2
  125. package/src/list/listui.js +5 -7
  126. package/src/list/listutils.d.ts +22 -17
  127. package/src/list/listutils.js +24 -20
  128. package/src/{documentlist → list}/utils/listwalker.d.ts +5 -5
  129. package/src/{documentlist → list}/utils/listwalker.js +4 -4
  130. package/src/{documentlist → list}/utils/model.d.ts +4 -4
  131. package/src/{documentlist → list}/utils/model.js +3 -3
  132. package/src/{documentlist → list}/utils/postfixers.d.ts +5 -5
  133. package/src/{documentlist → list}/utils/postfixers.js +3 -3
  134. package/src/{documentlist → list}/utils/view.d.ts +3 -3
  135. package/src/{documentlist → list}/utils/view.js +1 -1
  136. package/src/list/utils.d.ts +2 -96
  137. package/src/list/utils.js +2 -342
  138. package/src/list.d.ts +6 -6
  139. package/src/list.js +6 -6
  140. package/src/listconfig.d.ts +10 -10
  141. package/src/listconfig.js +1 -1
  142. package/src/{documentlistproperties → listproperties}/converters.d.ts +5 -5
  143. package/src/{documentlistproperties → listproperties}/converters.js +1 -1
  144. package/src/listproperties/listpropertiesediting.d.ts +56 -40
  145. package/src/listproperties/listpropertiesediting.js +145 -575
  146. package/src/listproperties/listpropertiesui.d.ts +2 -2
  147. package/src/listproperties/listpropertiesui.js +6 -8
  148. package/src/{documentlistproperties/documentlistpropertiesutils.d.ts → listproperties/listpropertiesutils.d.ts} +5 -5
  149. package/src/{documentlistproperties/documentlistpropertiesutils.js → listproperties/listpropertiesutils.js} +6 -6
  150. package/src/listproperties/listreversedcommand.d.ts +4 -6
  151. package/src/listproperties/listreversedcommand.js +17 -14
  152. package/src/listproperties/liststartcommand.d.ts +4 -3
  153. package/src/listproperties/liststartcommand.js +17 -11
  154. package/src/listproperties/liststylecommand.d.ts +16 -11
  155. package/src/listproperties/liststylecommand.js +33 -19
  156. package/src/listproperties/ui/listpropertiesview.d.ts +5 -6
  157. package/src/listproperties/ui/listpropertiesview.js +3 -4
  158. package/src/{documentlistproperties → listproperties}/utils/style.d.ts +1 -1
  159. package/src/{documentlistproperties → listproperties}/utils/style.js +2 -2
  160. package/src/listproperties.d.ts +6 -5
  161. package/src/listproperties.js +6 -5
  162. package/src/tododocumentlist.d.ts +9 -8
  163. package/src/tododocumentlist.js +18 -8
  164. package/src/todolist/checktodolistcommand.d.ts +11 -14
  165. package/src/todolist/checktodolistcommand.js +37 -31
  166. package/src/{tododocumentlist → todolist}/todocheckboxchangeobserver.d.ts +6 -6
  167. package/src/{tododocumentlist → todolist}/todocheckboxchangeobserver.js +3 -3
  168. package/src/todolist/todolistediting.d.ts +5 -6
  169. package/src/todolist/todolistediting.js +314 -76
  170. package/src/todolist/todolistui.d.ts +2 -2
  171. package/src/todolist/todolistui.js +4 -5
  172. package/src/todolist.d.ts +6 -6
  173. package/src/todolist.js +6 -6
  174. package/theme/documentlist.css +1 -1
  175. package/theme/list.css +1 -1
  176. package/theme/listproperties.css +1 -1
  177. package/theme/liststyles.css +1 -1
  178. package/theme/todolist.css +1 -1
  179. package/src/documentlist/converters.d.ts +0 -65
  180. package/src/documentlist/converters.js +0 -441
  181. package/src/documentlist/documentlistcommand.js +0 -150
  182. package/src/documentlist/documentlistediting.d.ts +0 -212
  183. package/src/documentlist/documentlistediting.js +0 -646
  184. package/src/documentlist/documentlistutils.d.ts +0 -46
  185. package/src/documentlist/documentlistutils.js +0 -50
  186. package/src/documentlistproperties/documentlistpropertiesediting.d.ts +0 -88
  187. package/src/documentlistproperties/documentlistpropertiesediting.js +0 -266
  188. package/src/documentlistproperties/documentlistreversedcommand.d.ts +0 -36
  189. package/src/documentlistproperties/documentlistreversedcommand.js +0 -55
  190. package/src/documentlistproperties/documentliststartcommand.js +0 -57
  191. package/src/listproperties/ui/collapsibleview.d.ts +0 -63
  192. package/src/listproperties/ui/collapsibleview.js +0 -89
  193. package/src/liststyle.d.ts +0 -28
  194. package/src/liststyle.js +0 -36
  195. package/src/tododocumentlist/tododocumentlistediting.js +0 -399
  196. package/theme/collapsible.css +0 -10
  197. package/theme/icons/bulletedlist.svg +0 -1
  198. package/theme/icons/numberedlist.svg +0 -1
  199. package/theme/icons/todolist.svg +0 -1
@@ -1,8 +1,12 @@
1
1
  /**
2
- * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
2
+ * @license Copyright (c) 2003-2024, 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
- import { Command, type Editor } from 'ckeditor5/src/core';
5
+ /**
6
+ * @module list/list/listcommand
7
+ */
8
+ import type { Element } from 'ckeditor5/src/engine.js';
9
+ import { Command, type Editor } from 'ckeditor5/src/core.js';
6
10
  /**
7
11
  * The list command. It is used by the {@link module:list/list~List list feature}.
8
12
  */
@@ -14,6 +18,7 @@ export default class ListCommand extends Command {
14
18
  /**
15
19
  * A flag indicating whether the command is active, which means that the selection starts in a list of the same type.
16
20
  *
21
+ * @observable
17
22
  * @readonly
18
23
  */
19
24
  value: boolean;
@@ -32,14 +37,21 @@ export default class ListCommand extends Command {
32
37
  * Executes the list command.
33
38
  *
34
39
  * @fires execute
40
+ * @fires afterExecute
35
41
  * @param options Command options.
36
42
  * @param options.forceValue If set, it will force the command behavior. If `true`, the command will try to convert the
37
- * selected items and potentially the neighbor elements to the proper list items. If set to `false`, it will convert selected elements
43
+ * selected items and potentially the neighbor elements to the proper list items. If set to `false` it will convert selected elements
38
44
  * to paragraphs. If not set, the command will toggle selected elements to list items or paragraphs, depending on the selection.
39
45
  */
40
46
  execute(options?: {
41
47
  forceValue?: boolean;
42
48
  }): void;
49
+ /**
50
+ * Fires the `afterExecute` event.
51
+ *
52
+ * @param changedBlocks The changed list elements.
53
+ */
54
+ private _fireAfterExecute;
43
55
  /**
44
56
  * Checks the command's {@link #value}.
45
57
  *
@@ -53,3 +65,16 @@ export default class ListCommand extends Command {
53
65
  */
54
66
  private _checkEnabled;
55
67
  }
68
+ /**
69
+ * Event fired by the {@link ~ListCommand#execute} method.
70
+ *
71
+ * It allows to execute an action after executing the {@link ~ListCommand#execute} method,
72
+ * for example adjusting attributes of changed list items.
73
+ *
74
+ * @internal
75
+ * @eventName ~ListCommand#afterExecute
76
+ */
77
+ export type ListCommandAfterExecuteEvent = {
78
+ name: 'afterExecute';
79
+ args: [changedBlocks: Array<Element>];
80
+ };
@@ -1,9 +1,9 @@
1
1
  /**
2
- * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
2
+ * @license Copyright (c) 2003-2024, 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
- import { Command } from 'ckeditor5/src/core';
6
- import { first } from 'ckeditor5/src/utils';
5
+ import { Command } from 'ckeditor5/src/core.js';
6
+ import { splitListItemBefore, expandListBlocksToCompleteItems, getListItemBlocks, getListItems, removeListAttributes, outdentFollowingItems, ListItemUid, sortBlocks, getSelectedBlockObject, isListItemBlock, canBecomeSimpleListItem } from './utils/model.js';
7
7
  /**
8
8
  * The list command. It is used by the {@link module:list/list~List list feature}.
9
9
  */
@@ -29,175 +29,99 @@ export default class ListCommand extends Command {
29
29
  * Executes the list command.
30
30
  *
31
31
  * @fires execute
32
+ * @fires afterExecute
32
33
  * @param options Command options.
33
34
  * @param options.forceValue If set, it will force the command behavior. If `true`, the command will try to convert the
34
- * selected items and potentially the neighbor elements to the proper list items. If set to `false`, it will convert selected elements
35
+ * selected items and potentially the neighbor elements to the proper list items. If set to `false` it will convert selected elements
35
36
  * to paragraphs. If not set, the command will toggle selected elements to list items or paragraphs, depending on the selection.
36
37
  */
37
38
  execute(options = {}) {
38
39
  const model = this.editor.model;
39
40
  const document = model.document;
41
+ const selectedBlockObject = getSelectedBlockObject(model);
40
42
  const blocks = Array.from(document.selection.getSelectedBlocks())
41
- .filter(block => checkCanBecomeListItem(block, model.schema));
43
+ .filter(block => model.schema.checkAttribute(block, 'listType') || canBecomeSimpleListItem(block, model.schema));
42
44
  // Whether we are turning off some items.
43
45
  const turnOff = options.forceValue !== undefined ? !options.forceValue : this.value;
44
- // If we are turning off items, we are going to rename them to paragraphs.
45
46
  model.change(writer => {
46
- // If part of a list got turned off, we need to handle (outdent) all of sub-items of the last turned-off item.
47
- // To be sure that model is all the time in a good state, we first fix items below turned-off item.
48
47
  if (turnOff) {
49
- // Start from the model item that is just after the last turned-off item.
50
- let next = blocks[blocks.length - 1].nextSibling;
51
- let currentIndent = Number.POSITIVE_INFINITY;
52
- let changes = [];
53
- // Correct indent of all items after the last turned off item.
54
- // Rules that should be followed:
55
- // 1. All direct sub-items of turned-off item should become indent 0, because the first item after it
56
- // will be the first item of a new list. Other items are at the same level, so should have same 0 index.
57
- // 2. All items with indent lower than indent of turned-off item should become indent 0, because they
58
- // should not end up as a child of any of list items that they were not children of before.
59
- // 3. All other items should have their indent changed relatively to it's parent.
60
- //
61
- // For example:
62
- // 1 * --------
63
- // 2 * --------
64
- // 3 * -------- <-- this is turned off.
65
- // 4 * -------- <-- this has to become indent = 0, because it will be first item on a new list.
66
- // 5 * -------- <-- this should be still be a child of item above, so indent = 1.
67
- // 6 * -------- <-- this has to become indent = 0, because it should not be a child of any of items above.
68
- // 7 * -------- <-- this should be still be a child of item above, so indent = 1.
69
- // 8 * -------- <-- this has to become indent = 0.
70
- // 9 * -------- <-- this should still be a child of item above, so indent = 1.
71
- // 10 * -------- <-- this should still be a child of item above, so indent = 2.
72
- // 11 * -------- <-- this should still be at the same level as item above, so indent = 2.
73
- // 12 * -------- <-- this and all below are left unchanged.
74
- // 13 * --------
75
- // 14 * --------
76
- //
77
- // After turning off 3 the list becomes:
78
- //
79
- // 1 * --------
80
- // 2 * --------
81
- //
82
- // 3 --------
83
- //
84
- // 4 * --------
85
- // 5 * --------
86
- // 6 * --------
87
- // 7 * --------
88
- // 8 * --------
89
- // 9 * --------
90
- // 10 * --------
91
- // 11 * --------
92
- // 12 * --------
93
- // 13 * --------
94
- // 14 * --------
95
- //
96
- // Thanks to this algorithm no lists are mismatched and no items get unexpected children/parent, while
97
- // those parent-child connection which are possible to maintain are still maintained. It's worth noting
98
- // that this is the same effect that we would be get by multiple use of outdent command. However doing
99
- // it like this is much more efficient because it's less operation (less memory usage, easier OT) and
100
- // less conversion (faster).
101
- while (next && next.name == 'listItem' && next.getAttribute('listIndent') !== 0) {
102
- // Check each next list item, as long as its indent is bigger than 0.
103
- // If the indent is 0 we are not going to change anything anyway.
104
- const indent = next.getAttribute('listIndent');
105
- // We check if that's item indent is lower as current relative indent.
106
- if (indent < currentIndent) {
107
- // If it is, current relative indent becomes that indent.
108
- currentIndent = indent;
109
- }
110
- // Fix indent relatively to current relative indent.
111
- // Note, that if we just changed the current relative indent, the newIndent will be equal to 0.
112
- const newIndent = indent - currentIndent;
113
- // Save the entry in changes array. We do not apply it at the moment, because we will need to
114
- // reverse the changes so the last item is changed first.
115
- // This is to keep model in correct state all the time.
116
- changes.push({ element: next, listIndent: newIndent });
117
- // Find next item.
118
- next = next.nextSibling;
119
- }
120
- changes = changes.reverse();
121
- for (const item of changes) {
122
- writer.setAttribute('listIndent', item.listIndent, item.element);
48
+ const lastBlock = blocks[blocks.length - 1];
49
+ // Split the first block from the list item.
50
+ const itemBlocks = getListItemBlocks(lastBlock, { direction: 'forward' });
51
+ const changedBlocks = [];
52
+ if (itemBlocks.length > 1) {
53
+ changedBlocks.push(...splitListItemBefore(itemBlocks[1], writer));
123
54
  }
55
+ // Strip list attributes.
56
+ changedBlocks.push(...removeListAttributes(blocks, writer));
57
+ // Outdent items following the selected list item.
58
+ changedBlocks.push(...outdentFollowingItems(lastBlock, writer));
59
+ this._fireAfterExecute(changedBlocks);
124
60
  }
125
- // If we are turning on, we might change some items that are already `listItem`s but with different type.
126
- // Changing one nested list item to other type should also trigger changing all its siblings so the
127
- // whole nested list is of the same type.
128
- // Example (assume changing to numbered list):
129
- // * ------ <-- do not fix, top level item
130
- // * ------ <-- fix, because latter list item of this item's list is changed
131
- // * ------ <-- do not fix, item is not affected (different list)
132
- // * ------ <-- fix, because latter list item of this item's list is changed
133
- // * ------ <-- fix, because latter list item of this item's list is changed
134
- // * ---[-- <-- already in selection
135
- // * ------ <-- already in selection
136
- // * ------ <-- already in selection
137
- // * ------ <-- already in selection, but does not cause other list items to change because is top-level
138
- // * ---]-- <-- already in selection
139
- // * ------ <-- fix, because preceding list item of this item's list is changed
140
- // * ------ <-- do not fix, item is not affected (different list)
141
- // * ------ <-- do not fix, top level item
142
- if (!turnOff) {
143
- // Find lowest indent among selected items. This will be indicator what is the indent of
144
- // top-most list affected by the command.
145
- let lowestIndent = Number.POSITIVE_INFINITY;
146
- for (const item of blocks) {
147
- if (item.is('element', 'listItem') && item.getAttribute('listIndent') < lowestIndent) {
148
- lowestIndent = item.getAttribute('listIndent');
149
- }
61
+ // Changing type of list items for a collapsed selection inside a list item.
62
+ else if ((selectedBlockObject || document.selection.isCollapsed) && isListItemBlock(blocks[0])) {
63
+ const changedBlocks = getListItems(selectedBlockObject || blocks[0]);
64
+ for (const block of changedBlocks) {
65
+ writer.setAttribute('listType', this.type, block);
150
66
  }
151
- // Do not execute the fix for top-level lists.
152
- lowestIndent = lowestIndent === 0 ? 1 : lowestIndent;
153
- // Fix types of list items that are "before" the selected blocks.
154
- _fixType(blocks, true, lowestIndent);
155
- // Fix types of list items that are "after" the selected blocks.
156
- _fixType(blocks, false, lowestIndent);
67
+ this._fireAfterExecute(changedBlocks);
157
68
  }
158
- // Phew! Now it will be easier :).
159
- // For each block element that was in the selection, we will either: turn it to list item,
160
- // turn it to paragraph, or change it's type. Or leave it as it is.
161
- // Do it in reverse as there might be multiple blocks (same as with changing indents).
162
- for (const element of blocks.reverse()) {
163
- if (turnOff && element.name == 'listItem') {
164
- // We are turning off and the element is a `listItem` - it should be converted to `paragraph`.
165
- // List item specific attributes are removed by post fixer.
166
- writer.rename(element, 'paragraph');
167
- }
168
- else if (!turnOff && element.name != 'listItem') {
169
- // We are turning on and the element is not a `listItem` - it should be converted to `listItem`.
170
- // The order of operations is important to keep model in correct state.
171
- writer.setAttributes({ listType: this.type, listIndent: 0 }, element);
172
- writer.rename(element, 'listItem');
173
- }
174
- else if (!turnOff && element.name == 'listItem' && element.getAttribute('listType') != this.type) {
175
- // We are turning on and the element is a `listItem` but has different type - change it's type and
176
- // type of it's all siblings that have same indent.
177
- writer.setAttribute('listType', this.type, element);
69
+ // Turning on the list items for a non-collapsed selection.
70
+ else {
71
+ const changedBlocks = [];
72
+ for (const block of blocks) {
73
+ // Promote the given block to the list item.
74
+ if (!block.hasAttribute('listType')) {
75
+ // Rename block to a simple list item if this option is enabled.
76
+ if (!block.is('element', 'listItem') && canBecomeSimpleListItem(block, model.schema)) {
77
+ writer.rename(block, 'listItem');
78
+ }
79
+ writer.setAttributes({
80
+ listIndent: 0,
81
+ listItemId: ListItemUid.next(),
82
+ listType: this.type
83
+ }, block);
84
+ changedBlocks.push(block);
85
+ }
86
+ // Change the type of list item.
87
+ else {
88
+ for (const node of expandListBlocksToCompleteItems(block, { withNested: false })) {
89
+ if (node.getAttribute('listType') != this.type) {
90
+ writer.setAttribute('listType', this.type, node);
91
+ changedBlocks.push(node);
92
+ }
93
+ }
94
+ }
178
95
  }
96
+ this._fireAfterExecute(changedBlocks);
179
97
  }
180
- /**
181
- * Event fired by the {@link #execute} method.
182
- *
183
- * It allows to execute an action after executing the {@link ~ListCommand#execute} method, for example adjusting
184
- * attributes of changed blocks.
185
- *
186
- * @protected
187
- * @event _executeCleanup
188
- */
189
- this.fire('_executeCleanup', blocks);
190
98
  });
191
99
  }
100
+ /**
101
+ * Fires the `afterExecute` event.
102
+ *
103
+ * @param changedBlocks The changed list elements.
104
+ */
105
+ _fireAfterExecute(changedBlocks) {
106
+ this.fire('afterExecute', sortBlocks(new Set(changedBlocks)));
107
+ }
192
108
  /**
193
109
  * Checks the command's {@link #value}.
194
110
  *
195
111
  * @returns The current value.
196
112
  */
197
113
  _getValue() {
198
- // Check whether closest `listItem` ancestor of the position has a correct type.
199
- const listItem = first(this.editor.model.document.selection.getSelectedBlocks());
200
- return !!listItem && listItem.is('element', 'listItem') && listItem.getAttribute('listType') == this.type;
114
+ const selection = this.editor.model.document.selection;
115
+ const blocks = Array.from(selection.getSelectedBlocks());
116
+ if (!blocks.length) {
117
+ return false;
118
+ }
119
+ for (const block of blocks) {
120
+ if (block.getAttribute('listType') != this.type) {
121
+ return false;
122
+ }
123
+ }
124
+ return true;
201
125
  }
202
126
  /**
203
127
  * Checks whether the command can be enabled in the current context.
@@ -205,70 +129,22 @@ export default class ListCommand extends Command {
205
129
  * @returns Whether the command should be enabled.
206
130
  */
207
131
  _checkEnabled() {
132
+ const model = this.editor.model;
133
+ const schema = model.schema;
134
+ const selection = model.document.selection;
135
+ const blocks = Array.from(selection.getSelectedBlocks());
136
+ if (!blocks.length) {
137
+ return false;
138
+ }
208
139
  // If command value is true it means that we are in list item, so the command should be enabled.
209
140
  if (this.value) {
210
141
  return true;
211
142
  }
212
- const selection = this.editor.model.document.selection;
213
- const schema = this.editor.model.schema;
214
- const firstBlock = first(selection.getSelectedBlocks());
215
- if (!firstBlock) {
216
- return false;
217
- }
218
- // Otherwise, check if list item can be inserted at the position start.
219
- return checkCanBecomeListItem(firstBlock, schema);
220
- }
221
- }
222
- /**
223
- * Helper function used when one or more list item have their type changed. Fixes type of other list items
224
- * that are affected by the change (are in same lists) but are not directly in selection. The function got extracted
225
- * not to duplicated code, as same fix has to be performed before and after selection.
226
- *
227
- * @param blocks Blocks that are in selection.
228
- * @param isBackward Specified whether fix will be applied for blocks before first selected block (`true`)
229
- * or blocks after last selected block (`false`).
230
- * @param lowestIndent Lowest indent among selected blocks.
231
- */
232
- function _fixType(blocks, isBackward, lowestIndent) {
233
- // We need to check previous sibling of first changed item and next siblings of last changed item.
234
- const startingItem = isBackward ? blocks[0] : blocks[blocks.length - 1];
235
- if (startingItem.is('element', 'listItem')) {
236
- let item = startingItem[isBackward ? 'previousSibling' : 'nextSibling'];
237
- // During processing items, keeps the lowest indent of already processed items.
238
- // This saves us from changing too many items.
239
- // Following example is for going forward as it is easier to read, however same applies to going backward.
240
- // * ------
241
- // * ------
242
- // * --[---
243
- // * ------ <-- `lowestIndent` should be 1
244
- // * --]--- <-- `startingItem`, `currentIndent` = 2, `lowestIndent` == 1
245
- // * ------ <-- should be fixed, `indent` == 2 == `currentIndent`
246
- // * ------ <-- should be fixed, set `currentIndent` to 1, `indent` == 1 == `currentIndent`
247
- // * ------ <-- should not be fixed, item is in different list, `indent` = 2, `indent` != `currentIndent`
248
- // * ------ <-- should be fixed, `indent` == 1 == `currentIndent`
249
- // * ------ <-- break loop (`indent` < `lowestIndent`)
250
- let currentIndent = startingItem.getAttribute('listIndent');
251
- // Look back until a list item with indent lower than reference `lowestIndent`.
252
- // That would be the parent of nested sublist which contains item having `lowestIndent`.
253
- while (item && item.is('element', 'listItem') && item.getAttribute('listIndent') >= lowestIndent) {
254
- if (currentIndent > item.getAttribute('listIndent')) {
255
- currentIndent = item.getAttribute('listIndent');
256
- }
257
- // Found an item that is in the same nested sublist.
258
- if (item.getAttribute('listIndent') == currentIndent) {
259
- // Just add the item to selected blocks like it was selected by the user.
260
- blocks[isBackward ? 'unshift' : 'push'](item);
143
+ for (const block of blocks) {
144
+ if (schema.checkAttribute(block, 'listType') || canBecomeSimpleListItem(block, schema)) {
145
+ return true;
261
146
  }
262
- item = item[isBackward ? 'previousSibling' : 'nextSibling'];
263
147
  }
148
+ return false;
264
149
  }
265
150
  }
266
- /**
267
- * Checks whether the given block can be replaced by a listItem.
268
- *
269
- * @param block A block to be tested.
270
- * @param schema The schema of the document.
271
- */
272
- function checkCanBecomeListItem(block, schema) {
273
- return schema.checkChild(block.parent, 'listItem') && !schema.isObject(block);
274
- }
@@ -1,18 +1,35 @@
1
1
  /**
2
- * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
2
+ * @license Copyright (c) 2003-2024, 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
- import ListUtils from './listutils';
6
- import { Plugin } from 'ckeditor5/src/core';
7
- import { Enter } from 'ckeditor5/src/enter';
8
- import { Delete } from 'ckeditor5/src/typing';
5
+ /**
6
+ * @module list/list/listediting
7
+ */
8
+ import { Plugin, type Editor } from 'ckeditor5/src/core.js';
9
+ import type { DowncastWriter, Element, ViewElement, ViewAttributeElement, Writer } from 'ckeditor5/src/engine.js';
10
+ import { Delete } from 'ckeditor5/src/typing.js';
11
+ import { Enter } from 'ckeditor5/src/enter.js';
12
+ import ListUtils from './listutils.js';
13
+ import { ListBlocksIterable } from './utils/listwalker.js';
14
+ import { ClipboardPipeline } from 'ckeditor5/src/clipboard.js';
15
+ import '../../theme/documentlist.css';
9
16
  import '../../theme/list.css';
10
17
  /**
11
- * The engine of the list feature. It handles creating, editing and removing lists and list items.
12
- *
13
- * It registers the `'numberedList'`, `'bulletedList'`, `'indentList'` and `'outdentList'` commands.
18
+ * Map of model attributes applicable to list blocks.
19
+ */
20
+ export interface ListItemAttributesMap {
21
+ listType?: 'numbered' | 'bulleted' | 'todo';
22
+ listIndent?: number;
23
+ listItemId?: string;
24
+ }
25
+ /**
26
+ * The editing part of the document-list feature. It handles creating, editing and removing lists and list items.
14
27
  */
15
28
  export default class ListEditing extends Plugin {
29
+ /**
30
+ * The list of registered downcast strategies.
31
+ */
32
+ private readonly _downcastStrategies;
16
33
  /**
17
34
  * @inheritDoc
18
35
  */
@@ -20,7 +37,11 @@ export default class ListEditing extends Plugin {
20
37
  /**
21
38
  * @inheritDoc
22
39
  */
23
- static get requires(): readonly [typeof Enter, typeof Delete, typeof ListUtils];
40
+ static get requires(): readonly [typeof Enter, typeof Delete, typeof ListUtils, typeof ClipboardPipeline];
41
+ /**
42
+ * @inheritDoc
43
+ */
44
+ constructor(editor: Editor);
24
45
  /**
25
46
  * @inheritDoc
26
47
  */
@@ -29,4 +50,163 @@ export default class ListEditing extends Plugin {
29
50
  * @inheritDoc
30
51
  */
31
52
  afterInit(): void;
53
+ /**
54
+ * Registers a downcast strategy.
55
+ *
56
+ * **Note**: Strategies must be registered in the `Plugin#init()` phase so that it can be applied
57
+ * in the `ListEditing#afterInit()`.
58
+ *
59
+ * @param strategy The downcast strategy to register.
60
+ */
61
+ registerDowncastStrategy(strategy: DowncastStrategy): void;
62
+ /**
63
+ * Returns list of model attribute names that should affect downcast conversion.
64
+ */
65
+ getListAttributeNames(): Array<string>;
66
+ /**
67
+ * Attaches the listener to the {@link module:engine/view/document~Document#event:delete} event and handles backspace/delete
68
+ * keys in and around document lists.
69
+ */
70
+ private _setupDeleteIntegration;
71
+ /**
72
+ * Attaches a listener to the {@link module:engine/view/document~Document#event:enter} event and handles enter key press
73
+ * in document lists.
74
+ */
75
+ private _setupEnterIntegration;
76
+ /**
77
+ * Attaches a listener to the {@link module:engine/view/document~Document#event:tab} event and handles tab key and tab+shift keys
78
+ * presses in document lists.
79
+ */
80
+ private _setupTabIntegration;
81
+ /**
82
+ * Registers the conversion helpers for the document-list feature.
83
+ */
84
+ private _setupConversion;
85
+ /**
86
+ * Registers model post-fixers.
87
+ */
88
+ private _setupModelPostFixing;
89
+ /**
90
+ * Integrates the feature with the clipboard via {@link module:engine/model/model~Model#insertContent} and
91
+ * {@link module:engine/model/model~Model#getSelectedContent}.
92
+ */
93
+ private _setupClipboardIntegration;
94
+ }
95
+ /**
96
+ * The attribute to attribute downcast strategy for UL, OL, LI elements.
97
+ */
98
+ export interface AttributeDowncastStrategy {
99
+ /**
100
+ * The scope of the downcast (whether it applies to LI or OL/UL).
101
+ */
102
+ scope: 'list' | 'item';
103
+ /**
104
+ * The model attribute name.
105
+ */
106
+ attributeName: string;
107
+ /**
108
+ * Sets the property on the view element.
109
+ */
110
+ setAttributeOnDowncast(writer: DowncastWriter, value: unknown, element: ViewElement): void;
111
+ }
112
+ /**
113
+ * The custom marker downcast strategy.
114
+ */
115
+ export interface ItemMarkerDowncastStrategy {
116
+ /**
117
+ * The scope of the downcast.
118
+ */
119
+ scope: 'itemMarker';
120
+ /**
121
+ * The model attribute name.
122
+ */
123
+ attributeName: string;
124
+ /**
125
+ * Creates a view element for a custom item marker.
126
+ */
127
+ createElement(writer: DowncastWriter, modelElement: Element, { dataPipeline }: {
128
+ dataPipeline?: boolean;
129
+ }): ViewElement | null;
130
+ /**
131
+ * Creates an AttributeElement to be used for wrapping a first block of a list item.
132
+ */
133
+ createWrapperElement?(writer: DowncastWriter, modelElement: Element, { dataPipeline }: {
134
+ dataPipeline?: boolean;
135
+ }): ViewAttributeElement;
136
+ /**
137
+ * Should return true if the given list block can be wrapped with the wrapper created by `createWrapperElement()`
138
+ * or only the marker element should be wrapped.
139
+ */
140
+ canWrapElement?(modelElement: Element): boolean;
32
141
  }
142
+ /**
143
+ * The downcast strategy.
144
+ */
145
+ export type DowncastStrategy = AttributeDowncastStrategy | ItemMarkerDowncastStrategy;
146
+ /**
147
+ * Event fired on changes detected on the model list element to verify if the view representation of a list element
148
+ * is representing those attributes.
149
+ *
150
+ * It allows triggering a re-wrapping of a list item.
151
+ *
152
+ * @internal
153
+ * @eventName ~ListEditing#postFixer
154
+ * @param listHead The head element of a list.
155
+ * @param writer The writer to do changes with.
156
+ * @param seenIds The set of already known IDs.
157
+ * @returns If a post-fixer made a change of the model tree, it should return `true`.
158
+ */
159
+ export type ListEditingPostFixerEvent = {
160
+ name: 'postFixer';
161
+ args: [
162
+ {
163
+ listNodes: ListBlocksIterable;
164
+ listHead: Element;
165
+ writer: Writer;
166
+ seenIds: Set<string>;
167
+ }
168
+ ];
169
+ return: boolean;
170
+ };
171
+ /**
172
+ * Event fired on changes detected on the model list element to verify if the view representation of a list element
173
+ * is representing those attributes.
174
+ *
175
+ * It allows triggering a re-wrapping of a list item.
176
+ *
177
+ * **Note**: For convenience this event is namespaced and could be captured as `checkAttributes:list` or `checkAttributes:item`.
178
+ *
179
+ * @internal
180
+ * @eventName ~ListEditing#checkAttributes
181
+ */
182
+ export type ListEditingCheckAttributesEvent = {
183
+ name: 'checkAttributes' | 'checkAttributes:list' | 'checkAttributes:item';
184
+ args: [
185
+ {
186
+ viewElement: ViewElement & {
187
+ id?: string;
188
+ };
189
+ modelAttributes: ListItemAttributesMap;
190
+ }
191
+ ];
192
+ return: boolean;
193
+ };
194
+ /**
195
+ * Event fired on changes detected on the model list element to verify if the view representation of a list block element
196
+ * is representing those attributes.
197
+ *
198
+ * It allows triggering a reconversion of a list item block.
199
+ *
200
+ * @internal
201
+ * @eventName ~ListEditing#checkElement
202
+ */
203
+ export type ListEditingCheckElementEvent = {
204
+ name: 'checkElement';
205
+ args: [
206
+ {
207
+ viewElement: ViewElement;
208
+ modelElement: Element;
209
+ }
210
+ ];
211
+ return: boolean;
212
+ };