@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/dist/index.js +292 -321
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/controller/editingcontroller.js +1 -27
- package/src/conversion/mapper.d.ts +16 -13
- package/src/conversion/mapper.js +45 -48
- package/src/index.d.ts +1 -0
- package/src/index.js +1 -0
package/dist/index.js
CHANGED
|
@@ -15770,7 +15770,7 @@ Range.prototype.is = function(type) {
|
|
|
15770
15770
|
* * it is an internal tool, used by `Mapper`, transparent to the outside (no additional effort when developing a plugin or a converter),
|
|
15771
15771
|
* * it stores all the necessary data internally, which makes it easier to disable or debug,
|
|
15772
15772
|
* * it is optimized for initial downcast process (long insertions), which is crucial for editor init and data save,
|
|
15773
|
-
* * it does not save all possible positions for memory considerations, although it is a possible improvement, which may
|
|
15773
|
+
* * it does not save all possible positions mapping for memory considerations, although it is a possible improvement, which may increase
|
|
15774
15774
|
* performance, as well as simplify some parts of the `MapperCache` logic.
|
|
15775
15775
|
*
|
|
15776
15776
|
* @internal
|
|
@@ -15786,13 +15786,8 @@ Range.prototype.is = function(type) {
|
|
|
15786
15786
|
* values for model offsets that are after a view node. So, in essence, positions inside text nodes are not cached. However, it takes
|
|
15787
15787
|
* from one to at most a few steps, to get from a cached position to a position that is inside a view text node.
|
|
15788
15788
|
*
|
|
15789
|
-
* Additionally, only one item per `modelOffset` is cached. There can be several view
|
|
15790
|
-
*
|
|
15791
|
-
*
|
|
15792
|
-
* * for model: `<paragraph>Some <$text bold=true italic=true>formatted</$text> text.</paragraph>`,
|
|
15793
|
-
* * and view: `<p>Some <em><strong>formatted</strong></em> text.</p>`,
|
|
15794
|
-
*
|
|
15795
|
-
* for model offset `14` (after "d"), we store view position after `<em>` element (i.e. view position: at `<p>`, offset `2`).
|
|
15789
|
+
* Additionally, only one item per `modelOffset` is cached. There can be several view positions that map to the same `modelOffset`.
|
|
15790
|
+
* Only the first save for `modelOffset` is stored.
|
|
15796
15791
|
*/ _cachedMapping = new WeakMap();
|
|
15797
15792
|
/**
|
|
15798
15793
|
* When `MapperCache` {@link ~MapperCache#save saves} view position -> model offset mapping, a `CacheItem` is inserted into certain
|
|
@@ -15821,13 +15816,22 @@ Range.prototype.is = function(type) {
|
|
|
15821
15816
|
*
|
|
15822
15817
|
* This is specified as a property to make it easier to set as an event callback and to later turn off that event.
|
|
15823
15818
|
*/ _invalidateOnTextChangeCallback = (evt, viewNode)=>{
|
|
15824
|
-
//
|
|
15825
|
-
this.
|
|
15819
|
+
// It is enough to validate starting from "after the text node", because the first cache entry that we might want to invalidate
|
|
15820
|
+
// is the position after this text node.
|
|
15821
|
+
//
|
|
15822
|
+
// For example - assume following view and following view positions cached (marked by `^`): `<p>Foo^<strong>bar^</strong>^abc^</p>`.
|
|
15823
|
+
//
|
|
15824
|
+
// If we change text "bar", we only need to invalidate cached positions after it. Cached positions before the text are not changed.
|
|
15825
|
+
//
|
|
15826
|
+
this._clearCacheAfter(viewNode);
|
|
15826
15827
|
};
|
|
15827
15828
|
/**
|
|
15828
15829
|
* Saves cache for given view position mapping <-> model offset mapping. The view position should be after a node (i.e. it cannot
|
|
15829
15830
|
* be the first position inside its parent, or in other words, `viewOffset` must be greater than `0`).
|
|
15830
15831
|
*
|
|
15832
|
+
* Note, that if `modelOffset` for given `viewContainer` was already saved, the stored view position (i.e. parent+offset) will not
|
|
15833
|
+
* be overwritten. However, it is important to still save it, as we still store additional data related to cached view positions.
|
|
15834
|
+
*
|
|
15831
15835
|
* @param viewParent View position parent.
|
|
15832
15836
|
* @param viewOffset View position offset. Must be greater than `0`.
|
|
15833
15837
|
* @param viewContainer Tracked view position ascendant (it may be the direct parent of the view position).
|
|
@@ -15839,23 +15843,18 @@ Range.prototype.is = function(type) {
|
|
|
15839
15843
|
const cacheItem = cache.cacheMap.get(modelOffset);
|
|
15840
15844
|
if (cacheItem) {
|
|
15841
15845
|
// We already cached this offset. Don't overwrite the cache.
|
|
15846
|
+
// However, we still need to set a proper entry in `_nodeToCacheListIndex`. We can figure it out based on existing `cacheItem`.
|
|
15842
15847
|
//
|
|
15843
|
-
// This assumes that `Mapper` works in a way that we first cache the parent and only then cache children, as we prefer position
|
|
15844
|
-
// after the parent ("closer" to the tracked ancestor). It might be safer to check which position is preferred (newly saved or
|
|
15845
|
-
// the one currently in cache) but it would require additional processing. For now, `Mapper#_findPositionIn()` and
|
|
15846
|
-
// `Mapper#getModelLength()` are implemented so that parents are cached before their children.
|
|
15847
|
-
//
|
|
15848
|
-
// So, don't create new cache if one already exists. Instead, only save `_nodeToCacheListIndex` value for the related view node.
|
|
15849
|
-
const viewChild = viewParent.getChild(viewOffset - 1);
|
|
15850
|
-
// Figure out what index to save with `viewChild`.
|
|
15851
15848
|
// We have a `cacheItem` for the `modelOffset`, so we can get a `viewPosition` from there. Before that view position, there
|
|
15852
15849
|
// must be a node. That node must have an index set. This will be the index we will want to use.
|
|
15853
15850
|
// Since we expect `viewOffset` to be greater than 0, then in almost all cases `modelOffset` will be greater than 0 as well.
|
|
15854
15851
|
// As a result, we can expect `cacheItem.viewPosition.nodeBefore` to be set.
|
|
15855
15852
|
//
|
|
15856
15853
|
// However, in an edge case, were the tracked element contains a 0-model-length view element as the first child (UI element or
|
|
15857
|
-
// an empty attribute element), then `modelOffset` will be 0, and `cacheItem` will be
|
|
15858
|
-
//
|
|
15854
|
+
// an empty attribute element), then `modelOffset` will be 0, and `cacheItem.viewPosition` will be before any view node.
|
|
15855
|
+
// In such edge case, `cacheItem.viewPosition.nodeBefore` is `undefined`, so we set index to `0`.
|
|
15856
|
+
//
|
|
15857
|
+
const viewChild = viewParent.getChild(viewOffset - 1);
|
|
15859
15858
|
const index = cacheItem.viewPosition.nodeBefore ? this._nodeToCacheListIndex.get(cacheItem.viewPosition.nodeBefore) : 0;
|
|
15860
15859
|
this._nodeToCacheListIndex.set(viewChild, index);
|
|
15861
15860
|
return;
|
|
@@ -15984,6 +15983,9 @@ Range.prototype.is = function(type) {
|
|
|
15984
15983
|
}
|
|
15985
15984
|
/**
|
|
15986
15985
|
* Invalidates cache inside `viewParent`, starting from given `index` in that parent.
|
|
15986
|
+
*
|
|
15987
|
+
* This method may clear a bit more cache than just what was saved after given `index`, but it is guaranteed that at least it
|
|
15988
|
+
* will invalidate everything after `index`.
|
|
15987
15989
|
*/ _clearCacheInsideParent(viewParent, index) {
|
|
15988
15990
|
if (index == 0) {
|
|
15989
15991
|
// Change at the beginning of the parent.
|
|
@@ -15992,28 +15994,22 @@ Range.prototype.is = function(type) {
|
|
|
15992
15994
|
this._clearCacheAll(viewParent);
|
|
15993
15995
|
} else {
|
|
15994
15996
|
// If this is not a tracked element, remove cache starting from before this element.
|
|
15995
|
-
|
|
15997
|
+
// Since it is not a tracked element, it has to have a parent.
|
|
15998
|
+
this._clearCacheInsideParent(viewParent.parent, viewParent.index);
|
|
15996
15999
|
}
|
|
15997
16000
|
} else {
|
|
15998
16001
|
// Change in the middle of the parent. Get a view node that's before the change.
|
|
15999
16002
|
const lastValidNode = viewParent.getChild(index - 1);
|
|
16000
|
-
// Then, clear all cache
|
|
16003
|
+
// Then, clear all cache after this view node.
|
|
16001
16004
|
//
|
|
16002
|
-
|
|
16003
|
-
// If the `lastValidNode` is the last unchanged node, then we could clear everything AFTER it, not before.
|
|
16004
|
-
// However, with the current setup, it didn't work properly and the actual gain wasn't that big on the tested data.
|
|
16005
|
-
// The problem was with following example: <p>Foo<em><strong>Xyz</strong></em>Bar</p>.
|
|
16006
|
-
// In this example we cache position after <em>, i.e. view position `<p>` 2 is saved with model offset 6.
|
|
16007
|
-
// Now, if we add some text in `<em>`, we won't validate this cached item even though it gets outdated.
|
|
16008
|
-
// So, if there's a need to have `_clearCacheAfter()`, we need to solve the above case first.
|
|
16009
|
-
//
|
|
16010
|
-
this._clearCacheStartingBefore(lastValidNode);
|
|
16005
|
+
this._clearCacheAfter(lastValidNode);
|
|
16011
16006
|
}
|
|
16012
16007
|
}
|
|
16013
16008
|
/**
|
|
16014
16009
|
* Clears all the cache for given tracked `viewContainer`.
|
|
16015
16010
|
*/ _clearCacheAll(viewContainer) {
|
|
16016
16011
|
const cache = this._cachedMapping.get(viewContainer);
|
|
16012
|
+
// TODO: Should clear `_nodeToCacheListIndex` too?
|
|
16017
16013
|
if (cache.maxModelOffset > 0) {
|
|
16018
16014
|
cache.maxModelOffset = 0;
|
|
16019
16015
|
cache.cacheList.length = 1;
|
|
@@ -16022,44 +16018,45 @@ Range.prototype.is = function(type) {
|
|
|
16022
16018
|
}
|
|
16023
16019
|
}
|
|
16024
16020
|
/**
|
|
16025
|
-
* Clears all the stored cache
|
|
16021
|
+
* Clears all the stored cache that is after given `viewNode`. The `viewNode` can be any node that is inside a tracked view element
|
|
16026
16022
|
* or view document fragment.
|
|
16027
|
-
|
|
16023
|
+
*
|
|
16024
|
+
* In reality, this function may clear a bit more cache than just "starting after" `viewNode`, but it is guaranteed that at least
|
|
16025
|
+
* all cache after `viewNode` is invalidated.
|
|
16026
|
+
*/ _clearCacheAfter(viewNode) {
|
|
16028
16027
|
// To quickly invalidate the cache, we base on the cache list index stored with the node. See docs for `this._nodeToCacheListIndex`.
|
|
16029
16028
|
const cacheListIndex = this._nodeToCacheListIndex.get(viewNode);
|
|
16030
16029
|
// If there is no index stored, it means that this `viewNode` has not been cached yet.
|
|
16031
16030
|
if (cacheListIndex === undefined) {
|
|
16032
|
-
// If the node is not cached, maybe it's parent is. We will try to invalidate the cache
|
|
16033
|
-
// Note, that there always must be a parent if we got here.
|
|
16031
|
+
// If the node is not cached, maybe it's parent is. We will try to invalidate the cache using the parent.
|
|
16034
16032
|
const viewParent = viewNode.parent;
|
|
16035
|
-
// If the parent is a non-tracked element, try clearing the cache starting before it.
|
|
16033
|
+
// If the parent is a non-tracked element, try clearing the cache starting from the position before it.
|
|
16036
16034
|
//
|
|
16037
|
-
//
|
|
16038
|
-
//
|
|
16039
|
-
//
|
|
16035
|
+
// For example: `<p>Abc<strong>def<em>ghi</em></strong></p>`.
|
|
16036
|
+
//
|
|
16037
|
+
// If `viewNode` is `<em>` in this case, and it was not cached yet, we will try to clear cache starting from before `<strong>`.
|
|
16040
16038
|
//
|
|
16041
16039
|
// If the parent is a tracked element, then it means there's no cache to clear (nothing after the element is cached).
|
|
16042
|
-
// In this case, there's nothing to do.
|
|
16040
|
+
// In this case, there's nothing to do. We assume that there are no "holes" in caching in direct children of tracked element
|
|
16041
|
+
// (that is if some children is cached, then its previous sibling is cached too, and we would not end up inside this `if`).
|
|
16042
|
+
//
|
|
16043
|
+
// TODO: Most probably this `if` could be removed altogether, after recent changes in Mapper.
|
|
16044
|
+
// TODO: Now we cache all items one after another, so there aren't any "holes" anywhere, not only on top-level.
|
|
16043
16045
|
//
|
|
16044
16046
|
if (!this._cachedMapping.has(viewParent)) {
|
|
16045
|
-
this.
|
|
16047
|
+
this._clearCacheInsideParent(viewParent.parent, viewParent.index);
|
|
16046
16048
|
}
|
|
16047
16049
|
return;
|
|
16048
16050
|
}
|
|
16049
|
-
// Note: there was a consideration to save the `viewContainer` value together with `cacheListIndex` value.
|
|
16050
|
-
// However, it is like it is on purpose. We want to find *current* mapped ancestor for the `viewNode`.
|
|
16051
|
-
// This is an essential step to verify if the cache is still up-to-date.
|
|
16052
|
-
// Actually, we could save `viewContainer` and compare it to current tracked ancestor to quickly invalidate.
|
|
16053
|
-
// But this kinda happens with our flow and other assumptions around caching list index anyway.
|
|
16054
16051
|
let viewContainer = viewNode.parent;
|
|
16055
16052
|
while(!this._cachedMapping.has(viewContainer)){
|
|
16056
16053
|
viewContainer = viewContainer.parent;
|
|
16057
16054
|
}
|
|
16058
|
-
this.
|
|
16055
|
+
this._clearCacheFromCacheIndex(viewContainer, cacheListIndex);
|
|
16059
16056
|
}
|
|
16060
16057
|
/**
|
|
16061
16058
|
* Clears all the cache in the cache list related to given `viewContainer`, starting from `index` (inclusive).
|
|
16062
|
-
*/
|
|
16059
|
+
*/ _clearCacheFromCacheIndex(viewContainer, index) {
|
|
16063
16060
|
if (index === 0) {
|
|
16064
16061
|
// Don't remove the first entry in the cache (this entry is always a mapping between view offset 0 <-> model offset 0,
|
|
16065
16062
|
// and it is a default value that is always expected to be in the cache list).
|
|
@@ -22842,251 +22839,6 @@ function getFromAttributeCreator(config) {
|
|
|
22842
22839
|
};
|
|
22843
22840
|
}
|
|
22844
22841
|
|
|
22845
|
-
/**
|
|
22846
|
-
* Injects selection post-fixer to the model.
|
|
22847
|
-
*
|
|
22848
|
-
* The role of the selection post-fixer is to ensure that the selection is in a correct place
|
|
22849
|
-
* after a {@link module:engine/model/model~Model#change `change()`} block was executed.
|
|
22850
|
-
*
|
|
22851
|
-
* The correct position means that:
|
|
22852
|
-
*
|
|
22853
|
-
* * All collapsed selection ranges are in a place where the {@link module:engine/model/schema~Schema}
|
|
22854
|
-
* allows a `$text`.
|
|
22855
|
-
* * None of the selection's non-collapsed ranges crosses a {@link module:engine/model/schema~Schema#isLimit limit element}
|
|
22856
|
-
* boundary (a range must be rooted within one limit element).
|
|
22857
|
-
* * Only {@link module:engine/model/schema~Schema#isSelectable selectable elements} can be selected from the outside
|
|
22858
|
-
* (e.g. `[<paragraph>foo</paragraph>]` is invalid). This rule applies independently to both selection ends, so this
|
|
22859
|
-
* selection is correct: `<paragraph>f[oo</paragraph><imageBlock></imageBlock>]`.
|
|
22860
|
-
*
|
|
22861
|
-
* If the position is not correct, the post-fixer will automatically correct it.
|
|
22862
|
-
*
|
|
22863
|
-
* ## Fixing a non-collapsed selection
|
|
22864
|
-
*
|
|
22865
|
-
* See as an example a selection that starts in a P1 element and ends inside the text of a TD element
|
|
22866
|
-
* (`[` and `]` are range boundaries and `(l)` denotes an element defined as `isLimit=true`):
|
|
22867
|
-
*
|
|
22868
|
-
* ```
|
|
22869
|
-
* root
|
|
22870
|
-
* |- element P1
|
|
22871
|
-
* | |- "foo" root
|
|
22872
|
-
* |- element TABLE (l) P1 TABLE P2
|
|
22873
|
-
* | |- element TR (l) f o[o TR TR b a r
|
|
22874
|
-
* | | |- element TD (l) TD TD
|
|
22875
|
-
* | | |- "aaa" a]a a b b b
|
|
22876
|
-
* | |- element TR (l)
|
|
22877
|
-
* | | |- element TD (l) ||
|
|
22878
|
-
* | | |- "bbb" ||
|
|
22879
|
-
* |- element P2 VV
|
|
22880
|
-
* | |- "bar"
|
|
22881
|
-
* root
|
|
22882
|
-
* P1 TABLE] P2
|
|
22883
|
-
* f o[o TR TR b a r
|
|
22884
|
-
* TD TD
|
|
22885
|
-
* a a a b b b
|
|
22886
|
-
* ```
|
|
22887
|
-
*
|
|
22888
|
-
* In the example above, the TABLE, TR and TD are defined as `isLimit=true` in the schema. The range which is not contained within
|
|
22889
|
-
* a single limit element must be expanded to select the outermost limit element. The range end is inside the text node of the TD element.
|
|
22890
|
-
* As the TD element is a child of the TR and TABLE elements, where both are defined as `isLimit=true` in the schema, the range must be
|
|
22891
|
-
* expanded to select the whole TABLE element.
|
|
22892
|
-
*
|
|
22893
|
-
* **Note** If the selection contains multiple ranges, the method returns a minimal set of ranges that are not intersecting after expanding
|
|
22894
|
-
* them to select `isLimit=true` elements.
|
|
22895
|
-
*/ function injectSelectionPostFixer(model) {
|
|
22896
|
-
model.document.registerPostFixer((writer)=>selectionPostFixer(writer, model));
|
|
22897
|
-
}
|
|
22898
|
-
/**
|
|
22899
|
-
* The selection post-fixer.
|
|
22900
|
-
*/ function selectionPostFixer(writer, model) {
|
|
22901
|
-
const selection = model.document.selection;
|
|
22902
|
-
const schema = model.schema;
|
|
22903
|
-
const ranges = [];
|
|
22904
|
-
let wasFixed = false;
|
|
22905
|
-
for (const modelRange of selection.getRanges()){
|
|
22906
|
-
// Go through all ranges in selection and try fixing each of them.
|
|
22907
|
-
// Those ranges might overlap but will be corrected later.
|
|
22908
|
-
const correctedRange = tryFixingRange(modelRange, schema);
|
|
22909
|
-
// "Selection fixing" algorithms sometimes get lost. In consequence, it may happen
|
|
22910
|
-
// that a new range is returned but, in fact, it has the same positions as the original
|
|
22911
|
-
// range anyway. If this range is not discarded, a new selection will be set and that,
|
|
22912
|
-
// for instance, would destroy the selection attributes. Let's make sure that the post-fixer
|
|
22913
|
-
// actually worked first before setting a new selection.
|
|
22914
|
-
//
|
|
22915
|
-
// https://github.com/ckeditor/ckeditor5/issues/6693
|
|
22916
|
-
if (correctedRange && !correctedRange.isEqual(modelRange)) {
|
|
22917
|
-
ranges.push(correctedRange);
|
|
22918
|
-
wasFixed = true;
|
|
22919
|
-
} else {
|
|
22920
|
-
ranges.push(modelRange);
|
|
22921
|
-
}
|
|
22922
|
-
}
|
|
22923
|
-
// If any of ranges were corrected update the selection.
|
|
22924
|
-
if (wasFixed) {
|
|
22925
|
-
writer.setSelection(mergeIntersectingRanges(ranges), {
|
|
22926
|
-
backward: selection.isBackward
|
|
22927
|
-
});
|
|
22928
|
-
}
|
|
22929
|
-
return false;
|
|
22930
|
-
}
|
|
22931
|
-
/**
|
|
22932
|
-
* Tries fixing a range if it's incorrect.
|
|
22933
|
-
*
|
|
22934
|
-
* **Note:** This helper is used by the selection post-fixer and to fix the `beforeinput` target ranges.
|
|
22935
|
-
*
|
|
22936
|
-
* @returns Returns fixed range or null if range is valid.
|
|
22937
|
-
*/ function tryFixingRange(range, schema) {
|
|
22938
|
-
if (range.isCollapsed) {
|
|
22939
|
-
return tryFixingCollapsedRange(range, schema);
|
|
22940
|
-
}
|
|
22941
|
-
return tryFixingNonCollapsedRage(range, schema);
|
|
22942
|
-
}
|
|
22943
|
-
/**
|
|
22944
|
-
* Tries to fix collapsed ranges.
|
|
22945
|
-
*
|
|
22946
|
-
* * Fixes situation when a range is in a place where $text is not allowed
|
|
22947
|
-
*
|
|
22948
|
-
* @param range Collapsed range to fix.
|
|
22949
|
-
* @returns Returns fixed range or null if range is valid.
|
|
22950
|
-
*/ function tryFixingCollapsedRange(range, schema) {
|
|
22951
|
-
const originalPosition = range.start;
|
|
22952
|
-
const nearestSelectionRange = schema.getNearestSelectionRange(originalPosition);
|
|
22953
|
-
// This might be null, i.e. when the editor data is empty or the selection is inside a limit element
|
|
22954
|
-
// that doesn't allow text inside.
|
|
22955
|
-
// In the first case, there is no need to fix the selection range.
|
|
22956
|
-
// In the second, let's go up to the outer selectable element
|
|
22957
|
-
if (!nearestSelectionRange) {
|
|
22958
|
-
const ancestorObject = originalPosition.getAncestors().reverse().find((item)=>schema.isObject(item));
|
|
22959
|
-
if (ancestorObject) {
|
|
22960
|
-
return Range._createOn(ancestorObject);
|
|
22961
|
-
}
|
|
22962
|
-
return null;
|
|
22963
|
-
}
|
|
22964
|
-
if (!nearestSelectionRange.isCollapsed) {
|
|
22965
|
-
return nearestSelectionRange;
|
|
22966
|
-
}
|
|
22967
|
-
const fixedPosition = nearestSelectionRange.start;
|
|
22968
|
-
// Fixed position is the same as original - no need to return corrected range.
|
|
22969
|
-
if (originalPosition.isEqual(fixedPosition)) {
|
|
22970
|
-
return null;
|
|
22971
|
-
}
|
|
22972
|
-
return new Range(fixedPosition);
|
|
22973
|
-
}
|
|
22974
|
-
/**
|
|
22975
|
-
* Tries to fix an expanded range.
|
|
22976
|
-
*
|
|
22977
|
-
* @param range Expanded range to fix.
|
|
22978
|
-
* @returns Returns fixed range or null if range is valid.
|
|
22979
|
-
*/ function tryFixingNonCollapsedRage(range, schema) {
|
|
22980
|
-
const { start, end } = range;
|
|
22981
|
-
const isTextAllowedOnStart = schema.checkChild(start, '$text');
|
|
22982
|
-
const isTextAllowedOnEnd = schema.checkChild(end, '$text');
|
|
22983
|
-
const startLimitElement = schema.getLimitElement(start);
|
|
22984
|
-
const endLimitElement = schema.getLimitElement(end);
|
|
22985
|
-
// Ranges which both end are inside the same limit element (or root) might needs only minor fix.
|
|
22986
|
-
if (startLimitElement === endLimitElement) {
|
|
22987
|
-
// Range is valid when both position allows to place a text:
|
|
22988
|
-
// - <block>f[oobarba]z</block>
|
|
22989
|
-
// This would be "fixed" by a next check but as it will be the same it's better to return null so the selection stays the same.
|
|
22990
|
-
if (isTextAllowedOnStart && isTextAllowedOnEnd) {
|
|
22991
|
-
return null;
|
|
22992
|
-
}
|
|
22993
|
-
// Range that is on non-limit element (or is partially) must be fixed so it is placed inside the block around $text:
|
|
22994
|
-
// - [<block>foo</block>] -> <block>[foo]</block>
|
|
22995
|
-
// - [<block>foo]</block> -> <block>[foo]</block>
|
|
22996
|
-
// - <block>f[oo</block>] -> <block>f[oo]</block>
|
|
22997
|
-
// - [<block>foo</block><selectable></selectable>] -> <block>[foo</block><selectable></selectable>]
|
|
22998
|
-
if (checkSelectionOnNonLimitElements(start, end, schema)) {
|
|
22999
|
-
const isStartBeforeSelectable = start.nodeAfter && schema.isSelectable(start.nodeAfter);
|
|
23000
|
-
const fixedStart = isStartBeforeSelectable ? null : schema.getNearestSelectionRange(start, 'forward');
|
|
23001
|
-
const isEndAfterSelectable = end.nodeBefore && schema.isSelectable(end.nodeBefore);
|
|
23002
|
-
const fixedEnd = isEndAfterSelectable ? null : schema.getNearestSelectionRange(end, 'backward');
|
|
23003
|
-
// The schema.getNearestSelectionRange might return null - if that happens use original position.
|
|
23004
|
-
const rangeStart = fixedStart ? fixedStart.start : start;
|
|
23005
|
-
const rangeEnd = fixedEnd ? fixedEnd.end : end;
|
|
23006
|
-
return new Range(rangeStart, rangeEnd);
|
|
23007
|
-
}
|
|
23008
|
-
}
|
|
23009
|
-
const isStartInLimit = startLimitElement && !startLimitElement.is('rootElement');
|
|
23010
|
-
const isEndInLimit = endLimitElement && !endLimitElement.is('rootElement');
|
|
23011
|
-
// At this point we eliminated valid positions on text nodes so if one of range positions is placed inside a limit element
|
|
23012
|
-
// then the range crossed limit element boundaries and needs to be fixed.
|
|
23013
|
-
if (isStartInLimit || isEndInLimit) {
|
|
23014
|
-
const bothInSameParent = start.nodeAfter && end.nodeBefore && start.nodeAfter.parent === end.nodeBefore.parent;
|
|
23015
|
-
const expandStart = isStartInLimit && (!bothInSameParent || !isSelectable(start.nodeAfter, schema));
|
|
23016
|
-
const expandEnd = isEndInLimit && (!bothInSameParent || !isSelectable(end.nodeBefore, schema));
|
|
23017
|
-
// Although we've already found limit element on start/end positions we must find the outer-most limit element.
|
|
23018
|
-
// as limit elements might be nested directly inside (ie table > tableRow > tableCell).
|
|
23019
|
-
let fixedStart = start;
|
|
23020
|
-
let fixedEnd = end;
|
|
23021
|
-
if (expandStart) {
|
|
23022
|
-
fixedStart = Position._createBefore(findOutermostLimitAncestor(startLimitElement, schema));
|
|
23023
|
-
}
|
|
23024
|
-
if (expandEnd) {
|
|
23025
|
-
fixedEnd = Position._createAfter(findOutermostLimitAncestor(endLimitElement, schema));
|
|
23026
|
-
}
|
|
23027
|
-
return new Range(fixedStart, fixedEnd);
|
|
23028
|
-
}
|
|
23029
|
-
// Range was not fixed at this point so it is valid - ie it was placed around limit element already.
|
|
23030
|
-
return null;
|
|
23031
|
-
}
|
|
23032
|
-
/**
|
|
23033
|
-
* Finds the outer-most ancestor.
|
|
23034
|
-
*/ function findOutermostLimitAncestor(startingNode, schema) {
|
|
23035
|
-
let isLimitNode = startingNode;
|
|
23036
|
-
let parent = isLimitNode;
|
|
23037
|
-
// Find outer most isLimit block as such blocks might be nested (ie. in tables).
|
|
23038
|
-
while(schema.isLimit(parent) && parent.parent){
|
|
23039
|
-
isLimitNode = parent;
|
|
23040
|
-
parent = parent.parent;
|
|
23041
|
-
}
|
|
23042
|
-
return isLimitNode;
|
|
23043
|
-
}
|
|
23044
|
-
/**
|
|
23045
|
-
* Checks whether any of range boundaries is placed around non-limit elements.
|
|
23046
|
-
*/ function checkSelectionOnNonLimitElements(start, end, schema) {
|
|
23047
|
-
const startIsOnBlock = start.nodeAfter && !schema.isLimit(start.nodeAfter) || schema.checkChild(start, '$text');
|
|
23048
|
-
const endIsOnBlock = end.nodeBefore && !schema.isLimit(end.nodeBefore) || schema.checkChild(end, '$text');
|
|
23049
|
-
// We should fix such selection when one of those nodes needs fixing.
|
|
23050
|
-
return startIsOnBlock || endIsOnBlock;
|
|
23051
|
-
}
|
|
23052
|
-
/**
|
|
23053
|
-
* Returns a minimal non-intersecting array of ranges without duplicates.
|
|
23054
|
-
*
|
|
23055
|
-
* @param ranges Ranges to merge.
|
|
23056
|
-
* @returns Array of unique and non-intersecting ranges.
|
|
23057
|
-
*/ function mergeIntersectingRanges(ranges) {
|
|
23058
|
-
const rangesToMerge = [
|
|
23059
|
-
...ranges
|
|
23060
|
-
];
|
|
23061
|
-
const rangeIndexesToRemove = new Set();
|
|
23062
|
-
let currentRangeIndex = 1;
|
|
23063
|
-
while(currentRangeIndex < rangesToMerge.length){
|
|
23064
|
-
const currentRange = rangesToMerge[currentRangeIndex];
|
|
23065
|
-
const previousRanges = rangesToMerge.slice(0, currentRangeIndex);
|
|
23066
|
-
for (const [previousRangeIndex, previousRange] of previousRanges.entries()){
|
|
23067
|
-
if (rangeIndexesToRemove.has(previousRangeIndex)) {
|
|
23068
|
-
continue;
|
|
23069
|
-
}
|
|
23070
|
-
if (currentRange.isEqual(previousRange)) {
|
|
23071
|
-
rangeIndexesToRemove.add(previousRangeIndex);
|
|
23072
|
-
} else if (currentRange.isIntersecting(previousRange)) {
|
|
23073
|
-
rangeIndexesToRemove.add(previousRangeIndex);
|
|
23074
|
-
rangeIndexesToRemove.add(currentRangeIndex);
|
|
23075
|
-
const mergedRange = currentRange.getJoined(previousRange);
|
|
23076
|
-
rangesToMerge.push(mergedRange);
|
|
23077
|
-
}
|
|
23078
|
-
}
|
|
23079
|
-
currentRangeIndex++;
|
|
23080
|
-
}
|
|
23081
|
-
const nonIntersectingRanges = rangesToMerge.filter((_, index)=>!rangeIndexesToRemove.has(index));
|
|
23082
|
-
return nonIntersectingRanges;
|
|
23083
|
-
}
|
|
23084
|
-
/**
|
|
23085
|
-
* Checks if node exists and if it's a selectable.
|
|
23086
|
-
*/ function isSelectable(node, schema) {
|
|
23087
|
-
return node && schema.isSelectable(node);
|
|
23088
|
-
}
|
|
23089
|
-
|
|
23090
22842
|
// @if CK_DEBUG_ENGINE // const { dumpTrees, initDocumentDumping } = require( '../dev-utils/utils' );
|
|
23091
22843
|
/**
|
|
23092
22844
|
* A controller for the editing pipeline. The editing pipeline controls the {@link ~EditingController#model model} rendering,
|
|
@@ -23149,11 +22901,7 @@ function getFromAttributeCreator(config) {
|
|
|
23149
22901
|
priority: 'low'
|
|
23150
22902
|
});
|
|
23151
22903
|
// Convert selection from the view to the model when it changes in the view.
|
|
23152
|
-
this.listenTo(this.view.document, 'selectionChange', convertSelectionChange(this.model, this.mapper));
|
|
23153
|
-
// Fix `beforeinput` target ranges so that they map to the valid model ranges.
|
|
23154
|
-
this.listenTo(this.view.document, 'beforeinput', fixTargetRanges(this.mapper, this.model.schema, this.view), {
|
|
23155
|
-
priority: 'high'
|
|
23156
|
-
});
|
|
22904
|
+
this.listenTo(this.view.document, 'selectionChange', convertSelectionChange(this.model, this.mapper));
|
|
23157
22905
|
// Attach default model converters.
|
|
23158
22906
|
this.downcastDispatcher.on('insert:$text', insertText(), {
|
|
23159
22907
|
priority: 'lowest'
|
|
@@ -23266,28 +23014,6 @@ function getFromAttributeCreator(config) {
|
|
|
23266
23014
|
});
|
|
23267
23015
|
}
|
|
23268
23016
|
}
|
|
23269
|
-
/**
|
|
23270
|
-
* Checks whether the target ranges provided by the `beforeInput` event can be properly mapped to model ranges and fixes them if needed.
|
|
23271
|
-
*
|
|
23272
|
-
* This is using the same logic as the selection post-fixer.
|
|
23273
|
-
*/ function fixTargetRanges(mapper, schema, view) {
|
|
23274
|
-
return (evt, data)=>{
|
|
23275
|
-
// The Renderer is disabled while composing on non-android browsers, so we can't be sure that target ranges
|
|
23276
|
-
// could be properly mapped to view and model because the DOM and view tree drifted apart.
|
|
23277
|
-
if (view.document.isComposing && !env.isAndroid) {
|
|
23278
|
-
return;
|
|
23279
|
-
}
|
|
23280
|
-
for(let i = 0; i < data.targetRanges.length; i++){
|
|
23281
|
-
const viewRange = data.targetRanges[i];
|
|
23282
|
-
const modelRange = mapper.toModelRange(viewRange);
|
|
23283
|
-
const correctedRange = tryFixingRange(modelRange, schema);
|
|
23284
|
-
if (!correctedRange || correctedRange.isEqual(modelRange)) {
|
|
23285
|
-
continue;
|
|
23286
|
-
}
|
|
23287
|
-
data.targetRanges[i] = mapper.toViewRange(correctedRange);
|
|
23288
|
-
}
|
|
23289
|
-
};
|
|
23290
|
-
}
|
|
23291
23017
|
|
|
23292
23018
|
/**
|
|
23293
23019
|
* The model's schema. It defines the allowed and disallowed structures of nodes as well as nodes' attributes.
|
|
@@ -34276,6 +34002,251 @@ DocumentFragment.prototype.is = function(type) {
|
|
|
34276
34002
|
return false;
|
|
34277
34003
|
}
|
|
34278
34004
|
|
|
34005
|
+
/**
|
|
34006
|
+
* Injects selection post-fixer to the model.
|
|
34007
|
+
*
|
|
34008
|
+
* The role of the selection post-fixer is to ensure that the selection is in a correct place
|
|
34009
|
+
* after a {@link module:engine/model/model~Model#change `change()`} block was executed.
|
|
34010
|
+
*
|
|
34011
|
+
* The correct position means that:
|
|
34012
|
+
*
|
|
34013
|
+
* * All collapsed selection ranges are in a place where the {@link module:engine/model/schema~Schema}
|
|
34014
|
+
* allows a `$text`.
|
|
34015
|
+
* * None of the selection's non-collapsed ranges crosses a {@link module:engine/model/schema~Schema#isLimit limit element}
|
|
34016
|
+
* boundary (a range must be rooted within one limit element).
|
|
34017
|
+
* * Only {@link module:engine/model/schema~Schema#isSelectable selectable elements} can be selected from the outside
|
|
34018
|
+
* (e.g. `[<paragraph>foo</paragraph>]` is invalid). This rule applies independently to both selection ends, so this
|
|
34019
|
+
* selection is correct: `<paragraph>f[oo</paragraph><imageBlock></imageBlock>]`.
|
|
34020
|
+
*
|
|
34021
|
+
* If the position is not correct, the post-fixer will automatically correct it.
|
|
34022
|
+
*
|
|
34023
|
+
* ## Fixing a non-collapsed selection
|
|
34024
|
+
*
|
|
34025
|
+
* See as an example a selection that starts in a P1 element and ends inside the text of a TD element
|
|
34026
|
+
* (`[` and `]` are range boundaries and `(l)` denotes an element defined as `isLimit=true`):
|
|
34027
|
+
*
|
|
34028
|
+
* ```
|
|
34029
|
+
* root
|
|
34030
|
+
* |- element P1
|
|
34031
|
+
* | |- "foo" root
|
|
34032
|
+
* |- element TABLE (l) P1 TABLE P2
|
|
34033
|
+
* | |- element TR (l) f o[o TR TR b a r
|
|
34034
|
+
* | | |- element TD (l) TD TD
|
|
34035
|
+
* | | |- "aaa" a]a a b b b
|
|
34036
|
+
* | |- element TR (l)
|
|
34037
|
+
* | | |- element TD (l) ||
|
|
34038
|
+
* | | |- "bbb" ||
|
|
34039
|
+
* |- element P2 VV
|
|
34040
|
+
* | |- "bar"
|
|
34041
|
+
* root
|
|
34042
|
+
* P1 TABLE] P2
|
|
34043
|
+
* f o[o TR TR b a r
|
|
34044
|
+
* TD TD
|
|
34045
|
+
* a a a b b b
|
|
34046
|
+
* ```
|
|
34047
|
+
*
|
|
34048
|
+
* In the example above, the TABLE, TR and TD are defined as `isLimit=true` in the schema. The range which is not contained within
|
|
34049
|
+
* a single limit element must be expanded to select the outermost limit element. The range end is inside the text node of the TD element.
|
|
34050
|
+
* As the TD element is a child of the TR and TABLE elements, where both are defined as `isLimit=true` in the schema, the range must be
|
|
34051
|
+
* expanded to select the whole TABLE element.
|
|
34052
|
+
*
|
|
34053
|
+
* **Note** If the selection contains multiple ranges, the method returns a minimal set of ranges that are not intersecting after expanding
|
|
34054
|
+
* them to select `isLimit=true` elements.
|
|
34055
|
+
*/ function injectSelectionPostFixer(model) {
|
|
34056
|
+
model.document.registerPostFixer((writer)=>selectionPostFixer(writer, model));
|
|
34057
|
+
}
|
|
34058
|
+
/**
|
|
34059
|
+
* The selection post-fixer.
|
|
34060
|
+
*/ function selectionPostFixer(writer, model) {
|
|
34061
|
+
const selection = model.document.selection;
|
|
34062
|
+
const schema = model.schema;
|
|
34063
|
+
const ranges = [];
|
|
34064
|
+
let wasFixed = false;
|
|
34065
|
+
for (const modelRange of selection.getRanges()){
|
|
34066
|
+
// Go through all ranges in selection and try fixing each of them.
|
|
34067
|
+
// Those ranges might overlap but will be corrected later.
|
|
34068
|
+
const correctedRange = tryFixingRange(modelRange, schema);
|
|
34069
|
+
// "Selection fixing" algorithms sometimes get lost. In consequence, it may happen
|
|
34070
|
+
// that a new range is returned but, in fact, it has the same positions as the original
|
|
34071
|
+
// range anyway. If this range is not discarded, a new selection will be set and that,
|
|
34072
|
+
// for instance, would destroy the selection attributes. Let's make sure that the post-fixer
|
|
34073
|
+
// actually worked first before setting a new selection.
|
|
34074
|
+
//
|
|
34075
|
+
// https://github.com/ckeditor/ckeditor5/issues/6693
|
|
34076
|
+
if (correctedRange && !correctedRange.isEqual(modelRange)) {
|
|
34077
|
+
ranges.push(correctedRange);
|
|
34078
|
+
wasFixed = true;
|
|
34079
|
+
} else {
|
|
34080
|
+
ranges.push(modelRange);
|
|
34081
|
+
}
|
|
34082
|
+
}
|
|
34083
|
+
// If any of ranges were corrected update the selection.
|
|
34084
|
+
if (wasFixed) {
|
|
34085
|
+
writer.setSelection(mergeIntersectingRanges(ranges), {
|
|
34086
|
+
backward: selection.isBackward
|
|
34087
|
+
});
|
|
34088
|
+
}
|
|
34089
|
+
return false;
|
|
34090
|
+
}
|
|
34091
|
+
/**
|
|
34092
|
+
* Tries fixing a range if it's incorrect.
|
|
34093
|
+
*
|
|
34094
|
+
* **Note:** This helper is used by the selection post-fixer and to fix the `beforeinput` target ranges.
|
|
34095
|
+
*
|
|
34096
|
+
* @returns Returns fixed range or null if range is valid.
|
|
34097
|
+
*/ function tryFixingRange(range, schema) {
|
|
34098
|
+
if (range.isCollapsed) {
|
|
34099
|
+
return tryFixingCollapsedRange(range, schema);
|
|
34100
|
+
}
|
|
34101
|
+
return tryFixingNonCollapsedRage(range, schema);
|
|
34102
|
+
}
|
|
34103
|
+
/**
|
|
34104
|
+
* Tries to fix collapsed ranges.
|
|
34105
|
+
*
|
|
34106
|
+
* * Fixes situation when a range is in a place where $text is not allowed
|
|
34107
|
+
*
|
|
34108
|
+
* @param range Collapsed range to fix.
|
|
34109
|
+
* @returns Returns fixed range or null if range is valid.
|
|
34110
|
+
*/ function tryFixingCollapsedRange(range, schema) {
|
|
34111
|
+
const originalPosition = range.start;
|
|
34112
|
+
const nearestSelectionRange = schema.getNearestSelectionRange(originalPosition);
|
|
34113
|
+
// This might be null, i.e. when the editor data is empty or the selection is inside a limit element
|
|
34114
|
+
// that doesn't allow text inside.
|
|
34115
|
+
// In the first case, there is no need to fix the selection range.
|
|
34116
|
+
// In the second, let's go up to the outer selectable element
|
|
34117
|
+
if (!nearestSelectionRange) {
|
|
34118
|
+
const ancestorObject = originalPosition.getAncestors().reverse().find((item)=>schema.isObject(item));
|
|
34119
|
+
if (ancestorObject) {
|
|
34120
|
+
return Range._createOn(ancestorObject);
|
|
34121
|
+
}
|
|
34122
|
+
return null;
|
|
34123
|
+
}
|
|
34124
|
+
if (!nearestSelectionRange.isCollapsed) {
|
|
34125
|
+
return nearestSelectionRange;
|
|
34126
|
+
}
|
|
34127
|
+
const fixedPosition = nearestSelectionRange.start;
|
|
34128
|
+
// Fixed position is the same as original - no need to return corrected range.
|
|
34129
|
+
if (originalPosition.isEqual(fixedPosition)) {
|
|
34130
|
+
return null;
|
|
34131
|
+
}
|
|
34132
|
+
return new Range(fixedPosition);
|
|
34133
|
+
}
|
|
34134
|
+
/**
|
|
34135
|
+
* Tries to fix an expanded range.
|
|
34136
|
+
*
|
|
34137
|
+
* @param range Expanded range to fix.
|
|
34138
|
+
* @returns Returns fixed range or null if range is valid.
|
|
34139
|
+
*/ function tryFixingNonCollapsedRage(range, schema) {
|
|
34140
|
+
const { start, end } = range;
|
|
34141
|
+
const isTextAllowedOnStart = schema.checkChild(start, '$text');
|
|
34142
|
+
const isTextAllowedOnEnd = schema.checkChild(end, '$text');
|
|
34143
|
+
const startLimitElement = schema.getLimitElement(start);
|
|
34144
|
+
const endLimitElement = schema.getLimitElement(end);
|
|
34145
|
+
// Ranges which both end are inside the same limit element (or root) might needs only minor fix.
|
|
34146
|
+
if (startLimitElement === endLimitElement) {
|
|
34147
|
+
// Range is valid when both position allows to place a text:
|
|
34148
|
+
// - <block>f[oobarba]z</block>
|
|
34149
|
+
// This would be "fixed" by a next check but as it will be the same it's better to return null so the selection stays the same.
|
|
34150
|
+
if (isTextAllowedOnStart && isTextAllowedOnEnd) {
|
|
34151
|
+
return null;
|
|
34152
|
+
}
|
|
34153
|
+
// Range that is on non-limit element (or is partially) must be fixed so it is placed inside the block around $text:
|
|
34154
|
+
// - [<block>foo</block>] -> <block>[foo]</block>
|
|
34155
|
+
// - [<block>foo]</block> -> <block>[foo]</block>
|
|
34156
|
+
// - <block>f[oo</block>] -> <block>f[oo]</block>
|
|
34157
|
+
// - [<block>foo</block><selectable></selectable>] -> <block>[foo</block><selectable></selectable>]
|
|
34158
|
+
if (checkSelectionOnNonLimitElements(start, end, schema)) {
|
|
34159
|
+
const isStartBeforeSelectable = start.nodeAfter && schema.isSelectable(start.nodeAfter);
|
|
34160
|
+
const fixedStart = isStartBeforeSelectable ? null : schema.getNearestSelectionRange(start, 'forward');
|
|
34161
|
+
const isEndAfterSelectable = end.nodeBefore && schema.isSelectable(end.nodeBefore);
|
|
34162
|
+
const fixedEnd = isEndAfterSelectable ? null : schema.getNearestSelectionRange(end, 'backward');
|
|
34163
|
+
// The schema.getNearestSelectionRange might return null - if that happens use original position.
|
|
34164
|
+
const rangeStart = fixedStart ? fixedStart.start : start;
|
|
34165
|
+
const rangeEnd = fixedEnd ? fixedEnd.end : end;
|
|
34166
|
+
return new Range(rangeStart, rangeEnd);
|
|
34167
|
+
}
|
|
34168
|
+
}
|
|
34169
|
+
const isStartInLimit = startLimitElement && !startLimitElement.is('rootElement');
|
|
34170
|
+
const isEndInLimit = endLimitElement && !endLimitElement.is('rootElement');
|
|
34171
|
+
// At this point we eliminated valid positions on text nodes so if one of range positions is placed inside a limit element
|
|
34172
|
+
// then the range crossed limit element boundaries and needs to be fixed.
|
|
34173
|
+
if (isStartInLimit || isEndInLimit) {
|
|
34174
|
+
const bothInSameParent = start.nodeAfter && end.nodeBefore && start.nodeAfter.parent === end.nodeBefore.parent;
|
|
34175
|
+
const expandStart = isStartInLimit && (!bothInSameParent || !isSelectable(start.nodeAfter, schema));
|
|
34176
|
+
const expandEnd = isEndInLimit && (!bothInSameParent || !isSelectable(end.nodeBefore, schema));
|
|
34177
|
+
// Although we've already found limit element on start/end positions we must find the outer-most limit element.
|
|
34178
|
+
// as limit elements might be nested directly inside (ie table > tableRow > tableCell).
|
|
34179
|
+
let fixedStart = start;
|
|
34180
|
+
let fixedEnd = end;
|
|
34181
|
+
if (expandStart) {
|
|
34182
|
+
fixedStart = Position._createBefore(findOutermostLimitAncestor(startLimitElement, schema));
|
|
34183
|
+
}
|
|
34184
|
+
if (expandEnd) {
|
|
34185
|
+
fixedEnd = Position._createAfter(findOutermostLimitAncestor(endLimitElement, schema));
|
|
34186
|
+
}
|
|
34187
|
+
return new Range(fixedStart, fixedEnd);
|
|
34188
|
+
}
|
|
34189
|
+
// Range was not fixed at this point so it is valid - ie it was placed around limit element already.
|
|
34190
|
+
return null;
|
|
34191
|
+
}
|
|
34192
|
+
/**
|
|
34193
|
+
* Finds the outer-most ancestor.
|
|
34194
|
+
*/ function findOutermostLimitAncestor(startingNode, schema) {
|
|
34195
|
+
let isLimitNode = startingNode;
|
|
34196
|
+
let parent = isLimitNode;
|
|
34197
|
+
// Find outer most isLimit block as such blocks might be nested (ie. in tables).
|
|
34198
|
+
while(schema.isLimit(parent) && parent.parent){
|
|
34199
|
+
isLimitNode = parent;
|
|
34200
|
+
parent = parent.parent;
|
|
34201
|
+
}
|
|
34202
|
+
return isLimitNode;
|
|
34203
|
+
}
|
|
34204
|
+
/**
|
|
34205
|
+
* Checks whether any of range boundaries is placed around non-limit elements.
|
|
34206
|
+
*/ function checkSelectionOnNonLimitElements(start, end, schema) {
|
|
34207
|
+
const startIsOnBlock = start.nodeAfter && !schema.isLimit(start.nodeAfter) || schema.checkChild(start, '$text');
|
|
34208
|
+
const endIsOnBlock = end.nodeBefore && !schema.isLimit(end.nodeBefore) || schema.checkChild(end, '$text');
|
|
34209
|
+
// We should fix such selection when one of those nodes needs fixing.
|
|
34210
|
+
return startIsOnBlock || endIsOnBlock;
|
|
34211
|
+
}
|
|
34212
|
+
/**
|
|
34213
|
+
* Returns a minimal non-intersecting array of ranges without duplicates.
|
|
34214
|
+
*
|
|
34215
|
+
* @param ranges Ranges to merge.
|
|
34216
|
+
* @returns Array of unique and non-intersecting ranges.
|
|
34217
|
+
*/ function mergeIntersectingRanges(ranges) {
|
|
34218
|
+
const rangesToMerge = [
|
|
34219
|
+
...ranges
|
|
34220
|
+
];
|
|
34221
|
+
const rangeIndexesToRemove = new Set();
|
|
34222
|
+
let currentRangeIndex = 1;
|
|
34223
|
+
while(currentRangeIndex < rangesToMerge.length){
|
|
34224
|
+
const currentRange = rangesToMerge[currentRangeIndex];
|
|
34225
|
+
const previousRanges = rangesToMerge.slice(0, currentRangeIndex);
|
|
34226
|
+
for (const [previousRangeIndex, previousRange] of previousRanges.entries()){
|
|
34227
|
+
if (rangeIndexesToRemove.has(previousRangeIndex)) {
|
|
34228
|
+
continue;
|
|
34229
|
+
}
|
|
34230
|
+
if (currentRange.isEqual(previousRange)) {
|
|
34231
|
+
rangeIndexesToRemove.add(previousRangeIndex);
|
|
34232
|
+
} else if (currentRange.isIntersecting(previousRange)) {
|
|
34233
|
+
rangeIndexesToRemove.add(previousRangeIndex);
|
|
34234
|
+
rangeIndexesToRemove.add(currentRangeIndex);
|
|
34235
|
+
const mergedRange = currentRange.getJoined(previousRange);
|
|
34236
|
+
rangesToMerge.push(mergedRange);
|
|
34237
|
+
}
|
|
34238
|
+
}
|
|
34239
|
+
currentRangeIndex++;
|
|
34240
|
+
}
|
|
34241
|
+
const nonIntersectingRanges = rangesToMerge.filter((_, index)=>!rangeIndexesToRemove.has(index));
|
|
34242
|
+
return nonIntersectingRanges;
|
|
34243
|
+
}
|
|
34244
|
+
/**
|
|
34245
|
+
* Checks if node exists and if it's a selectable.
|
|
34246
|
+
*/ function isSelectable(node, schema) {
|
|
34247
|
+
return node && schema.isSelectable(node);
|
|
34248
|
+
}
|
|
34249
|
+
|
|
34279
34250
|
/**
|
|
34280
34251
|
* Deletes content of the selection and merge siblings. The resulting selection is always collapsed.
|
|
34281
34252
|
*
|
|
@@ -39292,5 +39263,5 @@ function* convertAttributes(attributes, converter) {
|
|
|
39292
39263
|
}
|
|
39293
39264
|
}
|
|
39294
39265
|
|
|
39295
|
-
export { AttributeElement, AttributeOperation, BubblingEventInfo, ClickObserver, Conversion, DataController, DataTransfer, DocumentFragment, DocumentSelection, DomConverter, DomEventData, DomEventObserver, DowncastWriter, EditingController, View as EditingView, Element, FocusObserver, History, HtmlDataProcessor, InsertOperation, LivePosition, LiveRange, MarkerOperation, Matcher, MergeOperation, Model, MouseObserver, MoveOperation, NoOperation, Observer, OperationFactory, Position, Range, RenameOperation, Renderer, RootAttributeOperation, RootOperation, SchemaContext, SplitOperation, StylesMap, StylesProcessor, TabObserver, Text, TextProxy, TouchObserver, TreeWalker, UpcastWriter, AttributeElement as ViewAttributeElement, ContainerElement as ViewContainerElement, Document$1 as ViewDocument, DocumentFragment$1 as ViewDocumentFragment, EditableElement as ViewEditableElement, Element$1 as ViewElement, EmptyElement as ViewEmptyElement, RawElement as ViewRawElement, RootEditableElement as ViewRootEditableElement, Text$1 as ViewText, TreeWalker$1 as ViewTreeWalker, UIElement as ViewUIElement, XmlDataProcessor, getData as _getModelData, getData$1 as _getViewData, parse as _parseModel, parse$1 as _parseView, setData as _setModelData, setData$1 as _setViewData, stringify as _stringifyModel, stringify$1 as _stringifyView, addBackgroundRules, addBorderRules, addMarginRules, addPaddingRules, autoParagraphEmptyRoots, disablePlaceholder, enablePlaceholder, getBoxSidesShorthandValue, getBoxSidesValueReducer, getBoxSidesValues, getFillerOffset$4 as getFillerOffset, getPositionShorthandNormalizer, getShorthandValues, hidePlaceholder, isAttachment, isColor, isLength, isLineStyle, isParagraphable, isPercentage, isPosition, isRepeat, isURL, needsPlaceholder, showPlaceholder, transformSets, wrapInParagraph };
|
|
39266
|
+
export { AttributeElement, AttributeOperation, BubblingEventInfo, ClickObserver, Conversion, DataController, DataTransfer, DocumentFragment, DocumentSelection, DomConverter, DomEventData, DomEventObserver, DowncastWriter, EditingController, View as EditingView, Element, FocusObserver, History, HtmlDataProcessor, InsertOperation, LivePosition, LiveRange, MarkerOperation, Matcher, MergeOperation, Model, MouseObserver, MoveOperation, NoOperation, Observer, OperationFactory, Position, Range, RenameOperation, Renderer, RootAttributeOperation, RootOperation, SchemaContext, SplitOperation, StylesMap, StylesProcessor, TabObserver, Text, TextProxy, TouchObserver, TreeWalker, UpcastWriter, AttributeElement as ViewAttributeElement, ContainerElement as ViewContainerElement, Document$1 as ViewDocument, DocumentFragment$1 as ViewDocumentFragment, EditableElement as ViewEditableElement, Element$1 as ViewElement, EmptyElement as ViewEmptyElement, RawElement as ViewRawElement, RootEditableElement as ViewRootEditableElement, Text$1 as ViewText, TreeWalker$1 as ViewTreeWalker, UIElement as ViewUIElement, XmlDataProcessor, getData as _getModelData, getData$1 as _getViewData, parse as _parseModel, parse$1 as _parseView, setData as _setModelData, setData$1 as _setViewData, stringify as _stringifyModel, stringify$1 as _stringifyView, tryFixingRange as _tryFixingModelRange, addBackgroundRules, addBorderRules, addMarginRules, addPaddingRules, autoParagraphEmptyRoots, disablePlaceholder, enablePlaceholder, getBoxSidesShorthandValue, getBoxSidesValueReducer, getBoxSidesValues, getFillerOffset$4 as getFillerOffset, getPositionShorthandNormalizer, getShorthandValues, hidePlaceholder, isAttachment, isColor, isLength, isLineStyle, isParagraphable, isPercentage, isPosition, isRepeat, isURL, needsPlaceholder, showPlaceholder, transformSets, wrapInParagraph };
|
|
39296
39267
|
//# sourceMappingURL=index.js.map
|