11import { IndexBuilder , type Index , type BlockNodeSerialized } from '@editorjs/model' ;
2+ import { OperationsTransformer } from './OperationsTransformer' ;
23
34/**
45 * Type of the operation
@@ -126,6 +127,11 @@ export class Operation<T extends OperationType = OperationType> {
126127 */
127128 public rev ?: number ;
128129
130+ /**
131+ * Transformer for operations
132+ */
133+ #transformer: OperationsTransformer = new OperationsTransformer ( ) ;
134+
129135 /**
130136 * Creates an instance of Operation
131137 *
@@ -215,217 +221,7 @@ export class Operation<T extends OperationType = OperationType> {
215221 * @param againstOp - operation to transform against
216222 */
217223 public transform < K extends OperationType > ( againstOp : Operation < K > | Operation < OperationType . Neutral > ) : Operation < T | OperationType . Neutral > {
218- /**
219- * Do not transform operations if they are on different documents
220- */
221- if ( this . index . documentId !== againstOp . index . documentId ) {
222- return this ;
223- }
224-
225- /**
226- * Do not transform if the againstOp index is greater or if againstOp is Modify op
227- */
228- if ( ! this . #shouldTransform( againstOp . index ) || againstOp . type === OperationType . Modify ) {
229- return this ;
230- }
231-
232- const newIndexBuilder = new IndexBuilder ( ) . from ( this . index ) ;
233-
234- switch ( true ) {
235- /**
236- * If one of the operations is neutral, return this operation
237- */
238- case ( this . type === OperationType . Neutral || againstOp . type === OperationType . Neutral ) : {
239- return Operation . from ( this ) ;
240- }
241- /**
242- * Every operation against modify operation stays the same
243- */
244- case ( againstOp . type === OperationType . Modify ) : {
245- break ;
246- }
247- case ( this . type === OperationType . Insert && againstOp . type === OperationType . Insert ) : {
248- /**
249- * Update block index if againstOp is insert of a block
250- */
251- if ( againstOp . index . isBlockIndex ) {
252- newIndexBuilder . addBlockIndex ( this . index . blockIndex ! ++ ) ;
253-
254- break ;
255- }
256-
257- /**
258- * Move current insert to the right on amount on chars, inserted by againstOp
259- */
260- if ( this . index . isTextIndex && againstOp . index . isTextIndex ) {
261- const againstOpLength = againstOp . data . payload ! . length ;
262-
263- newIndexBuilder . addTextRange ( [ this . index . textRange ! [ 0 ] + againstOpLength , this . index . textRange ! [ 1 ] + againstOpLength ] )
264-
265- break ;
266- }
267- }
268- case ( this . type === OperationType . Insert && againstOp . type === OperationType . Delete ) : {
269- /**
270- * Decrease block index if againstOp is Delete block before current insert
271- */
272- if ( againstOp . index . isBlockIndex && this . index . blockIndex ! > againstOp . index . blockIndex ! ) {
273- newIndexBuilder . addBlockIndex ( this . index . blockIndex ! -- ) ;
274-
275- break ;
276- }
277-
278- if ( this . index . isTextIndex && againstOp . index . isTextIndex ) {
279- /**
280- * Deleted the range on the left of the current insert
281- */
282- if ( this . index . textRange ! [ 0 ] > againstOp . index . textRange ! [ 1 ] ) {
283- newIndexBuilder . addTextRange ( [ this . index . textRange ! [ 0 ] - againstOp . index . textRange ! [ 1 ] , this . index . textRange ! [ 1 ] - againstOp . index . textRange ! [ 1 ] ] ) ;
284-
285- break ;
286- }
287-
288- /**
289- * Deleted the range, then trying to insert new text inside of the deleted range
290- * Then insert should be done in the start of the deleted range
291- */
292- if ( ( this . index . textRange ! [ 0 ] <= againstOp . index . textRange ! [ 0 ] ) && ( this . index . textRange ! [ 0 ] > againstOp . index . textRange ! [ 0 ] ) ) {
293- newIndexBuilder . addTextRange ( [ againstOp . index . textRange ! [ 0 ] , againstOp . index . textRange ! [ 0 ] ] ) ;
294- }
295- }
296-
297- break ;
298- }
299- case ( this . type === OperationType . Modify && againstOp . type === OperationType . Insert ) : {
300- /**
301- * Increase block index of the modify operation if againstOp insert a block before
302- */
303- if ( againstOp . index . isBlockIndex ) {
304- newIndexBuilder . addBlockIndex ( this . index . blockIndex ! ++ ) ;
305-
306- break ;
307- }
308-
309- /**
310- * Extend modify operation range if againstOp insert a text inside of the modify bounds
311- */
312- if ( againstOp . index . textRange ! [ 0 ] < this . index . textRange ! [ 0 ] && againstOp . index . textRange ! [ 1 ] > this . index . textRange ! [ 0 ] ) {
313- const againstOpLength = againstOp . index . textRange ! [ 1 ] - againstOp . index . textRange ! [ 0 ] ;
314-
315- newIndexBuilder . addTextRange ( [ this . index . textRange ! [ 0 ] , this . index . textRange ! [ 1 ] + againstOpLength ] ) ;
316- }
317- break ;
318- }
319- case ( this . type === OperationType . Modify && againstOp . type === OperationType . Delete ) : {
320- /**
321- * Decrease block index of the modify operation if againstOp delete a block before
322- */
323- if ( againstOp . index . isBlockIndex && this . index . blockIndex ! > againstOp . index . blockIndex ! ) {
324- newIndexBuilder . addBlockIndex ( this . index . blockIndex ! -- ) ;
325-
326- break ;
327- }
328-
329- /**
330- * Make modify operation neutral if againstOp deleted a block, to apply modify to
331- */
332- if ( againstOp . index . isBlockIndex && this . index . blockIndex ! === againstOp . index . blockIndex ! ) {
333- return new Operation ( OperationType . Neutral , this . index , { payload : [ ] } , this . userId ) ;
334-
335- }
336- break ;
337- }
338- case ( this . type === OperationType . Delete && againstOp . type === OperationType . Insert ) : {
339- /**
340- * Increase block index if againstOp insert a block before
341- */
342- if ( againstOp . index . isBlockIndex ) {
343- newIndexBuilder . addBlockIndex ( this . index . blockIndex ! ++ ) ;
344-
345- break ;
346- }
347-
348- if ( this . index . isTextIndex && againstOp . index . isTextIndex ) {
349- const againstOpLength = againstOp . data . payload ?. length ;
350-
351- /**
352- * Extend delete operation range if againstOp insert a text inside of the delete bounds
353- */
354- if ( ( againstOp . index . textRange ! [ 0 ] > this . index . textRange ! [ 0 ] ) && ( againstOp . index . textRange ! [ 0 ] < this . index . textRange ! [ 1 ] ) ) {
355- newIndexBuilder . addTextRange ( [ this . index . textRange ! [ 0 ] , this . index . textRange ! [ 1 ] + againstOpLength ] ) ;
356-
357- break ;
358- }
359-
360- /**
361- * Move deletion bounds to the right by amount of inserted text
362- */
363- if ( this . index . textRange ! [ 0 ] > againstOp . index . textRange ! [ 0 ] ) {
364- newIndexBuilder . addTextRange ( [ this . index . textRange ! [ 0 ] + againstOpLength , this . index . textRange ! [ 1 ] + againstOpLength ] ) ;
365- }
366- }
367- break ;
368- }
369- case ( this . type === OperationType . Delete && againstOp . type === OperationType . Delete ) : {
370- /**
371- * Decrease block index if againstOp delete a block before
372- */
373- if ( againstOp . index . isBlockIndex && this . index . blockIndex ! > againstOp . index . blockIndex ! ) {
374- newIndexBuilder . addBlockIndex ( this . index . blockIndex ! -- ) ;
375-
376- break ;
377- }
378-
379- if ( this . index . isTextIndex && againstOp . index . isTextIndex ) {
380- const againstOpLength = againstOp . index . textRange ! [ 1 ] - againstOp . index . textRange ! [ 0 ] ;
381-
382- /**
383- * Move deletion bounds to the left by amount of deleted text
384- */
385- if ( this . index . textRange ! [ 0 ] > againstOp . index . textRange ! [ 1 ] ) {
386- newIndexBuilder . addTextRange ( [ this . index . textRange ! [ 0 ] - againstOpLength , this . index . textRange ! [ 1 ] - againstOpLength ] ) ;
387-
388- break ;
389- }
390-
391- /**
392- * If operation tries to delete a range that is already deleted, return neutral operation
393- */
394- if ( this . index . textRange ! [ 0 ] >= againstOp . index . textRange ! [ 0 ] && this . index . textRange ! [ 1 ] <= againstOp . index . textRange ! [ 1 ] ) {
395- return new Operation ( OperationType . Neutral , this . index , { payload : [ ] } , this . userId ) ;
396- }
397-
398- /**
399- * Remove part of the delete operation range if it is already deleted by againstOp
400- * Cover three possible overlaps
401- */
402- if ( this . index . textRange ! [ 0 ] > againstOp . index . textRange ! [ 0 ] && this . index . textRange ! [ 1 ] > againstOp . index . textRange ! [ 1 ] ) {
403- newIndexBuilder . addTextRange ( [ againstOp . index . textRange ! [ 1 ] , this . index . textRange ! [ 1 ] ] ) ;
404-
405- break ;
406- }
407- if ( this . index . textRange ! [ 0 ] < againstOp . index . textRange ! [ 0 ] && this . index . textRange ! [ 1 ] < againstOp . index . textRange ! [ 1 ] ) {
408- newIndexBuilder . addTextRange ( [ this . index . textRange ! [ 0 ] , againstOp . index . textRange ! [ 0 ] ] ) ;
409-
410- break ;
411- }
412- if ( this . index . textRange ! [ 0 ] < againstOp . index . textRange ! [ 0 ] && this . index . textRange ! [ 1 ] > againstOp . index . textRange ! [ 1 ] ) {
413- newIndexBuilder . addTextRange ( [ this . index . textRange ! [ 0 ] , againstOp . index . textRange ! [ 1 ] - againstOpLength ] ) ;
414-
415- break ;
416- }
417- }
418- }
419- default : {
420- throw new Error ( 'Unsupported operation type' ) ;
421- }
422- }
423-
424- const operation = Operation . from ( this ) ;
425-
426- operation . index = newIndexBuilder . build ( ) ;
427-
428- return operation ;
224+ return this . #transformer. transform ( this , againstOp ) ;
429225 }
430226
431227 /**
@@ -440,31 +236,4 @@ export class Operation<T extends OperationType = OperationType> {
440236 rev : this . rev ! ,
441237 } ;
442238 }
443-
444- /**
445- * Checks if operation needs to be transformed:
446- * 1. If relative operation (againstOp) happened in the block before or at the same index of the Block of _this_ operation
447- * 2. If relative operation happened in the same block and same data key and before the text range of _this_ operation
448- *
449- * @param indexToCompare - index of a relative operation
450- */
451- #shouldTransform( indexToCompare : Index ) : boolean {
452- if ( indexToCompare . isBlockIndex && this . index . blockIndex !== undefined ) {
453- return indexToCompare . blockIndex ! <= this . index . blockIndex ;
454- }
455-
456- if ( indexToCompare . isTextIndex && this . index . isTextIndex ) {
457- return indexToCompare . dataKey === this . index . dataKey && indexToCompare . textRange ! [ 0 ] <= this . index . textRange ! [ 0 ] ;
458- }
459-
460- return false ;
461- }
462-
463- // #removeOverlappingTextRange(range: TextRange, againstRange: TextRange): TextRange {
464- // if (range[0] <= againstRange[0] && range[1] >= againstRange[0]) {
465- // return [againstRange[0], range[1]];
466- // }
467-
468- // return range;
469- // }
470239}
0 commit comments