@ckeditor/ckeditor5-engine 45.2.1-alpha.10 → 45.2.1-alpha.2
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 +320 -291
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/controller/editingcontroller.js +27 -1
- package/src/conversion/mapper.d.ts +13 -16
- package/src/conversion/mapper.js +48 -45
- package/src/index.d.ts +0 -1
- package/src/index.js +0 -1
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
|
|
15773
|
+
* * it does not save all possible positions for memory considerations, although it is a possible improvement, which may have increase
|
|
15774
15774
|
* performance, as well as simplify some parts of the `MapperCache` logic.
|
|
15775
15775
|
*
|
|
15776
15776
|
* @internal
|
|
@@ -15786,8 +15786,13 @@ 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
|
-
*
|
|
15789
|
+
* Additionally, only one item per `modelOffset` is cached. There can be several view nodes that "end" at the same `modelOffset`.
|
|
15790
|
+
* In this case, we favour positions that are closer to the mapped item. For example
|
|
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`).
|
|
15791
15796
|
*/ _cachedMapping = new WeakMap();
|
|
15792
15797
|
/**
|
|
15793
15798
|
* When `MapperCache` {@link ~MapperCache#save saves} view position -> model offset mapping, a `CacheItem` is inserted into certain
|
|
@@ -15816,22 +15821,13 @@ Range.prototype.is = function(type) {
|
|
|
15816
15821
|
*
|
|
15817
15822
|
* This is specified as a property to make it easier to set as an event callback and to later turn off that event.
|
|
15818
15823
|
*/ _invalidateOnTextChangeCallback = (evt, viewNode)=>{
|
|
15819
|
-
//
|
|
15820
|
-
|
|
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);
|
|
15824
|
+
// Text node has changed. Clear all the cache starting from before this text node.
|
|
15825
|
+
this._clearCacheStartingBefore(viewNode);
|
|
15827
15826
|
};
|
|
15828
15827
|
/**
|
|
15829
15828
|
* Saves cache for given view position mapping <-> model offset mapping. The view position should be after a node (i.e. it cannot
|
|
15830
15829
|
* be the first position inside its parent, or in other words, `viewOffset` must be greater than `0`).
|
|
15831
15830
|
*
|
|
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
|
-
*
|
|
15835
15831
|
* @param viewParent View position parent.
|
|
15836
15832
|
* @param viewOffset View position offset. Must be greater than `0`.
|
|
15837
15833
|
* @param viewContainer Tracked view position ascendant (it may be the direct parent of the view position).
|
|
@@ -15843,18 +15839,23 @@ Range.prototype.is = function(type) {
|
|
|
15843
15839
|
const cacheItem = cache.cacheMap.get(modelOffset);
|
|
15844
15840
|
if (cacheItem) {
|
|
15845
15841
|
// 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`.
|
|
15847
15842
|
//
|
|
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`.
|
|
15848
15851
|
// We have a `cacheItem` for the `modelOffset`, so we can get a `viewPosition` from there. Before that view position, there
|
|
15849
15852
|
// must be a node. That node must have an index set. This will be the index we will want to use.
|
|
15850
15853
|
// Since we expect `viewOffset` to be greater than 0, then in almost all cases `modelOffset` will be greater than 0 as well.
|
|
15851
15854
|
// As a result, we can expect `cacheItem.viewPosition.nodeBefore` to be set.
|
|
15852
15855
|
//
|
|
15853
15856
|
// However, in an edge case, were the tracked element contains a 0-model-length view element as the first child (UI element or
|
|
15854
|
-
// an empty attribute element), then `modelOffset` will be 0, and `cacheItem
|
|
15855
|
-
// In such edge case, `cacheItem.viewPosition.nodeBefore` is
|
|
15856
|
-
//
|
|
15857
|
-
const viewChild = viewParent.getChild(viewOffset - 1);
|
|
15857
|
+
// an empty attribute element), then `modelOffset` will be 0, and `cacheItem` will be the first cache item, which is before any
|
|
15858
|
+
// view node. In such edge case, `cacheItem.viewPosition.nodeBefore` is undefined, and we manually set to `0`.
|
|
15858
15859
|
const index = cacheItem.viewPosition.nodeBefore ? this._nodeToCacheListIndex.get(cacheItem.viewPosition.nodeBefore) : 0;
|
|
15859
15860
|
this._nodeToCacheListIndex.set(viewChild, index);
|
|
15860
15861
|
return;
|
|
@@ -15983,9 +15984,6 @@ Range.prototype.is = function(type) {
|
|
|
15983
15984
|
}
|
|
15984
15985
|
/**
|
|
15985
15986
|
* 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`.
|
|
15989
15987
|
*/ _clearCacheInsideParent(viewParent, index) {
|
|
15990
15988
|
if (index == 0) {
|
|
15991
15989
|
// Change at the beginning of the parent.
|
|
@@ -15994,22 +15992,28 @@ Range.prototype.is = function(type) {
|
|
|
15994
15992
|
this._clearCacheAll(viewParent);
|
|
15995
15993
|
} else {
|
|
15996
15994
|
// If this is not a tracked element, remove cache starting from before this element.
|
|
15997
|
-
|
|
15998
|
-
this._clearCacheInsideParent(viewParent.parent, viewParent.index);
|
|
15995
|
+
this._clearCacheStartingBefore(viewParent);
|
|
15999
15996
|
}
|
|
16000
15997
|
} else {
|
|
16001
15998
|
// Change in the middle of the parent. Get a view node that's before the change.
|
|
16002
15999
|
const lastValidNode = viewParent.getChild(index - 1);
|
|
16003
|
-
// Then, clear all cache
|
|
16000
|
+
// Then, clear all cache starting from before this view node.
|
|
16004
16001
|
//
|
|
16005
|
-
|
|
16002
|
+
// Possible performance improvement. We could have had `_clearCacheAfter( lastValidNode )` instead.
|
|
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);
|
|
16006
16011
|
}
|
|
16007
16012
|
}
|
|
16008
16013
|
/**
|
|
16009
16014
|
* Clears all the cache for given tracked `viewContainer`.
|
|
16010
16015
|
*/ _clearCacheAll(viewContainer) {
|
|
16011
16016
|
const cache = this._cachedMapping.get(viewContainer);
|
|
16012
|
-
// TODO: Should clear `_nodeToCacheListIndex` too?
|
|
16013
16017
|
if (cache.maxModelOffset > 0) {
|
|
16014
16018
|
cache.maxModelOffset = 0;
|
|
16015
16019
|
cache.cacheList.length = 1;
|
|
@@ -16018,45 +16022,44 @@ Range.prototype.is = function(type) {
|
|
|
16018
16022
|
}
|
|
16019
16023
|
}
|
|
16020
16024
|
/**
|
|
16021
|
-
* Clears all the stored cache
|
|
16025
|
+
* Clears all the stored cache starting before given `viewNode`. The `viewNode` can be any node that is inside a tracked view element
|
|
16022
16026
|
* or view document fragment.
|
|
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) {
|
|
16027
|
+
*/ _clearCacheStartingBefore(viewNode) {
|
|
16027
16028
|
// To quickly invalidate the cache, we base on the cache list index stored with the node. See docs for `this._nodeToCacheListIndex`.
|
|
16028
16029
|
const cacheListIndex = this._nodeToCacheListIndex.get(viewNode);
|
|
16029
16030
|
// If there is no index stored, it means that this `viewNode` has not been cached yet.
|
|
16030
16031
|
if (cacheListIndex === undefined) {
|
|
16031
|
-
// If the node is not cached, maybe it's parent is. We will try to invalidate the cache
|
|
16032
|
+
// If the node is not cached, maybe it's parent is. We will try to invalidate the cache starting from before the parent.
|
|
16033
|
+
// Note, that there always must be a parent if we got here.
|
|
16032
16034
|
const viewParent = viewNode.parent;
|
|
16033
|
-
// If the parent is a non-tracked element, try clearing the cache starting
|
|
16035
|
+
// If the parent is a non-tracked element, try clearing the cache starting before it.
|
|
16034
16036
|
//
|
|
16035
|
-
//
|
|
16036
|
-
//
|
|
16037
|
-
//
|
|
16037
|
+
// This situation may happen e.g. if structure like `<p><strong><em>Foo</em></strong>...` was stepped over in
|
|
16038
|
+
// `Mapper#_findPositionIn()` and the children are not cached yet, but the `<strong>` element is. If something changes
|
|
16039
|
+
// inside this structure, make sure to invalidate all the cache after `<strong>`.
|
|
16038
16040
|
//
|
|
16039
16041
|
// If the parent is a tracked element, then it means there's no cache to clear (nothing after the element is cached).
|
|
16040
|
-
// In this case, there's nothing to do.
|
|
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.
|
|
16042
|
+
// In this case, there's nothing to do.
|
|
16045
16043
|
//
|
|
16046
16044
|
if (!this._cachedMapping.has(viewParent)) {
|
|
16047
|
-
this.
|
|
16045
|
+
this._clearCacheStartingBefore(viewParent);
|
|
16048
16046
|
}
|
|
16049
16047
|
return;
|
|
16050
16048
|
}
|
|
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.
|
|
16051
16054
|
let viewContainer = viewNode.parent;
|
|
16052
16055
|
while(!this._cachedMapping.has(viewContainer)){
|
|
16053
16056
|
viewContainer = viewContainer.parent;
|
|
16054
16057
|
}
|
|
16055
|
-
this.
|
|
16058
|
+
this._clearCacheFromIndex(viewContainer, cacheListIndex);
|
|
16056
16059
|
}
|
|
16057
16060
|
/**
|
|
16058
16061
|
* Clears all the cache in the cache list related to given `viewContainer`, starting from `index` (inclusive).
|
|
16059
|
-
*/
|
|
16062
|
+
*/ _clearCacheFromIndex(viewContainer, index) {
|
|
16060
16063
|
if (index === 0) {
|
|
16061
16064
|
// Don't remove the first entry in the cache (this entry is always a mapping between view offset 0 <-> model offset 0,
|
|
16062
16065
|
// and it is a default value that is always expected to be in the cache list).
|
|
@@ -22839,6 +22842,251 @@ function getFromAttributeCreator(config) {
|
|
|
22839
22842
|
};
|
|
22840
22843
|
}
|
|
22841
22844
|
|
|
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
|
+
|
|
22842
23090
|
// @if CK_DEBUG_ENGINE // const { dumpTrees, initDocumentDumping } = require( '../dev-utils/utils' );
|
|
22843
23091
|
/**
|
|
22844
23092
|
* A controller for the editing pipeline. The editing pipeline controls the {@link ~EditingController#model model} rendering,
|
|
@@ -22902,6 +23150,10 @@ function getFromAttributeCreator(config) {
|
|
|
22902
23150
|
});
|
|
22903
23151
|
// Convert selection from the view to the model when it changes in the view.
|
|
22904
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
|
+
});
|
|
22905
23157
|
// Attach default model converters.
|
|
22906
23158
|
this.downcastDispatcher.on('insert:$text', insertText(), {
|
|
22907
23159
|
priority: 'lowest'
|
|
@@ -23014,6 +23266,28 @@ function getFromAttributeCreator(config) {
|
|
|
23014
23266
|
});
|
|
23015
23267
|
}
|
|
23016
23268
|
}
|
|
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
|
+
}
|
|
23017
23291
|
|
|
23018
23292
|
/**
|
|
23019
23293
|
* The model's schema. It defines the allowed and disallowed structures of nodes as well as nodes' attributes.
|
|
@@ -34002,251 +34276,6 @@ DocumentFragment.prototype.is = function(type) {
|
|
|
34002
34276
|
return false;
|
|
34003
34277
|
}
|
|
34004
34278
|
|
|
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
|
-
|
|
34250
34279
|
/**
|
|
34251
34280
|
* Deletes content of the selection and merge siblings. The resulting selection is always collapsed.
|
|
34252
34281
|
*
|
|
@@ -39263,5 +39292,5 @@ function* convertAttributes(attributes, converter) {
|
|
|
39263
39292
|
}
|
|
39264
39293
|
}
|
|
39265
39294
|
|
|
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,
|
|
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 };
|
|
39267
39296
|
//# sourceMappingURL=index.js.map
|