@ckeditor/ckeditor5-engine 31.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.
Files changed (117) hide show
  1. package/LICENSE.md +2 -2
  2. package/package.json +25 -25
  3. package/src/controller/datacontroller.js +71 -80
  4. package/src/controller/editingcontroller.js +83 -6
  5. package/src/conversion/conversion.js +15 -14
  6. package/src/conversion/conversionhelpers.js +1 -1
  7. package/src/conversion/downcastdispatcher.js +298 -367
  8. package/src/conversion/downcasthelpers.js +771 -63
  9. package/src/conversion/mapper.js +105 -60
  10. package/src/conversion/modelconsumable.js +85 -35
  11. package/src/conversion/upcastdispatcher.js +3 -6
  12. package/src/conversion/upcasthelpers.js +4 -2
  13. package/src/conversion/viewconsumable.js +1 -1
  14. package/src/dataprocessor/basichtmlwriter.js +1 -1
  15. package/src/dataprocessor/dataprocessor.jsdoc +1 -1
  16. package/src/dataprocessor/htmldataprocessor.js +9 -31
  17. package/src/dataprocessor/htmlwriter.js +1 -1
  18. package/src/dataprocessor/xmldataprocessor.js +1 -1
  19. package/src/dev-utils/model.js +16 -14
  20. package/src/dev-utils/operationreplayer.js +1 -1
  21. package/src/dev-utils/utils.js +1 -1
  22. package/src/dev-utils/view.js +7 -7
  23. package/src/index.js +2 -1
  24. package/src/model/batch.js +77 -10
  25. package/src/model/differ.js +87 -59
  26. package/src/model/document.js +13 -4
  27. package/src/model/documentfragment.js +1 -1
  28. package/src/model/documentselection.js +1 -1
  29. package/src/model/element.js +1 -1
  30. package/src/model/history.js +1 -1
  31. package/src/model/item.jsdoc +1 -1
  32. package/src/model/liveposition.js +1 -1
  33. package/src/model/liverange.js +1 -1
  34. package/src/model/markercollection.js +29 -5
  35. package/src/model/model.js +18 -9
  36. package/src/model/node.js +1 -1
  37. package/src/model/nodelist.js +1 -1
  38. package/src/model/operation/attributeoperation.js +1 -1
  39. package/src/model/operation/detachoperation.js +1 -1
  40. package/src/model/operation/insertoperation.js +1 -1
  41. package/src/model/operation/markeroperation.js +1 -1
  42. package/src/model/operation/mergeoperation.js +1 -1
  43. package/src/model/operation/moveoperation.js +1 -1
  44. package/src/model/operation/nooperation.js +1 -1
  45. package/src/model/operation/operation.js +1 -1
  46. package/src/model/operation/operationfactory.js +1 -1
  47. package/src/model/operation/renameoperation.js +1 -1
  48. package/src/model/operation/rootattributeoperation.js +1 -1
  49. package/src/model/operation/splitoperation.js +1 -1
  50. package/src/model/operation/transform.js +1 -1
  51. package/src/model/operation/utils.js +1 -1
  52. package/src/model/position.js +1 -1
  53. package/src/model/range.js +1 -1
  54. package/src/model/rootelement.js +1 -1
  55. package/src/model/schema.js +1 -1
  56. package/src/model/selection.js +1 -1
  57. package/src/model/text.js +1 -1
  58. package/src/model/textproxy.js +1 -1
  59. package/src/model/treewalker.js +1 -1
  60. package/src/model/utils/autoparagraphing.js +1 -1
  61. package/src/model/utils/deletecontent.js +1 -1
  62. package/src/model/utils/getselectedcontent.js +1 -1
  63. package/src/model/utils/insertcontent.js +1 -1
  64. package/src/model/utils/modifyselection.js +15 -8
  65. package/src/model/utils/selection-post-fixer.js +37 -30
  66. package/src/model/writer.js +17 -27
  67. package/src/view/attributeelement.js +1 -1
  68. package/src/view/containerelement.js +1 -1
  69. package/src/view/document.js +3 -2
  70. package/src/view/documentfragment.js +1 -1
  71. package/src/view/documentselection.js +1 -1
  72. package/src/view/domconverter.js +169 -47
  73. package/src/view/downcastwriter.js +121 -5
  74. package/src/view/editableelement.js +1 -1
  75. package/src/view/element.js +29 -1
  76. package/src/view/elementdefinition.jsdoc +1 -1
  77. package/src/view/emptyelement.js +1 -1
  78. package/src/view/filler.js +1 -1
  79. package/src/view/item.jsdoc +1 -1
  80. package/src/view/matcher.js +15 -14
  81. package/src/view/node.js +1 -1
  82. package/src/view/observer/arrowkeysobserver.js +1 -1
  83. package/src/view/observer/bubblingemittermixin.js +1 -1
  84. package/src/view/observer/bubblingeventinfo.js +1 -1
  85. package/src/view/observer/clickobserver.js +1 -1
  86. package/src/view/observer/compositionobserver.js +1 -1
  87. package/src/view/observer/domeventdata.js +1 -1
  88. package/src/view/observer/domeventobserver.js +1 -1
  89. package/src/view/observer/fakeselectionobserver.js +1 -1
  90. package/src/view/observer/focusobserver.js +1 -1
  91. package/src/view/observer/inputobserver.js +1 -1
  92. package/src/view/observer/keyobserver.js +1 -1
  93. package/src/view/observer/mouseobserver.js +1 -1
  94. package/src/view/observer/mutationobserver.js +1 -1
  95. package/src/view/observer/observer.js +1 -1
  96. package/src/view/observer/selectionobserver.js +3 -3
  97. package/src/view/placeholder.js +1 -1
  98. package/src/view/position.js +1 -1
  99. package/src/view/range.js +1 -1
  100. package/src/view/rawelement.js +1 -1
  101. package/src/view/renderer.js +7 -17
  102. package/src/view/rooteditableelement.js +1 -1
  103. package/src/view/selection.js +1 -1
  104. package/src/view/styles/background.js +1 -1
  105. package/src/view/styles/border.js +1 -1
  106. package/src/view/styles/margin.js +1 -1
  107. package/src/view/styles/padding.js +1 -1
  108. package/src/view/styles/utils.js +1 -1
  109. package/src/view/stylesmap.js +1 -1
  110. package/src/view/text.js +1 -1
  111. package/src/view/textproxy.js +1 -1
  112. package/src/view/treewalker.js +1 -1
  113. package/src/view/uielement.js +1 -1
  114. package/src/view/upcastwriter.js +1 -1
  115. package/src/view/view.js +1 -1
  116. package/theme/placeholder.css +10 -1
  117. package/theme/renderer.css +2 -2
