11import { IndexBuilder } from "@editorjs/model" ;
22import { Operation , OperationType } from "./Operation" ;
3+ import { getRangesIntersectionType , RangeIntersectionType } from "./utils/getRangesIntersectionType" ;
34
45/**
56 * Class that transforms operation against another operation
@@ -19,63 +20,40 @@ export class OperationsTransformer {
1920 }
2021
2122 /**
22- * Method that desides what kind of transformation should be applied to the operation
23+ * Method that returns new operation based on the type of againstOp index
2324 * Cases:
24- * 1. Against operations is a block operation and current operation is also a block operation
25- * - check that againstOp affects current operation and transform against block operation
25+ * 1. Against operation is a block operation and current operation is also a block operation
26+ * - check if againstOp affects current operation, update operation's block index
2627 *
2728 * 2. Against operation is a block operation and current operation is a text operation
28- * - same as above, check that againstOp affects current operation and transform against block operation
29+ * - same as above, check if againstOp affects current operation and update operation's block index
2930 *
3031 * 3. Against operation is a text operation and current operation is a block operation
3132 * - text operation does not afftect block operation - so return copy of current operation
3233 *
3334 * 4. Against operation is a text operation and current operation is also a text operation
34- * - check that againstOp affects current operation and transform against text operation
35+ * - check if againstOp affects current operation and update operation's text index
3536 *
3637 * @param operation - operation to be transformed
3738 * @param againstOp - operation against which the current operation should be transformed
3839 * @returns new operation
3940 */
4041 #applyTransformation< T extends OperationType > ( operation : Operation < T > , againstOp : Operation < OperationType > ) : Operation < T > | Operation < OperationType . Neutral > {
41- const currentIndex = operation . index ;
4242 const againstIndex = againstOp . index ;
4343
44- /**
45- * Cover 1 and 2 cases
46- *
47- * Check that againstOp is a block operation
48- */
49- if ( againstIndex . isBlockIndex && currentIndex . blockIndex !== undefined ) {
50- /**
51- * Check that againstOp affects current operation
52- */
53- if ( againstIndex . blockIndex ! <= currentIndex . blockIndex ) {
54- return this . #transformAgainstBlockOperation( operation , againstOp ) ;
55- }
56- }
44+ switch ( true ) {
45+ case ( againstIndex . isBlockIndex ) :
46+ return this . #transformAgainstBlockOperation( operation , againstOp ) ;
47+
48+ case ( againstIndex . isTextIndex ) :
49+ return this . #transformAgainstTextOperation( operation , againstOp ) ;
5750
58- /**
59- * Cover 4 case
60- *
61- * Check that againstOp is a text operation and current operation is also a text operation
62- */
63- if ( againstIndex . isTextIndex && currentIndex . isTextIndex ) {
6451 /**
65- * Check that againstOp affects current operation (text operation on the same block and same input)
66- * and against op happened on the left side or has overlapping range
52+ * @todo Cover all index types
6753 */
68- if ( currentIndex . dataKey === againstIndex . dataKey && currentIndex . blockIndex === againstIndex . blockIndex && againstIndex . textRange ! [ 0 ] <= currentIndex . textRange ! [ 0 ] ) {
69- return this . #transformAgainstTextOperation( operation , againstOp ) ;
70- }
54+ default :
55+ throw new Error ( 'Unsupported index type' ) ;
7156 }
72-
73- /**
74- * Cover 3 case
75- *
76- * Return copy of current operation
77- */
78- return Operation . from ( operation ) ;
7957 }
8058
8159 /**
@@ -100,7 +78,21 @@ export class OperationsTransformer {
10078 */
10179 #transformAgainstBlockOperation< T extends OperationType > ( operation : Operation < T > , againstOp : Operation < OperationType > ) : Operation < T > | Operation < OperationType . Neutral > {
10280 const newIndexBuilder = new IndexBuilder ( ) . from ( operation . index ) ;
103-
81+
82+ /**
83+ * If current operation has no block index, return copy of the current operation
84+ */
85+ if ( ! operation . index . isBlockIndex ) {
86+ return Operation . from ( operation ) ;
87+ }
88+
89+ /**
90+ * Check that againstOp affects current operation
91+ */
92+ if ( againstOp . index . blockIndex ! <= operation . index . blockIndex ! ) {
93+ return Operation . from ( operation ) ;
94+ }
95+
10496 /**
10597 * Update the index of the current operation
10698 */
@@ -136,7 +128,11 @@ export class OperationsTransformer {
136128 /**
137129 * Return new operation with the updated index
138130 */
139- return new Operation ( operation . type , newIndexBuilder . build ( ) , operation . data , operation . userId , operation . rev ) ;
131+ const newOp = Operation . from ( operation ) ;
132+
133+ newOp . index = newIndexBuilder . build ( ) ;
134+
135+ return newOp ;
140136 }
141137
142138 /**
@@ -195,31 +191,36 @@ export class OperationsTransformer {
195191 #transformAgainstTextInsert< T extends OperationType > ( operation : Operation < T > , againstOp : Operation < OperationType > ) : Operation < T > | Operation < OperationType . Neutral > {
196192 const newIndexBuilder = new IndexBuilder ( ) . from ( operation . index ) ;
197193
198- const amountOfInsertedCharacters = againstOp . data . payload ! . length ;
199- const againstOpIsOnTheLeft = againstOp . index . textRange ! [ 0 ] < operation . index . textRange ! [ 0 ] ;
200- const currentOpAgregatesAgainstOp = ( operation . index . textRange ! [ 0 ] <= againstOp . index . textRange ! [ 0 ] ) && ( operation . index . textRange ! [ 1 ] >= againstOp . index . textRange ! [ 1 ] ) ;
194+ const insertedLength = againstOp . data . payload ! . length ;
195+
196+ const index = operation . index ;
197+ const againstIndex = againstOp . index ;
201198
202199 /**
203- * Cover case 1
200+ * In this case, againstOp is insert operatioin, there would be only two possible intersections
201+ * - None - inserted text is on the left side of the current operation
202+ * - Includes - inserted text is inside of the current operation text range
204203 */
205- if ( againstOpIsOnTheLeft ) {
206- /**
207- * Move text index of the current operation to the right by amount of inserted characters
208- */
209- newIndexBuilder . addTextRange ( [ againstOp . index . textRange ! [ 0 ] + amountOfInsertedCharacters , againstOp . index . textRange ! [ 1 ] + amountOfInsertedCharacters ] ) ;
204+ const intersectionType = getRangesIntersectionType ( index . textRange ! , againstIndex . textRange ! ) ;
205+
206+ switch ( intersectionType ) {
207+ case ( RangeIntersectionType . None ) :
208+ newIndexBuilder . addTextRange ( [ index . textRange ! [ 0 ] + insertedLength , index . textRange ! [ 1 ] + insertedLength ] ) ;
209+ break ;
210+
211+ case ( RangeIntersectionType . Includes ) :
212+ newIndexBuilder . addTextRange ( [ index . textRange ! [ 0 ] , index . textRange ! [ 1 ] + insertedLength ] ) ;
213+ break ;
210214 }
211215
212216 /**
213- * Cover case 2
217+ * Return new operation with the updated index
214218 */
215- if ( currentOpAgregatesAgainstOp ) {
216- /**
217- * Move right bound of the current operation to the right by amount of inserted characters to include the inserted text
218- */
219- newIndexBuilder . addTextRange ( [ operation . index . textRange ! [ 0 ] , operation . index . textRange ! [ 1 ] + amountOfInsertedCharacters ] ) ;
220- }
219+ const newOp = Operation . from ( operation ) ;
221220
222- return new Operation ( operation . type , newIndexBuilder . build ( ) , operation . data , operation . userId , operation . rev ) ;
221+ newOp . index = newIndexBuilder . build ( ) ;
222+
223+ return newOp ;
223224 }
224225
225226 /**
@@ -248,67 +249,58 @@ export class OperationsTransformer {
248249 */
249250 #transformAgainstTextDelete< T extends OperationType > ( operation : Operation < T > , againstOp : Operation < OperationType > ) : Operation < T > | Operation < OperationType . Neutral > {
250251 const newIndexBuilder = new IndexBuilder ( ) . from ( operation . index ) ;
251-
252252 const deletedAmount = againstOp . data . payload ! . length ;
253253
254- const deleteIsOnTheLeft = againstOp . index . textRange ! [ 1 ] < operation . index . textRange ! [ 0 ] ;
255-
256- const deletedLeftSide = ( againstOp . index . textRange ! [ 0 ] <= operation . index . textRange ! [ 0 ] )
257- && ( againstOp . index . textRange ! [ 1 ] < operation . index . textRange ! [ 1 ] )
258- && ( againstOp . index . textRange ! [ 1 ] > operation . index . textRange ! [ 0 ] ) ;
259-
260- const deletedRightSide = ( againstOp . index . textRange ! [ 0 ] > operation . index . textRange ! [ 0 ] )
261- && ( againstOp . index . textRange ! [ 0 ] < operation . index . textRange ! [ 1 ] )
262- && ( againstOp . index . textRange ! [ 1 ] <= operation . index . textRange ! [ 1 ] ) ;
263-
264- const deletedInside = ( againstOp . index . textRange ! [ 0 ] > operation . index . textRange ! [ 0 ] )
265- && ( againstOp . index . textRange ! [ 1 ] < operation . index . textRange ! [ 1 ] ) ;
254+ const index = operation . index ;
255+ const againstIndex = againstOp . index ;
256+
257+ const intersectionType = getRangesIntersectionType ( index . textRange ! , againstIndex . textRange ! ) ;
266258
267- const deletedFull = ( againstOp . index . textRange ! [ 0 ] <= operation . index . textRange ! [ 0 ] )
268- && ( againstOp . index . textRange ! [ 1 ] >= operation . index . textRange ! [ 1 ] ) ;
259+ switch ( intersectionType ) {
260+ /**
261+ * Cover case 1
262+ */
263+ case ( RangeIntersectionType . None ) :
264+ newIndexBuilder . addTextRange ( [ index . textRange ! [ 0 ] - deletedAmount , index . textRange ! [ 1 ] - deletedAmount ] ) ;
265+ break ;
269266
270- /**
271- * Cover case 1
272- */
273- if ( deleteIsOnTheLeft ) {
274- newIndexBuilder . addTextRange ( [ operation . index . textRange ! [ 0 ] - deletedAmount , operation . index . textRange ! [ 1 ] - deletedAmount ] ) ;
275- }
267+ /**
268+ * Cover case 2. 1
269+ */
270+ case ( RangeIntersectionType . Left ) :
271+ newIndexBuilder . addTextRange ( [ againstIndex . textRange ! [ 0 ] , index . textRange ! [ 1 ] - deletedAmount ] ) ;
272+ break ;
276273
277- /**
278- * Cover case 2.1
279- */
280- if ( deletedLeftSide ) {
281- const deletedFromCurrentOpRange = operation . index . textRange ! [ 0 ] - againstOp . index . textRange ! [ 1 ] ;
274+ /**
275+ * Cover case 2.2
276+ */
277+ case ( RangeIntersectionType . Right ) :
278+ const overlapLength = index . textRange ! [ 1 ] - againstIndex . textRange ! [ 0 ] ;
282279
283- newIndexBuilder . addTextRange ( [ againstOp . index . textRange ! [ 0 ] , operation . index . textRange ! [ 1 ] - deletedFromCurrentOpRange ] ) ;
284- }
280+ newIndexBuilder . addTextRange ( [ index . textRange ! [ 0 ] , index . textRange ! [ 1 ] - overlapLength ] ) ;
281+ break ;
285282
286- /**
287- * Cover case 2.2
288- */
289- if ( deletedRightSide ) {
290- const deletedFromCurrentOpRange = operation . index . textRange ! [ 1 ] - againstOp . index . textRange ! [ 0 ] ;
283+ /**
284+ * Cover case 3
285+ */
286+ case ( RangeIntersectionType . Includes ) :
287+ newIndexBuilder . addTextRange ( [ index . textRange ! [ 0 ] , index . textRange ! [ 1 ] - deletedAmount ] ) ;
288+ break ;
291289
292- newIndexBuilder . addTextRange ( [ operation . index . textRange ! [ 0 ] , operation . index . textRange ! [ 1 ] - deletedFromCurrentOpRange ] ) ;
290+ /**
291+ * Cover case 4
292+ */
293+ case ( RangeIntersectionType . Included ) :
294+ return new Operation ( OperationType . Neutral , newIndexBuilder . build ( ) , { payload : [ ] } , operation . userId , operation . rev ) ;
293295 }
294296
295297 /**
296- * Cover case 3
298+ * Return new operation with updated index
297299 */
298- if ( deletedInside ) {
299- newIndexBuilder . addTextRange ( [ operation . index . textRange ! [ 0 ] , operation . index . textRange ! [ 1 ] - deletedAmount ] ) ;
300- }
300+ const newOp = Operation . from ( operation ) ;
301301
302- /**
303- * Cover case 4
304- */
305- if ( deletedFull ) {
306- return new Operation ( OperationType . Neutral , newIndexBuilder . build ( ) , { payload : [ ] } , operation . userId , operation . rev ) ;
307- }
302+ newOp . index = newIndexBuilder . build ( ) ;
308303
309- /**
310- * Return new operation with updated index
311- */
312- return new Operation ( operation . type , newIndexBuilder . build ( ) , operation . data , operation . userId , operation . rev ) ;
304+ return newOp ;
313305 }
314306}
0 commit comments