@ckeditor/ckeditor5-engine 42.0.2-alpha.2 → 43.0.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/CHANGELOG.md +1 -820
- package/dist/dev-utils/model.d.ts +2 -0
- package/dist/dev-utils/view.d.ts +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +466 -271
- package/dist/index.js.map +1 -1
- package/dist/model/schema.d.ts +149 -51
- package/dist/view/observer/focusobserver.d.ts +12 -0
- package/dist/view/observer/mutationobserver.d.ts +34 -5
- package/dist/view/observer/selectionobserver.d.ts +1 -2
- package/dist/view/renderer.d.ts +12 -0
- package/dist/view/view.d.ts +1 -4
- package/package.json +2 -2
- package/src/conversion/upcasthelpers.js +0 -7
- package/src/dev-utils/model.d.ts +2 -0
- package/src/dev-utils/model.js +4 -2
- package/src/dev-utils/utils.js +7 -0
- package/src/dev-utils/view.d.ts +1 -0
- package/src/dev-utils/view.js +3 -0
- package/src/index.d.ts +3 -1
- package/src/index.js +2 -0
- package/src/model/model.js +1 -5
- package/src/model/schema.d.ts +149 -51
- package/src/model/schema.js +200 -70
- package/src/model/utils/insertcontent.js +21 -65
- package/src/view/domconverter.js +13 -9
- package/src/view/observer/compositionobserver.js +2 -0
- package/src/view/observer/focusobserver.d.ts +12 -0
- package/src/view/observer/focusobserver.js +55 -25
- package/src/view/observer/inputobserver.js +7 -5
- package/src/view/observer/mutationobserver.d.ts +34 -5
- package/src/view/observer/mutationobserver.js +8 -11
- package/src/view/observer/selectionobserver.d.ts +1 -2
- package/src/view/observer/selectionobserver.js +27 -9
- package/src/view/renderer.d.ts +12 -0
- package/src/view/renderer.js +111 -63
- package/src/view/view.d.ts +1 -4
- package/src/view/view.js +9 -0
package/src/model/schema.js
CHANGED
|
@@ -35,6 +35,24 @@ export default class Schema extends /* #__PURE__ */ ObservableMixin() {
|
|
|
35
35
|
* A dictionary containing attribute properties.
|
|
36
36
|
*/
|
|
37
37
|
this._attributeProperties = {};
|
|
38
|
+
/**
|
|
39
|
+
* Stores additional callbacks registered for schema items, which are evaluated when {@link ~Schema#checkChild} is called.
|
|
40
|
+
*
|
|
41
|
+
* Keys are schema item names for which the callbacks are registered. Values are arrays with the callbacks.
|
|
42
|
+
*
|
|
43
|
+
* Some checks are added under {@link ~Schema#_genericCheckSymbol} key, these are evaluated for every {@link ~Schema#checkChild} call.
|
|
44
|
+
*/
|
|
45
|
+
this._customChildChecks = new Map();
|
|
46
|
+
/**
|
|
47
|
+
* Stores additional callbacks registered for attribute names, which are evaluated when {@link ~Schema#checkAttribute} is called.
|
|
48
|
+
*
|
|
49
|
+
* Keys are schema attribute names for which the callbacks are registered. Values are arrays with the callbacks.
|
|
50
|
+
*
|
|
51
|
+
* Some checks are added under {@link ~Schema#_genericCheckSymbol} key, these are evaluated for every
|
|
52
|
+
* {@link ~Schema#checkAttribute} call.
|
|
53
|
+
*/
|
|
54
|
+
this._customAttributeChecks = new Map();
|
|
55
|
+
this._genericCheckSymbol = Symbol('$generic');
|
|
38
56
|
this.decorate('checkChild');
|
|
39
57
|
this.decorate('checkAttribute');
|
|
40
58
|
this.on('checkAttribute', (evt, args) => {
|
|
@@ -314,7 +332,7 @@ export default class Schema extends /* #__PURE__ */ ObservableMixin() {
|
|
|
314
332
|
return !!(def.isContent || def.isObject);
|
|
315
333
|
}
|
|
316
334
|
/**
|
|
317
|
-
* Checks whether the given node
|
|
335
|
+
* Checks whether the given node can be a child of the given context.
|
|
318
336
|
*
|
|
319
337
|
* ```ts
|
|
320
338
|
* schema.checkChild( model.document.getRoot(), paragraph ); // -> false
|
|
@@ -322,28 +340,34 @@ export default class Schema extends /* #__PURE__ */ ObservableMixin() {
|
|
|
322
340
|
* schema.register( 'paragraph', {
|
|
323
341
|
* allowIn: '$root'
|
|
324
342
|
* } );
|
|
343
|
+
*
|
|
325
344
|
* schema.checkChild( model.document.getRoot(), paragraph ); // -> true
|
|
326
345
|
* ```
|
|
327
346
|
*
|
|
328
|
-
*
|
|
329
|
-
* schema
|
|
330
|
-
*
|
|
331
|
-
*
|
|
347
|
+
* Both {@link module:engine/model/schema~Schema#addChildCheck callback checks} and declarative rules (added when
|
|
348
|
+
* {@link module:engine/model/schema~Schema#register registering} and {@link module:engine/model/schema~Schema#extend extending} items)
|
|
349
|
+
* are evaluated when this method is called.
|
|
350
|
+
*
|
|
351
|
+
* Note that callback checks have bigger priority than declarative rules checks and may overwrite them.
|
|
352
|
+
*
|
|
353
|
+
* Note that when verifying whether the given node can be a child of the given context, the schema also verifies the entire
|
|
354
|
+
* context – from its root to its last element. Therefore, it is possible for `checkChild()` to return `false` even though
|
|
355
|
+
* the `context` last element can contain the checked child. It happens if one of the `context` elements does not allow its child.
|
|
356
|
+
* When `context` is verified, {@link module:engine/model/schema~Schema#addChildCheck custom checks} are considered as well.
|
|
332
357
|
*
|
|
333
358
|
* @fires checkChild
|
|
334
359
|
* @param context The context in which the child will be checked.
|
|
335
360
|
* @param def The child to check.
|
|
336
361
|
*/
|
|
337
362
|
checkChild(context, def) {
|
|
338
|
-
// Note: context and
|
|
363
|
+
// Note: `context` and `def` are already normalized here to `SchemaContext` and `SchemaCompiledItemDefinition`.
|
|
339
364
|
if (!def) {
|
|
340
365
|
return false;
|
|
341
366
|
}
|
|
342
|
-
return this._checkContextMatch(
|
|
367
|
+
return this._checkContextMatch(context, def);
|
|
343
368
|
}
|
|
344
369
|
/**
|
|
345
|
-
* Checks whether the given attribute can be applied in the given context (on the last
|
|
346
|
-
* item of the context).
|
|
370
|
+
* Checks whether the given attribute can be applied in the given context (on the last item of the context).
|
|
347
371
|
*
|
|
348
372
|
* ```ts
|
|
349
373
|
* schema.checkAttribute( textNode, 'bold' ); // -> false
|
|
@@ -351,23 +375,37 @@ export default class Schema extends /* #__PURE__ */ ObservableMixin() {
|
|
|
351
375
|
* schema.extend( '$text', {
|
|
352
376
|
* allowAttributes: 'bold'
|
|
353
377
|
* } );
|
|
378
|
+
*
|
|
354
379
|
* schema.checkAttribute( textNode, 'bold' ); // -> true
|
|
355
380
|
* ```
|
|
356
381
|
*
|
|
382
|
+
* Both {@link module:engine/model/schema~Schema#addAttributeCheck callback checks} and declarative rules (added when
|
|
383
|
+
* {@link module:engine/model/schema~Schema#register registering} and {@link module:engine/model/schema~Schema#extend extending} items)
|
|
384
|
+
* are evaluated when this method is called.
|
|
385
|
+
*
|
|
386
|
+
* Note that callback checks have bigger priority than declarative rules checks and may overwrite them.
|
|
387
|
+
*
|
|
357
388
|
* @fires checkAttribute
|
|
358
389
|
* @param context The context in which the attribute will be checked.
|
|
390
|
+
* @param attributeName Name of attribute to check in the given context.
|
|
359
391
|
*/
|
|
360
392
|
checkAttribute(context, attributeName) {
|
|
393
|
+
// Note: `context` is already normalized here to `SchemaContext`.
|
|
361
394
|
const def = this.getDefinition(context.last);
|
|
362
395
|
if (!def) {
|
|
363
396
|
return false;
|
|
364
397
|
}
|
|
365
|
-
|
|
398
|
+
// First, check all attribute checks declared as callbacks.
|
|
399
|
+
// Note that `_evaluateAttributeChecks()` will return `undefined` if neither child check was applicable (no decision was made).
|
|
400
|
+
const isAllowed = this._evaluateAttributeChecks(context, attributeName);
|
|
401
|
+
// If the decision was not made inside attribute check callbacks, then use declarative rules.
|
|
402
|
+
return isAllowed !== undefined ? isAllowed : def.allowAttributes.includes(attributeName);
|
|
366
403
|
}
|
|
367
404
|
/**
|
|
368
405
|
* Checks whether the given element (`elementToMerge`) can be merged with the specified base element (`positionOrBaseElement`).
|
|
369
406
|
*
|
|
370
|
-
* In other words –
|
|
407
|
+
* In other words – both elements are not a limit elements and whether `elementToMerge`'s children
|
|
408
|
+
* {@link #checkChild are allowed} in the `positionOrBaseElement`.
|
|
371
409
|
*
|
|
372
410
|
* This check ensures that elements merged with {@link module:engine/model/writer~Writer#merge `Writer#merge()`}
|
|
373
411
|
* will be valid.
|
|
@@ -400,6 +438,9 @@ export default class Schema extends /* #__PURE__ */ ObservableMixin() {
|
|
|
400
438
|
}
|
|
401
439
|
return this.checkMerge(nodeBefore, nodeAfter);
|
|
402
440
|
}
|
|
441
|
+
if (this.isLimit(positionOrBaseElement) || this.isLimit(elementToMerge)) {
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
403
444
|
for (const child of elementToMerge.getChildren()) {
|
|
404
445
|
if (!this.checkChild(positionOrBaseElement, child)) {
|
|
405
446
|
return false;
|
|
@@ -412,110 +453,139 @@ export default class Schema extends /* #__PURE__ */ ObservableMixin() {
|
|
|
412
453
|
*
|
|
413
454
|
* Callbacks allow you to implement rules which are not otherwise possible to achieve
|
|
414
455
|
* by using the declarative API of {@link module:engine/model/schema~SchemaItemDefinition}.
|
|
415
|
-
* For example, by using this method you can disallow elements in specific contexts.
|
|
416
456
|
*
|
|
417
|
-
*
|
|
418
|
-
* you can use that event instead.
|
|
457
|
+
* Note that callback checks have bigger priority than declarative rules checks and may overwrite them.
|
|
419
458
|
*
|
|
420
|
-
*
|
|
459
|
+
* For example, by using this method you can disallow elements in specific contexts:
|
|
421
460
|
*
|
|
422
461
|
* ```ts
|
|
423
|
-
* // Disallow heading1
|
|
462
|
+
* // Disallow `heading1` inside a `blockQuote` that is inside a table.
|
|
424
463
|
* schema.addChildCheck( ( context, childDefinition ) => {
|
|
425
|
-
* if ( context.endsWith( 'blockQuote' )
|
|
464
|
+
* if ( context.endsWith( 'tableCell blockQuote' ) ) {
|
|
426
465
|
* return false;
|
|
427
466
|
* }
|
|
467
|
+
* }, 'heading1' );
|
|
468
|
+
* ```
|
|
469
|
+
*
|
|
470
|
+
* You can skip the optional `itemName` parameter to evaluate the callback for every `checkChild()` call.
|
|
471
|
+
*
|
|
472
|
+
* ```ts
|
|
473
|
+
* // Inside specific custom element, allow only children, which allows for a specific attribute.
|
|
474
|
+
* schema.addChildCheck( ( context, childDefinition ) => {
|
|
475
|
+
* if ( context.endsWith( 'myElement' ) ) {
|
|
476
|
+
* return childDefinition.allowAttributes.includes( 'myAttribute' );
|
|
477
|
+
* }
|
|
428
478
|
* } );
|
|
429
479
|
* ```
|
|
430
480
|
*
|
|
431
|
-
*
|
|
481
|
+
* Please note that the generic callbacks may affect the editor performance and should be avoided if possible.
|
|
482
|
+
*
|
|
483
|
+
* When one of the callbacks makes a decision (returns `true` or `false`) the processing is finished and other callbacks are not fired.
|
|
484
|
+
* Callbacks are fired in the order they were added, however generic callbacks are fired before callbacks added for a specified item.
|
|
485
|
+
*
|
|
486
|
+
* You can also use `checkChild` event, if you need even better control. The result from the example above could also be
|
|
487
|
+
* achieved with following event callback:
|
|
432
488
|
*
|
|
433
489
|
* ```ts
|
|
434
490
|
* schema.on( 'checkChild', ( evt, args ) => {
|
|
435
491
|
* const context = args[ 0 ];
|
|
436
492
|
* const childDefinition = args[ 1 ];
|
|
437
493
|
*
|
|
438
|
-
* if ( context.endsWith( '
|
|
494
|
+
* if ( context.endsWith( 'myElement' ) ) {
|
|
439
495
|
* // Prevent next listeners from being called.
|
|
440
496
|
* evt.stop();
|
|
441
|
-
* // Set the checkChild()
|
|
442
|
-
* evt.return =
|
|
497
|
+
* // Set the `checkChild()` return value.
|
|
498
|
+
* evt.return = childDefinition.allowAttributes.includes( 'myAttribute' );
|
|
443
499
|
* }
|
|
444
500
|
* }, { priority: 'high' } );
|
|
445
501
|
* ```
|
|
446
502
|
*
|
|
503
|
+
* Note that the callback checks and declarative rules checks are processed on `normal` priority.
|
|
504
|
+
*
|
|
505
|
+
* Adding callbacks this way can also negatively impact editor performance.
|
|
506
|
+
*
|
|
447
507
|
* @param callback The callback to be called. It is called with two parameters:
|
|
448
508
|
* {@link module:engine/model/schema~SchemaContext} (context) instance and
|
|
449
|
-
* {@link module:engine/model/schema~SchemaCompiledItemDefinition} (
|
|
450
|
-
*
|
|
451
|
-
*
|
|
509
|
+
* {@link module:engine/model/schema~SchemaCompiledItemDefinition} (definition). The callback may return `true/false` to override
|
|
510
|
+
* `checkChild()`'s return value. If it does not return a boolean value, the default algorithm (or other callbacks) will define
|
|
511
|
+
* `checkChild()`'s return value.
|
|
512
|
+
* @param itemName Name of the schema item for which the callback is registered. If specified, the callback will be run only for
|
|
513
|
+
* `checkChild()` calls which `def` parameter matches the `itemName`. Otherwise, the callback will run for every `checkChild` call.
|
|
452
514
|
*/
|
|
453
|
-
addChildCheck(callback) {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
if (!childDef) {
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
const retValue = callback(ctx, childDef);
|
|
462
|
-
if (typeof retValue == 'boolean') {
|
|
463
|
-
evt.stop();
|
|
464
|
-
evt.return = retValue;
|
|
465
|
-
}
|
|
466
|
-
}, { priority: 'high' });
|
|
515
|
+
addChildCheck(callback, itemName) {
|
|
516
|
+
const key = itemName !== undefined ? itemName : this._genericCheckSymbol;
|
|
517
|
+
const checks = this._customChildChecks.get(key) || [];
|
|
518
|
+
checks.push(callback);
|
|
519
|
+
this._customChildChecks.set(key, checks);
|
|
467
520
|
}
|
|
468
521
|
/**
|
|
469
522
|
* Allows registering a callback to the {@link #checkAttribute} method calls.
|
|
470
523
|
*
|
|
471
524
|
* Callbacks allow you to implement rules which are not otherwise possible to achieve
|
|
472
525
|
* by using the declarative API of {@link module:engine/model/schema~SchemaItemDefinition}.
|
|
473
|
-
* For example, by using this method you can disallow attribute if node to which it is applied
|
|
474
|
-
* is contained within some other element (e.g. you want to disallow `bold` on `$text` within `heading1`).
|
|
475
526
|
*
|
|
476
|
-
*
|
|
477
|
-
*
|
|
527
|
+
* Note that callback checks have bigger priority than declarative rules checks and may overwrite them.
|
|
528
|
+
*
|
|
529
|
+
* For example, by using this method you can disallow setting attributes on nodes in specific contexts:
|
|
530
|
+
*
|
|
531
|
+
* ```ts
|
|
532
|
+
* // Disallow setting `bold` on text inside `heading1` element:
|
|
533
|
+
* schema.addAttributeCheck( context => {
|
|
534
|
+
* if ( context.endsWith( 'heading1 $text' ) ) {
|
|
535
|
+
* return false;
|
|
536
|
+
* }
|
|
537
|
+
* }, 'bold' );
|
|
538
|
+
* ```
|
|
478
539
|
*
|
|
479
|
-
*
|
|
540
|
+
* You can skip the optional `attributeName` parameter to evaluate the callback for every `checkAttribute()` call.
|
|
480
541
|
*
|
|
481
542
|
* ```ts
|
|
482
|
-
* // Disallow
|
|
543
|
+
* // Disallow formatting attributes on text inside custom `myTitle` element:
|
|
483
544
|
* schema.addAttributeCheck( ( context, attributeName ) => {
|
|
484
|
-
* if ( context.endsWith( '
|
|
545
|
+
* if ( context.endsWith( 'myTitle $text' ) && schema.getAttributeProperties( attributeName ).isFormatting ) {
|
|
485
546
|
* return false;
|
|
486
547
|
* }
|
|
487
548
|
* } );
|
|
488
549
|
* ```
|
|
489
550
|
*
|
|
490
|
-
*
|
|
551
|
+
* Please note that the generic callbacks may affect the editor performance and should be avoided if possible.
|
|
552
|
+
*
|
|
553
|
+
* When one of the callbacks makes a decision (returns `true` or `false`) the processing is finished and other callbacks are not fired.
|
|
554
|
+
* Callbacks are fired in the order they were added, however generic callbacks are fired before callbacks added for a specified item.
|
|
555
|
+
*
|
|
556
|
+
* You can also use {@link #event:checkAttribute} event, if you need even better control. The result from the example above could also
|
|
557
|
+
* be achieved with following event callback:
|
|
491
558
|
*
|
|
492
559
|
* ```ts
|
|
493
560
|
* schema.on( 'checkAttribute', ( evt, args ) => {
|
|
494
561
|
* const context = args[ 0 ];
|
|
495
562
|
* const attributeName = args[ 1 ];
|
|
496
563
|
*
|
|
497
|
-
* if ( context.endsWith( '
|
|
564
|
+
* if ( context.endsWith( 'myTitle $text' ) && schema.getAttributeProperties( attributeName ).isFormatting ) {
|
|
498
565
|
* // Prevent next listeners from being called.
|
|
499
566
|
* evt.stop();
|
|
500
|
-
* // Set the checkAttribute()
|
|
567
|
+
* // Set the `checkAttribute()` return value.
|
|
501
568
|
* evt.return = false;
|
|
502
569
|
* }
|
|
503
570
|
* }, { priority: 'high' } );
|
|
504
571
|
* ```
|
|
505
572
|
*
|
|
573
|
+
* Note that the callback checks and declarative rules checks are processed on `normal` priority.
|
|
574
|
+
*
|
|
575
|
+
* Adding callbacks this way can also negatively impact editor performance.
|
|
576
|
+
*
|
|
506
577
|
* @param callback The callback to be called. It is called with two parameters:
|
|
507
|
-
* {@link module:engine/model/schema~SchemaContext
|
|
508
|
-
*
|
|
509
|
-
*
|
|
578
|
+
* {@link module:engine/model/schema~SchemaContext `context`} and attribute name. The callback may return `true` or `false`, to
|
|
579
|
+
* override `checkAttribute()`'s return value. If it does not return a boolean value, the default algorithm (or other callbacks)
|
|
580
|
+
* will define `checkAttribute()`'s return value.
|
|
581
|
+
* @param attributeName Name of the attribute for which the callback is registered. If specified, the callback will be run only for
|
|
582
|
+
* `checkAttribute()` calls with matching `attributeName`. Otherwise, the callback will run for every `checkAttribute()` call.
|
|
510
583
|
*/
|
|
511
|
-
addAttributeCheck(callback) {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
evt.return = retValue;
|
|
517
|
-
}
|
|
518
|
-
}, { priority: 'high' });
|
|
584
|
+
addAttributeCheck(callback, attributeName) {
|
|
585
|
+
const key = attributeName !== undefined ? attributeName : this._genericCheckSymbol;
|
|
586
|
+
const checks = this._customAttributeChecks.get(key) || [];
|
|
587
|
+
checks.push(callback);
|
|
588
|
+
this._customAttributeChecks.set(key, checks);
|
|
519
589
|
}
|
|
520
590
|
/**
|
|
521
591
|
* This method allows assigning additional metadata to the model attributes. For example,
|
|
@@ -861,20 +931,64 @@ export default class Schema extends /* #__PURE__ */ ObservableMixin() {
|
|
|
861
931
|
// Compile final definitions. Unnecessary properties are removed and some additional cleaning is applied.
|
|
862
932
|
this._compiledDefinitions = compileDefinitions(definitions);
|
|
863
933
|
}
|
|
864
|
-
_checkContextMatch(
|
|
865
|
-
const
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
934
|
+
_checkContextMatch(context, def) {
|
|
935
|
+
const parentItem = context.last;
|
|
936
|
+
// First, check all child checks declared as callbacks.
|
|
937
|
+
// Note that `_evaluateChildChecks()` will return `undefined` if neither child check was applicable (no decision was made).
|
|
938
|
+
let isAllowed = this._evaluateChildChecks(context, def);
|
|
939
|
+
// If the decision was not made inside child check callbacks, then use declarative rules.
|
|
940
|
+
isAllowed = isAllowed !== undefined ? isAllowed : def.allowIn.includes(parentItem.name);
|
|
941
|
+
// If the item is not allowed in the `context`, return `false`.
|
|
942
|
+
if (!isAllowed) {
|
|
943
|
+
return false;
|
|
874
944
|
}
|
|
875
|
-
|
|
945
|
+
// If the item is allowed, recursively verify the rest of the `context`.
|
|
946
|
+
const parentItemDefinition = this.getDefinition(parentItem);
|
|
947
|
+
const parentContext = context.trimLast();
|
|
948
|
+
// One of the items in the original `context` did not have a definition specified. In this case, the whole context is disallowed.
|
|
949
|
+
if (!parentItemDefinition) {
|
|
876
950
|
return false;
|
|
877
951
|
}
|
|
952
|
+
// Whole `context` was verified and passed checks.
|
|
953
|
+
if (parentContext.length == 0) {
|
|
954
|
+
return true;
|
|
955
|
+
}
|
|
956
|
+
// Verify "truncated" parent context. The last item of the original context is now the definition to check.
|
|
957
|
+
return this._checkContextMatch(parentContext, parentItemDefinition);
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Calls child check callbacks to decide whether `def` is allowed in `context`. It uses both generic and specific (defined for `def`
|
|
961
|
+
* item) callbacks. If neither callback makes a decision, `undefined` is returned.
|
|
962
|
+
*
|
|
963
|
+
* Note that the first callback that makes a decision "wins", i.e., if any callback returns `true` or `false`, then the processing
|
|
964
|
+
* is over and that result is returned.
|
|
965
|
+
*/
|
|
966
|
+
_evaluateChildChecks(context, def) {
|
|
967
|
+
const genericChecks = this._customChildChecks.get(this._genericCheckSymbol) || [];
|
|
968
|
+
const childChecks = this._customChildChecks.get(def.name) || [];
|
|
969
|
+
for (const check of [...genericChecks, ...childChecks]) {
|
|
970
|
+
const result = check(context, def);
|
|
971
|
+
if (result !== undefined) {
|
|
972
|
+
return result;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Calls attribute check callbacks to decide whether `attributeName` can be set on the last element of `context`. It uses both
|
|
978
|
+
* generic and specific (defined for `attributeName`) callbacks. If neither callback makes a decision, `undefined` is returned.
|
|
979
|
+
*
|
|
980
|
+
* Note that the first callback that makes a decision "wins", i.e., if any callback returns `true` or `false`, then the processing
|
|
981
|
+
* is over and that result is returned.
|
|
982
|
+
*/
|
|
983
|
+
_evaluateAttributeChecks(context, attributeName) {
|
|
984
|
+
const genericChecks = this._customAttributeChecks.get(this._genericCheckSymbol) || [];
|
|
985
|
+
const childChecks = this._customAttributeChecks.get(attributeName) || [];
|
|
986
|
+
for (const check of [...genericChecks, ...childChecks]) {
|
|
987
|
+
const result = check(context, attributeName);
|
|
988
|
+
if (result !== undefined) {
|
|
989
|
+
return result;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
878
992
|
}
|
|
879
993
|
/**
|
|
880
994
|
* Takes a flat range and an attribute name. Traverses the range recursively and deeply to find and return all ranges
|
|
@@ -1050,6 +1164,22 @@ export class SchemaContext {
|
|
|
1050
1164
|
ctx._items = [...this._items, ...ctx._items];
|
|
1051
1165
|
return ctx;
|
|
1052
1166
|
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Returns a new schema context that is based on this context but has the last item removed.
|
|
1169
|
+
*
|
|
1170
|
+
* ```ts
|
|
1171
|
+
* const ctxParagraph = new SchemaContext( [ '$root', 'blockQuote', 'paragraph' ] );
|
|
1172
|
+
* const ctxBlockQuote = ctxParagraph.trimLast(); // Items in `ctxBlockQuote` are: `$root` an `blockQuote`.
|
|
1173
|
+
* const ctxRoot = ctxBlockQuote.trimLast(); // Items in `ctxRoot` are: `$root`.
|
|
1174
|
+
* ```
|
|
1175
|
+
*
|
|
1176
|
+
* @returns A new reduced schema context instance.
|
|
1177
|
+
*/
|
|
1178
|
+
trimLast() {
|
|
1179
|
+
const ctx = new SchemaContext([]);
|
|
1180
|
+
ctx._items = this._items.slice(0, -1);
|
|
1181
|
+
return ctx;
|
|
1182
|
+
}
|
|
1053
1183
|
/**
|
|
1054
1184
|
* Gets an item on the given index.
|
|
1055
1185
|
*/
|
|
@@ -299,25 +299,14 @@ class Insertion {
|
|
|
299
299
|
* Handles insertion of a single node.
|
|
300
300
|
*/
|
|
301
301
|
_handleNode(node) {
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
this.
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
// Try to find a place for the given node.
|
|
310
|
-
// Check if a node can be inserted in the given position or it would be accepted if a paragraph would be inserted.
|
|
311
|
-
// Inserts the auto paragraph if it would allow for insertion.
|
|
312
|
-
let isAllowed = this._checkAndAutoParagraphToAllowedPosition(node);
|
|
313
|
-
if (!isAllowed) {
|
|
314
|
-
// Split the position.parent's branch up to a point where the node can be inserted.
|
|
315
|
-
// If it isn't allowed in the whole branch, then of course don't split anything.
|
|
316
|
-
isAllowed = this._checkAndSplitToAllowedPosition(node);
|
|
317
|
-
if (!isAllowed) {
|
|
302
|
+
// Split the position.parent's branch up to a point where the node can be inserted.
|
|
303
|
+
// If it isn't allowed in the whole branch, then of course don't split anything.
|
|
304
|
+
if (!this._checkAndSplitToAllowedPosition(node)) {
|
|
305
|
+
// Handle element children if it's not an object (strip container).
|
|
306
|
+
if (!this.schema.isObject(node)) {
|
|
318
307
|
this._handleDisallowedNode(node);
|
|
319
|
-
return;
|
|
320
308
|
}
|
|
309
|
+
return;
|
|
321
310
|
}
|
|
322
311
|
// Add node to the current temporary DocumentFragment.
|
|
323
312
|
this._appendToFragment(node);
|
|
@@ -354,19 +343,6 @@ class Insertion {
|
|
|
354
343
|
this.position = livePosition.toPosition();
|
|
355
344
|
livePosition.detach();
|
|
356
345
|
}
|
|
357
|
-
/**
|
|
358
|
-
* @param node The object element.
|
|
359
|
-
*/
|
|
360
|
-
_handleObject(node) {
|
|
361
|
-
// Try finding it a place in the tree.
|
|
362
|
-
if (this._checkAndSplitToAllowedPosition(node)) {
|
|
363
|
-
this._appendToFragment(node);
|
|
364
|
-
}
|
|
365
|
-
// Try autoparagraphing.
|
|
366
|
-
else {
|
|
367
|
-
this._tryAutoparagraphing(node);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
346
|
/**
|
|
371
347
|
* @param node The disallowed node which needs to be handled.
|
|
372
348
|
*/
|
|
@@ -375,10 +351,6 @@ class Insertion {
|
|
|
375
351
|
if (node.is('element')) {
|
|
376
352
|
this.handleNodes(node.getChildren());
|
|
377
353
|
}
|
|
378
|
-
// If text is not allowed, try autoparagraphing it.
|
|
379
|
-
else {
|
|
380
|
-
this._tryAutoparagraphing(node);
|
|
381
|
-
}
|
|
382
354
|
}
|
|
383
355
|
/**
|
|
384
356
|
* Append a node to the temporary DocumentFragment.
|
|
@@ -594,37 +566,9 @@ class Insertion {
|
|
|
594
566
|
this.model.schema.checkMerge(node, nextSibling);
|
|
595
567
|
}
|
|
596
568
|
/**
|
|
597
|
-
*
|
|
598
|
-
*
|
|
599
|
-
* @param node The node which needs to be autoparagraphed.
|
|
600
|
-
*/
|
|
601
|
-
_tryAutoparagraphing(node) {
|
|
602
|
-
const paragraph = this.writer.createElement('paragraph');
|
|
603
|
-
// Do not autoparagraph if the paragraph won't be allowed there,
|
|
604
|
-
// cause that would lead to an infinite loop. The paragraph would be rejected in
|
|
605
|
-
// the next _handleNode() call and we'd be here again.
|
|
606
|
-
if (this._getAllowedIn(this.position.parent, paragraph) && this.schema.checkChild(paragraph, node)) {
|
|
607
|
-
paragraph._appendChild(node);
|
|
608
|
-
this._handleNode(paragraph);
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
/**
|
|
612
|
-
* Checks if a node can be inserted in the given position or it would be accepted if a paragraph would be inserted.
|
|
613
|
-
* It also handles inserting the paragraph.
|
|
614
|
-
*
|
|
615
|
-
* @returns Whether an allowed position was found.
|
|
616
|
-
* `false` is returned if the node isn't allowed at the current position or in auto paragraph, `true` if was.
|
|
569
|
+
* Inserts a paragraph and moves the insertion position into it.
|
|
617
570
|
*/
|
|
618
|
-
|
|
619
|
-
if (this.schema.checkChild(this.position.parent, node)) {
|
|
620
|
-
return true;
|
|
621
|
-
}
|
|
622
|
-
// Do not auto paragraph if the paragraph won't be allowed there,
|
|
623
|
-
// cause that would lead to an infinite loop. The paragraph would be rejected in
|
|
624
|
-
// the next _handleNode() call and we'd be here again.
|
|
625
|
-
if (!this.schema.checkChild(this.position.parent, 'paragraph') || !this.schema.checkChild('paragraph', node)) {
|
|
626
|
-
return false;
|
|
627
|
-
}
|
|
571
|
+
_insertAutoParagraph() {
|
|
628
572
|
// Insert nodes collected in temporary DocumentFragment if the position parent needs change to process further nodes.
|
|
629
573
|
this._insertPartialFragment();
|
|
630
574
|
// Insert a paragraph and move insertion position to it.
|
|
@@ -633,7 +577,6 @@ class Insertion {
|
|
|
633
577
|
this._setAffectedBoundaries(this.position);
|
|
634
578
|
this._lastAutoParagraph = paragraph;
|
|
635
579
|
this.position = this.writer.createPositionAt(paragraph, 0);
|
|
636
|
-
return true;
|
|
637
580
|
}
|
|
638
581
|
/**
|
|
639
582
|
* @returns Whether an allowed position was found.
|
|
@@ -680,18 +623,31 @@ class Insertion {
|
|
|
680
623
|
this.canMergeWith.add(this.position.nodeAfter);
|
|
681
624
|
}
|
|
682
625
|
}
|
|
626
|
+
// At this point, we split elements up to the parent in which `node` is allowed.
|
|
627
|
+
// Note that `_getAllowedIn()` checks if the `node` is allowed either directly, or when auto-paragraphed.
|
|
628
|
+
// So, let's check if the `node` is allowed directly. If not, we need to auto-paragraph it.
|
|
629
|
+
if (!this.schema.checkChild(this.position.parent, node)) {
|
|
630
|
+
this._insertAutoParagraph();
|
|
631
|
+
}
|
|
683
632
|
return true;
|
|
684
633
|
}
|
|
685
634
|
/**
|
|
686
635
|
* Gets the element in which the given node is allowed. It checks the passed element and all its ancestors.
|
|
687
636
|
*
|
|
637
|
+
* It also verifies if auto-paragraphing could help.
|
|
638
|
+
*
|
|
688
639
|
* @param contextElement The element in which context the node should be checked.
|
|
689
640
|
* @param childNode The node to check.
|
|
690
641
|
*/
|
|
691
642
|
_getAllowedIn(contextElement, childNode) {
|
|
643
|
+
// Check if a node can be inserted in the given context...
|
|
692
644
|
if (this.schema.checkChild(contextElement, childNode)) {
|
|
693
645
|
return contextElement;
|
|
694
646
|
}
|
|
647
|
+
// ...or it would be accepted if a paragraph would be inserted.
|
|
648
|
+
if (this.schema.checkChild(contextElement, 'paragraph') && this.schema.checkChild('paragraph', childNode)) {
|
|
649
|
+
return contextElement;
|
|
650
|
+
}
|
|
695
651
|
// If the child wasn't allowed in the context element and the element is a limit there's no point in
|
|
696
652
|
// checking any further towards the root. This is it: the limit is unsplittable and there's nothing
|
|
697
653
|
// we can do about it. Without this check, the algorithm will analyze parent of the limit and may create
|
package/src/view/domconverter.js
CHANGED
|
@@ -1288,24 +1288,28 @@ export default class DomConverter {
|
|
|
1288
1288
|
startPosition: getNext ? ViewPosition._createAfter(node) : ViewPosition._createBefore(node),
|
|
1289
1289
|
direction: getNext ? 'forward' : 'backward'
|
|
1290
1290
|
});
|
|
1291
|
-
for (const
|
|
1291
|
+
for (const { item } of treeWalker) {
|
|
1292
|
+
// Found a text node in the same container element.
|
|
1293
|
+
if (item.is('$textProxy')) {
|
|
1294
|
+
return item;
|
|
1295
|
+
}
|
|
1296
|
+
// Found a transparent element, skip it and continue inside it.
|
|
1297
|
+
else if (item.is('element') && item.getCustomProperty('dataPipeline:transparentRendering')) {
|
|
1298
|
+
continue;
|
|
1299
|
+
}
|
|
1292
1300
|
// <br> found – it works like a block boundary, so do not scan further.
|
|
1293
|
-
if (
|
|
1301
|
+
else if (item.is('element', 'br')) {
|
|
1294
1302
|
return null;
|
|
1295
1303
|
}
|
|
1296
1304
|
// Found an inline object (for example an image).
|
|
1297
|
-
else if (this._isInlineObjectElement(
|
|
1298
|
-
return
|
|
1305
|
+
else if (this._isInlineObjectElement(item)) {
|
|
1306
|
+
return item;
|
|
1299
1307
|
}
|
|
1300
1308
|
// ViewContainerElement is found on a way to next ViewText node, so given `node` was first/last
|
|
1301
1309
|
// text node in its container element.
|
|
1302
|
-
else if (
|
|
1310
|
+
else if (item.is('containerElement')) {
|
|
1303
1311
|
return null;
|
|
1304
1312
|
}
|
|
1305
|
-
// Found a text node in the same container element.
|
|
1306
|
-
else if (value.item.is('$textProxy')) {
|
|
1307
|
-
return value.item;
|
|
1308
|
-
}
|
|
1309
1313
|
}
|
|
1310
1314
|
return null;
|
|
1311
1315
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* @module engine/view/observer/compositionobserver
|
|
7
7
|
*/
|
|
8
8
|
import DomEventObserver from './domeventobserver.js';
|
|
9
|
+
// @if CK_DEBUG_TYPING // const { _debouncedLine } = require( '../../dev-utils/utils.js' );
|
|
9
10
|
/**
|
|
10
11
|
* {@link module:engine/view/document~Document#event:compositionstart Compositionstart},
|
|
11
12
|
* {@link module:engine/view/document~Document#event:compositionupdate compositionupdate} and
|
|
@@ -48,6 +49,7 @@ export default class CompositionObserver extends DomEventObserver {
|
|
|
48
49
|
*/
|
|
49
50
|
onDomEvent(domEvent) {
|
|
50
51
|
// @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
|
|
52
|
+
// @if CK_DEBUG_TYPING // _debouncedLine();
|
|
51
53
|
// @if CK_DEBUG_TYPING // console.group( `%c[CompositionObserver]%c ${ domEvent.type }`, 'color: green', '' );
|
|
52
54
|
// @if CK_DEBUG_TYPING // }
|
|
53
55
|
this.fire(domEvent.type, domEvent, {
|
|
@@ -47,6 +47,18 @@ export default class FocusObserver extends DomEventObserver<'focus' | 'blur'> {
|
|
|
47
47
|
* @inheritDoc
|
|
48
48
|
*/
|
|
49
49
|
destroy(): void;
|
|
50
|
+
/**
|
|
51
|
+
* The `focus` event handler.
|
|
52
|
+
*/
|
|
53
|
+
private _handleFocus;
|
|
54
|
+
/**
|
|
55
|
+
* The `blur` event handler.
|
|
56
|
+
*/
|
|
57
|
+
private _handleBlur;
|
|
58
|
+
/**
|
|
59
|
+
* Clears timeout.
|
|
60
|
+
*/
|
|
61
|
+
private _clearTimeout;
|
|
50
62
|
}
|
|
51
63
|
/**
|
|
52
64
|
* Fired when one of the editables gets focus.
|