@@ -1,5 +1,5 @@
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
 
@@ -1,5 +1,5 @@
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
 
@@ -1,5 +1,5 @@
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
 
@@ -7,7 +7,7 @@
7
7
  * @module engine/dataprocessor/htmldataprocessor
8
8
  */
9
9
 
10
- /* globals document, DOMParser, Node */
10
+ /* globals document, DOMParser */
11
11
 
12
12
  import BasicHtmlWriter from './basichtmlwriter';
13
13
  import DomConverter from '../view/domconverter';
@@ -114,37 +114,15 @@ export default class HtmlDataProcessor {
114
114
  * @returns {DocumentFragment}
115
115
  */
116
116
  _toDom( data ) {
117
- const document = this.domParser.parseFromString( data, 'text/html' );
118
- const fragment = document.createDocumentFragment();
119
-
120
- // The rules for parsing an HTML string can be read on https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inhtml.
121
- //
122
- // In short, parsing tokens in an HTML string starts with the so-called "initial" insertion mode. When a DOM parser is in this
123
- // state and encounters a comment node, it inserts this comment node as the last child of the newly-created `HTMLDocument` object.
124
- // The parser then proceeds to successive insertion modes during parsing subsequent tokens and appends in the `HTMLDocument` object
125
- // other nodes (like <html>, <head>, <body>). This causes that the first leading comments from HTML string become the first nodes
126
- // in the `HTMLDocument` object, but not in the <body> collection, because they are ultimately located before the <html> element.
127
- //
128
- // Therefore, so that such leading comments do not disappear, they all are moved from the `HTMLDocument` object to the document
129
- // fragment, until the <html> element is encountered.
130
- //
131
- // See: https://github.com/ckeditor/ckeditor5/issues/9861.
132
- let documentChildNode = document.firstChild;
133
-
134
- while ( !documentChildNode.isSameNode( document.documentElement ) ) {
135
- const node = documentChildNode;
136
-
137
- documentChildNode = documentChildNode.nextSibling;
138
-
139
- // It seems that `DOMParser#parseFromString()` adds only comment nodes directly to the `HTMLDocument` object, before the <html>
140
- // node. The condition below is just to be sure we are moving only comment nodes.
141
-
142
- /* istanbul ignore else */
143
- if ( node.nodeType == Node.COMMENT_NODE ) {
144
- fragment.appendChild( node );
145
- }
117
+ // Wrap data with a <body> tag so leading non-layout nodes (like <script>, <style>, HTML comment)
118
+ // will be preserved in the body collection.
119
+ // Do it only for data that is not a full HTML document.
120
+ if ( !data.match( /<(?:html|body|head|meta)(?:\s[^>]*)?>/i ) ) {
121
+ data = `<body>${ data }</body>`;
146
122
  }
147
123
 
124
+ const document = this.domParser.parseFromString( data, 'text/html' );
125
+ const fragment = document.createDocumentFragment();
148
126
  const bodyChildNodes = document.body.childNodes;
149
127
 
150
128
  while ( bodyChildNodes.length > 0 ) {
@@ -1,5 +1,5 @@
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
 
@@ -1,5 +1,5 @@
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
 
@@ -1,5 +1,5 @@
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
 
@@ -31,6 +31,7 @@ import Mapper from '../conversion/mapper';
31
31
  import {
32
32
  convertCollapsedSelection,
33
33
  convertRangeSelection,
34
+ insertAttributesAndChildren,
34
35
  insertElement,
35
36
  insertText,
36
37
  insertUIElement,
@@ -102,7 +103,7 @@ getData._stringify = stringify;
102
103
  * name will be used.
103
104
  * @param {Array<Object>} [options.selectionAttributes] A list of attributes which will be passed to the selection.
104
105
  * @param {Boolean} [options.lastRangeBackward=false] If set to `true`, the last range will be added as backward.
105
- * @param {String} [options.batchType='default'] Batch type used for inserting elements.
106
+ * @param {Object} [options.batchType] Batch type used for inserting elements. See {@link module:engine/model/batch~Batch#constructor}.
106
107
  * See {@link module:engine/model/batch~Batch#type}.
107
108
  */
108
109
  export function setData( model, data, options = {} ) {
@@ -128,7 +129,7 @@ export function setData( model, data, options = {} ) {
128
129
  modelDocumentFragment = parsedResult;
129
130
  }
130
131
 
131
- if ( typeof options.batchType === 'string' ) {
132
+ if ( options.batchType !== undefined ) {
132
133
  model.enqueueChange( options.batchType, writeToModel );
133
134
  } else {
134
135
  model.change( writeToModel );
@@ -233,6 +234,7 @@ export function stringify( node, selectionOrPositionOrRange = null, markers = nu
233
234
  mapper.bindElements( node.root, viewRoot );
234
235
 
235
236
  downcastDispatcher.on( 'insert:$text', insertText() );
237
+ downcastDispatcher.on( 'insert', insertAttributesAndChildren(), { priority: 'lowest' } );
236
238
  downcastDispatcher.on( 'attribute', ( evt, data, conversionApi ) => {
237
239
  if ( data.item instanceof ModelSelection || data.item instanceof DocumentSelection || data.item.is( '$textProxy' ) ) {
238
240
  const converter = wrap( ( modelAttributeValue, { writer } ) => {
@@ -260,28 +262,28 @@ export function stringify( node, selectionOrPositionOrRange = null, markers = nu
260
262
  return writer.createUIElement( name );
261
263
  } ) );
262
264
 
265
+ const markersMap = new Map();
266
+
267
+ if ( markers ) {
268
+ // To provide stable results, sort markers by name.
269
+ for ( const marker of Array.from( markers ).sort( ( a, b ) => a.name < b.name ? 1 : -1 ) ) {
270
+ markersMap.set( marker.name, marker.getRange() );
271
+ }
272
+ }
273
+
263
274
  // Convert model to view.
264
275
  const writer = view._writer;
265
- downcastDispatcher.convertInsert( range, writer );
276
+ downcastDispatcher.convert( range, markersMap, writer );
266
277
 
267
278
  // Convert model selection to view selection.
268
279
  if ( selection ) {
269
280
  downcastDispatcher.convertSelection( selection, markers || model.markers, writer );
270
281
  }
271
282
 
272
- if ( markers ) {
273
- // To provide stable results, sort markers by name.
274
- markers = Array.from( markers ).sort( ( a, b ) => a.name < b.name ? 1 : -1 );
275
-
276
- for ( const marker of markers ) {
277
- downcastDispatcher.convertMarkerAdd( marker.name, marker.getRange(), writer );
278
- }
279
- }
280
-
281
283
  // Parse view to data string.
282
284
  let data = viewStringify( viewRoot, viewDocument.selection, { sameSelectionCharacters: true } );
283
285
 
284
- // Removing unneccessary <div> and </div> added because `viewRoot` was also stringified alongside input data.
286
+ // Removing unnecessary <div> and </div> added because `viewRoot` was also stringified alongside input data.
285
287
  data = data.substr( 5, data.length - 11 );
286
288
 
287
289
  view.destroy();
@@ -1,5 +1,5 @@
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
 
@@ -1,5 +1,5 @@
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
 
@@ -1,5 +1,5 @@
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
 
@@ -67,8 +67,8 @@ const domConverterStub = {
67
67
  * @param {Boolean} [options.renderRawElements=false] When set to `true`, the inner content of each
68
68
  * {@link module:engine/view/rawelement~RawElement} will be printed.
69
69
  * @param {Object} [options.domConverter=null] When set to an actual {@link module:engine/view/domconverter~DomConverter DomConverter}
70
- * instance it lets the conversion go through exactly the same flow the editing view is going, i.e. with view data
71
- * filtering. Otherwise the simple stub is used.
70
+ * instance, it lets the conversion go through exactly the same flow the editing view is going through,
71
+ * i.e. with view data filtering. Otherwise the simple stub is used.
72
72
  * @returns {String} The stringified data.
73
73
  */
74
74
  export function getData( view, options = {} ) {
@@ -253,8 +253,8 @@ setData._parse = parse;
253
253
  * @param {Boolean} [options.renderRawElements=false] When set to `true`, the inner content of each
254
254
  * {@link module:engine/view/rawelement~RawElement} will be printed.
255
255
  * @param {Object} [options.domConverter={}] When set to an actual {@link module:engine/view/domconverter~DomConverter DomConverter}
256
- * instance it lets the conversion go through exactly the same flow the editing view is going, i.e. with view data
257
- * filtering. Otherwise the simple stub is used.
256
+ * instance, it lets the conversion go through exactly the same flow the editing view is going through,
257
+ * i.e. with view data filtering. Otherwise the simple stub is used.
258
258
  * @returns {String} An HTML-like string representing the view.
259
259
  */
260
260
  export function stringify( node, selectionOrPositionOrRange = null, options = {} ) {
@@ -645,8 +645,8 @@ class ViewStringify {
645
645
  * {@link module:engine/view/uielement~UIElement} will be printed.
646
646
  * @param {Boolean} [options.renderRawElements=false] When set to `true`, the inner content of each
647
647
  * @param {Object} [options.domConverter={}] When set to an actual {@link module:engine/view/domconverter~DomConverter DomConverter}
648
- * instance it lets the conversion go through exactly the same flow the editing view is going, i.e. with view data
649
- * filtering. Otherwise the simple stub is used.
648
+ * instance, it lets the conversion go through exactly the same flow the editing view is going through,
649
+ * i.e. with view data filtering. Otherwise the simple stub is used.
650
650
  * {@link module:engine/view/rawelement~RawElement} will be printed.
651
651
  */
652
652
  constructor( root, selection, options ) {
package/src/index.js CHANGED
@@ -1,5 +1,5 @@
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
 
@@ -28,6 +28,7 @@ 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 History } from './model/history';
31
32
 
32
33
  export { default as DomConverter } from './view/domconverter';
33
34
  export { default as Renderer } from './view/renderer';
@@ -1,5 +1,5 @@
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
 
@@ -7,6 +7,8 @@
7
7
  * @module engine/model/batch
8
8
  */
9
9
 
10
+ import { logWarning } from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
11
+
10
12
  /**
11
13
  * A batch instance groups model changes ({@link module:engine/model/operation/operation~Operation operations}). All operations
12
14
  * grouped in a single batch can be reverted together, so you can also think about a batch as of a single undo step. If you want
@@ -25,9 +27,30 @@ export default class Batch {
25
27
  *
26
28
  * @see module:engine/model/model~Model#enqueueChange
27
29
  * @see module:engine/model/model~Model#change
28
- * @param {'transparent'|'default'} [type='default'] The type of the batch.
30
+ * @param {Object} [type] A set of flags that specify the type of the batch. Batch type can alter how some of the features work
31
+ * when encountering a given `Batch` instance (for example, when a feature listens to applied operations).
32
+ * @param {Boolean} [type.isUndoable=true] Whether a batch can be undone through undo feature.
33
+ * @param {Boolean} [type.isLocal=true] Whether a batch includes operations created locally (`true`) or operations created on
34
+ * other, remote editors (`false`).
35
+ * @param {Boolean} [type.isUndo=false] Whether a batch was created by the undo feature and undoes other operations.
36
+ * @param {Boolean} [type.isTyping=false] Whether a batch includes operations connected with a typing action.
29
37
  */
30
- constructor( type = 'default' ) {
38
+ constructor( type = {} ) {
39
+ if ( typeof type === 'string' ) {
40
+ type = type === 'transparent' ? { isUndoable: false } : {};
41
+
42
+ /**
43
+ * The string value for a `type` property of the `Batch` constructor has been deprecated and will be removed in the near future.
44
+ * Please refer to the {@link module:engine/model/batch~Batch#constructor `Batch` constructor API documentation} for more
45
+ * information.
46
+ *
47
+ * @error batch-constructor-deprecated-string-type
48
+ */
49
+ logWarning( 'batch-constructor-deprecated-string-type' );
50
+ }
51
+
52
+ const { isUndoable = true, isLocal = true, isUndo = false, isTyping = false } = type;
53
+
31
54
  /**
32
55
  * An array of operations that compose this batch.
33
56
  *
@@ -37,17 +60,61 @@ export default class Batch {
37
60
  this.operations = [];
38
61
 
39
62
  /**
40
- * The type of the batch.
63
+ * Whether the batch can be undone through the undo feature.
64
+ *
65
+ * @readonly
66
+ * @type {Boolean}
67
+ */
68
+ this.isUndoable = isUndoable;
69
+
70
+ /**
71
+ * Whether the batch includes operations created locally (`true`) or operations created on other, remote editors (`false`).
72
+ *
73
+ * @readonly
74
+ * @type {Boolean}
75
+ */
76
+ this.isLocal = isLocal;
77
+
78
+ /**
79
+ * Whether the batch was created by the undo feature and undoes other operations.
41
80
  *
42
- * It can be one of the following values:
43
- * * `'default'` &ndash; All "normal" batches. This is the most commonly used type.
44
- * * `'transparent'` &ndash; A batch that should be ignored by other features, i.e. an initial batch or collaborative editing
45
- * changes.
81
+ * @readonly
82
+ * @type {Boolean}
83
+ */
84
+ this.isUndo = isUndo;
85
+
86
+ /**
87
+ * Whether the batch includes operations connected with typing.
46
88
  *
47
89
  * @readonly
48
- * @type {'transparent'|'default'}
90
+ * @type {Boolean}
49
91
  */
50
- this.type = type;
92
+ this.isTyping = isTyping;
93
+ }
94
+
95
+ /**
96
+ * The type of the batch.
97
+ *
98
+ * **This property has been deprecated and is always set to the `'default'` value.**
99
+ *
100
+ * It can be one of the following values:
101
+ * * `'default'` &ndash; All "normal" batches. This is the most commonly used type.
102
+ * * `'transparent'` &ndash; A batch that should be ignored by other features, i.e. an initial batch or collaborative editing
103
+ * changes.
104
+ *
105
+ * @deprecated
106
+ * @type {'default'}
107
+ */
108
+ get type() {
109
+ /**
110
+ * The {@link module:engine/model/batch~Batch#type `Batch#type` } property has been deprecated and will be removed in the near
111
+ * future. Use `Batch#isLocal`, `Batch#isUndoable`, `Batch#isUndo` and `Batch#isTyping` instead.
112
+ *
113
+ * @error batch-type-deprecated
114
+ */
115
+ logWarning( 'batch-type-deprecated' );
116
+
117
+ return 'default';
51
118
  }
52
119
 
53
120
  /**
@@ -1,5 +1,5 @@
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
 
@@ -58,11 +58,12 @@ export default class Differ {
58
58
  * A map that stores all changed markers.
59
59
  *
60
60
  * The keys of the map are marker names.
61
- * The values of the map are objects with the `oldRange` and `newRange` properties. They store the marker range
62
- * state before and after the change.
61
+ * The values of the map are objects with the following properties:
62
+ * - `oldMarkerData`,
63
+ * - `newMarkerData`.
63
64
  *
64
65
  * @private
65
- * @type {Map}
66
+ * @type {Map.<String, Object>}
66
67
  */
67
68
  this._changedMarkers = new Map();
68
69
 
@@ -98,6 +99,14 @@ export default class Differ {
98
99
  * @type {Array.<Object>|null}
99
100
  */
100
101
  this._cachedChangesWithGraveyard = null;
102
+
103
+ /**
104
+ * Set of model items that were marked to get refreshed in {@link #_refreshItem}.
105
+ *
106
+ * @private
107
+ * @type {Set.<module:engine/model/item~Item>}
108
+ */
109
+ this._refreshedItems = new Set();
101
110
  }
102
111
 
103
112
  /**
@@ -110,32 +119,6 @@ export default class Differ {
110
119
  return this._changesInElement.size == 0 && this._changedMarkers.size == 0;
111
120
  }
112
121
 
113
- /**
114
- * Marks given `item` in differ to be "refreshed". It means that the item will be marked as removed and inserted in the differ changes
115
- * set, so it will be effectively re-converted when differ changes will be handled by a dispatcher.
116
- *
117
- * @param {module:engine/model/item~Item} item Item to refresh.
118
- */
119
- refreshItem( item ) {
120
- if ( this._isInInsertedElement( item.parent ) ) {
121
- return;
122
- }
123
-
124
- this._markRemove( item.parent, item.startOffset, item.offsetSize );
125
- this._markInsert( item.parent, item.startOffset, item.offsetSize );
126
-
127
- const range = Range._createOn( item );
128
-
129
- for ( const marker of this._markerCollection.getMarkersIntersectingRange( range ) ) {
130
- const markerRange = marker.getRange();
131
-
132
- this.bufferMarkerChange( marker.name, markerRange, markerRange, marker.affectsData );
133
- }
134
-
135
- // Clear cache after each buffered operation as it is no longer valid.
136
- this._cachedChanges = null;
137
- }
138
-
139
122
  /**
140
123
  * Buffers the given operation. An operation has to be buffered before it is executed.
141
124
  *
@@ -208,9 +191,9 @@ export default class Differ {
208
191
  const range = Range._createFromPositionAndShift( operation.position, 1 );
209
192
 
210
193
  for ( const marker of this._markerCollection.getMarkersIntersectingRange( range ) ) {
211
- const markerRange = marker.getRange();
194
+ const markerData = marker.getData();
212
195
 
213
- this.bufferMarkerChange( marker.name, markerRange, markerRange, marker.affectsData );
196
+ this.bufferMarkerChange( marker.name, markerData, markerData );
214
197
  }
215
198
 
216
199
  break;
@@ -267,27 +250,23 @@ export default class Differ {
267
250
  * Buffers a marker change.
268
251
  *
269
252
  * @param {String} markerName The name of the marker that changed.
270
- * @param {module:engine/model/range~Range|null} oldRange Marker range before the change or `null` if the marker has just
271
- * been created.
272
- * @param {module:engine/model/range~Range|null} newRange Marker range after the change or `null` if the marker was removed.
273
- * @param {Boolean} affectsData Flag indicating whether marker affects the editor data.
253
+ * @param {module:engine/model/markercollection~MarkerData} oldMarkerData Marker data before the change.
254
+ * @param {module:engine/model/markercollection~MarkerData} newMarkerData Marker data after the change.
274
255
  */
275
- bufferMarkerChange( markerName, oldRange, newRange, affectsData ) {
256
+ bufferMarkerChange( markerName, oldMarkerData, newMarkerData ) {
276
257
  const buffered = this._changedMarkers.get( markerName );
277
258
 
278
259
  if ( !buffered ) {
279
260
  this._changedMarkers.set( markerName, {
280
- oldRange,
281
- newRange,
282
- affectsData
261
+ newMarkerData,
262
+ oldMarkerData
283
263
  } );
284
264
  } else {
285
- buffered.newRange = newRange;
286
- buffered.affectsData = affectsData;
265
+ buffered.newMarkerData = newMarkerData;
287
266
 
288
- if ( buffered.oldRange == null && buffered.newRange == null ) {
289
- // The marker is going to be removed (`newRange == null`) but it did not exist before the first buffered change
290
- // (`buffered.oldRange == null`). In this case, do not keep the marker in buffer at all.
267
+ if ( buffered.oldMarkerData.range == null && newMarkerData.range == null ) {
268
+ // The marker is going to be removed (`newMarkerData.range == null`) but it did not exist before the first buffered change
269
+ // (`buffered.oldMarkerData.range == null`). In this case, do not keep the marker in buffer at all.
291
270
  this._changedMarkers.delete( markerName );
292
271
  }
293
272
  }
@@ -302,8 +281,8 @@ export default class Differ {
302
281
  const result = [];
303
282
 
304
283
  for ( const [ name, change ] of this._changedMarkers ) {
305
- if ( change.oldRange != null ) {
306
- result.push( { name, range: change.oldRange } );
284
+ if ( change.oldMarkerData.range != null ) {
285
+ result.push( { name, range: change.oldMarkerData.range } );
307
286
  }
308
287
  }
309
288
 
@@ -319,8 +298,8 @@ export default class Differ {
319
298
  const result = [];
320
299
 
321
300
  for ( const [ name, change ] of this._changedMarkers ) {
322
- if ( change.newRange != null ) {
323
- result.push( { name, range: change.newRange } );
301
+ if ( change.newMarkerData.range != null ) {
302
+ result.push( { name, range: change.newMarkerData.range } );
324
303
  }
325
304
  }
326
305
 
@@ -333,12 +312,12 @@ export default class Differ {
333
312
  * @returns {Array.<Object>}
334
313
  */
335
314
  getChangedMarkers() {
336
- return Array.from( this._changedMarkers ).map( item => (
315
+ return Array.from( this._changedMarkers ).map( ( [ name, change ] ) => (
337
316
  {
338
- name: item[ 0 ],
317
+ name,
339
318
  data: {
340
- oldRange: item[ 1 ].oldRange,
341
- newRange: item[ 1 ].newRange
319
+ oldRange: change.oldMarkerData.range,
320
+ newRange: change.newMarkerData.range
342
321
  }
343
322
  }
344
323
  ) );
@@ -351,13 +330,23 @@ export default class Differ {
351
330
  *
352
331
  * * model structure changes,
353
332
  * * attribute changes,
354
- * * changes of markers which were defined as `affectingData`.
333
+ * * changes of markers which were defined as `affectsData`,
334
+ * * changes of markers' `affectsData` property.
355
335
  *
356
336
  * @returns {Boolean}
357
337
  */
358
338
  hasDataChanges() {
359
- for ( const [ , change ] of this._changedMarkers ) {
360
- if ( change.affectsData ) {
339
+ for ( const { newMarkerData, oldMarkerData } of this._changedMarkers.values() ) {
340
+ if ( newMarkerData.affectsData !== oldMarkerData.affectsData ) {
341
+ return true;
342
+ }
343
+
344
+ 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
+
361
350
  return true;
362
351
  }
363
352
  }
@@ -540,16 +529,25 @@ export default class Differ {
540
529
  this._changeCount = 0;
541
530
 
542
531
  // Cache changes.
543
- this._cachedChangesWithGraveyard = diffSet.slice();
532
+ this._cachedChangesWithGraveyard = diffSet;
544
533
  this._cachedChanges = diffSet.filter( _changesInGraveyardFilter );
545
534
 
546
535
  if ( options.includeChangesInGraveyard ) {
547
- return this._cachedChangesWithGraveyard;
536
+ return this._cachedChangesWithGraveyard.slice();
548
537
  } else {
549
- return this._cachedChanges;
538
+ return this._cachedChanges.slice();
550
539
  }
551
540
  }
552
541
 
542
+ /**
543
+ * Returns a set of model items that were marked to get refreshed.
544
+ *
545
+ * @return {Set.<module:engine/model/item~Item>}
546
+ */
547
+ getRefreshedItems() {
548
+ return new Set( this._refreshedItems );
549
+ }
550
+
553
551
  /**
554
552
  * Resets `Differ`. Removes all buffered changes.
555
553
  */
@@ -557,6 +555,36 @@ export default class Differ {
557
555
  this._changesInElement.clear();
558
556
  this._elementSnapshots.clear();
559
557
  this._changedMarkers.clear();
558
+ this._refreshedItems = new Set();
559
+ this._cachedChanges = null;
560
+ }
561
+
562
+ /**
563
+ * Marks the given `item` in differ to be "refreshed". It means that the item will be marked as removed and inserted
564
+ * in the differ changes set, so it will be effectively re-converted when the differ changes are handled by a dispatcher.
565
+ *
566
+ * @protected
567
+ * @param {module:engine/model/item~Item} item Item to refresh.
568
+ */
569
+ _refreshItem( item ) {
570
+ if ( this._isInInsertedElement( item.parent ) ) {
571
+ return;
572
+ }
573
+
574
+ this._markRemove( item.parent, item.startOffset, item.offsetSize );
575
+ this._markInsert( item.parent, item.startOffset, item.offsetSize );
576
+
577
+ this._refreshedItems.add( item );
578
+
579
+ const range = Range._createOn( item );
580
+
581
+ for ( const marker of this._markerCollection.getMarkersIntersectingRange( range ) ) {
582
+ const markerData = marker.getData();
583
+
584
+ this.bufferMarkerChange( marker.name, markerData, markerData );
585
+ }
586
+
587
+ // Clear cache after each buffered operation as it is no longer valid.
560
588
  this._cachedChanges = null;
561
589
  }
562
590
 
@@ -1,5 +1,5 @@
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
 
@@ -157,14 +157,23 @@ export default class Document {
157
157
  // Buffer marker changes.
158
158
  // This is not covered in buffering operations because markers may change outside of them (when they
159
159
  // are modified using `model.markers` collection, not through `MarkerOperation`).
160
- this.listenTo( model.markers, 'update', ( evt, marker, oldRange, newRange ) => {
160
+ this.listenTo( model.markers, 'update', ( evt, marker, oldRange, newRange, oldMarkerData ) => {
161
+ // Copy the `newRange` to the new marker data as during the marker removal the range is not updated.
162
+ const newMarkerData = { ...marker.getData(), range: newRange };
163
+
161
164
  // Whenever marker is updated, buffer that change.
162
- this.differ.bufferMarkerChange( marker.name, oldRange, newRange, marker.affectsData );
165
+ this.differ.bufferMarkerChange( marker.name, oldMarkerData, newMarkerData );
163
166
 
164
167
  if ( oldRange === null ) {
165
168
  // If this is a new marker, add a listener that will buffer change whenever marker changes.
166
169
  marker.on( 'change', ( evt, oldRange ) => {
167
- this.differ.bufferMarkerChange( marker.name, oldRange, marker.getRange(), marker.affectsData );
170
+ const markerData = marker.getData();
171
+
172
+ this.differ.bufferMarkerChange(
173
+ marker.name,
174
+ { ...markerData, range: oldRange },
175
+ markerData
176
+ );
168
177
  } );
169
178
  }
170
179
  } );
@@ -1,5 +1,5 @@
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
 
@@ -1,5 +1,5 @@
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