@ckeditor/ckeditor5-list 40.2.0 → 41.1.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 (196) hide show
  1. package/CHANGELOG.md +25 -25
  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 +20 -20
  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 -2
  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 -5
  157. package/src/listproperties/ui/listpropertiesview.js +3 -3
  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/liststyle.d.ts +0 -28
  192. package/src/liststyle.js +0 -36
  193. package/src/tododocumentlist/tododocumentlistediting.js +0 -399
  194. package/theme/icons/bulletedlist.svg +0 -1
  195. package/theme/icons/numberedlist.svg +0 -1
  196. package/theme/icons/todolist.svg +0 -1
@@ -0,0 +1,696 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ /**
6
+ * @module list/legacylistproperties/legacylistpropertiesediting
7
+ */
8
+ import { Plugin } from 'ckeditor5/src/core.js';
9
+ import LegacyListEditing from '../legacylist/legacylistediting.js';
10
+ import LegacyListStyleCommand from './legacyliststylecommand.js';
11
+ import LegacyListReversedCommand from './legacylistreversedcommand.js';
12
+ import LegacyListStartCommand from './legacyliststartcommand.js';
13
+ import { getSiblingListItem, getSiblingNodes } from '../legacylist/legacyutils.js';
14
+ const DEFAULT_LIST_TYPE = 'default';
15
+ /**
16
+ * The engine of the list properties feature.
17
+ *
18
+ * It sets the value for the `listItem` attribute of the {@link module:list/legacylist~LegacyList `<listItem>`} element that
19
+ * allows modifying the list style type.
20
+ *
21
+ * It registers the `'listStyle'`, `'listReversed'` and `'listStart'` commands if they are enabled in the configuration.
22
+ * Read more in {@link module:list/listconfig~ListPropertiesConfig}.
23
+ */
24
+ export default class LegacyListPropertiesEditing extends Plugin {
25
+ /**
26
+ * @inheritDoc
27
+ */
28
+ static get requires() {
29
+ return [LegacyListEditing];
30
+ }
31
+ /**
32
+ * @inheritDoc
33
+ */
34
+ static get pluginName() {
35
+ return 'LegacyListPropertiesEditing';
36
+ }
37
+ /**
38
+ * @inheritDoc
39
+ */
40
+ constructor(editor) {
41
+ super(editor);
42
+ editor.config.define('list', {
43
+ properties: {
44
+ styles: true,
45
+ startIndex: false,
46
+ reversed: false
47
+ }
48
+ });
49
+ }
50
+ /**
51
+ * @inheritDoc
52
+ */
53
+ init() {
54
+ const editor = this.editor;
55
+ const model = editor.model;
56
+ const enabledProperties = editor.config.get('list.properties');
57
+ const strategies = createAttributeStrategies(enabledProperties);
58
+ // Extend schema.
59
+ model.schema.extend('listItem', {
60
+ allowAttributes: strategies.map(s => s.attributeName)
61
+ });
62
+ for (const strategy of strategies) {
63
+ strategy.addCommand(editor);
64
+ }
65
+ // Fix list attributes when modifying their nesting levels (the `listIndent` attribute).
66
+ this.listenTo(editor.commands.get('indentList'), '_executeCleanup', fixListAfterIndentListCommand(editor, strategies));
67
+ this.listenTo(editor.commands.get('outdentList'), '_executeCleanup', fixListAfterOutdentListCommand(editor, strategies));
68
+ this.listenTo(editor.commands.get('bulletedList'), '_executeCleanup', restoreDefaultListStyle(editor));
69
+ this.listenTo(editor.commands.get('numberedList'), '_executeCleanup', restoreDefaultListStyle(editor));
70
+ // Register a post-fixer that ensures that the attributes is specified in each `listItem` element.
71
+ model.document.registerPostFixer(fixListAttributesOnListItemElements(editor, strategies));
72
+ // Set up conversion.
73
+ editor.conversion.for('upcast').add(upcastListItemAttributes(strategies));
74
+ editor.conversion.for('downcast').add(downcastListItemAttributes(strategies));
75
+ // Handle merging two separated lists into the single one.
76
+ this._mergeListAttributesWhileMergingLists(strategies);
77
+ }
78
+ /**
79
+ * @inheritDoc
80
+ */
81
+ afterInit() {
82
+ const editor = this.editor;
83
+ // Enable post-fixer that removes the attributes from to-do list items only if the "TodoList" plugin is on.
84
+ // We need to registry the hook here since the `TodoList` plugin can be added after the `ListPropertiesEditing`.
85
+ if (editor.commands.get('todoList')) {
86
+ editor.model.document.registerPostFixer(removeListItemAttributesFromTodoList(editor));
87
+ }
88
+ }
89
+ /**
90
+ * Starts listening to {@link module:engine/model/model~Model#deleteContent} and checks whether two lists will be merged into a single
91
+ * one after deleting the content.
92
+ *
93
+ * The purpose of this action is to adjust the `listStyle`, `listReversed` and `listStart` values
94
+ * for the list that was merged.
95
+ *
96
+ * Consider the following model's content:
97
+ *
98
+ * ```xml
99
+ * <listItem listIndent="0" listType="bulleted" listStyle="square">UL List item 1</listItem>
100
+ * <listItem listIndent="0" listType="bulleted" listStyle="square">UL List item 2</listItem>
101
+ * <paragraph>[A paragraph.]</paragraph>
102
+ * <listItem listIndent="0" listType="bulleted" listStyle="circle">UL List item 1</listItem>
103
+ * <listItem listIndent="0" listType="bulleted" listStyle="circle">UL List item 2</listItem>
104
+ * ```
105
+ *
106
+ * After removing the paragraph element, the second list will be merged into the first one.
107
+ * We want to inherit the `listStyle` attribute for the second list from the first one.
108
+ *
109
+ * ```xml
110
+ * <listItem listIndent="0" listType="bulleted" listStyle="square">UL List item 1</listItem>
111
+ * <listItem listIndent="0" listType="bulleted" listStyle="square">UL List item 2</listItem>
112
+ * <listItem listIndent="0" listType="bulleted" listStyle="square">UL List item 1</listItem>
113
+ * <listItem listIndent="0" listType="bulleted" listStyle="square">UL List item 2</listItem>
114
+ * ```
115
+ *
116
+ * See https://github.com/ckeditor/ckeditor5/issues/7879.
117
+ *
118
+ * @param attributeStrategies Strategies for the enabled attributes.
119
+ */
120
+ _mergeListAttributesWhileMergingLists(attributeStrategies) {
121
+ const editor = this.editor;
122
+ const model = editor.model;
123
+ // First the outer-most`listItem` in the first list reference.
124
+ // If found, the lists should be merged and this `listItem` provides the attributes
125
+ // and it is also a starting point when searching for items in the second list.
126
+ let firstMostOuterItem;
127
+ // Check whether the removed content is between two lists.
128
+ this.listenTo(model, 'deleteContent', (evt, [selection]) => {
129
+ const firstPosition = selection.getFirstPosition();
130
+ const lastPosition = selection.getLastPosition();
131
+ // Typing or removing content in a single item. Aborting.
132
+ if (firstPosition.parent === lastPosition.parent) {
133
+ return;
134
+ }
135
+ // An element before the content that will be removed is not a list.
136
+ if (!firstPosition.parent.is('element', 'listItem')) {
137
+ return;
138
+ }
139
+ const nextSibling = lastPosition.parent.nextSibling;
140
+ // An element after the content that will be removed is not a list.
141
+ if (!nextSibling || !nextSibling.is('element', 'listItem')) {
142
+ return;
143
+ }
144
+ // Find the outermost list item based on the `listIndent` attribute. We can't assume that `listIndent=0`
145
+ // because the selection can be hooked in nested lists.
146
+ //
147
+ // <listItem listIndent="0" listType="bulleted" listStyle="square">UL List item 1</listItem>
148
+ // <listItem listIndent="1" listType="bulleted" listStyle="square">UL List [item 1.1</listItem>
149
+ // <listItem listIndent="0" listType="bulleted" listStyle="circle">[]UL List item 1.</listItem>
150
+ // <listItem listIndent="1" listType="bulleted" listStyle="circle">UL List ]item 1.1</listItem>
151
+ //
152
+ // After deleting the content, we would like to inherit the "square" attribute for the last element:
153
+ //
154
+ // <listItem listIndent="0" listType="bulleted" listStyle="square">UL List item 1</listItem>
155
+ // <listItem listIndent="1" listType="bulleted" listStyle="square">UL List []item 1.1</listItem>
156
+ const mostOuterItemList = getSiblingListItem(firstPosition.parent, {
157
+ sameIndent: true,
158
+ listIndent: nextSibling.getAttribute('listIndent')
159
+ });
160
+ // The outermost list item may not exist while removing elements between lists with different value
161
+ // of the `listIndent` attribute. In such a case we don't want to update anything. See: #8073.
162
+ if (!mostOuterItemList) {
163
+ return;
164
+ }
165
+ if (mostOuterItemList.getAttribute('listType') === nextSibling.getAttribute('listType')) {
166
+ firstMostOuterItem = mostOuterItemList;
167
+ }
168
+ }, { priority: 'high' });
169
+ // If so, update the `listStyle` attribute for the second list.
170
+ this.listenTo(model, 'deleteContent', () => {
171
+ if (!firstMostOuterItem) {
172
+ return;
173
+ }
174
+ model.change(writer => {
175
+ // Find the first most-outer item list in the merged list.
176
+ // A case when the first list item in the second list was merged into the last item in the first list.
177
+ //
178
+ // <listItem listIndent="0" listType="bulleted" listStyle="square">UL List item 1</listItem>
179
+ // <listItem listIndent="0" listType="bulleted" listStyle="square">UL List item 2</listItem>
180
+ // <listItem listIndent="0" listType="bulleted" listStyle="circle">[]UL List item 1</listItem>
181
+ // <listItem listIndent="0" listType="bulleted" listStyle="circle">UL List item 2</listItem>
182
+ const secondListMostOuterItem = getSiblingListItem(firstMostOuterItem.nextSibling, {
183
+ sameIndent: true,
184
+ listIndent: firstMostOuterItem.getAttribute('listIndent'),
185
+ direction: 'forward'
186
+ });
187
+ // If the selection ends in a non-list element, there are no <listItem>s that would require adjustments.
188
+ // See: #8642.
189
+ if (!secondListMostOuterItem) {
190
+ firstMostOuterItem = null;
191
+ return;
192
+ }
193
+ const items = [
194
+ secondListMostOuterItem,
195
+ ...getSiblingNodes(writer.createPositionAt(secondListMostOuterItem, 0), 'forward')
196
+ ];
197
+ for (const listItem of items) {
198
+ for (const strategy of attributeStrategies) {
199
+ if (strategy.appliesToListItem(listItem)) {
200
+ const attributeName = strategy.attributeName;
201
+ const value = firstMostOuterItem.getAttribute(attributeName);
202
+ writer.setAttribute(attributeName, value, listItem);
203
+ }
204
+ }
205
+ }
206
+ });
207
+ firstMostOuterItem = null;
208
+ }, { priority: 'low' });
209
+ }
210
+ }
211
+ /**
212
+ * Creates an array of strategies for dealing with enabled listItem attributes.
213
+ */
214
+ function createAttributeStrategies(enabledProperties) {
215
+ const strategies = [];
216
+ if (enabledProperties.styles) {
217
+ strategies.push({
218
+ attributeName: 'listStyle',
219
+ defaultValue: DEFAULT_LIST_TYPE,
220
+ addCommand(editor) {
221
+ editor.commands.add('listStyle', new LegacyListStyleCommand(editor, DEFAULT_LIST_TYPE));
222
+ },
223
+ appliesToListItem() {
224
+ return true;
225
+ },
226
+ setAttributeOnDowncast(writer, listStyle, element) {
227
+ if (listStyle && listStyle !== DEFAULT_LIST_TYPE) {
228
+ writer.setStyle('list-style-type', listStyle, element);
229
+ }
230
+ else {
231
+ writer.removeStyle('list-style-type', element);
232
+ }
233
+ },
234
+ getAttributeOnUpcast(listParent) {
235
+ return listParent.getStyle('list-style-type') || DEFAULT_LIST_TYPE;
236
+ }
237
+ });
238
+ }
239
+ if (enabledProperties.reversed) {
240
+ strategies.push({
241
+ attributeName: 'listReversed',
242
+ defaultValue: false,
243
+ addCommand(editor) {
244
+ editor.commands.add('listReversed', new LegacyListReversedCommand(editor));
245
+ },
246
+ appliesToListItem(item) {
247
+ return item.getAttribute('listType') == 'numbered';
248
+ },
249
+ setAttributeOnDowncast(writer, listReversed, element) {
250
+ if (listReversed) {
251
+ writer.setAttribute('reversed', 'reversed', element);
252
+ }
253
+ else {
254
+ writer.removeAttribute('reversed', element);
255
+ }
256
+ },
257
+ getAttributeOnUpcast(listParent) {
258
+ return listParent.hasAttribute('reversed');
259
+ }
260
+ });
261
+ }
262
+ if (enabledProperties.startIndex) {
263
+ strategies.push({
264
+ attributeName: 'listStart',
265
+ defaultValue: 1,
266
+ addCommand(editor) {
267
+ editor.commands.add('listStart', new LegacyListStartCommand(editor));
268
+ },
269
+ appliesToListItem(item) {
270
+ return item.getAttribute('listType') == 'numbered';
271
+ },
272
+ setAttributeOnDowncast(writer, listStart, element) {
273
+ if (listStart == 0 || listStart > 1) {
274
+ writer.setAttribute('start', listStart, element);
275
+ }
276
+ else {
277
+ writer.removeAttribute('start', element);
278
+ }
279
+ },
280
+ getAttributeOnUpcast(listParent) {
281
+ const startAttributeValue = listParent.getAttribute('start');
282
+ return startAttributeValue >= 0 ? startAttributeValue : 1;
283
+ }
284
+ });
285
+ }
286
+ return strategies;
287
+ }
288
+ /**
289
+ * Returns a converter consumes the `style`, `reversed` and `start` attribute.
290
+ * In `style` it searches for the `list-style-type` definition.
291
+ * If not found, the `"default"` value will be used.
292
+ */
293
+ function upcastListItemAttributes(attributeStrategies) {
294
+ return (dispatcher) => {
295
+ dispatcher.on('element:li', (evt, data, conversionApi) => {
296
+ // https://github.com/ckeditor/ckeditor5/issues/13858
297
+ if (!data.modelRange) {
298
+ return;
299
+ }
300
+ const listParent = data.viewItem.parent;
301
+ const listItem = data.modelRange.start.nodeAfter || data.modelRange.end.nodeBefore;
302
+ for (const strategy of attributeStrategies) {
303
+ if (strategy.appliesToListItem(listItem)) {
304
+ const listStyle = strategy.getAttributeOnUpcast(listParent);
305
+ conversionApi.writer.setAttribute(strategy.attributeName, listStyle, listItem);
306
+ }
307
+ }
308
+ }, { priority: 'low' });
309
+ };
310
+ }
311
+ /**
312
+ * Returns a converter that adds `reversed`, `start` attributes and adds `list-style-type` definition as a value for the `style` attribute.
313
+ * The `"default"` values are removed and not present in the view/data.
314
+ */
315
+ function downcastListItemAttributes(attributeStrategies) {
316
+ return (dispatcher) => {
317
+ for (const strategy of attributeStrategies) {
318
+ dispatcher.on(`attribute:${strategy.attributeName}:listItem`, (evt, data, conversionApi) => {
319
+ const viewWriter = conversionApi.writer;
320
+ const currentElement = data.item;
321
+ const previousElement = getSiblingListItem(currentElement.previousSibling, {
322
+ sameIndent: true,
323
+ listIndent: currentElement.getAttribute('listIndent'),
324
+ direction: 'backward'
325
+ });
326
+ const viewItem = conversionApi.mapper.toViewElement(currentElement);
327
+ // A case when elements represent different lists. We need to separate their container.
328
+ if (!areRepresentingSameList(currentElement, previousElement)) {
329
+ viewWriter.breakContainer(viewWriter.createPositionBefore(viewItem));
330
+ }
331
+ strategy.setAttributeOnDowncast(viewWriter, data.attributeNewValue, viewItem.parent);
332
+ }, { priority: 'low' });
333
+ }
334
+ };
335
+ /**
336
+ * Checks whether specified list items belong to the same list.
337
+ */
338
+ function areRepresentingSameList(listItem1, listItem2) {
339
+ return listItem2 &&
340
+ listItem1.getAttribute('listType') === listItem2.getAttribute('listType') &&
341
+ listItem1.getAttribute('listIndent') === listItem2.getAttribute('listIndent') &&
342
+ listItem1.getAttribute('listStyle') === listItem2.getAttribute('listStyle') &&
343
+ listItem1.getAttribute('listReversed') === listItem2.getAttribute('listReversed') &&
344
+ listItem1.getAttribute('listStart') === listItem2.getAttribute('listStart');
345
+ }
346
+ }
347
+ /**
348
+ * When indenting list, nested list should clear its value for the attributes or inherit from nested lists.
349
+ *
350
+ * ■ List item 1.
351
+ * ■ List item 2.[]
352
+ * ■ List item 3.
353
+ * editor.execute( 'indentList' );
354
+ *
355
+ * ■ List item 1.
356
+ * ○ List item 2.[]
357
+ * ■ List item 3.
358
+ */
359
+ function fixListAfterIndentListCommand(editor, attributeStrategies) {
360
+ return (evt, changedItems) => {
361
+ const root = changedItems[0];
362
+ const rootIndent = root.getAttribute('listIndent');
363
+ const itemsToUpdate = changedItems.filter(item => item.getAttribute('listIndent') === rootIndent);
364
+ // A case where a few list items are indented must be checked separately
365
+ // since `getSiblingListItem()` returns the first changed element.
366
+ // ■ List item 1.
367
+ // ○ [List item 2.
368
+ // ○ List item 3.]
369
+ // ■ List item 4.
370
+ //
371
+ // List items: `2` and `3` should be adjusted.
372
+ let previousSibling = null;
373
+ if (root.previousSibling.getAttribute('listIndent') + 1 !== rootIndent) {
374
+ previousSibling = getSiblingListItem(root.previousSibling, {
375
+ sameIndent: true, direction: 'backward', listIndent: rootIndent
376
+ });
377
+ }
378
+ editor.model.change(writer => {
379
+ for (const item of itemsToUpdate) {
380
+ for (const strategy of attributeStrategies) {
381
+ if (strategy.appliesToListItem(item)) {
382
+ const valueToSet = previousSibling == null ?
383
+ strategy.defaultValue :
384
+ previousSibling.getAttribute(strategy.attributeName);
385
+ writer.setAttribute(strategy.attributeName, valueToSet, item);
386
+ }
387
+ }
388
+ }
389
+ });
390
+ };
391
+ }
392
+ /**
393
+ * When outdenting a list, a nested list should copy attribute values
394
+ * from the previous sibling list item including the same value for the `listIndent` value.
395
+ *
396
+ * ■ List item 1.
397
+ * ○ List item 2.[]
398
+ * ■ List item 3.
399
+ *
400
+ * editor.execute( 'outdentList' );
401
+ *
402
+ * ■ List item 1.
403
+ * ■ List item 2.[]
404
+ * ■ List item 3.
405
+ */
406
+ function fixListAfterOutdentListCommand(editor, attributeStrategies) {
407
+ return (evt, changedItems) => {
408
+ changedItems = changedItems.reverse().filter(item => item.is('element', 'listItem'));
409
+ if (!changedItems.length) {
410
+ return;
411
+ }
412
+ const indent = changedItems[0].getAttribute('listIndent');
413
+ const listType = changedItems[0].getAttribute('listType');
414
+ let listItem = changedItems[0].previousSibling;
415
+ // ■ List item 1.
416
+ // ○ List item 2.
417
+ // ○ List item 3.[]
418
+ // ■ List item 4.
419
+ //
420
+ // After outdenting a list, `List item 3` should inherit the `listStyle` attribute from `List item 1`.
421
+ //
422
+ // ■ List item 1.
423
+ // ○ List item 2.
424
+ // ■ List item 3.[]
425
+ // ■ List item 4.
426
+ if (listItem.is('element', 'listItem')) {
427
+ while (listItem.getAttribute('listIndent') !== indent) {
428
+ listItem = listItem.previousSibling;
429
+ }
430
+ }
431
+ else {
432
+ listItem = null;
433
+ }
434
+ // Outdenting such a list should restore values based on `List item 4`.
435
+ // ■ List item 1.[]
436
+ // ○ List item 2.
437
+ // ○ List item 3.
438
+ // ■ List item 4.
439
+ if (!listItem) {
440
+ listItem = changedItems[changedItems.length - 1].nextSibling;
441
+ }
442
+ // And such a list should not modify anything.
443
+ // However, `listItem` can indicate a node below the list. Be sure that we have the `listItem` element.
444
+ // ■ List item 1.[]
445
+ // ○ List item 2.
446
+ // ○ List item 3.
447
+ // <paragraph>The later if check.</paragraph>
448
+ if (!listItem || !listItem.is('element', 'listItem')) {
449
+ return;
450
+ }
451
+ // Do not modify the list if found `listItem` represents other type of list than outdented list items.
452
+ if (listItem.getAttribute('listType') !== listType) {
453
+ return;
454
+ }
455
+ editor.model.change(writer => {
456
+ const itemsToUpdate = changedItems.filter(item => item.getAttribute('listIndent') === indent);
457
+ for (const item of itemsToUpdate) {
458
+ for (const strategy of attributeStrategies) {
459
+ if (strategy.appliesToListItem(item)) {
460
+ const attributeName = strategy.attributeName;
461
+ const valueToSet = listItem.getAttribute(attributeName);
462
+ writer.setAttribute(attributeName, valueToSet, item);
463
+ }
464
+ }
465
+ }
466
+ });
467
+ };
468
+ }
469
+ /**
470
+ * Each `listItem` element must have specified the `listStyle`, `listReversed` and `listStart` attributes
471
+ * if they are enabled and supported by its `listType`.
472
+ * This post-fixer checks whether inserted elements `listItem` elements should inherit the attribute values from
473
+ * their sibling nodes or should use the default values.
474
+ *
475
+ * Paragraph[]
476
+ * ■ List item 1. // [listStyle="square", listType="bulleted"]
477
+ * ■ List item 2. // ...
478
+ * ■ List item 3. // ...
479
+ *
480
+ * editor.execute( 'bulletedList' )
481
+ *
482
+ * ■ Paragraph[] // [listStyle="square", listType="bulleted"]
483
+ * ■ List item 1. // [listStyle="square", listType="bulleted"]
484
+ * ■ List item 2.
485
+ * ■ List item 3.
486
+ *
487
+ * It also covers a such change:
488
+ *
489
+ * [Paragraph 1
490
+ * Paragraph 2]
491
+ * ■ List item 1. // [listStyle="square", listType="bulleted"]
492
+ * ■ List item 2. // ...
493
+ * ■ List item 3. // ...
494
+ *
495
+ * editor.execute( 'numberedList' )
496
+ *
497
+ * 1. [Paragraph 1 // [listStyle="default", listType="numbered"]
498
+ * 2. Paragraph 2] // [listStyle="default", listType="numbered"]
499
+ * ■ List item 1. // [listStyle="square", listType="bulleted"]
500
+ * ■ List item 2. // ...
501
+ * ■ List item 3. // ...
502
+ */
503
+ function fixListAttributesOnListItemElements(editor, attributeStrategies) {
504
+ return (writer) => {
505
+ let wasFixed = false;
506
+ const insertedListItems = getChangedListItems(editor.model.document.differ.getChanges())
507
+ .filter(item => {
508
+ // Don't touch todo lists. They are handled in another post-fixer.
509
+ return item.getAttribute('listType') !== 'todo';
510
+ });
511
+ if (!insertedListItems.length) {
512
+ return wasFixed;
513
+ }
514
+ // Check whether the last inserted element is next to the `listItem` element.
515
+ //
516
+ // ■ Paragraph[] // <-- The inserted item.
517
+ // ■ List item 1.
518
+ let existingListItem = insertedListItems[insertedListItems.length - 1].nextSibling;
519
+ // If it doesn't, maybe the `listItem` was inserted at the end of the list.
520
+ //
521
+ // ■ List item 1.
522
+ // ■ Paragraph[] // <-- The inserted item.
523
+ if (!existingListItem || !existingListItem.is('element', 'listItem')) {
524
+ existingListItem = insertedListItems[0].previousSibling;
525
+ if (existingListItem) {
526
+ const indent = insertedListItems[0].getAttribute('listIndent');
527
+ // But we need to find a `listItem` with the `listIndent=0` attribute.
528
+ // If doesn't, maybe the `listItem` was inserted at the end of the list.
529
+ //
530
+ // ■ List item 1.
531
+ // ○ List item 2.
532
+ // ■ Paragraph[] // <-- The inserted item.
533
+ while (existingListItem.is('element', 'listItem') && existingListItem.getAttribute('listIndent') !== indent) {
534
+ existingListItem = existingListItem.previousSibling;
535
+ // If the item does not exist, most probably there is no other content in the editor. See: #8072.
536
+ if (!existingListItem) {
537
+ break;
538
+ }
539
+ }
540
+ }
541
+ }
542
+ for (const strategy of attributeStrategies) {
543
+ const attributeName = strategy.attributeName;
544
+ for (const item of insertedListItems) {
545
+ if (!strategy.appliesToListItem(item)) {
546
+ writer.removeAttribute(attributeName, item);
547
+ continue;
548
+ }
549
+ if (!item.hasAttribute(attributeName)) {
550
+ if (shouldInheritListType(existingListItem, item, strategy)) {
551
+ writer.setAttribute(attributeName, existingListItem.getAttribute(attributeName), item);
552
+ }
553
+ else {
554
+ writer.setAttribute(attributeName, strategy.defaultValue, item);
555
+ }
556
+ wasFixed = true;
557
+ }
558
+ else {
559
+ // Adjust the `listStyle`, `listReversed` and `listStart`
560
+ // attributes for inserted (pasted) items. See #8160.
561
+ //
562
+ // ■ List item 1. // [listStyle="square", listType="bulleted"]
563
+ // ○ List item 1.1. // [listStyle="circle", listType="bulleted"]
564
+ // ○ [] (selection is here)
565
+ //
566
+ // Then, pasting a list with different attributes (listStyle, listType):
567
+ //
568
+ // 1. First. // [listStyle="decimal", listType="numbered"]
569
+ // 2. Second // [listStyle="decimal", listType="numbered"]
570
+ //
571
+ // The `listType` attribute will be corrected by the `ListEditing` converters.
572
+ // We need to adjust the `listStyle` attribute. Expected structure:
573
+ //
574
+ // ■ List item 1. // [listStyle="square", listType="bulleted"]
575
+ // ○ List item 1.1. // [listStyle="circle", listType="bulleted"]
576
+ // ○ First. // [listStyle="circle", listType="bulleted"]
577
+ // ○ Second // [listStyle="circle", listType="bulleted"]
578
+ const previousSibling = item.previousSibling;
579
+ if (shouldInheritListTypeFromPreviousItem(previousSibling, item, strategy.attributeName)) {
580
+ writer.setAttribute(attributeName, previousSibling.getAttribute(attributeName), item);
581
+ wasFixed = true;
582
+ }
583
+ }
584
+ }
585
+ }
586
+ return wasFixed;
587
+ };
588
+ }
589
+ /**
590
+ * Checks whether the `listStyle`, `listReversed` and `listStart` attributes
591
+ * should be copied from the `baseItem` element.
592
+ *
593
+ * The attribute should be copied if the inserted element does not have defined it and
594
+ * the value for the element is other than default in the base element.
595
+ */
596
+ function shouldInheritListType(baseItem, itemToChange, attributeStrategy) {
597
+ if (!baseItem) {
598
+ return false;
599
+ }
600
+ const baseListAttribute = baseItem.getAttribute(attributeStrategy.attributeName);
601
+ if (!baseListAttribute) {
602
+ return false;
603
+ }
604
+ if (baseListAttribute == attributeStrategy.defaultValue) {
605
+ return false;
606
+ }
607
+ if (baseItem.getAttribute('listType') !== itemToChange.getAttribute('listType')) {
608
+ return false;
609
+ }
610
+ return true;
611
+ }
612
+ /**
613
+ * Checks whether the `listStyle`, `listReversed` and `listStart` attributes
614
+ * should be copied from previous list item.
615
+ *
616
+ * The attribute should be copied if there's a mismatch of styles of the pasted list into a nested list.
617
+ * Top-level lists are not normalized as we allow side-by-side list of different types.
618
+ */
619
+ function shouldInheritListTypeFromPreviousItem(previousItem, itemToChange, attributeName) {
620
+ if (!previousItem || !previousItem.is('element', 'listItem')) {
621
+ return false;
622
+ }
623
+ if (itemToChange.getAttribute('listType') !== previousItem.getAttribute('listType')) {
624
+ return false;
625
+ }
626
+ const previousItemIndent = previousItem.getAttribute('listIndent');
627
+ if (previousItemIndent < 1 || previousItemIndent !== itemToChange.getAttribute('listIndent')) {
628
+ return false;
629
+ }
630
+ const previousItemListAttribute = previousItem.getAttribute(attributeName);
631
+ if (!previousItemListAttribute || previousItemListAttribute === itemToChange.getAttribute(attributeName)) {
632
+ return false;
633
+ }
634
+ return true;
635
+ }
636
+ /**
637
+ * Removes the `listStyle`, `listReversed` and `listStart` attributes from "todo" list items.
638
+ */
639
+ function removeListItemAttributesFromTodoList(editor) {
640
+ return (writer) => {
641
+ const todoListItems = getChangedListItems(editor.model.document.differ.getChanges())
642
+ .filter(item => {
643
+ // Handle the todo lists only. The rest is handled in another post-fixer.
644
+ return item.getAttribute('listType') === 'todo' && (item.hasAttribute('listStyle') ||
645
+ item.hasAttribute('listReversed') ||
646
+ item.hasAttribute('listStart'));
647
+ });
648
+ if (!todoListItems.length) {
649
+ return false;
650
+ }
651
+ for (const item of todoListItems) {
652
+ writer.removeAttribute('listStyle', item);
653
+ writer.removeAttribute('listReversed', item);
654
+ writer.removeAttribute('listStart', item);
655
+ }
656
+ return true;
657
+ };
658
+ }
659
+ /**
660
+ * Restores the `listStyle` attribute after changing the list type.
661
+ */
662
+ function restoreDefaultListStyle(editor) {
663
+ return (evt, changedItems) => {
664
+ changedItems = changedItems.filter(item => item.is('element', 'listItem'));
665
+ editor.model.change(writer => {
666
+ for (const item of changedItems) {
667
+ // Remove the attribute. Post-fixer will restore the proper value.
668
+ writer.removeAttribute('listStyle', item);
669
+ }
670
+ });
671
+ };
672
+ }
673
+ /**
674
+ * Returns the `listItem` that was inserted or changed.
675
+ *
676
+ * @param changes The changes list returned by the differ.
677
+ */
678
+ function getChangedListItems(changes) {
679
+ const items = [];
680
+ for (const change of changes) {
681
+ const item = getItemFromChange(change);
682
+ if (item && item.is('element', 'listItem')) {
683
+ items.push(item);
684
+ }
685
+ }
686
+ return items;
687
+ }
688
+ function getItemFromChange(change) {
689
+ if (change.type === 'attribute') {
690
+ return change.range.start.nodeAfter;
691
+ }
692
+ if (change.type === 'insert') {
693
+ return change.position.nodeAfter;
694
+ }
695
+ return null;
696
+ }