@ckeditor/ckeditor5-engine 45.2.1-alpha.3 → 45.2.1-alpha.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-engine",
3
- "version": "45.2.1-alpha.3",
3
+ "version": "45.2.1-alpha.5",
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.1-alpha.3",
27
+ "@ckeditor/ckeditor5-utils": "45.2.1-alpha.5",
28
28
  "es-toolkit": "1.32.0"
29
29
  },
30
30
  "author": "CKSource (http://cksource.com/)",
@@ -5,14 +5,13 @@
5
5
  /**
6
6
  * @module engine/controller/editingcontroller
7
7
  */
8
- import { CKEditorError, ObservableMixin, env } from '@ckeditor/ckeditor5-utils';
8
+ import { CKEditorError, ObservableMixin } from '@ckeditor/ckeditor5-utils';
9
9
  import RootEditableElement from '../view/rooteditableelement.js';
10
10
  import View from '../view/view.js';
11
11
  import Mapper from '../conversion/mapper.js';
12
12
  import DowncastDispatcher from '../conversion/downcastdispatcher.js';
13
13
  import { cleanSelection, convertCollapsedSelection, convertRangeSelection, insertAttributesAndChildren, insertText, remove } from '../conversion/downcasthelpers.js';
14
14
  import { convertSelectionChange } from '../conversion/upcasthelpers.js';
15
- import { tryFixingRange } from '../model/utils/selection-post-fixer.js';
16
15
  // @if CK_DEBUG_ENGINE // const { dumpTrees, initDocumentDumping } = require( '../dev-utils/utils' );
17
16
  /**
18
17
  * A controller for the editing pipeline. The editing pipeline controls the {@link ~EditingController#model model} rendering,
@@ -76,8 +75,6 @@ export default class EditingController extends /* #__PURE__ */ ObservableMixin()
76
75
  }, { priority: 'low' });
77
76
  // Convert selection from the view to the model when it changes in the view.
78
77
  this.listenTo(this.view.document, 'selectionChange', convertSelectionChange(this.model, this.mapper));
79
- // Fix `beforeinput` target ranges so that they map to the valid model ranges.
80
- this.listenTo(this.view.document, 'beforeinput', fixTargetRanges(this.mapper, this.model.schema, this.view), { priority: 'high' });
81
78
  // Attach default model converters.
82
79
  this.downcastDispatcher.on('insert:$text', insertText(), { priority: 'lowest' });
83
80
  this.downcastDispatcher.on('insert', insertAttributesAndChildren(), { priority: 'lowest' });
@@ -182,26 +179,3 @@ export default class EditingController extends /* #__PURE__ */ ObservableMixin()
182
179
  });
183
180
  }
184
181
  }
