@ckeditor/ckeditor5-list 40.2.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 (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 +8 -8
  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
@@ -1,22 +1,21 @@
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
5
  /**
6
6
  * @module list/listproperties/listpropertiesediting
7
7
  */
8
- import { Plugin } from 'ckeditor5/src/core';
9
- import ListEditing from '../list/listediting';
10
- import ListStyleCommand from './liststylecommand';
11
- import ListReversedCommand from './listreversedcommand';
12
- import ListStartCommand from './liststartcommand';
13
- import { getSiblingListItem, getSiblingNodes } from '../list/utils';
8
+ import { Plugin } from 'ckeditor5/src/core.js';
9
+ import ListEditing from '../list/listediting.js';
10
+ import ListStartCommand from './liststartcommand.js';
11
+ import ListStyleCommand from './liststylecommand.js';
12
+ import ListReversedCommand from './listreversedcommand.js';
13
+ import { listPropertiesUpcastConverter } from './converters.js';
14
+ import { getAllSupportedStyleTypes, getListTypeFromListStyleType, getListStyleTypeFromTypeAttribute, getTypeAttributeFromListStyleType } from './utils/style.js';
15
+ import ListPropertiesUtils from './listpropertiesutils.js';
14
16
  const DEFAULT_LIST_TYPE = 'default';
15
17
  /**
16
- * The engine of the list properties feature.
17
- *
18
- * It sets the value for the `listItem` attribute of the {@link module:list/list~List `<listItem>`} element that
19
- * allows modifying the list style type.
18
+ * The document list properties engine feature.
20
19
  *
21
20
  * It registers the `'listStyle'`, `'listReversed'` and `'listStart'` commands if they are enabled in the configuration.
22
21
  * Read more in {@link module:list/listconfig~ListPropertiesConfig}.
@@ -26,7 +25,7 @@ export default class ListPropertiesEditing extends Plugin {
26
25
  * @inheritDoc
27
26
  */
28
27
  static get requires() {
29
- return [ListEditing];
28
+ return [ListEditing, ListPropertiesUtils];
30
29
  }
31
30
  /**
32
31
  * @inheritDoc
@@ -39,12 +38,10 @@ export default class ListPropertiesEditing extends Plugin {
39
38
  */
40
39
  constructor(editor) {
41
40
  super(editor);
42
- editor.config.define('list', {
43
- properties: {
44
- styles: true,
45
- startIndex: false,
46
- reversed: false
47
- }
41
+ editor.config.define('list.properties', {
42
+ styles: true,
43
+ startIndex: false,
44
+ reversed: false
48
45
  });
49
46
  }
50
47
  /**
@@ -53,159 +50,97 @@ export default class ListPropertiesEditing extends Plugin {
53
50
  init() {
54
51
  const editor = this.editor;
55
52
  const model = editor.model;
53
+ const listEditing = editor.plugins.get(ListEditing);
56
54
  const enabledProperties = editor.config.get('list.properties');
57
55
  const strategies = createAttributeStrategies(enabledProperties);
58
- // Extend schema.
59
- model.schema.extend('listItem', {
60
- allowAttributes: strategies.map(s => s.attributeName)
61
- });
62
56
  for (const strategy of strategies) {
63
57
  strategy.addCommand(editor);
58
+ model.schema.extend('$listItem', { allowAttributes: strategy.attributeName });
59
+ // Register downcast strategy.
60
+ listEditing.registerDowncastStrategy({
61
+ scope: 'list',
62
+ attributeName: strategy.attributeName,
63
+ setAttributeOnDowncast(writer, attributeValue, viewElement) {
64
+ strategy.setAttributeOnDowncast(writer, attributeValue, viewElement);
65
+ }
66
+ });
64
67
  }
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
68
  // 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;
69
+ editor.conversion.for('upcast').add(dispatcher => {
70
+ for (const strategy of strategies) {
71
+ dispatcher.on('element:ol', listPropertiesUpcastConverter(strategy));
72
+ dispatcher.on('element:ul', listPropertiesUpcastConverter(strategy));
167
73
  }
168
- }, { priority: 'high' });
169
- // If so, update the `listStyle` attribute for the second list.
170
- this.listenTo(model, 'deleteContent', () => {
171
- if (!firstMostOuterItem) {
172
- return;
74
+ });
75
+ // Verify if the list view element (ul or ol) requires refreshing.
76
+ listEditing.on('checkAttributes:list', (evt, { viewElement, modelAttributes }) => {
77
+ for (const strategy of strategies) {
78
+ if (strategy.getAttributeOnUpcast(viewElement) != modelAttributes[strategy.attributeName]) {
79
+ evt.return = true;
80
+ evt.stop();
81
+ }
173
82
  }
83
+ });
84
+ // Reset list properties after indenting list items.
85
+ this.listenTo(editor.commands.get('indentList'), 'afterExecute', (evt, changedBlocks) => {
174
86
  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);
87
+ for (const node of changedBlocks) {
88
+ for (const strategy of strategies) {
89
+ if (strategy.appliesToListItem(node)) {
90
+ // Just reset the attribute.
91
+ // If there is a previous indented list that this node should be merged into,
92
+ // the postfixer will unify all the attributes of both sub-lists.
93
+ writer.setAttribute(strategy.attributeName, strategy.defaultValue, node);
203
94
  }
204
95
  }
205
96
  }
206
97
  });
207
- firstMostOuterItem = null;
208
- }, { priority: 'low' });
98
+ });
99
+ // Add or remove list properties attributes depending on the list type.
100
+ listEditing.on('postFixer', (evt, { listNodes, writer }) => {
101
+ for (const { node } of listNodes) {
102
+ for (const strategy of strategies) {
103
+ // Check if attribute is valid.
104
+ if (strategy.hasValidAttribute(node)) {
105
+ continue;
106
+ }
107
+ // Add missing default property attributes...
108
+ if (strategy.appliesToListItem(node)) {
109
+ writer.setAttribute(strategy.attributeName, strategy.defaultValue, node);
110
+ }
111
+ // ...or remove invalid property attributes.
112
+ else {
113
+ writer.removeAttribute(strategy.attributeName, node);
114
+ }
115
+ evt.return = true;
116
+ }
117
+ }
118
+ });
119
+ // Make sure that all items in a single list (items at the same level & listType) have the same properties.
120
+ listEditing.on('postFixer', (evt, { listNodes, writer }) => {
121
+ for (const { node, previousNodeInList } of listNodes) {
122
+ // This is a first item of a nested list.
123
+ if (!previousNodeInList) {
124
+ continue;
125
+ }
126
+ // This is a first block of a list of a different type.
127
+ if (previousNodeInList.getAttribute('listType') != node.getAttribute('listType')) {
128
+ continue;
129
+ }
130
+ // Copy properties from the previous one.
131
+ for (const strategy of strategies) {
132
+ const { attributeName } = strategy;
133
+ if (!strategy.appliesToListItem(node)) {
134
+ continue;
135
+ }
136
+ const value = previousNodeInList.getAttribute(attributeName);
137
+ if (node.getAttribute(attributeName) != value) {
138
+ writer.setAttribute(attributeName, value, node);
139
+ evt.return = true;
140
+ }
141
+ }
142
+ }
143
+ });
209
144
  }
210
145
  }
211
146
  /**
@@ -214,25 +149,61 @@ export default class ListPropertiesEditing extends Plugin {
214
149
  function createAttributeStrategies(enabledProperties) {
215
150
  const strategies = [];
216
151
  if (enabledProperties.styles) {
152
+ const useAttribute = typeof enabledProperties.styles == 'object' && enabledProperties.styles.useAttribute;
217
153
  strategies.push({
218
154
  attributeName: 'listStyle',
219
155
  defaultValue: DEFAULT_LIST_TYPE,
156
+ viewConsumables: { styles: 'list-style-type' },
220
157
  addCommand(editor) {
221
- editor.commands.add('listStyle', new ListStyleCommand(editor, DEFAULT_LIST_TYPE));
158
+ let supportedTypes = getAllSupportedStyleTypes();
159
+ if (useAttribute) {
160
+ supportedTypes = supportedTypes.filter(styleType => !!getTypeAttributeFromListStyleType(styleType));
161
+ }
162
+ editor.commands.add('listStyle', new ListStyleCommand(editor, DEFAULT_LIST_TYPE, supportedTypes));
222
163
  },
223
- appliesToListItem() {
224
- return true;
164
+ appliesToListItem(item) {
165
+ return item.getAttribute('listType') == 'numbered' || item.getAttribute('listType') == 'bulleted';
166
+ },
167
+ hasValidAttribute(item) {
168
+ if (!this.appliesToListItem(item)) {
169
+ return !item.hasAttribute('listStyle');
170
+ }
171
+ if (!item.hasAttribute('listStyle')) {
172
+ return false;
173
+ }
174
+ const value = item.getAttribute('listStyle');
175
+ if (value == DEFAULT_LIST_TYPE) {
176
+ return true;
177
+ }
178
+ return getListTypeFromListStyleType(value) == item.getAttribute('listType');
225
179
  },
226
180
  setAttributeOnDowncast(writer, listStyle, element) {
227
181
  if (listStyle && listStyle !== DEFAULT_LIST_TYPE) {
228
- writer.setStyle('list-style-type', listStyle, element);
229
- }
230
- else {
231
- writer.removeStyle('list-style-type', element);
182
+ if (useAttribute) {
183
+ const value = getTypeAttributeFromListStyleType(listStyle);
184
+ if (value) {
185
+ writer.setAttribute('type', value, element);
186
+ return;
187
+ }
188
+ }
189
+ else {
190
+ writer.setStyle('list-style-type', listStyle, element);
191
+ return;
192
+ }
232
193
  }
194
+ writer.removeStyle('list-style-type', element);
195
+ writer.removeAttribute('type', element);
233
196
  },
234
197
  getAttributeOnUpcast(listParent) {
235
- return listParent.getStyle('list-style-type') || DEFAULT_LIST_TYPE;
198
+ const style = listParent.getStyle('list-style-type');
199
+ if (style) {
200
+ return style;
201
+ }
202
+ const attribute = listParent.getAttribute('type');
203
+ if (attribute) {
204
+ return getListStyleTypeFromTypeAttribute(attribute);
205
+ }
206
+ return DEFAULT_LIST_TYPE;
236
207
  }
237
208
  });
238
209
  }
@@ -240,12 +211,16 @@ function createAttributeStrategies(enabledProperties) {
240
211
  strategies.push({
241
212
  attributeName: 'listReversed',
242
213
  defaultValue: false,
214
+ viewConsumables: { attributes: 'reversed' },
243
215
  addCommand(editor) {
244
216
  editor.commands.add('listReversed', new ListReversedCommand(editor));
245
217
  },
246
218
  appliesToListItem(item) {
247
219
  return item.getAttribute('listType') == 'numbered';
248
220
  },
221
+ hasValidAttribute(item) {
222
+ return this.appliesToListItem(item) == item.hasAttribute('listReversed');
223
+ },
249
224
  setAttributeOnDowncast(writer, listReversed, element) {
250
225
  if (listReversed) {
251
226
  writer.setAttribute('reversed', 'reversed', element);
@@ -263,12 +238,16 @@ function createAttributeStrategies(enabledProperties) {
263
238
  strategies.push({
264
239
  attributeName: 'listStart',
265
240
  defaultValue: 1,
241
+ viewConsumables: { attributes: 'start' },
266
242
  addCommand(editor) {
267
243
  editor.commands.add('listStart', new ListStartCommand(editor));
268
244
  },
269
245
  appliesToListItem(item) {
270
246
  return item.getAttribute('listType') == 'numbered';
271
247
  },
248
+ hasValidAttribute(item) {
249
+ return this.appliesToListItem(item) == item.hasAttribute('listStart');
250
+ },
272
251
  setAttributeOnDowncast(writer, listStart, element) {
273
252
  if (listStart == 0 || listStart > 1) {
274
253
  writer.setAttribute('start', listStart, element);
@@ -285,412 +264,3 @@ function createAttributeStrategies(enabledProperties) {
285
264
  }
286
265
  return strategies;
287
266
  }
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
- }