|
|
@ -1,14 +1,12 @@
|
|
|
|
define(function () {
|
|
|
|
define(function () {
|
|
|
|
|
|
|
|
|
|
|
|
/* applyChange takes:
|
|
|
|
/* diff takes two strings, the old content, and the desired content
|
|
|
|
ctx: the context (aka the realtime)
|
|
|
|
it returns the difference between these two strings in the form
|
|
|
|
oldval: the old value
|
|
|
|
of an 'Operation' (as defined in chainpad.js).
|
|
|
|
newval: the new value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it performs a diff on the two values, and generates patches
|
|
|
|
diff is purely functional.
|
|
|
|
which are then passed into `ctx.remove` and `ctx.insert`
|
|
|
|
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
var applyChange = function(ctx, oldval, newval) {
|
|
|
|
var diff = function (oldval, newval) {
|
|
|
|
// Strings are immutable and have reference equality. I think this test is O(1), so its worth doing.
|
|
|
|
// Strings are immutable and have reference equality. I think this test is O(1), so its worth doing.
|
|
|
|
if (oldval === newval) {
|
|
|
|
if (oldval === newval) {
|
|
|
|
return;
|
|
|
|
return;
|
|
|
@ -25,38 +23,66 @@ var applyChange = function(ctx, oldval, newval) {
|
|
|
|
commonEnd++;
|
|
|
|
commonEnd++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var result;
|
|
|
|
var toRemove;
|
|
|
|
|
|
|
|
var toInsert;
|
|
|
|
/* throw some assertions in here before dropping patches into the realtime
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* throw some assertions in here before dropping patches into the realtime */
|
|
|
|
if (oldval.length !== commonStart + commonEnd) {
|
|
|
|
if (oldval.length !== commonStart + commonEnd) {
|
|
|
|
if (ctx.localChange) { ctx.localChange(true); }
|
|
|
|
toRemove = oldval.length - commonStart - commonEnd;
|
|
|
|
result = oldval.length - commonStart - commonEnd;
|
|
|
|
|
|
|
|
ctx.remove(commonStart, result);
|
|
|
|
|
|
|
|
console.log('removal at position: %s, length: %s', commonStart, result);
|
|
|
|
|
|
|
|
console.log("remove: [" + oldval.slice(commonStart, commonStart + result ) + ']');
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (newval.length !== commonStart + commonEnd) {
|
|
|
|
if (newval.length !== commonStart + commonEnd) {
|
|
|
|
if (ctx.localChange) { ctx.localChange(true); }
|
|
|
|
toInsert = newval.slice(commonStart, newval.length - commonEnd);
|
|
|
|
result = newval.slice(commonStart, newval.length - commonEnd);
|
|
|
|
|
|
|
|
ctx.insert(commonStart, result);
|
|
|
|
|
|
|
|
console.log("insert: [" + result + "]");
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var userDoc;
|
|
|
|
return {
|
|
|
|
try {
|
|
|
|
type: 'Operation',
|
|
|
|
var userDoc = ctx.getUserDoc();
|
|
|
|
offset: commonStart,
|
|
|
|
JSON.parse(userDoc);
|
|
|
|
toInsert: toInsert,
|
|
|
|
} catch (err) {
|
|
|
|
toRemove: toRemove
|
|
|
|
console.error('[textPatcherParseErr]');
|
|
|
|
|
|
|
|
console.error(err);
|
|
|
|
|
|
|
|
window.REALTIME_MODULE.textPatcher_parseError = {
|
|
|
|
|
|
|
|
error: err,
|
|
|
|
|
|
|
|
userDoc: userDoc
|
|
|
|
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* patch accepts a realtime facade and an operation (which might be falsey)
|
|
|
|
|
|
|
|
it applies the operation to the realtime as components (remove/insert)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
patch has no return value, and operates solely through side effects on
|
|
|
|
|
|
|
|
the realtime facade.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
var patch = function (ctx, op) {
|
|
|
|
|
|
|
|
if (!op) { return; }
|
|
|
|
|
|
|
|
if (op.toRemove) { ctx.remove(op.offset, op.toRemove); }
|
|
|
|
|
|
|
|
if (op.toInsert) { ctx.insert(op.offset, op.toInsert); }
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* log accepts a string and an operation, and prints an object to the console
|
|
|
|
|
|
|
|
the object will display the content which is to be removed, and the content
|
|
|
|
|
|
|
|
which will be inserted in its place.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
log is useful for debugging, but can otherwise be disabled.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
var log = function (text, op) {
|
|
|
|
|
|
|
|
if (!op) { return; }
|
|
|
|
|
|
|
|
console.log({
|
|
|
|
|
|
|
|
insert: op.toInsert,
|
|
|
|
|
|
|
|
remove: text.slice(op.offset, op.offset + op.toRemove)
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* applyChange takes:
|
|
|
|
|
|
|
|
ctx: the context (aka the realtime)
|
|
|
|
|
|
|
|
oldval: the old value
|
|
|
|
|
|
|
|
newval: the new value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it performs a diff on the two values, and generates patches
|
|
|
|
|
|
|
|
which are then passed into `ctx.remove` and `ctx.insert`.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Due to its reliance on patch, applyChange has side effects on the supplied
|
|
|
|
|
|
|
|
realtime facade.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
var applyChange = function(ctx, oldval, newval) {
|
|
|
|
|
|
|
|
var op = diff(oldval, newval);
|
|
|
|
|
|
|
|
// log(oldval, op)
|
|
|
|
|
|
|
|
patch(ctx, op);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var create = function(config) {
|
|
|
|
var create = function(config) {
|
|
|
@ -84,5 +110,11 @@ var create = function(config) {
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return { create: create };
|
|
|
|
return {
|
|
|
|
|
|
|
|
create: create, // create a TextPatcher object
|
|
|
|
|
|
|
|
diff: diff, // diff two strings
|
|
|
|
|
|
|
|
patch: patch, // apply an operation to a chainpad's realtime facade
|
|
|
|
|
|
|
|
log: log, // print the components of an operation
|
|
|
|
|
|
|
|
applyChange: applyChange // a convenient wrapper around diff/log/patch
|
|
|
|
|
|
|
|
};
|
|
|
|
});
|
|
|
|
});
|
|
|
|