@ckeditor/ckeditor5-engine 34.2.0 → 35.1.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 (125) hide show
  1. package/CHANGELOG.md +823 -0
  2. package/LICENSE.md +4 -0
  3. package/package.json +32 -25
  4. package/src/controller/datacontroller.js +467 -561
  5. package/src/controller/editingcontroller.js +168 -204
  6. package/src/conversion/conversion.js +541 -565
  7. package/src/conversion/conversionhelpers.js +24 -28
  8. package/src/conversion/downcastdispatcher.js +457 -686
  9. package/src/conversion/downcasthelpers.js +1583 -1965
  10. package/src/conversion/mapper.js +518 -707
  11. package/src/conversion/modelconsumable.js +240 -283
  12. package/src/conversion/upcastdispatcher.js +372 -718
  13. package/src/conversion/upcasthelpers.js +707 -818
  14. package/src/conversion/viewconsumable.js +524 -581
  15. package/src/dataprocessor/basichtmlwriter.js +12 -16
  16. package/src/dataprocessor/dataprocessor.js +5 -0
  17. package/src/dataprocessor/htmldataprocessor.js +101 -117
  18. package/src/dataprocessor/htmlwriter.js +1 -18
  19. package/src/dataprocessor/xmldataprocessor.js +117 -138
  20. package/src/dev-utils/model.js +260 -352
  21. package/src/dev-utils/operationreplayer.js +106 -126
  22. package/src/dev-utils/utils.js +34 -51
  23. package/src/dev-utils/view.js +632 -753
  24. package/src/index.js +0 -11
  25. package/src/model/batch.js +111 -127
  26. package/src/model/differ.js +988 -1233
  27. package/src/model/document.js +340 -449
  28. package/src/model/documentfragment.js +327 -364
  29. package/src/model/documentselection.js +996 -1189
  30. package/src/model/element.js +306 -410
  31. package/src/model/history.js +224 -262
  32. package/src/model/item.js +5 -0
  33. package/src/model/liveposition.js +84 -145
  34. package/src/model/liverange.js +108 -185
  35. package/src/model/markercollection.js +379 -480
  36. package/src/model/model.js +883 -1034
  37. package/src/model/node.js +419 -463
  38. package/src/model/nodelist.js +175 -201
  39. package/src/model/operation/attributeoperation.js +153 -182
  40. package/src/model/operation/detachoperation.js +64 -83
  41. package/src/model/operation/insertoperation.js +135 -166
  42. package/src/model/operation/markeroperation.js +114 -140
  43. package/src/model/operation/mergeoperation.js +163 -191
  44. package/src/model/operation/moveoperation.js +157 -187
  45. package/src/model/operation/nooperation.js +28 -38
  46. package/src/model/operation/operation.js +106 -125
  47. package/src/model/operation/operationfactory.js +30 -34
  48. package/src/model/operation/renameoperation.js +109 -135
  49. package/src/model/operation/rootattributeoperation.js +155 -188
  50. package/src/model/operation/splitoperation.js +196 -232
  51. package/src/model/operation/transform.js +1833 -2204
  52. package/src/model/operation/utils.js +140 -204
  53. package/src/model/position.js +899 -1053
  54. package/src/model/range.js +910 -1028
  55. package/src/model/rootelement.js +77 -97
  56. package/src/model/schema.js +1189 -1835
  57. package/src/model/selection.js +745 -862
  58. package/src/model/text.js +90 -114
  59. package/src/model/textproxy.js +204 -240
  60. package/src/model/treewalker.js +316 -397
  61. package/src/model/typecheckable.js +16 -0
  62. package/src/model/utils/autoparagraphing.js +32 -44
  63. package/src/model/utils/deletecontent.js +334 -418
  64. package/src/model/utils/findoptimalinsertionrange.js +25 -36
  65. package/src/model/utils/getselectedcontent.js +96 -118
  66. package/src/model/utils/insertcontent.js +654 -773
  67. package/src/model/utils/insertobject.js +96 -119
  68. package/src/model/utils/modifyselection.js +120 -158
  69. package/src/model/utils/selection-post-fixer.js +153 -201
  70. package/src/model/writer.js +1305 -1474
  71. package/src/view/attributeelement.js +189 -225
  72. package/src/view/containerelement.js +75 -85
  73. package/src/view/document.js +172 -215
  74. package/src/view/documentfragment.js +200 -249
  75. package/src/view/documentselection.js +338 -367
  76. package/src/view/domconverter.js +1371 -1613
  77. package/src/view/downcastwriter.js +1747 -2076
  78. package/src/view/editableelement.js +81 -97
  79. package/src/view/element.js +739 -890
  80. package/src/view/elementdefinition.js +5 -0
  81. package/src/view/emptyelement.js +82 -92
  82. package/src/view/filler.js +35 -50
  83. package/src/view/item.js +5 -0
  84. package/src/view/matcher.js +260 -559
  85. package/src/view/node.js +274 -360
  86. package/src/view/observer/arrowkeysobserver.js +19 -28
  87. package/src/view/observer/bubblingemittermixin.js +120 -263
  88. package/src/view/observer/bubblingeventinfo.js +47 -55
  89. package/src/view/observer/clickobserver.js +7 -13
  90. package/src/view/observer/compositionobserver.js +14 -24
  91. package/src/view/observer/domeventdata.js +57 -67
  92. package/src/view/observer/domeventobserver.js +40 -64
  93. package/src/view/observer/fakeselectionobserver.js +81 -96
  94. package/src/view/observer/focusobserver.js +45 -61
  95. package/src/view/observer/inputobserver.js +7 -13
  96. package/src/view/observer/keyobserver.js +17 -27
  97. package/src/view/observer/mouseobserver.js +7 -14
  98. package/src/view/observer/mutationobserver.js +220 -315
  99. package/src/view/observer/observer.js +81 -102
  100. package/src/view/observer/selectionobserver.js +191 -246
  101. package/src/view/observer/tabobserver.js +23 -36
  102. package/src/view/placeholder.js +128 -173
  103. package/src/view/position.js +350 -401
  104. package/src/view/range.js +453 -513
  105. package/src/view/rawelement.js +85 -112
  106. package/src/view/renderer.js +874 -1014
  107. package/src/view/rooteditableelement.js +80 -90
  108. package/src/view/selection.js +608 -689
  109. package/src/view/styles/background.js +43 -44
  110. package/src/view/styles/border.js +220 -276
  111. package/src/view/styles/margin.js +8 -17
  112. package/src/view/styles/padding.js +8 -16
  113. package/src/view/styles/utils.js +127 -160
  114. package/src/view/stylesmap.js +728 -905
  115. package/src/view/text.js +102 -126
  116. package/src/view/textproxy.js +144 -170
  117. package/src/view/treewalker.js +383 -479
  118. package/src/view/typecheckable.js +19 -0
  119. package/src/view/uielement.js +166 -187
  120. package/src/view/upcastwriter.js +395 -449
  121. package/src/view/view.js +569 -664
  122. package/src/dataprocessor/dataprocessor.jsdoc +0 -64
  123. package/src/model/item.jsdoc +0 -14
  124. package/src/view/elementdefinition.jsdoc +0 -59
  125. package/src/view/item.jsdoc +0 -14
