@ckeditor/ckeditor5-engine 35.0.1 → 35.2.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 (124) hide show
  1. package/CHANGELOG.md +4 -4
  2. package/package.json +30 -24
  3. package/src/controller/datacontroller.js +467 -561
  4. package/src/controller/editingcontroller.js +168 -204
  5. package/src/conversion/conversion.js +541 -565
  6. package/src/conversion/conversionhelpers.js +24 -28
  7. package/src/conversion/downcastdispatcher.js +457 -686
  8. package/src/conversion/downcasthelpers.js +1583 -1965
  9. package/src/conversion/mapper.js +518 -707
  10. package/src/conversion/modelconsumable.js +240 -283
  11. package/src/conversion/upcastdispatcher.js +372 -718
  12. package/src/conversion/upcasthelpers.js +707 -818
  13. package/src/conversion/viewconsumable.js +524 -581
  14. package/src/dataprocessor/basichtmlwriter.js +12 -16
  15. package/src/dataprocessor/dataprocessor.js +5 -0
  16. package/src/dataprocessor/htmldataprocessor.js +100 -116
  17. package/src/dataprocessor/htmlwriter.js +1 -18
  18. package/src/dataprocessor/xmldataprocessor.js +116 -137
  19. package/src/dev-utils/model.js +260 -352
  20. package/src/dev-utils/operationreplayer.js +106 -126
  21. package/src/dev-utils/utils.js +34 -51
  22. package/src/dev-utils/view.js +632 -753
  23. package/src/index.js +0 -11
  24. package/src/model/batch.js +111 -127
  25. package/src/model/differ.js +988 -1233
  26. package/src/model/document.js +340 -449
  27. package/src/model/documentfragment.js +327 -364
  28. package/src/model/documentselection.js +996 -1189
  29. package/src/model/element.js +306 -410
  30. package/src/model/history.js +224 -262
  31. package/src/model/item.js +5 -0
  32. package/src/model/liveposition.js +84 -145
  33. package/src/model/liverange.js +108 -185
  34. package/src/model/markercollection.js +379 -480
  35. package/src/model/model.js +883 -1034
  36. package/src/model/node.js +419 -463
  37. package/src/model/nodelist.js +176 -201
  38. package/src/model/operation/attributeoperation.js +153 -182
  39. package/src/model/operation/detachoperation.js +64 -83
  40. package/src/model/operation/insertoperation.js +135 -166
  41. package/src/model/operation/markeroperation.js +114 -140
  42. package/src/model/operation/mergeoperation.js +163 -191
  43. package/src/model/operation/moveoperation.js +157 -187
  44. package/src/model/operation/nooperation.js +28 -38
  45. package/src/model/operation/operation.js +106 -125
  46. package/src/model/operation/operationfactory.js +30 -34
  47. package/src/model/operation/renameoperation.js +109 -135
  48. package/src/model/operation/rootattributeoperation.js +155 -188
  49. package/src/model/operation/splitoperation.js +196 -232
  50. package/src/model/operation/transform.js +1833 -2204
  51. package/src/model/operation/utils.js +140 -204
  52. package/src/model/position.js +980 -1053
  53. package/src/model/range.js +910 -1028
  54. package/src/model/rootelement.js +77 -97
  55. package/src/model/schema.js +1189 -1835
  56. package/src/model/selection.js +745 -862
  57. package/src/model/text.js +90 -114
  58. package/src/model/textproxy.js +204 -240
  59. package/src/model/treewalker.js +316 -397
  60. package/src/model/typecheckable.js +16 -0
  61. package/src/model/utils/autoparagraphing.js +32 -44
  62. package/src/model/utils/deletecontent.js +334 -418
  63. package/src/model/utils/findoptimalinsertionrange.js +25 -36
  64. package/src/model/utils/getselectedcontent.js +96 -118
  65. package/src/model/utils/insertcontent.js +757 -773
  66. package/src/model/utils/insertobject.js +96 -119
  67. package/src/model/utils/modifyselection.js +120 -158
  68. package/src/model/utils/selection-post-fixer.js +153 -201
  69. package/src/model/writer.js +1305 -1474
  70. package/src/view/attributeelement.js +189 -225
  71. package/src/view/containerelement.js +75 -85
  72. package/src/view/document.js +172 -215
  73. package/src/view/documentfragment.js +200 -249
  74. package/src/view/documentselection.js +338 -367
  75. package/src/view/domconverter.js +1370 -1617
  76. package/src/view/downcastwriter.js +1747 -2076
  77. package/src/view/editableelement.js +81 -97
  78. package/src/view/element.js +739 -890
  79. package/src/view/elementdefinition.js +5 -0
  80. package/src/view/emptyelement.js +82 -92
  81. package/src/view/filler.js +35 -50
  82. package/src/view/item.js +5 -0
  83. package/src/view/matcher.js +260 -559
  84. package/src/view/node.js +274 -360
  85. package/src/view/observer/arrowkeysobserver.js +19 -28
  86. package/src/view/observer/bubblingemittermixin.js +120 -263
  87. package/src/view/observer/bubblingeventinfo.js +47 -55
  88. package/src/view/observer/clickobserver.js +7 -13
  89. package/src/view/observer/compositionobserver.js +14 -24
  90. package/src/view/observer/domeventdata.js +57 -67
  91. package/src/view/observer/domeventobserver.js +40 -64
  92. package/src/view/observer/fakeselectionobserver.js +81 -96
  93. package/src/view/observer/focusobserver.js +45 -61
  94. package/src/view/observer/inputobserver.js +7 -13
  95. package/src/view/observer/keyobserver.js +17 -27
  96. package/src/view/observer/mouseobserver.js +7 -14
  97. package/src/view/observer/mutationobserver.js +220 -315
  98. package/src/view/observer/observer.js +81 -102
  99. package/src/view/observer/selectionobserver.js +199 -246
  100. package/src/view/observer/tabobserver.js +23 -36
  101. package/src/view/placeholder.js +128 -173
  102. package/src/view/position.js +350 -401
  103. package/src/view/range.js +453 -513
  104. package/src/view/rawelement.js +85 -112
  105. package/src/view/renderer.js +874 -1018
  106. package/src/view/rooteditableelement.js +80 -90
  107. package/src/view/selection.js +608 -689
  108. package/src/view/styles/background.js +43 -44
  109. package/src/view/styles/border.js +220 -276
  110. package/src/view/styles/margin.js +8 -17
  111. package/src/view/styles/padding.js +8 -16
  112. package/src/view/styles/utils.js +127 -160
  113. package/src/view/stylesmap.js +728 -905
  114. package/src/view/text.js +102 -126
  115. package/src/view/textproxy.js +144 -170
  116. package/src/view/treewalker.js +383 -479
  117. package/src/view/typecheckable.js +19 -0
  118. package/src/view/uielement.js +166 -187
  119. package/src/view/upcastwriter.js +395 -449
  120. package/src/view/view.js +569 -664
  121. package/src/dataprocessor/dataprocessor.jsdoc +0 -64
  122. package/src/model/item.jsdoc +0 -14
  123. package/src/view/elementdefinition.jsdoc +0 -59
  124. 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
  }