@ckeditor/ckeditor5-engine 44.2.1-alpha.0 → 44.3.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1677 -1258
- package/dist/index.js.map +1 -1
- package/package.json +2 -4
- package/src/conversion/downcasthelpers.js +14 -42
- package/src/conversion/viewconsumable.d.ts +65 -97
- package/src/conversion/viewconsumable.js +217 -215
- package/src/view/attributeelement.d.ts +14 -0
- package/src/view/attributeelement.js +26 -0
- package/src/view/domconverter.js +72 -7
- package/src/view/downcastwriter.d.ts +32 -20
- package/src/view/downcastwriter.js +18 -142
- package/src/view/element.d.ts +309 -17
- package/src/view/element.js +432 -137
- package/src/view/matcher.d.ts +38 -16
- package/src/view/matcher.js +91 -166
- package/src/view/stylesmap.d.ts +68 -6
- package/src/view/stylesmap.js +144 -8
- package/src/view/tokenlist.d.ts +109 -0
- package/src/view/tokenlist.js +196 -0
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* @module engine/conversion/viewconsumable
|
|
7
7
|
*/
|
|
8
|
-
import { CKEditorError
|
|
8
|
+
import { CKEditorError } from '@ckeditor/ckeditor5-utils';
|
|
9
9
|
/**
|
|
10
10
|
* Class used for handling consumption of view {@link module:engine/view/element~Element elements},
|
|
11
11
|
* {@link module:engine/view/text~Text text nodes} and {@link module:engine/view/documentfragment~DocumentFragment document fragments}.
|
|
@@ -41,6 +41,35 @@ export default class ViewConsumable {
|
|
|
41
41
|
*/
|
|
42
42
|
this._consumables = new Map();
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Adds view {@link module:engine/view/element~Element element}, {@link module:engine/view/text~Text text node} or
|
|
46
|
+
* {@link module:engine/view/documentfragment~DocumentFragment document fragment} as ready to be consumed.
|
|
47
|
+
*
|
|
48
|
+
* ```ts
|
|
49
|
+
* viewConsumable.add( p, { name: true } ); // Adds element's name to consume.
|
|
50
|
+
* viewConsumable.add( p, { attributes: 'name' } ); // Adds element's attribute.
|
|
51
|
+
* viewConsumable.add( p, { classes: 'foobar' } ); // Adds element's class.
|
|
52
|
+
* viewConsumable.add( p, { styles: 'color' } ); // Adds element's style
|
|
53
|
+
* viewConsumable.add( p, { attributes: 'name', styles: 'color' } ); // Adds attribute and style.
|
|
54
|
+
* viewConsumable.add( p, { classes: [ 'baz', 'bar' ] } ); // Multiple consumables can be provided.
|
|
55
|
+
* viewConsumable.add( textNode ); // Adds text node to consume.
|
|
56
|
+
* viewConsumable.add( docFragment ); // Adds document fragment to consume.
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `viewconsumable-invalid-attribute` when `class` or `style`
|
|
60
|
+
* attribute is provided - it should be handled separately by providing actual style/class.
|
|
61
|
+
*
|
|
62
|
+
* ```ts
|
|
63
|
+
* viewConsumable.add( p, { attributes: 'style' } ); // This call will throw an exception.
|
|
64
|
+
* viewConsumable.add( p, { styles: 'color' } ); // This is properly handled style.
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* @param consumables Used only if first parameter is {@link module:engine/view/element~Element view element} instance.
|
|
68
|
+
* @param consumables.name If set to true element's name will be included.
|
|
69
|
+
* @param consumables.attributes Attribute name or array of attribute names.
|
|
70
|
+
* @param consumables.classes Class name or array of class names.
|
|
71
|
+
* @param consumables.styles Style name or array of style names.
|
|
72
|
+
*/
|
|
44
73
|
add(element, consumables) {
|
|
45
74
|
let elementConsumables;
|
|
46
75
|
// For text nodes and document fragments just mark them as consumable.
|
|
@@ -56,7 +85,7 @@ export default class ViewConsumable {
|
|
|
56
85
|
else {
|
|
57
86
|
elementConsumables = this._consumables.get(element);
|
|
58
87
|
}
|
|
59
|
-
elementConsumables.add(consumables);
|
|
88
|
+
elementConsumables.add(consumables ? normalizeConsumables(consumables) : element._getConsumables());
|
|
60
89
|
}
|
|
61
90
|
/**
|
|
62
91
|
* Tests if {@link module:engine/view/element~Element view element}, {@link module:engine/view/text~Text text node} or
|
|
@@ -100,7 +129,7 @@ export default class ViewConsumable {
|
|
|
100
129
|
return elementConsumables;
|
|
101
130
|
}
|
|
102
131
|
// For elements test consumables object.
|
|
103
|
-
return elementConsumables.test(consumables);
|
|
132
|
+
return elementConsumables.test(normalizeConsumables(consumables));
|
|
104
133
|
}
|
|
105
134
|
/**
|
|
106
135
|
* Consumes {@link module:engine/view/element~Element view element}, {@link module:engine/view/text~Text text node} or
|
|
@@ -134,18 +163,20 @@ export default class ViewConsumable {
|
|
|
134
163
|
* otherwise returns `false`.
|
|
135
164
|
*/
|
|
136
165
|
consume(element, consumables) {
|
|
137
|
-
if (
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
this._consumables.set(element, false);
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
// For elements - consume consumables object.
|
|
144
|
-
this._consumables.get(element).consume(consumables);
|
|
166
|
+
if (element.is('$text') || element.is('documentFragment')) {
|
|
167
|
+
if (!this.test(element, consumables)) {
|
|
168
|
+
return false;
|
|
145
169
|
}
|
|
170
|
+
// For text nodes and document fragments set value to false.
|
|
171
|
+
this._consumables.set(element, false);
|
|
146
172
|
return true;
|
|
147
173
|
}
|
|
148
|
-
|
|
174
|
+
// For elements - consume consumables object.
|
|
175
|
+
const elementConsumables = this._consumables.get(element);
|
|
176
|
+
if (elementConsumables === undefined) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
return elementConsumables.consume(normalizeConsumables(consumables));
|
|
149
180
|
}
|
|
150
181
|
/**
|
|
151
182
|
* Reverts {@link module:engine/view/element~Element view element}, {@link module:engine/view/text~Text text node} or
|
|
@@ -187,40 +218,10 @@ export default class ViewConsumable {
|
|
|
187
218
|
}
|
|
188
219
|
else {
|
|
189
220
|
// For elements - revert items from consumables object.
|
|
190
|
-
elementConsumables.revert(consumables);
|
|
221
|
+
elementConsumables.revert(normalizeConsumables(consumables));
|
|
191
222
|
}
|
|
192
223
|
}
|
|
193
224
|
}
|
|
194
|
-
/**
|
|
195
|
-
* Creates consumable object from {@link module:engine/view/element~Element view element}. Consumable object will include
|
|
196
|
-
* element's name and all its attributes, classes and styles.
|
|
197
|
-
*/
|
|
198
|
-
static consumablesFromElement(element) {
|
|
199
|
-
const consumables = {
|
|
200
|
-
element,
|
|
201
|
-
name: true,
|
|
202
|
-
attributes: [],
|
|
203
|
-
classes: [],
|
|
204
|
-
styles: []
|
|
205
|
-
};
|
|
206
|
-
const attributes = element.getAttributeKeys();
|
|
207
|
-
for (const attribute of attributes) {
|
|
208
|
-
// Skip classes and styles - will be added separately.
|
|
209
|
-
if (attribute == 'style' || attribute == 'class') {
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
consumables.attributes.push(attribute);
|
|
213
|
-
}
|
|
214
|
-
const classes = element.getClassNames();
|
|
215
|
-
for (const className of classes) {
|
|
216
|
-
consumables.classes.push(className);
|
|
217
|
-
}
|
|
218
|
-
const styles = element.getStyleNames();
|
|
219
|
-
for (const style of styles) {
|
|
220
|
-
consumables.styles.push(style);
|
|
221
|
-
}
|
|
222
|
-
return consumables;
|
|
223
|
-
}
|
|
224
225
|
/**
|
|
225
226
|
* Creates {@link module:engine/conversion/viewconsumable~ViewConsumable ViewConsumable} instance from
|
|
226
227
|
* {@link module:engine/view/node~Node node} or {@link module:engine/view/documentfragment~DocumentFragment document fragment}.
|
|
@@ -236,22 +237,16 @@ export default class ViewConsumable {
|
|
|
236
237
|
}
|
|
237
238
|
if (from.is('$text')) {
|
|
238
239
|
instance.add(from);
|
|
239
|
-
return instance;
|
|
240
|
-
}
|
|
241
|
-
// Add `from` itself, if it is an element.
|
|
242
|
-
if (from.is('element')) {
|
|
243
|
-
instance.add(from, ViewConsumable.consumablesFromElement(from));
|
|
244
240
|
}
|
|
245
|
-
if (from.is('documentFragment')) {
|
|
241
|
+
else if (from.is('element') || from.is('documentFragment')) {
|
|
246
242
|
instance.add(from);
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
243
|
+
for (const child of from.getChildren()) {
|
|
244
|
+
ViewConsumable.createFrom(child, instance);
|
|
245
|
+
}
|
|
250
246
|
}
|
|
251
247
|
return instance;
|
|
252
248
|
}
|
|
253
249
|
}
|
|
254
|
-
const CONSUMABLE_TYPES = ['attributes', 'classes', 'styles'];
|
|
255
250
|
/**
|
|
256
251
|
* This is a private helper-class for {@link module:engine/conversion/viewconsumable~ViewConsumable}.
|
|
257
252
|
* It represents and manipulates consumable parts of a single {@link module:engine/view/element~Element}.
|
|
@@ -260,16 +255,21 @@ export class ViewElementConsumables {
|
|
|
260
255
|
/**
|
|
261
256
|
* Creates ViewElementConsumables instance.
|
|
262
257
|
*
|
|
263
|
-
* @param from View
|
|
258
|
+
* @param from View element from which `ViewElementConsumables` is being created.
|
|
264
259
|
*/
|
|
265
260
|
constructor(from) {
|
|
266
|
-
|
|
261
|
+
/**
|
|
262
|
+
* Flag indicating if name of the element can be consumed.
|
|
263
|
+
*/
|
|
267
264
|
this._canConsumeName = null;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
265
|
+
/**
|
|
266
|
+
* A map of element's consumables.
|
|
267
|
+
* * For plain attributes the value is a boolean indicating whether the attribute is available to consume.
|
|
268
|
+
* * For token based attributes (like class list and style) the value is a map of tokens to booleans
|
|
269
|
+
* indicating whether the token is available to consume on the given attribute.
|
|
270
|
+
*/
|
|
271
|
+
this._attributes = new Map();
|
|
272
|
+
this.element = from;
|
|
273
273
|
}
|
|
274
274
|
/**
|
|
275
275
|
* Adds consumable parts of the {@link module:engine/view/element~Element view element}.
|
|
@@ -283,31 +283,60 @@ export class ViewElementConsumables {
|
|
|
283
283
|
* Attributes classes and styles:
|
|
284
284
|
*
|
|
285
285
|
* ```ts
|
|
286
|
-
* consumables.add( { attributes: 'title',
|
|
287
|
-
* consumables.add( { attributes: [ 'title', 'name' ],
|
|
286
|
+
* consumables.add( { attributes: [ [ 'title' ], [ 'class', 'foo' ], [ 'style', 'color'] ] } );
|
|
287
|
+
* consumables.add( { attributes: [ [ 'title' ], [ 'name' ], [ 'class', 'foo' ], [ 'class', 'bar' ] ] } );
|
|
288
288
|
* ```
|
|
289
289
|
*
|
|
290
|
+
* Note: This method accepts only {@link module:engine/view/element~NormalizedConsumables}.
|
|
291
|
+
* You can use {@link module:engine/conversion/viewconsumable~normalizeConsumables} helper to convert from
|
|
292
|
+
* {@link module:engine/conversion/viewconsumable~Consumables} to `NormalizedConsumables`.
|
|
293
|
+
*
|
|
290
294
|
* Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `viewconsumable-invalid-attribute` when `class` or `style`
|
|
291
295
|
* attribute is provided - it should be handled separately by providing `style` and `class` in consumables object.
|
|
292
296
|
*
|
|
293
297
|
* @param consumables Object describing which parts of the element can be consumed.
|
|
294
|
-
* @param consumables.name If set to `true` element's name will be added as consumable.
|
|
295
|
-
* @param consumables.attributes Attribute name or array of attribute names to add as consumable.
|
|
296
|
-
* @param consumables.classes Class name or array of class names to add as consumable.
|
|
297
|
-
* @param consumables.styles Style name or array of style names to add as consumable.
|
|
298
298
|
*/
|
|
299
299
|
add(consumables) {
|
|
300
300
|
if (consumables.name) {
|
|
301
301
|
this._canConsumeName = true;
|
|
302
302
|
}
|
|
303
|
-
for (const
|
|
304
|
-
if (
|
|
305
|
-
this.
|
|
303
|
+
for (const [name, token] of consumables.attributes) {
|
|
304
|
+
if (token) {
|
|
305
|
+
let attributeTokens = this._attributes.get(name);
|
|
306
|
+
if (!attributeTokens || typeof attributeTokens == 'boolean') {
|
|
307
|
+
attributeTokens = new Map();
|
|
308
|
+
this._attributes.set(name, attributeTokens);
|
|
309
|
+
}
|
|
310
|
+
attributeTokens.set(token, true);
|
|
311
|
+
}
|
|
312
|
+
else if (name == 'style' || name == 'class') {
|
|
313
|
+
/**
|
|
314
|
+
* Class and style attributes should be handled separately in
|
|
315
|
+
* {@link module:engine/conversion/viewconsumable~ViewConsumable#add `ViewConsumable#add()`}.
|
|
316
|
+
*
|
|
317
|
+
* What you have done is trying to use:
|
|
318
|
+
*
|
|
319
|
+
* ```ts
|
|
320
|
+
* consumables.add( { attributes: [ 'class', 'style' ] } );
|
|
321
|
+
* ```
|
|
322
|
+
*
|
|
323
|
+
* While each class and style should be registered separately:
|
|
324
|
+
*
|
|
325
|
+
* ```ts
|
|
326
|
+
* consumables.add( { classes: 'some-class', styles: 'font-weight' } );
|
|
327
|
+
* ```
|
|
328
|
+
*
|
|
329
|
+
* @error viewconsumable-invalid-attribute
|
|
330
|
+
*/
|
|
331
|
+
throw new CKEditorError('viewconsumable-invalid-attribute', this);
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
this._attributes.set(name, true);
|
|
306
335
|
}
|
|
307
336
|
}
|
|
308
337
|
}
|
|
309
338
|
/**
|
|
310
|
-
* Tests if parts of the {@link module:engine/view/
|
|
339
|
+
* Tests if parts of the {@link module:engine/view/element~Element view element} can be consumed.
|
|
311
340
|
*
|
|
312
341
|
* Element's name can be tested:
|
|
313
342
|
*
|
|
@@ -318,15 +347,11 @@ export class ViewElementConsumables {
|
|
|
318
347
|
* Attributes classes and styles:
|
|
319
348
|
*
|
|
320
349
|
* ```ts
|
|
321
|
-
* consumables.test( { attributes: 'title',
|
|
322
|
-
* consumables.test( { attributes: [ 'title', 'name' ],
|
|
350
|
+
* consumables.test( { attributes: [ [ 'title' ], [ 'class', 'foo' ], [ 'style', 'color' ] ] } );
|
|
351
|
+
* consumables.test( { attributes: [ [ 'title' ], [ 'name' ], [ 'class', 'foo' ], [ 'class', 'bar' ] ] } );
|
|
323
352
|
* ```
|
|
324
353
|
*
|
|
325
354
|
* @param consumables Object describing which parts of the element should be tested.
|
|
326
|
-
* @param consumables.name If set to `true` element's name will be tested.
|
|
327
|
-
* @param consumables.attributes Attribute name or array of attribute names to test.
|
|
328
|
-
* @param consumables.classes Class name or array of class names to test.
|
|
329
|
-
* @param consumables.styles Style name or array of style names to test.
|
|
330
355
|
* @returns `true` when all tested items can be consumed, `null` when even one of the items
|
|
331
356
|
* was never marked for consumption and `false` when even one of the items was already consumed.
|
|
332
357
|
*/
|
|
@@ -335,11 +360,38 @@ export class ViewElementConsumables {
|
|
|
335
360
|
if (consumables.name && !this._canConsumeName) {
|
|
336
361
|
return this._canConsumeName;
|
|
337
362
|
}
|
|
338
|
-
for (const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
363
|
+
for (const [name, token] of consumables.attributes) {
|
|
364
|
+
const value = this._attributes.get(name);
|
|
365
|
+
// Return null if attribute is not found.
|
|
366
|
+
if (value === undefined) {
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
// Already consumed.
|
|
370
|
+
if (value === false) {
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
// Simple attribute is not consumed so continue to next attribute.
|
|
374
|
+
if (value === true) {
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
if (!token) {
|
|
378
|
+
// Tokenized attribute but token is not specified so check if all tokens are not consumed.
|
|
379
|
+
for (const tokenValue of value.values()) {
|
|
380
|
+
// Already consumed token.
|
|
381
|
+
if (!tokenValue) {
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
const tokenValue = value.get(token);
|
|
388
|
+
// Return null if token is not found.
|
|
389
|
+
if (tokenValue === undefined) {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
// Already consumed.
|
|
393
|
+
if (!tokenValue) {
|
|
394
|
+
return false;
|
|
343
395
|
}
|
|
344
396
|
}
|
|
345
397
|
}
|
|
@@ -347,8 +399,9 @@ export class ViewElementConsumables {
|
|
|
347
399
|
return true;
|
|
348
400
|
}
|
|
349
401
|
/**
|
|
350
|
-
*
|
|
351
|
-
*
|
|
402
|
+
* Tests if parts of the {@link module:engine/view/element~Element view element} can be consumed and consumes them if available.
|
|
403
|
+
* It returns `true` when all items included in method's call can be consumed, otherwise returns `false`.
|
|
404
|
+
*
|
|
352
405
|
* Element's name can be consumed:
|
|
353
406
|
*
|
|
354
407
|
* ```ts
|
|
@@ -358,25 +411,44 @@ export class ViewElementConsumables {
|
|
|
358
411
|
* Attributes classes and styles:
|
|
359
412
|
*
|
|
360
413
|
* ```ts
|
|
361
|
-
* consumables.consume( { attributes: 'title',
|
|
362
|
-
* consumables.consume( { attributes: [ 'title', 'name' ],
|
|
414
|
+
* consumables.consume( { attributes: [ [ 'title' ], [ 'class', 'foo' ], [ 'style', 'color' ] ] } );
|
|
415
|
+
* consumables.consume( { attributes: [ [ 'title' ], [ 'name' ], [ 'class', 'foo' ], [ 'class', 'bar' ] ] } );
|
|
363
416
|
* ```
|
|
364
417
|
*
|
|
365
418
|
* @param consumables Object describing which parts of the element should be consumed.
|
|
366
|
-
* @
|
|
367
|
-
* @param consumables.attributes Attribute name or array of attribute names to consume.
|
|
368
|
-
* @param consumables.classes Class name or array of class names to consume.
|
|
369
|
-
* @param consumables.styles Style name or array of style names to consume.
|
|
419
|
+
* @returns `true` when all tested items can be consumed and `false` when even one of the items could not be consumed.
|
|
370
420
|
*/
|
|
371
421
|
consume(consumables) {
|
|
422
|
+
if (!this.test(consumables)) {
|
|
423
|
+
return false;
|
|
424
|
+
}
|
|
372
425
|
if (consumables.name) {
|
|
373
426
|
this._canConsumeName = false;
|
|
374
427
|
}
|
|
375
|
-
for (const
|
|
376
|
-
|
|
377
|
-
|
|
428
|
+
for (const [name, token] of consumables.attributes) {
|
|
429
|
+
// `value` must be set, because `this.test()` returned `true`.
|
|
430
|
+
const value = this._attributes.get(name);
|
|
431
|
+
// Plain (not tokenized) not-consumed attribute.
|
|
432
|
+
if (typeof value == 'boolean') {
|
|
433
|
+
// Use Element API to collect related attributes.
|
|
434
|
+
for (const [toConsume] of this.element._getConsumables(name, token).attributes) {
|
|
435
|
+
this._attributes.set(toConsume, false);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
else if (!token) {
|
|
439
|
+
// Tokenized attribute but token is not specified so consume all tokens.
|
|
440
|
+
for (const token of value.keys()) {
|
|
441
|
+
value.set(token, false);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
// Use Element API to collect related attribute tokens.
|
|
446
|
+
for (const [, toConsume] of this.element._getConsumables(name, token).attributes) {
|
|
447
|
+
value.set(toConsume, false);
|
|
448
|
+
}
|
|
378
449
|
}
|
|
379
450
|
}
|
|
451
|
+
return true;
|
|
380
452
|
}
|
|
381
453
|
/**
|
|
382
454
|
* Revert already consumed parts of {@link module:engine/view/element~Element view Element}, so they can be consumed once again.
|
|
@@ -389,147 +461,77 @@ export class ViewElementConsumables {
|
|
|
389
461
|
* Attributes classes and styles:
|
|
390
462
|
*
|
|
391
463
|
* ```ts
|
|
392
|
-
* consumables.revert( { attributes: 'title',
|
|
393
|
-
* consumables.revert( { attributes: [ 'title', 'name' ],
|
|
464
|
+
* consumables.revert( { attributes: [ [ 'title' ], [ 'class', 'foo' ], [ 'style', 'color' ] ] } );
|
|
465
|
+
* consumables.revert( { attributes: [ [ 'title' ], [ 'name' ], [ 'class', 'foo' ], [ 'class', 'bar' ] ] } );
|
|
394
466
|
* ```
|
|
395
467
|
*
|
|
396
468
|
* @param consumables Object describing which parts of the element should be reverted.
|
|
397
|
-
* @param consumables.name If set to `true` element's name will be reverted.
|
|
398
|
-
* @param consumables.attributes Attribute name or array of attribute names to revert.
|
|
399
|
-
* @param consumables.classes Class name or array of class names to revert.
|
|
400
|
-
* @param consumables.styles Style name or array of style names to revert.
|
|
401
469
|
*/
|
|
402
470
|
revert(consumables) {
|
|
403
471
|
if (consumables.name) {
|
|
404
472
|
this._canConsumeName = true;
|
|
405
473
|
}
|
|
406
|
-
for (const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* Helper method that adds consumables of a given type: attribute, class or style.
|
|
414
|
-
*
|
|
415
|
-
* Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `viewconsumable-invalid-attribute` when `class` or `style`
|
|
416
|
-
* type is provided - it should be handled separately by providing actual style/class type.
|
|
417
|
-
*
|
|
418
|
-
* @param type Type of the consumable item: `attributes`, `classes` or `styles`.
|
|
419
|
-
* @param item Consumable item or array of items.
|
|
420
|
-
*/
|
|
421
|
-
_add(type, item) {
|
|
422
|
-
const items = toArray(item);
|
|
423
|
-
const consumables = this._consumables[type];
|
|
424
|
-
for (const name of items) {
|
|
425
|
-
if (type === 'attributes' && (name === 'class' || name === 'style')) {
|
|
426
|
-
/**
|
|
427
|
-
* Class and style attributes should be handled separately in
|
|
428
|
-
* {@link module:engine/conversion/viewconsumable~ViewConsumable#add `ViewConsumable#add()`}.
|
|
429
|
-
*
|
|
430
|
-
* What you have done is trying to use:
|
|
431
|
-
*
|
|
432
|
-
* ```ts
|
|
433
|
-
* consumables.add( { attributes: [ 'class', 'style' ] } );
|
|
434
|
-
* ```
|
|
435
|
-
*
|
|
436
|
-
* While each class and style should be registered separately:
|
|
437
|
-
*
|
|
438
|
-
* ```ts
|
|
439
|
-
* consumables.add( { classes: 'some-class', styles: 'font-weight' } );
|
|
440
|
-
* ```
|
|
441
|
-
*
|
|
442
|
-
* @error viewconsumable-invalid-attribute
|
|
443
|
-
*/
|
|
444
|
-
throw new CKEditorError('viewconsumable-invalid-attribute', this);
|
|
474
|
+
for (const [name, token] of consumables.attributes) {
|
|
475
|
+
const value = this._attributes.get(name);
|
|
476
|
+
// Plain consumed attribute.
|
|
477
|
+
if (value === false) {
|
|
478
|
+
this._attributes.set(name, true);
|
|
479
|
+
continue;
|
|
445
480
|
}
|
|
446
|
-
|
|
447
|
-
if (
|
|
448
|
-
|
|
449
|
-
consumables.set(alsoName, true);
|
|
450
|
-
}
|
|
481
|
+
// Unknown attribute or not consumed.
|
|
482
|
+
if (value === undefined || value === true) {
|
|
483
|
+
continue;
|
|
451
484
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
*
|
|
457
|
-
* @param type Type of the consumable item: `attributes`, `classes` or `styles`.
|
|
458
|
-
* @param item Consumable item or array of items.
|
|
459
|
-
* @returns Returns `true` if all items can be consumed, `null` when one of the items cannot be
|
|
460
|
-
* consumed and `false` when one of the items is already consumed.
|
|
461
|
-
*/
|
|
462
|
-
_test(type, item) {
|
|
463
|
-
const items = toArray(item);
|
|
464
|
-
const consumables = this._consumables[type];
|
|
465
|
-
for (const name of items) {
|
|
466
|
-
if (type === 'attributes' && (name === 'class' || name === 'style')) {
|
|
467
|
-
const consumableName = name == 'class' ? 'classes' : 'styles';
|
|
468
|
-
// Check all classes/styles if class/style attribute is tested.
|
|
469
|
-
const value = this._test(consumableName, [...this._consumables[consumableName].keys()]);
|
|
470
|
-
if (value !== true) {
|
|
471
|
-
return value;
|
|
485
|
+
if (!token) {
|
|
486
|
+
// Tokenized attribute but token is not specified so revert all tokens.
|
|
487
|
+
for (const token of value.keys()) {
|
|
488
|
+
value.set(token, true);
|
|
472
489
|
}
|
|
473
490
|
}
|
|
474
491
|
else {
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
return null;
|
|
479
|
-
}
|
|
480
|
-
if (!value) {
|
|
481
|
-
return false;
|
|
492
|
+
const tokenValue = value.get(token);
|
|
493
|
+
if (tokenValue === false) {
|
|
494
|
+
value.set(token, true);
|
|
482
495
|
}
|
|
496
|
+
// Note that revert of consumed related styles is not handled.
|
|
483
497
|
}
|
|
484
498
|
}
|
|
485
|
-
return true;
|
|
486
499
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
for (const name of items) {
|
|
497
|
-
if (type === 'attributes' && (name === 'class' || name === 'style')) {
|
|
498
|
-
const consumableName = name == 'class' ? 'classes' : 'styles';
|
|
499
|
-
// If class or style is provided for consumption - consume them all.
|
|
500
|
-
this._consume(consumableName, [...this._consumables[consumableName].keys()]);
|
|
501
|
-
}
|
|
502
|
-
else {
|
|
503
|
-
consumables.set(name, false);
|
|
504
|
-
if (type == 'styles') {
|
|
505
|
-
for (const toConsume of this.element.document.stylesProcessor.getRelatedStyles(name)) {
|
|
506
|
-
consumables.set(toConsume, false);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
}
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Normalizes a {@link module:engine/conversion/viewconsumable~Consumables} or {@link module:engine/view/matcher~Match}
|
|
503
|
+
* to a {@link module:engine/view/element~NormalizedConsumables}.
|
|
504
|
+
*/
|
|
505
|
+
export function normalizeConsumables(consumables) {
|
|
506
|
+
const attributes = [];
|
|
507
|
+
if ('attributes' in consumables && consumables.attributes) {
|
|
508
|
+
normalizeConsumablePart(attributes, consumables.attributes);
|
|
511
509
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
510
|
+
if ('classes' in consumables && consumables.classes) {
|
|
511
|
+
normalizeConsumablePart(attributes, consumables.classes, 'class');
|
|
512
|
+
}
|
|
513
|
+
if ('styles' in consumables && consumables.styles) {
|
|
514
|
+
normalizeConsumablePart(attributes, consumables.styles, 'style');
|
|
515
|
+
}
|
|
516
|
+
return {
|
|
517
|
+
name: consumables.name || false,
|
|
518
|
+
attributes
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Normalizes a list of consumable attributes to a common tuple format.
|
|
523
|
+
*/
|
|
524
|
+
function normalizeConsumablePart(attributes, items, prefix) {
|
|
525
|
+
if (typeof items == 'string') {
|
|
526
|
+
attributes.push(prefix ? [prefix, items] : [items]);
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
for (const item of items) {
|
|
530
|
+
if (Array.isArray(item)) {
|
|
531
|
+
attributes.push(item);
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
attributes.push(prefix ? [prefix, item] : [item]);
|
|
533
535
|
}
|
|
534
536
|
}
|
|
535
537
|
}
|
|
@@ -105,4 +105,18 @@ export default class AttributeElement extends Element {
|
|
|
105
105
|
* @returns Clone of this element.
|
|
106
106
|
*/
|
|
107
107
|
_clone(deep?: boolean): this;
|
|
108
|
+
/**
|
|
109
|
+
* Used by {@link module:engine/view/element~Element#_mergeAttributesFrom} to verify if the given element can be merged without
|
|
110
|
+
* conflicts into this element.
|
|
111
|
+
*
|
|
112
|
+
* @internal
|
|
113
|
+
*/
|
|
114
|
+
_canMergeAttributesFrom(otherElement: AttributeElement): boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Used by {@link module:engine/view/element~Element#_subtractAttributesOf} to verify if the given element attributes
|
|
117
|
+
* can be fully subtracted from this element.
|
|
118
|
+
*
|
|
119
|
+
* @internal
|
|
120
|
+
*/
|
|
121
|
+
_canSubtractAttributesOf(otherElement: AttributeElement): boolean;
|
|
108
122
|
}
|
|
@@ -135,6 +135,32 @@ class AttributeElement extends Element {
|
|
|
135
135
|
cloned._id = this._id;
|
|
136
136
|
return cloned;
|
|
137
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Used by {@link module:engine/view/element~Element#_mergeAttributesFrom} to verify if the given element can be merged without
|
|
140
|
+
* conflicts into this element.
|
|
141
|
+
*
|
|
142
|
+
* @internal
|
|
143
|
+
*/
|
|
144
|
+
_canMergeAttributesFrom(otherElement) {
|
|
145
|
+
// Can't merge if any of elements have an id or a difference of priority.
|
|
146
|
+
if (this.id !== null || otherElement.id !== null || this.priority !== otherElement.priority) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
return super._canMergeAttributesFrom(otherElement);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Used by {@link module:engine/view/element~Element#_subtractAttributesOf} to verify if the given element attributes
|
|
153
|
+
* can be fully subtracted from this element.
|
|
154
|
+
*
|
|
155
|
+
* @internal
|
|
156
|
+
*/
|
|
157
|
+
_canSubtractAttributesOf(otherElement) {
|
|
158
|
+
// Can't subtract if any of elements have an id or a difference of priority.
|
|
159
|
+
if (this.id !== null || otherElement.id !== null || this.priority !== otherElement.priority) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
return super._canSubtractAttributesOf(otherElement);
|
|
163
|
+
}
|
|
138
164
|
}
|
|
139
165
|
AttributeElement.DEFAULT_PRIORITY = DEFAULT_PRIORITY;
|
|
140
166
|
export default AttributeElement;
|