@@ -2,14 +2,11 @@
2
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
-
6
5
  /**
7
6
  * @module engine/conversion/modelconsumable
8
7
  */
9
-
10
8
  import TextProxy from '../model/textproxy';
11
9
  import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
12
-
13
10
  /**
14
11
  * Manages a list of consumable values for the {@link module:engine/model/item~Item model items}.
15
12
  *
@@ -89,291 +86,251 @@ import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
89
86
  * } );
90
87
  */
91
88
  export default class ModelConsumable {
92
- /**
93
- * Creates an empty consumables list.
94
- */
95
- constructor() {
96
- /**
97
- * Contains list of consumable values.
98
- *
99
- * @private
100
- * @member {Map} module:engine/conversion/modelconsumable~ModelConsumable#_consumable
101
- */
102
- this._consumable = new Map();
103
-
104
- /**
105
- * For each {@link module:engine/model/textproxy~TextProxy} added to `ModelConsumable`, this registry holds a parent
106
- * of that `TextProxy` and the start and end indices of that `TextProxy`. This allows identification of the `TextProxy`
107
- * instances that point to the same part of the model but are different instances. Each distinct `TextProxy`
108
- * is given a unique `Symbol` which is then registered as consumable. This process is transparent for the `ModelConsumable`
109
- * API user because whenever `TextProxy` is added, tested, consumed or reverted, the internal mechanisms of
110
- * `ModelConsumable` translate `TextProxy` to that unique `Symbol`.
111
- *
112
- * @private
113
- * @member {Map} module:engine/conversion/modelconsumable~ModelConsumable#_textProxyRegistry
114
- */
115
- this._textProxyRegistry = new Map();
116
- }
117
-
118
- /**
119
- * Adds a consumable value to the consumables list and links it with a given model item.
120
- *
121
- * modelConsumable.add( modelElement, 'insert' ); // Add `modelElement` insertion change to consumable values.
122
- * modelConsumable.add( modelElement, 'addAttribute:bold' ); // Add `bold` attribute insertion on `modelElement` change.
123
- * modelConsumable.add( modelElement, 'removeAttribute:bold' ); // Add `bold` attribute removal on `modelElement` change.
124
- * modelConsumable.add( modelSelection, 'selection' ); // Add `modelSelection` to consumable values.
125
- * modelConsumable.add( modelRange, 'range' ); // Add `modelRange` to consumable values.
126
- *
127
- * @param {module:engine/model/item~Item|module:engine/model/selection~Selection|module:engine/model/range~Range} item
128
- * Model item, range or selection that has the consumable.
129
- * @param {String} type Consumable type. Will be normalized to a proper form, that is either `<word>` or `<part>:<part>`.
130
- * Second colon and everything after will be cut. Passing event name is a safe and good practice.
131
- */
132
- add( item, type ) {
133
- type = _normalizeConsumableType( type );
134
-
135
- if ( item instanceof TextProxy ) {
136
- item = this._getSymbolForTextProxy( item );
137
- }
138
-
139
- if ( !this._consumable.has( item ) ) {
140
- this._consumable.set( item, new Map() );
141
- }
142
-
143
- this._consumable.get( item ).set( type, true );
144
- }
145
-
146
- /**
147
- * Removes a given consumable value from a given model item.
148
- *
149
- * modelConsumable.consume( modelElement, 'insert' ); // Remove `modelElement` insertion change from consumable values.
150
- * modelConsumable.consume( modelElement, 'addAttribute:bold' ); // Remove `bold` attribute insertion on `modelElement` change.
151
- * modelConsumable.consume( modelElement, 'removeAttribute:bold' ); // Remove `bold` attribute removal on `modelElement` change.
152
- * modelConsumable.consume( modelSelection, 'selection' ); // Remove `modelSelection` from consumable values.
153
- * modelConsumable.consume( modelRange, 'range' ); // Remove 'modelRange' from consumable values.
154
- *
155
- * @param {module:engine/model/item~Item|module:engine/model/selection~Selection|module:engine/model/range~Range} item
156
- * Model item, range or selection from which consumable will be consumed.
157
- * @param {String} type Consumable type. Will be normalized to a proper form, that is either `<word>` or `<part>:<part>`.
158
- * Second colon and everything after will be cut. Passing event name is a safe and good practice.
159
- * @returns {Boolean} `true` if consumable value was available and was consumed, `false` otherwise.
160
- */
161
- consume( item, type ) {
162
- type = _normalizeConsumableType( type );
163
-
164
- if ( item instanceof TextProxy ) {
165
- item = this._getSymbolForTextProxy( item );
166
- }
167
-
168
- if ( this.test( item, type ) ) {
169
- this._consumable.get( item ).set( type, false );
170
-
171
- return true;
172
- } else {
173
- return false;
174
- }
175
- }
176
-
177
- /**
178
- * Tests whether there is a consumable value of a given type connected with a given model item.
179
- *
180
- * modelConsumable.test( modelElement, 'insert' ); // Check for `modelElement` insertion change.
181
- * modelConsumable.test( modelElement, 'addAttribute:bold' ); // Check for `bold` attribute insertion on `modelElement` change.
182
- * modelConsumable.test( modelElement, 'removeAttribute:bold' ); // Check for `bold` attribute removal on `modelElement` change.
183
- * modelConsumable.test( modelSelection, 'selection' ); // Check if `modelSelection` is consumable.
184
- * modelConsumable.test( modelRange, 'range' ); // Check if `modelRange` is consumable.
185
- *
186
- * @param {module:engine/model/item~Item|module:engine/model/selection~Selection|module:engine/model/range~Range} item
187
- * Model item, range or selection to be tested.
188
- * @param {String} type Consumable type. Will be normalized to a proper form, that is either `<word>` or `<part>:<part>`.
189
- * Second colon and everything after will be cut. Passing event name is a safe and good practice.
190
- * @returns {null|Boolean} `null` if such consumable was never added, `false` if the consumable values was
191
- * already consumed or `true` if it was added and not consumed yet.
192
- */
193
- test( item, type ) {
194
- type = _normalizeConsumableType( type );
195
-
196
- if ( item instanceof TextProxy ) {
197
- item = this._getSymbolForTextProxy( item );
198
- }
199
-
200
- const itemConsumables = this._consumable.get( item );
201
-
202
- if ( itemConsumables === undefined ) {
203
- return null;
204
- }
205
-
206
- const value = itemConsumables.get( type );
207
-
208
- if ( value === undefined ) {
209
- return null;
210
- }
211
-
212
- return value;
213
- }
214
-
215
- /**
216
- * Reverts consuming of a consumable value.
217
- *
218
- * modelConsumable.revert( modelElement, 'insert' ); // Revert consuming `modelElement` insertion change.
219
- * modelConsumable.revert( modelElement, 'addAttribute:bold' ); // Revert consuming `bold` attribute insert from `modelElement`.
220
- * modelConsumable.revert( modelElement, 'removeAttribute:bold' ); // Revert consuming `bold` attribute remove from `modelElement`.
221
- * modelConsumable.revert( modelSelection, 'selection' ); // Revert consuming `modelSelection`.
222
- * modelConsumable.revert( modelRange, 'range' ); // Revert consuming `modelRange`.
223
- *
224
- * @param {module:engine/model/item~Item|module:engine/model/selection~Selection|module:engine/model/range~Range} item
225
- * Model item, range or selection to be reverted.
226
- * @param {String} type Consumable type.
227
- * @returns {null|Boolean} `true` if consumable has been reversed, `false` otherwise. `null` if the consumable has
228
- * never been added.
229
- */
230
- revert( item, type ) {
231
- type = _normalizeConsumableType( type );
232
-
233
- if ( item instanceof TextProxy ) {
234
- item = this._getSymbolForTextProxy( item );
235
- }
236
-
237
- const test = this.test( item, type );
238
-
239
- if ( test === false ) {
240
- this._consumable.get( item ).set( type, true );
241
-
242
- return true;
243
- } else if ( test === true ) {
244
- return false;
245
- }
246
-
247
- return null;
248
- }
249
-
250
- /**
251
- * Verifies if all events from the specified group were consumed.
252
- *
253
- * @param {String} eventGroup The events group to verify.
254
- */
255
- verifyAllConsumed( eventGroup ) {
256
- const items = [];
257
-
258
- for ( const [ item, consumables ] of this._consumable ) {
259
- for ( const [ event, canConsume ] of consumables ) {
260
- const eventPrefix = event.split( ':' )[ 0 ];
261
-
262
- if ( canConsume && eventGroup == eventPrefix ) {
263
- items.push( {
264
- event,
265
- item: item.name || item.description
266
- } );
267
- }
268
- }
269
- }
270
-
271
- if ( items.length ) {
272
- /**
273
- * Some of the {@link module:engine/model/item~Item model items} were not consumed while downcasting the model to view.
274
- *
275
- * This might be the effect of:
276
- *
277
- * * A missing converter for some model elements. Make sure that you registered downcast converters for all model elements.
278
- * * A custom converter that does not consume converted items. Make sure that you
279
- * {@link module:engine/conversion/modelconsumable~ModelConsumable#consume consumed} all model elements that you converted
280
- * from the model to the view.
281
- * * A custom converter that called `event.stop()`. When providing a custom converter, keep in mind that you should not stop
282
- * the event. If you stop it then the default converter at the `lowest` priority will not trigger the conversion of this node's
283
- * attributes and child nodes.
284
- *
285
- * @error conversion-model-consumable-not-consumed
286
- * @param {Array.<module:engine/model/item~Item>} items Items that were not consumed.
287
- */
288
- throw new CKEditorError( 'conversion-model-consumable-not-consumed', null, { items } );
289
- }
290
- }
291
-
292
- /**
293
- * Gets a unique symbol for the passed {@link module:engine/model/textproxy~TextProxy} instance. All `TextProxy` instances that
294
- * have same parent, same start index and same end index will get the same symbol.
295
- *
296
- * Used internally to correctly consume `TextProxy` instances.
297
- *
298
- * @protected
299
- * @param {module:engine/model/textproxy~TextProxy} textProxy `TextProxy` instance to get a symbol for.
300
- * @returns {Symbol} Symbol representing all equal instances of `TextProxy`.
301
- */
302
- _getSymbolForTextProxy( textProxy ) {
303
- let symbol = null;
304
-
305
- const startMap = this._textProxyRegistry.get( textProxy.startOffset );
306
-
307
- if ( startMap ) {
308
- const endMap = startMap.get( textProxy.endOffset );
309
-
310
- if ( endMap ) {
311
- symbol = endMap.get( textProxy.parent );
312
- }
313
- }
314
-
315
- if ( !symbol ) {
316
- symbol = this._addSymbolForTextProxy( textProxy );
317
- }
318
-
319
- return symbol;
320
- }
321
-
322
- /**
323
- * Adds a symbol for the given {@link module:engine/model/textproxy~TextProxy} instance.
324
- *
325
- * Used internally to correctly consume `TextProxy` instances.
326
- *
327
- * @private
328
- * @param {module:engine/model/textproxy~TextProxy} textProxy Text proxy instance.
329
- * @returns {Symbol} Symbol generated for given `TextProxy`.
330
- */
331
- _addSymbolForTextProxy( textProxy ) {
332
- const start = textProxy.startOffset;
333
- const end = textProxy.endOffset;
334
- const parent = textProxy.parent;
335
-
336
- const symbol = Symbol( '$textProxy:' + textProxy.data );
337
- let startMap, endMap;
338
-
339
- startMap = this._textProxyRegistry.get( start );
340
-
341
- if ( !startMap ) {
342
- startMap = new Map();
343
- this._textProxyRegistry.set( start, startMap );
344
- }
345
-
346
- endMap = startMap.get( end );
347
-
348
- if ( !endMap ) {
349
- endMap = new Map();
350
- startMap.set( end, endMap );
351
- }
352
-
353
- endMap.set( parent, symbol );
354
-
355
- return symbol;
356
- }
89
+ /**
90
+ * Creates an empty consumables list.
91
+ */
92
+ constructor() {
93
+ /**
94
+ * Contains list of consumable values.
95
+ *
96
+ * @private
97
+ * @member {Map} module:engine/conversion/modelconsumable~ModelConsumable#_consumable
98
+ */
99
+ this._consumable = new Map();
100
+ /**
101
+ * For each {@link module:engine/model/textproxy~TextProxy} added to `ModelConsumable`, this registry holds a parent
102
+ * of that `TextProxy` and the start and end indices of that `TextProxy`. This allows identification of the `TextProxy`
103
+ * instances that point to the same part of the model but are different instances. Each distinct `TextProxy`
104
+ * is given a unique `Symbol` which is then registered as consumable. This process is transparent for the `ModelConsumable`
105
+ * API user because whenever `TextProxy` is added, tested, consumed or reverted, the internal mechanisms of
106
+ * `ModelConsumable` translate `TextProxy` to that unique `Symbol`.
107
+ *
108
+ * @private
109
+ * @member {Map} module:engine/conversion/modelconsumable~ModelConsumable#_textProxyRegistry
110
+ */
111
+ this._textProxyRegistry = new Map();
112
+ }
113
+ /**
114
+ * Adds a consumable value to the consumables list and links it with a given model item.
115
+ *
116
+ * modelConsumable.add( modelElement, 'insert' ); // Add `modelElement` insertion change to consumable values.
117
+ * modelConsumable.add( modelElement, 'addAttribute:bold' ); // Add `bold` attribute insertion on `modelElement` change.
118
+ * modelConsumable.add( modelElement, 'removeAttribute:bold' ); // Add `bold` attribute removal on `modelElement` change.
119
+ * modelConsumable.add( modelSelection, 'selection' ); // Add `modelSelection` to consumable values.
120
+ * modelConsumable.add( modelRange, 'range' ); // Add `modelRange` to consumable values.
121
+ *
122
+ * @param {module:engine/model/item~Item|module:engine/model/selection~Selection|module:engine/model/range~Range} item
123
+ * Model item, range or selection that has the consumable.
124
+ * @param {String} type Consumable type. Will be normalized to a proper form, that is either `<word>` or `<part>:<part>`.
125
+ * Second colon and everything after will be cut. Passing event name is a safe and good practice.
126
+ */
127
+ add(item, type) {
128
+ type = _normalizeConsumableType(type);
129
+ if (item instanceof TextProxy) {
130
+ item = this._getSymbolForTextProxy(item);
131
+ }
132
+ if (!this._consumable.has(item)) {
133
+ this._consumable.set(item, new Map());
134
+ }
135
+ this._consumable.get(item).set(type, true);
136
+ }
137
+ /**
138
+ * Removes a given consumable value from a given model item.
139
+ *
140
+ * modelConsumable.consume( modelElement, 'insert' ); // Remove `modelElement` insertion change from consumable values.
141
+ * modelConsumable.consume( modelElement, 'addAttribute:bold' ); // Remove `bold` attribute insertion on `modelElement` change.
142
+ * modelConsumable.consume( modelElement, 'removeAttribute:bold' ); // Remove `bold` attribute removal on `modelElement` change.
143
+ * modelConsumable.consume( modelSelection, 'selection' ); // Remove `modelSelection` from consumable values.
144
+ * modelConsumable.consume( modelRange, 'range' ); // Remove 'modelRange' from consumable values.
145
+ *
146
+ * @param {module:engine/model/item~Item|module:engine/model/selection~Selection|module:engine/model/range~Range} item
147
+ * Model item, range or selection from which consumable will be consumed.
148
+ * @param {String} type Consumable type. Will be normalized to a proper form, that is either `<word>` or `<part>:<part>`.
149
+ * Second colon and everything after will be cut. Passing event name is a safe and good practice.
150
+ * @returns {Boolean} `true` if consumable value was available and was consumed, `false` otherwise.
151
+ */
152
+ consume(item, type) {
153
+ type = _normalizeConsumableType(type);
154
+ if (item instanceof TextProxy) {
155
+ item = this._getSymbolForTextProxy(item);
156
+ }
157
+ if (this.test(item, type)) {
158
+ this._consumable.get(item).set(type, false);
159
+ return true;
160
+ }
161
+ else {
162
+ return false;
163
+ }
164
+ }
165
+ /**
166
+ * Tests whether there is a consumable value of a given type connected with a given model item.
167
+ *
168
+ * modelConsumable.test( modelElement, 'insert' ); // Check for `modelElement` insertion change.
169
+ * modelConsumable.test( modelElement, 'addAttribute:bold' ); // Check for `bold` attribute insertion on `modelElement` change.
170
+ * modelConsumable.test( modelElement, 'removeAttribute:bold' ); // Check for `bold` attribute removal on `modelElement` change.
171
+ * modelConsumable.test( modelSelection, 'selection' ); // Check if `modelSelection` is consumable.
172
+ * modelConsumable.test( modelRange, 'range' ); // Check if `modelRange` is consumable.
173
+ *
174
+ * @param {module:engine/model/item~Item|module:engine/model/selection~Selection|module:engine/model/range~Range} item
175
+ * Model item, range or selection to be tested.
176
+ * @param {String} type Consumable type. Will be normalized to a proper form, that is either `<word>` or `<part>:<part>`.
177
+ * Second colon and everything after will be cut. Passing event name is a safe and good practice.
178
+ * @returns {null|Boolean} `null` if such consumable was never added, `false` if the consumable values was
179
+ * already consumed or `true` if it was added and not consumed yet.
180
+ */
181
+ test(item, type) {
182
+ type = _normalizeConsumableType(type);
183
+ if (item instanceof TextProxy) {
184
+ item = this._getSymbolForTextProxy(item);
185
+ }
186
+ const itemConsumables = this._consumable.get(item);
187
+ if (itemConsumables === undefined) {
188
+ return null;
189
+ }
190
+ const value = itemConsumables.get(type);
191
+ if (value === undefined) {
192
+ return null;
193
+ }
194
+ return value;
195
+ }
196
+ /**
197
+ * Reverts consuming of a consumable value.
198
+ *
199
+ * modelConsumable.revert( modelElement, 'insert' ); // Revert consuming `modelElement` insertion change.
200
+ * modelConsumable.revert( modelElement, 'addAttribute:bold' ); // Revert consuming `bold` attribute insert from `modelElement`.
201
+ * modelConsumable.revert( modelElement, 'removeAttribute:bold' ); // Revert consuming `bold` attribute remove from `modelElement`.
202
+ * modelConsumable.revert( modelSelection, 'selection' ); // Revert consuming `modelSelection`.
203
+ * modelConsumable.revert( modelRange, 'range' ); // Revert consuming `modelRange`.
204
+ *
205
+ * @param {module:engine/model/item~Item|module:engine/model/selection~Selection|module:engine/model/range~Range} item
206
+ * Model item, range or selection to be reverted.
207
+ * @param {String} type Consumable type.
208
+ * @returns {null|Boolean} `true` if consumable has been reversed, `false` otherwise. `null` if the consumable has
209
+ * never been added.
210
+ */
211
+ revert(item, type) {
212
+ type = _normalizeConsumableType(type);
213
+ if (item instanceof TextProxy) {
214
+ item = this._getSymbolForTextProxy(item);
215
+ }
216
+ const test = this.test(item, type);
217
+ if (test === false) {
218
+ this._consumable.get(item).set(type, true);
219
+ return true;
220
+ }
221
+ else if (test === true) {
222
+ return false;
223
+ }
224
+ return null;
225
+ }
226
+ /**
227
+ * Verifies if all events from the specified group were consumed.
228
+ *
229
+ * @param {String} eventGroup The events group to verify.
230
+ */
231
+ verifyAllConsumed(eventGroup) {
232
+ const items = [];
233
+ for (const [item, consumables] of this._consumable) {
234
+ for (const [event, canConsume] of consumables) {
235
+ const eventPrefix = event.split(':')[0];
236
+ if (canConsume && eventGroup == eventPrefix) {
237
+ items.push({
238
+ event,
239
+ item: item.name || item.description
240
+ });
241
+ }
242
+ }
243
+ }
244
+ if (items.length) {
245
+ /**
246
+ * Some of the {@link module:engine/model/item~Item model items} were not consumed while downcasting the model to view.
247
+ *
248
+ * This might be the effect of:
249
+ *
250
+ * * A missing converter for some model elements. Make sure that you registered downcast converters for all model elements.
251
+ * * A custom converter that does not consume converted items. Make sure that you
252
+ * {@link module:engine/conversion/modelconsumable~ModelConsumable#consume consumed} all model elements that you converted
253
+ * from the model to the view.
254
+ * * A custom converter that called `event.stop()`. When providing a custom converter, keep in mind that you should not stop
255
+ * the event. If you stop it then the default converter at the `lowest` priority will not trigger the conversion of this node's
256
+ * attributes and child nodes.
257
+ *
258
+ * @error conversion-model-consumable-not-consumed
259
+ * @param {Array.<module:engine/model/item~Item>} items Items that were not consumed.
260
+ */
261
+ throw new CKEditorError('conversion-model-consumable-not-consumed', null, { items });
262
+ }
263
+ }
264
+ /**
265
+ * Gets a unique symbol for the passed {@link module:engine/model/textproxy~TextProxy} instance. All `TextProxy` instances that
266
+ * have same parent, same start index and same end index will get the same symbol.
267
+ *
268
+ * Used internally to correctly consume `TextProxy` instances.
269
+ *
270
+ * @internal
271
+ * @protected
272
+ * @param {module:engine/model/textproxy~TextProxy} textProxy `TextProxy` instance to get a symbol for.
273
+ * @returns {Symbol} Symbol representing all equal instances of `TextProxy`.
274
+ */
275
+ _getSymbolForTextProxy(textProxy) {
276
+ let symbol = null;
277
+ const startMap = this._textProxyRegistry.get(textProxy.startOffset);
278
+ if (startMap) {
279
+ const endMap = startMap.get(textProxy.endOffset);
280
+ if (endMap) {
281
+ symbol = endMap.get(textProxy.parent);
282
+ }
283
+ }
284
+ if (!symbol) {
285
+ symbol = this._addSymbolForTextProxy(textProxy);
286
+ }
287
+ return symbol;
288
+ }
289
+ /**
290
+ * Adds a symbol for the given {@link module:engine/model/textproxy~TextProxy} instance.
291
+ *
292
+ * Used internally to correctly consume `TextProxy` instances.
293
+ *
294
+ * @private
295
+ * @param {module:engine/model/textproxy~TextProxy} textProxy Text proxy instance.
296
+ * @returns {Symbol} Symbol generated for given `TextProxy`.
297
+ */
298
+ _addSymbolForTextProxy(textProxy) {
299
+ const start = textProxy.startOffset;
300
+ const end = textProxy.endOffset;
301
+ const parent = textProxy.parent;
302
+ const symbol = Symbol('$textProxy:' + textProxy.data);
303
+ let startMap;
304
+ let endMap;
305
+ startMap = this._textProxyRegistry.get(start);
306
+ if (!startMap) {
307
+ startMap = new Map();
308
+ this._textProxyRegistry.set(start, startMap);
309
+ }
310
+ endMap = startMap.get(end);
311
+ if (!endMap) {
312
+ endMap = new Map();
313
+ startMap.set(end, endMap);
314
+ }
315
+ endMap.set(parent, symbol);
316
+ return symbol;
317
+ }
357
318
  }
