@@ -17,6 +17,7 @@ import Position from '../position';
1717import NoOperation from '../operation/nooperation' ;
1818import AttributeOperation from '../operation/attributeoperation' ;
1919import InsertOperation from '../operation/insertoperation' ;
20+ import ReinsertOperation from '../operation/reinsertoperation' ;
2021
2122import Delta from './delta' ;
2223import AttributeDelta from './attributedelta' ;
@@ -96,10 +97,12 @@ addTransformationCase( AttributeDelta, SplitDelta, ( a, b, context ) => {
9697
9798// Add special case for InsertDelta x MergeDelta transformation.
9899addTransformationCase ( InsertDelta , MergeDelta , ( a , b , context ) => {
100+ const undoMode = context . aWasUndone || context . bWasUndone ;
101+
99102 // If insert is applied at the same position where merge happened, we reverse the merge (we treat it like it
100103 // didn't happen) and then apply the original insert operation. This is "mirrored" in MergeDelta x InsertDelta
101104 // transformation below, where we simply do not apply MergeDelta.
102- if ( a . position . isEqual ( b . position ) ) {
105+ if ( ! undoMode && a . position . isEqual ( b . position ) ) {
103106 return [
104107 b . getReversed ( ) ,
105108 a . clone ( )
@@ -155,9 +158,11 @@ addTransformationCase( MoveDelta, MergeDelta, ( a, b, context ) => {
155158
156159// Add special case for MergeDelta x InsertDelta transformation.
157160addTransformationCase ( MergeDelta , InsertDelta , ( a , b , context ) => {
161+ const undoMode = context . aWasUndone || context . bWasUndone ;
162+
158163 // If merge is applied at the same position where we inserted a range of nodes we cancel the merge as it's results
159164 // may be unexpected and very weird. Even if we do some "magic" we don't know what really are users' expectations.
160- if ( a . position . isEqual ( b . position ) ) {
165+ if ( ! undoMode && a . position . isEqual ( b . position ) ) {
161166 return [ noDelta ( ) ] ;
162167 }
163168
@@ -182,8 +187,14 @@ addTransformationCase( MergeDelta, MoveDelta, ( a, b, context ) => {
182187 return defaultTransform ( a , b , context ) ;
183188} ) ;
184189
185- // Add special case for SplitDelta x SplitDelta transformation.
186190addTransformationCase ( SplitDelta , SplitDelta , ( a , b , context ) => {
191+ const undoMode = context . aWasUndone || context . bWasUndone ;
192+
193+ // Do not apply special transformation case if transformation is in undo mode.
194+ if ( undoMode ) {
195+ return defaultTransform ( a , b , context ) ;
196+ }
197+
187198 // Do not apply special transformation case if `SplitDelta` has `NoOperation` as the second operation.
188199 if ( ! a . position || ! b . position ) {
189200 return defaultTransform ( a , b , context ) ;
@@ -194,23 +205,48 @@ addTransformationCase( SplitDelta, SplitDelta, ( a, b, context ) => {
194205
195206 // The special case is for splits inside the same parent.
196207 if ( a . position . root == b . position . root && compareArrays ( pathA , pathB ) == 'same' ) {
197- const newContext = Object . assign ( { } , context ) ;
208+ a = a . clone ( ) ;
209+
210+ if ( a . position . offset <= b . position . offset ) {
211+ // If both first operations are `ReinsertOperation`s, we might need to transform `a._cloneOperation`,
212+ // so it will take correct node from graveyard.
213+ if (
214+ a . _cloneOperation instanceof ReinsertOperation && b . _cloneOperation instanceof ReinsertOperation &&
215+ a . _cloneOperation . sourcePosition . offset > b . _cloneOperation . sourcePosition . offset
216+ ) {
217+ a . _cloneOperation . sourcePosition . offset -- ;
218+ }
198219
199- // If `a` delta splits in further location, make sure that it will move some nodes by forcing it to be strong.
200- // Similarly, if `a` splits closer, make sure that it is transformed accordingly.
201- if ( a . position . offset != b . position . offset ) {
202- // We need to ensure that incoming operation is strong / weak.
203- newContext . isStrong = a . position . offset > b . position . offset ;
204- }
220+ // `a` splits closer or at same offset.
221+ // Change how many nodes are moved. Do not move nodes that were moved by delta `b`.
222+ const aRange = Range . createFromPositionAndShift ( a . position , a . _moveOperation . howMany ) ;
223+ const bRange = Range . createFromPositionAndShift ( b . position , b . _moveOperation . howMany ) ;
205224
206- // Then, default transformation is almost good.
207- // We need to change insert operations offsets, though.
208- // We will use `context.insertBefore` for this (but only if it is not set!)
209- if ( context . insertBefore === undefined ) {
210- newContext . insertBefore = newContext . isStrong ;
211- }
225+ const diff = aRange . getDifference ( bRange ) ;
226+
227+ let newHowMany = 0 ;
228+
229+ for ( const range of diff ) {
230+ newHowMany += range . end . offset - range . start . offset ;
231+ }
212232
213- return defaultTransform ( a , b , newContext ) ;
233+ if ( newHowMany === 0 ) {
234+ a . operations . pop ( ) ; // Remove last operation (`MoveOperation`).
235+ a . addOperation ( new NoOperation ( a . operations [ 0 ] . baseVersion + 1 ) ) ; // Add `NoOperation` instead.
236+ } else {
237+ a . operations [ 1 ] . howMany = newHowMany ;
238+ }
239+
240+ return [ a ] ;
241+ } else {
242+ // `a` splits further.
243+ // This is more complicated case, thankfully we can solve it using default transformation and setting proper context.
244+ const newContext = Object . assign ( { } , context ) ;
245+ newContext . isStrong = true ;
246+ newContext . insertBefore = true ;
247+
248+ return defaultTransform ( a , b , newContext ) ;
249+ }
214250 }
215251
216252 return defaultTransform ( a , b , context ) ;
0 commit comments