Skip to content

Commit 7a5a0e4

Browse files
committed
feat(collab): add operations transformer class
1 parent 9357cba commit 7a5a0e4

File tree

5 files changed

+323
-240
lines changed

5 files changed

+323
-240
lines changed

packages/collaboration-manager/src/OperationsBatch.spec.ts renamed to packages/collaboration-manager/src/BatchedOperation.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createDataKey, IndexBuilder } from '@editorjs/model';
2-
import { OperationsBatch } from './OperationsBatch.js';
2+
import { OperationsBatch } from './BatchedOperation.js';
33
import { Operation, OperationType, SerializedOperation } from './Operation.js';
44

55
const templateIndex = new IndexBuilder()

packages/collaboration-manager/src/OperationsBatch.ts renamed to packages/collaboration-manager/src/BatchedOperation.ts

File renamed without changes.

packages/collaboration-manager/src/CollaborationManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
} from '@editorjs/model';
1111
import type { CoreConfig } from '@editorjs/sdk';
1212
import { OTClient } from './client/index.js';
13-
import { OperationsBatch } from './OperationsBatch.js';
13+
import { OperationsBatch } from './BatchedOperation.js';
1414
import { type ModifyOperationData, Operation, OperationType } from './Operation.js';
1515
import { UndoRedoManager } from './UndoRedoManager.js';
1616

Lines changed: 7 additions & 238 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { 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

Comments
 (0)