|
@@ -5,10 +5,60 @@
|
|
|
|
|
|
|
|
'use strict';
|
|
'use strict';
|
|
|
|
|
|
|
|
-import OT from '../../operation/transform.js';
|
|
|
|
|
|
|
+import operationTransform from '../operation/transform.js';
|
|
|
|
|
+import arrayUtils from '../../lib/lodash/array.js';
|
|
|
|
|
|
|
|
const specialCases = new Map();
|
|
const specialCases = new Map();
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Transforms given {@link core.treeModel.delta.Delta delta} by another {@link core.treeModel.delta.Delta delta} and
|
|
|
|
|
+ * returns the result of that transformation as an array containing one or more {@link core.treeModel.delta.Delta delta}
|
|
|
|
|
+ * instances.
|
|
|
|
|
+ *
|
|
|
|
|
+ * Delta transformations heavily base on {@link core.treeModel.operation.transform operational transformations}. Since
|
|
|
|
|
+ * delta is a list of operations most situations can be handled thanks to operational transformation. Unfortunately,
|
|
|
|
|
+ * deltas are more complicated than operations and have they semantic meaning, as they represent user's editing intentions.
|
|
|
|
|
+ *
|
|
|
|
|
+ * Sometimes, simple operational transformation on deltas' operations might result in some unexpected results. Those
|
|
|
|
|
+ * results would be fine from OT point of view, but would not reflect user's intentions. Because of such conflicts
|
|
|
|
|
+ * we need to handle transformations in special cases in a custom way.
|
|
|
|
|
+ *
|
|
|
|
|
+ * The function itself looks whether two given delta types have a special case function registered. If so, the deltas are
|
|
|
|
|
+ * transformed using that function. If not, {@link core.treeModel.delta.defaultTransform default transformation algorithm}
|
|
|
|
|
+ * is used.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @see core.treeModel.operation.transform
|
|
|
|
|
+ *
|
|
|
|
|
+ * @external core.treeModel.delta.transform
|
|
|
|
|
+ * @function core.treeModel.delta.transform.transform
|
|
|
|
|
+ * @param {core.treeModel.delta.Delta} a Delta that will be transformed.
|
|
|
|
|
+ * @param {core.treeModel.delta.Delta} b Delta to transform by.
|
|
|
|
|
+ * @param {Boolean} isAMoreImportantThanB Flag indicating whether the delta which will be transformed (`a`) should be treated
|
|
|
|
|
+ * as more important when resolving conflicts. Note that this flag is used only if provided deltas have same
|
|
|
|
|
+ * {@link core.treeModel.delta.priorities priority}. If deltas have different priorities, their importance is resolved
|
|
|
|
|
+ * automatically and overwrites this flag.
|
|
|
|
|
+ * @returns {Array.<core.treeModel.delta.Delta>} Result of the transformation.
|
|
|
|
|
+ */
|
|
|
|
|
+export default function transform( a, b, isAMoreImportantThanB ) {
|
|
|
|
|
+ const transformAlgorithm = getTransformationCase( a, b ) || defaultTransform;
|
|
|
|
|
+
|
|
|
|
|
+ const transformed = transformAlgorithm( a, b, isAMoreImportantThanB );
|
|
|
|
|
+ const baseVersion = arrayUtils.last( b.operations ).baseVersion;
|
|
|
|
|
+
|
|
|
|
|
+ return updateBaseVersion( baseVersion, transformed );
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Updates base versions of operations inside deltas (which are the results of delta transformation).
|
|
|
|
|
+function updateBaseVersion( baseVersion, deltas ) {
|
|
|
|
|
+ for ( let delta of deltas ) {
|
|
|
|
|
+ for ( let op of delta.operations ) {
|
|
|
|
|
+ op.baseVersion = ++baseVersion;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return deltas;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* The default delta transformation function. It is used for those deltas that are not in special case conflict.
|
|
* The default delta transformation function. It is used for those deltas that are not in special case conflict.
|
|
|
*
|
|
*
|
|
@@ -67,15 +117,15 @@ export function defaultTransform( a, b, isAMoreImportantThanB ) {
|
|
|
// This can be easier understood when operations sets to transform are represented by diamond diagrams:
|
|
// This can be easier understood when operations sets to transform are represented by diamond diagrams:
|
|
|
// http://www.codecommit.com/blog/java/understanding-and-applying-operational-transformation
|
|
// http://www.codecommit.com/blog/java/understanding-and-applying-operational-transformation
|
|
|
|
|
|
|
|
- // Using push.apply because OT function is returning an array with one or multiple results.
|
|
|
|
|
- Array.prototype.push.apply( newByOps, OT( opB, op, !isAMoreImportantThanB ) );
|
|
|
|
|
|
|
+ // Using push.apply because operationTransform function is returning an array with one or multiple results.
|
|
|
|
|
+ Array.prototype.push.apply( newByOps, operationTransform( opB, op, !isAMoreImportantThanB ) );
|
|
|
|
|
|
|
|
// Then, we transform operation from delta A by operation from delta B.
|
|
// Then, we transform operation from delta A by operation from delta B.
|
|
|
- const results = OT( op, opB, isAMoreImportantThanB );
|
|
|
|
|
|
|
+ const results = operationTransform( op, opB, isAMoreImportantThanB );
|
|
|
|
|
|
|
|
// We replace currently processed operation from `ops` array by the results of transformation.
|
|
// We replace currently processed operation from `ops` array by the results of transformation.
|
|
|
- // Note, that we process single operation but the OT result might be an array, so we might
|
|
|
|
|
- // splice-in more operations. We will process them further in next iterations. Right now we
|
|
|
|
|
|
|
+ // Note, that we process single operation but the operationTransform result might be an array, so we
|
|
|
|
|
+ // might splice-in more operations. We will process them further in next iterations. Right now we
|
|
|
// just save them in `ops` array and move `i` pointer by proper offset.
|
|
// just save them in `ops` array and move `i` pointer by proper offset.
|
|
|
Array.prototype.splice.apply( ops, [ i, 1 ].concat( results ) );
|
|
Array.prototype.splice.apply( ops, [ i, 1 ].concat( results ) );
|
|
|
|
|
|