@ckeditor/ckeditor5-engine 32.0.0 → 33.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-engine",
3
- "version": "32.0.0",
3
+ "version": "33.0.0",
4
4
  "description": "The editing engine of CKEditor 5 – the best browser-based rich text editor.",
5
5
  "keywords": [
6
6
  "wysiwyg",
@@ -23,30 +23,30 @@
23
23
  ],
24
24
  "main": "src/index.js",
25
25
  "dependencies": {
26
- "@ckeditor/ckeditor5-utils": "^32.0.0",
26
+ "@ckeditor/ckeditor5-utils": "^33.0.0",
27
27
  "lodash-es": "^4.17.15"
28
28
  },
29
29
  "devDependencies": {
30
- "@ckeditor/ckeditor5-basic-styles": "^32.0.0",
31
- "@ckeditor/ckeditor5-block-quote": "^32.0.0",
32
- "@ckeditor/ckeditor5-clipboard": "^32.0.0",
33
- "@ckeditor/ckeditor5-cloud-services": "^32.0.0",
34
- "@ckeditor/ckeditor5-core": "^32.0.0",
35
- "@ckeditor/ckeditor5-editor-classic": "^32.0.0",
36
- "@ckeditor/ckeditor5-enter": "^32.0.0",
37
- "@ckeditor/ckeditor5-essentials": "^32.0.0",
38
- "@ckeditor/ckeditor5-heading": "^32.0.0",
39
- "@ckeditor/ckeditor5-image": "^32.0.0",
40
- "@ckeditor/ckeditor5-link": "^32.0.0",
41
- "@ckeditor/ckeditor5-list": "^32.0.0",
42
- "@ckeditor/ckeditor5-mention": "^32.0.0",
43
- "@ckeditor/ckeditor5-paragraph": "^32.0.0",
44
- "@ckeditor/ckeditor5-table": "^32.0.0",
45
- "@ckeditor/ckeditor5-theme-lark": "^32.0.0",
46
- "@ckeditor/ckeditor5-typing": "^32.0.0",
47
- "@ckeditor/ckeditor5-ui": "^32.0.0",
48
- "@ckeditor/ckeditor5-undo": "^32.0.0",
49
- "@ckeditor/ckeditor5-widget": "^32.0.0",
30
+ "@ckeditor/ckeditor5-basic-styles": "^33.0.0",
31
+ "@ckeditor/ckeditor5-block-quote": "^33.0.0",
32
+ "@ckeditor/ckeditor5-clipboard": "^33.0.0",
33
+ "@ckeditor/ckeditor5-cloud-services": "^33.0.0",
34
+ "@ckeditor/ckeditor5-core": "^33.0.0",
35
+ "@ckeditor/ckeditor5-editor-classic": "^33.0.0",
36
+ "@ckeditor/ckeditor5-enter": "^33.0.0",
37
+ "@ckeditor/ckeditor5-essentials": "^33.0.0",
38
+ "@ckeditor/ckeditor5-heading": "^33.0.0",
39
+ "@ckeditor/ckeditor5-image": "^33.0.0",
40
+ "@ckeditor/ckeditor5-link": "^33.0.0",
41
+ "@ckeditor/ckeditor5-list": "^33.0.0",
42
+ "@ckeditor/ckeditor5-mention": "^33.0.0",
43
+ "@ckeditor/ckeditor5-paragraph": "^33.0.0",
44
+ "@ckeditor/ckeditor5-table": "^33.0.0",
45
+ "@ckeditor/ckeditor5-theme-lark": "^33.0.0",
46
+ "@ckeditor/ckeditor5-typing": "^33.0.0",
47
+ "@ckeditor/ckeditor5-ui": "^33.0.0",
48
+ "@ckeditor/ckeditor5-undo": "^33.0.0",
49
+ "@ckeditor/ckeditor5-widget": "^33.0.0",
50
50
  "webpack": "^5.58.1",
51
51
  "webpack-cli": "^4.9.0"
52
52
  },
@@ -14,7 +14,7 @@ import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
14
14
  import Mapper from '../conversion/mapper';
