@ckeditor/ckeditor5-list 29.2.0 → 32.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 (144) hide show
  1. package/LICENSE.md +2 -2
  2. package/build/list.js +3 -3
  3. package/build/translations/ar.js +1 -1
  4. package/build/translations/ast.js +1 -1
  5. package/build/translations/az.js +1 -1
  6. package/build/translations/bg.js +1 -1
  7. package/build/translations/cs.js +1 -1
  8. package/build/translations/da.js +1 -1
  9. package/build/translations/de-ch.js +1 -1
  10. package/build/translations/de.js +1 -1
  11. package/build/translations/el.js +1 -1
  12. package/build/translations/en-au.js +1 -1
  13. package/build/translations/en-gb.js +1 -1
  14. package/build/translations/eo.js +1 -1
  15. package/build/translations/es.js +1 -1
  16. package/build/translations/et.js +1 -1
  17. package/build/translations/eu.js +1 -1
  18. package/build/translations/fa.js +1 -1
  19. package/build/translations/fi.js +1 -1
  20. package/build/translations/fr.js +1 -1
  21. package/build/translations/gl.js +1 -1
  22. package/build/translations/he.js +1 -1
  23. package/build/translations/hi.js +1 -1
  24. package/build/translations/hr.js +1 -1
  25. package/build/translations/hu.js +1 -1
  26. package/build/translations/id.js +1 -1
  27. package/build/translations/it.js +1 -1
  28. package/build/translations/ja.js +1 -1
  29. package/build/translations/km.js +1 -1
  30. package/build/translations/kn.js +1 -1
  31. package/build/translations/ko.js +1 -1
  32. package/build/translations/ku.js +1 -1
  33. package/build/translations/lt.js +1 -1
  34. package/build/translations/lv.js +1 -1
  35. package/build/translations/nb.js +1 -1
  36. package/build/translations/ne.js +1 -1
  37. package/build/translations/nl.js +1 -1
  38. package/build/translations/no.js +1 -1
  39. package/build/translations/pl.js +1 -1
  40. package/build/translations/pt-br.js +1 -1
  41. package/build/translations/pt.js +1 -1
  42. package/build/translations/ro.js +1 -1
  43. package/build/translations/ru.js +1 -1
  44. package/build/translations/si.js +1 -1
  45. package/build/translations/sk.js +1 -1
  46. package/build/translations/sq.js +1 -1
  47. package/build/translations/sr-latn.js +1 -1
  48. package/build/translations/sr.js +1 -1
  49. package/build/translations/sv.js +1 -1
  50. package/build/translations/tk.js +1 -1
  51. package/build/translations/tr.js +1 -1
  52. package/build/translations/ug.js +1 -1
  53. package/build/translations/uk.js +1 -1
  54. package/build/translations/uz.js +1 -0
  55. package/build/translations/vi.js +1 -1
  56. package/build/translations/zh-cn.js +1 -1
  57. package/build/translations/zh.js +1 -1
  58. package/ckeditor5-metadata.json +8 -4
  59. package/lang/contexts.json +5 -1
  60. package/lang/translations/ar.po +17 -1
  61. package/lang/translations/ast.po +17 -1
  62. package/lang/translations/az.po +17 -1
  63. package/lang/translations/bg.po +17 -1
  64. package/lang/translations/cs.po +17 -1
  65. package/lang/translations/da.po +17 -1
  66. package/lang/translations/de-ch.po +17 -1
  67. package/lang/translations/de.po +17 -1
  68. package/lang/translations/el.po +17 -1
  69. package/lang/translations/en-au.po +17 -1
  70. package/lang/translations/en-gb.po +17 -1
  71. package/lang/translations/en.po +17 -1
  72. package/lang/translations/eo.po +17 -1
  73. package/lang/translations/es.po +20 -4
  74. package/lang/translations/et.po +17 -1
  75. package/lang/translations/eu.po +17 -1
  76. package/lang/translations/fa.po +17 -1
  77. package/lang/translations/fi.po +17 -1
  78. package/lang/translations/fr.po +17 -1
  79. package/lang/translations/gl.po +17 -1
  80. package/lang/translations/he.po +17 -1
  81. package/lang/translations/hi.po +17 -1
  82. package/lang/translations/hr.po +17 -1
  83. package/lang/translations/hu.po +17 -1
  84. package/lang/translations/id.po +17 -1
  85. package/lang/translations/it.po +17 -1
  86. package/lang/translations/ja.po +17 -1
  87. package/lang/translations/km.po +17 -1
  88. package/lang/translations/kn.po +17 -1
  89. package/lang/translations/ko.po +17 -1
  90. package/lang/translations/ku.po +17 -1
  91. package/lang/translations/lt.po +17 -1
  92. package/lang/translations/lv.po +17 -1
  93. package/lang/translations/nb.po +17 -1
  94. package/lang/translations/ne.po +17 -1
  95. package/lang/translations/nl.po +21 -5
  96. package/lang/translations/no.po +17 -1
  97. package/lang/translations/pl.po +17 -1
  98. package/lang/translations/pt-br.po +17 -1
  99. package/lang/translations/pt.po +17 -1
  100. package/lang/translations/ro.po +17 -1
  101. package/lang/translations/ru.po +17 -1
  102. package/lang/translations/si.po +17 -1
  103. package/lang/translations/sk.po +17 -1
  104. package/lang/translations/sq.po +17 -1
  105. package/lang/translations/sr-latn.po +17 -1
  106. package/lang/translations/sr.po +17 -1
  107. package/lang/translations/sv.po +17 -1
  108. package/lang/translations/tk.po +17 -1
  109. package/lang/translations/tr.po +17 -1
  110. package/lang/translations/ug.po +17 -1
  111. package/lang/translations/uk.po +17 -1
  112. package/lang/translations/uz.po +125 -0
  113. package/lang/translations/vi.po +17 -1
  114. package/lang/translations/zh-cn.po +17 -1
  115. package/lang/translations/zh.po +36 -20
  116. package/package.json +27 -27
  117. package/src/checktodolistcommand.js +1 -1
  118. package/src/converters.js +14 -15
  119. package/src/indentcommand.js +1 -1
  120. package/src/index.js +4 -4
  121. package/src/list.js +24 -1
  122. package/src/listcommand.js +2 -2
  123. package/src/listediting.js +1 -1
  124. package/src/listproperties.js +96 -0
  125. package/src/{liststyleediting.js → listpropertiesediting.js} +299 -122
  126. package/src/listpropertiesui.js +314 -0
  127. package/src/listreversedcommand.js +64 -0
  128. package/src/liststartcommand.js +63 -0
  129. package/src/liststyle.js +12 -6
  130. package/src/liststylecommand.js +5 -22
  131. package/src/listui.js +1 -1
  132. package/src/todolist.js +1 -1
  133. package/src/todolistconverters.js +1 -1
  134. package/src/todolistediting.js +1 -1
  135. package/src/todolistui.js +1 -1
  136. package/src/ui/collapsibleview.js +152 -0
  137. package/src/ui/listpropertiesview.js +405 -0
  138. package/src/utils.js +49 -4
  139. package/theme/collapsible.css +10 -0
  140. package/theme/listproperties.css +10 -0
  141. package/theme/liststyles.css +2 -6
  142. package/theme/todolist.css +1 -1
  143. package/CHANGELOG.md +0 -272
  144. package/src/liststyleui.js +0 -225
