@@ -377,13 +377,17 @@ const ot = {
377377 const rangeA = Range . createFromPositionAndShift ( a . sourcePosition , a . howMany ) ;
378378 const rangeB = Range . createFromPositionAndShift ( b . sourcePosition , b . howMany ) ;
379379
380+ // Assign `context.isStrong` to a different variable, because the value may change during execution of
381+ // this algorithm and we do not want to override original `context.isStrong` that will be used in later transformations.
382+ let isStrong = context . isStrong ;
383+
380384 // Whether range moved by operation `b` is includable in operation `a` move range.
381385 // For this, `a` operation has to be sticky (so `b` sticks to the range) and context has to allow stickiness.
382386 const includeB = a . isSticky && ! context . forceNotSticky ;
383387
384388 // Evaluate new target position for transformed operation.
385- // Check whether there is a forced order of nodes or use `context. isStrong` flag for conflict resolving.
386- const insertBefore = context . insertBefore === undefined ? ! context . isStrong : context . insertBefore ;
389+ // Check whether there is a forced order of nodes or use `isStrong` flag for conflict resolving.
390+ const insertBefore = context . insertBefore === undefined ? ! isStrong : context . insertBefore ;
387391
388392 // `a.targetPosition` could be affected by the `b` operation. We will transform it.
389393 const newTargetPosition = a . targetPosition . _getTransformedByMove (
@@ -425,7 +429,7 @@ const ot = {
425429 rangeA . start = rangeA . start . _getTransformedByMove ( b . sourcePosition , b . targetPosition , b . howMany , ! includeB ) ;
426430 rangeA . end = rangeA . end . _getTransformedByMove ( b . sourcePosition , b . targetPosition , b . howMany , includeB ) ;
427431
428- return makeMoveOperationsFromRanges ( a , [ rangeA ] , newTargetPosition ) ;
432+ return makeMoveOperationsFromRanges ( [ rangeA ] , newTargetPosition , a ) ;
429433 }
430434
431435 //
@@ -441,7 +445,7 @@ const ot = {
441445 rangeA . start = rangeA . start . _getCombined ( b . sourcePosition , b . getMovedRangeStart ( ) ) ;
442446 rangeA . end = rangeA . end . _getCombined ( b . sourcePosition , b . getMovedRangeStart ( ) ) ;
443447
444- return makeMoveOperationsFromRanges ( a , [ rangeA ] , newTargetPosition ) ;
448+ return makeMoveOperationsFromRanges ( [ rangeA ] , newTargetPosition , a ) ;
445449 }
446450 //
447451 // End of special case #2.
@@ -463,7 +467,7 @@ const ot = {
463467 rangeA . start = rangeA . start . _getTransformedByMove ( b . sourcePosition , b . targetPosition , b . howMany , ! includeB ) ;
464468 rangeA . end = rangeA . end . _getTransformedByMove ( b . sourcePosition , b . targetPosition , b . howMany , includeB ) ;
465469
466- return makeMoveOperationsFromRanges ( a , [ rangeA ] , newTargetPosition ) ;
470+ return makeMoveOperationsFromRanges ( [ rangeA ] , newTargetPosition , a ) ;
467471 }
468472 //
469473 // End of special case #3.
@@ -473,39 +477,43 @@ const ot = {
473477 // Default case - ranges are on the same level or are not connected with each other.
474478 //
475479 // Modifier for default case.
476- // Modifies `context. isStrong` in certain conditions.
480+ // Modifies `isStrong` flag in certain conditions.
477481 //
478482 // If only one of operations is a remove operation, we force remove operation to be the "stronger" one
479483 // to provide more expected results. This is done only if `context.forceWeakRemove` is set to `false`.
480484 // `context.forceWeakRemove` is set to `true` in certain conditions when transformation takes place during undo.
481485 if ( ! context . forceWeakRemove ) {
482486 if ( a instanceof RemoveOperation && ! ( b instanceof RemoveOperation ) ) {
483- context . isStrong = true ;
487+ isStrong = true ;
484488 } else if ( ! ( a instanceof RemoveOperation ) && b instanceof RemoveOperation ) {
485- context . isStrong = false ;
489+ isStrong = false ;
486490 }
487491 }
488492
489493 // Handle operation's source ranges - check how `rangeA` is affected by `b` operation.
490494 // This will aggregate transformed ranges.
491- let ranges = [ ] ;
495+ const ranges = [ ] ;
492496
493497 // Get the "difference part" of `a` operation source range.
494498 // This is an array with one or two ranges. Two ranges if `rangeB` is inside `rangeA`.
495499 const difference = rangeA . getDifference ( rangeB ) ;
496500
497501 for ( const range of difference ) {
498502 // Transform those ranges by `b` operation. For example if `b` moved range from before those ranges, fix those ranges.
499- range . start = range . start . _getTransformedByMove ( b . sourcePosition , b . targetPosition , b . howMany , ! includeB ) ;
500- range . end = range . end . _getTransformedByMove ( b . sourcePosition , b . targetPosition , b . howMany , includeB ) ;
503+ range . start = range . start . _getTransformedByDeletion ( b . sourcePosition , b . howMany ) ;
504+ range . end = range . end . _getTransformedByDeletion ( b . sourcePosition , b . howMany ) ;
505+
506+ // If `b` operation targets into `rangeA` on the same level, spread `rangeA` into two ranges.
507+ const shouldSpread = compareArrays ( range . start . getParentPath ( ) , b . getMovedRangeStart ( ) . getParentPath ( ) ) == 'same' ;
508+ const newRanges = range . _getTransformedByInsertion ( b . getMovedRangeStart ( ) , b . howMany , shouldSpread , includeB ) ;
501509
502- ranges . push ( range ) ;
510+ ranges . push ( ... newRanges ) ;
503511 }
504512
505513 // Then, we have to manage the "common part" of both move ranges.
506514 const common = rangeA . getIntersection ( rangeB ) ;
507515
508- if ( common !== null && context . isStrong && ! bTargetsToA ) {
516+ if ( common !== null && isStrong && ! bTargetsToA ) {
509517 // Calculate the new position of that part of original range.
510518 common . start = common . start . _getCombined ( b . sourcePosition , b . getMovedRangeStart ( ) ) ;
511519 common . end = common . end . _getCombined ( b . sourcePosition , b . getMovedRangeStart ( ) ) ;
@@ -521,7 +529,7 @@ const ot = {
521529 }
522530 // If there is one difference range, we need to check whether common part was before it or after it.
523531 else if ( ranges . length == 1 ) {
524- if ( rangeB . start . isBefore ( rangeA . start ) ) {
532+ if ( rangeB . start . isBefore ( rangeA . start ) || rangeB . start . isEqual ( rangeA . start ) ) {
525533 ranges . unshift ( common ) ;
526534 } else {
527535 ranges . push ( common ) ;
@@ -536,34 +544,11 @@ const ot = {
536544
537545 if ( ranges . length === 0 ) {
538546 // If there are no "source ranges", nothing should be changed.
539- // Note that this can happen only if `context. isStrong == false` and `rangeA.isEqual( rangeB )`.
547+ // Note that this can happen only if `isStrong == false` and `rangeA.isEqual( rangeB )`.
540548 return [ new NoOperation ( a . baseVersion ) ] ;
541549 }
542550
543- // At this moment we have some ranges and a target position, to which those ranges should be moved.
544- // Order in `ranges` array is the go-to order of after transformation. We can reverse the `ranges`
545- // array so the last range will be moved first. This will ensure that each transformed `MoveOperation`
546- // can have same `targetPosition` and the order of ranges will be kept.
547- ranges = ranges . reverse ( ) ;
548-
549- // We are almost done. We have `ranges` and `targetPosition` to make operations from.
550- // Unfortunately, those operations may affect each other. Precisely, first operation after move
551- // may affect source range of second and third operation. Same with second operation affecting third.
552- // We need to fix those source ranges once again, before converting `ranges` to operations.
553- // Keep in mind that `targetPosition` is already set in stone thanks to the trick above.
554- for ( let i = 1 ; i < ranges . length ; i ++ ) {
555- for ( let j = 0 ; j < i ; j ++ ) {
556- const howMany = ranges [ j ] . end . offset - ranges [ j ] . start . offset ;
557-
558- // Thankfully, all ranges in `ranges` array are:
559- // * non-intersecting (these are part of original `a` operation source range), and
560- // * `newTargetPosition` does not target into them (opposite would mean that `a` operation targets "inside itself").
561- // This means that the transformation will be "clean" and always return one result.
562- ranges [ i ] = ranges [ i ] . _getTransformedByMove ( ranges [ j ] . start , newTargetPosition , howMany ) [ 0 ] ;
563- }
564- }
565-
566- return makeMoveOperationsFromRanges ( a , ranges , newTargetPosition ) ;
551+ return makeMoveOperationsFromRanges ( ranges , newTargetPosition , a ) ;
567552 }
568553 }
569554} ;
@@ -646,29 +631,66 @@ function joinRanges( ranges ) {
646631 }
647632}
648633
649- // Map transformed range(s) to operations and return them.
650- function makeMoveOperationsFromRanges ( a , ranges , targetPosition ) {
651- return ranges . map ( range => {
652- // We want to keep correct operation class.
653- let OperationClass ;
654-
655- if ( targetPosition . root . rootName == '$graveyard' ) {
656- OperationClass = RemoveOperation ;
657- } else if ( range . start . root . rootName == '$graveyard' ) {
658- OperationClass = ReinsertOperation ;
659- } else {
660- OperationClass = MoveOperation ;
634+ // Helper function for `MoveOperation` x `MoveOperation` transformation.
635+ // Convert given ranges and target position to move operations and return them.
636+ // Ranges and target position will be transformed on-the-fly when generating operations.
637+ // Given `ranges` should be in the order of how they were in the original transformed operation.
638+ // Given `targetPosition` is the target position of the first range from `ranges`.
639+ function makeMoveOperationsFromRanges ( ranges , targetPosition , a ) {
640+ // At this moment we have some ranges and a target position, to which those ranges should be moved.
641+ // Order in `ranges` array is the go-to order of after transformation.
642+ //
643+ // We are almost done. We have `ranges` and `targetPosition` to make operations from.
644+ // Unfortunately, those operations may affect each other. Precisely, first operation after move
645+ // may affect source range and target position of second and third operation. Same with second
646+ // operation affecting third.
647+ //
648+ // We need to fix those source ranges and target positions once again, before converting `ranges` to operations.
649+ const operations = [ ] ;
650+
651+ // Keep in mind that nothing will be transformed if there is just one range in `ranges`.
652+ for ( let i = 0 ; i < ranges . length ; i ++ ) {
653+ // Create new operation out of a range and target position.
654+ const op = makeMoveOperation ( ranges [ i ] , targetPosition , a . isSticky ) ;
655+
656+ operations . push ( op ) ;
657+
658+ // Transform other ranges by the generated operation.
659+ for ( let j = i + 1 ; j < ranges . length ; j ++ ) {
660+ // All ranges in `ranges` array should be:
661+ // * non-intersecting (these are part of original operation source range), and
662+ // * `targetPosition` does not target into them (opposite would mean that transformed operation targets "inside itself").
663+ //
664+ // This means that the transformation will be "clean" and always return one result.
665+ ranges [ j ] = ranges [ j ] . _getTransformedByMove ( op . sourcePosition , op . targetPosition , op . howMany ) [ 0 ] ;
661666 }
662667
663- const result = new OperationClass (
664- range . start ,
665- range . end . offset - range . start . offset ,
666- targetPosition ,
667- 0 // Is corrected anyway later.
668- ) ;
668+ targetPosition = targetPosition . _getTransformedByMove ( op . sourcePosition , op . targetPosition , op . howMany , true , false ) ;
669+ }
670+
671+ return operations ;
672+ }
673+
674+ function makeMoveOperation ( range , targetPosition , isSticky ) {
675+ // We want to keep correct operation class.
676+ let OperationClass ;
677+
678+ if ( targetPosition . root . rootName == '$graveyard' ) {
679+ OperationClass = RemoveOperation ;
680+ } else if ( range . start . root . rootName == '$graveyard' ) {
681+ OperationClass = ReinsertOperation ;
682+ } else {
683+ OperationClass = MoveOperation ;
684+ }
685+
686+ const result = new OperationClass (
687+ range . start ,
688+ range . end . offset - range . start . offset ,
689+ targetPosition ,
690+ 0 // Is corrected anyway later.
691+ ) ;
669692
670- result . isSticky = a . isSticky ;
693+ result . isSticky = isSticky ;
671694
672- return result ;
673- } ) ;
695+ return result ;
674696}
0 commit comments