358
-
359
319
  // Returns a normalized consumable type name from the given string. A normalized consumable type name is a string that has
360
320
  // at most one colon, for example: `insert` or `addMarker:highlight`. If a string to normalize has more "parts" (more colons),
361
321
  // the further parts are dropped, for example: `addattribute:bold:$text` -> `addattributes:bold`.
362
322
  //
363
323
  // @param {String} type Consumable type.
364
324
  // @returns {String} Normalized consumable type.
365
- function _normalizeConsumableType( type ) {
366
- const parts = type.split( ':' );
367
-
368
- // For inserts allow passing event name, it's stored in the context of a specified element so the element name is not needed.
369
- if ( parts[ 0 ] == 'insert' ) {
370
- return parts[ 0 ];
371
- }
372
-
373
- // Markers are identified by the whole name (otherwise we would consume the whole markers group).
374
- if ( parts[ 0 ] == 'addMarker' || parts[ 0 ] == 'removeMarker' ) {
375
- return type;
376
- }
377
-
378
- return parts.length > 1 ? parts[ 0 ] + ':' + parts[ 1 ] : parts[ 0 ];
325
+ function _normalizeConsumableType(type) {
326
+ const parts = type.split(':');
327
+ // For inserts allow passing event name, it's stored in the context of a specified element so the element name is not needed.
328
+ if (parts[0] == 'insert') {
329
+ return parts[0];
330
+ }
331
+ // Markers are identified by the whole name (otherwise we would consume the whole markers group).
332
+ if (parts[0] == 'addMarker' || parts[0] == 'removeMarker') {
333
+ return type;
334
+ }
335
+ return parts.length > 1 ? parts[0] + ':' + parts[1] : parts[0];
379
336
  }