@@ -1,30 +1,33 @@
1
1
  /**
2
- * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
2
+ * @license Copyright (c) 2003-2022, 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
  /**
7
- * @module list/liststyleediting
7
+ * @module list/listpropertiesediting
8
8
  */
9
9
 
10
10
  import { Plugin } from 'ckeditor5/src/core';
11
11
  import ListEditing from './listediting';
12
12
  import ListStyleCommand from './liststylecommand';
13
+ import ListReversedCommand from './listreversedcommand';
14
+ import ListStartCommand from './liststartcommand';
13
15
  import { getSiblingListItem, getSiblingNodes } from './utils';
14
16
 
15
17
  const DEFAULT_LIST_TYPE = 'default';
16
18
 
17
19
  /**
18
- * The list style engine feature.
20
+ * The engine of the list properties feature.
19
21
  *
20
22
  * It sets the value for the `listItem` attribute of the {@link module:list/list~List `<listItem>`} element that
21
23
  * allows modifying the list style type.
22
24
  *
23
- * It registers the `'listStyle'` command.
25
+ * It registers the `'listStyle'`, `'listReversed'` and `'listStart'` commands if they are enabled in the configuration.
26
+ * Read more in {@link module:list/listproperties~ListPropertiesConfig}.
24
27
  *
25
28
  * @extends module:core/plugin~Plugin
26
29
  */
