1
- export interface Operation {
2
- operation : string ;
3
- index : number ;
1
+ export type Operation = {
2
+ // TODO: we may need to remodel our operations and re-think how exactly this should be structured
3
+ path : number [ ] ;
4
+ editType : "insert" | "delete" | "noop" ;
4
5
}
5
6
7
+ // constant noop type
8
+ export const noop : Operation = { path : [ ] , editType : "noop" } ;
6
9
7
- export const transform = ( a : Operation , b : Operation ) : [ Operation , Operation ] => [ { operation : 'add' , index : 3 } , { operation : 'add' , index : 3 } ] ;
10
+ // transform takes in two operations and returns their transformed ops
11
+ export const transform = ( a : Operation , b : Operation ) : [ Operation , Operation ] => {
12
+ const transformedPaths = transformPaths ( a , b ) ;
13
+ [ a . path , b . path ] = transformedPaths ;
14
+
15
+ return [ normalise ( a ) , normalise ( b ) ] ;
16
+ }
17
+
18
+ // transformPaths takes in two operations and transforms them accordingly, note that it only returns the updated paths
19
+ const transformPaths = ( a : Operation , b : Operation ) : [ number [ ] , number [ ] ] => {
20
+ const tp = transformationPoint ( a . path , b . path ) ;
21
+ if ( ! effectIndependent ( a . path , b . path , tp ) ) {
22
+ switch ( true ) {
23
+ case a . editType === "insert" && b . editType === "insert" : return transformInserts ( a . path , b . path , tp ) ;
24
+ case a . editType === "delete" && b . editType === "delete" : return transformDeletes ( a . path , b . path , tp ) ;
25
+ case a . editType === "insert" && b . editType === "delete" : return transformInsertDelete ( a . path , b . path , tp ) ;
26
+ default :
27
+ const result = transformInsertDelete ( b . path , a . path , tp ) ;
28
+ result . reverse ( ) ;
29
+ return result ;
30
+ }
31
+ }
32
+
33
+ return [ a . path , b . path ] ;
34
+ }
35
+
36
+ // transformInserts takes 2 paths and their transformation point and transforms them as if they were insertion functions
37
+ const transformInserts = ( a : number [ ] , b : number [ ] , tp : number ) : [ number [ ] , number [ ] ] => {
38
+ switch ( true ) {
39
+ case a [ tp ] > b [ tp ] : return [ update ( a , tp , 1 ) , b ] ;
40
+ case a [ tp ] < b [ tp ] : return [ a , update ( b , tp , 1 ) ] ;
41
+ default :
42
+ return a . length > b . length
43
+ ? [ update ( a , tp , 1 ) , b ]
44
+ : ( a . length < b . length ? [ a , update ( b , tp , 1 ) ] : [ a , b ] ) ;
45
+ }
46
+ }
47
+
48
+ // transformDeletes takes 2 paths and transforms them as if they were deletion operations
49
+ const transformDeletes = ( a : number [ ] , b : number [ ] , tp : number ) : [ number [ ] , number [ ] ] => {
50
+ switch ( true ) {
51
+ case a [ tp ] > b [ tp ] : return [ update ( a , tp , - 1 ) , b ] ;
52
+ case a [ tp ] < b [ tp ] : return [ a , update ( b , tp , - 1 ) ] ;
53
+ default :
54
+ return a . length > b . length
55
+ ? [ [ ] , b ]
56
+ : ( a . length < b . length ? [ a , [ ] ] : [ [ ] , [ ] ] ) ;
57
+ }
58
+ }
59
+
60
+ // transformInsertDelete takes an insertion operation and a deletion operation and transforms them
61
+ const transformInsertDelete = ( insertOp : number [ ] , deleteOp : number [ ] , tp : number ) : [ number [ ] , number [ ] ] => {
62
+ switch ( true ) {
63
+ case insertOp [ tp ] > deleteOp [ tp ] : return [ update ( insertOp , tp , - 1 ) , deleteOp ] ;
64
+ case insertOp [ tp ] < deleteOp [ tp ] : return [ insertOp , update ( deleteOp , tp , 1 ) ] ;
65
+ default :
66
+ return insertOp . length > deleteOp . length
67
+ ? [ [ ] , deleteOp ]
68
+ : [ insertOp , update ( deleteOp , tp , 1 ) ]
69
+ }
70
+ }
71
+
72
+ // update updates a specific index in a path
73
+ const update = ( pos : number [ ] , toChange : number , change : number ) => {
74
+ pos [ toChange ] += change ;
75
+ return pos ;
76
+ }
77
+
78
+ // transformationPoint takes in two paths and computes their transformation point
79
+ const transformationPoint = ( a : number [ ] , b : number [ ] ) : number =>
80
+ [ ...Array ( Math . max ( a . length , b . length ) ) . keys ( ) ]
81
+ . find ( i => ( a [ i ] ?? - 1 ) != ( b [ i ] ?? - 1 ) ) ?? ( Math . max ( a . length , b . length ) - 1 ) ;
82
+
83
+
84
+ // effectIndependence takes two paths and determines if their effect is independent or not
85
+ const effectIndependent = ( a : number [ ] , b : number [ ] , transformationPoint : number ) : boolean =>
86
+ a . length > ( transformationPoint + 1 ) && b . length > ( transformationPoint + 1 ) ||
87
+ ( a [ transformationPoint ] > b [ transformationPoint ] && a . length < b . length ) ||
88
+ ( a [ transformationPoint ] < b [ transformationPoint ] && a . length > b . length ) ;
89
+
90
+ // normalise turns an empty operation into a noop
91
+ const normalise = ( a : Operation ) : Operation =>
92
+ a . path . length === 0
93
+ ? noop
94
+ : a ;
0 commit comments