@ckeditor/ckeditor5-engine 43.1.0 → 43.2.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +182 -99
- package/dist/index.js.map +1 -1
- package/dist/view/observer/domeventobserver.d.ts +5 -0
- package/package.json +2 -2
- package/src/dataprocessor/htmldataprocessor.js +1 -1
- package/src/model/nodelist.js +8 -1
- package/src/model/operation/transform.js +136 -62
- package/src/model/range.js +9 -12
- package/src/model/schema.js +2 -2
- package/src/view/domconverter.js +1 -1
- package/src/view/observer/domeventobserver.d.ts +5 -0
- package/src/view/observer/domeventobserver.js +6 -1
- package/src/view/observer/selectionobserver.js +1 -1
- package/src/view/renderer.js +11 -8
- package/src/view/styles/utils.js +1 -1
- package/src/view/stylesmap.js +8 -9
package/dist/index.js
CHANGED
|
@@ -1621,20 +1621,19 @@ TextProxy$1.prototype.is = function(type) {
|
|
|
1621
1621
|
*
|
|
1622
1622
|
* @param styles Object holding normalized styles.
|
|
1623
1623
|
*/ getStyleNames(styles) {
|
|
1624
|
+
const styleNamesKeysSet = new Set();
|
|
1624
1625
|
// Find all extractable styles that have a value.
|
|
1625
|
-
const
|
|
1626
|
+
for (const name of this._consumables.keys()){
|
|
1626
1627
|
const style = this.getNormalized(name, styles);
|
|
1627
|
-
if (style && typeof style
|
|
1628
|
-
|
|
1628
|
+
if (style && (typeof style != 'object' || Object.keys(style).length)) {
|
|
1629
|
+
styleNamesKeysSet.add(name);
|
|
1629
1630
|
}
|
|
1630
|
-
|
|
1631
|
-
});
|
|
1631
|
+
}
|
|
1632
1632
|
// For simple styles (for example `color`) we don't have a map of those styles
|
|
1633
1633
|
// but they are 1 to 1 with normalized object keys.
|
|
1634
|
-
const
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
]);
|
|
1634
|
+
for (const name of Object.keys(styles)){
|
|
1635
|
+
styleNamesKeysSet.add(name);
|
|
1636
|
+
}
|
|
1638
1637
|
return Array.from(styleNamesKeysSet);
|
|
1639
1638
|
}
|
|
1640
1639
|
/**
|
|
@@ -7705,19 +7704,22 @@ const validNodesToInsert = [
|
|
|
7705
7704
|
// in 'this._updateChildrenMappings()' so it will be processed separately.
|
|
7706
7705
|
return;
|
|
7707
7706
|
}
|
|
7708
|
-
|
|
7709
|
-
|
|
7710
|
-
//
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
for (const key of domAttrKeys){
|
|
7707
|
+
// Remove attributes from DOM elements if they do not exist in the view.
|
|
7708
|
+
//
|
|
7709
|
+
// Note: It is important to first remove DOM attributes and then set new ones, because some view attributes may be renamed
|
|
7710
|
+
// as they are set on DOM (due to unsafe attributes handling). If we set the view attribute first, and then remove
|
|
7711
|
+
// non-existing DOM attributes, then we would remove the attribute that we just set.
|
|
7712
|
+
for (const domAttr of domElement.attributes){
|
|
7713
|
+
const key = domAttr.name;
|
|
7716
7714
|
// All other attributes not present in the DOM should be removed.
|
|
7717
7715
|
if (!viewElement.hasAttribute(key)) {
|
|
7718
7716
|
this.domConverter.removeDomElementAttribute(domElement, key);
|
|
7719
7717
|
}
|
|
7720
7718
|
}
|
|
7719
|
+
// Add or overwrite attributes.
|
|
7720
|
+
for (const key of viewElement.getAttributeKeys()){
|
|
7721
|
+
this.domConverter.setDomElementAttribute(domElement, key, viewElement.getAttribute(key), viewElement);
|
|
7722
|
+
}
|
|
7721
7723
|
}
|
|
7722
7724
|
/**
|
|
7723
7725
|
* Checks if elements child list needs to be updated and possibly updates it.
|
|
@@ -8453,7 +8455,7 @@ const UNSAFE_ELEMENT_REPLACEMENT_ATTRIBUTE = 'data-ck-unsafe-element';
|
|
|
8453
8455
|
if (viewElement) {
|
|
8454
8456
|
this._domToViewMapping.delete(domElement);
|
|
8455
8457
|
this._viewToDomMapping.delete(viewElement);
|
|
8456
|
-
for (const child of
|
|
8458
|
+
for (const child of domElement.children){
|
|
8457
8459
|
this.unbindDomElement(child);
|
|
8458
8460
|
}
|
|
8459
8461
|
}
|
|
@@ -9927,6 +9929,10 @@ const UNSAFE_ELEMENT_REPLACEMENT_ATTRIBUTE = 'data-ck-unsafe-element';
|
|
|
9927
9929
|
* If set to `true` DOM events will be listened on the capturing phase.
|
|
9928
9930
|
* Default value is `false`.
|
|
9929
9931
|
*/ useCapture = false;
|
|
9932
|
+
/**
|
|
9933
|
+
* If set to `true`, indicates that the function specified by listener will never call `preventDefault()`.
|
|
9934
|
+
* Default value is `false`.
|
|
9935
|
+
*/ usePassive = false;
|
|
9930
9936
|
/**
|
|
9931
9937
|
* @inheritDoc
|
|
9932
9938
|
*/ observe(domElement) {
|
|
@@ -9939,7 +9945,8 @@ const UNSAFE_ELEMENT_REPLACEMENT_ATTRIBUTE = 'data-ck-unsafe-element';
|
|
|
9939
9945
|
this.onDomEvent(domEvent);
|
|
9940
9946
|
}
|
|
9941
9947
|
}, {
|
|
9942
|
-
useCapture: this.useCapture
|
|
9948
|
+
useCapture: this.useCapture,
|
|
9949
|
+
usePassive: this.usePassive
|
|
9943
9950
|
});
|
|
9944
9951
|
});
|
|
9945
9952
|
}
|
|
@@ -10481,7 +10488,7 @@ function sameNodes(child1, child2) {
|
|
|
10481
10488
|
priority: 'highest',
|
|
10482
10489
|
useCapture: true
|
|
10483
10490
|
});
|
|
10484
|
-
this.listenTo(domDocument, 'selectionchange', (
|
|
10491
|
+
this.listenTo(domDocument, 'selectionchange', ()=>{
|
|
10485
10492
|
// @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) {
|
|
10486
10493
|
// @if CK_DEBUG_TYPING // _debouncedLine();
|
|
10487
10494
|
// @if CK_DEBUG_TYPING // const domSelection = domDocument.defaultView!.getSelection();
|
|
@@ -11909,7 +11916,14 @@ Node$1.prototype.is = function(type) {
|
|
|
11909
11916
|
* {@link module:engine/model/node~Node#offsetSize offset sizes} of all nodes that are before this node in this node list.
|
|
11910
11917
|
*/ getNodeStartOffset(node) {
|
|
11911
11918
|
const index = this.getNodeIndex(node);
|
|
11912
|
-
|
|
11919
|
+
if (index === null) {
|
|
11920
|
+
return null;
|
|
11921
|
+
}
|
|
11922
|
+
let sum = 0;
|
|
11923
|
+
for(let i = 0; i < index; i++){
|
|
11924
|
+
sum += this._nodes[i].offsetSize;
|
|
11925
|
+
}
|
|
11926
|
+
return sum;
|
|
11913
11927
|
}
|
|
11914
11928
|
/**
|
|
11915
11929
|
* Converts index to offset in node list.
|
|
@@ -14520,26 +14534,23 @@ Position.prototype.is = function(type) {
|
|
|
14520
14534
|
// If we are going to return just a one range, one of the ranges need to be the reference one.
|
|
14521
14535
|
// Other ranges will be stuck to that range, if possible.
|
|
14522
14536
|
const ref = ranges[0];
|
|
14523
|
-
// 2. Sort all the ranges so it's easier to process them.
|
|
14537
|
+
// 2. Sort all the ranges, so it's easier to process them.
|
|
14524
14538
|
ranges.sort((a, b)=>{
|
|
14525
14539
|
return a.start.isAfter(b.start) ? 1 : -1;
|
|
14526
14540
|
});
|
|
14527
14541
|
// 3. Check at which index the reference range is now.
|
|
14528
14542
|
const refIndex = ranges.indexOf(ref);
|
|
14529
14543
|
// 4. At this moment we don't need the original range.
|
|
14530
|
-
// We are going to modify the result and we need to return a new instance of Range.
|
|
14544
|
+
// We are going to modify the result, and we need to return a new instance of Range.
|
|
14531
14545
|
// We have to create a copy of the reference range.
|
|
14532
14546
|
const result = new this(ref.start, ref.end);
|
|
14533
14547
|
// 5. Ranges should be checked and glued starting from the range that is closest to the reference range.
|
|
14534
14548
|
// Since ranges are sorted, start with the range with index that is closest to reference range index.
|
|
14535
|
-
|
|
14536
|
-
|
|
14537
|
-
|
|
14538
|
-
|
|
14539
|
-
|
|
14540
|
-
} else {
|
|
14541
|
-
break;
|
|
14542
|
-
}
|
|
14549
|
+
for(let i = refIndex - 1; i >= 0; i--){
|
|
14550
|
+
if (ranges[i].end.isEqual(result.start)) {
|
|
14551
|
+
result.start = Position._createAt(ranges[i].start);
|
|
14552
|
+
} else {
|
|
14553
|
+
break;
|
|
14543
14554
|
}
|
|
14544
14555
|
}
|
|
14545
14556
|
// 6. Ranges should be checked and glued starting from the range that is closest to the reference range.
|
|
@@ -21899,7 +21910,7 @@ const CONSUMABLE_TYPES = [
|
|
|
21899
21910
|
_sourceDefinitions = {};
|
|
21900
21911
|
/**
|
|
21901
21912
|
* A dictionary containing attribute properties.
|
|
21902
|
-
*/ _attributeProperties =
|
|
21913
|
+
*/ _attributeProperties = Object.create(null);
|
|
21903
21914
|
/**
|
|
21904
21915
|
* Stores additional callbacks registered for schema items, which are evaluated when {@link ~Schema#checkChild} is called.
|
|
21905
21916
|
*
|
|
@@ -22487,7 +22498,7 @@ const CONSUMABLE_TYPES = [
|
|
|
22487
22498
|
*
|
|
22488
22499
|
* @param attributeName A name of the attribute.
|
|
22489
22500
|
*/ getAttributeProperties(attributeName) {
|
|
22490
|
-
return this._attributeProperties[attributeName] ||
|
|
22501
|
+
return this._attributeProperties[attributeName] || Object.create(null);
|
|
22491
22502
|
}
|
|
22492
22503
|
/**
|
|
22493
22504
|
* Returns the lowest {@link module:engine/model/schema~Schema#isLimit limit element} containing the entire
|
|
@@ -23886,7 +23897,7 @@ function removeDisallowedAttributeFromNode(schema, node, writer) {
|
|
|
23886
23897
|
// Wrap data with a <body> tag so leading non-layout nodes (like <script>, <style>, HTML comment)
|
|
23887
23898
|
// will be preserved in the body collection.
|
|
23888
23899
|
// Do it only for data that is not a full HTML document.
|
|
23889
|
-
if (
|
|
23900
|
+
if (!/<(?:html|body|head|meta)(?:\s[^>]*)?>/i.test(data.trim().slice(0, 10_000))) {
|
|
23890
23901
|
data = `<body>${data}</body>`;
|
|
23891
23902
|
}
|
|
23892
23903
|
const document = this.domParser.parseFromString(data, 'text/html');
|
|
@@ -26921,6 +26932,8 @@ const transformations = new Map();
|
|
|
26921
26932
|
operationsA.splice(i, 1, ...newOpsA);
|
|
26922
26933
|
operationsB.splice(indexB, 1, ...newOpsB);
|
|
26923
26934
|
}
|
|
26935
|
+
handlePartialMarkerOperations(operationsA);
|
|
26936
|
+
handlePartialMarkerOperations(operationsB);
|
|
26924
26937
|
if (options.padWithNoOps) {
|
|
26925
26938
|
// If no-operations padding is enabled, count how many extra `a` and `b` operations were generated.
|
|
26926
26939
|
const brokenOperationsACount = operationsA.length - data.originalOperationsACount;
|
|
@@ -27036,6 +27049,9 @@ const transformations = new Map();
|
|
|
27036
27049
|
} else {
|
|
27037
27050
|
const range = Range._createFromPositionAndShift(opB.sourcePosition, opB.howMany);
|
|
27038
27051
|
if (opA.splitPosition.hasSameParentAs(opB.sourcePosition) && range.containsPosition(opA.splitPosition)) {
|
|
27052
|
+
// TODO: Potential bug -- we are saving offset value directly and it is not later updated during OT.
|
|
27053
|
+
// TODO: This may cause a bug it here was an non-undone operation that may have impacted this offset.
|
|
27054
|
+
// TODO: Similar error was with MarkerOperation relations, where full path was saved and never updated.
|
|
27039
27055
|
const howMany = range.end.offset - opA.splitPosition.offset;
|
|
27040
27056
|
const offset = opA.splitPosition.offset - range.start.offset;
|
|
27041
27057
|
this._setRelation(opA, opB, {
|
|
@@ -27073,17 +27089,7 @@ const transformations = new Map();
|
|
|
27073
27089
|
if (!markerRange) {
|
|
27074
27090
|
return;
|
|
27075
27091
|
}
|
|
27076
|
-
if (opB instanceof
|
|
27077
|
-
const movedRange = Range._createFromPositionAndShift(opB.sourcePosition, opB.howMany);
|
|
27078
|
-
const affectedLeft = movedRange.containsPosition(markerRange.start) || movedRange.start.isEqual(markerRange.start);
|
|
27079
|
-
const affectedRight = movedRange.containsPosition(markerRange.end) || movedRange.end.isEqual(markerRange.end);
|
|
27080
|
-
if ((affectedLeft || affectedRight) && !movedRange.containsRange(markerRange)) {
|
|
27081
|
-
this._setRelation(opA, opB, {
|
|
27082
|
-
side: affectedLeft ? 'left' : 'right',
|
|
27083
|
-
path: affectedLeft ? markerRange.start.path.slice() : markerRange.end.path.slice()
|
|
27084
|
-
});
|
|
27085
|
-
}
|
|
27086
|
-
} else if (opB instanceof MergeOperation) {
|
|
27092
|
+
if (opB instanceof MergeOperation) {
|
|
27087
27093
|
const wasInLeftElement = markerRange.start.isEqual(opB.targetPosition);
|
|
27088
27094
|
const wasStartBeforeMergedElement = markerRange.start.isEqual(opB.deletionPosition);
|
|
27089
27095
|
const wasEndBeforeMergedElement = markerRange.end.isEqual(opB.deletionPosition);
|
|
@@ -27195,6 +27201,56 @@ const transformations = new Map();
|
|
|
27195
27201
|
operations.push(new NoOperation(0));
|
|
27196
27202
|
}
|
|
27197
27203
|
}
|
|
27204
|
+
/**
|
|
27205
|
+
* Transformed operations set may include marker operations which were broken into multiple marker operations during transformation.
|
|
27206
|
+
* It represents marker range being broken into multiple pieces as the transformation was processed. Each partial marker operation is
|
|
27207
|
+
* a piece of the original marker range.
|
|
27208
|
+
*
|
|
27209
|
+
* These partial marker operations ("marker range pieces") should be "glued" together if, after transformations, the ranges ended up
|
|
27210
|
+
* next to each other.
|
|
27211
|
+
*
|
|
27212
|
+
* If the ranges did not end up next to each other, then partial marker operations should be discarded, as the marker range cannot
|
|
27213
|
+
* be broken into two pieces.
|
|
27214
|
+
*
|
|
27215
|
+
* There is always one "reference" marker operation (the original operation) and there may be some partial marker operations. Partial
|
|
27216
|
+
* marker operations have base version set to `-1`. If the `operations` set includes partial marker operations, then they are always
|
|
27217
|
+
* after the original marker operation.
|
|
27218
|
+
*
|
|
27219
|
+
* See also `MarkerOperation` x `MoveOperation` transformation.
|
|
27220
|
+
* See also https://github.com/ckeditor/ckeditor5/pull/17071.
|
|
27221
|
+
*/ function handlePartialMarkerOperations(operations) {
|
|
27222
|
+
const markerOps = new Map();
|
|
27223
|
+
for(let i = 0; i < operations.length; i++){
|
|
27224
|
+
const op = operations[i];
|
|
27225
|
+
if (!(op instanceof MarkerOperation)) {
|
|
27226
|
+
continue;
|
|
27227
|
+
}
|
|
27228
|
+
if (op.baseVersion !== -1) {
|
|
27229
|
+
markerOps.set(op.name, {
|
|
27230
|
+
op,
|
|
27231
|
+
ranges: op.newRange ? [
|
|
27232
|
+
op.newRange
|
|
27233
|
+
] : []
|
|
27234
|
+
});
|
|
27235
|
+
} else {
|
|
27236
|
+
if (op.newRange) {
|
|
27237
|
+
// `markerOps.get( op.name )` must exist because original marker operation is always before partial marker operations.
|
|
27238
|
+
// If the original marker operation was changed to `NoOperation`, then the partial marker operations would be changed
|
|
27239
|
+
// to `NoOperation` as well, so this is not a case.
|
|
27240
|
+
markerOps.get(op.name).ranges.push(op.newRange);
|
|
27241
|
+
}
|
|
27242
|
+
operations.splice(i, 1);
|
|
27243
|
+
i--;
|
|
27244
|
+
}
|
|
27245
|
+
}
|
|
27246
|
+
for (const { op, ranges } of markerOps.values()){
|
|
27247
|
+
if (ranges.length) {
|
|
27248
|
+
op.newRange = Range._createFromRanges(ranges);
|
|
27249
|
+
} else {
|
|
27250
|
+
op.newRange = null;
|
|
27251
|
+
}
|
|
27252
|
+
}
|
|
27253
|
+
}
|
|
27198
27254
|
// -----------------------
|
|
27199
27255
|
setTransformation(AttributeOperation, AttributeOperation, (a, b, context)=>{
|
|
27200
27256
|
// If operations in conflict, check if their ranges intersect and manage them properly.
|
|
@@ -27562,32 +27618,47 @@ setTransformation(MarkerOperation, MergeOperation, (a, b)=>{
|
|
|
27562
27618
|
a
|
|
27563
27619
|
];
|
|
27564
27620
|
});
|
|
27565
|
-
setTransformation(MarkerOperation, MoveOperation, (a, b
|
|
27621
|
+
setTransformation(MarkerOperation, MoveOperation, (a, b)=>{
|
|
27622
|
+
const result = [
|
|
27623
|
+
a
|
|
27624
|
+
];
|
|
27566
27625
|
if (a.oldRange) {
|
|
27567
27626
|
a.oldRange = Range._createFromRanges(a.oldRange._getTransformedByMoveOperation(b));
|
|
27568
27627
|
}
|
|
27569
27628
|
if (a.newRange) {
|
|
27570
|
-
|
|
27571
|
-
|
|
27572
|
-
|
|
27573
|
-
|
|
27574
|
-
|
|
27575
|
-
|
|
27576
|
-
|
|
27577
|
-
|
|
27578
|
-
|
|
27579
|
-
|
|
27580
|
-
|
|
27581
|
-
|
|
27582
|
-
|
|
27583
|
-
|
|
27584
|
-
|
|
27629
|
+
// In many simple cases the marker range will be kept integral after the transformation. For example, if some nodes
|
|
27630
|
+
// were inserted before the range, or into the range, then the marker range is not broken into two.
|
|
27631
|
+
//
|
|
27632
|
+
// However, if some nodes are taken out of the range and moved somewhere else, or are moved into the range, then the marker
|
|
27633
|
+
// range is "broken" into two or three pieces, and these pieces must be transformed and updated separately.
|
|
27634
|
+
//
|
|
27635
|
+
// When the marker range is transformed by move operation, as a result we get an array with one (simple case) or multiple
|
|
27636
|
+
// ("broken range" case) ranges.
|
|
27637
|
+
const ranges = a.newRange._getTransformedByMoveOperation(b);
|
|
27638
|
+
a.newRange = ranges[0];
|
|
27639
|
+
// If there are multiple ranges, we will create separate marker operations for each piece of the original marker range.
|
|
27640
|
+
// Since they will be marker operations, they will be processed through the transformation process.
|
|
27641
|
+
//
|
|
27642
|
+
// However, we cannot create multiple ranges for the same marker (for the same marker name). A marker has only one range.
|
|
27643
|
+
// So, we cannot really have multiple marker operations for the same marker. We will keep the track of the separate marker
|
|
27644
|
+
// operations to see, if after all transformations, the marker pieces are next to each other or not. If so, we will glue
|
|
27645
|
+
// them together to the original marker operation (`a`). If not, we will discard them. These extra operations will never
|
|
27646
|
+
// be executed, as they will only exist temporarily during the transformation process.
|
|
27647
|
+
//
|
|
27648
|
+
// We will call these additional marker operations "partial marker operations" and we will mark them with negative base version.
|
|
27649
|
+
//
|
|
27650
|
+
// See also `handlePartialMarkerOperations()`.
|
|
27651
|
+
// See also https://github.com/ckeditor/ckeditor5/pull/17071.
|
|
27652
|
+
//
|
|
27653
|
+
for(let i = 1; i < ranges.length; i++){
|
|
27654
|
+
const op = a.clone();
|
|
27655
|
+
op.oldRange = null;
|
|
27656
|
+
op.newRange = ranges[i];
|
|
27657
|
+
op.baseVersion = -1;
|
|
27658
|
+
result.push(op);
|
|
27585
27659
|
}
|
|
27586
|
-
a.newRange = Range._createFromRanges(a.newRange._getTransformedByMoveOperation(b));
|
|
27587
27660
|
}
|
|
27588
|
-
return
|
|
27589
|
-
a
|
|
27590
|
-
];
|
|
27661
|
+
return result;
|
|
27591
27662
|
});
|
|
27592
27663
|
setTransformation(MarkerOperation, SplitOperation, (a, b, context)=>{
|
|
27593
27664
|
if (a.oldRange) {
|
|
@@ -27600,6 +27671,8 @@ setTransformation(MarkerOperation, SplitOperation, (a, b, context)=>{
|
|
|
27600
27671
|
a.newRange.start = Position._createAt(b.insertionPosition);
|
|
27601
27672
|
} else if (a.newRange.start.isEqual(b.splitPosition) && !context.abRelation.wasInLeftElement) {
|
|
27602
27673
|
a.newRange.start = Position._createAt(b.moveTargetPosition);
|
|
27674
|
+
} else {
|
|
27675
|
+
a.newRange.start = aNewRange.start;
|
|
27603
27676
|
}
|
|
27604
27677
|
if (a.newRange.end.isEqual(b.splitPosition) && context.abRelation.wasInRightElement) {
|
|
27605
27678
|
a.newRange.end = Position._createAt(b.moveTargetPosition);
|
|
@@ -27712,6 +27785,7 @@ setTransformation(MergeOperation, MergeOperation, (a, b, context)=>{
|
|
|
27712
27785
|
}
|
|
27713
27786
|
}
|
|
27714
27787
|
// The default case.
|
|
27788
|
+
// TODO: Possibly, there's a missing case for same `targetPosition` but different `sourcePosition`.
|
|
27715
27789
|
//
|
|
27716
27790
|
if (a.sourcePosition.hasSameParentAs(b.targetPosition)) {
|
|
27717
27791
|
a.howMany += b.howMany;
|
|
@@ -27737,10 +27811,10 @@ setTransformation(MergeOperation, MoveOperation, (a, b, context)=>{
|
|
|
27737
27811
|
// was to have it all deleted, together with its children. From user experience point of view, moving back the
|
|
27738
27812
|
// removed nodes might be unexpected. This means that in this scenario we will block the merging.
|
|
27739
27813
|
//
|
|
27740
|
-
// The exception
|
|
27814
|
+
// The exception to this rule would be if the remove operation was later undone.
|
|
27741
27815
|
//
|
|
27742
27816
|
const removedRange = Range._createFromPositionAndShift(b.sourcePosition, b.howMany);
|
|
27743
|
-
if (b.type == 'remove' && !context.bWasUndone
|
|
27817
|
+
if (b.type == 'remove' && !context.bWasUndone) {
|
|
27744
27818
|
if (a.deletionPosition.hasSameParentAs(b.sourcePosition) && removedRange.containsPosition(a.sourcePosition)) {
|
|
27745
27819
|
return [
|
|
27746
27820
|
new NoOperation(0)
|
|
@@ -27822,59 +27896,66 @@ setTransformation(MergeOperation, SplitOperation, (a, b, context)=>{
|
|
|
27822
27896
|
// Case 1:
|
|
27823
27897
|
//
|
|
27824
27898
|
// Merge operation moves nodes to the place where split happens.
|
|
27825
|
-
//
|
|
27899
|
+
//
|
|
27900
|
+
// This is a classic situation when there are two paragraphs, and there is a split (enter) at the end of the first
|
|
27826
27901
|
// paragraph and there is a merge (delete) at the beginning of the second paragraph:
|
|
27827
27902
|
//
|
|
27828
27903
|
// <p>Foo{}</p><p>[]Bar</p>.
|
|
27829
27904
|
//
|
|
27830
|
-
//
|
|
27905
|
+
// User A presses enter after `Foo`, while User B presses backspace before `Bar`. It is intuitive that after both operations, the
|
|
27906
|
+
// editor state should stay the same.
|
|
27831
27907
|
//
|
|
27832
27908
|
// State after split:
|
|
27833
|
-
// <p>Foo</p><p></p><p>Bar</p>
|
|
27909
|
+
// <p>Foo</p><p></p><p>[]Bar</p>
|
|
27834
27910
|
//
|
|
27835
|
-
//
|
|
27911
|
+
// When this happens, `Bar` should be merged to the newly created paragraph, to maintain the editor state:
|
|
27836
27912
|
// <p>Foo</p><p>Bar</p>
|
|
27837
27913
|
//
|
|
27838
|
-
//
|
|
27914
|
+
// Another option is to merge into the original paragraph `Foo`, according to the `targetPosition`. This results in an incorrect state:
|
|
27839
27915
|
// <p>FooBar</p><p></p>
|
|
27840
27916
|
//
|
|
27841
|
-
//
|
|
27842
|
-
//
|
|
27917
|
+
// Also, consider an example where User A also writes something in the new paragraph:
|
|
27918
|
+
// <p>Foo</p><p>Xyz</p><p>[]Bar</p>
|
|
27919
|
+
//
|
|
27920
|
+
// In this case it is clear that merge should happen into `[ 1, 3 ]` not into `[ 0, 3 ]`. It first has to be transformed to `[ 1, 0 ]`,
|
|
27921
|
+
// and then transformed be insertion into `[ 1, 3 ]`.
|
|
27843
27922
|
//
|
|
27844
|
-
//
|
|
27923
|
+
// So, usually we want to move `targetPosition` to the new paragraph when it is same as split position. This is how it is handled
|
|
27924
|
+
// in the default transformation (`_getTransformedBySplitOperation()`). We don't need a special case for this.
|
|
27845
27925
|
//
|
|
27846
|
-
//
|
|
27847
|
-
// happens when the merge operation earlier was transformed by "the same" merge operation. If merge operation
|
|
27848
|
-
// targets inside the element we want to keep the original target position (and not transform it) because
|
|
27849
|
-
// we have additional context telling us that we want to merge to the original element. We can check if the
|
|
27850
|
-
// merge operation points inside element by checking what is `SplitOperation#howMany`. Since merge target position
|
|
27851
|
-
// is same as split position, if `howMany` is non-zero, it means that the merge target position is inside an element.
|
|
27926
|
+
// However, there are two exceptions, when we **do not** want to transform `targetPosition`, and we need a special case then.
|
|
27852
27927
|
//
|
|
27853
|
-
//
|
|
27928
|
+
// These exceptions happen only if undo is involved. During OT, above presented case (`<p>Foo{}</p><p>[]Bar</p>`) is the only way
|
|
27929
|
+
// how `SplitOperation#splitPosition` and `MergeOperation#targetPosition` can be the same.
|
|
27930
|
+
//
|
|
27931
|
+
// First exception is when the element to merge is in the graveyard and split operation uses it. In that case
|
|
27854
27932
|
// if target position would be transformed, the merge operation would target at the source position:
|
|
27855
27933
|
//
|
|
27856
|
-
// root: <p>Foo</p> graveyard: <p></p>
|
|
27934
|
+
// root: <p>Foo[]</p> graveyard: <p></p>
|
|
27857
27935
|
//
|
|
27858
27936
|
// SplitOperation: root [ 0, 3 ] using graveyard [ 0 ] (howMany = 0)
|
|
27859
27937
|
// MergeOperation: graveyard [ 0, 0 ] -> root [ 0, 3 ] (howMany = 0)
|
|
27860
27938
|
//
|
|
27861
|
-
// Since split operation moves the graveyard
|
|
27862
|
-
//
|
|
27863
|
-
//
|
|
27864
|
-
// root: <p>Foo</p><p></p> graveyard:
|
|
27939
|
+
// Since split operation moves the graveyard element back to the root (to path `[ 1 ]`), the merge operation `sourcePosition` changes.
|
|
27940
|
+
// After split we have: `<p>Foo</p><p></p>`, so `sourcePosition` is `[ 1, 0 ]`. But if `targetPosition` is transformed, then it
|
|
27941
|
+
// also becomes `[ 1, 0 ]`. In this case, we want to keep the `targetPosition` as it was.
|
|
27865
27942
|
//
|
|
27866
|
-
//
|
|
27943
|
+
// Second exception is connected strictly with undo relations. If this `MergeOperation` was earlier transformed by
|
|
27944
|
+
// `MergeOperation` and we stored an information that earlier the target position was not affected, then here, when transforming by
|
|
27945
|
+
// `SplitOperation` we are not going to change it as well.
|
|
27867
27946
|
//
|
|
27868
|
-
//
|
|
27947
|
+
// For these two cases we will only transform `sourcePosition` and return early.
|
|
27869
27948
|
//
|
|
27870
|
-
//
|
|
27871
|
-
//
|
|
27872
|
-
//
|
|
27949
|
+
// Note, that earlier there was also third special case here. `targetPosition` was not transformed, if it pointed into the middle of
|
|
27950
|
+
// target element, not into its end (as usual). This can also happen only with undo involved. However, it wasn't always a correct
|
|
27951
|
+
// solution, as in some cases we actually wanted to transform `targetPosition`. Also, this case usually happens together with the second
|
|
27952
|
+
// case described above. There is only one scenario that we have in our unit tests, where this third case happened without second case.
|
|
27953
|
+
// However, this scenario went fine no matter if we transformed `targetPosition` or not. That's because this happened in the middle
|
|
27954
|
+
// of transformation process and the operation was correctly transformed later on.
|
|
27873
27955
|
//
|
|
27874
27956
|
if (a.targetPosition.isEqual(b.splitPosition)) {
|
|
27875
|
-
const mergeInside = b.howMany != 0;
|
|
27876
27957
|
const mergeSplittingElement = b.graveyardPosition && a.deletionPosition.isEqual(b.graveyardPosition);
|
|
27877
|
-
if (
|
|
27958
|
+
if (mergeSplittingElement || context.abRelation == 'mergeTargetNotMoved') {
|
|
27878
27959
|
a.sourcePosition = a.sourcePosition._getTransformedBySplitOperation(b);
|
|
27879
27960
|
return [
|
|
27880
27961
|
a
|
|
@@ -28218,12 +28299,14 @@ setTransformation(MoveOperation, MergeOperation, (a, b, context)=>{
|
|
|
28218
28299
|
const results = [];
|
|
28219
28300
|
let gyMoveSource = b.graveyardPosition.clone();
|
|
28220
28301
|
let splitNodesMoveSource = b.targetPosition._getTransformedByMergeOperation(b);
|
|
28302
|
+
// `a.targetPosition` points to graveyard, so it was probably affected by `b` (which moved merged element to the graveyard).
|
|
28303
|
+
const aTarget = a.targetPosition.getTransformedByOperation(b);
|
|
28221
28304
|
if (a.howMany > 1) {
|
|
28222
|
-
results.push(new MoveOperation(a.sourcePosition, a.howMany - 1,
|
|
28223
|
-
gyMoveSource = gyMoveSource._getTransformedByMove(a.sourcePosition,
|
|
28224
|
-
splitNodesMoveSource = splitNodesMoveSource._getTransformedByMove(a.sourcePosition,
|
|
28305
|
+
results.push(new MoveOperation(a.sourcePosition, a.howMany - 1, aTarget, 0));
|
|
28306
|
+
gyMoveSource = gyMoveSource._getTransformedByMove(a.sourcePosition, aTarget, a.howMany - 1);
|
|
28307
|
+
splitNodesMoveSource = splitNodesMoveSource._getTransformedByMove(a.sourcePosition, aTarget, a.howMany - 1);
|
|
28225
28308
|
}
|
|
28226
|
-
const gyMoveTarget = b.deletionPosition._getCombined(a.sourcePosition,
|
|
28309
|
+
const gyMoveTarget = b.deletionPosition._getCombined(a.sourcePosition, aTarget);
|
|
28227
28310
|
const gyMove = new MoveOperation(gyMoveSource, 1, gyMoveTarget, 0);
|
|
28228
28311
|
const splitNodesMoveTargetPath = gyMove.getMovedRangeStart().path.slice();
|
|
28229
28312
|
splitNodesMoveTargetPath.push(0);
|
|
@@ -35839,7 +35922,7 @@ const urlRegExp = /^url\(/;
|
|
|
35839
35922
|
* // will return [ 'red', 'blue', 'RGB(0, 0, 0)' ]
|
|
35840
35923
|
* ```
|
|
35841
35924
|
*/ function getShorthandValues(string) {
|
|
35842
|
-
const matches = string.matchAll(CSS_SHORTHAND_VALUE_REGEXP);
|
|
35925
|
+
const matches = string.trim().slice(0, 1500).matchAll(CSS_SHORTHAND_VALUE_REGEXP);
|
|
35843
35926
|
return Array.from(matches).map((i)=>i[0]);
|
|
35844
35927
|
}
|
|
35845
35928
|
|