@ckeditor/ckeditor5-engine 45.2.0 → 45.2.1-alpha.1
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 +79 -46
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/conversion/mapper.d.ts +11 -27
- package/src/conversion/mapper.js +76 -47
- package/src/view/domconverter.js +7 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ckeditor/ckeditor5-engine",
|
|
3
|
-
"version": "45.2.
|
|
3
|
+
"version": "45.2.1-alpha.1",
|
|
4
4
|
"description": "The editing engine of CKEditor 5 – the best browser-based rich text editor.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"wysiwyg",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"type": "module",
|
|
25
25
|
"main": "src/index.js",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@ckeditor/ckeditor5-utils": "45.2.
|
|
27
|
+
"@ckeditor/ckeditor5-utils": "45.2.1-alpha.1",
|
|
28
28
|
"es-toolkit": "1.32.0"
|
|
29
29
|
},
|
|
30
30
|
"author": "CKSource (http://cksource.com/)",
|
|
@@ -357,6 +357,17 @@ export default class Mapper extends /* #__PURE__ */ Mapper_base {
|
|
|
357
357
|
* @returns View position mapped to `targetModelOffset`.
|
|
358
358
|
*/
|
|
359
359
|
private _findPositionStartingFrom;
|
|
360
|
+
/**
|
|
361
|
+
* Gets the length of the view element in the model and updates cache values after each view item it visits.
|
|
362
|
+
*
|
|
363
|
+
* See also {@link #getModelLength}.
|
|
364
|
+
*
|
|
365
|
+
* @param viewNode View node.
|
|
366
|
+
* @param viewContainer Ancestor of `viewNode` that is a mapped view element.
|
|
367
|
+
* @param modelOffset Model offset at which the `viewNode` starts.
|
|
368
|
+
* @returns Length of the node in the tree model.
|
|
369
|
+
*/
|
|
370
|
+
private _getModelLengthAndCache;
|
|
360
371
|
/**
|
|
361
372
|
* Because we prefer positions in the text nodes over positions next to text nodes, if the view position was next to a text node,
|
|
362
373
|
* it moves it into the text node instead.
|
|
@@ -494,33 +505,6 @@ export declare class MapperCache extends /* #__PURE__ */ MapperCache_base {
|
|
|
494
505
|
* @param modelOffset Model offset in a model element or document fragment, which is mapped to `viewContainer`.
|
|
495
506
|
*/
|
|
496
507
|
getClosest(viewContainer: ViewElement | ViewDocumentFragment, modelOffset: number): CacheItem;
|
|
497
|
-
/**
|
|
498
|
-
* Moves a view position to a preferred location.
|
|
499
|
-
*
|
|
500
|
-
* The view position is moved up from a non-tracked view element as long as it remains at the end of its current parent.
|
|
501
|
-
*
|
|
502
|
-
* See example below to understand when it is important:
|
|
503
|
-
*
|
|
504
|
-
* Starting state:
|
|
505
|
-
*
|
|
506
|
-
* ```
|
|
507
|
-
* <p>This is <strong>some <em>heavily <u>formatted</u>^ piece of</em></strong> text.</p>
|
|
508
|
-
* ```
|
|
509
|
-
*
|
|
510
|
-
* Then we remove " piece of " and invalidate some cache:
|
|
511
|
-
*
|
|
512
|
-
* ```
|
|
513
|
-
* <p>This is <strong>some <em>heavily <u>formatted</u>^</em></strong> text.</p>
|
|
514
|
-
* ```
|
|
515
|
-
*
|
|
516
|
-
* Now, if we ask for model offset after letter "d" in "formatted", we should get a position in " text", but we will get in `<em>`.
|
|
517
|
-
* For this scenario, we need to hoist the position.
|
|
518
|
-
*
|
|
519
|
-
* ```
|
|
520
|
-
* <p>This is <strong>some <em>heavily <u>formatted</u></em></strong>^ text.</p>
|
|
521
|
-
* ```
|
|
522
|
-
*/
|
|
523
|
-
private _hoistViewPosition;
|
|
524
508
|
/**
|
|
525
509
|
* Starts tracking given `viewContainer`, which must be mapped to a model element or model document fragment.
|
|
526
510
|
*
|
package/src/conversion/mapper.js
CHANGED
|
@@ -564,26 +564,64 @@ export default class Mapper extends /* #__PURE__ */ EmitterMixin() {
|
|
|
564
564
|
else {
|
|
565
565
|
viewOffset = viewParent.parent.getChildIndex(viewParent) + 1;
|
|
566
566
|
viewParent = viewParent.parent;
|
|
567
|
+
// Cache view position after stepping out of the view element to make sure that all visited view positions are cached.
|
|
568
|
+
// Otherwise, cache invalidation may work incorrectly.
|
|
569
|
+
if (useCache) {
|
|
570
|
+
this._cache.save(viewParent, viewOffset, viewContainer, traversedModelOffset);
|
|
571
|
+
}
|
|
567
572
|
continue;
|
|
568
573
|
}
|
|
569
574
|
}
|
|
570
|
-
|
|
575
|
+
if (useCache) {
|
|
576
|
+
lastLength = this._getModelLengthAndCache(viewNode, viewContainer, traversedModelOffset);
|
|
577
|
+
}
|
|
578
|
+
else {
|
|
579
|
+
lastLength = this.getModelLength(viewNode);
|
|
580
|
+
}
|
|
571
581
|
traversedModelOffset += lastLength;
|
|
572
582
|
viewOffset++;
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
583
|
+
}
|
|
584
|
+
let viewPosition = new ViewPosition(viewParent, viewOffset);
|
|
585
|
+
if (useCache) {
|
|
586
|
+
// Make sure to hoist view position and save cache for all view positions along the way that have the same `modelOffset`.
|
|
587
|
+
//
|
|
588
|
+
// Consider example view:
|
|
589
|
+
//
|
|
590
|
+
// <p>Foo<strong><i>bar<em>xyz</em></i></strong>abc</p>
|
|
591
|
+
//
|
|
592
|
+
// Lets assume that we looked for model offset `9`, starting from `6` (as it was previously cached value).
|
|
593
|
+
// In this case we will only traverse over `<em>xyz</em>` and cache view positions after "xyz" and after `</em>`.
|
|
594
|
+
// After stepping over `<em>xyz</em>`, we will stop processing this view, as we will reach target model offset `9`.
|
|
595
|
+
//
|
|
596
|
+
// However, position before `</strong>` and before `abc` are also valid positions for model offset `9`.
|
|
597
|
+
// Additionally, `Mapper` is supposed to return "hoisted" view positions, that is, we prefer positions that are closer to
|
|
598
|
+
// the mapped `viewContainer`. If a position is nested inside attribute elements, it should be "moved up" if possible.
|
|
599
|
+
//
|
|
600
|
+
// As we hoist the view position, we need to make sure that all view positions valid for model offset `9` are cached.
|
|
601
|
+
// This is necessary for cache invalidation to work correctly.
|
|
602
|
+
//
|
|
603
|
+
// To hoist a view position, we "go up" as long as the position is at the end of a non-mapped view element. We also cache
|
|
604
|
+
// all necessary values. See example:
|
|
605
|
+
//
|
|
606
|
+
// <p>Foo<strong><i>bar<em>xyz</em>^</i></strong>abc</p>
|
|
607
|
+
// <p>Foo<strong><i>bar<em>xyz</em></i>^</strong>abc</p>
|
|
608
|
+
// <p>Foo<strong><i>bar<em>xyz</em></i></strong>^abc</p>
|
|
609
|
+
//
|
|
610
|
+
while (viewPosition.isAtEnd && viewPosition.parent !== viewContainer && viewPosition.parent.parent) {
|
|
611
|
+
const cacheViewParent = viewPosition.parent.parent;
|
|
612
|
+
const cacheViewOffset = cacheViewParent.getChildIndex(viewPosition.parent) + 1;
|
|
613
|
+
this._cache.save(cacheViewParent, cacheViewOffset, viewContainer, traversedModelOffset);
|
|
614
|
+
viewPosition = new ViewPosition(cacheViewParent, cacheViewOffset);
|
|
582
615
|
}
|
|
583
616
|
}
|
|
584
617
|
if (traversedModelOffset == targetModelOffset) {
|
|
585
618
|
// If it equals we found the position.
|
|
586
|
-
|
|
619
|
+
//
|
|
620
|
+
// Try moving view position into a text node if possible, as the editor engine prefers positions inside view text nodes.
|
|
621
|
+
//
|
|
622
|
+
// <p>Foo<strong><i>bar<em>xyz</em></i></strong>[]abc</p> --> <p>Foo<strong><i>bar<em>xyz</em></i></strong>{}abc</p>
|
|
623
|
+
//
|
|
624
|
+
return this._moveViewPositionToTextNode(viewPosition);
|
|
587
625
|
}
|
|
588
626
|
else {
|
|
589
627
|
// If it is higher we overstepped with the last traversed view node.
|
|
@@ -591,6 +629,32 @@ export default class Mapper extends /* #__PURE__ */ EmitterMixin() {
|
|
|
591
629
|
return this._findPositionStartingFrom(new ViewPosition(viewNode, 0), traversedModelOffset - lastLength, targetModelOffset, viewContainer, useCache);
|
|
592
630
|
}
|
|
593
631
|
}
|
|
632
|
+
/**
|
|
633
|
+
* Gets the length of the view element in the model and updates cache values after each view item it visits.
|
|
634
|
+
*
|
|
635
|
+
* See also {@link #getModelLength}.
|
|
636
|
+
*
|
|
637
|
+
* @param viewNode View node.
|
|
638
|
+
* @param viewContainer Ancestor of `viewNode` that is a mapped view element.
|
|
639
|
+
* @param modelOffset Model offset at which the `viewNode` starts.
|
|
640
|
+
* @returns Length of the node in the tree model.
|
|
641
|
+
*/
|
|
642
|
+
_getModelLengthAndCache(viewNode, viewContainer, modelOffset) {
|
|
643
|
+
let len = 0;
|
|
644
|
+
if (this._viewToModelMapping.has(viewNode)) {
|
|
645
|
+
len = 1;
|
|
646
|
+
}
|
|
647
|
+
else if (viewNode.is('$text')) {
|
|
648
|
+
len = viewNode.data.length;
|
|
649
|
+
}
|
|
650
|
+
else if (!viewNode.is('uiElement')) {
|
|
651
|
+
for (const child of viewNode.getChildren()) {
|
|
652
|
+
len += this._getModelLengthAndCache(child, viewContainer, modelOffset + len);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
this._cache.save(viewNode.parent, viewNode.index + 1, viewContainer, modelOffset + len);
|
|
656
|
+
return len;
|
|
657
|
+
}
|
|
594
658
|
/**
|
|
595
659
|
* Because we prefer positions in the text nodes over positions next to text nodes, if the view position was next to a text node,
|
|
596
660
|
* it moves it into the text node instead.
|
|
@@ -812,46 +876,11 @@ export class MapperCache extends /* #__PURE__ */ EmitterMixin() {
|
|
|
812
876
|
else {
|
|
813
877
|
result = this.startTracking(viewContainer);
|
|
814
878
|
}
|
|
815
|
-
const viewPosition = this._hoistViewPosition(result.viewPosition);
|
|
816
879
|
return {
|
|
817
880
|
modelOffset: result.modelOffset,
|
|
818
|
-
viewPosition
|
|
881
|
+
viewPosition: result.viewPosition.clone()
|
|
819
882
|
};
|
|
820
883
|
}
|
|
821
|
-
/**
|
|
822
|
-
* Moves a view position to a preferred location.
|
|
823
|
-
*
|
|
824
|
-
* The view position is moved up from a non-tracked view element as long as it remains at the end of its current parent.
|
|
825
|
-
*
|
|
826
|
-
* See example below to understand when it is important:
|
|
827
|
-
*
|
|
828
|
-
* Starting state:
|
|
829
|
-
*
|
|
830
|
-
* ```
|
|
831
|
-
* <p>This is <strong>some <em>heavily <u>formatted</u>^ piece of</em></strong> text.</p>
|
|
832
|
-
* ```
|
|
833
|
-
*
|
|
834
|
-
* Then we remove " piece of " and invalidate some cache:
|
|
835
|
-
*
|
|
836
|
-
* ```
|
|
837
|
-
* <p>This is <strong>some <em>heavily <u>formatted</u>^</em></strong> text.</p>
|
|
838
|
-
* ```
|
|
839
|
-
*
|
|
840
|
-
* Now, if we ask for model offset after letter "d" in "formatted", we should get a position in " text", but we will get in `<em>`.
|
|
841
|
-
* For this scenario, we need to hoist the position.
|
|
842
|
-
*
|
|
843
|
-
* ```
|
|
844
|
-
* <p>This is <strong>some <em>heavily <u>formatted</u></em></strong>^ text.</p>
|
|
845
|
-
* ```
|
|
846
|
-
*/
|
|
847
|
-
_hoistViewPosition(viewPosition) {
|
|
848
|
-
while (viewPosition.parent.parent && !this._cachedMapping.has(viewPosition.parent) && viewPosition.isAtEnd) {
|
|
849
|
-
const parent = viewPosition.parent.parent;
|
|
850
|
-
const offset = parent.getChildIndex(viewPosition.parent) + 1;
|
|
851
|
-
viewPosition = new ViewPosition(parent, offset);
|
|
852
|
-
}
|
|
853
|
-
return viewPosition;
|
|
854
|
-
}
|
|
855
884
|
/**
|
|
856
885
|
* Starts tracking given `viewContainer`, which must be mapped to a model element or model document fragment.
|
|
857
886
|
*
|
package/src/view/domconverter.js
CHANGED
|
@@ -494,6 +494,13 @@ export default class DomConverter {
|
|
|
494
494
|
if (startsWithFiller(domParent)) {
|
|
495
495
|
offset += INLINE_FILLER_LENGTH;
|
|
496
496
|
}
|
|
497
|
+
// In case someone uses outdated view position, but DOM text node was already changed while typing.
|
|
498
|
+
// See: https://github.com/ckeditor/ckeditor5/issues/18648.
|
|
499
|
+
// Note that when checking Renderer#_isSelectionInInlineFiller() this might be other element
|
|
500
|
+
// than a text node as it is triggered before applying view changes to the DOM.
|
|
501
|
+
if (domParent.data && offset > domParent.data.length) {
|
|
502
|
+
offset = domParent.data.length;
|
|
503
|
+
}
|
|
497
504
|
return { parent: domParent, offset };
|
|
498
505
|
}
|
|
499
506
|
else {
|