Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.

Commit 61da3ec

Browse files
authored
Merge pull request #1768 from ckeditor/t/1767
Feature: `transformSets()` will now return a `Map` instance linking transformed operations to the original operations.
2 parents ad9159c + 50d87d3 commit 61da3ec

File tree

2 files changed

+91
-21
lines changed

2 files changed

+91
-21
lines changed

src/model/operation/transform.js

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -148,16 +148,24 @@ export function transform( a, b, context = {} ) {
148148
* @returns {Object} Transformation result.
149149
* @returns {Array.<module:engine/model/operation/operation~Operation>} return.operationsA Transformed `operationsA`.
150150
* @returns {Array.<module:engine/model/operation/operation~Operation>} return.operationsB Transformed `operationsB`.
151+
* @returns {Map} return.originalOperations A map that links transformed operations to original operations. The keys are the transformed
152+
* operations and the values are the original operations from the input (`operationsA` and `operationsB`).
151153
*/
152154
export function transformSets( operationsA, operationsB, options ) {
153155
// Create new arrays so the originally passed arguments are not changed.
154156
// No need to clone operations, they are cloned as they are transformed.
155157
operationsA = operationsA.slice();
156158
operationsB = operationsB.slice();
157159

160+
const contextFactory = new ContextFactory( options.document, options.useRelations, options.forceWeakRemove );
161+
contextFactory.setOriginalOperations( operationsA );
162+
contextFactory.setOriginalOperations( operationsB );
163+
164+
const originalOperations = contextFactory.originalOperations;
165+
158166
// If one of sets is empty there is simply nothing to transform, so return sets as they are.
159167
if ( operationsA.length == 0 || operationsB.length == 0 ) {
160-
return { operationsA, operationsB };
168+
return { operationsA, operationsB, originalOperations };
161169
}
162170
//
163171
// Following is a description of transformation process:
@@ -305,10 +313,6 @@ export function transformSets( operationsA, operationsB, options ) {
305313
originalOperationsBCount: operationsB.length
306314
};
307315

308-
const contextFactory = new ContextFactory( options.document, options.useRelations, options.forceWeakRemove );
309-
contextFactory.setOriginalOperations( operationsA );
310-
contextFactory.setOriginalOperations( operationsB );
311-
312316
// Index of currently transformed operation `a`.
313317
let i = 0;
314318

@@ -374,7 +378,7 @@ export function transformSets( operationsA, operationsB, options ) {
374378
updateBaseVersions( operationsA, data.nextBaseVersionB );
375379
updateBaseVersions( operationsB, data.nextBaseVersionA );
376380

377-
return { operationsA, operationsB };
381+
return { operationsA, operationsB, originalOperations };
378382
}
379383

380384
// Gathers additional data about operations processed during transformation. Can be used to obtain contextual information
@@ -388,6 +392,13 @@ class ContextFactory {
388392
// @param {Boolean} [forceWeakRemove=false] If set to `false`, remove operation will be always stronger than move operation,
389393
// so the removed nodes won't end up back in the document root. When set to `true`, context data will be used.
390394
constructor( document, useRelations, forceWeakRemove = false ) {
395+
// For each operation that is created during transformation process, we keep a reference to the original operation
396+
// which it comes from. The original operation works as a kind of "identifier". Every contextual information
397+
// gathered during transformation that we want to save for given operation, is actually saved for the original operation.
398+
// This way no matter if operation `a` is cloned, then transformed, even breaks, we still have access to the previously
399+
// gathered data through original operation reference.
400+
this.originalOperations = new Map();
401+
391402
// `model.History` instance which information about undone operations will be taken from.
392403
this._history = document.history;
393404

@@ -396,13 +407,6 @@ class ContextFactory {
396407

397408
this._forceWeakRemove = !!forceWeakRemove;
398409

399-
// For each operation that is created during transformation process, we keep a reference to the original operation
400-
// which it comes from. The original operation works as a kind of "identifier". Every contextual information
401-
// gathered during transformation that we want to save for given operation, is actually saved for the original operation.
402-
// This way no matter if operation `a` is cloned, then transformed, even breaks, we still have access to the previously
403-
// gathered data through original operation reference.
404-
this._originalOperations = new Map();
405-
406410
// Relations is a double-map structure (maps in map) where for two operations we store how those operations were related
407411
// to each other. Those relations are evaluated during transformation process. For every transformated pair of operations
408412
// we keep relations between them.
@@ -428,10 +432,10 @@ class ContextFactory {
428432
// @param {Array.<module:engine/model/operation/operation~Operation>} operations
429433
// @param {module:engine/model/operation/operation~Operation|null} [takeFrom=null]
430434
setOriginalOperations( operations, takeFrom = null ) {
431-
const originalOperation = takeFrom ? this._originalOperations.get( takeFrom ) : null;
435+
const originalOperation = takeFrom ? this.originalOperations.get( takeFrom ) : null;
432436

433437
for ( const operation of operations ) {
434-
this._originalOperations.set( operation, originalOperation || operation );
438+
this.originalOperations.set( operation, originalOperation || operation );
435439
}
436440
}
437441

@@ -605,7 +609,7 @@ class ContextFactory {
605609
// For `op`, get its original operation. After all, if `op` is a clone (or even transformed clone) of another
606610
// operation, literally `op` couldn't be undone. It was just generated. If anything, it was the operation it origins
607611
// from which was undone. So get that original operation.
608-
const originalOp = this._originalOperations.get( op );
612+
const originalOp = this.originalOperations.get( op );
609613

610614
// And check with the document if the original operation was undone.
611615
return originalOp.wasUndone || this._history.isUndoneOperation( originalOp );
@@ -637,15 +641,15 @@ class ContextFactory {
637641
// @returns {String|null}
638642
_getRelation( opA, opB ) {
639643
// Get the original operation. Similarly as in `wasUndone()` it is used as an universal identifier for stored data.
640-
const origB = this._originalOperations.get( opB );
644+
const origB = this.originalOperations.get( opB );
641645
const undoneB = this._history.getUndoneOperation( origB );
642646

643647
// If `opB` is not undoing any operation, there is no relation.
644648
if ( !undoneB ) {
645649
return null;
646650
}
647651

648-
const origA = this._originalOperations.get( opA );
652+
const origA = this.originalOperations.get( opA );
649653
const relationsA = this._relations.get( origA );
650654

651655
// Get all relations for `opA`, and check if there is a relation with `opB`-undone-counterpart. If so, return it.
@@ -664,8 +668,8 @@ class ContextFactory {
664668
// @param {String} relation
665669
_setRelation( opA, opB, relation ) {
666670
// As always, setting is for original operations, not the clones/transformed operations.
667-
const origA = this._originalOperations.get( opA );
668-
const origB = this._originalOperations.get( opB );
671+
const origA = this.originalOperations.get( opA );
672+
const origB = this.originalOperations.get( opB );
669673

670674
let relationsA = this._relations.get( origA );
671675

tests/model/operation/transform.js

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
44
*/
55

6-
import { transform } from '../../../src/model/operation/transform';
6+
import { transform, transformSets } from '../../../src/model/operation/transform';
77

88
import Model from '../../../src/model/model';
99
import RootElement from '../../../src/model/rootelement';
@@ -2607,3 +2607,69 @@ describe( 'transform', () => {
26072607
} );
26082608
} );
26092609
} );
2610+
2611+
describe( 'transformSets', () => {
2612+
let model, doc, root, node;
2613+
2614+
beforeEach( () => {
2615+
model = new Model();
2616+
doc = model.document;
2617+
root = doc.createRoot();
2618+
2619+
node = new Node();
2620+
} );
2621+
2622+
it( 'originalOperations should correctly link transformed operations with original operations #1', () => {
2623+
const position = new Position( root, [ 0 ] );
2624+
2625+
const a = new InsertOperation( position, [ node ], 0 );
2626+
2627+
const { operationsA, originalOperations } = transformSets( [ a ], [], {
2628+
document: doc,
2629+
useRelations: false,
2630+
padWithNoOps: false
2631+
} );
2632+
2633+
expect( originalOperations.get( operationsA[ 0 ] ) ).to.equal( a );
2634+
} );
2635+
2636+
it( 'originalOperations should correctly link transformed operations with original operations #2', () => {
2637+
const position = new Position( root, [ 0 ] );
2638+
2639+
const b = new InsertOperation( position, [ node ], 0 );
2640+
2641+
const { operationsB, originalOperations } = transformSets( [], [ b ], {
2642+
document: doc,
2643+
useRelations: false,
2644+
padWithNoOps: false
2645+
} );
2646+
2647+
expect( originalOperations.get( operationsB[ 0 ] ) ).to.equal( b );
2648+
} );
2649+
2650+
it( 'originalOperations should correctly link transformed operations with original operations #3', () => {
2651+
const position = new Position( root, [ 4 ] );
2652+
2653+
const a = new InsertOperation( position, [ node ], 0 );
2654+
const b = new AttributeOperation(
2655+
new Range(
2656+
new Position( root, [ 2 ] ),
2657+
new Position( root, [ 11 ] )
2658+
),
2659+
'foo',
2660+
'bar',
2661+
'xyz',
2662+
0
2663+
);
2664+
2665+
const { operationsA, operationsB, originalOperations } = transformSets( [ a ], [ b ], {
2666+
document: doc,
2667+
useRelations: false,
2668+
padWithNoOps: false
2669+
} );
2670+
2671+
expect( originalOperations.get( operationsA[ 0 ] ) ).to.equal( a );
2672+
expect( originalOperations.get( operationsB[ 0 ] ) ).to.equal( b );
2673+
expect( originalOperations.get( operationsB[ 1 ] ) ).to.equal( b );
2674+
} );
2675+
} );

0 commit comments

Comments
 (0)