@ckeditor/ckeditor5-engine 33.0.0 → 34.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/README.md CHANGED
@@ -3,7 +3,8 @@ CKEditor 5 editing engine
3
3
 
4
4
  [![npm version](https://badge.fury.io/js/%40ckeditor%2Fckeditor5-engine.svg)](https://www.npmjs.com/package/@ckeditor/ckeditor5-engine)
5
5
  [![Coverage Status](https://coveralls.io/repos/github/ckeditor/ckeditor5/badge.svg?branch=master)](https://coveralls.io/github/ckeditor/ckeditor5?branch=master)
6
- [![Build Status](https://travis-ci.com/ckeditor/ckeditor5.svg?branch=master)](https://travis-ci.com/ckeditor/ckeditor5)
6
+ [![Build Status](https://travis-ci.com/ckeditor/ckeditor5.svg?branch=master)](https://app.travis-ci.com/github/ckeditor/ckeditor5)
7
+ ![Dependency Status](https://img.shields.io/librariesio/release/npm/@ckeditor/ckeditor5-engine)
7
8
 
8
9
  The CKEditor 5 editing engine implements a flexible MVC-based architecture for creating rich text editing features.
9
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-engine",
3
- "version": "33.0.0",
3
+ "version": "34.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": "^33.0.0",
26
+ "@ckeditor/ckeditor5-utils": "^34.0.0",
27
27
  "lodash-es": "^4.17.15"
28
28
  },
29
29
  "devDependencies": {
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",
30
+ "@ckeditor/ckeditor5-basic-styles": "^34.0.0",
31
+ "@ckeditor/ckeditor5-block-quote": "^34.0.0",
32
+ "@ckeditor/ckeditor5-clipboard": "^34.0.0",
33
+ "@ckeditor/ckeditor5-cloud-services": "^34.0.0",
34
+ "@ckeditor/ckeditor5-core": "^34.0.0",
35
+ "@ckeditor/ckeditor5-editor-classic": "^34.0.0",
36
+ "@ckeditor/ckeditor5-enter": "^34.0.0",
37
+ "@ckeditor/ckeditor5-essentials": "^34.0.0",
38
+ "@ckeditor/ckeditor5-heading": "^34.0.0",
39
+ "@ckeditor/ckeditor5-image": "^34.0.0",
40
+ "@ckeditor/ckeditor5-link": "^34.0.0",
41
+ "@ckeditor/ckeditor5-list": "^34.0.0",
42
+ "@ckeditor/ckeditor5-mention": "^34.0.0",
43
+ "@ckeditor/ckeditor5-paragraph": "^34.0.0",
44
+ "@ckeditor/ckeditor5-table": "^34.0.0",
45
+ "@ckeditor/ckeditor5-theme-lark": "^34.0.0",
46
+ "@ckeditor/ckeditor5-typing": "^34.0.0",
47
+ "@ckeditor/ckeditor5-ui": "^34.0.0",
48
+ "@ckeditor/ckeditor5-undo": "^34.0.0",
49
+ "@ckeditor/ckeditor5-widget": "^34.0.0",
50
50
  "webpack": "^5.58.1",
51
51
  "webpack-cli": "^4.9.0"
52
52
  },
@@ -151,8 +151,9 @@ export default class DowncastHelpers extends ConversionHelpers {
151
151
  * the view element. Note that the view will be reconverted if any of the listed attributes changes.
152
152
  * @param {Boolean} [config.model.children] Specifies whether the view element requires reconversion if the list
153
153
  * of the model child nodes changed.
154
- * @param {module:engine/view/elementdefinition~ElementDefinition|Function} config.view A view element definition or a function
155
- * that takes the model element and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
154
+ * @param {module:engine/view/elementdefinition~ElementDefinition|module:engine/conversion/downcasthelpers~ElementCreatorFunction}
155
+ * config.view A view element definition or a function that takes the model element and
156
+ * {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
156
157
  * as parameters and returns a view container element.
157
158
  * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
158
159
  */
@@ -377,7 +378,8 @@ export default class DowncastHelpers extends ConversionHelpers {
377
378
  * @param {Object} config Conversion configuration.
378
379
  * @param {String|Object} config.model The key of the attribute to convert from or a `{ key, values }` object. `values` is an array
379
380
  * of `String`s with possible values if the model attribute is an enumerable.
380
- * @param {module:engine/view/elementdefinition~ElementDefinition|Function|Object} config.view A view element definition or a function
381
+ * @param {module:engine/view/elementdefinition~ElementDefinition|Object|
382
+ * module:engine/conversion/downcasthelpers~AttributeElementCreatorFunction} config.view A view element definition or a function
381
383
  * that takes the model attribute value and
382
384
  * {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API} as parameters and returns a view
383
385
  * attribute element. If `config.model.values` is given, `config.view` should be an object assigning values from `config.model.values`
@@ -459,8 +461,9 @@ export default class DowncastHelpers extends ConversionHelpers {
459
461
  * @param {Object} config Conversion configuration.
460
462
  * @param {String|Object} config.model The key of the attribute to convert from or a `{ key, values, [ name ] }` object describing
461
463
  * the attribute key, possible values and, optionally, an element name to convert from.
462
- * @param {String|Object|Function} config.view A view attribute key, or a `{ key, value }` object or a function that takes
463
- * the model attribute value and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
464
+ * @param {String|Object|module:engine/conversion/downcasthelpers~AttributeCreatorFunction} config.view A view attribute key,
465
+ * or a `{ key, value }` object or a function that takes the model attribute value and
466
+ * {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
464
467
  * as parameters and returns a `{ key, value }` object. If the `key` is `'class'`, the `value` can be a `String` or an
465
468
  * array of `String`s. If the `key` is `'style'`, the `value` is an object with key-value pairs. In other cases, `value` is a `String`.
466
469
  * If `config.model.values` is set, `config.view` should be an object assigning values from `config.model.values` to
@@ -971,10 +974,10 @@ export function wrap( elementCreator ) {
971
974
 
972
975
  // Recreate current wrapping node. It will be used to unwrap view range if the attribute value has changed
973
976
  // or the attribute was removed.
974
- const oldViewElement = elementCreator( data.attributeOldValue, conversionApi );
977
+ const oldViewElement = elementCreator( data.attributeOldValue, conversionApi, data );
975
978
 
976
979
  // Create node to wrap with.
977
- const newViewElement = elementCreator( data.attributeNewValue, conversionApi );
980
+ const newViewElement = elementCreator( data.attributeNewValue, conversionApi, data );
978
981
 
979
982
  if ( !oldViewElement && !newViewElement ) {
980
983
  return;
@@ -1037,7 +1040,7 @@ export function insertElement( elementCreator, consumer = defaultConsumer ) {
1037
1040
  return;
1038
1041
  }
1039
1042
 
1040
- const viewElement = elementCreator( data.item, conversionApi );
1043
+ const viewElement = elementCreator( data.item, conversionApi, data );
1041
1044
 
1042
1045
  if ( !viewElement ) {
1043
1046
  return;
@@ -1085,7 +1088,7 @@ export function insertStructure( elementCreator, consumer ) {
1085
1088
  conversionApi.writer._registerSlotFactory( createSlotFactory( data.item, slotsMap, conversionApi ) );
1086
1089
 
1087
1090
  // View creation.
1088
- const viewElement = elementCreator( data.item, conversionApi );
1091
+ const viewElement = elementCreator( data.item, conversionApi, data );
1089
1092
 
1090
1093
  conversionApi.writer._clearSlotFactory();
1091
1094
 
@@ -1376,8 +1379,8 @@ function changeAttribute( attributeCreator ) {
1376
1379
  return;
1377
1380
  }
1378
1381
 
1379
- const oldAttribute = attributeCreator( data.attributeOldValue, conversionApi );
1380
- const newAttribute = attributeCreator( data.attributeNewValue, conversionApi );
1382
+ const oldAttribute = attributeCreator( data.attributeOldValue, conversionApi, data );
1383
+ const newAttribute = attributeCreator( data.attributeNewValue, conversionApi, data );
1381
1384
 
1382
1385
  if ( !oldAttribute && !newAttribute ) {
1383
1386
  return;
@@ -1653,7 +1656,8 @@ function removeHighlight( highlightDescriptor ) {
1653
1656
  // @param {String|Object} config.model The description or a name of the model element to convert.
1654
1657
  // @param {String|Array.<String>} [config.model.attributes] List of attributes triggering element reconversion.
1655
1658
  // @param {Boolean} [config.model.children] Should reconvert element if the list of model child nodes changed.
1656
- // @param {module:engine/view/elementdefinition~ElementDefinition|Function} config.view
1659
+ // @param {module:engine/view/elementdefinition~ElementDefinition|module:engine/conversion/downcasthelpers~ElementCreatorFunction}
1660
+ // config.view
1657
1661
  // @returns {Function} Conversion helper.
1658
1662
  function downcastElementToElement( config ) {
1659
1663
  config = cloneDeep( config );
@@ -1760,10 +1764,11 @@ function downcastElementToStructure( config ) {
1760
1764
  // @param {Object} config Conversion configuration.
1761
1765
  // @param {String|Object} config.model The key of the attribute to convert from or a `{ key, values }` object. `values` is an array
1762
1766
  // of `String`s with possible values if the model attribute is an enumerable.
1763
- // @param {module:engine/view/elementdefinition~ElementDefinition|Function|Object} config.view A view element definition or a function
1764
- // that takes the model attribute value and {@link module:engine/view/downcastwriter~DowncastWriter view downcast writer}
1765
- // as parameters and returns a view attribute element. If `config.model.values` is
1766
- // given, `config.view` should be an object assigning values from `config.model.values` to view element definitions or functions.
1767
+ // @param {module:engine/view/elementdefinition~ElementDefinition|module:engine/conversion/downcasthelpers~AttributeElementCreatorFunction|
1768
+ // Object} config.view A view element definition or a function that takes the model attribute value and
1769
+ // {@link module:engine/view/downcastwriter~DowncastWriter view downcast writer} as parameters and returns a view attribute element.
1770
+ // If `config.model.values` is given, `config.view` should be an object assigning values from `config.model.values` to view element
1771
+ // definitions or functions.
1767
1772
  // @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
1768
1773
  // @returns {Function} Conversion helper.
1769
1774
  function downcastAttributeToElement( config ) {
@@ -1798,9 +1803,10 @@ function downcastAttributeToElement( config ) {
1798
1803
  // @param {Object} config Conversion configuration.
1799
1804
  // @param {String|Object} config.model The key of the attribute to convert from or a `{ key, values, [ name ] }` object describing
1800
1805
  // the attribute key, possible values and, optionally, an element name to convert from.
1801
- // @param {String|Object|Function} config.view A view attribute key, or a `{ key, value }` object or a function that takes
1802
- // the model attribute value and returns a `{ key, value }` object. If `key` is `'class'`, `value` can be a `String` or an
1803
- // array of `String`s. If `key` is `'style'`, `value` is an object with key-value pairs. In other cases, `value` is a `String`.
1806
+ // @param {String|Object|module:engine/conversion/downcasthelpers~AttributeCreatorFunction} config.view A view attribute key,
1807
+ // or a `{ key, value }` object or a function that takes the model attribute value and returns a `{ key, value }` object.
1808
+ // If `key` is `'class'`, `value` can be a `String` or an array of `String`s. If `key` is `'style'`, `value` is an object with
1809
+ // key-value pairs. In other cases, `value` is a `String`.
1804
1810
  // If `config.model.values` is set, `config.view` should be an object assigning values from `config.model.values` to
1805
1811
  // `{ key, value }` objects or a functions.
1806
1812
  // @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
@@ -2386,6 +2392,23 @@ function defaultConsumer( item, consumable, { preflight } = {} ) {
2386
2392
  * @see module:engine/conversion/downcasthelpers~insertStructure
2387
2393
  */
2388
2394
 
2395
+ /**
2396
+ * A view element creator function that takes the model element and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi
2397
+ * downcast conversion API} as parameters and returns a view container element.
2398
+ *
2399
+ * @callback module:engine/conversion/downcasthelpers~ElementCreatorFunction
2400
+ * @param {module:engine/model/element~Element} element The model element to be converted to the view structure.
2401
+ * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion interface.
2402
+ * @param {Object} data Additional information about the change (same as for
2403
+ * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:insert `insert`} event).
2404
+ * @param {module:engine/model/item~Item} data.item Inserted item.
2405
+ * @param {module:engine/model/range~Range} data.range Range spanning over inserted item.
2406
+ * @returns {module:engine/view/element~Element} The view element.
2407
+ *
2408
+ * @see module:engine/conversion/downcasthelpers~DowncastHelpers#elementToElement
2409
+ * @see module:engine/conversion/downcasthelpers~insertElement
2410
+ */
2411
+
2389
2412
  /**
2390
2413
  * A function that takes the model element and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast
2391
2414
  * conversion API} as parameters and returns a view container element with slots for model child nodes to be converted into.
@@ -2393,12 +2416,60 @@ function defaultConsumer( item, consumable, { preflight } = {} ) {
2393
2416
  * @callback module:engine/conversion/downcasthelpers~StructureCreatorFunction
2394
2417
  * @param {module:engine/model/element~Element} element The model element to be converted to the view structure.
2395
2418
  * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion interface.
2419
+ * @param {Object} data Additional information about the change (same as for
2420
+ * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:insert `insert`} event).
2421
+ * @param {module:engine/model/item~Item} data.item Inserted item.
2422
+ * @param {module:engine/model/range~Range} data.range Range spanning over inserted item.
2396
2423
  * @returns {module:engine/view/element~Element} The view structure with slots for model child nodes.
2397
2424
  *
2398
2425
  * @see module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure
2399
2426
  * @see module:engine/conversion/downcasthelpers~insertStructure
2400
2427
  */
2401
2428
 
2429
+ /**
2430
+ * A view element creator function that takes the model attribute value and
2431
+ * {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API} as parameters and returns a view
2432
+ * attribute element.
2433
+ *
2434
+ * @callback module:engine/conversion/downcasthelpers~AttributeElementCreatorFunction
2435
+ * @param {*} attributeValue The model attribute value to be converted to the view attribute element.
2436
+ * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion interface.
2437
+ * @param {Object} data Additional information about the change (same as for
2438
+ * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:attribute `attribute`} event).
2439
+ * @param {module:engine/model/item~Item|module:engine/model/documentselection~DocumentSelection} data.item Changed item
2440
+ * or converted selection.
2441
+ * @param {module:engine/model/range~Range} data.range Range spanning over changed item or selection range.
2442
+ * @param {String} data.attributeKey Attribute key.
2443
+ * @param {*} data.attributeOldValue Attribute value before the change. This is `null` when selection attribute is converted.
2444
+ * @param {*} data.attributeNewValue New attribute value.
2445
+ * @returns {module:engine/view/attributeelement~AttributeElement} The view attribute element.
2446
+ *
2447
+ * @see module:engine/conversion/downcasthelpers~DowncastHelpers#attributeToElement
2448
+ * @see module:engine/conversion/downcasthelpers~wrap
2449
+ */
2450
+
2451
+ /**
2452
+ * A function that takes the model attribute value and
2453
+ * {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
2454
+ * as parameters.
2455
+ *
2456
+ * @callback module:engine/conversion/downcasthelpers~AttributeCreatorFunction
2457
+ * @param {*} attributeValue The model attribute value to be converted to the view attribute element.
2458
+ * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion interface.
2459
+ * @param {Object} data Additional information about the change (same as for
2460
+ * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:attribute `attribute`} event).
2461
+ * @param {module:engine/model/item~Item|module:engine/model/documentselection~DocumentSelection} data.item Changed item
2462
+ * or converted selection.
2463
+ * @param {module:engine/model/range~Range} data.range Range spanning over changed item or selection range.
2464
+ * @param {String} data.attributeKey Attribute key.
2465
+ * @param {*} data.attributeOldValue Attribute value before the change. This is `null` when selection attribute is converted.
2466
+ * @param {*} data.attributeNewValue New attribute value.
2467
+ * @returns {Object|null} A `{ key, value }` object. If `key` is `'class'`, `value` can be a `String` or an
2468
+ * array of `String`s. If `key` is `'style'`, `value` is an object with key-value pairs. In other cases, `value` is a `String`.
2469
+ *
2470
+ * @see module:engine/conversion/downcasthelpers~DowncastHelpers#attributeToAttribute
2471
+ */
2472
+
2402
2473
  /**
2403
2474
  * A function that is expected to consume all the consumables that were used by the element creator.
2404
2475
  *
@@ -121,9 +121,9 @@ export default class Mapper {
121
121
  *
122
122
  * Make sure that the model element is correctly converted to the view.
123
123
  *
124
- * @error mapping-view-position-parent-not-found
124
+ * @error mapping-model-position-view-parent-not-found
125
125
  */
126
- throw new CKEditorError( 'mapping-view-position-parent-not-found', this, { modelPosition: data.modelPosition } );
126
+ throw new CKEditorError( 'mapping-model-position-view-parent-not-found', this, { modelPosition: data.modelPosition } );
127
127
  }
128
128
 
129
129
  data.viewPosition = this.findPositionIn( viewContainer, data.modelPosition.offset );
@@ -151,6 +151,16 @@ export default class UpcastDispatcher {
151
151
  */
152
152
  this._modelCursor = null;
153
153
 
154
+ /**
155
+ * The list of elements that were created during splitting but should not get removed on conversion end even if they are empty.
156
+ *
157
+ * After the conversion process the list is cleared.
158
+ *
159
+ * @private
160
+ * @type {Set.<module:engine/model/element~Element>}
161
+ */
162
+ this._emptyElementsToKeep = new Set();
163
+
154
164
  /**
155
165
  * An interface passed by the dispatcher to the event callbacks.
156
166
  *
@@ -167,6 +177,7 @@ export default class UpcastDispatcher {
167
177
  // Advanced API - use only if custom position handling is needed.
168
178
  this.conversionApi.splitToAllowedParent = this._splitToAllowedParent.bind( this );
169
179
  this.conversionApi.getSplitParts = this._getSplitParts.bind( this );
180
+ this.conversionApi.keepEmptyElement = this._keepEmptyElement.bind( this );
170
181
  }
171
182
 
172
183
  /**
@@ -226,6 +237,7 @@ export default class UpcastDispatcher {
226
237
  // Clear split elements & parents lists.
227
238
  this._splitParts.clear();
228
239
  this._cursorParents.clear();
240
+ this._emptyElementsToKeep.clear();
229
241
 
230
242
  // Clear conversion API.
231
243
  this.conversionApi.writer = null;
@@ -451,6 +463,15 @@ export default class UpcastDispatcher {
451
463
  return parts;
452
464
  }
453
465
 
466
+ /**
467
+ * Mark an element that were created during splitting that it should not get removed on conversion end even if it's empty.
468
+ *
469
+ * @private
470
+ */
471
+ _keepEmptyElement( element ) {
472
+ this._emptyElementsToKeep.add( element );
473
+ }
474
+
454
475
  /**
455
476
  * Checks if there are any empty elements created while splitting and removes them.
456
477
  *
@@ -463,7 +484,7 @@ export default class UpcastDispatcher {
463
484
  let anyRemoved = false;
464
485
 
465
486
  for ( const element of this._splitParts.keys() ) {
466
- if ( element.isEmpty ) {
487
+ if ( element.isEmpty && !this._emptyElementsToKeep.has( element ) ) {
467
488
  this.conversionApi.writer.remove( element );
468
489
  this._splitParts.delete( element );
469
490
 
@@ -757,6 +778,15 @@ function createContextTree( contextDefinition, writer ) {
757
778
  * @returns {Array.<module:engine/model/element~Element>}
758
779
  */
759
780
 
781
+ /**
782
+ * Mark an element that was created during splitting that it should not get removed on conversion end even if it's empty.
783
+ *
784
+ * **Note:** This is an advanced method. For most cases you will not need to keep the split empty element.
785
+ *
786
+ * @method #keepEmptyElement
787
+ * @param {module:engine/model/element~Element} element
788
+ */
789
+
760
790
  /**
761
791
  * Stores information about what parts of the processed view item are still waiting to be handled. After a piece of view item
762
792
  * was converted, an appropriate consumable value should be
package/src/index.js CHANGED
@@ -28,11 +28,17 @@ export { default as LivePosition } from './model/liveposition';
28
28
  export { default as Model } from './model/model';
29
29
  export { default as TreeWalker } from './model/treewalker';
30
30
  export { default as Element } from './model/element';
31
+ export { default as Position } from './model/position';
32
+ export { default as DocumentFragment } from './model/documentfragment';
31
33
  export { default as History } from './model/history';
34
+ export { default as Text } from './model/text';
32
35
 
33
36
  export { default as DomConverter } from './view/domconverter';
34
37
  export { default as Renderer } from './view/renderer';
35
38
  export { default as ViewDocument } from './view/document';
39
+ export { default as ViewText } from './view/text';
40
+ export { default as ViewElement } from './view/element';
41
+ export { default as ViewDocumentFragment } from './view/documentfragment';
36
42
 
37
43
  export { getFillerOffset } from './view/containerelement';
38
44
  export { default as Observer } from './view/observer/observer';
@@ -336,23 +336,27 @@ export default class Differ {
336
336
  * @returns {Boolean}
337
337
  */
338
338
  hasDataChanges() {
339
+ if ( this._changesInElement.size > 0 ) {
340
+ return true;
341
+ }
342
+
339
343
  for ( const { newMarkerData, oldMarkerData } of this._changedMarkers.values() ) {
340
344
  if ( newMarkerData.affectsData !== oldMarkerData.affectsData ) {
341
345
  return true;
342
346
  }
343
347
 
344
348
  if ( newMarkerData.affectsData ) {
345
- // Skip markers, which ranges have not changed.
346
- if ( newMarkerData.range && oldMarkerData.range && newMarkerData.range.isEqual( oldMarkerData.range ) ) {
347
- return false;
348
- }
349
+ const markerAdded = newMarkerData.range && !oldMarkerData.range;
350
+ const markerRemoved = !newMarkerData.range && oldMarkerData.range;
351
+ const markerChanged = newMarkerData.range && oldMarkerData.range && !newMarkerData.range.isEqual( oldMarkerData.range );
349
352
 
350
- return true;
353
+ if ( markerAdded || markerRemoved || markerChanged ) {
354
+ return true;
355
+ }
351
356
  }
352
357
  }
353
358
 
354
- // If markers do not affect the data, check whether there are some changes in elements.
355
- return this._changesInElement.size > 0;
359
+ return false;
356
360
  }
357
361
 
358
362
  /**
@@ -419,12 +423,12 @@ export default class Differ {
419
423
  for ( const action of actions ) {
420
424
  if ( action === 'i' ) {
421
425
  // Generate diff item for this element and insert it into the diff set.
422
- diffSet.push( this._getInsertDiff( element, i, elementChildren[ i ].name ) );
426
+ diffSet.push( this._getInsertDiff( element, i, elementChildren[ i ] ) );
423
427
 
424
428
  i++;
425
429
  } else if ( action === 'r' ) {
426
430
  // Generate diff item for this element and insert it into the diff set.
427
- diffSet.push( this._getRemoveDiff( element, i, snapshotChildren[ j ].name ) );
431
+ diffSet.push( this._getRemoveDiff( element, i, snapshotChildren[ j ] ) );
428
432
 
429
433
  j++;
430
434
  } else if ( action === 'a' ) {
@@ -925,14 +929,15 @@ export default class Differ {
925
929
  * @private
926
930
  * @param {module:engine/model/element~Element} parent The element in which the change happened.
927
931
  * @param {Number} offset The offset at which change happened.
928
- * @param {String} name The name of the removed element or `'$text'` for a character.
932
+ * @param {Object} elementSnapshot The snapshot of the removed element a character.
929
933
  * @returns {Object} The diff item.
930
934
  */
931
- _getInsertDiff( parent, offset, name ) {
935
+ _getInsertDiff( parent, offset, elementSnapshot ) {
932
936
  return {
933
937
  type: 'insert',
934
938
  position: Position._createAt( parent, offset ),
935
- name,
939
+ name: elementSnapshot.name,
940
+ attributes: new Map( elementSnapshot.attributes ),
936
941
  length: 1,
937
942
  changeCount: this._changeCount++
938
943
  };
@@ -944,14 +949,15 @@ export default class Differ {
944
949
  * @private
945
950
  * @param {module:engine/model/element~Element} parent The element in which change happened.
946
951
  * @param {Number} offset The offset at which change happened.
947
- * @param {String} name The name of the removed element or `'$text'` for a character.
952
+ * @param {Object} elementSnapshot The snapshot of the removed element a character.
948
953
  * @returns {Object} The diff item.
949
954
  */
950
- _getRemoveDiff( parent, offset, name ) {
955
+ _getRemoveDiff( parent, offset, elementSnapshot ) {
951
956
  return {
952
957
  type: 'remove',
953
958
  position: Position._createAt( parent, offset ),
954
- name,
959
+ name: elementSnapshot.name,
960
+ attributes: new Map( elementSnapshot.attributes ),
955
961
  length: 1,
956
962
  changeCount: this._changeCount++
957
963
  };
@@ -1229,6 +1235,12 @@ function _changesInGraveyardFilter( entry ) {
1229
1235
  * @member {String} module:engine/model/differ~DiffItemInsert#name
1230
1236
  */
1231
1237
 
1238
+ /**
1239
+ * Map of attributes that were set on the item while it was inserted.
1240
+ *
1241
+ * @member {Map.<String,*>} module:engine/model/differ~DiffItemInsert#attributes
1242
+ */
1243
+
1232
1244
  /**
1233
1245
  * The position where the node was inserted.
1234
1246
  *
@@ -1260,6 +1272,12 @@ function _changesInGraveyardFilter( entry ) {
1260
1272
  * @member {String} module:engine/model/differ~DiffItemRemove#name
1261
1273
  */
1262
1274
 
1275
+ /**
1276
+ * Map of attributes that were set on the item while it was removed.
1277
+ *
1278
+ * @member {Map.<String,*>} module:engine/model/differ~DiffItemRemove#attributes
1279
+ */
1280
+
1263
1281
  /**
1264
1282
  * The position where the node was removed.
1265
1283
  *