Merge branch 'merge-staging' into staging
This commit is contained in:
@@ -25,9 +25,12 @@ define([], function () {
|
||||
};
|
||||
|
||||
Util.find = function (map, path) {
|
||||
return (map && path.reduce(function (p, n) {
|
||||
return typeof(p[n]) !== 'undefined' && p[n];
|
||||
}, map));
|
||||
var l = path.length;
|
||||
for (var i = 0; i < l; i++) {
|
||||
if (typeof(map[path[i]]) === 'undefined') { return; }
|
||||
map = map[path[i]];
|
||||
}
|
||||
return map;
|
||||
};
|
||||
|
||||
Util.uid = function () {
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/common/common-util.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/common-realtime.js',
|
||||
'/common/outer/network-config.js',
|
||||
'/bower_components/textpatcher/TextPatcher.js'
|
||||
], function ($, Crypto, CPNetflux, Util, Hash, Realtime, NetConfig, TextPatcher) {
|
||||
//var Messages = Cryptpad.Messages;
|
||||
//var noop = function () {};
|
||||
], function (Crypto, CPNetflux, Util, Hash, Realtime, NetConfig) {
|
||||
var finish = function (S, err, doc) {
|
||||
if (S.done) { return; }
|
||||
S.cb(err, doc);
|
||||
@@ -75,9 +71,7 @@ define([
|
||||
var realtime = Session.session = info.realtime;
|
||||
Session.network = info.network;
|
||||
|
||||
TextPatcher.create({
|
||||
realtime: realtime,
|
||||
})(doc);
|
||||
realtime.contentUpdate(doc);
|
||||
|
||||
var to = window.setTimeout(function () {
|
||||
cb(new Error("Timeout"));
|
||||
|
||||
@@ -2,16 +2,16 @@ define([
|
||||
'jquery',
|
||||
'/bower_components/chainpad-listmap/chainpad-listmap.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js?v=0.1.5',
|
||||
'/bower_components/textpatcher/TextPatcher.amd.js',
|
||||
'/common/userObject.js',
|
||||
'/common/common-interface.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/common-util.js',
|
||||
'/common/common-constants.js',
|
||||
'/common/migrate-user-object.js',
|
||||
'/bower_components/chainpad/chainpad.dist.js',
|
||||
'/common/outer/network-config.js',
|
||||
'/common/outer/local-store.js',
|
||||
], function ($, Listmap, Crypto, TextPatcher, FO, UI, Hash, Util, Constants, Migrate, NetConfig,
|
||||
], function ($, Listmap, Crypto, FO, UI, Hash, Util, Constants, Migrate, ChainPad, NetConfig,
|
||||
LocalStore) {
|
||||
/*
|
||||
This module uses localStorage, which is synchronous, but exposes an
|
||||
@@ -305,6 +305,8 @@ define([
|
||||
crypto: Crypto.createEncryptor(secret.keys),
|
||||
userName: 'fs',
|
||||
logLevel: 1,
|
||||
ChainPad: ChainPad,
|
||||
classic: true,
|
||||
};
|
||||
|
||||
var exp = {};
|
||||
|
||||
@@ -5,9 +5,11 @@ define([
|
||||
'/common/common-util.js',
|
||||
'/common/outer/network-config.js',
|
||||
'/common/credential.js',
|
||||
'/bower_components/chainpad/chainpad.dist.js',
|
||||
|
||||
'/bower_components/tweetnacl/nacl-fast.min.js',
|
||||
'/bower_components/scrypt-async/scrypt-async.min.js', // better load speed
|
||||
], function ($, Listmap, Crypto, Util, NetConfig, Cred) {
|
||||
], function ($, Listmap, Crypto, Util, NetConfig, Cred, ChainPad) {
|
||||
var Exports = {
|
||||
Cred: Cred,
|
||||
};
|
||||
@@ -64,6 +66,8 @@ define([
|
||||
validateKey: opt.keys.validateKey, // derived validation key
|
||||
crypto: Crypto.createEncryptor(opt.keys),
|
||||
logLevel: 1,
|
||||
classic: true,
|
||||
ChainPad: ChainPad,
|
||||
};
|
||||
|
||||
var rt = opt.rt = Listmap.create(config);
|
||||
@@ -133,7 +137,7 @@ define([
|
||||
return void cb('ALREADY_REGISTERED', res);
|
||||
}
|
||||
|
||||
cb(void 0, res);
|
||||
setTimeout(function () { cb(void 0, res); });
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -52,6 +52,11 @@ define([
|
||||
return hash;
|
||||
};
|
||||
|
||||
LocalStore.setUserHash = function (hash) {
|
||||
var sHash = Hash.serializeHash(hash);
|
||||
localStorage[Constants.userHashKey] = sHash;
|
||||
};
|
||||
|
||||
LocalStore.getAccountName = function () {
|
||||
return localStorage[Constants.userNameKey];
|
||||
};
|
||||
|
||||
@@ -2,9 +2,7 @@ define([
|
||||
'jquery',
|
||||
'/bower_components/hyperjson/hyperjson.js',
|
||||
'/common/toolbar3.js',
|
||||
'/bower_components/chainpad-json-validator/json-ot.js',
|
||||
'json.sortify',
|
||||
'/bower_components/textpatcher/TextPatcher.js',
|
||||
'/bower_components/nthen/index.js',
|
||||
'/common/sframe-common.js',
|
||||
'/customize/messages.js',
|
||||
@@ -13,6 +11,7 @@ define([
|
||||
'/common/common-thumbnail.js',
|
||||
'/common/common-feedback.js',
|
||||
'/customize/application_config.js',
|
||||
'/bower_components/chainpad/chainpad.dist.js',
|
||||
|
||||
'/bower_components/file-saver/FileSaver.min.js',
|
||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||
@@ -22,9 +21,7 @@ define([
|
||||
$,
|
||||
Hyperjson,
|
||||
Toolbar,
|
||||
JsonOT,
|
||||
JSONSortify,
|
||||
TextPatcher,
|
||||
nThen,
|
||||
SFCommon,
|
||||
Messages,
|
||||
@@ -32,7 +29,8 @@ define([
|
||||
UI,
|
||||
Thumb,
|
||||
Feedback,
|
||||
AppConfig)
|
||||
AppConfig,
|
||||
ChainPad)
|
||||
{
|
||||
var SaveAs = window.saveAs;
|
||||
|
||||
@@ -63,7 +61,6 @@ define([
|
||||
|
||||
var common;
|
||||
var cpNfInner;
|
||||
var textPatcher;
|
||||
var readOnly;
|
||||
var title;
|
||||
var toolbar;
|
||||
@@ -181,10 +178,11 @@ define([
|
||||
result in a feedback loop, which we call a browser
|
||||
fight */
|
||||
// what changed?
|
||||
var op = TextPatcher.diff(newContentStrNoMeta, newContent2StrNoMeta);
|
||||
var ops = ChainPad.Diff.diff(newContentStrNoMeta, newContent2StrNoMeta);
|
||||
// log the changes
|
||||
TextPatcher.log(newContentStrNoMeta, op);
|
||||
var sop = JSON.stringify(TextPatcher.format(newContentStrNoMeta, op));
|
||||
console.log(newContentStrNoMeta);
|
||||
console.log(ops);
|
||||
var sop = JSON.stringify([ newContentStrNoMeta, ops ]);
|
||||
|
||||
var fights = window.CryptPad_fights = window.CryptPad_fights || [];
|
||||
var index = fights.indexOf(sop);
|
||||
@@ -231,7 +229,7 @@ define([
|
||||
}
|
||||
|
||||
var contentStr = JSONSortify(content);
|
||||
textPatcher(contentStr);
|
||||
cpNfInner.chainpad.contentUpdate(contentStr);
|
||||
if (cpNfInner.chainpad.getUserDoc() !== contentStr) {
|
||||
console.error("realtime.getUserDoc() !== shjson");
|
||||
}
|
||||
@@ -377,7 +375,7 @@ define([
|
||||
}).nThen(function (waitFor) {
|
||||
cpNfInner = common.startRealtime({
|
||||
// really basic operational transform
|
||||
transformFunction: options.transformFunction || JsonOT.transform,
|
||||
patchTransformer: options.patchTransformer || ChainPad.SmartJSONTransformer,
|
||||
|
||||
// cryptpad debug logging (default is 1)
|
||||
// logLevel: 0,
|
||||
@@ -409,8 +407,6 @@ define([
|
||||
cpNfInner.metadataMgr.onChange(checkReady);
|
||||
checkReady();
|
||||
|
||||
textPatcher = TextPatcher.create({ realtime: cpNfInner.chainpad });
|
||||
|
||||
var infiniteSpinnerModal = false;
|
||||
window.setInterval(function () {
|
||||
if (state === STATE.DISCONNECTED) { return; }
|
||||
|
||||
@@ -1,751 +0,0 @@
|
||||
require.config({ paths: { 'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify' } });
|
||||
define([
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/bower_components/chainpad-json-validator/json-ot.js',
|
||||
'json.sortify',
|
||||
'/bower_components/textpatcher/TextPatcher.js',
|
||||
], function (Realtime, JsonOT, Sortify, TextPatcher) {
|
||||
var api = {};
|
||||
// "Proxy" is undefined in Safari : we need to use an normal object and check if there are local
|
||||
// changes regurlarly.
|
||||
var isFakeProxy = typeof window.Proxy === "undefined";
|
||||
|
||||
var DeepProxy = api.DeepProxy = (function () {
|
||||
var deepProxy = {};
|
||||
|
||||
var isArray = deepProxy.isArray = Array.isArray || function (obj) {
|
||||
return Object.toString(obj) === '[object Array]';
|
||||
};
|
||||
|
||||
/* Arrays and nulls both register as 'object' when using native typeof
|
||||
we need to distinguish them as their own types, so use this instead. */
|
||||
var type = deepProxy.type = function (dat) {
|
||||
return dat === null? 'null': isArray(dat)?'array': typeof(dat);
|
||||
};
|
||||
|
||||
/* Check if an (sub-)element in an object or an array and should be a proxy.
|
||||
If the browser doesn't support Proxy, return false */
|
||||
var isProxyable = deepProxy.isProxyable = function (obj, forceCheck) {
|
||||
if (typeof forceCheck === "undefined" && isFakeProxy) { return false; }
|
||||
return ['object', 'array'].indexOf(type(obj)) !== -1;
|
||||
};
|
||||
|
||||
/* Any time you set a value, check its type.
|
||||
If that type is proxyable, make a new proxy. */
|
||||
var setter = deepProxy.set = function (cb) {
|
||||
return function (obj, prop, value) {
|
||||
if (prop === 'on') {
|
||||
throw new Error("'on' is a reserved attribute name for realtime lists and maps");
|
||||
}
|
||||
if (isProxyable(value)) {
|
||||
obj[prop] = deepProxy.create(value, cb);
|
||||
} else {
|
||||
obj[prop] = value;
|
||||
}
|
||||
|
||||
cb();
|
||||
return obj[prop] || true; // always return truthey or you have problems
|
||||
};
|
||||
};
|
||||
|
||||
var pathMatches = deepProxy.pathMatches = function (path, pattern) {
|
||||
return !pattern.some(function (x, i) {
|
||||
return x !== path[i];
|
||||
});
|
||||
};
|
||||
|
||||
var lengthDescending = function (a, b) { return b.pattern.length - a.pattern.length; };
|
||||
|
||||
/* TODO implement 'off' as well.
|
||||
change 'setter' to warn users when they attempt to set 'off'
|
||||
*/
|
||||
var on = function(events) {
|
||||
return function (evt, pattern, f) {
|
||||
switch (evt) {
|
||||
case 'change':
|
||||
// pattern needs to be an array
|
||||
pattern = type(pattern) === 'array'? pattern: [pattern];
|
||||
|
||||
events.change.push({
|
||||
cb: function (oldval, newval, path, root) {
|
||||
if (pathMatches(path, pattern)) {
|
||||
return f(oldval, newval, path, root);
|
||||
}
|
||||
},
|
||||
pattern: pattern,
|
||||
});
|
||||
// sort into descending order so we evaluate in order of specificity
|
||||
events.change.sort(lengthDescending);
|
||||
|
||||
break;
|
||||
case 'remove':
|
||||
pattern = type(pattern) === 'array'? pattern: [pattern];
|
||||
|
||||
events.remove.push({
|
||||
cb: function (oldval, path, root) {
|
||||
if (pathMatches(path, pattern)) { return f(oldval, path, root); }
|
||||
},
|
||||
pattern: pattern,
|
||||
});
|
||||
|
||||
events.remove.sort(lengthDescending);
|
||||
|
||||
break;
|
||||
case 'ready':
|
||||
events.ready.push({
|
||||
// on('ready' has a different signature than
|
||||
// change and delete, so use 'pattern', not 'f'
|
||||
|
||||
cb: function (info) {
|
||||
pattern(info);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'disconnect':
|
||||
events.disconnect.push({
|
||||
cb: function (info) {
|
||||
// as above
|
||||
pattern(info);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'reconnect':
|
||||
events.reconnect.push({
|
||||
cb: function (info) {
|
||||
// as above
|
||||
pattern(info);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'create':
|
||||
events.create.push({
|
||||
cb: function (info) {
|
||||
pattern(info);
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
};
|
||||
|
||||
var getter = deepProxy.get = function (/* cb */) {
|
||||
var events = {
|
||||
disconnect: [],
|
||||
reconnect: [],
|
||||
change: [],
|
||||
ready: [],
|
||||
remove: [],
|
||||
create: [],
|
||||
};
|
||||
|
||||
return function (obj, prop) {
|
||||
if (prop === 'on') {
|
||||
return on(events);
|
||||
} else if (prop === '_isProxy') {
|
||||
return true;
|
||||
} else if (prop === '_events') {
|
||||
return events;
|
||||
}
|
||||
return obj[prop];
|
||||
};
|
||||
};
|
||||
|
||||
var deleter = deepProxy.delete = function (cb) {
|
||||
return function (obj, prop) {
|
||||
if (typeof(obj[prop]) === 'undefined') { return true; }
|
||||
delete obj[prop];
|
||||
cb();
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
||||
var handlers = deepProxy.handlers = function (cb, isRoot) {
|
||||
if (!isRoot) {
|
||||
return {
|
||||
set: setter(cb),
|
||||
get: function (obj, prop) {
|
||||
if (prop === '_isProxy') {
|
||||
return true;
|
||||
}
|
||||
return obj[prop];
|
||||
},
|
||||
deleteProperty: deleter(cb),
|
||||
};
|
||||
}
|
||||
return {
|
||||
set: setter(cb),
|
||||
get: getter(cb),
|
||||
deleteProperty: deleter(cb),
|
||||
};
|
||||
};
|
||||
|
||||
var remoteChangeFlag = deepProxy.remoteChangeFlag = false;
|
||||
|
||||
var stringifyFakeProxy = deepProxy.stringifyFakeProxy = function (proxy) {
|
||||
var copy = JSON.parse(Sortify(proxy));
|
||||
delete copy._events;
|
||||
delete copy._isProxy;
|
||||
return Sortify(copy);
|
||||
};
|
||||
|
||||
deepProxy.checkLocalChange = function (obj, cb) {
|
||||
if (!isFakeProxy) { return; }
|
||||
var oldObj = stringifyFakeProxy(obj);
|
||||
window.setInterval(function() {
|
||||
var newObj = stringifyFakeProxy(obj);
|
||||
if (newObj !== oldObj) {
|
||||
oldObj = newObj;
|
||||
if (remoteChangeFlag) {
|
||||
remoteChangeFlag = false;
|
||||
} else {
|
||||
cb();
|
||||
}
|
||||
}
|
||||
},300);
|
||||
};
|
||||
|
||||
var create = deepProxy.create = function (obj, opt, isRoot) {
|
||||
/* recursively create proxies in case users do:
|
||||
`x.a = {b: {c: 5}};
|
||||
|
||||
otherwise the inner object is not a proxy, which leads to incorrect
|
||||
behaviour on the client that initiated the object (but not for
|
||||
clients that receive the objects) */
|
||||
|
||||
// if the user supplied a callback, use it to create handlers
|
||||
// this saves a bit of work in recursion
|
||||
var methods = type(opt) === 'function'? handlers(opt, isRoot) : opt;
|
||||
switch (type(obj)) {
|
||||
case 'object':
|
||||
var keys = Object.keys(obj);
|
||||
keys.forEach(function (k) {
|
||||
if (isProxyable(obj[k]) && !obj[k]._isProxy) {
|
||||
obj[k] = create(obj[k], opt);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'array':
|
||||
obj.forEach(function (o, i) {
|
||||
if (isProxyable(o) && !o._isProxy) {
|
||||
obj[i] = create(obj[i], opt);
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
// if it's not an array or object, you don't need to proxy it
|
||||
throw new Error('attempted to make a proxy of an unproxyable object');
|
||||
}
|
||||
if (!isFakeProxy) {
|
||||
if (obj._isProxy) {
|
||||
return obj;
|
||||
}
|
||||
return new window.Proxy(obj, methods);
|
||||
}
|
||||
|
||||
var proxy = JSON.parse(JSON.stringify(obj));
|
||||
|
||||
if (isRoot) {
|
||||
var events = {
|
||||
disconnect: [],
|
||||
reconnect: [],
|
||||
change: [],
|
||||
ready: [],
|
||||
remove: [],
|
||||
create: [],
|
||||
};
|
||||
proxy.on = on(events);
|
||||
proxy._events = events;
|
||||
}
|
||||
return proxy;
|
||||
};
|
||||
|
||||
// onChange(path, key, root, oldval, newval)
|
||||
var onChange = function (path, key, root, oldval, newval) {
|
||||
var P = path.slice(0);
|
||||
P.push(key);
|
||||
|
||||
/* returning false in your callback terminates 'bubbling up'
|
||||
we can accomplish this with Array.some because we've presorted
|
||||
listeners by the specificity of their path
|
||||
*/
|
||||
root._events.change.some(function (handler) {
|
||||
return handler.cb(oldval, newval, P, root) === false;
|
||||
});
|
||||
};
|
||||
|
||||
var find = deepProxy.find = function (map, path) {
|
||||
/* safely search for nested values in an object via a path */
|
||||
return (map && path.reduce(function (p, n) {
|
||||
return typeof p[n] !== 'undefined' && p[n];
|
||||
}, map)) || undefined;
|
||||
};
|
||||
|
||||
var onRemove = function (path, key, root, old, top) {
|
||||
var newpath = path.concat(key);
|
||||
var X = find(root, newpath);
|
||||
|
||||
var t_X = type(X);
|
||||
|
||||
/* TODO 'find' is correct but unnecessarily expensive.
|
||||
optimize it. */
|
||||
|
||||
switch (t_X) {
|
||||
case 'array':
|
||||
|
||||
if (top) {
|
||||
// the top of an onremove should emit an onchange instead
|
||||
onChange(path, key, root, old, undefined);// no newval since it's a deletion
|
||||
} else {
|
||||
root._events.remove.forEach(function (handler) {
|
||||
return handler.cb(X, newpath, root);
|
||||
});
|
||||
}
|
||||
// remove all of the array's children
|
||||
X.forEach(function (x, i) {
|
||||
onRemove(newpath, i, root);
|
||||
});
|
||||
|
||||
break;
|
||||
case 'object':
|
||||
if (top) {
|
||||
onChange(path, key, root, old, undefined);// no newval since it's a deletion
|
||||
} else {
|
||||
root._events.remove.forEach(function (handler) {
|
||||
return handler.cb(X, newpath, root, old, false);
|
||||
});
|
||||
}
|
||||
// remove all of the object's children
|
||||
Object.keys(X).forEach(function (key) {
|
||||
onRemove(newpath, key, root, X[key], false);
|
||||
});
|
||||
|
||||
break;
|
||||
default:
|
||||
root._events.remove.forEach(function (handler) {
|
||||
return handler.cb(X, newpath, root);
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/* compare a new object 'B' against an existing proxy object 'A'
|
||||
provide a unary function 'f' for the purpose of constructing new
|
||||
deep proxies from regular objects and arrays.
|
||||
|
||||
Supply the path as you recurse, for the purpose of emitting events
|
||||
attached to particular paths within the complete structure.
|
||||
|
||||
Operates entirely via side effects on 'A'
|
||||
*/
|
||||
var objects = deepProxy.objects = function (A, B, f, path, root) {
|
||||
var Akeys = Object.keys(A);
|
||||
var Bkeys = Object.keys(B);
|
||||
|
||||
/* iterating over the keys in B will tell you if a new key exists
|
||||
it will not tell you if a key has been removed.
|
||||
to accomplish that you will need to iterate over A's keys
|
||||
*/
|
||||
|
||||
/* TODO return a truthy or falsey value (in 'objects' and 'arrays')
|
||||
so that we have some measure of whether an object or array changed
|
||||
(from the higher level in the tree, rather than doing everything
|
||||
at the leaf level).
|
||||
|
||||
bonus points if you can defer events until the complete diff has
|
||||
finished (collect them into an array or something, and simplify
|
||||
the event if possible)
|
||||
*/
|
||||
|
||||
Bkeys.forEach(function (b) {
|
||||
var t_b = type(B[b]);
|
||||
var old = A[b];
|
||||
|
||||
if (Akeys.indexOf(b) === -1) {
|
||||
// there was an insertion
|
||||
|
||||
// mind the fallthrough behaviour
|
||||
switch (t_b) {
|
||||
case 'undefined':
|
||||
// umm. this should never happen?
|
||||
throw new Error("undefined type has key. this shouldn't happen?");
|
||||
case 'array':
|
||||
case 'object':
|
||||
A[b] = f(B[b]);
|
||||
break;
|
||||
default:
|
||||
A[b] = B[b];
|
||||
}
|
||||
|
||||
// insertions are a change
|
||||
|
||||
// onChange(path, key, root, oldval, newval)
|
||||
onChange(path, b, root, old, B[b]);
|
||||
return;
|
||||
}
|
||||
|
||||
// else the key already existed
|
||||
var t_a = type(A[b]);
|
||||
if (t_a !== t_b) {
|
||||
// its type changed!
|
||||
console.log("type changed from [%s] to [%s]", t_a, t_b);
|
||||
switch (t_b) {
|
||||
case 'undefined':
|
||||
throw new Error("first pass should never reveal undefined keys");
|
||||
case 'array':
|
||||
A[b] = f(B[b]);
|
||||
// make a new proxy
|
||||
break;
|
||||
case 'object':
|
||||
A[b] = f(B[b]);
|
||||
// make a new proxy
|
||||
break;
|
||||
default:
|
||||
// all other datatypes just require assignment.
|
||||
A[b] = B[b];
|
||||
break;
|
||||
}
|
||||
|
||||
// type changes always mean a change happened
|
||||
onChange(path, b, root, old, B[b]);
|
||||
return;
|
||||
}
|
||||
|
||||
// values might have changed, if not types
|
||||
if (['array', 'object'].indexOf(t_a) === -1) {
|
||||
// it's not an array or object, so we can do deep equality
|
||||
if (A[b] !== B[b]) {
|
||||
// not equal, so assign
|
||||
A[b] = B[b];
|
||||
|
||||
onChange(path, b, root, old, B[b]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// else it's an array or object
|
||||
var nextPath = path.slice(0).concat(b);
|
||||
if (t_a === 'object') {
|
||||
// it's an object
|
||||
objects.call(root, A[b], B[b], f, nextPath, root);
|
||||
} else {
|
||||
// it's an array
|
||||
deepProxy.arrays.call(root, A[b], B[b], f, nextPath, root);
|
||||
}
|
||||
});
|
||||
Akeys.forEach(function (a) {
|
||||
var old = A[a];
|
||||
|
||||
if (a === "on" || a === "_events") { return; }
|
||||
|
||||
// the key was deleted
|
||||
if (Bkeys.indexOf(a) === -1 || type(B[a]) === 'undefined') {
|
||||
onRemove(path, a, root, old, true);
|
||||
delete A[a];
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
var arrays = deepProxy.arrays = function (A, B, f, path, root) {
|
||||
var l_A = A.length;
|
||||
var l_B = B.length;
|
||||
|
||||
if (l_A !== l_B) {
|
||||
// B is longer than Aj
|
||||
// there has been an insertion
|
||||
|
||||
// OR
|
||||
|
||||
// A is longer than B
|
||||
// there has been a deletion
|
||||
|
||||
B.forEach(function (b, i) {
|
||||
var t_a = type(A[i]);
|
||||
var t_b = type(b);
|
||||
|
||||
var old = A[i];
|
||||
|
||||
if (t_a !== t_b) {
|
||||
// type changes are always destructive
|
||||
// that's good news because destructive is easy
|
||||
switch (t_b) {
|
||||
case 'undefined':
|
||||
throw new Error('this should never happen');
|
||||
case 'object':
|
||||
A[i] = f(b);
|
||||
break;
|
||||
case 'array':
|
||||
A[i] = f(b);
|
||||
break;
|
||||
default:
|
||||
A[i] = b;
|
||||
break;
|
||||
}
|
||||
|
||||
// path, key, root object, oldvalue, newvalue
|
||||
onChange(path, i, root, old, b);
|
||||
} else {
|
||||
// same type
|
||||
var nextPath = path.slice(0).concat(i);
|
||||
|
||||
switch (t_b) {
|
||||
case 'object':
|
||||
objects.call(root, A[i], b, f, nextPath, root);
|
||||
break;
|
||||
case 'array':
|
||||
if (arrays.call(root, A[i], b, f, nextPath, root)) {
|
||||
onChange(path, i, root, old, b);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (b !== A[i]) {
|
||||
A[i] = b;
|
||||
onChange(path, i, root, old, b);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (l_A > l_B) {
|
||||
// A was longer than B, so there have been deletions
|
||||
var i = l_B;
|
||||
//var t_a;
|
||||
var old;
|
||||
|
||||
for (; i <= l_B; i++) {
|
||||
// recursively delete
|
||||
old = A[i];
|
||||
|
||||
onRemove(path, i, root, old, true);
|
||||
}
|
||||
// cool
|
||||
}
|
||||
|
||||
A.length = l_B;
|
||||
return;
|
||||
}
|
||||
|
||||
// else they are the same length, iterate over their values
|
||||
A.forEach(function (a, i) {
|
||||
var t_a = type(a);
|
||||
var t_b = type(B[i]);
|
||||
|
||||
var old = a;
|
||||
|
||||
// they have different types
|
||||
if (t_a !== t_b) {
|
||||
switch (t_b) {
|
||||
case 'undefined':
|
||||
onRemove(path, i, root, old, true);
|
||||
break;
|
||||
|
||||
// watch out for fallthrough behaviour
|
||||
// if it's an object or array, create a proxy
|
||||
case 'object':
|
||||
case 'array':
|
||||
A[i] = f(B[i]);
|
||||
break;
|
||||
default:
|
||||
A[i] = B[i];
|
||||
break;
|
||||
}
|
||||
|
||||
onChange(path, i, root, old, B[i]);
|
||||
return;
|
||||
}
|
||||
|
||||
// they are the same type, clone the paths array and push to it
|
||||
var nextPath = path.slice(0).concat(i);
|
||||
|
||||
// same type
|
||||
switch (t_b) {
|
||||
case 'undefined':
|
||||
throw new Error('existing key had type `undefined`. this should never happen');
|
||||
case 'object':
|
||||
if (objects.call(root, A[i], B[i], f, nextPath, root)) {
|
||||
onChange(path, i, root, old, B[i]);
|
||||
}
|
||||
break;
|
||||
case 'array':
|
||||
if (arrays.call(root, A[i], B[i], f, nextPath, root)) {
|
||||
onChange(path, i, root, old, B[i]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (A[i] !== B[i]) {
|
||||
A[i] = B[i];
|
||||
onChange(path, i, root, old, B[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
return;
|
||||
};
|
||||
|
||||
deepProxy.update = function (A, B, cb) {
|
||||
var t_A = type(A);
|
||||
var t_B = type(B);
|
||||
|
||||
if (t_A !== t_B) {
|
||||
throw new Error("Proxy updates can't result in type changes");
|
||||
}
|
||||
|
||||
switch (t_B) {
|
||||
/* use .call so you can supply a different `this` value */
|
||||
case 'array':
|
||||
arrays.call(A, A, B, function (obj) {
|
||||
return create(obj, cb);
|
||||
}, [], A);
|
||||
break;
|
||||
case 'object':
|
||||
// arrays.call(this, A , B , f, path , root)
|
||||
objects.call(A, A, B, function (obj) {
|
||||
return create(obj, cb);
|
||||
}, [], A);
|
||||
break;
|
||||
default:
|
||||
throw new Error("unsupported realtime datatype:" + t_B);
|
||||
}
|
||||
};
|
||||
|
||||
return deepProxy;
|
||||
}());
|
||||
|
||||
api.create = function (cfg) {
|
||||
/* validate your inputs before proceeding */
|
||||
|
||||
if (!DeepProxy.isProxyable(cfg.data, true)) {
|
||||
throw new Error('unsupported datatype: '+ DeepProxy.type(cfg.data));
|
||||
}
|
||||
|
||||
var realtimeOptions = {
|
||||
userName: cfg.userName,
|
||||
initialState: Sortify(cfg.data),
|
||||
readOnly: cfg.readOnly,
|
||||
transformFunction: JsonOT.transform || JsonOT.validate,
|
||||
logLevel: typeof(cfg.logLevel) === 'undefined'? 0: cfg.logLevel,
|
||||
validateContent: function (content) {
|
||||
try {
|
||||
JSON.parse(content);
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error("Failed to parse, rejecting patch");
|
||||
return false;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var initializing = true;
|
||||
|
||||
var setterCb = function () {
|
||||
if (!DeepProxy.remoteChangeFlag) { realtimeOptions.onLocal(); }
|
||||
};
|
||||
|
||||
var rt = {};
|
||||
var realtime;
|
||||
|
||||
var proxy;
|
||||
var patchText;
|
||||
|
||||
realtimeOptions.onRemote = function () {
|
||||
if (initializing) { return; }
|
||||
// TODO maybe implement history support here
|
||||
|
||||
var userDoc = realtime.getUserDoc();
|
||||
var parsed = JSON.parse(userDoc);
|
||||
|
||||
DeepProxy.remoteChangeFlag = true;
|
||||
DeepProxy.update(proxy, parsed, setterCb);
|
||||
DeepProxy.remoteChangeFlag = false;
|
||||
};
|
||||
|
||||
var onLocal = realtimeOptions.onLocal = function () {
|
||||
if (initializing) { return; }
|
||||
var strung = isFakeProxy? DeepProxy.stringifyFakeProxy(proxy): Sortify(proxy);
|
||||
patchText(strung);
|
||||
|
||||
// try harder
|
||||
if (realtime.getUserDoc() !== strung) { patchText(strung); }
|
||||
|
||||
// onLocal
|
||||
if (cfg.onLocal) { cfg.onLocal(); }
|
||||
};
|
||||
|
||||
proxy = DeepProxy.create(cfg.data, setterCb, true);
|
||||
console.log(proxy);
|
||||
|
||||
realtimeOptions.onInit = function (info) {
|
||||
proxy._events.create.forEach(function (handler) {
|
||||
handler.cb(info);
|
||||
});
|
||||
};
|
||||
|
||||
var ready = false;
|
||||
realtimeOptions.onReady = function (info) {
|
||||
if (ready) { return; }
|
||||
// create your patcher
|
||||
if (realtime !== info.realtime) {
|
||||
realtime = rt.realtime = info.realtime;
|
||||
patchText = TextPatcher.create({
|
||||
realtime: realtime,
|
||||
logging: cfg.logging || false,
|
||||
});
|
||||
} else {
|
||||
console.error(realtime);
|
||||
}
|
||||
|
||||
var userDoc = realtime.getUserDoc();
|
||||
var parsed = JSON.parse(userDoc);
|
||||
|
||||
DeepProxy.update(proxy, parsed, setterCb);
|
||||
|
||||
proxy._events.ready.forEach(function (handler) {
|
||||
handler.cb(info);
|
||||
});
|
||||
|
||||
DeepProxy.checkLocalChange(proxy, onLocal);
|
||||
|
||||
initializing = false;
|
||||
ready = true;
|
||||
};
|
||||
|
||||
realtimeOptions.onAbort = function (info) {
|
||||
proxy._events.disconnect.forEach(function (handler) {
|
||||
handler.cb(info);
|
||||
});
|
||||
};
|
||||
|
||||
realtimeOptions.onConnectionChange = function (info) {
|
||||
if (info.state) { // reconnect
|
||||
initializing = true;
|
||||
proxy._events.reconnect.forEach(function (handler) {
|
||||
handler.cb(info);
|
||||
});
|
||||
return;
|
||||
}
|
||||
// disconnected
|
||||
proxy._events.disconnect.forEach(function (handler) {
|
||||
handler.cb(info);
|
||||
});
|
||||
};
|
||||
|
||||
realtimeOptions.onError = function (info) {
|
||||
proxy._events.disconnect.forEach(function (handler) {
|
||||
handler.cb(info);
|
||||
});
|
||||
};
|
||||
|
||||
realtime = rt.cpCnInner = cfg.common.startRealtime(realtimeOptions);
|
||||
rt.proxy = proxy;
|
||||
rt.realtime = realtime;
|
||||
|
||||
return rt;
|
||||
};
|
||||
|
||||
return api;
|
||||
});
|
||||
@@ -18,8 +18,7 @@ define([
|
||||
'/common/common-util.js',
|
||||
'/customize/application_config.js',
|
||||
'/bower_components/chainpad/chainpad.dist.js'
|
||||
], function (Util, AppConfig) {
|
||||
var ChainPad = window.ChainPad;
|
||||
], function (Util, AppConfig, ChainPad) {
|
||||
var module = { exports: {} };
|
||||
|
||||
var badStateTimeout = typeof(AppConfig.badStateTimeout) === 'number' ?
|
||||
@@ -37,7 +36,7 @@ define([
|
||||
var onReady = config.onReady || function () { };
|
||||
var userName = config.userName;
|
||||
var initialState = config.initialState;
|
||||
var transformFunction = config.transformFunction;
|
||||
if (config.transformFunction) { throw new Error("transformFunction is nolonger allowed"); }
|
||||
var patchTransformer = config.patchTransformer;
|
||||
var validateContent = config.validateContent;
|
||||
var avgSyncMilliseconds = config.avgSyncMilliseconds;
|
||||
@@ -50,7 +49,6 @@ define([
|
||||
var chainpad = ChainPad.create({
|
||||
userName: userName,
|
||||
initialState: initialState,
|
||||
transformFunction: transformFunction,
|
||||
patchTransformer: patchTransformer,
|
||||
validateContent: validateContent,
|
||||
avgSyncMilliseconds: avgSyncMilliseconds,
|
||||
|
||||
@@ -6,8 +6,9 @@ define([
|
||||
'/common/common-ui-elements.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/common-util.js',
|
||||
'/bower_components/textpatcher/TextPatcher.js',
|
||||
], function ($, Modes, Themes, Messages, UIElements, Hash, Util, TextPatcher) {
|
||||
'/common/text-cursor.js',
|
||||
'/bower_components/chainpad/chainpad.dist.js',
|
||||
], function ($, Modes, Themes, Messages, UIElements, Hash, Util, TextCursor, ChainPad) {
|
||||
var module = {};
|
||||
|
||||
var cursorToPos = function(cursor, oldText) {
|
||||
@@ -37,7 +38,7 @@ define([
|
||||
return cursor;
|
||||
};
|
||||
|
||||
module.setValueAndCursor = function (editor, oldDoc, remoteDoc, TextPatcher) {
|
||||
module.setValueAndCursor = function (editor, oldDoc, remoteDoc) {
|
||||
var scroll = editor.getScrollInfo();
|
||||
//get old cursor here
|
||||
var oldCursor = {};
|
||||
@@ -47,9 +48,9 @@ define([
|
||||
editor.setValue(remoteDoc);
|
||||
editor.save();
|
||||
|
||||
var op = TextPatcher.diff(oldDoc, remoteDoc);
|
||||
var ops = ChainPad.Diff.diff(oldDoc, remoteDoc);
|
||||
var selects = ['selectionStart', 'selectionEnd'].map(function (attr) {
|
||||
return TextPatcher.transformCursor(oldCursor[attr], op);
|
||||
return TextCursor.transformCursor(oldCursor[attr], ops);
|
||||
});
|
||||
|
||||
if(selects[0] === selects[1]) {
|
||||
@@ -299,8 +300,8 @@ define([
|
||||
return { content: content };
|
||||
};
|
||||
|
||||
exp.setValueAndCursor = function (oldDoc, remoteDoc, TextPatcher) {
|
||||
return module.setValueAndCursor(editor, oldDoc, remoteDoc, TextPatcher);
|
||||
exp.setValueAndCursor = function (oldDoc, remoteDoc) {
|
||||
return module.setValueAndCursor(editor, oldDoc, remoteDoc);
|
||||
};
|
||||
|
||||
/////
|
||||
@@ -312,7 +313,7 @@ define([
|
||||
exp.contentUpdate = function (newContent) {
|
||||
var oldDoc = canonicalize($textarea.val());
|
||||
var remoteDoc = newContent.content;
|
||||
exp.setValueAndCursor(oldDoc, remoteDoc, TextPatcher);
|
||||
exp.setValueAndCursor(oldDoc, remoteDoc);
|
||||
};
|
||||
|
||||
exp.getContent = function () {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/common/common-interface.js',
|
||||
'/bower_components/chainpad-json-validator/json-ot.js',
|
||||
//'/bower_components/chainpad-json-validator/json-ot.js',
|
||||
|
||||
'/bower_components/chainpad/chainpad.dist.js',
|
||||
], function ($, UI, JsonOT) {
|
||||
var ChainPad = window.ChainPad;
|
||||
], function ($, UI, ChainPad /* JsonOT */) {
|
||||
//var ChainPad = window.ChainPad;
|
||||
var History = {};
|
||||
|
||||
var getStates = function (rt) {
|
||||
@@ -33,7 +33,9 @@ define([
|
||||
}
|
||||
},
|
||||
initialState: '',
|
||||
transformFunction: JsonOT.validate,
|
||||
//patchTransformer: ChainPad.NaiveJSONTransformer,
|
||||
//logLevel: 0,
|
||||
//transformFunction: JsonOT.validate,
|
||||
logLevel: config.debug ? 1 : 0,
|
||||
noPrune: true
|
||||
});
|
||||
|
||||
29
www/common/text-cursor.js
Normal file
29
www/common/text-cursor.js
Normal file
@@ -0,0 +1,29 @@
|
||||
define([
|
||||
], function () {
|
||||
var module = { exports: {} };
|
||||
var transformCursor = function (cursor, op) {
|
||||
if (!op) { return cursor; }
|
||||
|
||||
var pos = op.offset;
|
||||
var remove = op.toRemove;
|
||||
var insert = op.toInsert.length;
|
||||
if (typeof cursor === 'undefined') { return; }
|
||||
if (typeof remove === 'number' && pos < cursor) {
|
||||
cursor -= Math.min(remove, cursor - pos);
|
||||
}
|
||||
if (typeof insert === 'number' && pos < cursor) {
|
||||
cursor += insert;
|
||||
}
|
||||
return cursor;
|
||||
};
|
||||
module.exports.transformCursor = function (cursor, ops) {
|
||||
if (Array.isArray(ops)) {
|
||||
for (var i = ops.length - 1; i >= 0; i--) {
|
||||
cursor = transformCursor(cursor, ops[i]);
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
return transformCursor(ops);
|
||||
};
|
||||
return module.exports;
|
||||
});
|
||||
Reference in New Issue
Block a user