27
- export default class ListStyleEditing extends Plugin {
30
+ export default class ListPropertiesEditing extends Plugin {
28
31
  /**
29
32
  * @inheritDoc
30
33
  */
@@ -36,7 +39,22 @@ export default class ListStyleEditing extends Plugin {
36
39
  * @inheritDoc
37
40
  */
38
41
  static get pluginName() {
39
- return 'ListStyleEditing';
42
+ return 'ListPropertiesEditing';
43
+ }
44
+
45
+ /**
46
+ * @inheritDoc
47
+ */
48
+ constructor( editor ) {
49
+ super( editor );
50
+
51
+ editor.config.define( 'list', {
52
+ properties: {
53
+ styles: true,
54
+ startIndex: false,
55
+ reversed: false
56
+ }
57
+ } );
40
58
  }
41
59
 
42
60
  /**
@@ -46,29 +64,34 @@ export default class ListStyleEditing extends Plugin {
46
64
  const editor = this.editor;
47
65
  const model = editor.model;
48
66
 
67
+ const enabledProperties = editor.config.get( 'list.properties' );
68
+ const strategies = createAttributeStrategies( enabledProperties );
69
+
49
70
  // Extend schema.
50
71
  model.schema.extend( 'listItem', {
51
- allowAttributes: [ 'listStyle' ]
72
+ allowAttributes: strategies.map( s => s.attributeName )
52
73
  } );
53
74
 
54
- editor.commands.add( 'listStyle', new ListStyleCommand( editor, DEFAULT_LIST_TYPE ) );
75
+ for ( const strategy of strategies ) {
76
+ strategy.addCommand( editor );
77
+ }
55
78
 
56
79
  // Fix list attributes when modifying their nesting levels (the `listIndent` attribute).
57
- this.listenTo( editor.commands.get( 'indentList' ), '_executeCleanup', fixListAfterIndentListCommand( editor ) );
58
- this.listenTo( editor.commands.get( 'outdentList' ), '_executeCleanup', fixListAfterOutdentListCommand( editor ) );
80
+ this.listenTo( editor.commands.get( 'indentList' ), '_executeCleanup', fixListAfterIndentListCommand( editor, strategies ) );
81
+ this.listenTo( editor.commands.get( 'outdentList' ), '_executeCleanup', fixListAfterOutdentListCommand( editor, strategies ) );
59
82
 
60
83
  this.listenTo( editor.commands.get( 'bulletedList' ), '_executeCleanup', restoreDefaultListStyle( editor ) );
61
84
  this.listenTo( editor.commands.get( 'numberedList' ), '_executeCleanup', restoreDefaultListStyle( editor ) );
62
85
 
63
- // Register a post-fixer that ensures that the `listStyle` attribute is specified in each `listItem` element.
64
- model.document.registerPostFixer( fixListStyleAttributeOnListItemElements( editor ) );
86
+ // Register a post-fixer that ensures that the attributes is specified in each `listItem` element.
87
+ model.document.registerPostFixer( fixListAttributesOnListItemElements( editor, strategies ) );
65
88
 
66
89
  // Set up conversion.
67
- editor.conversion.for( 'upcast' ).add( upcastListItemStyle() );
68
- editor.conversion.for( 'downcast' ).add( downcastListStyleAttribute() );
90
+ editor.conversion.for( 'upcast' ).add( upcastListItemAttributes( strategies ) );
91
+ editor.conversion.for( 'downcast' ).add( downcastListItemAttributes( strategies ) );
69
92
 
70
93
  // Handle merging two separated lists into the single one.
71
- this._mergeListStyleAttributeWhileMergingLists();
94
+ this._mergeListAttributesWhileMergingLists( strategies );
72
95
  }
73
96
 
74
97
  /**
@@ -77,18 +100,19 @@ export default class ListStyleEditing extends Plugin {
77
100
  afterInit() {
78
101
  const editor = this.editor;
79
102
 
80
- // Enable post-fixer that removes the `listStyle` attribute from to-do list items only if the "TodoList" plugin is on.
81
- // We need to registry the hook here since the `TodoList` plugin can be added after the `ListStyleEditing`.
103
+ // Enable post-fixer that removes the attributes from to-do list items only if the "TodoList" plugin is on.
104
+ // We need to registry the hook here since the `TodoList` plugin can be added after the `ListPropertiesEditing`.
82
105
  if ( editor.commands.get( 'todoList' ) ) {
83
- editor.model.document.registerPostFixer( removeListStyleAttributeFromTodoList( editor ) );
106
+ editor.model.document.registerPostFixer( removeListItemAttributesFromTodoList( editor ) );
84
107
  }
85
108
  }
86
109
 
87
110
  /**
88
- * Starts listening to {@link module:engine/model/model~Model#deleteContent} checks whether two lists will be merged into a single one
89
- * after deleting the content.
111
+ * Starts listening to {@link module:engine/model/model~Model#deleteContent} and checks whether two lists will be merged into a single
112
+ * one after deleting the content.
90
113
  *
91
- * The purpose of this action is to adjust the `listStyle` value for the list that was merged.
114
+ * The purpose of this action is to adjust the `listStyle`, `listReversed` and `listStart` values
115
+ * for the list that was merged.
92
116
  *
93
117
  * Consider the following model's content:
94
118
  *
@@ -109,13 +133,14 @@ export default class ListStyleEditing extends Plugin {
109
133
  * See https://github.com/ckeditor/ckeditor5/issues/7879.
110
134
  *
111
135
  * @private
136
+ * @param {Array.<module:list/listpropertiesediting~AttributeStrategy>} attributeStrategies Strategies for the enabled attributes.
112
137
  */
113
- _mergeListStyleAttributeWhileMergingLists() {
138
+ _mergeListAttributesWhileMergingLists( attributeStrategies ) {
114
139
  const editor = this.editor;
115
140
  const model = editor.model;
116
141
 
117
142
  // First the outer-most`listItem` in the first list reference.
118
- // If found, the lists should be merged and this `listItem` provides the `listStyle` attribute
143
+ // If found, the lists should be merged and this `listItem` provides the attributes
119
144
  // and it is also a starting point when searching for items in the second list.
120
145
  let firstMostOuterItem;
121
146
 
@@ -189,13 +214,27 @@ export default class ListStyleEditing extends Plugin {
189
214
  direction: 'forward'
190
215
  } );
191
216
 
217
+ // If the selection ends in a non-list element, there are no <listItem>s that would require adjustments.
218
+ // See: #8642.
219
+ if ( !secondListMostOuterItem ) {
220
+ firstMostOuterItem = null;
221
+ return;
222
+ }
223
+
192
224
  const items = [
193
225
  secondListMostOuterItem,
194
226
  ...getSiblingNodes( writer.createPositionAt( secondListMostOuterItem, 0 ), 'forward' )
195
227
  ];
196
228
 
197
229
  for ( const listItem of items ) {
198
- writer.setAttribute( 'listStyle', firstMostOuterItem.getAttribute( 'listStyle' ), listItem );
230
+ for ( const strategy of attributeStrategies ) {
231
+ if ( strategy.appliesToListItem( listItem ) ) {
232
+ const attributeName = strategy.attributeName;
233
+ const value = firstMostOuterItem.getAttribute( attributeName );
234
+
235
+ writer.setAttribute( attributeName, value, listItem );
236
+ }
237
+ }
199
238
  }
200
239
  } );
201
240
 
@@ -204,11 +243,120 @@ export default class ListStyleEditing extends Plugin {
204
243
  }
205
244
  }
206
245
 
207
- // Returns a converter that consumes the `style` attribute and searches for the `list-style-type` definition.
246
+ /**
247
+ * Strategy for dealing with `listItem` attributes supported by this plugin.
248
+ *
249
+ * @typedef {Object} AttributeStrategy
250
+ * @private
251
+ * @property {String} #attributeName
252
+ * @property {*} #defaultValue
253
+ * @property {Function} #addCommand
254
+ * @property {Function} #appliesToListItem
255
+ * @property {Function} #setAttributeOnDowncast
256
+ * @property {Function} #getAttributeOnUpcast
257
+ */
258
+
259
+ // Creates an array of strategies for dealing with enabled listItem attributes.
260
+ //
261
+ // @param {Object} enabledProperties
262
+ // @param {Boolean} enabledProperties.styles
263
+ // @param {Boolean} enabledProperties.reversed
264
+ // @param {Boolean} enabledProperties.startIndex
265
+ // @returns {Array.<module:list/listpropertiesediting~AttributeStrategy>}
266
+ function createAttributeStrategies( enabledProperties ) {
267
+ const strategies = [];
268
+
269
+ if ( enabledProperties.styles ) {
270
+ strategies.push( {
271
+ attributeName: 'listStyle',
272
+ defaultValue: DEFAULT_LIST_TYPE,
273
+
274
+ addCommand( editor ) {
275
+ editor.commands.add( 'listStyle', new ListStyleCommand( editor, DEFAULT_LIST_TYPE ) );
276
+ },
277
+
278
+ appliesToListItem() {
279
+ return true;
280
+ },
281
+
282
+ setAttributeOnDowncast( writer, listStyle, element ) {
283
+ if ( listStyle && listStyle !== DEFAULT_LIST_TYPE ) {
284
+ writer.setStyle( 'list-style-type', listStyle, element );
285
+ } else {
286
+ writer.removeStyle( 'list-style-type', element );
287
+ }
288
+ },
289
+
290
+ getAttributeOnUpcast( listParent ) {
291
+ return listParent.getStyle( 'list-style-type' ) || DEFAULT_LIST_TYPE;
292
+ }
293
+ } );
294
+ }
295
+
296
+ if ( enabledProperties.reversed ) {
297
+ strategies.push( {
298
+ attributeName: 'listReversed',
299
+ defaultValue: false,
300
+
301
+ addCommand( editor ) {
302
+ editor.commands.add( 'listReversed', new ListReversedCommand( editor ) );
303
+ },
304
+
305
+ appliesToListItem( item ) {
306
+ return item.getAttribute( 'listType' ) == 'numbered';
307
+ },
308
+
309
+ setAttributeOnDowncast( writer, listReversed, element ) {
310
+ if ( listReversed ) {
311
+ writer.setAttribute( 'reversed', 'reversed', element );
312
+ } else {
313
+ writer.removeAttribute( 'reversed', element );
314
+ }
315
+ },
316
+
317
+ getAttributeOnUpcast( listParent ) {
318
+ return listParent.hasAttribute( 'reversed' );
319
+ }
320
+ } );
321
+ }
322
+
323
+ if ( enabledProperties.startIndex ) {
324
+ strategies.push( {
325
+ attributeName: 'listStart',
326
+ defaultValue: 1,
327
+
328
+ addCommand( editor ) {
329
+ editor.commands.add( 'listStart', new ListStartCommand( editor ) );
330
+ },
331
+
332
+ appliesToListItem( item ) {
333
+ return item.getAttribute( 'listType' ) == 'numbered';
334
+ },
335
+
336
+ setAttributeOnDowncast( writer, listStart, element ) {
337
+ if ( listStart != 1 ) {
338
+ writer.setAttribute( 'start', listStart, element );
339
+ } else {
340
+ writer.removeAttribute( 'start', element );
341
+ }
342
+ },
343
+
344
+ getAttributeOnUpcast( listParent ) {
345
+ return listParent.getAttribute( 'start' ) || 1;
346
+ }
347
+ } );
348
+ }
349
+
350
+ return strategies;
351
+ }
352
+
353
+ // Returns a converter consumes the `style`, `reversed` and `start` attribute.
354
+ // In `style` it searches for the `list-style-type` definition.
208
355
  // If not found, the `"default"` value will be used.
209
356
  //
357
+ // @param {Array.<module:list/listpropertiesediting~AttributeStrategy>} attributeStrategies
210
358
  // @returns {Function}
211
- function upcastListItemStyle() {
359
+ function upcastListItemAttributes( attributeStrategies ) {
212
360
  return dispatcher => {
213
361
  dispatcher.on( 'element:li', ( evt, data, conversionApi ) => {
214
362
  const listParent = data.viewItem.parent;
@@ -219,39 +367,45 @@ function upcastListItemStyle() {
219
367
  return;
220
368
  }
221
369
 
222
- const listStyle = listParent.getStyle( 'list-style-type' ) || DEFAULT_LIST_TYPE;
223
370
  const listItem = data.modelRange.start.nodeAfter || data.modelRange.end.nodeBefore;
224
371
 
225
- conversionApi.writer.setAttribute( 'listStyle', listStyle, listItem );
372
+ for ( const strategy of attributeStrategies ) {
373
+ if ( strategy.appliesToListItem( listItem ) ) {
374
+ const listStyle = strategy.getAttributeOnUpcast( listParent );
375
+ conversionApi.writer.setAttribute( strategy.attributeName, listStyle, listItem );
376
+ }
377
+ }
226
378
  }, { priority: 'low' } );
227
379
  };
228
380
  }
229
381
 
230
- // Returns a converter that adds the `list-style-type` definition as a value for the `style` attribute.
231
- // The `"default"` value is removed and not present in the view/data.
382
+ // Returns a converter that adds `reversed`, `start` attributes and adds `list-style-type` definition as a value for the `style` attribute.
383
+ // The `"default"` values are removed and not present in the view/data.
232
384
  //
385
+ // @param {Array.<module:list/listpropertiesediting~AttributeStrategy>} attributeStrategies
233
386
  // @returns {Function}
234
- function downcastListStyleAttribute() {
387
+ function downcastListItemAttributes( attributeStrategies ) {
235
388
  return dispatcher => {
236
- dispatcher.on( 'attribute:listStyle:listItem', ( evt, data, conversionApi ) => {
237
- const viewWriter = conversionApi.writer;
238
- const currentElement = data.item;
389
+ for ( const strategy of attributeStrategies ) {
390
+ dispatcher.on( `attribute:${ strategy.attributeName }:listItem`, ( evt, data, conversionApi ) => {
391
+ const viewWriter = conversionApi.writer;
392
+ const currentElement = data.item;
239
393
 
240
- const previousElement = getSiblingListItem( currentElement.previousSibling, {
241
- sameIndent: true,
242
- listIndent: currentElement.getAttribute( 'listIndent' ),
243
- direction: 'backward'
244
- } );
245
-
246
- const viewItem = conversionApi.mapper.toViewElement( currentElement );
394
+ const previousElement = getSiblingListItem( currentElement.previousSibling, {
395
+ sameIndent: true,
396
+ listIndent: currentElement.getAttribute( 'listIndent' ),
397
+ direction: 'backward'
398
+ } );
247
399
 
248
- // A case when elements represent different lists. We need to separate their container.
249
- if ( !areRepresentingSameList( currentElement, previousElement ) ) {
250
- viewWriter.breakContainer( viewWriter.createPositionBefore( viewItem ) );
251
- }
400
+ const viewItem = conversionApi.mapper.toViewElement( currentElement );
252
401
 
253
- setListStyle( viewWriter, data.attributeNewValue, viewItem.parent );
254
- }, { priority: 'low' } );
402
+ // A case when elements represent different lists. We need to separate their container.
403
+ if ( !areRepresentingSameList( currentElement, previousElement ) ) {
404
+ viewWriter.breakContainer( viewWriter.createPositionBefore( viewItem ) );
405
+ }
406
+ strategy.setAttributeOnDowncast( viewWriter, data.attributeNewValue, viewItem.parent );
407
+ }, { priority: 'low' } );
408
+ }
255
409
  };
256
410
 
257
411
  // Checks whether specified list items belong to the same list.
@@ -263,24 +417,13 @@ function downcastListStyleAttribute() {
263
417
  return listItem2 &&
264
418
  listItem1.getAttribute( 'listType' ) === listItem2.getAttribute( 'listType' ) &&
265
419
  listItem1.getAttribute( 'listIndent' ) === listItem2.getAttribute( 'listIndent' ) &&
266
- listItem1.getAttribute( 'listStyle' ) === listItem2.getAttribute( 'listStyle' );
267
- }
268
-
269
- // Updates or removes the `list-style-type` from the `element`.
270
- //
271
- // @param {module:engine/view/downcastwriter~DowncastWriter} writer
272
- // @param {String} listStyle
273
- // @param {module:engine/view/element~Element} element
274
- function setListStyle( writer, listStyle, element ) {
275
- if ( listStyle && listStyle !== DEFAULT_LIST_TYPE ) {
276
- writer.setStyle( 'list-style-type', listStyle, element );
277
- } else {
278
- writer.removeStyle( 'list-style-type', element );
279
- }
420
+ listItem1.getAttribute( 'listStyle' ) === listItem2.getAttribute( 'listStyle' ) &&
421
+ listItem1.getAttribute( 'listReversed' ) === listItem2.getAttribute( 'listReversed' ) &&
422
+ listItem1.getAttribute( 'listStart' ) === listItem2.getAttribute( 'listStart' );
280
423
  }
281
424
  }
282
425
 
283
- // When indenting list, nested list should clear its value for the `listStyle` attribute or inherit from nested lists.
426
+ // When indenting list, nested list should clear its value for the attributes or inherit from nested lists.
284
427
  //
285
428
  // ■ List item 1.
286
429
  // ■ List item 2.[]
@@ -292,11 +435,10 @@ function downcastListStyleAttribute() {
292
435
  // ■ List item 3.
293
436
  //
294
437
  // @param {module:core/editor/editor~Editor} editor
438
+ // @param {Array.<module:list/listpropertiesediting~AttributeStrategy>} attributeStrategies
295
439
  // @returns {Function}
296
- function fixListAfterIndentListCommand( editor ) {
440
+ function fixListAfterIndentListCommand( editor, attributeStrategies ) {
297
441
  return ( evt, changedItems ) => {
298
- let valueToSet;
299
-
300
442
  const root = changedItems[ 0 ];
301
443
  const rootIndent = root.getAttribute( 'listIndent' );
302
444
 
@@ -310,26 +452,31 @@ function fixListAfterIndentListCommand( editor ) {
310
452
  // ■ List item 4.
311
453
  //
312
454
  // List items: `2` and `3` should be adjusted.
313
- if ( root.previousSibling.getAttribute( 'listIndent' ) + 1 === rootIndent ) {
314
- // valueToSet = root.previousSibling.getAttribute( 'listStyle' ) || DEFAULT_LIST_TYPE;
315
- valueToSet = DEFAULT_LIST_TYPE;
316
- } else {
317
- const previousSibling = getSiblingListItem( root.previousSibling, {
455
+ let previousSibling = null;
456
+
457
+ if ( root.previousSibling.getAttribute( 'listIndent' ) + 1 !== rootIndent ) {
458
+ previousSibling = getSiblingListItem( root.previousSibling, {
318
459
  sameIndent: true, direction: 'backward', listIndent: rootIndent
319
460
  } );
320
-
321
- valueToSet = previousSibling.getAttribute( 'listStyle' );
322
461
  }
323
462
 
324
463
  editor.model.change( writer => {
325
464
  for ( const item of itemsToUpdate ) {
326
- writer.setAttribute( 'listStyle', valueToSet, item );
465
+ for ( const strategy of attributeStrategies ) {
466
+ if ( strategy.appliesToListItem( item ) ) {
467
+ const valueToSet = previousSibling == null ?
468
+ strategy.defaultValue :
469
+ previousSibling.getAttribute( strategy.attributeName );
470
+
471
+ writer.setAttribute( strategy.attributeName, valueToSet, item );
472
+ }
473
+ }
327
474
  }
328
475
  } );
329
476
  };
330
477
  }
331
478
 
332
- // When outdenting a list, a nested list should copy its value for the `listStyle` attribute
479
+ // When outdenting a list, a nested list should copy attribute values
333
480
  // from the previous sibling list item including the same value for the `listIndent` value.
334
481
  //
335
482
  // ■ List item 1.
@@ -343,8 +490,9 @@ function fixListAfterIndentListCommand( editor ) {
343
490
  // ■ List item 3.
344
491
  //
345
492
  // @param {module:core/editor/editor~Editor} editor
493
+ // @param {Array.<module:list/listpropertiesediting~AttributeStrategy>} attributeStrategies
346
494
  // @returns {Function}
347
- function fixListAfterOutdentListCommand( editor ) {
495
+ function fixListAfterOutdentListCommand( editor, attributeStrategies ) {
348
496
  return ( evt, changedItems ) => {
349
497
  changedItems = changedItems.reverse().filter( item => item.is( 'element', 'listItem' ) );
350
498
 
@@ -403,15 +551,23 @@ function fixListAfterOutdentListCommand( editor ) {
403
551
  const itemsToUpdate = changedItems.filter( item => item.getAttribute( 'listIndent' ) === indent );
404
552
 
405
553
  for ( const item of itemsToUpdate ) {
406
- writer.setAttribute( 'listStyle', listItem.getAttribute( 'listStyle' ), item );
554
+ for ( const strategy of attributeStrategies ) {
555
+ if ( strategy.appliesToListItem( item ) ) {
556
+ const attributeName = strategy.attributeName;
557
+ const valueToSet = listItem.getAttribute( attributeName );
558
+
559
+ writer.setAttribute( attributeName, valueToSet, item );
560
+ }
561
+ }
407
562
  }
408
563
  } );
409
564
  };
410
565
  }
411
566
 
412
- // Each `listItem` element must have specified the `listStyle` attribute.
413
- // This post-fixer checks whether inserted elements `listItem` elements should inherit the `listStyle` value from
414
- // their sibling nodes or should use the default value.
567
+ // Each `listItem` element must have specified the `listStyle`, `listReversed` and `listStart` attributes
568
+ // if they are enabled and supported by its `listType`.
569
+ // This post-fixer checks whether inserted elements `listItem` elements should inherit the attribute values from
570
+ // their sibling nodes or should use the default values.
415
571
  //
416
572
  // Paragraph[]
417
573
  // ■ List item 1. // [listStyle="square", listType="bulleted"]
@@ -442,8 +598,9 @@ function fixListAfterOutdentListCommand( editor ) {
442
598
  // ■ List item 3. // ...
443
599
  //
444
600
  // @param {module:core/editor/editor~Editor} editor
601
+ // @param {Array.<module:list/listpropertiesediting~AttributeStrategy>} attributeStrategies
445
602
  // @returns {Function}
446
- function fixListStyleAttributeOnListItemElements( editor ) {
603
+ function fixListAttributesOnListItemElements( editor, attributeStrategies ) {
447
604
  return writer => {
448
605
  let wasFixed = false;
449
606
 
@@ -490,39 +647,50 @@ function fixListStyleAttributeOnListItemElements( editor ) {
490
647
  }
491
648
  }
492
649
 
493
- for ( const item of insertedListItems ) {
494
- if ( !item.hasAttribute( 'listStyle' ) ) {
495
- if ( shouldInheritListType( existingListItem, item ) ) {
496
- writer.setAttribute( 'listStyle', existingListItem.getAttribute( 'listStyle' ), item );
497
- } else {
498
- writer.setAttribute( 'listStyle', DEFAULT_LIST_TYPE, item );
499
- }
500
- wasFixed = true;
501
- } else {
502
- // Adjust the `listStyle` attribute for inserted (pasted) items. See #8160.
503
- //
504
- // ■ List item 1. // [listStyle="square", listType="bulleted"]
505
- // ○ List item 1.1. // [listStyle="circle", listType="bulleted"]
506
- // ○ [] (selection is here)
507
- //
508
- // Then, pasting a list with different attributes (listStyle, listType):
509
- //
510
- // 1. First. // [listStyle="decimal", listType="numbered"]
511
- // 2. Second // [listStyle="decimal", listType="numbered"]
512
- //
513
- // The `listType` attribute will be corrected by the `ListEditing` converters.
514
- // We need to adjust the `listStyle` attribute. Expected structure:
515
- //
516
- // ■ List item 1. // [listStyle="square", listType="bulleted"]
517
- // ○ List item 1.1. // [listStyle="circle", listType="bulleted"]
518
- // ○ First. // [listStyle="circle", listType="bulleted"]
519
- // ○ Second // [listStyle="circle", listType="bulleted"]
520
- const previousSibling = item.previousSibling;
650
+ for ( const strategy of attributeStrategies ) {
651
+ const attributeName = strategy.attributeName;
521
652
 
522
- if ( shouldInheritListTypeFromPreviousItem( previousSibling, item ) ) {
523
- writer.setAttribute( 'listStyle', previousSibling.getAttribute( 'listStyle' ), item );
653
+ for ( const item of insertedListItems ) {
654
+ if ( !strategy.appliesToListItem( item ) ) {
655
+ writer.removeAttribute( attributeName, item );
524
656
 
657
+ continue;
658
+ }
659
+
660
+ if ( !item.hasAttribute( attributeName ) ) {
661
+ if ( shouldInheritListType( existingListItem, item, strategy ) ) {
662
+ writer.setAttribute( attributeName, existingListItem.getAttribute( attributeName ), item );
663
+ } else {
664
+ writer.setAttribute( attributeName, strategy.defaultValue, item );
665
+ }
525
666
  wasFixed = true;
667
+ } else {
668
+ // Adjust the `listStyle`, `listReversed` and `listStart`
669
+ // attributes for inserted (pasted) items. See #8160.
670
+ //
671
+ // ■ List item 1. // [listStyle="square", listType="bulleted"]
672
+ // ○ List item 1.1. // [listStyle="circle", listType="bulleted"]
673
+ // ○ [] (selection is here)
674
+ //
675
+ // Then, pasting a list with different attributes (listStyle, listType):
676
+ //
677
+ // 1. First. // [listStyle="decimal", listType="numbered"]
678
+ // 2. Second // [listStyle="decimal", listType="numbered"]
679
+ //
680
+ // The `listType` attribute will be corrected by the `ListEditing` converters.
681
+ // We need to adjust the `listStyle` attribute. Expected structure:
682
+ //
683
+ // ■ List item 1. // [listStyle="square", listType="bulleted"]
684
+ // ○ List item 1.1. // [listStyle="circle", listType="bulleted"]
685
+ // ○ First. // [listStyle="circle", listType="bulleted"]
686
+ // ○ Second // [listStyle="circle", listType="bulleted"]
687
+ const previousSibling = item.previousSibling;
688
+
689
+ if ( shouldInheritListTypeFromPreviousItem( previousSibling, item, strategy.attributeName ) ) {
690
+ writer.setAttribute( attributeName, previousSibling.getAttribute( attributeName ), item );
691
+
692
+ wasFixed = true;
693
+ }
526
694
  }
527
695
  }
528
696
  }
@@ -531,26 +699,28 @@ function fixListStyleAttributeOnListItemElements( editor ) {
531
699
  };
532
700
  }
533
701
 
534
- // Checks whether the `listStyle` attribute should be copied from the `baseItem` element.
702
+ // Checks whether the `listStyle`, `listReversed` and `listStart` attributes
703
+ // should be copied from the `baseItem` element.
535
704
  //
536
705
  // The attribute should be copied if the inserted element does not have defined it and
537
706
  // the value for the element is other than default in the base element.
538
707
  //
539
708
  // @param {module:engine/model/element~Element|null} baseItem
540
709
  // @param {module:engine/model/element~Element} itemToChange
710
+ // @param {module:list/listpropertiesediting~AttributeStrategy} attributeStrategy
541
711
  // @returns {Boolean}
542
- function shouldInheritListType( baseItem, itemToChange ) {
712
+ function shouldInheritListType( baseItem, itemToChange, attributeStrategy ) {
543
713
  if ( !baseItem ) {
544
714
  return false;
545
715
  }
546
716
 
547
- const baseListStyle = baseItem.getAttribute( 'listStyle' );
717
+ const baseListAttribute = baseItem.getAttribute( attributeStrategy.attributeName );
548
718
 
549
- if ( !baseListStyle ) {
719
+ if ( !baseListAttribute ) {
550
720
  return false;
551
721
  }
552
722
 
553
- if ( baseListStyle === DEFAULT_LIST_TYPE ) {
723
+ if ( baseListAttribute == attributeStrategy.defaultValue ) {
554
724
  return false;
555
725
  }
556
726
 
@@ -561,7 +731,8 @@ function shouldInheritListType( baseItem, itemToChange ) {
561
731
  return true;
562
732
  }
563
733
 
564
- // Checks whether the `listStyle` attribute should be copied from previous list item.
734
+ // Checks whether the `listStyle`, `listReversed` and `listStart` attributes
735
+ // should be copied from previous list item.
565
736
  //
566
737
  // The attribute should be copied if there's a mismatch of styles of the pasted list into a nested list.
567
738
  // Top-level lists are not normalized as we allow side-by-side list of different types.
@@ -569,7 +740,7 @@ function shouldInheritListType( baseItem, itemToChange ) {
569
740
  // @param {module:engine/model/element~Element|null} previousItem
570
741
  // @param {module:engine/model/element~Element} itemToChange
571
742
  // @returns {Boolean}
572
- function shouldInheritListTypeFromPreviousItem( previousItem, itemToChange ) {
743
+ function shouldInheritListTypeFromPreviousItem( previousItem, itemToChange, attributeName ) {
573
744
  if ( !previousItem || !previousItem.is( 'element', 'listItem' ) ) {
574
745
  return false;
575
746
  }
@@ -584,25 +755,29 @@ function shouldInheritListTypeFromPreviousItem( previousItem, itemToChange ) {
584
755
  return false;
585
756
  }
586
757
 
587
- const previousItemListStyle = previousItem.getAttribute( 'listStyle' );
758
+ const previousItemListAttribute = previousItem.getAttribute( attributeName );
588
759
 
589
- if ( !previousItemListStyle || previousItemListStyle === itemToChange.getAttribute( 'listStyle' ) ) {
760
+ if ( !previousItemListAttribute || previousItemListAttribute === itemToChange.getAttribute( attributeName ) ) {
590
761
  return false;
591
762
  }
592
763
 
593
764
  return true;
594
765
  }
595
766
 
596
- // Removes the `listStyle` attribute from "todo" list items.
767
+ // Removes the `listStyle`, `listReversed` and `listStart` attributes from "todo" list items.
597
768
  //
598
769
  // @param {module:core/editor/editor~Editor} editor
599
770
  // @returns {Function}
600
- function removeListStyleAttributeFromTodoList( editor ) {
771
+ function removeListItemAttributesFromTodoList( editor ) {
601
772
  return writer => {
602
773
  const todoListItems = getChangedListItems( editor.model.document.differ.getChanges() )
603
774
  .filter( item => {
604
775
  // Handle the todo lists only. The rest is handled in another post-fixer.
605
- return item.getAttribute( 'listType' ) === 'todo' && item.hasAttribute( 'listStyle' );
776
+ return item.getAttribute( 'listType' ) === 'todo' && (
777
+ item.hasAttribute( 'listStyle' ) ||
778
+ item.hasAttribute( 'listReversed' ) ||
779
+ item.hasAttribute( 'listStart' )
780
+ );
606
781
  } );
607
782
 
608
783
  if ( !todoListItems.length ) {
@@ -611,6 +786,8 @@ function removeListStyleAttributeFromTodoList( editor ) {
611
786
 
612
787
  for ( const item of todoListItems ) {
613
788
  writer.removeAttribute( 'listStyle', item );
789
+ writer.removeAttribute( 'listReversed', item );
790
+ writer.removeAttribute( 'listStart', item );
614
791
  }
615
792
 
616
793
  return true;