15
15
 
16
16
  import DowncastDispatcher from '../conversion/downcastdispatcher';
17
- import { insertText } from '../conversion/downcasthelpers';
17
+ import { insertAttributesAndChildren, insertText } from '../conversion/downcasthelpers';
18
18
 
19
19
  import UpcastDispatcher from '../conversion/upcastdispatcher';
20
20
  import { convertText, convertToModelFragment } from '../conversion/upcasthelpers';
@@ -31,7 +31,7 @@ import HtmlDataProcessor from '../dataprocessor/htmldataprocessor';
31
31
  * Controller for the data pipeline. The data pipeline controls how data is retrieved from the document
32
32
  * and set inside it. Hence, the controller features two methods which allow to {@link ~DataController#get get}
33
33
  * and {@link ~DataController#set set} data of the {@link ~DataController#model model}
34
- * using given:
34
+ * using the given:
35
35
  *
36
36
  * * {@link module:engine/dataprocessor/dataprocessor~DataProcessor data processor},
37
37
  * * downcast converters,
@@ -61,8 +61,8 @@ export default class DataController {
61
61
  this.model = model;
62
62
 
63
63
  /**
64
- * Mapper used for the conversion. It has no permanent bindings, because they are created when getting data and
65
- * cleared directly after the data are converted. However, the mapper is defined as a class property, because
64
+ * Mapper used for the conversion. It has no permanent bindings, because these are created while getting data and
65
+ * ae cleared directly after the data are converted. However, the mapper is defined as a class property, because
66
66
  * it needs to be passed to the `DowncastDispatcher` as a conversion API.
67
67
  *
68
68
  * @readonly
@@ -81,6 +81,7 @@ export default class DataController {
81
81
  schema: model.schema
82
82
  } );
83
83
  this.downcastDispatcher.on( 'insert:$text', insertText(), { priority: 'lowest' } );
84
+ this.downcastDispatcher.on( 'insert', insertAttributesAndChildren(), { priority: 'lowest' } );
84
85
 
85
86
  /**
86
87
  * Upcast dispatcher used by the {@link #set set method}. Upcast converters should be attached to it.
@@ -138,7 +139,7 @@ export default class DataController {
138
139
  //
139
140
  // Note that if there is no default converter for the element it will be skipped, for instance `<b>foo</b>` will be
140
141
  // converted to nothing. We therefore add `convertToModelFragment` as a last converter so it converts children of that
141
- // element to the document fragment and so `<b>foo</b>` will be converted to `foo` if there is no converter for `<b>`.
142
+ // element to the document fragment so `<b>foo</b>` will still be converted to `foo` even if there is no converter for `<b>`.
142
143
  this.upcastDispatcher.on( 'text', convertText(), { priority: 'lowest' } );
143
144
  this.upcastDispatcher.on( 'element', convertToModelFragment(), { priority: 'lowest' } );
144
145
  this.upcastDispatcher.on( 'documentFragment', convertToModelFragment(), { priority: 'lowest' } );
@@ -147,14 +148,14 @@ export default class DataController {
147
148
  this.decorate( 'set' );
148
149
  this.decorate( 'get' );
149
150
 
150
- // Fire the `ready` event when the initialization has completed. Such low-level listener gives possibility
151
+ // Fire the `ready` event when the initialization has completed. Such low-level listener offers the possibility
151
152
  // to plug into the initialization pipeline without interrupting the initialization flow.
152
153
  this.on( 'init', () => {
153
154
  this.fire( 'ready' );
154
155
  }, { priority: 'lowest' } );
155
156
 
156
- // Fix empty roots after DataController is 'ready' (note that init method could be decorated and stopped).
157
- // We need to handle this event because initial data could be empty and post-fixer would not get triggered.
157
+ // Fix empty roots after DataController is 'ready' (note that the init method could be decorated and stopped).
158
+ // We need to handle this event because initial data could be empty and the post-fixer would not get triggered.
158
159
  this.on( 'ready', () => {
159
160
  this.model.enqueueChange( { isUndoable: false }, autoParagraphEmptyRoots );
160
161
  }, { priority: 'lowest' } );
@@ -170,7 +171,7 @@ export default class DataController {
170
171
  * @param {String} [options.rootName='main'] Root name.
171
172
  * @param {String} [options.trim='empty'] Whether returned data should be trimmed. This option is set to `empty` by default,
172
173
  * which means whenever editor content is considered empty, an empty string will be returned. To turn off trimming completely
173
- * use `'none'`. In such cases exact content will be returned (for example `<p>&nbsp;</p>` for an empty editor).
174
+ * use `'none'`. In such cases the exact content will be returned (for example a `<p>&nbsp;</p>` for an empty editor).
174
175
  * @returns {String} Output data.
175
176
  */
176
177
  get( options = {} ) {
@@ -179,7 +180,7 @@ export default class DataController {
179
180
  if ( !this._checkIfRootsExists( [ rootName ] ) ) {
180
181
  /**
181
182
  * Cannot get data from a non-existing root. This error is thrown when {@link #get DataController#get() method}
182
- * is called with non-existent root name. For example, if there is an editor instance with only `main` root,
183
+ * is called with a non-existent root name. For example, if there is an editor instance with only `main` root,
183
184
  * calling {@link #get} like:
184
185
  *
185
186
  * data.get( { rootName: 'root2' } );
@@ -203,10 +204,10 @@ export default class DataController {
203
204
  /**
204
205
  * Returns the content of the given {@link module:engine/model/element~Element model's element} or
205
206
  * {@link module:engine/model/documentfragment~DocumentFragment model document fragment} converted by the downcast converters
206
- * attached to {@link #downcastDispatcher} and formatted by the {@link #processor data processor}.
207
+ * attached to the {@link #downcastDispatcher} and formatted by the {@link #processor data processor}.
207
208
  *
208
209
  * @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} modelElementOrFragment
209
- * Element whose content will be stringified.
210
+ * The element whose content will be stringified.
210
211
  * @param {Object} [options] Additional configuration passed to the conversion process.
211
212
  * @returns {String} Output data.
212
213
  */
@@ -221,12 +222,12 @@ export default class DataController {
221
222
  /**
222
223
  * Returns the content of the given {@link module:engine/model/element~Element model element} or
223
224
  * {@link module:engine/model/documentfragment~DocumentFragment model document fragment} converted by the downcast
224
- * converters attached to {@link #downcastDispatcher} to a
225
+ * converters attached to {@link #downcastDispatcher} into a
225
226
  * {@link module:engine/view/documentfragment~DocumentFragment view document fragment}.
226
227
  *
227
228
  * @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} modelElementOrFragment
228
229
  * Element or document fragment whose content will be converted.
229
- * @param {Object} [options={}] Additional configuration that will be available through
230
+ * @param {Object} [options={}] Additional configuration that will be available through the
230
231
  * {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi#options} during the conversion process.
231
232
  * @returns {module:engine/view/documentfragment~DocumentFragment} Output view DocumentFragment.
232
233
  */
@@ -234,7 +235,7 @@ export default class DataController {
234
235
  const viewDocument = this.viewDocument;
235
236
  const viewWriter = this._viewWriter;
236
237
 
237
- // Clear bindings so the call to this method gives correct results.
238
+ // Clear bindings so the call to this method returns correct results.
238
239
  this.mapper.clearBindings();
239
240
 
240
241
  // First, convert elements.
@@ -243,57 +244,46 @@ export default class DataController {
243
244
 
244
245
  this.mapper.bindElements( modelElementOrFragment, viewDocumentFragment );
245
246
 
246
- // Make additional options available during conversion process through `conversionApi`.
247
- this.downcastDispatcher.conversionApi.options = options;
248
-
249
- // We have no view controller and rendering to DOM in DataController so view.change() block is not used here.
250
- this.downcastDispatcher.convertInsert( modelRange, viewWriter );
251
-
252
- // Convert markers.
247
+ // Prepare list of markers.
253
248
  // For document fragment, simply take the markers assigned to this document fragment.
254
249
  // For model root, all markers in that root will be taken.
255
250
  // For model element, we need to check which markers are intersecting with this element and relatively modify the markers' ranges.
256
251
  // Collapsed markers at element boundary, although considered as not intersecting with the element, will also be returned.
257
252
  const markers = modelElementOrFragment.is( 'documentFragment' ) ?
258
- Array.from( modelElementOrFragment.markers ) :
253
+ modelElementOrFragment.markers :
259
254
  _getMarkersRelativeToElement( modelElementOrFragment );
260
255
 
261
- for ( const [ name, range ] of markers ) {
262
- this.downcastDispatcher.convertMarkerAdd( name, range, viewWriter );
263
- }
264
-
265
- // Clean `conversionApi`.
266
- delete this.downcastDispatcher.conversionApi.options;
256
+ this.downcastDispatcher.convert( modelRange, markers, viewWriter, options );
267
257
 
268
258
  return viewDocumentFragment;
269
259
  }
270
260
 
271
261
  /**
272
- * Sets initial input data parsed by the {@link #processor data processor} and
262
+ * Sets the initial input data parsed by the {@link #processor data processor} and
273
263
  * converted by the {@link #upcastDispatcher view-to-model converters}.
274
- * Initial data can be set only to document that {@link module:engine/model/document~Document#version} is equal 0.
264
+ * Initial data can be only set to a document whose {@link module:engine/model/document~Document#version} is equal 0.
275
265
  *
276
266
  * **Note** This method is {@link module:utils/observablemixin~ObservableMixin#decorate decorated} which is
277
267
  * used by e.g. collaborative editing plugin that syncs remote data on init.
278
268
  *
279
- * When data is passed as a string it is initialized on a default `main` root:
269
+ * When data is passed as a string, it is initialized on the default `main` root:
280
270
  *
281
- * dataController.init( '<p>Foo</p>' ); // Initializes data on the `main` root.
271
+ * dataController.init( '<p>Foo</p>' ); // Initializes data on the `main` root only, as no other is specified.
282
272
  *
283
- * To initialize data on a different root or multiple roots at once, object containing `rootName` - `data` pairs should be passed:
273
+ * To initialize data on a different root or multiple roots at once, an object containing `rootName` - `data` pairs should be passed:
284
274
  *
285
- * dataController.init( { main: '<p>Foo</p>', title: '<h1>Bar</h1>' } ); // Initializes data on the `main` and `title` roots.
275
+ * dataController.init( { main: '<p>Foo</p>', title: '<h1>Bar</h1>' } ); // Initializes data on both the `main` and `title` roots.
286
276
  *
287
277
  * @fires init
288
- * @param {String|Object.<String,String>} data Input data as a string or an object containing `rootName` - `data`
278
+ * @param {String|Object.<String,String>} data Input data as a string or an object containing the `rootName` - `data`
289
279
  * pairs to initialize data on multiple roots at once.
290
280
  * @returns {Promise} Promise that is resolved after the data is set on the editor.
291
281
  */
292
282
  init( data ) {
293
283
  if ( this.model.document.version ) {
294
284
  /**
295
- * Cannot set initial data to not empty {@link module:engine/model/document~Document}.
296
- * Initial data should be set once, during {@link module:core/editor/editor~Editor} initialization,
285
+ * Cannot set initial data to a non-empty {@link module:engine/model/document~Document}.
286
+ * Initial data should be set once, during the {@link module:core/editor/editor~Editor} initialization,
297
287
  * when the {@link module:engine/model/document~Document#version} is equal 0.
298
288
  *
299
289
  * @error datacontroller-init-document-not-empty
@@ -310,7 +300,7 @@ export default class DataController {
310
300
 
311
301
  if ( !this._checkIfRootsExists( Object.keys( initialData ) ) ) {
312
302
  /**
313
- * Cannot init data on a non-existing root. This error is thrown when {@link #init DataController#init() method}
303
+ * Cannot init data on a non-existent root. This error is thrown when {@link #init DataController#init() method}
314
304
  * is called with non-existent root name. For example, if there is an editor instance with only `main` root,
315
305
  * calling {@link #init} like:
316
306
  *
@@ -334,48 +324,48 @@ export default class DataController {
334
324
  }
335
325
 
336
326
  /**
337
- * Sets input data parsed by the {@link #processor data processor} and
327
+ * Sets the input data parsed by the {@link #processor data processor} and
338
328
  * converted by the {@link #upcastDispatcher view-to-model converters}.
339
- * This method can be used any time to replace existing editor data by the new one without clearing the
329
+ * This method can be used any time to replace existing editor data with the new one without clearing the
340
330
  * {@link module:engine/model/document~Document#history document history}.
341
331
  *
342
332
  * This method also creates a batch with all the changes applied. If all you need is to parse data, use
343
333
  * the {@link #parse} method.
344
334
  *
345
- * When data is passed as a string it is set on a default `main` root:
335
+ * When data is passed as a string it is set on the default `main` root:
346
336
  *
347
- * dataController.set( '<p>Foo</p>' ); // Sets data on the `main` root.
337
+ * dataController.set( '<p>Foo</p>' ); // Sets data on the `main` root, as no other is specified.
348
338
  *
349
- * To set data on a different root or multiple roots at once, object containing `rootName` - `data` pairs should be passed:
339
+ * To set data on a different root or multiple roots at once, an object containing `rootName` - `data` pairs should be passed:
350
340
  *
351
- * dataController.set( { main: '<p>Foo</p>', title: '<h1>Bar</h1>' } ); // Sets data on the `main` and `title` roots.
341
+ * dataController.set( { main: '<p>Foo</p>', title: '<h1>Bar</h1>' } ); // Sets data on the `main` and `title` roots as specified.
352
342
  *
353
- * To set the data with preserved undo stack and add the change to the undo stack, set `{ isUndoable: true }` as `batchType` option.
343
+ * To set the data with a preserved undo stack and add the change to the undo stack, set `{ isUndoable: true }` as a `batchType` option.
354
344
  *
355
345
  * dataController.set( '<p>Foo</p>', { batchType: { isUndoable: true } } );
356
346
  *
357
347
  * @fires set
358
- * @param {String|Object.<String,String>} data Input data as a string or an object containing `rootName` - `data`
348
+ * @param {String|Object.<String,String>} data Input data as a string or an object containing the `rootName` - `data`
359
349
  * pairs to set data on multiple roots at once.
360
350
  * @param {Object} [options={}] Options for setting data.
361
351
  * @param {Object} [options.batchType] The batch type that will be used to create a batch for the changes applied by this method.
362
352
  * By default, the batch will be set as {@link module:engine/model/batch~Batch#isUndoable not undoable} and the undo stack will be
363
- * cleared after the new data is applied (all undo steps will be removed). If batch type `isUndoable` flag will be set to `true`,
364
- * the undo stack will be preserved.
353
+ * cleared after the new data is applied (all undo steps will be removed). If the batch type `isUndoable` flag is be set to `true`,
354
+ * the undo stack will be preserved instead and not cleared when new data is applied.
365
355
  */
366
356
  set( data, options = {} ) {
367
357
  let newData = {};
368
358
 
369
359
  if ( typeof data === 'string' ) {
370
- newData.main = data; // Default root is 'main'. To set data on a different root, object should be passed.
360
+ newData.main = data; // The default root is 'main'. To set data on a different root, an object should be passed.
371
361
  } else {
372
362
  newData = data;
373
363
  }
374
364
 
375
365
  if ( !this._checkIfRootsExists( Object.keys( newData ) ) ) {
376
366
  /**
377
- * Cannot set data on a non-existing root. This error is thrown when {@link #set DataController#set() method}
378
- * is called with non-existent root name. For example, if there is an editor instance with only `main` root,
367
+ * Cannot set data on a non-existent root. This error is thrown when the {@link #set DataController#set() method}
368
+ * is called with non-existent root name. For example, if there is an editor instance with only the default `main` root,
379
369
  * calling {@link #set} like:
380
370
  *
381
371
  * data.set( { main: '<p>Foo</p>', root2: '<p>Bar</p>' } );
@@ -428,7 +418,7 @@ export default class DataController {
428
418
  * {@link module:engine/model/documentfragment~DocumentFragment#markers static markers map}.
429
419
  *
430
420
  * @param {module:engine/view/element~Element|module:engine/view/documentfragment~DocumentFragment} viewElementOrFragment
431
- * Element or document fragment whose content will be converted.
421
+ * The element or document fragment whose content will be converted.
432
422
  * @param {module:engine/model/schema~SchemaContextDefinition} [context='$root'] Base context in which the view will
433
423
  * be converted to the model. See: {@link module:engine/conversion/upcastdispatcher~UpcastDispatcher#convert}.
434
424
  * @returns {module:engine/model/documentfragment~DocumentFragment} Output document fragment.
@@ -440,7 +430,7 @@ export default class DataController {
440
430
  }
441
431
 
442
432
  /**
443
- * Adds a style processor normalization rules.
433
+ * Adds the style processor normalization rules.
444
434
  *
445
435
  * You can implement your own rules as well as use one of the available processor rules:
446
436
  *
@@ -456,18 +446,18 @@ export default class DataController {
456
446
  }
457
447
 
458
448
  /**
459
- * Registers a {@link module:engine/view/matcher~MatcherPattern} on {@link #htmlProcessor htmlProcessor}
460
- * and {@link #processor processor} for view elements whose content should be treated as a raw data
461
- * and not processed during conversion from DOM to view elements.
449
+ * Registers a {@link module:engine/view/matcher~MatcherPattern} on an {@link #htmlProcessor htmlProcessor}
450
+ * and a {@link #processor processor} for view elements whose content should be treated as raw data
451
+ * and not processed during the conversion from DOM to view elements.
462
452
  *
463
- * The raw data can be later accessed by {@link module:engine/view/element~Element#getCustomProperty view element custom property}
453
+ * The raw data can be later accessed by the {@link module:engine/view/element~Element#getCustomProperty view element custom property}
464
454
  * `"$rawContent"`.
465
455
  *
466
456
  * @param {module:engine/view/matcher~MatcherPattern} pattern Pattern matching all view elements whose content should
467
457
  * be treated as a raw data.
468
458
  */
469
459
  registerRawContentMatcher( pattern ) {
470
- // No need to register the pattern if both `htmlProcessor` and `processor` are the same instances.
460
+ // No need to register the pattern if both the `htmlProcessor` and `processor` are the same instances.
471
461
  if ( this.processor && this.processor !== this.htmlProcessor ) {
472
462
  this.processor.registerRawContentMatcher( pattern );
473
463
  }
@@ -483,7 +473,7 @@ export default class DataController {
483
473
  }
484
474
 
485
475
  /**
486
- * Checks if all provided root names are existing editor roots.
476
+ * Checks whether all provided root names are actually existing editor roots.
487
477
  *
488
478
  * @private
489
479
  * @param {Array.<String>} rootNames Root names to check.
@@ -506,7 +496,7 @@ export default class DataController {
506
496
  */
507
497
 
508
498
  /**
509
- * Event fired after the {@link #init `init()` method} was run. It can be {@link #listenTo listened to} in order to adjust or modify
499
+ * An event fired after the {@link #init `init()` method} was run. It can be {@link #listenTo listened to} in order to adjust or modify
510
500
  * the initialization flow. However, if the `init` event is stopped or prevented, the {@link #event:ready `ready` event}
511
501
  * should be fired manually.
512
502
  *
@@ -517,9 +507,9 @@ export default class DataController {
517
507
  */
518
508
 
519
509
  /**
520
- * Event fired after {@link #set set() method} has been run.
510
+ * An event fired after {@link #set set() method} has been run.
521
511
  *
522
- * The `set` event is fired by decorated {@link #set} method.
512
+ * The `set` event is fired by the decorated {@link #set} method.
523
513
  * See {@link module:utils/observablemixin~ObservableMixin#decorate} for more information and samples.
524
514
  *
525
515
  * @event set
@@ -528,7 +518,7 @@ export default class DataController {
528
518
  /**
529
519
  * Event fired after the {@link #get get() method} has been run.
530
520
  *
531
- * The `get` event is fired by decorated {@link #get} method.
521
+ * The `get` event is fired by the decorated {@link #get} method.
532
522
  * See {@link module:utils/observablemixin~ObservableMixin#decorate} for more information and samples.
533
523
  *
534
524
  * @event get
@@ -547,7 +537,7 @@ function _getMarkersRelativeToElement( element ) {
547
537
  const doc = element.root.document;
548
538
 
549
539
  if ( !doc ) {
550
- return [];
540
+ return new Map();
551
541
  }
552
542
 
553
543
  const elementRange = ModelRange._createIn( element );
@@ -581,7 +571,7 @@ function _getMarkersRelativeToElement( element ) {
581
571
  // reverse DOM order, and intersecting ranges are in something approximating
582
572
  // reverse DOM order (since reverse DOM order doesn't have a precise meaning
583
573
  // when working with intersecting ranges).
584
- return result.sort( ( [ n1, r1 ], [ n2, r2 ] ) => {
574
+ result.sort( ( [ n1, r1 ], [ n2, r2 ] ) => {
585
575
  if ( r1.end.compareWith( r2.start ) !== 'after' ) {
586
576
  // m1.end <= m2.start -- m1 is entirely <= m2
587
577
  return 1;
@@ -608,4 +598,6 @@ function _getMarkersRelativeToElement( element ) {
608
598
  }
609
599
  }
610
600
  } );
601
+
602
+ return new Map( result );
611
603
  }
@@ -11,16 +11,24 @@ import RootEditableElement from '../view/rooteditableelement';
11
11
  import View from '../view/view';
12
12
  import Mapper from '../conversion/mapper';
13
13
  import DowncastDispatcher from '../conversion/downcastdispatcher';
14
- import { clearAttributes, convertCollapsedSelection, convertRangeSelection, insertText, remove } from '../conversion/downcasthelpers';
14
+ import {
15
+ clearAttributes,
16
+ convertCollapsedSelection,
17
+ convertRangeSelection,
18
+ insertAttributesAndChildren,
19
+ insertText,
20
+ remove
21
+ } from '../conversion/downcasthelpers';
15
22
 
16
23
  import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
17
24
  import mix from '@ckeditor/ckeditor5-utils/src/mix';
25
+ import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
18
26
  import { convertSelectionChange } from '../conversion/upcasthelpers';
19
27
 
20
28
  // @if CK_DEBUG_ENGINE // const { dumpTrees, initDocumentDumping } = require( '../dev-utils/utils' );
21
29
 
22
30
  /**
23
- * Controller for the editing pipeline. The editing pipeline controls {@link ~EditingController#model model} rendering,
31
+ * A controller for the editing pipeline. The editing pipeline controls the {@link ~EditingController#model model} rendering,
24
32
  * including selection handling. It also creates the {@link ~EditingController#view view} which builds a
25
33
  * browser-independent virtualization over the DOM elements. The editing controller also attaches default converters.
26
34
  *
@@ -51,7 +59,7 @@ export default class EditingController {
51
59
  this.view = new View( stylesProcessor );
52
60
 
53
61
  /**
54
- * Mapper which describes the model-view binding.
62
+ * A mapper that describes the model-view binding.
55
63
  *
56
64
  * @readonly
57
65
  * @member {module:engine/conversion/mapper~Mapper}
@@ -59,7 +67,7 @@ export default class EditingController {
59
67
  this.mapper = new Mapper();
60
68
 
61
69
  /**
62
- * Downcast dispatcher that converts changes from the model to {@link #view the editing view}.
70
+ * Downcast dispatcher that converts changes from the model to the {@link #view editing view}.
63
71
  *
64
72
  * @readonly
65
73
  * @member {module:engine/conversion/downcastdispatcher~DowncastDispatcher} #downcastDispatcher
@@ -74,7 +82,7 @@ export default class EditingController {
74
82
  const markers = this.model.markers;
75
83
 
76
84
  // When plugins listen on model changes (on selection change, post fixers, etc.) and change the view as a result of
77
- // model's change, they might trigger view rendering before the conversion is completed (e.g. before the selection
85
+ // the model's change, they might trigger view rendering before the conversion is completed (e.g. before the selection
78
86
  // is converted). We disable rendering for the length of the outermost model change() block to prevent that.
79
87
  //
80
88
  // See https://github.com/ckeditor/ckeditor5-engine/issues/1528
@@ -101,6 +109,7 @@ export default class EditingController {
101
109
 
102
110
  // Attach default model converters.
103
111
  this.downcastDispatcher.on( 'insert:$text', insertText(), { priority: 'lowest' } );
112
+ this.downcastDispatcher.on( 'insert', insertAttributesAndChildren(), { priority: 'lowest' } );
104
113
  this.downcastDispatcher.on( 'remove', remove(), { priority: 'low' } );
105
114
 
106
115
  // Attach default model selection converters.
@@ -144,6 +153,74 @@ export default class EditingController {
144
153
  this.view.destroy();
145
154
  this.stopListening();
146
155
  }
156
+
157
+ /**
158
+ * Calling this method will refresh the marker by triggering the downcast conversion for it.
159
+ *
160
+ * Reconverting the marker is useful when you want to change its {@link module:engine/view/element~Element view element}
161
+ * without changing any marker data. For instance:
162
+ *
163
+ * let isCommentActive = false;
164
+ *
165
+ * model.conversion.markerToHighlight( {
166
+ * model: 'comment',
167
+ * view: data => {
168
+ * const classes = [ 'comment-marker' ];
169
+ *
170
+ * if ( isCommentActive ) {
171
+ * classes.push( 'comment-marker--active' );
172
+ * }
173
+ *
174
+ * return { classes };
175
+ * }
176
+ * } );
177
+ *
178
+ * // ...
179
+ *
180
+ * // Change the property that indicates if marker is displayed as active or not.
181
+ * isCommentActive = true;
182
+ *
183
+ * // Reconverting will downcast and synchronize the marker with the new isCommentActive state value.
184
+ * editor.editing.reconvertMarker( 'comment' );
185
+ *
186
+ * **Note**: If you want to reconvert a model item, use {@link #reconvertItem} instead.
187
+ *
188
+ * @param {String|module:engine/model/markercollection~Marker} markerOrName Name of a marker to update, or a marker instance.
189
+ */
190
+ reconvertMarker( markerOrName ) {
191
+ const markerName = typeof markerOrName == 'string' ? markerOrName : markerOrName.name;
192
+ const currentMarker = this.model.markers.get( markerName );
193
+
194
+ if ( !currentMarker ) {
195
+ /**
196
+ * The marker with the provided name does not exist and cannot be reconverted.
197
+ *
198
+ * @error editingcontroller-reconvertmarker-marker-not-exist
199
+ * @param {String} markerName The name of the reconverted marker.
200
+ */
201
+ throw new CKEditorError( 'editingcontroller-reconvertmarker-marker-not-exist', this, { markerName } );
202
+ }
203
+
204
+ this.model.change( () => {
205
+ this.model.markers._refresh( currentMarker );
206
+ } );
207
+ }
208
+
209
+ /**
210
+ * Calling this method will downcast a model item on demand (by requesting a refresh in the {@link module:engine/model/differ~Differ}).
211
+ *
212
+ * You can use it if you want the view representation of a specific item updated as a response to external modifications. For instance,
213
+ * when the view structure depends not only on the associated model data but also on some external state.
214
+ *
215
+ * **Note**: If you want to reconvert a model marker, use {@link #reconvertMarker} instead.
216
+ *
217
+ * @param {module:engine/model/item~Item} item Item to refresh.
218
+ */
219
+ reconvertItem( item ) {
220
+ this.model.change( () => {
221
+ this.model.document.differ._refreshItem( item );
222
+ } );
223
+ }
147
224
  }
148
225
 
149
226
  mix( EditingController, ObservableMixin );