185
- /**
186
- * Checks whether the target ranges provided by the `beforeInput` event can be properly mapped to model ranges and fixes them if needed.
187
- *
188
- * This is using the same logic as the selection post-fixer.
189
- */
190
- function fixTargetRanges(mapper, schema, view) {
191
- return (evt, data) => {
192
- // The Renderer is disabled while composing on non-android browsers, so we can't be sure that target ranges
193
- // could be properly mapped to view and model because the DOM and view tree drifted apart.
194
- if (view.document.isComposing && !env.isAndroid) {
195
- return;
196
- }
197
- for (let i = 0; i < data.targetRanges.length; i++) {
198
- const viewRange = data.targetRanges[i];
199
- const modelRange = mapper.toModelRange(viewRange);
200
- const correctedRange = tryFixingRange(modelRange, schema);
201
- if (!correctedRange || correctedRange.isEqual(modelRange)) {
202
- continue;
203
- }
204
- data.targetRanges[i] = mapper.toViewRange(correctedRange);
205
- }
206
- };
207
- }
@@ -412,7 +412,7 @@ declare const MapperCache_base: {
412
412
  * * it is an internal tool, used by `Mapper`, transparent to the outside (no additional effort when developing a plugin or a converter),
413
413
  * * it stores all the necessary data internally, which makes it easier to disable or debug,
414
414
  * * it is optimized for initial downcast process (long insertions), which is crucial for editor init and data save,
415
- * * it does not save all possible positions for memory considerations, although it is a possible improvement, which may have increase
415
+ * * it does not save all possible positions mapping for memory considerations, although it is a possible improvement, which may increase
416
416
  * performance, as well as simplify some parts of the `MapperCache` logic.
417
417
  *
418
418
  * @internal
@@ -429,13 +429,8 @@ export declare class MapperCache extends /* #__PURE__ */ MapperCache_base {
429
429
  * values for model offsets that are after a view node. So, in essence, positions inside text nodes are not cached. However, it takes
430
430
  * from one to at most a few steps, to get from a cached position to a position that is inside a view text node.
431
431
  *
432
- * Additionally, only one item per `modelOffset` is cached. There can be several view nodes that "end" at the same `modelOffset`.
433
- * In this case, we favour positions that are closer to the mapped item. For example
434
- *
435
- * * for model: `<paragraph>Some <$text bold=true italic=true>formatted</$text> text.</paragraph>`,
436
- * * and view: `<p>Some <em><strong>formatted</strong></em> text.</p>`,
437
- *
438
- * for model offset `14` (after "d"), we store view position after `<em>` element (i.e. view position: at `<p>`, offset `2`).
432
+ * Additionally, only one item per `modelOffset` is cached. There can be several view positions that map to the same `modelOffset`.
433
+ * Only the first save for `modelOffset` is stored.
439
434
  */
440
435
  private _cachedMapping;
441
436
  /**
@@ -469,6 +464,9 @@ export declare class MapperCache extends /* #__PURE__ */ MapperCache_base {
469
464
  * Saves cache for given view position mapping <-> model offset mapping. The view position should be after a node (i.e. it cannot
470
465
  * be the first position inside its parent, or in other words, `viewOffset` must be greater than `0`).
471
466
  *
467
+ * Note, that if `modelOffset` for given `viewContainer` was already saved, the stored view position (i.e. parent+offset) will not
468
+ * be overwritten. However, it is important to still save it, as we still store additional data related to cached view positions.
469
+ *
472
470
  * @param viewParent View position parent.
473
471
  * @param viewOffset View position offset. Must be greater than `0`.
474
472
  * @param viewContainer Tracked view position ascendant (it may be the direct parent of the view position).
@@ -525,6 +523,9 @@ export declare class MapperCache extends /* #__PURE__ */ MapperCache_base {
525
523
  stopTracking(viewContainer: ViewElement | ViewDocumentFragment): void;
526
524
  /**
527
525
  * Invalidates cache inside `viewParent`, starting from given `index` in that parent.
526
+ *
527
+ * This method may clear a bit more cache than just what was saved after given `index`, but it is guaranteed that at least it
528
+ * will invalidate everything after `index`.
528
529
  */
529
530
  private _clearCacheInsideParent;
530
531
  /**
@@ -532,14 +533,17 @@ export declare class MapperCache extends /* #__PURE__ */ MapperCache_base {
532
533
  */
533
534
  private _clearCacheAll;
534
535
  /**
535
- * Clears all the stored cache starting before given `viewNode`. The `viewNode` can be any node that is inside a tracked view element
536
+ * Clears all the stored cache that is after given `viewNode`. The `viewNode` can be any node that is inside a tracked view element
536
537
  * or view document fragment.
538
+ *
539
+ * In reality, this function may clear a bit more cache than just "starting after" `viewNode`, but it is guaranteed that at least
540
+ * all cache after `viewNode` is invalidated.
537
541
  */
538
- private _clearCacheStartingBefore;
542
+ private _clearCacheAfter;
539
543
  /**
540
544
  * Clears all the cache in the cache list related to given `viewContainer`, starting from `index` (inclusive).
541
545
  */
542
- private _clearCacheFromIndex;
546
+ private _clearCacheFromCacheIndex;
543
547
  /**
544
548
  * Finds a cache item in the given cache list, which `modelOffset` is closest (but smaller or equal) to given `offset`.
545
549
  *
@@ -704,10 +708,9 @@ export type MapperViewToModelPositionEventData = {
704
708
  * and assuming `<paragraph>` and `<p>` are mapped, following example `CacheItem`s are possible:
705
709
  *
706
710
  * * `viewPosition` = `<p>`, 1; `modelOffset` = 5
707
- * * `viewPosition` = `"bold"`, 2; `modelOffset` = 7
708
711
  * * `viewPosition` = `<strong>, 1; `modelOffset` = 9
709
- * * `viewPosition` = `" text"`, 0; `modelOffset` = 9
710
712
  * * `viewPosition` = `<p>`, 2; `modelOffset` = 9
713
+ * * `viewPosition` = `<p>`, 3; `modelOffset` = 14
711
714
  */
712
715
  type CacheItem = {
713
716
  viewPosition: ViewPosition;
@@ -708,7 +708,7 @@ export default class Mapper extends /* #__PURE__ */ EmitterMixin() {
708
708
  * * it is an internal tool, used by `Mapper`, transparent to the outside (no additional effort when developing a plugin or a converter),
709
709
  * * it stores all the necessary data internally, which makes it easier to disable or debug,
710
710
  * * it is optimized for initial downcast process (long insertions), which is crucial for editor init and data save,
711
- * * it does not save all possible positions for memory considerations, although it is a possible improvement, which may have increase
711
+ * * it does not save all possible positions mapping for memory considerations, although it is a possible improvement, which may increase
712
712
  * performance, as well as simplify some parts of the `MapperCache` logic.
713
713
  *
714
714
  * @internal
@@ -725,13 +725,8 @@ export class MapperCache extends /* #__PURE__ */ EmitterMixin() {
725
725
  * values for model offsets that are after a view node. So, in essence, positions inside text nodes are not cached. However, it takes
726
726
  * from one to at most a few steps, to get from a cached position to a position that is inside a view text node.
727
727
  *
728
- * Additionally, only one item per `modelOffset` is cached. There can be several view nodes that "end" at the same `modelOffset`.
729
- * In this case, we favour positions that are closer to the mapped item. For example
730
- *
731
- * * for model: `<paragraph>Some <$text bold=true italic=true>formatted</$text> text.</paragraph>`,
732
- * * and view: `<p>Some <em><strong>formatted</strong></em> text.</p>`,
733
- *
734
- * for model offset `14` (after "d"), we store view position after `<em>` element (i.e. view position: at `<p>`, offset `2`).
728
+ * Additionally, only one item per `modelOffset` is cached. There can be several view positions that map to the same `modelOffset`.
729
+ * Only the first save for `modelOffset` is stored.
735
730
  */
736
731
  _cachedMapping = new WeakMap();
737
732
  /**
@@ -764,13 +759,22 @@ export class MapperCache extends /* #__PURE__ */ EmitterMixin() {
764
759
  * This is specified as a property to make it easier to set as an event callback and to later turn off that event.
765
760
  */
766
761
  _invalidateOnTextChangeCallback = (evt, viewNode) => {
767
- // Text node has changed. Clear all the cache starting from before this text node.
768
- this._clearCacheStartingBefore(viewNode);
762
+ // It is enough to validate starting from "after the text node", because the first cache entry that we might want to invalidate
763
+ // is the position after this text node.
764
+ //
765
+ // For example - assume following view and following view positions cached (marked by `^`): `<p>Foo^<strong>bar^</strong>^abc^</p>`.
766
+ //
767
+ // If we change text "bar", we only need to invalidate cached positions after it. Cached positions before the text are not changed.
768
+ //
769
+ this._clearCacheAfter(viewNode);
769
770
  };
770
771
  /**
771
772
  * Saves cache for given view position mapping <-> model offset mapping. The view position should be after a node (i.e. it cannot
772
773
  * be the first position inside its parent, or in other words, `viewOffset` must be greater than `0`).
773
774
  *
775
+ * Note, that if `modelOffset` for given `viewContainer` was already saved, the stored view position (i.e. parent+offset) will not
776
+ * be overwritten. However, it is important to still save it, as we still store additional data related to cached view positions.
777
+ *
774
778
  * @param viewParent View position parent.
775
779
  * @param viewOffset View position offset. Must be greater than `0`.
776
780
  * @param viewContainer Tracked view position ascendant (it may be the direct parent of the view position).
@@ -783,23 +787,18 @@ export class MapperCache extends /* #__PURE__ */ EmitterMixin() {
783
787
  const cacheItem = cache.cacheMap.get(modelOffset);
784
788
  if (cacheItem) {
785
789
  // We already cached this offset. Don't overwrite the cache.
790
+ // However, we still need to set a proper entry in `_nodeToCacheListIndex`. We can figure it out based on existing `cacheItem`.
786
791
  //
787
- // This assumes that `Mapper` works in a way that we first cache the parent and only then cache children, as we prefer position
788
- // after the parent ("closer" to the tracked ancestor). It might be safer to check which position is preferred (newly saved or
789
- // the one currently in cache) but it would require additional processing. For now, `Mapper#_findPositionIn()` and
790
- // `Mapper#getModelLength()` are implemented so that parents are cached before their children.
791
- //
792
- // So, don't create new cache if one already exists. Instead, only save `_nodeToCacheListIndex` value for the related view node.
793
- const viewChild = viewParent.getChild(viewOffset - 1);
794
- // Figure out what index to save with `viewChild`.
795
792
  // We have a `cacheItem` for the `modelOffset`, so we can get a `viewPosition` from there. Before that view position, there
796
793
  // must be a node. That node must have an index set. This will be the index we will want to use.
797
794
  // Since we expect `viewOffset` to be greater than 0, then in almost all cases `modelOffset` will be greater than 0 as well.
798
795
  // As a result, we can expect `cacheItem.viewPosition.nodeBefore` to be set.
799
796
  //
800
797
  // However, in an edge case, were the tracked element contains a 0-model-length view element as the first child (UI element or
801
- // an empty attribute element), then `modelOffset` will be 0, and `cacheItem` will be the first cache item, which is before any
802
- // view node. In such edge case, `cacheItem.viewPosition.nodeBefore` is undefined, and we manually set to `0`.
798
+ // an empty attribute element), then `modelOffset` will be 0, and `cacheItem.viewPosition` will be before any view node.
799
+ // In such edge case, `cacheItem.viewPosition.nodeBefore` is `undefined`, so we set index to `0`.
800
+ //
801
+ const viewChild = viewParent.getChild(viewOffset - 1);
803
802
  const index = cacheItem.viewPosition.nodeBefore ? this._nodeToCacheListIndex.get(cacheItem.viewPosition.nodeBefore) : 0;
804
803
  this._nodeToCacheListIndex.set(viewChild, index);
805
804
  return;
@@ -921,6 +920,9 @@ export class MapperCache extends /* #__PURE__ */ EmitterMixin() {
921
920
  }
922
921
  /**
923
922
  * Invalidates cache inside `viewParent`, starting from given `index` in that parent.
923
+ *
924
+ * This method may clear a bit more cache than just what was saved after given `index`, but it is guaranteed that at least it
925
+ * will invalidate everything after `index`.
924
926
  */
925
927
  _clearCacheInsideParent(viewParent, index) {
926
928
  if (index == 0) {
@@ -931,23 +933,16 @@ export class MapperCache extends /* #__PURE__ */ EmitterMixin() {
931
933
  }
932
934
  else {
933
935
  // If this is not a tracked element, remove cache starting from before this element.
934
- this._clearCacheStartingBefore(viewParent);
936
+ // Since it is not a tracked element, it has to have a parent.
937
+ this._clearCacheInsideParent(viewParent.parent, viewParent.index);
935
938
  }
936
939
  }
937
940
  else {
938
941
  // Change in the middle of the parent. Get a view node that's before the change.
939
942
  const lastValidNode = viewParent.getChild(index - 1);
940
- // Then, clear all cache starting from before this view node.
941
- //
942
- // Possible performance improvement. We could have had `_clearCacheAfter( lastValidNode )` instead.
943
- // If the `lastValidNode` is the last unchanged node, then we could clear everything AFTER it, not before.
944
- // However, with the current setup, it didn't work properly and the actual gain wasn't that big on the tested data.
945
- // The problem was with following example: <p>Foo<em><strong>Xyz</strong></em>Bar</p>.
946
- // In this example we cache position after <em>, i.e. view position `<p>` 2 is saved with model offset 6.
947
- // Now, if we add some text in `<em>`, we won't validate this cached item even though it gets outdated.
948
- // So, if there's a need to have `_clearCacheAfter()`, we need to solve the above case first.
943
+ // Then, clear all cache after this view node.
949
944
  //
950
- this._clearCacheStartingBefore(lastValidNode);
945
+ this._clearCacheAfter(lastValidNode);
951
946
  }
952
947
  }
953
948
  /**
@@ -955,6 +950,7 @@ export class MapperCache extends /* #__PURE__ */ EmitterMixin() {
955
950
  */
956
951
  _clearCacheAll(viewContainer) {
957
952
  const cache = this._cachedMapping.get(viewContainer);
953
+ // TODO: Should clear `_nodeToCacheListIndex` too?
958
954
  if (cache.maxModelOffset > 0) {
959
955
  cache.maxModelOffset = 0;
960
956
  cache.cacheList.length = 1;
@@ -963,46 +959,47 @@ export class MapperCache extends /* #__PURE__ */ EmitterMixin() {
963
959
  }
964
960
  }
965
961
  /**
966
- * Clears all the stored cache starting before given `viewNode`. The `viewNode` can be any node that is inside a tracked view element
962
+ * Clears all the stored cache that is after given `viewNode`. The `viewNode` can be any node that is inside a tracked view element
967
963
  * or view document fragment.
964
+ *
965
+ * In reality, this function may clear a bit more cache than just "starting after" `viewNode`, but it is guaranteed that at least
966
+ * all cache after `viewNode` is invalidated.
968
967
  */
969
- _clearCacheStartingBefore(viewNode) {
968
+ _clearCacheAfter(viewNode) {
970
969
  // To quickly invalidate the cache, we base on the cache list index stored with the node. See docs for `this._nodeToCacheListIndex`.
971
970
  const cacheListIndex = this._nodeToCacheListIndex.get(viewNode);
972
971
  // If there is no index stored, it means that this `viewNode` has not been cached yet.
973
972
  if (cacheListIndex === undefined) {
974
- // If the node is not cached, maybe it's parent is. We will try to invalidate the cache starting from before the parent.
975
- // Note, that there always must be a parent if we got here.
973
+ // If the node is not cached, maybe it's parent is. We will try to invalidate the cache using the parent.
976
974
  const viewParent = viewNode.parent;
977
- // If the parent is a non-tracked element, try clearing the cache starting before it.
975
+ // If the parent is a non-tracked element, try clearing the cache starting from the position before it.
976
+ //
977
+ // For example: `<p>Abc<strong>def<em>ghi</em></strong></p>`.
978
978
  //
979
- // This situation may happen e.g. if structure like `<p><strong><em>Foo</em></strong>...` was stepped over in
980
- // `Mapper#_findPositionIn()` and the children are not cached yet, but the `<strong>` element is. If something changes
981
- // inside this structure, make sure to invalidate all the cache after `<strong>`.
979
+ // If `viewNode` is `<em>` in this case, and it was not cached yet, we will try to clear cache starting from before `<strong>`.
982
980
  //
983
981
  // If the parent is a tracked element, then it means there's no cache to clear (nothing after the element is cached).
984
- // In this case, there's nothing to do.
982
+ // In this case, there's nothing to do. We assume that there are no "holes" in caching in direct children of tracked element
983
+ // (that is if some children is cached, then its previous sibling is cached too, and we would not end up inside this `if`).
984
+ //
985
+ // TODO: Most probably this `if` could be removed altogether, after recent changes in Mapper.
986
+ // TODO: Now we cache all items one after another, so there aren't any "holes" anywhere, not only on top-level.
985
987
  //
986
988
  if (!this._cachedMapping.has(viewParent)) {
987
- this._clearCacheStartingBefore(viewParent);
989
+ this._clearCacheInsideParent(viewParent.parent, viewParent.index);
988
990
  }
989
991
  return;
990
992
  }
991
- // Note: there was a consideration to save the `viewContainer` value together with `cacheListIndex` value.
992
- // However, it is like it is on purpose. We want to find *current* mapped ancestor for the `viewNode`.
993
- // This is an essential step to verify if the cache is still up-to-date.
994
- // Actually, we could save `viewContainer` and compare it to current tracked ancestor to quickly invalidate.
995
- // But this kinda happens with our flow and other assumptions around caching list index anyway.
996
993
  let viewContainer = viewNode.parent;
997
994
  while (!this._cachedMapping.has(viewContainer)) {
998
995
  viewContainer = viewContainer.parent;
999
996
  }
1000
- this._clearCacheFromIndex(viewContainer, cacheListIndex);
997
+ this._clearCacheFromCacheIndex(viewContainer, cacheListIndex);
1001
998
  }
1002
999
  /**
1003
1000
  * Clears all the cache in the cache list related to given `viewContainer`, starting from `index` (inclusive).
1004
1001
  */
1005
- _clearCacheFromIndex(viewContainer, index) {
1002
+ _clearCacheFromCacheIndex(viewContainer, index) {
1006
1003
  if (index === 0) {
1007
1004
  // Don't remove the first entry in the cache (this entry is always a mapping between view offset 0 <-> model offset 0,
1008
1005
  // and it is a default value that is always expected to be in the cache list).
package/src/index.d.ts CHANGED
@@ -118,3 +118,4 @@ export * from './view/styles/padding.js';
118
118
  export * from './view/styles/utils.js';
119
119
  export { getData as _getModelData, setData as _setModelData, parse as _parseModel, stringify as _stringifyModel } from './dev-utils/model.js';
120
120
  export { getData as _getViewData, setData as _setViewData, parse as _parseView, stringify as _stringifyView } from './dev-utils/view.js';
121
+ export { tryFixingRange as _tryFixingModelRange } from './model/utils/selection-post-fixer.js';
package/src/index.js CHANGED
@@ -83,3 +83,4 @@ export * from './view/styles/utils.js';
83
83
  // Development / testing utils.
84
84
  export { getData as _getModelData, setData as _setModelData, parse as _parseModel, stringify as _stringifyModel } from './dev-utils/model.js';
85
85
  export { getData as _getViewData, setData as _setViewData, parse as _parseView, stringify as _stringifyView } from './dev-utils/view.js';
86
+ export { tryFixingRange as _tryFixingModelRange } from './model/utils/selection-post-fixer.js';