|
|
@@ -72,7 +72,7 @@ function updateBaseVersions( operations, baseVersion ) {
|
|
|
return operations;
|
|
|
}
|
|
|
|
|
|
-function transform( a, b, context = { aIsStrong: false } ) {
|
|
|
+function transform( a, b, context = { aIsStrong: false, wasUndone: () => false, getRelation: () => null } ) {
|
|
|
const transformationFunction = getTransformation( a, b );
|
|
|
|
|
|
return transformationFunction( a.clone(), b, context );
|
|
|
@@ -124,6 +124,8 @@ function transformSets( operationsA, operationsB, options ) {
|
|
|
if ( options.useContext ) {
|
|
|
updateOriginalOperation( context, opA, newOpA );
|
|
|
updateOriginalOperation( context, opB, newOpB );
|
|
|
+
|
|
|
+ updateRelations( context, opA, opB );
|
|
|
}
|
|
|
|
|
|
opsA.splice( k, 1, ...newOpA );
|
|
|
@@ -176,6 +178,7 @@ function initializeContext( opsA, opsB, options ) {
|
|
|
}
|
|
|
|
|
|
context.document = options.document;
|
|
|
+ context.relations = new Map();
|
|
|
|
|
|
context.wasUndone = function( op ) {
|
|
|
if ( !options.useContext ) {
|
|
|
@@ -187,9 +190,151 @@ function initializeContext( opsA, opsB, options ) {
|
|
|
return this.document.history.isUndoneOperation( originalOp );
|
|
|
};
|
|
|
|
|
|
+ context.getRelation = function( opA, opB ) {
|
|
|
+ if ( !options.useContext ) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ const origB = this.originalOperations.get( opB );
|
|
|
+ const undoneB = this.document.history.getUndoneOperation( origB );
|
|
|
+
|
|
|
+ if ( !undoneB ) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ const origA = this.originalOperations.get( opA );
|
|
|
+ const relationsA = this.relations.get( origA );
|
|
|
+
|
|
|
+ if ( relationsA ) {
|
|
|
+ return relationsA.get( undoneB ) || null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ };
|
|
|
+
|
|
|
return context;
|
|
|
}
|
|
|
|
|
|
+function updateRelations( context, opA, opB ) {
|
|
|
+ switch ( opA.constructor ) {
|
|
|
+ case MoveOperation:
|
|
|
+ case RemoveOperation:
|
|
|
+ case ReinsertOperation: {
|
|
|
+ switch ( opB.constructor ) {
|
|
|
+ case MergeOperation: {
|
|
|
+ if ( opA.targetPosition.isEqual( opB.sourcePosition ) || opB.movedRange.containsPosition( opA.targetPosition ) ) {
|
|
|
+ setRelation( context, opA, opB, 'insertAtSource' );
|
|
|
+ setRelation( context, opB, opA, 'splitBefore' );
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ case MoveOperation: {
|
|
|
+ if ( opA.targetPosition.isEqual( opB.sourcePosition ) || opA.targetPosition.isBefore( opB.sourcePosition ) ) {
|
|
|
+ setRelation( context, opA, opB, 'insertBefore' );
|
|
|
+ setRelation( context, opB, opA, 'insertAfter' );
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ case UnwrapOperation: {
|
|
|
+ const isInside = opA.targetPosition.hasSameParentAs( opB.targetPosition );
|
|
|
+
|
|
|
+ if ( isInside ) {
|
|
|
+ setRelation( context, opA, opB, 'insertInside' );
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ case SplitOperation: {
|
|
|
+ switch ( opB.constructor ) {
|
|
|
+ case MergeOperation: {
|
|
|
+ if ( opA.position.isBefore( opB.sourcePosition ) ) {
|
|
|
+ setRelation( context, opA, opB, 'splitBefore' );
|
|
|
+ setRelation( context, opB, opA, 'splitAfter' );
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ case MoveOperation: {
|
|
|
+ if ( opA.position.isEqual( opB.sourcePosition ) || opA.position.isBefore( opB.sourcePosition ) ) {
|
|
|
+ setRelation( context, opA, opB, 'splitBefore' );
|
|
|
+ setRelation( context, opB, opA, 'insertAtSource' );
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ case UnwrapOperation: {
|
|
|
+ const isInside = opA.position.hasSameParentAs( opB.position );
|
|
|
+
|
|
|
+ if ( isInside ) {
|
|
|
+ setRelation( context, opA, opB, 'splitInside' );
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ case InsertOperation: {
|
|
|
+ switch ( opB.constructor ) {
|
|
|
+ case MergeOperation: {
|
|
|
+ if ( opA.position.isEqual( opB.sourcePosition ) || opB.movedRange.containsPosition( opA.position ) ) {
|
|
|
+ setRelation( context, opA, opB, 'insertAtSource' );
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ case MoveOperation: {
|
|
|
+ if ( opA.position.isEqual( opB.sourcePosition ) || opA.position.isBefore( opB.sourcePosition ) ) {
|
|
|
+ setRelation( context, opA, opB, 'insertBefore' );
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ case UnwrapOperation: {
|
|
|
+ const isInside = opA.position.hasSameParentAs( opB.position );
|
|
|
+
|
|
|
+ if ( isInside ) {
|
|
|
+ setRelation( context, opA, opB, 'insertInside' );
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function setRelation( context, opA, opB, relation ) {
|
|
|
+ const origA = context.originalOperations.get( opA );
|
|
|
+ const origB = context.originalOperations.get( opB );
|
|
|
+
|
|
|
+ let relationsA = context.relations.get( origA );
|
|
|
+
|
|
|
+ if ( !relationsA ) {
|
|
|
+ relationsA = new Map();
|
|
|
+ context.relations.set( origA, relationsA );
|
|
|
+ }
|
|
|
+
|
|
|
+ relationsA.set( origB, relation );
|
|
|
+}
|
|
|
+
|
|
|
function updateOriginalOperation( context, oldOp, newOps ) {
|
|
|
const originalOp = context.originalOperations.get( oldOp );
|
|
|
|
|
|
@@ -345,8 +490,12 @@ setTransformation( AttributeOperation, MergeOperation, ( a, b ) => {
|
|
|
|
|
|
// Case 1: Attribute change on the merged element. In this case, the merged element was moved to graveyard.
|
|
|
// An additional attribute operation that will change the (re)moved element needs to be generated.
|
|
|
+ // Do it only, if there is more than one element in attribute range. If there is only one element,
|
|
|
+ // it will be handled by the default algorithm.
|
|
|
//
|
|
|
- if ( a.range.start.hasSameParentAs( b.deletionPosition ) ) {
|
|
|
+ const howMany = a.range.end.offset - a.range.start.offset;
|
|
|
+
|
|
|
+ if ( howMany > 1 && a.range.start.hasSameParentAs( b.deletionPosition ) ) {
|
|
|
if ( a.range.containsPosition( b.deletionPosition ) || a.range.start.isEqual( b.deletionPosition ) ) {
|
|
|
ranges.push( Range.createFromPositionAndShift( b.graveyardPosition, 1 ) );
|
|
|
}
|
|
|
@@ -541,13 +690,23 @@ setTransformation( InsertOperation, InsertOperation, ( a, b, context ) => {
|
|
|
return [ a ];
|
|
|
} );
|
|
|
|
|
|
-setTransformation( InsertOperation, MoveOperation, ( a, b ) => {
|
|
|
+setTransformation( InsertOperation, MoveOperation, ( a, b, context ) => {
|
|
|
+ if ( a.position.isEqual( b.targetPosition ) && context.getRelation( a, b ) == 'insertBefore' ) {
|
|
|
+ return [ a ];
|
|
|
+ }
|
|
|
+
|
|
|
a.position = a.position._getTransformedByMoveOperation( b );
|
|
|
|
|
|
return [ a ];
|
|
|
} );
|
|
|
|
|
|
-setTransformation( InsertOperation, SplitOperation, ( a, b ) => {
|
|
|
+setTransformation( InsertOperation, SplitOperation, ( a, b, context ) => {
|
|
|
+ if ( a.position.isEqual( b.position ) && context.getRelation( a, b ) == 'insertAtSource' ) {
|
|
|
+ a.position = b.moveTargetPosition;
|
|
|
+
|
|
|
+ return [ a ];
|
|
|
+ }
|
|
|
+
|
|
|
a.position = a.position._getTransformedBySplitOperation( b );
|
|
|
|
|
|
return [ a ];
|
|
|
@@ -559,7 +718,13 @@ setTransformation( InsertOperation, MergeOperation, ( a, b ) => {
|
|
|
return [ a ];
|
|
|
} );
|
|
|
|
|
|
-setTransformation( InsertOperation, WrapOperation, ( a, b ) => {
|
|
|
+setTransformation( InsertOperation, WrapOperation, ( a, b, context ) => {
|
|
|
+ if ( a.position.isEqual( b.position ) && context.getRelation( a, b ) == 'insertInside' ) {
|
|
|
+ a.position = b.targetPosition;
|
|
|
+
|
|
|
+ return [ a ];
|
|
|
+ }
|
|
|
+
|
|
|
a.position = a.position._getTransformedByWrapOperation( b );
|
|
|
|
|
|
return [ a ];
|
|
|
@@ -716,7 +881,7 @@ setTransformation( MergeOperation, MoveOperation, ( a, b, context ) => {
|
|
|
a.sourcePosition = a.sourcePosition._getTransformedByMoveOperation( b );
|
|
|
a.targetPosition = a.targetPosition._getTransformedByMoveOperation( b );
|
|
|
|
|
|
- if ( !a.graveyardPosition.isEqual( b.targetPosition ) || !context.aIsStrong ) {
|
|
|
+ if ( !a.graveyardPosition.isEqual( b.targetPosition ) ) {
|
|
|
a.graveyardPosition = a.graveyardPosition._getTransformedByMoveOperation( b );
|
|
|
}
|
|
|
|
|
|
@@ -747,9 +912,9 @@ setTransformation( MergeOperation, SplitOperation, ( a, b ) => {
|
|
|
// There is one exception though - when the split operation is a result of undo. In those cases, it is needed
|
|
|
// to keep `targetPosition` intact, so the nodes are returned to the correct element.
|
|
|
//
|
|
|
- if ( a.targetPosition.isEqual( b.position ) && b.graveyardPosition ) {
|
|
|
- return [ a ];
|
|
|
- }
|
|
|
+ // if ( a.targetPosition.isEqual( b.position ) && b.graveyardPosition ) {
|
|
|
+ // return [ a ];
|
|
|
+ // }
|
|
|
|
|
|
a.targetPosition = a.targetPosition._getTransformedBySplitOperation( b );
|
|
|
|
|
|
@@ -814,14 +979,14 @@ setTransformation( MergeOperation, UnwrapOperation, ( a, b, context ) => {
|
|
|
|
|
|
// -----------------------
|
|
|
|
|
|
-setTransformation( MoveOperation, InsertOperation, ( a, b ) => {
|
|
|
+setTransformation( MoveOperation, InsertOperation, ( a, b, context ) => {
|
|
|
const moveRange = Range.createFromPositionAndShift( a.sourcePosition, a.howMany );
|
|
|
const transformed = moveRange._getTransformedByInsertOperation( b, false )[ 0 ];
|
|
|
|
|
|
a.sourcePosition = transformed.start;
|
|
|
a.howMany = transformed.end.offset - transformed.start.offset;
|
|
|
|
|
|
- if ( !a.targetPosition.isEqual( b.position ) ) {
|
|
|
+ if ( !a.targetPosition.isEqual( b.position ) || context.getRelation( b, a ) == 'insertBefore' ) {
|
|
|
a.targetPosition = a.targetPosition._getTransformedByInsertOperation( b );
|
|
|
}
|
|
|
|
|
|
@@ -838,14 +1003,23 @@ setTransformation( MoveOperation, MoveOperation, ( a, b, context ) => {
|
|
|
|
|
|
// Assign `context.aIsStrong` to a different variable, because the value may change during execution of
|
|
|
// this algorithm and we do not want to override original `context.aIsStrong` that will be used in later transformations.
|
|
|
- let aIsStrong = context.aIsStrong;
|
|
|
+ let aIsStrong = context.aIsStrong || context.getRelation( a, b ) == 'insertBefore';
|
|
|
|
|
|
// `a.targetPosition` could be affected by the `b` operation. We will transform it.
|
|
|
- const newTargetPosition = a.targetPosition._getTransformedByMove(
|
|
|
- b.sourcePosition,
|
|
|
- b.targetPosition,
|
|
|
- b.howMany
|
|
|
- );
|
|
|
+ let newTargetPosition;
|
|
|
+
|
|
|
+ if ( a.targetPosition.isEqual( b.targetPosition ) && aIsStrong ) {
|
|
|
+ newTargetPosition = a.targetPosition._getTransformedByDeletion(
|
|
|
+ b.sourcePosition,
|
|
|
+ b.howMany
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ newTargetPosition = a.targetPosition._getTransformedByMove(
|
|
|
+ b.sourcePosition,
|
|
|
+ b.targetPosition,
|
|
|
+ b.howMany
|
|
|
+ );
|
|
|
+ }
|
|
|
|
|
|
//
|
|
|
// Special case #1 + mirror.
|
|
|
@@ -993,7 +1167,7 @@ setTransformation( MoveOperation, MoveOperation, ( a, b, context ) => {
|
|
|
return makeMoveOperationsFromRanges( ranges, newTargetPosition );
|
|
|
} );
|
|
|
|
|
|
-setTransformation( MoveOperation, SplitOperation, ( a, b ) => {
|
|
|
+setTransformation( MoveOperation, SplitOperation, ( a, b, context ) => {
|
|
|
const newTargetPosition = a.targetPosition._getTransformedBySplitOperation( b );
|
|
|
|
|
|
// Case 1: Last element in the moved range got split.
|
|
|
@@ -1028,8 +1202,8 @@ setTransformation( MoveOperation, SplitOperation, ( a, b ) => {
|
|
|
rightRange = rightRange._getTransformedBySplitOperation( b );
|
|
|
|
|
|
const ranges = [
|
|
|
- rightRange,
|
|
|
- Range.createFromPositionAndShift( moveRange.start, b.position.offset - moveRange.start.offset )
|
|
|
+ new Range( moveRange.start, b.position ),
|
|
|
+ rightRange
|
|
|
];
|
|
|
|
|
|
return makeMoveOperationsFromRanges( ranges, newTargetPosition );
|
|
|
@@ -1043,6 +1217,10 @@ setTransformation( MoveOperation, SplitOperation, ( a, b ) => {
|
|
|
a.howMany = transformed.end.offset - transformed.start.offset;
|
|
|
a.targetPosition = newTargetPosition;
|
|
|
|
|
|
+ if ( a.targetPosition.isEqual( b.position ) && context.getRelation( a, b ) == 'insertAtSource' ) {
|
|
|
+ a.targetPosition = b.targetPosition;
|
|
|
+ }
|
|
|
+
|
|
|
return [ a ];
|
|
|
} );
|
|
|
|
|
|
@@ -1081,7 +1259,7 @@ setTransformation( MoveOperation, MergeOperation, ( a, b, context ) => {
|
|
|
return [ a ];
|
|
|
} );
|
|
|
|
|
|
-setTransformation( MoveOperation, WrapOperation, ( a, b ) => {
|
|
|
+setTransformation( MoveOperation, WrapOperation, ( a, b, context ) => {
|
|
|
const moveRange = Range.createFromPositionAndShift( a.sourcePosition, a.howMany );
|
|
|
const newTargetPosition = a.targetPosition._getTransformedByWrapOperation( b );
|
|
|
|
|
|
@@ -1133,6 +1311,10 @@ setTransformation( MoveOperation, WrapOperation, ( a, b ) => {
|
|
|
a.howMany = transformed.end.offset - transformed.start.offset;
|
|
|
a.targetPosition = newTargetPosition;
|
|
|
|
|
|
+ if ( a.targetPosition.isEqual( b.position ) && context.getRelation( a, b ) == 'insertInside' ) {
|
|
|
+ a.targetPosition = b.targetPosition;
|
|
|
+ }
|
|
|
+
|
|
|
return [ a ];
|
|
|
} );
|
|
|
|
|
|
@@ -1235,7 +1417,11 @@ setTransformation( RootAttributeOperation, RootAttributeOperation, ( a, b, conte
|
|
|
|
|
|
// -----------------------
|
|
|
|
|
|
-setTransformation( SplitOperation, InsertOperation, ( a, b ) => {
|
|
|
+setTransformation( SplitOperation, InsertOperation, ( a, b, context ) => {
|
|
|
+ if ( a.position.isEqual( b.position ) && context.getRelation( b, a ) == 'insertAtSource' ) {
|
|
|
+ return [ a ];
|
|
|
+ }
|
|
|
+
|
|
|
a.position = a.position._getTransformedByInsertOperation( b );
|
|
|
|
|
|
return [ a ];
|
|
|
@@ -1251,7 +1437,7 @@ setTransformation( SplitOperation, MergeOperation, ( a, b ) => {
|
|
|
return [ a ];
|
|
|
} );
|
|
|
|
|
|
-setTransformation( SplitOperation, MoveOperation, ( a, b ) => {
|
|
|
+setTransformation( SplitOperation, MoveOperation, ( a, b, context ) => {
|
|
|
if ( a.graveyardPosition ) {
|
|
|
a.graveyardPosition = a.graveyardPosition._getTransformedByMoveOperation( b );
|
|
|
}
|
|
|
@@ -1279,6 +1465,12 @@ setTransformation( SplitOperation, MoveOperation, ( a, b ) => {
|
|
|
return [ a ];
|
|
|
}
|
|
|
|
|
|
+ if ( a.position.isEqual( b.targetPosition ) && context.getRelation( a, b ) == 'splitBefore' ) {
|
|
|
+ a.position = a.position._getTransformedByDeletion( b.sourcePosition, b.howMany );
|
|
|
+
|
|
|
+ return [ a ];
|
|
|
+ }
|
|
|
+
|
|
|
// The default case.
|
|
|
//
|
|
|
a.position = a.position._getTransformedByMoveOperation( b );
|
|
|
@@ -1286,7 +1478,7 @@ setTransformation( SplitOperation, MoveOperation, ( a, b ) => {
|
|
|
return [ a ];
|
|
|
} );
|
|
|
|
|
|
-setTransformation( SplitOperation, SplitOperation, ( a, b ) => {
|
|
|
+setTransformation( SplitOperation, SplitOperation, ( a, b, context ) => {
|
|
|
if ( a.position.isEqual( b.position ) ) {
|
|
|
if ( !a.graveyardPosition && !b.graveyardPosition ) {
|
|
|
return getNoOp();
|
|
|
@@ -1295,6 +1487,8 @@ setTransformation( SplitOperation, SplitOperation, ( a, b ) => {
|
|
|
if ( a.graveyardPosition && b.graveyardPosition && a.graveyardPosition.isEqual( b.graveyardPosition ) ) {
|
|
|
return getNoOp();
|
|
|
}
|
|
|
+ } else if ( a.position.isEqual( b.insertionPosition ) && context.getRelation( a, b ) == 'splitBefore' ) {
|
|
|
+ return [ a ];
|
|
|
} else {
|
|
|
a.position = a.position._getTransformedBySplitOperation( b );
|
|
|
}
|
|
|
@@ -1306,7 +1500,7 @@ setTransformation( SplitOperation, SplitOperation, ( a, b ) => {
|
|
|
return [ a ];
|
|
|
} );
|
|
|
|
|
|
-setTransformation( SplitOperation, WrapOperation, ( a, b ) => {
|
|
|
+setTransformation( SplitOperation, WrapOperation, ( a, b, context ) => {
|
|
|
// Case 1: If split position has been wrapped, reverse the wrapping so that split can be applied as intended.
|
|
|
// This is an edge case scenario where it is difficult to find a correct solution.
|
|
|
// Since it will be a rare (or only theoretical) scenario, the algorithm will perform the easy solution.
|
|
|
@@ -1324,7 +1518,11 @@ setTransformation( SplitOperation, WrapOperation, ( a, b ) => {
|
|
|
return [ reversed, a ];
|
|
|
}
|
|
|
|
|
|
- a.position = a.position._getTransformedByWrapOperation( b );
|
|
|
+ if ( a.position.isEqual( b.position ) && context.getRelation( a, b ) == 'splitInside' ) {
|
|
|
+ a.position = b.targetPosition;
|
|
|
+ } else {
|
|
|
+ a.position = a.position._getTransformedByWrapOperation( b );
|
|
|
+ }
|
|
|
|
|
|
if ( a.graveyardPosition && b.graveyardPosition ) {
|
|
|
a.graveyardPosition = a.graveyardPosition._getTransformedByDeletion( b.graveyardPosition, 1 );
|
|
|
@@ -1333,8 +1531,17 @@ setTransformation( SplitOperation, WrapOperation, ( a, b ) => {
|
|
|
return [ a ];
|
|
|
} );
|
|
|
|
|
|
-setTransformation( SplitOperation, UnwrapOperation, ( a, b ) => {
|
|
|
- a.position = a.position._getTransformedByUnwrapOperation( b );
|
|
|
+setTransformation( SplitOperation, UnwrapOperation, ( a, b, context ) => {
|
|
|
+ const splitInside = a.position.hasSameParentAs( b.position );
|
|
|
+
|
|
|
+ if ( splitInside && !context.wasUndone( b ) ) {
|
|
|
+ const path = b.graveyardPosition.path.slice();
|
|
|
+ path.push( 0 );
|
|
|
+
|
|
|
+ a.position = new Position( b.graveyardPosition.root, path );
|
|
|
+ } else {
|
|
|
+ a.position = a.position._getTransformedByUnwrapOperation( b );
|
|
|
+ }
|
|
|
|
|
|
if ( a.graveyardPosition ) {
|
|
|
a.graveyardPosition = a.graveyardPosition._getTransformedByInsertion( b.graveyardPosition, 1 );
|
|
|
@@ -1345,7 +1552,13 @@ setTransformation( SplitOperation, UnwrapOperation, ( a, b ) => {
|
|
|
|
|
|
// -----------------------
|
|
|
|
|
|
-setTransformation( WrapOperation, InsertOperation, ( a, b ) => {
|
|
|
+setTransformation( WrapOperation, InsertOperation, ( a, b, context ) => {
|
|
|
+ if ( a.position.isEqual( b.position ) && context.getRelation( b, a ) == 'insertInside' ) {
|
|
|
+ a.howMany += b.howMany;
|
|
|
+
|
|
|
+ return [ a ];
|
|
|
+ }
|
|
|
+
|
|
|
const transformed = a.wrappedRange._getTransformedByInsertOperation( b, false )[ 0 ];
|
|
|
|
|
|
a.position = transformed.start;
|
|
|
@@ -1367,11 +1580,18 @@ setTransformation( WrapOperation, MergeOperation, ( a, b ) => {
|
|
|
return [ a ];
|
|
|
} );
|
|
|
|
|
|
-setTransformation( WrapOperation, MoveOperation, ( a, b ) => {
|
|
|
+setTransformation( WrapOperation, MoveOperation, ( a, b, context ) => {
|
|
|
if ( a.graveyardPosition ) {
|
|
|
a.graveyardPosition = a.graveyardPosition._getTransformedByMoveOperation( b );
|
|
|
}
|
|
|
|
|
|
+ if ( a.position.isEqual( b.targetPosition ) && context.getRelation( b, a ) == 'insertInside' ) {
|
|
|
+ a.position._getTransformedByDeletion( b.sourcePosition, b.howMany );
|
|
|
+ a.howMany += b.howMany;
|
|
|
+
|
|
|
+ return [ a ];
|
|
|
+ }
|
|
|
+
|
|
|
const ranges = breakRangeByMoveOperation( a.wrappedRange, b, false );
|
|
|
|
|
|
if ( ranges.length == 0 ) {
|
|
|
@@ -1388,10 +1608,14 @@ setTransformation( WrapOperation, MoveOperation, ( a, b ) => {
|
|
|
} );
|
|
|
} );
|
|
|
|
|
|
-setTransformation( WrapOperation, SplitOperation, ( a, b ) => {
|
|
|
+setTransformation( WrapOperation, SplitOperation, ( a, b, context ) => {
|
|
|
// Case 1: If range to wrap got split by split operation cancel the wrapping.
|
|
|
+ // Do that only if this is not undo mode. If `b` operation was earlier transformed by unwrap operation
|
|
|
+ // and the split position was inside the unwrapped range, then proceed without special case.
|
|
|
//
|
|
|
- if ( a.position.hasSameParentAs( b.position ) && a.wrappedRange.containsPosition( b.position ) ) {
|
|
|
+ const isInside = a.position.hasSameParentAs( b.position ) && a.wrappedRange.containsPosition( b.position );
|
|
|
+
|
|
|
+ if ( isInside && context.getRelation( b, a ) !== 'splitInside' ) {
|
|
|
// We cannot just return no-op in this case, because in the mirror case scenario the wrap is reversed, which
|
|
|
// might introduce a new node in the graveyard (if the wrap didn't have `graveyardPosition`, then the wrap
|
|
|
// created a new element which was put to the graveyard when the wrap was reversed).
|
|
|
@@ -1401,7 +1625,7 @@ setTransformation( WrapOperation, SplitOperation, ( a, b ) => {
|
|
|
const graveyard = a.position.root.document.graveyard;
|
|
|
const graveyardPosition = new Position( graveyard, [ 0 ] );
|
|
|
|
|
|
- return new InsertOperation( graveyardPosition, a.element, 0 );
|
|
|
+ return [ new InsertOperation( graveyardPosition, a.element, 0 ) ];
|
|
|
} else {
|
|
|
return getNoOp();
|
|
|
}
|
|
|
@@ -1622,7 +1846,7 @@ setTransformation( UnwrapOperation, MergeOperation, ( a, b, context ) => {
|
|
|
return [ a ];
|
|
|
} );
|
|
|
|
|
|
-setTransformation( UnwrapOperation, MoveOperation, ( a, b, context ) => {
|
|
|
+setTransformation( UnwrapOperation, MoveOperation, ( a, b ) => {
|
|
|
// Case 1: Move operation moves nodes from the unwrapped element.
|
|
|
// This does not have any impact on `UnwrapOperation#position`, but `#howMany` has to be changed.
|
|
|
//
|
|
|
@@ -1640,7 +1864,7 @@ setTransformation( UnwrapOperation, MoveOperation, ( a, b, context ) => {
|
|
|
|
|
|
a.position = a.position._getTransformedByMoveOperation( b );
|
|
|
|
|
|
- if ( !a.graveyardPosition.isEqual( b.targetPosition ) || !context.aIsStrong ) {
|
|
|
+ if ( !a.graveyardPosition.isEqual( b.targetPosition ) ) {
|
|
|
a.graveyardPosition = a.graveyardPosition._getTransformedByMoveOperation( b );
|
|
|
}
|
|
|
|
|
|
@@ -1691,6 +1915,10 @@ setTransformation( UnwrapOperation, WrapOperation, ( a, b ) => {
|
|
|
a.howMany = a.howMany - b.howMany + 1;
|
|
|
}
|
|
|
|
|
|
+ if ( b.graveyardPosition && compareArrays( a.position.getParentPath(), b.graveyardPosition.path ) == 'same' ) {
|
|
|
+ a.howMany = b.howMany;
|
|
|
+ }
|
|
|
+
|
|
|
// The default case.
|
|
|
//
|
|
|
a.position = a.position._getTransformedByWrapOperation( b );
|
|
|
@@ -1709,6 +1937,8 @@ setTransformation( UnwrapOperation, UnwrapOperation, ( a, b, context ) => {
|
|
|
a.position = new Position( b.graveyardPosition.root, path );
|
|
|
a.howMany = 0;
|
|
|
a.graveyardPosition = Position.createFromPosition( b.graveyardPosition );
|
|
|
+
|
|
|
+ return [ a ];
|
|
|
}
|
|
|
|
|
|
a.position = a.position._getTransformedByUnwrapOperation( b );
|
|
|
@@ -1759,8 +1989,7 @@ function makeMoveOperationsFromRanges( ranges, targetPosition ) {
|
|
|
ranges[ j ] = ranges[ j ]._getTransformedByMove( op.sourcePosition, op.targetPosition, op.howMany )[ 0 ];
|
|
|
}
|
|
|
|
|
|
- // targetPosition.stickiness = 'toPrevious';
|
|
|
- targetPosition = targetPosition._getTransformedByMove( op.sourcePosition, op.targetPosition, op.howMany, true );
|
|
|
+ targetPosition = targetPosition._getTransformedByMove( op.sourcePosition, op.targetPosition, op.howMany );
|
|
|
}
|
|
|
|
|
|
return operations;
|