@@ -1669,12 +1669,21 @@ setTransformation( MoveOperation, SplitOperation, ( a, b, context ) => {
16691669 // The default case.
16701670 //
16711671 const transformed = moveRange . _getTransformedBySplitOperation ( b ) ;
1672+ const ranges = [ transformed ] ;
16721673
1673- a . sourcePosition = transformed . start ;
1674- a . howMany = transformed . end . offset - transformed . start . offset ;
1675- a . targetPosition = newTargetPosition ;
1674+ // Case 5:
1675+ //
1676+ // Moved range contains graveyard element used by split operation. Add extra move operation to the result.
1677+ //
1678+ if ( b . graveyardPosition ) {
1679+ const movesGraveyardElement = moveRange . start . isEqual ( b . graveyardPosition ) || moveRange . containsPosition ( b . graveyardPosition ) ;
16761680
1677- return [ a ] ;
1681+ if ( a . howMany > 1 && movesGraveyardElement ) {
1682+ ranges . push ( Range . createFromPositionAndShift ( b . insertionPosition , 1 ) ) ;
1683+ }
1684+ }
1685+
1686+ return _makeMoveOperationsFromRanges ( ranges , newTargetPosition ) ;
16781687} ) ;
16791688
16801689setTransformation ( MoveOperation , MergeOperation , ( a , b , context ) => {
@@ -1692,16 +1701,30 @@ setTransformation( MoveOperation, MergeOperation, ( a, b, context ) => {
16921701 // removed nodes might be unexpected. This means that in this scenario we will reverse merging and remove the element.
16931702 //
16941703 if ( ! context . aWasUndone ) {
1695- const gyMoveTarget = Position . createFromPosition ( b . graveyardPosition ) ;
1696- const gyMove = new MoveOperation ( b . graveyardPosition , 1 , gyMoveTarget , 0 ) ;
1704+ const results = [ ] ;
16971705
1698- const targetPositionPath = b . graveyardPosition . path . slice ( ) ;
1706+ let gyMoveSource = Position . createFromPosition ( b . graveyardPosition ) ;
1707+ let splitNodesMoveSource = Position . createFromPosition ( b . targetPosition ) ;
1708+
1709+ if ( a . howMany > 1 ) {
1710+ results . push ( new MoveOperation ( a . sourcePosition , a . howMany - 1 , a . targetPosition , 0 ) ) ;
1711+ gyMoveSource = gyMoveSource . _getTransformedByInsertion ( a . targetPosition , a . howMany - 1 ) ;
1712+ splitNodesMoveSource = splitNodesMoveSource . _getTransformedByMove ( a . sourcePosition , a . targetPosition , a . howMany - 1 ) ;
1713+ }
1714+
1715+ const gyMoveTarget = b . deletionPosition . _getCombined ( a . sourcePosition , a . targetPosition ) ;
1716+ const gyMove = new MoveOperation ( gyMoveSource , 1 , gyMoveTarget , 0 ) ;
1717+
1718+ const targetPositionPath = gyMove . getMovedRangeStart ( ) . path . slice ( ) ;
16991719 targetPositionPath . push ( 0 ) ;
17001720
1701- return [
1702- gyMove ,
1703- new MoveOperation ( b . targetPosition , b . howMany , new Position ( a . targetPosition . root , targetPositionPath ) , 0 )
1704- ] ;
1721+ const splitNodesMoveTarget = new Position ( gyMove . targetPosition . root , targetPositionPath ) ;
1722+ const splitNodesMove = new MoveOperation ( splitNodesMoveSource , b . howMany , splitNodesMoveTarget , 0 ) ;
1723+
1724+ results . push ( gyMove ) ;
1725+ results . push ( splitNodesMove ) ;
1726+
1727+ return results ;
17051728 }
17061729 } else {
17071730 // Case 2:
@@ -1934,14 +1957,21 @@ setTransformation( SplitOperation, MoveOperation, ( a, b, context ) => {
19341957 if ( a . graveyardPosition ) {
19351958 // Case 1:
19361959 //
1937- // Split operation graveyard node was moved. In this case move operation is stronger and the split insertion position
1938- // should be corrected.
1960+ // Split operation graveyard node was moved. In this case move operation is stronger. Since graveyard element
1961+ // is already moved to the correct position, we need to only move the nodes after the split position.
1962+ // This will be done by `MoveOperation` instead of `SplitOperation`.
19391963 //
1940- if ( rangeToMove . containsPosition ( a . graveyardPosition ) || rangeToMove . start . isEqual ( a . graveyardPosition ) ) {
1941- a . insertionPosition = Position . createFromPosition ( b . targetPosition ) ;
1942- a . splitPosition = a . splitPosition . _getTransformedByMoveOperation ( b ) ;
1964+ if ( rangeToMove . start . isEqual ( a . graveyardPosition ) || rangeToMove . containsPosition ( a . graveyardPosition ) ) {
1965+ const sourcePosition = a . splitPosition . _getTransformedByMoveOperation ( b ) ;
19431966
1944- return [ a ] ;
1967+ const newParentPosition = a . graveyardPosition . _getTransformedByMoveOperation ( b ) ;
1968+ const newTargetPath = newParentPosition . path . slice ( ) ;
1969+ newTargetPath . push ( 0 ) ;
1970+
1971+ const newTargetPosition = new Position ( newParentPosition . root , newTargetPath ) ;
1972+ const moveOp = new MoveOperation ( sourcePosition , a . howMany , newTargetPosition , 0 ) ;
1973+
1974+ return [ moveOp ] ;
19451975 }
19461976
19471977 a . graveyardPosition = a . graveyardPosition . _getTransformedByMoveOperation ( b ) ;
@@ -2172,7 +2202,14 @@ function _makeMoveOperationsFromRanges( ranges, targetPosition ) {
21722202 for ( let i = 0 ; i < ranges . length ; i ++ ) {
21732203 // Create new operation out of a range and target position.
21742204 const range = ranges [ i ] ;
2175- const op = new MoveOperation ( range . start , range . end . offset - range . start . offset , targetPosition , 0 ) ;
2205+ const op = new MoveOperation (
2206+ range . start ,
2207+ range . end . offset - range . start . offset ,
2208+ // If the target is the end of the move range this operation doesn't really move anything.
2209+ // In this case, it is better for OT to use range start instead of range end.
2210+ targetPosition . isEqual ( range . end ) ? range . start : targetPosition ,
2211+ 0
2212+ ) ;
21762213
21772214 operations . push ( op ) ;
21782215
0 commit comments