From 7235c8fe72c36f0beb8defba8bbec51a886ae181 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stian=20H=C3=A5klev?= Date: Thu, 4 Apr 2019 13:52:55 +0200 Subject: [PATCH 01/44] Presence changes --- lib/json0.js | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/lib/json0.js b/lib/json0.js index dc3a405..ed603e8 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -133,6 +133,87 @@ function convertToText(c) { delete c.o; } +function isValidPresence(presence) { + if ( + presence == null || + typeof presence.u !== 'string' || + typeof presence.c !== 'number' || + !isFinite(presence.c) || + Math.floor(presence.c) !== presence.c || + !Array.isArray(presence.s) + ) { + return false; + } + + var selections = presence.s; + + for (var i = 0, l = selections.length; i < l; ++i) { + var selection = selections[i]; + + if ( + !Array.isArray(selection) || + selection.length !== 2 || + selection[0] !== (selection[0] | 0) || + selection[1] !== (selection[1] | 0) + ) { + return false; + } + } + + return true; +} + +json.createPresence = function(presence) { + return presence; +}; + +json.comparePresence = function(pres1, pres2) { + if (!pres1 || !pres2) { + return false; + } + if (!pres1.p || !pres2.p) { + return false; + } + if (pres1.t !== pres2.t) { + return false; + } + if (pres1.t && subtypes[pres1.t]) { + if (pres1.p[0] === pres2.p[0]) { + return subtypes[pres1.t].comparePresence(pres1, pres2); + } + } else return pres1 === pres2; +}; + +json.transformPresence = function(presence, op, isOwn) { + console.group('json transform', presence, op); + if (op.length < 1) { + return presence; + } + const exOp = op[0]; + const opT = op[0].t; + console.log('exop', exOp, opT, exOp.p); + if (opT && subtypes[opT] && exOp.p) { + if (!presence.p || !presence.p[0] || presence.p[0] !== exOp.p[0]) { + console.log('creating presence', presence, exOp.p[0]); + presence = { + ...subtypes[opT].createPresence(), + p: op[0].p, + u: presence.u, + t: op[0].t + }; + console.log(presence); + } + presence = { + ...subtypes[opT].transformPresence(presence, op, isOwn), + p: op[0].p, + t: op[0].t + }; + console.log('result', presence); + console.groupEnd() + } + return presence; +}; + json.apply = function(snapshot, op) { json.checkValidOp(op); From 7023ee3560fae10b6e8c61baf7b3422121449c32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stian=20H=C3=A5klev?= Date: Thu, 4 Apr 2019 14:01:17 +0200 Subject: [PATCH 02/44] WIP --- lib/json0.js | 66 +++++++++++++++++----------------------------------- package.json | 6 ++--- 2 files changed, 24 insertions(+), 48 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index ed603e8..31152e8 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -133,40 +133,15 @@ function convertToText(c) { delete c.o; } -function isValidPresence(presence) { - if ( - presence == null || - typeof presence.u !== 'string' || - typeof presence.c !== 'number' || - !isFinite(presence.c) || - Math.floor(presence.c) !== presence.c || - !Array.isArray(presence.s) - ) { - return false; - } - - var selections = presence.s; - - for (var i = 0, l = selections.length; i < l; ++i) { - var selection = selections[i]; - - if ( - !Array.isArray(selection) || - selection.length !== 2 || - selection[0] !== (selection[0] | 0) || - selection[1] !== (selection[1] | 0) - ) { - return false; - } - } - - return true; -} - +// not checking anything here, we should probably check that u: exists +// (only thing we care about at json0 top level), and then delegate +// to any subtypes if there is already subtype presence data json.createPresence = function(presence) { return presence; }; +// this needs more thinking/testing, looking a bit more carefully at +// how this is implemented in ot-rich-text, etc. json.comparePresence = function(pres1, pres2) { if (!pres1 || !pres2) { return false; @@ -184,32 +159,33 @@ json.comparePresence = function(pres1, pres2) { } else return pres1 === pres2; }; +// this is the key function, always run client-side, both on +// the client that creates a text-change, and on the clients +// that receive text-changes (ops). if there are no ops, just +// return presence, if there are ops, delegate to the subtype +// responsible for those ops (currently only ot-rich-text). +// I am making assumptions many places that all ops will be +// of the same subtype, not sure if this is a given. +// We're only concerned about the first level of object/array, +// not sure if the spec allows nesting of subtypes. json.transformPresence = function(presence, op, isOwn) { - console.group('json transform', presence, op); if (op.length < 1) { return presence; } - const exOp = op[0]; - const opT = op[0].t; - console.log('exop', exOp, opT, exOp.p); - if (opT && subtypes[opT] && exOp.p) { + const representativeOp = op[0]; + const opTtype = op[0].t; + const path = representativeOp.p && representativeOp.p[0] + if (opType && subtypes[opType] && path) { if (!presence.p || !presence.p[0] || presence.p[0] !== exOp.p[0]) { - console.log('creating presence', presence, exOp.p[0]); - presence = { - ...subtypes[opT].createPresence(), - p: op[0].p, - u: presence.u, - t: op[0].t - }; - console.log(presence); + return presence } + // return result of running the subtype's transformPresence, + // but add path and type, which the subtype will not include presence = { ...subtypes[opT].transformPresence(presence, op, isOwn), p: op[0].p, t: op[0].t }; - console.log('result', presence); - console.groupEnd() } return presence; }; diff --git a/package.json b/package.json index b6c9df6..3d98220 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "ot-json0", - "version": "1.1.0", + "name": "@houshuang/ot-json0", + "version": "1.2.0", "description": "JSON OT type", "main": "lib/index.js", "directories": { @@ -17,7 +17,7 @@ }, "repository": { "type": "git", - "url": "git://github.com/ottypes/json0" + "url": "git://github.com/houshuang/json0" }, "keywords": [ "ot", From 22c5bd477bb8951c93f5cc38b33dc382dd5a8fb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stian=20H=C3=A5klev?= Date: Thu, 4 Apr 2019 14:10:26 +0200 Subject: [PATCH 03/44] WIP --- lib/json0.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index 31152e8..4b562e7 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -173,16 +173,16 @@ json.transformPresence = function(presence, op, isOwn) { return presence; } const representativeOp = op[0]; - const opTtype = op[0].t; + const opType = op[0].t; const path = representativeOp.p && representativeOp.p[0] if (opType && subtypes[opType] && path) { - if (!presence.p || !presence.p[0] || presence.p[0] !== exOp.p[0]) { + if (!presence.p || !presence.p[0] || presence.p[0] !== path) { return presence } // return result of running the subtype's transformPresence, // but add path and type, which the subtype will not include presence = { - ...subtypes[opT].transformPresence(presence, op, isOwn), + ...subtypes[opType].transformPresence(presence, op, isOwn), p: op[0].p, t: op[0].t }; From b25c3a7627eb7ee217771c4ca8cfff77311a4490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stian=20H=C3=A5klev?= Date: Sat, 6 Apr 2019 10:51:06 +0200 Subject: [PATCH 04/44] WIP --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 3d98220..c7d133e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "@houshuang/ot-json0", - "version": "1.2.0", - "description": "JSON OT type", + "name": "@minervaproject/ot-json0", + "version": "1.3.0", + "description": "JSON OT type, fork of sharedb/ot-json0@1.2.0", "main": "lib/index.js", "directories": { "test": "test" From beb947cd69f6d802a1ca75df1a4b0538d29521f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stian=20H=C3=A5klev?= Date: Thu, 11 Apr 2019 11:46:09 +0200 Subject: [PATCH 05/44] WIP --- lib/json0.js | 290 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 202 insertions(+), 88 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index 4b562e7..36b21d9 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -29,7 +29,7 @@ var isArray = function(obj) { * @returns {boolean} */ var isObject = function(obj) { - return (!!obj) && (obj.constructor === Object); + return !!obj && obj.constructor === Object; }; /** @@ -66,7 +66,7 @@ json.create = function(data) { }; json.invertComponent = function(c) { - var c_ = {p: c.p}; + var c_ = { p: c.p }; // handle subtype ops if (c.t && subtypes[c.t]) { @@ -83,8 +83,8 @@ json.invertComponent = function(c) { if (c.na !== void 0) c_.na = -c.na; if (c.lm !== void 0) { - c_.lm = c.p[c.p.length-1]; - c_.p = c.p.slice(0,c.p.length-1).concat([c.lm]); + c_.lm = c.p[c.p.length - 1]; + c_.p = c.p.slice(0, c.p.length - 1).concat([c.lm]); } return c_; @@ -106,20 +106,21 @@ json.checkValidOp = function(op) { }; json.checkList = function(elem) { - if (!isArray(elem)) - throw new Error('Referenced element not a list'); + if (!isArray(elem)) throw new Error('Referenced element not a list'); }; json.checkObj = function(elem) { if (!isObject(elem)) { - throw new Error("Referenced element not an object (it was " + JSON.stringify(elem) + ")"); + throw new Error( + 'Referenced element not an object (it was ' + JSON.stringify(elem) + ')' + ); } }; // helper functions to convert old string ops to and from subtype ops function convertFromText(c) { c.t = 'text0'; - var o = {p: c.p.pop()}; + var o = { p: c.p.pop() }; if (c.si != null) o.i = c.si; if (c.sd != null) o.d = c.sd; c.o = [o]; @@ -159,6 +160,100 @@ json.comparePresence = function(pres1, pres2) { } else return pres1 === pres2; }; +var transformPosition = function(cursor, op, isOwnOp) { + var cursor = clone(cursor); + + var opIsAncestor = cursor.length >= op.p.length; // true also if op is self + var opIsSibling = cursor.length === op.p.length; // true also if op is self + var opIsAncestorSibling = cursor.length >= op.p.length; // true also if op is self or sibling of self + var equalUpTo = -1; + for (var i = 0; i < op.p.length; i++) { + if (op.p[i] !== cursor[i]) { + opIsAncestor = false; + if (i < op.p.length - 1) { + opIsSibling = false; + opIsAncestorSibling = false; + } + } + if (equalUpTo === i - 1 && op.p[i] === cursor[i]) { + equalUpTo += 1; + } + } + + if (opIsSibling) { + if (op.sd) { + cursor[cursor.length - 1] = text.transformCursor( + cursor[cursor.length - 1], + [{ p: op.p[op.p.length - 1], d: op.sd }], + isOwnOp ? 'right' : 'left' + ); + } + if (op.si) { + cursor[cursor.length - 1] = text.transformCursor( + cursor[cursor.length - 1], + [{ p: op.p[op.p.length - 1], i: op.si }], + isOwnOp ? 'right' : 'left' + ); + } + } + + if (opIsAncestor) { + if (op.lm !== undefined) { + cursor[equalUpTo] = op.lm; + } + if (op.od && op.oi) { + cursor = op.p.slice(0, op.p.length); + } else if (op.od) { + cursor = op.p.slice(0, op.p.length - 1); + } else if (op.ld && op.li) { + cursor = op.p.slice(0, op.p.length); + } else if (op.ld) { + cursor = op.p.slice(0, op.p.length - 1); + } + } + + if (opIsAncestorSibling) { + var lastPathIdx = op.p.length - 1; + if ( + !opIsAncestor && + op.ld && + !op.li && + op.p[lastPathIdx] < cursor[lastPathIdx] + ) { + cursor[lastPathIdx] -= 1; + } else if (!op.ld && op.li && op.p[lastPathIdx] <= cursor[lastPathIdx]) { + cursor[lastPathIdx] += 1; + } + + // if move item in list from after to before + if ( + !opIsAncestor && + op.lm !== undefined && + op.p[lastPathIdx] > cursor[lastPathIdx] && + op.lm <= cursor[lastPathIdx] + ) { + cursor[lastPathIdx] += 1; + // if move item in list from before to after + } else if ( + !opIsAncestor && + op.lm !== undefined && + op.p[lastPathIdx] < cursor[lastPathIdx] && + op.lm >= cursor[lastPathIdx] + ) { + cursor[lastPathIdx] -= 1; + } + } + + return cursor; +}; + +json.transformCursor = function(cursor, op, isOwnOp) { + for (var i = 0; i < op.length; i++) { + cursor = transformPosition(cursor, op[i], isOwnOp); + } + return cursor; +}; + // this is the key function, always run client-side, both on // the client that creates a text-change, and on the clients // that receive text-changes (ops). if there are no ops, just @@ -168,25 +263,28 @@ json.comparePresence = function(pres1, pres2) { // of the same subtype, not sure if this is a given. // We're only concerned about the first level of object/array, // not sure if the spec allows nesting of subtypes. -json.transformPresence = function(presence, op, isOwn) { +json.transformPresence = function(presence, ops, isOwn) { if (op.length < 1) { return presence; } - const representativeOp = op[0]; - const opType = op[0].t; - const path = representativeOp.p && representativeOp.p[0] - if (opType && subtypes[opType] && path) { - if (!presence.p || !presence.p[0] || presence.p[0] !== path) { - return presence + ops.forEach(op => { + const opType = op.t; + const path = op.p && op.p[0]; + if (opType && subtypes[opType] && path) { + // No path given, or path does not match path in presence -- + // no need to transform path since no conflict + if (!presence.p || !presence.p[0] || presence.p[0] !== path) { + return presence; + } + // return result of running the subtype's transformPresence, + // but add path and type, which the subtype will not include + presence.cursor = subtypes[opType].transformCursor( + presence, + op, + isOwn + ).cursor; } - // return result of running the subtype's transformPresence, - // but add path and type, which the subtype will not include - presence = { - ...subtypes[opType].transformPresence(presence, op, isOwn), - p: op[0].p, - t: op[0].t - }; - } + }); return presence; }; @@ -203,8 +301,7 @@ json.apply = function(snapshot, op) { var c = op[i]; // convert old string ops to use subtype for backwards compatibility - if (c.si != null || c.sd != null) - convertFromText(c); + if (c.si != null || c.sd != null) convertFromText(c); var parent = null; var parentKey = null; @@ -219,15 +316,14 @@ json.apply = function(snapshot, op) { elem = elem[key]; key = p; - if (parent == null) - throw new Error('Path invalid'); + if (parent == null) throw new Error('Path invalid'); } // handle subtype ops if (c.t && c.o !== void 0 && subtypes[c.t]) { elem[key] = subtypes[c.t].apply(elem[key], c.o); - // Number add + // Number add } else if (c.na !== void 0) { if (typeof elem[key] != 'number') throw new Error('Referenced element not a number'); @@ -245,14 +341,14 @@ json.apply = function(snapshot, op) { // List insert else if (c.li !== void 0) { json.checkList(elem); - elem.splice(key,0, c.li); + elem.splice(key, 0, c.li); } // List delete else if (c.ld !== void 0) { json.checkList(elem); // Should check the list element matches c.ld here too. - elem.splice(key,1); + elem.splice(key, 1); } // List move @@ -261,9 +357,9 @@ json.apply = function(snapshot, op) { if (c.lm != key) { var e = elem[key]; // Remove it... - elem.splice(key,1); + elem.splice(key, 1); // And insert it back. - elem.splice(c.lm,0,e); + elem.splice(c.lm, 0, e); } } @@ -281,9 +377,7 @@ json.apply = function(snapshot, op) { // Should check that elem[key] == c.od delete elem[key]; - } - - else { + } else { throw new Error('invalid / missing instruction in op'); } } @@ -314,19 +408,17 @@ json.incrementalApply = function(snapshot, op, _yield) { }; // Checks if two paths, p1 and p2 match. -var pathMatches = json.pathMatches = function(p1, p2, ignoreLast) { - if (p1.length != p2.length) - return false; +var pathMatches = (json.pathMatches = function(p1, p2, ignoreLast) { + if (p1.length != p2.length) return false; for (var i = 0; i < p1.length; i++) { - if (p1[i] !== p2[i] && (!ignoreLast || i !== p1.length - 1)) - return false; + if (p1[i] !== p2[i] && (!ignoreLast || i !== p1.length - 1)) return false; } return true; -}; +}); -json.append = function(dest,c) { +json.append = function(dest, c) { c = clone(c); if (dest.length === 0) { @@ -345,7 +437,10 @@ json.append = function(dest,c) { if (pathMatches(c.p, last.p)) { // handle subtype ops if (c.t && last.t && c.t === last.t && subtypes[c.t]) { - last.o = subtypes[c.t].compose(last.o, c.o); + last.o = subtypes[c.t].compose( + last.o, + c.o + ); // convert back to old string ops if (c.si != null || c.sd != null) { @@ -360,8 +455,12 @@ json.append = function(dest,c) { convertToText(last); } } else if (last.na != null && c.na != null) { - dest[dest.length - 1] = {p: last.p, na: last.na + c.na}; - } else if (last.li !== undefined && c.li === undefined && c.ld === last.li) { + dest[dest.length - 1] = { p: last.p, na: last.na + c.na }; + } else if ( + last.li !== undefined && + c.li === undefined && + c.ld === last.li + ) { // insert immediately followed by delete becomes a noop. if (last.ld !== undefined) { // leave the delete part of the replace @@ -369,7 +468,12 @@ json.append = function(dest,c) { } else { dest.pop(); } - } else if (last.od !== undefined && last.oi === undefined && c.oi !== undefined && c.od === undefined) { + } else if ( + last.od !== undefined && + last.oi === undefined && + c.oi !== undefined && + c.od === undefined + ) { last.oi = c.oi; } else if (last.oi !== undefined && c.od !== undefined) { // The last path component inserted something that the new component deletes (or replaces). @@ -389,7 +493,10 @@ json.append = function(dest,c) { } } else { // convert string ops back - if ((c.si != null || c.sd != null) && (last.si != null || last.sd != null)) { + if ( + (c.si != null || c.sd != null) && + (last.si != null || last.sd != null) + ) { convertToText(c); convertToText(last); } @@ -398,14 +505,14 @@ json.append = function(dest,c) { } }; -json.compose = function(op1,op2) { +json.compose = function(op1, op2) { json.checkValidOp(op1); json.checkValidOp(op2); var newOp = clone(op1); for (var i = 0; i < op2.length; i++) { - json.append(newOp,op2[i]); + json.append(newOp, op2[i]); } return newOp; @@ -420,7 +527,7 @@ json.normalize = function(op) { var c = op[i]; if (c.p == null) c.p = []; - json.append(newOp,c); + json.append(newOp, c); } return newOp; @@ -430,11 +537,9 @@ json.normalize = function(op) { json.commonLengthForOps = function(a, b) { var alen = a.p.length; var blen = b.p.length; - if (a.na != null || a.t) - alen++; + if (a.na != null || a.t) alen++; - if (b.na != null || b.t) - blen++; + if (b.na != null || b.t) blen++; if (alen === 0) return -1; if (blen === 0) return null; @@ -444,8 +549,7 @@ json.commonLengthForOps = function(a, b) { for (var i = 0; i < alen; i++) { var p = a.p[i]; - if (i >= blen || p !== b.p[i]) - return null; + if (i >= blen || p !== b.p[i]) return null; } return alen; @@ -453,7 +557,7 @@ json.commonLengthForOps = function(a, b) { // Returns true if an op can affect the given path json.canOpAffectPath = function(op, path) { - return json.commonLengthForOps({p:path}, op) != null; + return json.commonLengthForOps({ p: path }, op) != null; }; // transform c so it applies to a document with otherC applied. @@ -465,23 +569,25 @@ json.transformComponent = function(dest, c, otherC, type) { var cplength = c.p.length; var otherCplength = otherC.p.length; - if (c.na != null || c.t) - cplength++; + if (c.na != null || c.t) cplength++; - if (otherC.na != null || otherC.t) - otherCplength++; + if (otherC.na != null || otherC.t) otherCplength++; // if c is deleting something, and that thing is changed by otherC, we need to // update c to reflect that change for invertibility. - if (common2 != null && otherCplength > cplength && c.p[common2] == otherC.p[common2]) { + if ( + common2 != null && + otherCplength > cplength && + c.p[common2] == otherC.p[common2] + ) { if (c.ld !== void 0) { var oc = clone(otherC); oc.p = oc.p.slice(cplength); - c.ld = json.apply(clone(c.ld),[oc]); + c.ld = json.apply(clone(c.ld), [oc]); } else if (c.od !== void 0) { var oc = clone(otherC); oc.p = oc.p.slice(cplength); - c.od = json.apply(clone(c.od),[oc]); + c.od = json.apply(clone(c.od), [oc]); } } @@ -490,7 +596,10 @@ json.transformComponent = function(dest, c, otherC, type) { // backward compatibility for old string ops var oc = otherC; - if ((c.si != null || c.sd != null) && (otherC.si != null || otherC.sd != null)) { + if ( + (c.si != null || c.sd != null) && + (otherC.si != null || otherC.sd != null) + ) { convertFromText(c); oc = clone(otherC); convertFromText(oc); @@ -539,10 +648,14 @@ json.transformComponent = function(dest, c, otherC, type) { } } } else if (otherC.li !== void 0) { - if (c.li !== void 0 && c.ld === undefined && commonOperand && c.p[common] === otherC.p[common]) { + if ( + c.li !== void 0 && + c.ld === undefined && + commonOperand && + c.p[common] === otherC.p[common] + ) { // in li vs. li, left wins. - if (type === 'right') - c.p[common]++; + if (type === 'right') c.p[common]++; } else if (otherC.p[common] <= c.p[common]) { c.p[common]++; } @@ -550,8 +663,7 @@ json.transformComponent = function(dest, c, otherC, type) { if (c.lm !== void 0) { if (commonOperand) { // otherC edits the same list we edit - if (otherC.p[common] <= c.lm) - c.lm++; + if (otherC.p[common] <= c.lm) c.lm++; // changing c.from is handled above. } } @@ -566,9 +678,7 @@ json.transformComponent = function(dest, c, otherC, type) { var p = otherC.p[common]; var from = c.p[common]; var to = c.lm; - if (p < to || (p === to && from < to)) - c.lm--; - + if (p < to || (p === to && from < to)) c.lm--; } } @@ -588,7 +698,6 @@ json.transformComponent = function(dest, c, otherC, type) { } } } - } else if (otherC.lm !== void 0) { if (c.lm !== void 0 && cplength === otherCplength) { // lm vs lm, here we go! @@ -604,7 +713,8 @@ json.transformComponent = function(dest, c, otherC, type) { // they moved it! tie break. if (type === 'left') { c.p[common] = otherTo; - if (from === to) // ugh + if (from === to) + // ugh c.lm = otherTo; } else { return dest; @@ -616,7 +726,8 @@ json.transformComponent = function(dest, c, otherC, type) { else if (from === otherTo) { if (otherFrom > otherTo) { c.p[common]++; - if (from === to) // ugh, again + if (from === to) + // ugh, again c.lm++; } } @@ -625,15 +736,16 @@ json.transformComponent = function(dest, c, otherC, type) { if (to > otherFrom) { c.lm--; } else if (to === otherFrom) { - if (to > from) - c.lm--; + if (to > from) c.lm--; } if (to > otherTo) { c.lm++; } else if (to === otherTo) { // if we're both moving in the same direction, tie break - if ((otherTo > otherFrom && to > from) || - (otherTo < otherFrom && to < from)) { + if ( + (otherTo > otherFrom && to > from) || + (otherTo < otherFrom && to < from) + ) { if (type === 'right') c.lm++; } else { if (to > from) c.lm++; @@ -665,8 +777,7 @@ json.transformComponent = function(dest, c, otherC, type) { else if (p === to && from > to) c.p[common]++; } } - } - else if (otherC.oi !== void 0 && otherC.od !== void 0) { + } else if (otherC.oi !== void 0 && otherC.od !== void 0) { if (c.p[common] === otherC.p[common]) { if (c.oi !== void 0 && commonOperand) { // we inserted where someone else replaced @@ -686,15 +797,14 @@ json.transformComponent = function(dest, c, otherC, type) { if (c.oi !== void 0 && c.p[common] === otherC.p[common]) { // left wins if we try to insert at the same place if (type === 'left') { - json.append(dest,{p: c.p, od:otherC.oi}); + json.append(dest, { p: c.p, od: otherC.oi }); } else { return dest; } } } else if (otherC.od !== void 0) { if (c.p[common] == otherC.p[common]) { - if (!commonOperand) - return dest; + if (!commonOperand) return dest; if (c.oi !== void 0) { delete c.od; } else { @@ -704,11 +814,16 @@ json.transformComponent = function(dest, c, otherC, type) { } } - json.append(dest,c); + json.append(dest, c); return dest; }; -require('./bootstrapTransform')(json, json.transformComponent, json.checkValidOp, json.append); +require('./bootstrapTransform')( + json, + json.transformComponent, + json.checkValidOp, + json.append +); /** * Register a subtype for string operations, using the text0 type. @@ -717,4 +832,3 @@ var text = require('./text0'); json.registerSubtype(text); module.exports = json; - From aadf1ef61f445cce872c94a039863d05e3c4e4d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stian=20H=C3=A5klev?= Date: Thu, 11 Apr 2019 13:52:34 +0200 Subject: [PATCH 06/44] WIP --- yarn.lock | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 yarn.lock diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..e13b328 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,172 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +cli-progress@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-2.1.1.tgz#45ee1b143487c19043a3262131ccb4676f87f032" + integrity sha512-TSJw3LY9ZRSis7yYzQ7flIdtQMbacd9oYoiFphJhI4SzgmqF0zErO+uNv0lbUjk1L4AGfHQJ4OVYYzW+JV66KA== + dependencies: + colors "^1.1.2" + string-width "^2.1.1" + +coffee-script@^1.7.1: + version "1.12.7" + resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.12.7.tgz#c05dae0cb79591d05b3070a8433a98c9a89ccc53" + integrity sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw== + +colors@^1.1.2: + version "1.3.3" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d" + integrity sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg== + +commander@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-0.6.1.tgz#fa68a14f6a945d54dbbe50d8cdb3320e9e3b1a06" + integrity sha1-+mihT2qUXVTbvlDYzbMyDp47GgY= + +commander@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.3.0.tgz#fd430e889832ec353b9acd1de217c11cb3eef873" + integrity sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM= + +debug@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.0.0.tgz#89bd9df6732b51256bc6705342bba02ed12131ef" + integrity sha1-ib2d9nMrUSVrxnBTQrugLtEhMe8= + dependencies: + ms "0.6.2" + +diff@1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/diff/-/diff-1.0.8.tgz#343276308ec991b7bc82267ed55bc1411f971666" + integrity sha1-NDJ2MI7Jkbe8giZ+1VvBQR+XFmY= + +escape-string-regexp@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz#4dbc2fe674e71949caf3fb2695ce7f2dc1d9a8d1" + integrity sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE= + +glob@3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-3.2.3.tgz#e313eeb249c7affaa5c475286b0e115b59839467" + integrity sha1-4xPusknHr/qlxHUoaw4RW1mDlGc= + dependencies: + graceful-fs "~2.0.0" + inherits "2" + minimatch "~0.2.11" + +graceful-fs@~2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-2.0.3.tgz#7cd2cdb228a4a3f36e95efa6cc142de7d1a136d0" + integrity sha1-fNLNsiiko/Nule+mzBQt59GhNtA= + +growl@1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.8.1.tgz#4b2dec8d907e93db336624dcec0183502f8c9428" + integrity sha1-Sy3sjZB+k9szZiTc7AGDUC+MlCg= + +inherits@2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +jade@0.26.3: + version "0.26.3" + resolved "https://registry.yarnpkg.com/jade/-/jade-0.26.3.tgz#8f10d7977d8d79f2f6ff862a81b0513ccb25686c" + integrity sha1-jxDXl32NefL2/4YqgbBRPMslaGw= + dependencies: + commander "0.6.1" + mkdirp "0.3.0" + +lru-cache@2: + version "2.7.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" + integrity sha1-bUUk6LlV+V1PW1iFHOId1y+06VI= + +minimatch@~0.2.11: + version "0.2.14" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.2.14.tgz#c74e780574f63c6f9a090e90efbe6ef53a6a756a" + integrity sha1-x054BXT2PG+aCQ6Q775u9TpqdWo= + dependencies: + lru-cache "2" + sigmund "~1.0.0" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +mkdirp@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e" + integrity sha1-G79asbqCevI1dRQ0kEJkVfSB/h4= + +mkdirp@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.0.tgz#1d73076a6df986cd9344e15e71fcc05a4c9abf12" + integrity sha1-HXMHam35hs2TROFecfzAWkyavxI= + dependencies: + minimist "0.0.8" + +mocha@^1.20.1: + version "1.21.5" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-1.21.5.tgz#7c58b09174df976e434a23b1e8d639873fc529e9" + integrity sha1-fFiwkXTfl25DSiOx6NY5hz/FKek= + dependencies: + commander "2.3.0" + debug "2.0.0" + diff "1.0.8" + escape-string-regexp "1.0.2" + glob "3.2.3" + growl "1.8.1" + jade "0.26.3" + mkdirp "0.5.0" + +ms@0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-0.6.2.tgz#d89c2124c6fdc1353d65a8b77bf1aac4b193708c" + integrity sha1-2JwhJMb9wTU9Zai3e/GqxLGTcIw= + +ot-fuzzer@^1.0.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ot-fuzzer/-/ot-fuzzer-1.2.1.tgz#41f70305fdd1d55268f6cc169c14c0eb9e5fb31c" + integrity sha512-dOm+Wb1Mqrw8ql5ksiZFf3Bdsruj5r4mHaa3COqtbg0ClkGjxZXwMGgMLjWslq/b0maeiw5yvYwIdH6As4svHg== + dependencies: + cli-progress "^2.1.1" + seedrandom "^2.4.4" + +seedrandom@^2.4.4: + version "2.4.4" + resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-2.4.4.tgz#b25ea98632c73e45f58b77cfaa931678df01f9ba" + integrity sha512-9A+PDmgm+2du77B5i0Ip2cxOqqHjgNxnBgglxLcX78A2D6c2rTo61z4jnVABpF4cKeDMDG+cmXXvdnqse2VqMA== + +sigmund@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" + integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA= + +string-width@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" From 957924ddf9a841bdae75c4c61195300632fb46ba Mon Sep 17 00:00:00 2001 From: curran Date: Fri, 12 Apr 2019 10:57:44 +0530 Subject: [PATCH 07/44] Remove minor formatting changes to clean up PR diff. --- lib/json0.js | 327 +++++++++++++++++++++++++-------------------------- 1 file changed, 162 insertions(+), 165 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index 36b21d9..d7bf987 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -1,8 +1,6 @@ /* This is the implementation of the JSON OT type. - Spec is here: https://github.com/josephg/ShareJS/wiki/JSON-Operations - Note: This is being made obsolete. It will soon be replaced by the JSON2 type. */ @@ -29,7 +27,7 @@ var isArray = function(obj) { * @returns {boolean} */ var isObject = function(obj) { - return !!obj && obj.constructor === Object; + return (!!obj) && (obj.constructor === Object); }; /** @@ -66,7 +64,7 @@ json.create = function(data) { }; json.invertComponent = function(c) { - var c_ = { p: c.p }; + var c_ = {p: c.p}; // handle subtype ops if (c.t && subtypes[c.t]) { @@ -83,8 +81,8 @@ json.invertComponent = function(c) { if (c.na !== void 0) c_.na = -c.na; if (c.lm !== void 0) { - c_.lm = c.p[c.p.length - 1]; - c_.p = c.p.slice(0, c.p.length - 1).concat([c.lm]); + c_.lm = c.p[c.p.length-1]; + c_.p = c.p.slice(0,c.p.length-1).concat([c.lm]); } return c_; @@ -106,21 +104,20 @@ json.checkValidOp = function(op) { }; json.checkList = function(elem) { - if (!isArray(elem)) throw new Error('Referenced element not a list'); + if (!isArray(elem)) + throw new Error('Referenced element not a list'); }; json.checkObj = function(elem) { if (!isObject(elem)) { - throw new Error( - 'Referenced element not an object (it was ' + JSON.stringify(elem) + ')' - ); + throw new Error("Referenced element not an object (it was " + JSON.stringify(elem) + ")"); } }; // helper functions to convert old string ops to and from subtype ops function convertFromText(c) { c.t = 'text0'; - var o = { p: c.p.pop() }; + var o = {p: c.p.pop()}; if (c.si != null) o.i = c.si; if (c.sd != null) o.d = c.sd; c.o = [o]; @@ -134,160 +131,6 @@ function convertToText(c) { delete c.o; } -// not checking anything here, we should probably check that u: exists -// (only thing we care about at json0 top level), and then delegate -// to any subtypes if there is already subtype presence data -json.createPresence = function(presence) { - return presence; -}; - -// this needs more thinking/testing, looking a bit more carefully at -// how this is implemented in ot-rich-text, etc. -json.comparePresence = function(pres1, pres2) { - if (!pres1 || !pres2) { - return false; - } - if (!pres1.p || !pres2.p) { - return false; - } - if (pres1.t !== pres2.t) { - return false; - } - if (pres1.t && subtypes[pres1.t]) { - if (pres1.p[0] === pres2.p[0]) { - return subtypes[pres1.t].comparePresence(pres1, pres2); - } - } else return pres1 === pres2; -}; - -var transformPosition = function(cursor, op, isOwnOp) { - var cursor = clone(cursor); - - var opIsAncestor = cursor.length >= op.p.length; // true also if op is self - var opIsSibling = cursor.length === op.p.length; // true also if op is self - var opIsAncestorSibling = cursor.length >= op.p.length; // true also if op is self or sibling of self - var equalUpTo = -1; - for (var i = 0; i < op.p.length; i++) { - if (op.p[i] !== cursor[i]) { - opIsAncestor = false; - if (i < op.p.length - 1) { - opIsSibling = false; - opIsAncestorSibling = false; - } - } - if (equalUpTo === i - 1 && op.p[i] === cursor[i]) { - equalUpTo += 1; - } - } - - if (opIsSibling) { - if (op.sd) { - cursor[cursor.length - 1] = text.transformCursor( - cursor[cursor.length - 1], - [{ p: op.p[op.p.length - 1], d: op.sd }], - isOwnOp ? 'right' : 'left' - ); - } - if (op.si) { - cursor[cursor.length - 1] = text.transformCursor( - cursor[cursor.length - 1], - [{ p: op.p[op.p.length - 1], i: op.si }], - isOwnOp ? 'right' : 'left' - ); - } - } - - if (opIsAncestor) { - if (op.lm !== undefined) { - cursor[equalUpTo] = op.lm; - } - if (op.od && op.oi) { - cursor = op.p.slice(0, op.p.length); - } else if (op.od) { - cursor = op.p.slice(0, op.p.length - 1); - } else if (op.ld && op.li) { - cursor = op.p.slice(0, op.p.length); - } else if (op.ld) { - cursor = op.p.slice(0, op.p.length - 1); - } - } - - if (opIsAncestorSibling) { - var lastPathIdx = op.p.length - 1; - if ( - !opIsAncestor && - op.ld && - !op.li && - op.p[lastPathIdx] < cursor[lastPathIdx] - ) { - cursor[lastPathIdx] -= 1; - } else if (!op.ld && op.li && op.p[lastPathIdx] <= cursor[lastPathIdx]) { - cursor[lastPathIdx] += 1; - } - - // if move item in list from after to before - if ( - !opIsAncestor && - op.lm !== undefined && - op.p[lastPathIdx] > cursor[lastPathIdx] && - op.lm <= cursor[lastPathIdx] - ) { - cursor[lastPathIdx] += 1; - // if move item in list from before to after - } else if ( - !opIsAncestor && - op.lm !== undefined && - op.p[lastPathIdx] < cursor[lastPathIdx] && - op.lm >= cursor[lastPathIdx] - ) { - cursor[lastPathIdx] -= 1; - } - } - - return cursor; -}; - -json.transformCursor = function(cursor, op, isOwnOp) { - for (var i = 0; i < op.length; i++) { - cursor = transformPosition(cursor, op[i], isOwnOp); - } - return cursor; -}; - -// this is the key function, always run client-side, both on -// the client that creates a text-change, and on the clients -// that receive text-changes (ops). if there are no ops, just -// return presence, if there are ops, delegate to the subtype -// responsible for those ops (currently only ot-rich-text). -// I am making assumptions many places that all ops will be -// of the same subtype, not sure if this is a given. -// We're only concerned about the first level of object/array, -// not sure if the spec allows nesting of subtypes. -json.transformPresence = function(presence, ops, isOwn) { - if (op.length < 1) { - return presence; - } - ops.forEach(op => { - const opType = op.t; - const path = op.p && op.p[0]; - if (opType && subtypes[opType] && path) { - // No path given, or path does not match path in presence -- - // no need to transform path since no conflict - if (!presence.p || !presence.p[0] || presence.p[0] !== path) { - return presence; - } - // return result of running the subtype's transformPresence, - // but add path and type, which the subtype will not include - presence.cursor = subtypes[opType].transformCursor( - presence, - op, - isOwn - ).cursor; - } - }); - return presence; -}; - json.apply = function(snapshot, op) { json.checkValidOp(op); @@ -818,6 +661,160 @@ json.transformComponent = function(dest, c, otherC, type) { return dest; }; +// not checking anything here, we should probably check that u: exists +// (only thing we care about at json0 top level), and then delegate +// to any subtypes if there is already subtype presence data +json.createPresence = function(presence) { + return presence; +}; + +// this needs more thinking/testing, looking a bit more carefully at +// how this is implemented in ot-rich-text, etc. +json.comparePresence = function(pres1, pres2) { + if (!pres1 || !pres2) { + return false; + } + if (!pres1.p || !pres2.p) { + return false; + } + if (pres1.t !== pres2.t) { + return false; + } + if (pres1.t && subtypes[pres1.t]) { + if (pres1.p[0] === pres2.p[0]) { + return subtypes[pres1.t].comparePresence(pres1, pres2); + } + } else return pres1 === pres2; +}; + +var transformPosition = function(cursor, op, isOwnOp) { + var cursor = clone(cursor); + + var opIsAncestor = cursor.length >= op.p.length; // true also if op is self + var opIsSibling = cursor.length === op.p.length; // true also if op is self + var opIsAncestorSibling = cursor.length >= op.p.length; // true also if op is self or sibling of self + var equalUpTo = -1; + for (var i = 0; i < op.p.length; i++) { + if (op.p[i] !== cursor[i]) { + opIsAncestor = false; + if (i < op.p.length - 1) { + opIsSibling = false; + opIsAncestorSibling = false; + } + } + if (equalUpTo === i - 1 && op.p[i] === cursor[i]) { + equalUpTo += 1; + } + } + + if (opIsSibling) { + if (op.sd) { + cursor[cursor.length - 1] = text.transformCursor( + cursor[cursor.length - 1], + [{ p: op.p[op.p.length - 1], d: op.sd }], + isOwnOp ? 'right' : 'left' + ); + } + if (op.si) { + cursor[cursor.length - 1] = text.transformCursor( + cursor[cursor.length - 1], + [{ p: op.p[op.p.length - 1], i: op.si }], + isOwnOp ? 'right' : 'left' + ); + } + } + + if (opIsAncestor) { + if (op.lm !== undefined) { + cursor[equalUpTo] = op.lm; + } + if (op.od && op.oi) { + cursor = op.p.slice(0, op.p.length); + } else if (op.od) { + cursor = op.p.slice(0, op.p.length - 1); + } else if (op.ld && op.li) { + cursor = op.p.slice(0, op.p.length); + } else if (op.ld) { + cursor = op.p.slice(0, op.p.length - 1); + } + } + + if (opIsAncestorSibling) { + var lastPathIdx = op.p.length - 1; + if ( + !opIsAncestor && + op.ld && + !op.li && + op.p[lastPathIdx] < cursor[lastPathIdx] + ) { + cursor[lastPathIdx] -= 1; + } else if (!op.ld && op.li && op.p[lastPathIdx] <= cursor[lastPathIdx]) { + cursor[lastPathIdx] += 1; + } + + // if move item in list from after to before + if ( + !opIsAncestor && + op.lm !== undefined && + op.p[lastPathIdx] > cursor[lastPathIdx] && + op.lm <= cursor[lastPathIdx] + ) { + cursor[lastPathIdx] += 1; + // if move item in list from before to after + } else if ( + !opIsAncestor && + op.lm !== undefined && + op.p[lastPathIdx] < cursor[lastPathIdx] && + op.lm >= cursor[lastPathIdx] + ) { + cursor[lastPathIdx] -= 1; + } + } + + return cursor; +}; + +json.transformCursor = function(cursor, op, isOwnOp) { + for (var i = 0; i < op.length; i++) { + cursor = transformPosition(cursor, op[i], isOwnOp); + } + return cursor; +}; + +// this is the key function, always run client-side, both on +// the client that creates a text-change, and on the clients +// that receive text-changes (ops). if there are no ops, just +// return presence, if there are ops, delegate to the subtype +// responsible for those ops (currently only ot-rich-text). +// I am making assumptions many places that all ops will be +// of the same subtype, not sure if this is a given. +// We're only concerned about the first level of object/array, +// not sure if the spec allows nesting of subtypes. +json.transformPresence = function(presence, ops, isOwn) { + if (op.length < 1) { + return presence; + } + ops.forEach(op => { + const opType = op.t; + const path = op.p && op.p[0]; + if (opType && subtypes[opType] && path) { + // No path given, or path does not match path in presence -- + // no need to transform path since no conflict + if (!presence.p || !presence.p[0] || presence.p[0] !== path) { + return presence; + } + // return result of running the subtype's transformPresence, + // but add path and type, which the subtype will not include + presence.cursor = subtypes[opType].transformCursor( + presence, + op, + isOwn + ).cursor; + } + }); + return presence; +}; + require('./bootstrapTransform')( json, json.transformComponent, From ca187aabb0d21a5ea9d11326bdc153eae90f5599 Mon Sep 17 00:00:00 2001 From: curran Date: Fri, 12 Apr 2019 11:00:53 +0530 Subject: [PATCH 08/44] Remove minor formatting changes to clean up PR diff. --- lib/json0.js | 138 +++++++++++++++++++++++---------------------------- 1 file changed, 63 insertions(+), 75 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index d7bf987..08a4c21 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -144,7 +144,8 @@ json.apply = function(snapshot, op) { var c = op[i]; // convert old string ops to use subtype for backwards compatibility - if (c.si != null || c.sd != null) convertFromText(c); + if (c.si != null || c.sd != null) + convertFromText(c); var parent = null; var parentKey = null; @@ -159,14 +160,15 @@ json.apply = function(snapshot, op) { elem = elem[key]; key = p; - if (parent == null) throw new Error('Path invalid'); + if (parent == null) + throw new Error('Path invalid'); } // handle subtype ops if (c.t && c.o !== void 0 && subtypes[c.t]) { elem[key] = subtypes[c.t].apply(elem[key], c.o); - // Number add + // Number add } else if (c.na !== void 0) { if (typeof elem[key] != 'number') throw new Error('Referenced element not a number'); @@ -184,14 +186,14 @@ json.apply = function(snapshot, op) { // List insert else if (c.li !== void 0) { json.checkList(elem); - elem.splice(key, 0, c.li); + elem.splice(key,0, c.li); } // List delete else if (c.ld !== void 0) { json.checkList(elem); // Should check the list element matches c.ld here too. - elem.splice(key, 1); + elem.splice(key,1); } // List move @@ -200,9 +202,9 @@ json.apply = function(snapshot, op) { if (c.lm != key) { var e = elem[key]; // Remove it... - elem.splice(key, 1); + elem.splice(key,1); // And insert it back. - elem.splice(c.lm, 0, e); + elem.splice(c.lm,0,e); } } @@ -220,7 +222,9 @@ json.apply = function(snapshot, op) { // Should check that elem[key] == c.od delete elem[key]; - } else { + } + + else { throw new Error('invalid / missing instruction in op'); } } @@ -251,17 +255,19 @@ json.incrementalApply = function(snapshot, op, _yield) { }; // Checks if two paths, p1 and p2 match. -var pathMatches = (json.pathMatches = function(p1, p2, ignoreLast) { - if (p1.length != p2.length) return false; +var pathMatches = json.pathMatches = function(p1, p2, ignoreLast) { + if (p1.length != p2.length) + return false; for (var i = 0; i < p1.length; i++) { - if (p1[i] !== p2[i] && (!ignoreLast || i !== p1.length - 1)) return false; + if (p1[i] !== p2[i] && (!ignoreLast || i !== p1.length - 1)) + return false; } return true; -}); +}; -json.append = function(dest, c) { +json.append = function(dest,c) { c = clone(c); if (dest.length === 0) { @@ -280,10 +286,7 @@ json.append = function(dest, c) { if (pathMatches(c.p, last.p)) { // handle subtype ops if (c.t && last.t && c.t === last.t && subtypes[c.t]) { - last.o = subtypes[c.t].compose( - last.o, - c.o - ); + last.o = subtypes[c.t].compose(last.o, c.o); // convert back to old string ops if (c.si != null || c.sd != null) { @@ -298,12 +301,8 @@ json.append = function(dest, c) { convertToText(last); } } else if (last.na != null && c.na != null) { - dest[dest.length - 1] = { p: last.p, na: last.na + c.na }; - } else if ( - last.li !== undefined && - c.li === undefined && - c.ld === last.li - ) { + dest[dest.length - 1] = {p: last.p, na: last.na + c.na}; + } else if (last.li !== undefined && c.li === undefined && c.ld === last.li) { // insert immediately followed by delete becomes a noop. if (last.ld !== undefined) { // leave the delete part of the replace @@ -311,12 +310,7 @@ json.append = function(dest, c) { } else { dest.pop(); } - } else if ( - last.od !== undefined && - last.oi === undefined && - c.oi !== undefined && - c.od === undefined - ) { + } else if (last.od !== undefined && last.oi === undefined && c.oi !== undefined && c.od === undefined) { last.oi = c.oi; } else if (last.oi !== undefined && c.od !== undefined) { // The last path component inserted something that the new component deletes (or replaces). @@ -336,10 +330,7 @@ json.append = function(dest, c) { } } else { // convert string ops back - if ( - (c.si != null || c.sd != null) && - (last.si != null || last.sd != null) - ) { + if ((c.si != null || c.sd != null) && (last.si != null || last.sd != null)) { convertToText(c); convertToText(last); } @@ -348,14 +339,14 @@ json.append = function(dest, c) { } }; -json.compose = function(op1, op2) { +json.compose = function(op1,op2) { json.checkValidOp(op1); json.checkValidOp(op2); var newOp = clone(op1); for (var i = 0; i < op2.length; i++) { - json.append(newOp, op2[i]); + json.append(newOp,op2[i]); } return newOp; @@ -370,7 +361,7 @@ json.normalize = function(op) { var c = op[i]; if (c.p == null) c.p = []; - json.append(newOp, c); + json.append(newOp,c); } return newOp; @@ -380,9 +371,11 @@ json.normalize = function(op) { json.commonLengthForOps = function(a, b) { var alen = a.p.length; var blen = b.p.length; - if (a.na != null || a.t) alen++; + if (a.na != null || a.t) + alen++; - if (b.na != null || b.t) blen++; + if (b.na != null || b.t) + blen++; if (alen === 0) return -1; if (blen === 0) return null; @@ -392,7 +385,8 @@ json.commonLengthForOps = function(a, b) { for (var i = 0; i < alen; i++) { var p = a.p[i]; - if (i >= blen || p !== b.p[i]) return null; + if (i >= blen || p !== b.p[i]) + return null; } return alen; @@ -400,7 +394,7 @@ json.commonLengthForOps = function(a, b) { // Returns true if an op can affect the given path json.canOpAffectPath = function(op, path) { - return json.commonLengthForOps({ p: path }, op) != null; + return json.commonLengthForOps({p:path}, op) != null; }; // transform c so it applies to a document with otherC applied. @@ -412,25 +406,23 @@ json.transformComponent = function(dest, c, otherC, type) { var cplength = c.p.length; var otherCplength = otherC.p.length; - if (c.na != null || c.t) cplength++; + if (c.na != null || c.t) + cplength++; - if (otherC.na != null || otherC.t) otherCplength++; + if (otherC.na != null || otherC.t) + otherCplength++; // if c is deleting something, and that thing is changed by otherC, we need to // update c to reflect that change for invertibility. - if ( - common2 != null && - otherCplength > cplength && - c.p[common2] == otherC.p[common2] - ) { + if (common2 != null && otherCplength > cplength && c.p[common2] == otherC.p[common2]) { if (c.ld !== void 0) { var oc = clone(otherC); oc.p = oc.p.slice(cplength); - c.ld = json.apply(clone(c.ld), [oc]); + c.ld = json.apply(clone(c.ld),[oc]); } else if (c.od !== void 0) { var oc = clone(otherC); oc.p = oc.p.slice(cplength); - c.od = json.apply(clone(c.od), [oc]); + c.od = json.apply(clone(c.od),[oc]); } } @@ -439,10 +431,7 @@ json.transformComponent = function(dest, c, otherC, type) { // backward compatibility for old string ops var oc = otherC; - if ( - (c.si != null || c.sd != null) && - (otherC.si != null || otherC.sd != null) - ) { + if ((c.si != null || c.sd != null) && (otherC.si != null || otherC.sd != null)) { convertFromText(c); oc = clone(otherC); convertFromText(oc); @@ -491,14 +480,10 @@ json.transformComponent = function(dest, c, otherC, type) { } } } else if (otherC.li !== void 0) { - if ( - c.li !== void 0 && - c.ld === undefined && - commonOperand && - c.p[common] === otherC.p[common] - ) { + if (c.li !== void 0 && c.ld === undefined && commonOperand && c.p[common] === otherC.p[common]) { // in li vs. li, left wins. - if (type === 'right') c.p[common]++; + if (type === 'right') + c.p[common]++; } else if (otherC.p[common] <= c.p[common]) { c.p[common]++; } @@ -506,7 +491,8 @@ json.transformComponent = function(dest, c, otherC, type) { if (c.lm !== void 0) { if (commonOperand) { // otherC edits the same list we edit - if (otherC.p[common] <= c.lm) c.lm++; + if (otherC.p[common] <= c.lm) + c.lm++; // changing c.from is handled above. } } @@ -521,7 +507,9 @@ json.transformComponent = function(dest, c, otherC, type) { var p = otherC.p[common]; var from = c.p[common]; var to = c.lm; - if (p < to || (p === to && from < to)) c.lm--; + if (p < to || (p === to && from < to)) + c.lm--; + } } @@ -541,6 +529,7 @@ json.transformComponent = function(dest, c, otherC, type) { } } } + } else if (otherC.lm !== void 0) { if (c.lm !== void 0 && cplength === otherCplength) { // lm vs lm, here we go! @@ -556,8 +545,7 @@ json.transformComponent = function(dest, c, otherC, type) { // they moved it! tie break. if (type === 'left') { c.p[common] = otherTo; - if (from === to) - // ugh + if (from === to) // ugh c.lm = otherTo; } else { return dest; @@ -569,8 +557,7 @@ json.transformComponent = function(dest, c, otherC, type) { else if (from === otherTo) { if (otherFrom > otherTo) { c.p[common]++; - if (from === to) - // ugh, again + if (from === to) // ugh, again c.lm++; } } @@ -579,16 +566,15 @@ json.transformComponent = function(dest, c, otherC, type) { if (to > otherFrom) { c.lm--; } else if (to === otherFrom) { - if (to > from) c.lm--; + if (to > from) + c.lm--; } if (to > otherTo) { c.lm++; } else if (to === otherTo) { // if we're both moving in the same direction, tie break - if ( - (otherTo > otherFrom && to > from) || - (otherTo < otherFrom && to < from) - ) { + if ((otherTo > otherFrom && to > from) || + (otherTo < otherFrom && to < from)) { if (type === 'right') c.lm++; } else { if (to > from) c.lm++; @@ -620,7 +606,8 @@ json.transformComponent = function(dest, c, otherC, type) { else if (p === to && from > to) c.p[common]++; } } - } else if (otherC.oi !== void 0 && otherC.od !== void 0) { + } + else if (otherC.oi !== void 0 && otherC.od !== void 0) { if (c.p[common] === otherC.p[common]) { if (c.oi !== void 0 && commonOperand) { // we inserted where someone else replaced @@ -640,14 +627,15 @@ json.transformComponent = function(dest, c, otherC, type) { if (c.oi !== void 0 && c.p[common] === otherC.p[common]) { // left wins if we try to insert at the same place if (type === 'left') { - json.append(dest, { p: c.p, od: otherC.oi }); + json.append(dest,{p: c.p, od:otherC.oi}); } else { return dest; } } } else if (otherC.od !== void 0) { if (c.p[common] == otherC.p[common]) { - if (!commonOperand) return dest; + if (!commonOperand) + return dest; if (c.oi !== void 0) { delete c.od; } else { @@ -657,7 +645,7 @@ json.transformComponent = function(dest, c, otherC, type) { } } - json.append(dest, c); + json.append(dest,c); return dest; }; From 44ca0a0410d099d526287ea8ec013d3c0e781320 Mon Sep 17 00:00:00 2001 From: curran Date: Fri, 12 Apr 2019 11:02:41 +0530 Subject: [PATCH 09/44] Remove minor formatting changes to clean up PR diff. --- lib/json0.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index 08a4c21..7e3d3a5 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -1,6 +1,8 @@ /* This is the implementation of the JSON OT type. + Spec is here: https://github.com/josephg/ShareJS/wiki/JSON-Operations + Note: This is being made obsolete. It will soon be replaced by the JSON2 type. */ @@ -803,12 +805,7 @@ json.transformPresence = function(presence, ops, isOwn) { return presence; }; -require('./bootstrapTransform')( - json, - json.transformComponent, - json.checkValidOp, - json.append -); +require('./bootstrapTransform')(json, json.transformComponent, json.checkValidOp, json.append); /** * Register a subtype for string operations, using the text0 type. From 1b128116dab14a084558d057594103af724d1346 Mon Sep 17 00:00:00 2001 From: curran Date: Fri, 12 Apr 2019 11:31:07 +0530 Subject: [PATCH 10/44] Restore original package.json --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index c7d133e..b6c9df6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "@minervaproject/ot-json0", - "version": "1.3.0", - "description": "JSON OT type, fork of sharedb/ot-json0@1.2.0", + "name": "ot-json0", + "version": "1.1.0", + "description": "JSON OT type", "main": "lib/index.js", "directories": { "test": "test" @@ -17,7 +17,7 @@ }, "repository": { "type": "git", - "url": "git://github.com/houshuang/json0" + "url": "git://github.com/ottypes/json0" }, "keywords": [ "ot", From d7975516d9a17f232b5d2b95d18d4a368891b239 Mon Sep 17 00:00:00 2001 From: curran Date: Fri, 12 Apr 2019 11:31:36 +0530 Subject: [PATCH 11/44] Remove yarn.lock (as this was not already there in json0) --- yarn.lock | 172 ------------------------------------------------------ 1 file changed, 172 deletions(-) delete mode 100644 yarn.lock diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index e13b328..0000000 --- a/yarn.lock +++ /dev/null @@ -1,172 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - -cli-progress@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-2.1.1.tgz#45ee1b143487c19043a3262131ccb4676f87f032" - integrity sha512-TSJw3LY9ZRSis7yYzQ7flIdtQMbacd9oYoiFphJhI4SzgmqF0zErO+uNv0lbUjk1L4AGfHQJ4OVYYzW+JV66KA== - dependencies: - colors "^1.1.2" - string-width "^2.1.1" - -coffee-script@^1.7.1: - version "1.12.7" - resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.12.7.tgz#c05dae0cb79591d05b3070a8433a98c9a89ccc53" - integrity sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw== - -colors@^1.1.2: - version "1.3.3" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d" - integrity sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg== - -commander@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-0.6.1.tgz#fa68a14f6a945d54dbbe50d8cdb3320e9e3b1a06" - integrity sha1-+mihT2qUXVTbvlDYzbMyDp47GgY= - -commander@2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.3.0.tgz#fd430e889832ec353b9acd1de217c11cb3eef873" - integrity sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM= - -debug@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.0.0.tgz#89bd9df6732b51256bc6705342bba02ed12131ef" - integrity sha1-ib2d9nMrUSVrxnBTQrugLtEhMe8= - dependencies: - ms "0.6.2" - -diff@1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/diff/-/diff-1.0.8.tgz#343276308ec991b7bc82267ed55bc1411f971666" - integrity sha1-NDJ2MI7Jkbe8giZ+1VvBQR+XFmY= - -escape-string-regexp@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz#4dbc2fe674e71949caf3fb2695ce7f2dc1d9a8d1" - integrity sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE= - -glob@3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-3.2.3.tgz#e313eeb249c7affaa5c475286b0e115b59839467" - integrity sha1-4xPusknHr/qlxHUoaw4RW1mDlGc= - dependencies: - graceful-fs "~2.0.0" - inherits "2" - minimatch "~0.2.11" - -graceful-fs@~2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-2.0.3.tgz#7cd2cdb228a4a3f36e95efa6cc142de7d1a136d0" - integrity sha1-fNLNsiiko/Nule+mzBQt59GhNtA= - -growl@1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.8.1.tgz#4b2dec8d907e93db336624dcec0183502f8c9428" - integrity sha1-Sy3sjZB+k9szZiTc7AGDUC+MlCg= - -inherits@2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - -jade@0.26.3: - version "0.26.3" - resolved "https://registry.yarnpkg.com/jade/-/jade-0.26.3.tgz#8f10d7977d8d79f2f6ff862a81b0513ccb25686c" - integrity sha1-jxDXl32NefL2/4YqgbBRPMslaGw= - dependencies: - commander "0.6.1" - mkdirp "0.3.0" - -lru-cache@2: - version "2.7.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" - integrity sha1-bUUk6LlV+V1PW1iFHOId1y+06VI= - -minimatch@~0.2.11: - version "0.2.14" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.2.14.tgz#c74e780574f63c6f9a090e90efbe6ef53a6a756a" - integrity sha1-x054BXT2PG+aCQ6Q775u9TpqdWo= - dependencies: - lru-cache "2" - sigmund "~1.0.0" - -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= - -mkdirp@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e" - integrity sha1-G79asbqCevI1dRQ0kEJkVfSB/h4= - -mkdirp@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.0.tgz#1d73076a6df986cd9344e15e71fcc05a4c9abf12" - integrity sha1-HXMHam35hs2TROFecfzAWkyavxI= - dependencies: - minimist "0.0.8" - -mocha@^1.20.1: - version "1.21.5" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-1.21.5.tgz#7c58b09174df976e434a23b1e8d639873fc529e9" - integrity sha1-fFiwkXTfl25DSiOx6NY5hz/FKek= - dependencies: - commander "2.3.0" - debug "2.0.0" - diff "1.0.8" - escape-string-regexp "1.0.2" - glob "3.2.3" - growl "1.8.1" - jade "0.26.3" - mkdirp "0.5.0" - -ms@0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-0.6.2.tgz#d89c2124c6fdc1353d65a8b77bf1aac4b193708c" - integrity sha1-2JwhJMb9wTU9Zai3e/GqxLGTcIw= - -ot-fuzzer@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ot-fuzzer/-/ot-fuzzer-1.2.1.tgz#41f70305fdd1d55268f6cc169c14c0eb9e5fb31c" - integrity sha512-dOm+Wb1Mqrw8ql5ksiZFf3Bdsruj5r4mHaa3COqtbg0ClkGjxZXwMGgMLjWslq/b0maeiw5yvYwIdH6As4svHg== - dependencies: - cli-progress "^2.1.1" - seedrandom "^2.4.4" - -seedrandom@^2.4.4: - version "2.4.4" - resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-2.4.4.tgz#b25ea98632c73e45f58b77cfaa931678df01f9ba" - integrity sha512-9A+PDmgm+2du77B5i0Ip2cxOqqHjgNxnBgglxLcX78A2D6c2rTo61z4jnVABpF4cKeDMDG+cmXXvdnqse2VqMA== - -sigmund@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" - integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA= - -string-width@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" From 62f6acd9e9495d8a92429af1313f56086295e49d Mon Sep 17 00:00:00 2001 From: curran Date: Fri, 12 Apr 2019 11:48:49 +0530 Subject: [PATCH 12/44] Fix undefined reference --- lib/json0.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/json0.js b/lib/json0.js index 7e3d3a5..7f990ac 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -655,6 +655,7 @@ json.transformComponent = function(dest, c, otherC, type) { // (only thing we care about at json0 top level), and then delegate // to any subtypes if there is already subtype presence data json.createPresence = function(presence) { + console.log('create presence;'); return presence; }; @@ -781,7 +782,8 @@ json.transformCursor = function(cursor, op, isOwnOp) { // We're only concerned about the first level of object/array, // not sure if the spec allows nesting of subtypes. json.transformPresence = function(presence, ops, isOwn) { - if (op.length < 1) { + console.log('transform presence;'); + if (ops.length < 1) { return presence; } ops.forEach(op => { From bf02b8cfdce7db758cf9fc2e2464ac4461d6cfec Mon Sep 17 00:00:00 2001 From: curran Date: Fri, 12 Apr 2019 11:50:21 +0530 Subject: [PATCH 13/44] Comment unused code --- lib/json0.js | 186 +++++++++++++++++++++++++-------------------------- 1 file changed, 93 insertions(+), 93 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index 7f990ac..3df7973 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -678,99 +678,99 @@ json.comparePresence = function(pres1, pres2) { } else return pres1 === pres2; }; -var transformPosition = function(cursor, op, isOwnOp) { - var cursor = clone(cursor); - - var opIsAncestor = cursor.length >= op.p.length; // true also if op is self - var opIsSibling = cursor.length === op.p.length; // true also if op is self - var opIsAncestorSibling = cursor.length >= op.p.length; // true also if op is self or sibling of self - var equalUpTo = -1; - for (var i = 0; i < op.p.length; i++) { - if (op.p[i] !== cursor[i]) { - opIsAncestor = false; - if (i < op.p.length - 1) { - opIsSibling = false; - opIsAncestorSibling = false; - } - } - if (equalUpTo === i - 1 && op.p[i] === cursor[i]) { - equalUpTo += 1; - } - } - - if (opIsSibling) { - if (op.sd) { - cursor[cursor.length - 1] = text.transformCursor( - cursor[cursor.length - 1], - [{ p: op.p[op.p.length - 1], d: op.sd }], - isOwnOp ? 'right' : 'left' - ); - } - if (op.si) { - cursor[cursor.length - 1] = text.transformCursor( - cursor[cursor.length - 1], - [{ p: op.p[op.p.length - 1], i: op.si }], - isOwnOp ? 'right' : 'left' - ); - } - } - - if (opIsAncestor) { - if (op.lm !== undefined) { - cursor[equalUpTo] = op.lm; - } - if (op.od && op.oi) { - cursor = op.p.slice(0, op.p.length); - } else if (op.od) { - cursor = op.p.slice(0, op.p.length - 1); - } else if (op.ld && op.li) { - cursor = op.p.slice(0, op.p.length); - } else if (op.ld) { - cursor = op.p.slice(0, op.p.length - 1); - } - } - - if (opIsAncestorSibling) { - var lastPathIdx = op.p.length - 1; - if ( - !opIsAncestor && - op.ld && - !op.li && - op.p[lastPathIdx] < cursor[lastPathIdx] - ) { - cursor[lastPathIdx] -= 1; - } else if (!op.ld && op.li && op.p[lastPathIdx] <= cursor[lastPathIdx]) { - cursor[lastPathIdx] += 1; - } - - // if move item in list from after to before - if ( - !opIsAncestor && - op.lm !== undefined && - op.p[lastPathIdx] > cursor[lastPathIdx] && - op.lm <= cursor[lastPathIdx] - ) { - cursor[lastPathIdx] += 1; - // if move item in list from before to after - } else if ( - !opIsAncestor && - op.lm !== undefined && - op.p[lastPathIdx] < cursor[lastPathIdx] && - op.lm >= cursor[lastPathIdx] - ) { - cursor[lastPathIdx] -= 1; - } - } - - return cursor; -}; - -json.transformCursor = function(cursor, op, isOwnOp) { - for (var i = 0; i < op.length; i++) { - cursor = transformPosition(cursor, op[i], isOwnOp); - } - return cursor; -}; +// var transformPosition = function(cursor, op, isOwnOp) { +// var cursor = clone(cursor); +// +// var opIsAncestor = cursor.length >= op.p.length; // true also if op is self +// var opIsSibling = cursor.length === op.p.length; // true also if op is self +// var opIsAncestorSibling = cursor.length >= op.p.length; // true also if op is self or sibling of self +// var equalUpTo = -1; +// for (var i = 0; i < op.p.length; i++) { +// if (op.p[i] !== cursor[i]) { +// opIsAncestor = false; +// if (i < op.p.length - 1) { +// opIsSibling = false; +// opIsAncestorSibling = false; +// } +// } +// if (equalUpTo === i - 1 && op.p[i] === cursor[i]) { +// equalUpTo += 1; +// } +// } +// +// if (opIsSibling) { +// if (op.sd) { +// cursor[cursor.length - 1] = text.transformCursor( +// cursor[cursor.length - 1], +// [{ p: op.p[op.p.length - 1], d: op.sd }], +// isOwnOp ? 'right' : 'left' +// ); +// } +// if (op.si) { +// cursor[cursor.length - 1] = text.transformCursor( +// cursor[cursor.length - 1], +// [{ p: op.p[op.p.length - 1], i: op.si }], +// isOwnOp ? 'right' : 'left' +// ); +// } +// } +// +// if (opIsAncestor) { +// if (op.lm !== undefined) { +// cursor[equalUpTo] = op.lm; +// } +// if (op.od && op.oi) { +// cursor = op.p.slice(0, op.p.length); +// } else if (op.od) { +// cursor = op.p.slice(0, op.p.length - 1); +// } else if (op.ld && op.li) { +// cursor = op.p.slice(0, op.p.length); +// } else if (op.ld) { +// cursor = op.p.slice(0, op.p.length - 1); +// } +// } +// +// if (opIsAncestorSibling) { +// var lastPathIdx = op.p.length - 1; +// if ( +// !opIsAncestor && +// op.ld && +// !op.li && +// op.p[lastPathIdx] < cursor[lastPathIdx] +// ) { +// cursor[lastPathIdx] -= 1; +// } else if (!op.ld && op.li && op.p[lastPathIdx] <= cursor[lastPathIdx]) { +// cursor[lastPathIdx] += 1; +// } +// +// // if move item in list from after to before +// if ( +// !opIsAncestor && +// op.lm !== undefined && +// op.p[lastPathIdx] > cursor[lastPathIdx] && +// op.lm <= cursor[lastPathIdx] +// ) { +// cursor[lastPathIdx] += 1; +// // if move item in list from before to after +// } else if ( +// !opIsAncestor && +// op.lm !== undefined && +// op.p[lastPathIdx] < cursor[lastPathIdx] && +// op.lm >= cursor[lastPathIdx] +// ) { +// cursor[lastPathIdx] -= 1; +// } +// } +// +// return cursor; +// }; +// +// json.transformCursor = function(cursor, op, isOwnOp) { +// for (var i = 0; i < op.length; i++) { +// cursor = transformPosition(cursor, op[i], isOwnOp); +// } +// return cursor; +// }; // this is the key function, always run client-side, both on // the client that creates a text-change, and on the clients From 73adc914a26f214abe9ce1a51f939ca8bee81fc0 Mon Sep 17 00:00:00 2001 From: curran Date: Fri, 12 Apr 2019 11:54:02 +0530 Subject: [PATCH 14/44] Simplify comparePresence --- lib/json0.js | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index 3df7973..21fc241 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -659,23 +659,8 @@ json.createPresence = function(presence) { return presence; }; -// this needs more thinking/testing, looking a bit more carefully at -// how this is implemented in ot-rich-text, etc. json.comparePresence = function(pres1, pres2) { - if (!pres1 || !pres2) { - return false; - } - if (!pres1.p || !pres2.p) { - return false; - } - if (pres1.t !== pres2.t) { - return false; - } - if (pres1.t && subtypes[pres1.t]) { - if (pres1.p[0] === pres2.p[0]) { - return subtypes[pres1.t].comparePresence(pres1, pres2); - } - } else return pres1 === pres2; + return JSON.stringify(pres1) === JSON.stringify(pres2); }; // var transformPosition = function(cursor, op, isOwnOp) { From 095967e3720b40dd21745d6e41a45575010a3db7 Mon Sep 17 00:00:00 2001 From: curran Date: Fri, 12 Apr 2019 12:02:57 +0530 Subject: [PATCH 15/44] Simplify transformPresence --- lib/json0.js | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index 21fc241..4b32f47 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -655,7 +655,6 @@ json.transformComponent = function(dest, c, otherC, type) { // (only thing we care about at json0 top level), and then delegate // to any subtypes if there is already subtype presence data json.createPresence = function(presence) { - console.log('create presence;'); return presence; }; @@ -757,23 +756,15 @@ json.comparePresence = function(pres1, pres2) { // return cursor; // }; -// this is the key function, always run client-side, both on -// the client that creates a text-change, and on the clients -// that receive text-changes (ops). if there are no ops, just -// return presence, if there are ops, delegate to the subtype -// responsible for those ops (currently only ot-rich-text). -// I am making assumptions many places that all ops will be -// of the same subtype, not sure if this is a given. -// We're only concerned about the first level of object/array, -// not sure if the spec allows nesting of subtypes. -json.transformPresence = function(presence, ops, isOwn) { +json.transformPresence = function(presence, op, isOwn) { console.log('transform presence;'); - if (ops.length < 1) { + if (op.length < 1) { return presence; } - ops.forEach(op => { - const opType = op.t; - const path = op.p && op.p[0]; + // "c" stands for op "component". + for(c of op) { + const opType = c.t; + const path = c.p && c.p[0]; if (opType && subtypes[opType] && path) { // No path given, or path does not match path in presence -- // no need to transform path since no conflict @@ -782,13 +773,15 @@ json.transformPresence = function(presence, ops, isOwn) { } // return result of running the subtype's transformPresence, // but add path and type, which the subtype will not include + // TODO try subtypes[opType].transformPresence presence.cursor = subtypes[opType].transformCursor( presence, - op, + c, isOwn ).cursor; } - }); + }; + console.log(JSON.stringify(presence, null, 2)); return presence; }; From 6435e13292098ebda743e314da0723396c249fac Mon Sep 17 00:00:00 2001 From: curran Date: Fri, 12 Apr 2019 12:50:05 +0530 Subject: [PATCH 16/44] Add stub for presence tests --- .gitignore | 1 + test/presence.js | 397 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 398 insertions(+) create mode 100644 test/presence.js diff --git a/.gitignore b/.gitignore index 691378f..3344b9c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.swp *.DS_Store node_modules +package-lock.json diff --git a/test/presence.js b/test/presence.js new file mode 100644 index 0000000..738d14b --- /dev/null +++ b/test/presence.js @@ -0,0 +1,397 @@ +var assert = require('assert'); +var json = require('../lib/json0'); + +const { createPresence, transformPresence } = json; +// +//// These tests are inspired by the ones found here: +//// https://github.com/Teamwork/ot-rich-text/blob/master/test/Operation.js +describe('createPresence', () => { + it('basic tests', () => { + const presence = { u: '5', c: 8, s: [[1, 2], [9, 5]] }; + assert.strictEqual(createPresence(presence), presence); + }); +}); +// +//describe('transformPresence', () => { +// it('basic tests', () => { +// assert.deepEqual( +// transformPresence({ u: 'user', c: 8, s: [[5, 7]] }, [], true), +// { u: 'user', c: 8, s: [[5, 7]] } +// ); +// assert.deepEqual( +// transformPresence({ u: 'user', c: 8, s: [[5, 7]] }, [], false), +// { u: 'user', c: 8, s: [[5, 7]] } +// ); +// }); +// +// it('top level string operations', () => { +// // Before selection +// assert.deepEqual( +// transformPresence( +// { u: 'user', c: 8, s: [[5, 7]] }, +// [{ p: [0], si: 'a' }], // Insert the 'a' character at position 0. +// true +// ), +// { u: 'user', c: 8, s: [[6, 8]] } +// ); +// +// // Inside selection +// assert.deepEqual( +// transformPresence( +// { u: 'user', c: 8, s: [[5, 7]] }, +// [{ p: [6], si: 'a' }], +// true +// ), +// { u: 'user', c: 8, s: [[5, 8]] } +// ); +// +// // Multiple characters +// assert.deepEqual( +// transformPresence( +// { u: 'user', c: 8, s: [[5, 7]] }, +// [{ p: [6], si: 'abc' }], +// true +// ), +// { u: 'user', c: 8, s: [[5, 10]] } +// ); +// +// // String deletion +// assert.deepEqual( +// transformPresence( +// { u: 'user', c: 8, s: [[5, 7]] }, +// [{ p: [5], sd: 'abc' }], +// true +// ), +// { u: 'user', c: 8, s: [[5, 5]] } +// ); +// +// // After selection +// assert.deepEqual( +// transformPresence( +// { u: 'user', c: 8, s: [[5, 7]] }, +// [{ p: [8], si: 'a' }], +// true +// ), +// { u: 'user', c: 8, s: [[5, 7]] } +// ); +// }); +// +// it('nested string operations', () => { +// // Single level +// assert.deepEqual( +// transformPresence( +// { u: 'user', c: 8, s: [['content', 5, 7]] }, +// [{ p: ['content', 0], si: 'a' }], // Insert the 'a' character at position 0. +// true +// ), +// { u: 'user', c: 8, s: [['content', 6, 8]] } +// ); +// +// // Multiple level +// assert.deepEqual( +// transformPresence( +// { u: 'user', c: 8, s: [['content', 'deeply', 'nested', 5, 7]] }, +// [{ p: ['content', 'deeply', 'nested', 0], si: 'a' }], // Insert the 'a' character at position 0. +// true +// ), +// { u: 'user', c: 8, s: [['content', 'deeply', 'nested', 6, 8]] } +// ); +// +// // Op not matching path +// assert.deepEqual( +// transformPresence( +// { u: 'user', c: 8, s: [['content', 'deeply', 'nested', 5, 7]] }, +// [{ p: ['content', 'somewhere', 'else', 0], si: 'a' }], // Insert the 'a' character at position 0. +// true +// ), +// { u: 'user', c: 8, s: [['content', 'deeply', 'nested', 5, 7]] } +// ); +// +// // Multiple selections +// assert.deepEqual( +// transformPresence( +// { +// u: 'user', +// c: 8, +// s: [ +// ['content', 'deeply', 'nested', 5, 7], +// ['content', 'somewhere', 'else', 5, 7] +// ] +// }, +// [{ p: ['content', 'somewhere', 'else', 0], si: 'a' }], // Insert the 'a' character at position 0. +// true +// ), +// { +// u: 'user', +// c: 8, +// s: [ +// ['content', 'deeply', 'nested', 5, 7], +// ['content', 'somewhere', 'else', 6, 8] +// ] +// } +// ); +// }); +// + // TODO get to this point + + //assert.deepEqual( + // transformPresence( + // {u: 'user', c: 8, s: [[5, 7]]}, + // [createRetain(3), createDelete(2), createInsertText('a')], + // true, + // ), + // { + // u: 'user', + // c: 8, + // s: [[4, 6]], + // }, + //); + //assert.deepEqual( + // transformPresence( + // { + // u: 'user', + // c: 8, + // s: [[5, 7]], + // }, + // [createRetain(3), createDelete(2), createInsertText('a')], + // false, + // ), + // { + // u: 'user', + // c: 8, + // s: [[3, 6]], + // }, + //); + + //assert.deepEqual( + // transformPresence( + // { + // u: 'user', + // c: 8, + // s: [[5, 7]], + // }, + // [createRetain(5), createDelete(2), createInsertText('a')], + // true, + // ), + // { + // u: 'user', + // c: 8, + // s: [[6, 6]], + // }, + //); + //assert.deepEqual( + // transformPresence( + // { + // u: 'user', + // c: 8, + // s: [[5, 7]], + // }, + // [createRetain(5), createDelete(2), createInsertText('a')], + // false, + // ), + // { + // u: 'user', + // c: 8, + // s: [[5, 5]], + // }, + //); + + //assert.deepEqual( + // transformPresence( + // { + // u: 'user', + // c: 8, + // s: [[5, 7], [8, 2]], + // }, + // [createInsertText('a')], + // false, + // ), + // { + // u: 'user', + // c: 8, + // s: [[6, 8], [9, 3]], + // }, + //); + + //assert.deepEqual( + // transformPresence( + // { + // u: 'user', + // c: 8, + // s: [[1, 1], [2, 2]], + // }, + // [createInsertText('a')], + // false, + // ), + // { + // u: 'user', + // c: 8, + // s: [[2, 2], [3, 3]], + // }, + //); +//}); +// +// describe('comparePresence', () => { +// it('basic tests', () => { +// assert.strictEqual(comparePresence(), true); +// assert.strictEqual(comparePresence(undefined, undefined), true); +// assert.strictEqual(comparePresence(null, null), true); +// assert.strictEqual(comparePresence(null, undefined), false); +// assert.strictEqual(comparePresence(undefined, null), false); +// assert.strictEqual( +// comparePresence(undefined, { u: '', c: 0, s: [] }), +// false +// ); +// assert.strictEqual(comparePresence(null, { u: '', c: 0, s: [] }), false); +// assert.strictEqual( +// comparePresence({ u: '', c: 0, s: [] }, undefined), +// false +// ); +// assert.strictEqual(comparePresence({ u: '', c: 0, s: [] }, null), false); +// +// assert.strictEqual( +// comparePresence( +// { u: 'user', c: 8, s: [[1, 2]] }, +// { u: 'user', c: 8, s: [[1, 2]] } +// ), +// true +// ); +// assert.strictEqual( +// comparePresence( +// { u: 'user', c: 8, s: [[1, 2], [4, 6]] }, +// { u: 'user', c: 8, s: [[1, 2], [4, 6]] } +// ), +// true +// ); +// assert.strictEqual( +// comparePresence( +// { u: 'user', c: 8, s: [[1, 2]], unknownProperty: 5 }, +// { u: 'user', c: 8, s: [[1, 2]] } +// ), +// true +// ); +// assert.strictEqual( +// comparePresence( +// { u: 'user', c: 8, s: [[1, 2]] }, +// { u: 'user', c: 8, s: [[1, 2]], unknownProperty: 5 } +// ), +// true +// ); +// assert.strictEqual( +// comparePresence( +// { u: 'user', c: 8, s: [[1, 2]] }, +// { u: 'userX', c: 8, s: [[1, 2]] } +// ), +// false +// ); +// assert.strictEqual( +// comparePresence( +// { u: 'user', c: 8, s: [[1, 2]] }, +// { u: 'user', c: 9, s: [[1, 2]] } +// ), +// false +// ); +// assert.strictEqual( +// comparePresence( +// { u: 'user', c: 8, s: [[1, 2]] }, +// { u: 'user', c: 8, s: [[3, 2]] } +// ), +// false +// ); +// assert.strictEqual( +// comparePresence( +// { u: 'user', c: 8, s: [[1, 2]] }, +// { u: 'user', c: 8, s: [[1, 3]] } +// ), +// false +// ); +// assert.strictEqual( +// comparePresence( +// { u: 'user', c: 8, s: [[9, 8], [1, 2]] }, +// { u: 'user', c: 8, s: [[9, 8], [3, 2]] } +// ), +// false +// ); +// assert.strictEqual( +// comparePresence( +// { u: 'user', c: 8, s: [[9, 8], [1, 2]] }, +// { u: 'user', c: 8, s: [[9, 8], [1, 3]] } +// ), +// false +// ); +// assert.strictEqual( +// comparePresence( +// { u: 'user', c: 8, s: [[9, 8], [1, 2]] }, +// { u: 'user', c: 8, s: [[9, 8]] } +// ), +// false +// ); +// }); +// }); +// +// describe('isValidPresence', () => { +// it('basic tests', () => { +// assert.strictEqual(isValidPresence(), false); +// assert.strictEqual(isValidPresence(null), false); +// assert.strictEqual(isValidPresence([]), false); +// assert.strictEqual(isValidPresence({}), false); +// assert.strictEqual(isValidPresence({ u: 5, c: 8, s: [] }), false); +// assert.strictEqual(isValidPresence({ u: '5', c: '8', s: [] }), false); +// assert.strictEqual(isValidPresence({ u: '5', c: 8.5, s: [] }), false); +// assert.strictEqual(isValidPresence({ u: '5', c: Infinity, s: [] }), false); +// assert.strictEqual(isValidPresence({ u: '5', c: NaN, s: [] }), false); +// assert.strictEqual(isValidPresence({ u: '5', c: 8, s: {} }), false); +// assert.strictEqual(isValidPresence({ u: '5', c: 8, s: [] }), true); +// assert.strictEqual(isValidPresence({ u: '5', c: 8, s: [[]] }), false); +// assert.strictEqual(isValidPresence({ u: '5', c: 8, s: [[1]] }), false); +// assert.strictEqual(isValidPresence({ u: '5', c: 8, s: [[1, 2]] }), true); +// assert.strictEqual( +// isValidPresence({ u: '5', c: 8, s: [[1, 2, 3]] }), +// false +// ); +// assert.strictEqual( +// isValidPresence({ u: '5', c: 8, s: [[1, 2], []] }), +// false +// ); +// assert.strictEqual( +// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, 6]] }), +// true +// ); +// assert.strictEqual( +// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, '6']] }), +// false +// ); +// assert.strictEqual( +// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, 6.1]] }), +// false +// ); +// assert.strictEqual( +// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, Infinity]] }), +// false +// ); +// assert.strictEqual( +// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, NaN]] }), +// false +// ); +// assert.strictEqual( +// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, -0]] }), +// true +// ); +// assert.strictEqual( +// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, -1]] }), +// true +// ); +// assert.strictEqual( +// isValidPresence({ u: '5', c: 8, s: [[1, 2], ['3', 0]] }), +// false +// ); +// assert.strictEqual( +// isValidPresence({ u: '5', c: 8, s: [[1, '2'], [4, 0]] }), +// false +// ); +// assert.strictEqual( +// isValidPresence({ u: '5', c: 8, s: [['1', 2], [4, 0]] }), +// false +// ); +// }); +// }); From a249e05dceb7b589d8fdc2c70484395970f2756e Mon Sep 17 00:00:00 2001 From: curran Date: Fri, 12 Apr 2019 13:10:45 +0530 Subject: [PATCH 17/44] Add sketch for presence structure in README --- README.md | 22 ++++++++++++++++++++++ test/presence.js | 4 ++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cc1de54..8e4422f 100644 --- a/README.md +++ b/README.md @@ -294,6 +294,28 @@ offset in a string. `TEXT` must be contained at the location specified. --- +## Presence + +(inspired by https://github.com/Teamwork/ot-rich-text#presence) + +The shape of our presence data is as follows: + +``` +{ + u: '123', // User ID. + c: 8, // Change count for this user (for change detection). + p: ['some', 'path'], // Path for this presence object. + t: 'ot-rich-text', // Type for this presence object (an OT type). + s: { // Sub-presence object at this path (specific to the OT type). + u: '123', // An example of an ot-rich-text presence object. + c: 8, + s: [ [ 1, 1 ], [ 5, 7 ]] + } +} +``` + +The rest of the README content is from the original repo https://github.com/ottypes/json0. + # Commentary This library was written a couple of years ago by [Jeremy Apthorp](https://github.com/nornagon). It was diff --git a/test/presence.js b/test/presence.js index 738d14b..cf358b3 100644 --- a/test/presence.js +++ b/test/presence.js @@ -5,8 +5,8 @@ const { createPresence, transformPresence } = json; // //// These tests are inspired by the ones found here: //// https://github.com/Teamwork/ot-rich-text/blob/master/test/Operation.js -describe('createPresence', () => { - it('basic tests', () => { +describe.only('presence', () => { + it('createPresence', () => { const presence = { u: '5', c: 8, s: [[1, 2], [9, 5]] }; assert.strictEqual(createPresence(presence), presence); }); From 151c6ea2b09095596b45975c868c3339bd296c7c Mon Sep 17 00:00:00 2001 From: curran Date: Fri, 12 Apr 2019 13:12:50 +0530 Subject: [PATCH 18/44] Add first pass implementation from earlier --- lib/json0.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/lib/json0.js b/lib/json0.js index 4b32f47..66d963a 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -761,6 +761,29 @@ json.transformPresence = function(presence, op, isOwn) { if (op.length < 1) { return presence; } + + // TODO integrate any valid ideas from here. + //for (let c of op){ + // if(c.si || c.sd){ + // convertFromText(c) + // } + // if(c.t === 'text0') { + // //json.canOpAffectPath = function(op, path) { + // presence = Object.assign({}, presence, { + // s: presence.s.map(selection => { + // const path = selection.slice(0, selection.length - 2); + // if(canOpAffectPath(c, path)) { + // const [start, end] = selection.slice(selection.length - 2); + // return path.concat([ + // transformCursor(start, c.o), + // transformCursor(end, c.o), + // ]); + // } + // return selection; + // }) + // }); + // } + //} // "c" stands for op "component". for(c of op) { const opType = c.t; From 1a359af7c15476effd87bfcffc3235655d345cd0 Mon Sep 17 00:00:00 2001 From: curran Date: Fri, 12 Apr 2019 14:44:21 +0530 Subject: [PATCH 19/44] Invoke subtype transformPresence --- README.md | 4 ++-- lib/json0.js | 16 ++++------------ package.json | 5 +++-- test/presence.js | 39 +++++++++++++++++++++++++++++++++------ 4 files changed, 42 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 8e4422f..53c24c0 100644 --- a/README.md +++ b/README.md @@ -304,8 +304,8 @@ The shape of our presence data is as follows: { u: '123', // User ID. c: 8, // Change count for this user (for change detection). - p: ['some', 'path'], // Path for this presence object. - t: 'ot-rich-text', // Type for this presence object (an OT type). + p: ['some', 'path'], // Path of the sub-presence object. + t: 'ot-rich-text', // Type of the sub-presence object (an OT type). s: { // Sub-presence object at this path (specific to the OT type). u: '123', // An example of an ot-rich-text presence object. c: 8, diff --git a/lib/json0.js b/lib/json0.js index 66d963a..115a8fa 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -757,10 +757,7 @@ json.comparePresence = function(pres1, pres2) { // }; json.transformPresence = function(presence, op, isOwn) { - console.log('transform presence;'); - if (op.length < 1) { - return presence; - } + if (op.length === 0) return presence; // TODO integrate any valid ideas from here. //for (let c of op){ @@ -791,20 +788,15 @@ json.transformPresence = function(presence, op, isOwn) { if (opType && subtypes[opType] && path) { // No path given, or path does not match path in presence -- // no need to transform path since no conflict + // TODO try if(canOpAffectPath(c, path)) { if (!presence.p || !presence.p[0] || presence.p[0] !== path) { return presence; } - // return result of running the subtype's transformPresence, - // but add path and type, which the subtype will not include - // TODO try subtypes[opType].transformPresence - presence.cursor = subtypes[opType].transformCursor( - presence, - c, - isOwn - ).cursor; + presence = subtypes[opType].transformPresence(presence); } }; console.log(JSON.stringify(presence, null, 2)); + console.log('updated v'); return presence; }; diff --git a/package.json b/package.json index b6c9df6..b3cae36 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,10 @@ }, "dependencies": {}, "devDependencies": { - "ot-fuzzer": "^1.0.0", + "@teamwork/ot-rich-text": "^8.1.0", + "coffee-script": "^1.7.1", "mocha": "^1.20.1", - "coffee-script": "^1.7.1" + "ot-fuzzer": "^1.0.0" }, "scripts": { "test": "mocha" diff --git a/test/presence.js b/test/presence.js index cf358b3..cd87f52 100644 --- a/test/presence.js +++ b/test/presence.js @@ -1,14 +1,41 @@ -var assert = require('assert'); -var json = require('../lib/json0'); +const assert = require('assert'); +const json = require('../lib/json0'); +const richText = require('@teamwork/ot-rich-text') const { createPresence, transformPresence } = json; + +const samplePresence = { + u: '123', // User ID. + c: 8, // Change count for this user (for change detection). + p: ['some', 'path'], // Path of the sub-presence object. + t: 'ot-rich-text', // Type of the sub-presence object (an OT type). + s: { // Sub-presence object at this path (specific to the OT type). + u: '123', // An example of an ot-rich-text presence object. + c: 8, + s: [ [ 1, 1 ], [ 5, 7 ]] + } +} + // //// These tests are inspired by the ones found here: //// https://github.com/Teamwork/ot-rich-text/blob/master/test/Operation.js -describe.only('presence', () => { - it('createPresence', () => { - const presence = { u: '5', c: 8, s: [[1, 2], [9, 5]] }; - assert.strictEqual(createPresence(presence), presence); +describe.only('json0 presence', () => { + describe('createPresence', () => { + it('should return the passed in presence object', () => { + assert.strictEqual(createPresence(samplePresence), samplePresence); + }); + }); + describe('transformPresence', () => { + it('should preserve original presence in case of no-op', () => { + assert.deepEqual( + transformPresence(samplePresence, [], true), + samplePresence + ); + assert.deepEqual( + transformPresence(samplePresence, [], false), + samplePresence + ); + }); }); }); // From a2ab7e75e578f1073c21c73b48d918521b2b3a6d Mon Sep 17 00:00:00 2001 From: curran Date: Fri, 12 Apr 2019 15:13:52 +0530 Subject: [PATCH 20/44] Revise proposed structure for presence --- README.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 53c24c0..634ef98 100644 --- a/README.md +++ b/README.md @@ -300,18 +300,16 @@ offset in a string. `TEXT` must be contained at the location specified. The shape of our presence data is as follows: -``` -{ - u: '123', // User ID. - c: 8, // Change count for this user (for change detection). - p: ['some', 'path'], // Path of the sub-presence object. - t: 'ot-rich-text', // Type of the sub-presence object (an OT type). - s: { // Sub-presence object at this path (specific to the OT type). - u: '123', // An example of an ot-rich-text presence object. +```js +[ + 'some', 'path', // Path of the presence. + 'ot-rich-text', // Type of the presence (an OT type). + { // Presence object at this path (structure is specific to the OT type). + u: '123', // An example of an ot-rich-text presence object. c: 8, s: [ [ 1, 1 ], [ 5, 7 ]] } -} +] ``` The rest of the README content is from the original repo https://github.com/ottypes/json0. From f3eb0803eae266767b38b22e6dc1504d45e03047 Mon Sep 17 00:00:00 2001 From: curran Date: Fri, 12 Apr 2019 15:20:21 +0530 Subject: [PATCH 21/44] Revise structure in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 634ef98..2815608 100644 --- a/README.md +++ b/README.md @@ -303,8 +303,8 @@ The shape of our presence data is as follows: ```js [ 'some', 'path', // Path of the presence. - 'ot-rich-text', // Type of the presence (an OT type). - { // Presence object at this path (structure is specific to the OT type). + 'ot-rich-text', // Subtype of the presence (a registered subtype). + { // Opaque presence object (subtype-specific structure). u: '123', // An example of an ot-rich-text presence object. c: 8, s: [ [ 1, 1 ], [ 5, 7 ]] From db5deeb12610a16cda48c86533dae8dcb28fccee Mon Sep 17 00:00:00 2001 From: curran Date: Fri, 12 Apr 2019 15:29:31 +0530 Subject: [PATCH 22/44] Fix invocation of subtype .transformPresence --- lib/json0.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index 115a8fa..6230926 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -651,11 +651,11 @@ json.transformComponent = function(dest, c, otherC, type) { return dest; }; -// not checking anything here, we should probably check that u: exists -// (only thing we care about at json0 top level), and then delegate -// to any subtypes if there is already subtype presence data -json.createPresence = function(presence) { - return presence; +// TODO validate this. +// Note that this is the same data passed into doc.submitPresence, +// and we rely on developers passing in the correct structure here. +json.createPresence = function(presenceData) { + return presenceData; }; json.comparePresence = function(pres1, pres2) { @@ -756,7 +756,7 @@ json.comparePresence = function(pres1, pres2) { // return cursor; // }; -json.transformPresence = function(presence, op, isOwn) { +json.transformPresence = function(presence, op, isOwnOp) { if (op.length === 0) return presence; // TODO integrate any valid ideas from here. @@ -792,10 +792,9 @@ json.transformPresence = function(presence, op, isOwn) { if (!presence.p || !presence.p[0] || presence.p[0] !== path) { return presence; } - presence = subtypes[opType].transformPresence(presence); + presence = subtypes[opType].transformPresence(presence, [c], isOwnOp); } }; - console.log(JSON.stringify(presence, null, 2)); console.log('updated v'); return presence; }; From 00ecd57e5b3678349fe232ce64cacac19e1874a4 Mon Sep 17 00:00:00 2001 From: curran Date: Fri, 12 Apr 2019 17:01:11 +0530 Subject: [PATCH 23/44] Add first substantive test case for transformPresence --- lib/json0.js | 29 +++++++++++++++++---------- test/presence.js | 52 +++++++++++++++++++++++++++++------------------- 2 files changed, 50 insertions(+), 31 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index 6230926..9303bfa 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -755,6 +755,8 @@ json.comparePresence = function(pres1, pres2) { // } // return cursor; // }; +// + json.transformPresence = function(presence, op, isOwnOp) { if (op.length === 0) return presence; @@ -782,20 +784,25 @@ json.transformPresence = function(presence, op, isOwnOp) { // } //} // "c" stands for op "component". - for(c of op) { - const opType = c.t; - const path = c.p && c.p[0]; - if (opType && subtypes[opType] && path) { - // No path given, or path does not match path in presence -- - // no need to transform path since no conflict - // TODO try if(canOpAffectPath(c, path)) { - if (!presence.p || !presence.p[0] || presence.p[0] !== path) { - return presence; + for(let c of op) { + + // Transform against subtype ops only (for now). + if (c.t) { + const presencePath = presence.slice(0, presence.length - 2); + if(json.canOpAffectPath(c, presencePath)) { + const subPresenceType = presence[presence.length - 2]; + const subPresence = presence[presence.length - 1]; + // TODO test this check + //if(subPresenceType === c.t) { + const { transformPresence } = subtypes[subPresenceType]; + return presencePath.concat([ + subPresenceType, + transformPresence(subPresence, c.o, isOwnOp) + ]); + //} } - presence = subtypes[opType].transformPresence(presence, [c], isOwnOp); } }; - console.log('updated v'); return presence; }; diff --git a/test/presence.js b/test/presence.js index cd87f52..ced6012 100644 --- a/test/presence.js +++ b/test/presence.js @@ -1,20 +1,22 @@ const assert = require('assert'); const json = require('../lib/json0'); -const richText = require('@teamwork/ot-rich-text') +const otRichText = require('@teamwork/ot-rich-text') + +const { createInsertText } = otRichText.Action + +json.registerSubtype(otRichText.type); const { createPresence, transformPresence } = json; -const samplePresence = { - u: '123', // User ID. - c: 8, // Change count for this user (for change detection). - p: ['some', 'path'], // Path of the sub-presence object. - t: 'ot-rich-text', // Type of the sub-presence object (an OT type). - s: { // Sub-presence object at this path (specific to the OT type). - u: '123', // An example of an ot-rich-text presence object. +const samplePresence = [ + 'some', 'path', // Path of the presence. + 'ot-rich-text', // Subtype of the presence (a registered subtype). + { // Opaque presence object (subtype-specific structure). + u: '123', // An example of an ot-rich-text presence object. c: 8, s: [ [ 1, 1 ], [ 5, 7 ]] } -} +]; // //// These tests are inspired by the ones found here: @@ -36,21 +38,31 @@ describe.only('json0 presence', () => { samplePresence ); }); + it('should transform by top level op', () => { + + const o = [ + createInsertText('a') + ]; + + const op = [{ + p: ['some', 'path'], + t: otRichText.type.name, + o + }]; + + const isOwnOp = true; + + assert.deepEqual( + transformPresence( samplePresence, op, isOwnOp), + samplePresence.slice(0, samplePresence.length - 1).concat( + otRichText.type.transformPresence(samplePresence[samplePresence.length - 1], o, isOwnOp) + ) + ); + }); }); }); // //describe('transformPresence', () => { -// it('basic tests', () => { -// assert.deepEqual( -// transformPresence({ u: 'user', c: 8, s: [[5, 7]] }, [], true), -// { u: 'user', c: 8, s: [[5, 7]] } -// ); -// assert.deepEqual( -// transformPresence({ u: 'user', c: 8, s: [[5, 7]] }, [], false), -// { u: 'user', c: 8, s: [[5, 7]] } -// ); -// }); -// // it('top level string operations', () => { // // Before selection // assert.deepEqual( From 78b02ba0029edd0d07465d9a60376c1a1d385c7f Mon Sep 17 00:00:00 2001 From: curran Date: Fri, 12 Apr 2019 17:09:31 +0530 Subject: [PATCH 24/44] Clean transformPresence implementation. Introduce unpackPresence. --- lib/json0.js | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index 9303bfa..87a4b0f 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -757,10 +757,19 @@ json.comparePresence = function(pres1, pres2) { // }; // +const unpackPresence = presence => ({ + presencePath: presence.slice(0, presence.length - 2), + presenceType: presence[presence.length - 2], + subPresence: presence[presence.length - 1] +}); + +json.unpackPresence = unpackPresence; json.transformPresence = function(presence, op, isOwnOp) { if (op.length === 0) return presence; + const { presencePath, presenceType, subPresence } = unpackPresence(presence); + // TODO integrate any valid ideas from here. //for (let c of op){ // if(c.si || c.sd){ @@ -786,22 +795,20 @@ json.transformPresence = function(presence, op, isOwnOp) { // "c" stands for op "component". for(let c of op) { - // Transform against subtype ops only (for now). + // Transform against subtype ops. if (c.t) { - const presencePath = presence.slice(0, presence.length - 2); if(json.canOpAffectPath(c, presencePath)) { - const subPresenceType = presence[presence.length - 2]; - const subPresence = presence[presence.length - 1]; // TODO test this check //if(subPresenceType === c.t) { - const { transformPresence } = subtypes[subPresenceType]; - return presencePath.concat([ - subPresenceType, - transformPresence(subPresence, c.o, isOwnOp) - ]); + return presencePath.concat( + presenceType, + subtypes[presenceType].transformPresence(subPresence, c.o, isOwnOp) + ); //} } } + + // TODO transform against non-subtype ops. }; return presence; }; From 52e0fb851b7f09f3c86eaef21a981c40a1af7ffd Mon Sep 17 00:00:00 2001 From: curran Date: Sat, 13 Apr 2019 07:54:05 +0530 Subject: [PATCH 25/44] Add test case for matching presence type --- lib/json0.js | 23 +++++++++++++++-------- test/presence.js | 11 ++++++++++- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index 87a4b0f..3fe2b04 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -757,11 +757,17 @@ json.comparePresence = function(pres1, pres2) { // }; // -const unpackPresence = presence => ({ - presencePath: presence.slice(0, presence.length - 2), - presenceType: presence[presence.length - 2], - subPresence: presence[presence.length - 1] -}); +const unpackPresence = presence => { + if(!presence.slice) { + console.log('Expected presence to be an array. Received:'); + console.log(presence); + } + return { + presencePath: presence.slice(0, presence.length - 2), + presenceType: presence[presence.length - 2], + subPresence: presence[presence.length - 1] + }; +}; json.unpackPresence = unpackPresence; @@ -796,8 +802,9 @@ json.transformPresence = function(presence, op, isOwnOp) { for(let c of op) { // Transform against subtype ops. - if (c.t) { - if(json.canOpAffectPath(c, presencePath)) { + if (c.t && c.t === presenceType) { + // TODO test this check + //if(json.canOpAffectPath(c, presencePath)) { // TODO test this check //if(subPresenceType === c.t) { return presencePath.concat( @@ -805,7 +812,7 @@ json.transformPresence = function(presence, op, isOwnOp) { subtypes[presenceType].transformPresence(subPresence, c.o, isOwnOp) ); //} - } + //} } // TODO transform against non-subtype ops. diff --git a/test/presence.js b/test/presence.js index ced6012..84fd6b0 100644 --- a/test/presence.js +++ b/test/presence.js @@ -38,7 +38,7 @@ describe.only('json0 presence', () => { samplePresence ); }); - it('should transform by top level op', () => { + it('should transform by op with matching path and subtype', () => { const o = [ createInsertText('a') @@ -59,6 +59,15 @@ describe.only('json0 presence', () => { ) ); }); + + it('should not transform by op with matching path and non-matching subtype', () => { + const o = [ createInsertText('a') ]; + const op = [{ p: ['some', 'path'], t: 'some-invalid-name', o }]; + assert.deepEqual( + transformPresence( samplePresence, op ), + samplePresence + ); + }); }); }); // From 1d3d6da34c034423f987e7ad7b46931425a960d8 Mon Sep 17 00:00:00 2001 From: curran Date: Sat, 13 Apr 2019 08:00:19 +0530 Subject: [PATCH 26/44] Add test case for non-matching path --- lib/json0.js | 15 ++++++--------- test/presence.js | 19 ++++++++++++++++--- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index 3fe2b04..e3d6a3f 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -802,16 +802,13 @@ json.transformPresence = function(presence, op, isOwnOp) { for(let c of op) { // Transform against subtype ops. - if (c.t && c.t === presenceType) { + if (c.t && c.t === presenceType && json.pathMatches(c.p, presencePath)) { // TODO test this check - //if(json.canOpAffectPath(c, presencePath)) { - // TODO test this check - //if(subPresenceType === c.t) { - return presencePath.concat( - presenceType, - subtypes[presenceType].transformPresence(subPresence, c.o, isOwnOp) - ); - //} + //if(subPresenceType === c.t) { + return presencePath.concat( + presenceType, + subtypes[presenceType].transformPresence(subPresence, c.o, isOwnOp) + ); //} } diff --git a/test/presence.js b/test/presence.js index 84fd6b0..12586eb 100644 --- a/test/presence.js +++ b/test/presence.js @@ -61,10 +61,23 @@ describe.only('json0 presence', () => { }); it('should not transform by op with matching path and non-matching subtype', () => { - const o = [ createInsertText('a') ]; - const op = [{ p: ['some', 'path'], t: 'some-invalid-name', o }]; assert.deepEqual( - transformPresence( samplePresence, op ), + transformPresence( samplePresence, [{ + p: ['some', 'path'], + t: 'some-invalid-name', + o: [ createInsertText('a') ] + }] ), + samplePresence + ); + }); + + it('should not transform by op with non-matching path and matching subtype', () => { + assert.deepEqual( + transformPresence( samplePresence, [{ + p: ['some', 'other', 'path'], + t: otRichText.type.name, + o: [ createInsertText('a') ] + }] ), samplePresence ); }); From d5b7d0bbb6bc414e9f29618ad6e2719413a5a6d6 Mon Sep 17 00:00:00 2001 From: curran Date: Sat, 13 Apr 2019 08:08:45 +0530 Subject: [PATCH 27/44] Match style of existing code --- lib/json0.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index e3d6a3f..ad1cdc3 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -757,11 +757,15 @@ json.comparePresence = function(pres1, pres2) { // }; // +// Unpacks the presence array into meaningful parts. const unpackPresence = presence => { + + // Validate to help app developers find problems. if(!presence.slice) { console.log('Expected presence to be an array. Received:'); console.log(presence); } + return { presencePath: presence.slice(0, presence.length - 2), presenceType: presence[presence.length - 2], @@ -798,18 +802,20 @@ json.transformPresence = function(presence, op, isOwnOp) { // }); // } //} - // "c" stands for op "component". - for(let c of op) { + for (var i = 0; i < op.length; i++) { + var c = op[i]; + + // convert old string ops to use subtype for backwards compatibility + // TODO cover with tests + // if (c.si != null || c.sd != null) + // convertFromText(c); // Transform against subtype ops. if (c.t && c.t === presenceType && json.pathMatches(c.p, presencePath)) { - // TODO test this check - //if(subPresenceType === c.t) { return presencePath.concat( presenceType, subtypes[presenceType].transformPresence(subPresence, c.o, isOwnOp) ); - //} } // TODO transform against non-subtype ops. From bc3a74fa4b46e34bfa411464ca91a1455a54afd3 Mon Sep 17 00:00:00 2001 From: curran Date: Sat, 13 Apr 2019 09:07:30 +0530 Subject: [PATCH 28/44] Match style of existing code - use ES5 --- lib/json0.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index ad1cdc3..57bf823 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -758,7 +758,7 @@ json.comparePresence = function(pres1, pres2) { // // Unpacks the presence array into meaningful parts. -const unpackPresence = presence => { +function unpackPresence(presence) { // Validate to help app developers find problems. if(!presence.slice) { @@ -772,13 +772,15 @@ const unpackPresence = presence => { subPresence: presence[presence.length - 1] }; }; - json.unpackPresence = unpackPresence; json.transformPresence = function(presence, op, isOwnOp) { if (op.length === 0) return presence; - const { presencePath, presenceType, subPresence } = unpackPresence(presence); + var unpacked = unpackPresence(presence); + var presencePath = unpacked.presencePath; + var presenceType = unpacked.presenceType; + var subPresence = unpacked.subPresence; // TODO integrate any valid ideas from here. //for (let c of op){ From 552794cc7faaec5bfc3ff2e261e58c23ab2ee843 Mon Sep 17 00:00:00 2001 From: curran Date: Sat, 13 Apr 2019 09:16:38 +0530 Subject: [PATCH 29/44] Start working on text0 presence --- lib/json0.js | 3 - lib/text0.js | 8 + test/{presence.js => json0-presence.js} | 3 +- test/text0-presence.js | 451 ++++++++++++++++++++++++ 4 files changed, 460 insertions(+), 5 deletions(-) rename test/{presence.js => json0-presence.js} (99%) create mode 100644 test/text0-presence.js diff --git a/lib/json0.js b/lib/json0.js index 57bf823..3b7c19d 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -651,9 +651,6 @@ json.transformComponent = function(dest, c, otherC, type) { return dest; }; -// TODO validate this. -// Note that this is the same data passed into doc.submitPresence, -// and we rely on developers passing in the correct structure here. json.createPresence = function(presenceData) { return presenceData; }; diff --git a/lib/text0.js b/lib/text0.js index e26c6a9..c34dc51 100644 --- a/lib/text0.js +++ b/lib/text0.js @@ -253,4 +253,12 @@ text.invert = function(op) { return op; }; +text.createPresence = function(presenceData) { + return presenceData; +}; + +//text.comparePresence = function(pres1, pres2) { +// return JSON.stringify(pres1) === JSON.stringify(pres2); +//}; + require('./bootstrapTransform')(text, transformComponent, checkValidOp, append); diff --git a/test/presence.js b/test/json0-presence.js similarity index 99% rename from test/presence.js rename to test/json0-presence.js index 12586eb..63bbaaf 100644 --- a/test/presence.js +++ b/test/json0-presence.js @@ -18,10 +18,9 @@ const samplePresence = [ } ]; -// //// These tests are inspired by the ones found here: //// https://github.com/Teamwork/ot-rich-text/blob/master/test/Operation.js -describe.only('json0 presence', () => { +describe('json0 presence', () => { describe('createPresence', () => { it('should return the passed in presence object', () => { assert.strictEqual(createPresence(samplePresence), samplePresence); diff --git a/test/text0-presence.js b/test/text0-presence.js new file mode 100644 index 0000000..054428b --- /dev/null +++ b/test/text0-presence.js @@ -0,0 +1,451 @@ +const assert = require('assert'); +const text = require('../lib/text0'); + +const { createPresence, transformPresence } = text; + +// Inspired by ot-rich-text presence structure. +const samplePresence = { + u: '123', + c: 8, + s: [ [ 1, 1 ], [ 5, 7 ]] +}; + +// These tests are inspired by the ones found here: +// https://github.com/Teamwork/ot-rich-text/blob/master/test/Operation.js +describe.only('text0 presence', () => { + describe('createPresence', () => { + it('should return the passed in presence object', () => { + assert.strictEqual(createPresence(samplePresence), samplePresence); + }); + }); + // describe('transformPresence', () => { + // it('should preserve original presence in case of no-op', () => { + // assert.deepEqual( + // transformPresence(samplePresence, [], true), + // samplePresence + // ); + // assert.deepEqual( + // transformPresence(samplePresence, [], false), + // samplePresence + // ); + // }); + // it('should transform by op with matching path and subtype', () => { + + // const o = [ + // createInsertText('a') + // ]; + + // const op = [{ + // p: ['some', 'path'], + // t: otRichText.type.name, + // o + // }]; + + // const isOwnOp = true; + + // assert.deepEqual( + // transformPresence( samplePresence, op, isOwnOp), + // samplePresence.slice(0, samplePresence.length - 1).concat( + // otRichText.type.transformPresence(samplePresence[samplePresence.length - 1], o, isOwnOp) + // ) + // ); + // }); + + // it('should not transform by op with matching path and non-matching subtype', () => { + // assert.deepEqual( + // transformPresence( samplePresence, [{ + // p: ['some', 'path'], + // t: 'some-invalid-name', + // o: [ createInsertText('a') ] + // }] ), + // samplePresence + // ); + // }); + + // it('should not transform by op with non-matching path and matching subtype', () => { + // assert.deepEqual( + // transformPresence( samplePresence, [{ + // p: ['some', 'other', 'path'], + // t: otRichText.type.name, + // o: [ createInsertText('a') ] + // }] ), + // samplePresence + // ); + // }); + // }); +}); + +// +// +//describe('transformPresence', () => { +// it('top level string operations', () => { +// // Before selection +// assert.deepEqual( +// transformPresence( +// { u: 'user', c: 8, s: [[5, 7]] }, +// [{ p: [0], si: 'a' }], // Insert the 'a' character at position 0. +// true +// ), +// { u: 'user', c: 8, s: [[6, 8]] } +// ); +// +// // Inside selection +// assert.deepEqual( +// transformPresence( +// { u: 'user', c: 8, s: [[5, 7]] }, +// [{ p: [6], si: 'a' }], +// true +// ), +// { u: 'user', c: 8, s: [[5, 8]] } +// ); +// +// // Multiple characters +// assert.deepEqual( +// transformPresence( +// { u: 'user', c: 8, s: [[5, 7]] }, +// [{ p: [6], si: 'abc' }], +// true +// ), +// { u: 'user', c: 8, s: [[5, 10]] } +// ); +// +// // String deletion +// assert.deepEqual( +// transformPresence( +// { u: 'user', c: 8, s: [[5, 7]] }, +// [{ p: [5], sd: 'abc' }], +// true +// ), +// { u: 'user', c: 8, s: [[5, 5]] } +// ); +// +// // After selection +// assert.deepEqual( +// transformPresence( +// { u: 'user', c: 8, s: [[5, 7]] }, +// [{ p: [8], si: 'a' }], +// true +// ), +// { u: 'user', c: 8, s: [[5, 7]] } +// ); +// }); +// +// it('nested string operations', () => { +// // Single level +// assert.deepEqual( +// transformPresence( +// { u: 'user', c: 8, s: [['content', 5, 7]] }, +// [{ p: ['content', 0], si: 'a' }], // Insert the 'a' character at position 0. +// true +// ), +// { u: 'user', c: 8, s: [['content', 6, 8]] } +// ); +// +// // Multiple level +// assert.deepEqual( +// transformPresence( +// { u: 'user', c: 8, s: [['content', 'deeply', 'nested', 5, 7]] }, +// [{ p: ['content', 'deeply', 'nested', 0], si: 'a' }], // Insert the 'a' character at position 0. +// true +// ), +// { u: 'user', c: 8, s: [['content', 'deeply', 'nested', 6, 8]] } +// ); +// +// // Op not matching path +// assert.deepEqual( +// transformPresence( +// { u: 'user', c: 8, s: [['content', 'deeply', 'nested', 5, 7]] }, +// [{ p: ['content', 'somewhere', 'else', 0], si: 'a' }], // Insert the 'a' character at position 0. +// true +// ), +// { u: 'user', c: 8, s: [['content', 'deeply', 'nested', 5, 7]] } +// ); +// +// // Multiple selections +// assert.deepEqual( +// transformPresence( +// { +// u: 'user', +// c: 8, +// s: [ +// ['content', 'deeply', 'nested', 5, 7], +// ['content', 'somewhere', 'else', 5, 7] +// ] +// }, +// [{ p: ['content', 'somewhere', 'else', 0], si: 'a' }], // Insert the 'a' character at position 0. +// true +// ), +// { +// u: 'user', +// c: 8, +// s: [ +// ['content', 'deeply', 'nested', 5, 7], +// ['content', 'somewhere', 'else', 6, 8] +// ] +// } +// ); +// }); +// + // TODO get to this point + + //assert.deepEqual( + // transformPresence( + // {u: 'user', c: 8, s: [[5, 7]]}, + // [createRetain(3), createDelete(2), createInsertText('a')], + // true, + // ), + // { + // u: 'user', + // c: 8, + // s: [[4, 6]], + // }, + //); + //assert.deepEqual( + // transformPresence( + // { + // u: 'user', + // c: 8, + // s: [[5, 7]], + // }, + // [createRetain(3), createDelete(2), createInsertText('a')], + // false, + // ), + // { + // u: 'user', + // c: 8, + // s: [[3, 6]], + // }, + //); + + //assert.deepEqual( + // transformPresence( + // { + // u: 'user', + // c: 8, + // s: [[5, 7]], + // }, + // [createRetain(5), createDelete(2), createInsertText('a')], + // true, + // ), + // { + // u: 'user', + // c: 8, + // s: [[6, 6]], + // }, + //); + //assert.deepEqual( + // transformPresence( + // { + // u: 'user', + // c: 8, + // s: [[5, 7]], + // }, + // [createRetain(5), createDelete(2), createInsertText('a')], + // false, + // ), + // { + // u: 'user', + // c: 8, + // s: [[5, 5]], + // }, + //); + + //assert.deepEqual( + // transformPresence( + // { + // u: 'user', + // c: 8, + // s: [[5, 7], [8, 2]], + // }, + // [createInsertText('a')], + // false, + // ), + // { + // u: 'user', + // c: 8, + // s: [[6, 8], [9, 3]], + // }, + //); + + //assert.deepEqual( + // transformPresence( + // { + // u: 'user', + // c: 8, + // s: [[1, 1], [2, 2]], + // }, + // [createInsertText('a')], + // false, + // ), + // { + // u: 'user', + // c: 8, + // s: [[2, 2], [3, 3]], + // }, + //); +//}); +// +// describe('comparePresence', () => { +// it('basic tests', () => { +// assert.strictEqual(comparePresence(), true); +// assert.strictEqual(comparePresence(undefined, undefined), true); +// assert.strictEqual(comparePresence(null, null), true); +// assert.strictEqual(comparePresence(null, undefined), false); +// assert.strictEqual(comparePresence(undefined, null), false); +// assert.strictEqual( +// comparePresence(undefined, { u: '', c: 0, s: [] }), +// false +// ); +// assert.strictEqual(comparePresence(null, { u: '', c: 0, s: [] }), false); +// assert.strictEqual( +// comparePresence({ u: '', c: 0, s: [] }, undefined), +// false +// ); +// assert.strictEqual(comparePresence({ u: '', c: 0, s: [] }, null), false); +// +// assert.strictEqual( +// comparePresence( +// { u: 'user', c: 8, s: [[1, 2]] }, +// { u: 'user', c: 8, s: [[1, 2]] } +// ), +// true +// ); +// assert.strictEqual( +// comparePresence( +// { u: 'user', c: 8, s: [[1, 2], [4, 6]] }, +// { u: 'user', c: 8, s: [[1, 2], [4, 6]] } +// ), +// true +// ); +// assert.strictEqual( +// comparePresence( +// { u: 'user', c: 8, s: [[1, 2]], unknownProperty: 5 }, +// { u: 'user', c: 8, s: [[1, 2]] } +// ), +// true +// ); +// assert.strictEqual( +// comparePresence( +// { u: 'user', c: 8, s: [[1, 2]] }, +// { u: 'user', c: 8, s: [[1, 2]], unknownProperty: 5 } +// ), +// true +// ); +// assert.strictEqual( +// comparePresence( +// { u: 'user', c: 8, s: [[1, 2]] }, +// { u: 'userX', c: 8, s: [[1, 2]] } +// ), +// false +// ); +// assert.strictEqual( +// comparePresence( +// { u: 'user', c: 8, s: [[1, 2]] }, +// { u: 'user', c: 9, s: [[1, 2]] } +// ), +// false +// ); +// assert.strictEqual( +// comparePresence( +// { u: 'user', c: 8, s: [[1, 2]] }, +// { u: 'user', c: 8, s: [[3, 2]] } +// ), +// false +// ); +// assert.strictEqual( +// comparePresence( +// { u: 'user', c: 8, s: [[1, 2]] }, +// { u: 'user', c: 8, s: [[1, 3]] } +// ), +// false +// ); +// assert.strictEqual( +// comparePresence( +// { u: 'user', c: 8, s: [[9, 8], [1, 2]] }, +// { u: 'user', c: 8, s: [[9, 8], [3, 2]] } +// ), +// false +// ); +// assert.strictEqual( +// comparePresence( +// { u: 'user', c: 8, s: [[9, 8], [1, 2]] }, +// { u: 'user', c: 8, s: [[9, 8], [1, 3]] } +// ), +// false +// ); +// assert.strictEqual( +// comparePresence( +// { u: 'user', c: 8, s: [[9, 8], [1, 2]] }, +// { u: 'user', c: 8, s: [[9, 8]] } +// ), +// false +// ); +// }); +// }); +// +// describe('isValidPresence', () => { +// it('basic tests', () => { +// assert.strictEqual(isValidPresence(), false); +// assert.strictEqual(isValidPresence(null), false); +// assert.strictEqual(isValidPresence([]), false); +// assert.strictEqual(isValidPresence({}), false); +// assert.strictEqual(isValidPresence({ u: 5, c: 8, s: [] }), false); +// assert.strictEqual(isValidPresence({ u: '5', c: '8', s: [] }), false); +// assert.strictEqual(isValidPresence({ u: '5', c: 8.5, s: [] }), false); +// assert.strictEqual(isValidPresence({ u: '5', c: Infinity, s: [] }), false); +// assert.strictEqual(isValidPresence({ u: '5', c: NaN, s: [] }), false); +// assert.strictEqual(isValidPresence({ u: '5', c: 8, s: {} }), false); +// assert.strictEqual(isValidPresence({ u: '5', c: 8, s: [] }), true); +// assert.strictEqual(isValidPresence({ u: '5', c: 8, s: [[]] }), false); +// assert.strictEqual(isValidPresence({ u: '5', c: 8, s: [[1]] }), false); +// assert.strictEqual(isValidPresence({ u: '5', c: 8, s: [[1, 2]] }), true); +// assert.strictEqual( +// isValidPresence({ u: '5', c: 8, s: [[1, 2, 3]] }), +// false +// ); +// assert.strictEqual( +// isValidPresence({ u: '5', c: 8, s: [[1, 2], []] }), +// false +// ); +// assert.strictEqual( +// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, 6]] }), +// true +// ); +// assert.strictEqual( +// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, '6']] }), +// false +// ); +// assert.strictEqual( +// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, 6.1]] }), +// false +// ); +// assert.strictEqual( +// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, Infinity]] }), +// false +// ); +// assert.strictEqual( +// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, NaN]] }), +// false +// ); +// assert.strictEqual( +// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, -0]] }), +// true +// ); +// assert.strictEqual( +// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, -1]] }), +// true +// ); +// assert.strictEqual( +// isValidPresence({ u: '5', c: 8, s: [[1, 2], ['3', 0]] }), +// false +// ); +// assert.strictEqual( +// isValidPresence({ u: '5', c: 8, s: [[1, '2'], [4, 0]] }), +// false +// ); +// assert.strictEqual( +// isValidPresence({ u: '5', c: 8, s: [['1', 2], [4, 0]] }), +// false +// ); +// }); +// }); From bcb60c4b10bca1756aa8c46e9a3ea2551895fb34 Mon Sep 17 00:00:00 2001 From: curran Date: Sat, 13 Apr 2019 09:35:33 +0530 Subject: [PATCH 30/44] Add text0 transformPresence --- lib/text0.js | 22 ++++++++++++ test/text0-presence.js | 82 +++++++++++++----------------------------- 2 files changed, 46 insertions(+), 58 deletions(-) diff --git a/lib/text0.js b/lib/text0.js index c34dc51..3a57aa6 100644 --- a/lib/text0.js +++ b/lib/text0.js @@ -257,6 +257,28 @@ text.createPresence = function(presenceData) { return presenceData; }; +// Draws from https://github.com/Teamwork/ot-rich-text/blob/master/src/Operation.js +text.transformPresence = function(presence, operation, isOwnOperation) { + var user = presence.u; + var change = presence.c; + var selections = presence.s; + var side = isOwnOperation ? 'right' : 'left'; + var newSelections = new Array(selections.length); + + for (var i = 0, l = selections.length; i < l; ++i) { + newSelections[i] = [ + text.transformCursor(selections[i][0], operation, side), + text.transformCursor(selections[i][1], operation, side) + ]; + } + + return { + u: user, + c: change, + s: newSelections + } +} + //text.comparePresence = function(pres1, pres2) { // return JSON.stringify(pres1) === JSON.stringify(pres2); //}; diff --git a/test/text0-presence.js b/test/text0-presence.js index 054428b..e12588b 100644 --- a/test/text0-presence.js +++ b/test/text0-presence.js @@ -18,66 +18,32 @@ describe.only('text0 presence', () => { assert.strictEqual(createPresence(samplePresence), samplePresence); }); }); - // describe('transformPresence', () => { - // it('should preserve original presence in case of no-op', () => { - // assert.deepEqual( - // transformPresence(samplePresence, [], true), - // samplePresence - // ); - // assert.deepEqual( - // transformPresence(samplePresence, [], false), - // samplePresence - // ); - // }); - // it('should transform by op with matching path and subtype', () => { - - // const o = [ - // createInsertText('a') - // ]; - - // const op = [{ - // p: ['some', 'path'], - // t: otRichText.type.name, - // o - // }]; - - // const isOwnOp = true; - - // assert.deepEqual( - // transformPresence( samplePresence, op, isOwnOp), - // samplePresence.slice(0, samplePresence.length - 1).concat( - // otRichText.type.transformPresence(samplePresence[samplePresence.length - 1], o, isOwnOp) - // ) - // ); - // }); - - // it('should not transform by op with matching path and non-matching subtype', () => { - // assert.deepEqual( - // transformPresence( samplePresence, [{ - // p: ['some', 'path'], - // t: 'some-invalid-name', - // o: [ createInsertText('a') ] - // }] ), - // samplePresence - // ); - // }); + describe('transformPresence', () => { + it('should preserve original presence in case of no-op', () => { + assert.deepEqual( + transformPresence(samplePresence, [], true), + samplePresence + ); + assert.deepEqual( + transformPresence(samplePresence, [], false), + samplePresence + ); + }); + it('should transform against string insertion before selection', () => { + assert.deepEqual( + transformPresence( + samplePresence, + [{ p: [], i: 'a' }], // Insert the 'a' character at position 0. + true + ), + Object.assign({}, samplePresence, { + s: [ [ 2, 2 ], [ 6, 8 ]] + }) + ); + }); - // it('should not transform by op with non-matching path and matching subtype', () => { - // assert.deepEqual( - // transformPresence( samplePresence, [{ - // p: ['some', 'other', 'path'], - // t: otRichText.type.name, - // o: [ createInsertText('a') ] - // }] ), - // samplePresence - // ); - // }); - // }); + }); }); - -// -// -//describe('transformPresence', () => { // it('top level string operations', () => { // // Before selection // assert.deepEqual( From 3c9c3d0707a9a218f4591be8514d7055839f6e71 Mon Sep 17 00:00:00 2001 From: curran Date: Sat, 13 Apr 2019 11:16:07 +0530 Subject: [PATCH 31/44] Add tests for isOwnPosition behavior --- lib/text0.js | 1 + test/text0-presence.js | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/text0.js b/lib/text0.js index 3a57aa6..c0ca976 100644 --- a/lib/text0.js +++ b/lib/text0.js @@ -169,6 +169,7 @@ var transformPosition = function(pos, c, insertAfter) { // whether the cursor position is pushed after an insert (true) or before it // (false). text.transformCursor = function(position, op, side) { + var insertAfter = side === 'right'; for (var i = 0; i < op.length; i++) { position = transformPosition(position, op[i], insertAfter); diff --git a/test/text0-presence.js b/test/text0-presence.js index e12588b..a0e23d6 100644 --- a/test/text0-presence.js +++ b/test/text0-presence.js @@ -18,6 +18,7 @@ describe.only('text0 presence', () => { assert.strictEqual(createPresence(samplePresence), samplePresence); }); }); + describe('transformPresence', () => { it('should preserve original presence in case of no-op', () => { assert.deepEqual( @@ -29,11 +30,12 @@ describe.only('text0 presence', () => { samplePresence ); }); - it('should transform against string insertion before selection', () => { + + it('should transform against string insertion', () => { assert.deepEqual( transformPresence( samplePresence, - [{ p: [], i: 'a' }], // Insert the 'a' character at position 0. + [{ p: 0, i: 'a' }], // Insert the 'a' character at position 0. true ), Object.assign({}, samplePresence, { @@ -42,6 +44,22 @@ describe.only('text0 presence', () => { ); }); + it('should transform against own string insertion at presence position', () => { + const isOwnOperation = true; + assert.deepEqual( + transformPresence( samplePresence, [{ p: 1, i: 'a' }], isOwnOperation), + Object.assign({}, samplePresence, { s: [ [ 2, 2 ], [ 6, 8 ]] }) + ); + }); + + it('should transform against non-own string insertion at presence position', () => { + const isOwnOperation = false; + assert.deepEqual( + transformPresence( samplePresence, [{ p: 1, i: 'a' }], isOwnOperation), + Object.assign({}, samplePresence, { s: [ [ 1, 1 ], [ 6, 8 ]] }) + ); + }); + }); }); // it('top level string operations', () => { From a92c3767a069ea70ceeb695ee5575920871b1307 Mon Sep 17 00:00:00 2001 From: curran Date: Sat, 13 Apr 2019 11:21:32 +0530 Subject: [PATCH 32/44] Clean up PR diff --- lib/text0.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/text0.js b/lib/text0.js index c0ca976..3a57aa6 100644 --- a/lib/text0.js +++ b/lib/text0.js @@ -169,7 +169,6 @@ var transformPosition = function(pos, c, insertAfter) { // whether the cursor position is pushed after an insert (true) or before it // (false). text.transformCursor = function(position, op, side) { - var insertAfter = side === 'right'; for (var i = 0; i < op.length; i++) { position = transformPosition(position, op[i], insertAfter); From 072d49b0a1ee73a2b92eed708e053fffda2fa773 Mon Sep 17 00:00:00 2001 From: curran Date: Sat, 13 Apr 2019 11:23:35 +0530 Subject: [PATCH 33/44] Clean up PR diff --- lib/json0.js | 1 + test/text0-presence.js | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/json0.js b/lib/json0.js index 3b7c19d..5815a98 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -831,3 +831,4 @@ var text = require('./text0'); json.registerSubtype(text); module.exports = json; + diff --git a/test/text0-presence.js b/test/text0-presence.js index a0e23d6..aaf185f 100644 --- a/test/text0-presence.js +++ b/test/text0-presence.js @@ -59,7 +59,6 @@ describe.only('text0 presence', () => { Object.assign({}, samplePresence, { s: [ [ 1, 1 ], [ 6, 8 ]] }) ); }); - }); }); // it('top level string operations', () => { From b8ca4282754c26a52e74c214f847e3733deefe9b Mon Sep 17 00:00:00 2001 From: curran Date: Sat, 13 Apr 2019 11:47:36 +0530 Subject: [PATCH 34/44] Revise shape of presence data structure --- README.md | 14 ++++++-------- lib/json0.js | 14 ++++++-------- test/json0-presence.js | 22 +++++++++++----------- test/text0-presence.js | 2 +- 4 files changed, 24 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 2815608..707ad36 100644 --- a/README.md +++ b/README.md @@ -301,19 +301,17 @@ offset in a string. `TEXT` must be contained at the location specified. The shape of our presence data is as follows: ```js -[ - 'some', 'path', // Path of the presence. - 'ot-rich-text', // Subtype of the presence (a registered subtype). - { // Opaque presence object (subtype-specific structure). - u: '123', // An example of an ot-rich-text presence object. +{ + p: ['some', 'path'], // Path of the presence. + t: 'ot-rich-text', // Subtype of the presence (a registered subtype). + s: { // Opaque presence object (subtype-specific structure). + u: '123', // An example of an ot-rich-text presence object. c: 8, s: [ [ 1, 1 ], [ 5, 7 ]] } -] +} ``` -The rest of the README content is from the original repo https://github.com/ottypes/json0. - # Commentary This library was written a couple of years ago by [Jeremy Apthorp](https://github.com/nornagon). It was diff --git a/lib/json0.js b/lib/json0.js index 5815a98..f803648 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -774,10 +774,9 @@ json.unpackPresence = unpackPresence; json.transformPresence = function(presence, op, isOwnOp) { if (op.length === 0) return presence; - var unpacked = unpackPresence(presence); - var presencePath = unpacked.presencePath; - var presenceType = unpacked.presenceType; - var subPresence = unpacked.subPresence; + var presencePath = presence.p; + var presenceType = presence.t + var subPresence = presence.s; // TODO integrate any valid ideas from here. //for (let c of op){ @@ -811,10 +810,9 @@ json.transformPresence = function(presence, op, isOwnOp) { // Transform against subtype ops. if (c.t && c.t === presenceType && json.pathMatches(c.p, presencePath)) { - return presencePath.concat( - presenceType, - subtypes[presenceType].transformPresence(subPresence, c.o, isOwnOp) - ); + return Object.assign({}, presence, { + s: subtypes[presenceType].transformPresence(subPresence, c.o, isOwnOp) + }); } // TODO transform against non-subtype ops. diff --git a/test/json0-presence.js b/test/json0-presence.js index 63bbaaf..151baf2 100644 --- a/test/json0-presence.js +++ b/test/json0-presence.js @@ -6,21 +6,21 @@ const { createInsertText } = otRichText.Action json.registerSubtype(otRichText.type); -const { createPresence, transformPresence } = json; +const { createPresence, transformPresence, unpackPresence } = json; -const samplePresence = [ - 'some', 'path', // Path of the presence. - 'ot-rich-text', // Subtype of the presence (a registered subtype). - { // Opaque presence object (subtype-specific structure). - u: '123', // An example of an ot-rich-text presence object. +const samplePresence = { + p: ['some', 'path'], // Path of the presence. + t: 'ot-rich-text', // Subtype of the presence (a registered subtype). + s: { // Opaque presence object (subtype-specific structure). + u: '123', // An example of an ot-rich-text presence object. c: 8, s: [ [ 1, 1 ], [ 5, 7 ]] } -]; +} //// These tests are inspired by the ones found here: //// https://github.com/Teamwork/ot-rich-text/blob/master/test/Operation.js -describe('json0 presence', () => { +describe.only('json0 presence', () => { describe('createPresence', () => { it('should return the passed in presence object', () => { assert.strictEqual(createPresence(samplePresence), samplePresence); @@ -53,9 +53,9 @@ describe('json0 presence', () => { assert.deepEqual( transformPresence( samplePresence, op, isOwnOp), - samplePresence.slice(0, samplePresence.length - 1).concat( - otRichText.type.transformPresence(samplePresence[samplePresence.length - 1], o, isOwnOp) - ) + Object.assign({}, samplePresence, { + s: otRichText.type.transformPresence(samplePresence.s, o, isOwnOp) + }) ); }); diff --git a/test/text0-presence.js b/test/text0-presence.js index aaf185f..35721b4 100644 --- a/test/text0-presence.js +++ b/test/text0-presence.js @@ -12,7 +12,7 @@ const samplePresence = { // These tests are inspired by the ones found here: // https://github.com/Teamwork/ot-rich-text/blob/master/test/Operation.js -describe.only('text0 presence', () => { +describe('text0 presence', () => { describe('createPresence', () => { it('should return the passed in presence object', () => { assert.strictEqual(createPresence(samplePresence), samplePresence); From 424c4244d24c0fc06f422a78b6fe16036425790e Mon Sep 17 00:00:00 2001 From: curran Date: Sat, 13 Apr 2019 11:50:05 +0530 Subject: [PATCH 35/44] Simplify implementation --- lib/json0.js | 49 ++----------------------------------------------- 1 file changed, 2 insertions(+), 47 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index f803648..ff33ed9 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -754,52 +754,7 @@ json.comparePresence = function(pres1, pres2) { // }; // -// Unpacks the presence array into meaningful parts. -function unpackPresence(presence) { - - // Validate to help app developers find problems. - if(!presence.slice) { - console.log('Expected presence to be an array. Received:'); - console.log(presence); - } - - return { - presencePath: presence.slice(0, presence.length - 2), - presenceType: presence[presence.length - 2], - subPresence: presence[presence.length - 1] - }; -}; -json.unpackPresence = unpackPresence; - json.transformPresence = function(presence, op, isOwnOp) { - if (op.length === 0) return presence; - - var presencePath = presence.p; - var presenceType = presence.t - var subPresence = presence.s; - - // TODO integrate any valid ideas from here. - //for (let c of op){ - // if(c.si || c.sd){ - // convertFromText(c) - // } - // if(c.t === 'text0') { - // //json.canOpAffectPath = function(op, path) { - // presence = Object.assign({}, presence, { - // s: presence.s.map(selection => { - // const path = selection.slice(0, selection.length - 2); - // if(canOpAffectPath(c, path)) { - // const [start, end] = selection.slice(selection.length - 2); - // return path.concat([ - // transformCursor(start, c.o), - // transformCursor(end, c.o), - // ]); - // } - // return selection; - // }) - // }); - // } - //} for (var i = 0; i < op.length; i++) { var c = op[i]; @@ -809,9 +764,9 @@ json.transformPresence = function(presence, op, isOwnOp) { // convertFromText(c); // Transform against subtype ops. - if (c.t && c.t === presenceType && json.pathMatches(c.p, presencePath)) { + if (c.t && c.t === presence.t && json.pathMatches(c.p, presence.p)) { return Object.assign({}, presence, { - s: subtypes[presenceType].transformPresence(subPresence, c.o, isOwnOp) + s: subtypes[presence.t].transformPresence(presence.s, c.o, isOwnOp) }); } From d1edf5acd1f850d32dcd50292e0ff8c1c1311fee Mon Sep 17 00:00:00 2001 From: curran Date: Sat, 13 Apr 2019 12:03:37 +0530 Subject: [PATCH 36/44] Add test for transformPresence by op with multiple components --- lib/json0.js | 2 +- test/json0-presence.js | 31 +++++++++++++++++++++++-------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index ff33ed9..631dbf4 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -765,7 +765,7 @@ json.transformPresence = function(presence, op, isOwnOp) { // Transform against subtype ops. if (c.t && c.t === presence.t && json.pathMatches(c.p, presence.p)) { - return Object.assign({}, presence, { + presence = Object.assign({}, presence, { s: subtypes[presence.t].transformPresence(presence.s, c.o, isOwnOp) }); } diff --git a/test/json0-presence.js b/test/json0-presence.js index 151baf2..697be94 100644 --- a/test/json0-presence.js +++ b/test/json0-presence.js @@ -2,7 +2,7 @@ const assert = require('assert'); const json = require('../lib/json0'); const otRichText = require('@teamwork/ot-rich-text') -const { createInsertText } = otRichText.Action +const { createInsertText, createRetain, createDelete } = otRichText.Action json.registerSubtype(otRichText.type); @@ -39,9 +39,7 @@ describe.only('json0 presence', () => { }); it('should transform by op with matching path and subtype', () => { - const o = [ - createInsertText('a') - ]; + const o = [ createInsertText('a') ]; const op = [{ p: ['some', 'path'], @@ -59,24 +57,41 @@ describe.only('json0 presence', () => { ); }); + it('should transform by op with multiple components', () => { + const o1 = [ createInsertText('a') ]; + const o2 = [ createRetain(3), createDelete(2), createInsertText('a') ]; + + let s = samplePresence.s; + s = otRichText.type.transformPresence(s, o1); + s = otRichText.type.transformPresence(s, o2); + + assert.deepEqual( + transformPresence(samplePresence, [ + { p: ['some', 'path'], t: otRichText.type.name, o: o1 }, + { p: ['some', 'path'], t: otRichText.type.name, o: o2 } + ]), + Object.assign({}, samplePresence, { s }) + ); + }); + it('should not transform by op with matching path and non-matching subtype', () => { assert.deepEqual( - transformPresence( samplePresence, [{ + transformPresence(samplePresence, [{ p: ['some', 'path'], t: 'some-invalid-name', o: [ createInsertText('a') ] - }] ), + }]), samplePresence ); }); it('should not transform by op with non-matching path and matching subtype', () => { assert.deepEqual( - transformPresence( samplePresence, [{ + transformPresence(samplePresence, [{ p: ['some', 'other', 'path'], t: otRichText.type.name, o: [ createInsertText('a') ] - }] ), + }]), samplePresence ); }); From e7c6f03d1b11772268869f21f4460ed72604bdb9 Mon Sep 17 00:00:00 2001 From: curran Date: Sat, 13 Apr 2019 14:36:14 +0530 Subject: [PATCH 37/44] Finalize tests for text0-presence --- lib/text0.js | 6 +- test/json0-presence.js | 14 +- test/text0-presence.js | 443 ++++++----------------------------------- 3 files changed, 73 insertions(+), 390 deletions(-) diff --git a/lib/text0.js b/lib/text0.js index 3a57aa6..cf7a64a 100644 --- a/lib/text0.js +++ b/lib/text0.js @@ -279,8 +279,8 @@ text.transformPresence = function(presence, operation, isOwnOperation) { } } -//text.comparePresence = function(pres1, pres2) { -// return JSON.stringify(pres1) === JSON.stringify(pres2); -//}; +text.comparePresence = function(pres1, pres2) { + return JSON.stringify(pres1) === JSON.stringify(pres2); +}; require('./bootstrapTransform')(text, transformComponent, checkValidOp, append); diff --git a/test/json0-presence.js b/test/json0-presence.js index 697be94..5ba1f6f 100644 --- a/test/json0-presence.js +++ b/test/json0-presence.js @@ -37,7 +37,7 @@ describe.only('json0 presence', () => { samplePresence ); }); - it('should transform by op with matching path and subtype', () => { + it('should transform by subtype op with matching path and subtype', () => { const o = [ createInsertText('a') ]; @@ -58,7 +58,7 @@ describe.only('json0 presence', () => { }); it('should transform by op with multiple components', () => { - const o1 = [ createInsertText('a') ]; + const o1 = [ createInsertText('foo') ]; const o2 = [ createRetain(3), createDelete(2), createInsertText('a') ]; let s = samplePresence.s; @@ -96,6 +96,16 @@ describe.only('json0 presence', () => { ); }); }); + //it('should transform by text op with matching path and subtype', () => { + // const op = [{ p: 0, si: 'a' }]; + // const isOwnOp = true; + // assert.deepEqual( + // transformPresence( samplePresence, op, isOwnOp), + // Object.assign({}, samplePresence, { + // s: otRichText.type.transformPresence(samplePresence.s, o, isOwnOp) + // }) + // ); + //}); }); // //describe('transformPresence', () => { diff --git a/test/text0-presence.js b/test/text0-presence.js index 35721b4..7983722 100644 --- a/test/text0-presence.js +++ b/test/text0-presence.js @@ -1,10 +1,10 @@ const assert = require('assert'); const text = require('../lib/text0'); -const { createPresence, transformPresence } = text; +const { createPresence, comparePresence, transformPresence } = text; // Inspired by ot-rich-text presence structure. -const samplePresence = { +const sampleTextPresence = { u: '123', c: 8, s: [ [ 1, 1 ], [ 5, 7 ]] @@ -12,33 +12,48 @@ const samplePresence = { // These tests are inspired by the ones found here: // https://github.com/Teamwork/ot-rich-text/blob/master/test/Operation.js -describe('text0 presence', () => { +describe.only('text0 presence', () => { describe('createPresence', () => { it('should return the passed in presence object', () => { - assert.strictEqual(createPresence(samplePresence), samplePresence); + assert.strictEqual(createPresence(sampleTextPresence), sampleTextPresence); + }); + }); + + describe('comparePresence', () => { + it('should return true if equal', () => { + assert(comparePresence(sampleTextPresence, sampleTextPresence)); + }); + + it('should return false if not equal', () => { + assert(!comparePresence( + sampleTextPresence, + Object.assign({}, sampleTextPresence, { + s: [ [ 2, 2 ], [ 6, 8 ]] + }) + )); }); }); describe('transformPresence', () => { it('should preserve original presence in case of no-op', () => { assert.deepEqual( - transformPresence(samplePresence, [], true), - samplePresence + transformPresence(sampleTextPresence, [], true), + sampleTextPresence ); assert.deepEqual( - transformPresence(samplePresence, [], false), - samplePresence + transformPresence(sampleTextPresence, [], false), + sampleTextPresence ); }); it('should transform against string insertion', () => { assert.deepEqual( transformPresence( - samplePresence, + sampleTextPresence, [{ p: 0, i: 'a' }], // Insert the 'a' character at position 0. true ), - Object.assign({}, samplePresence, { + Object.assign({}, sampleTextPresence, { s: [ [ 2, 2 ], [ 6, 8 ]] }) ); @@ -47,388 +62,46 @@ describe('text0 presence', () => { it('should transform against own string insertion at presence position', () => { const isOwnOperation = true; assert.deepEqual( - transformPresence( samplePresence, [{ p: 1, i: 'a' }], isOwnOperation), - Object.assign({}, samplePresence, { s: [ [ 2, 2 ], [ 6, 8 ]] }) + transformPresence( sampleTextPresence, [{ p: 1, i: 'a' }], isOwnOperation), + Object.assign({}, sampleTextPresence, { s: [ [ 2, 2 ], [ 6, 8 ]] }) ); }); it('should transform against non-own string insertion at presence position', () => { const isOwnOperation = false; assert.deepEqual( - transformPresence( samplePresence, [{ p: 1, i: 'a' }], isOwnOperation), - Object.assign({}, samplePresence, { s: [ [ 1, 1 ], [ 6, 8 ]] }) + transformPresence( sampleTextPresence, [{ p: 1, i: 'a' }], isOwnOperation), + Object.assign({}, sampleTextPresence, { s: [ [ 1, 1 ], [ 6, 8 ]] }) ); }); - }); -}); -// it('top level string operations', () => { -// // Before selection -// assert.deepEqual( -// transformPresence( -// { u: 'user', c: 8, s: [[5, 7]] }, -// [{ p: [0], si: 'a' }], // Insert the 'a' character at position 0. -// true -// ), -// { u: 'user', c: 8, s: [[6, 8]] } -// ); -// -// // Inside selection -// assert.deepEqual( -// transformPresence( -// { u: 'user', c: 8, s: [[5, 7]] }, -// [{ p: [6], si: 'a' }], -// true -// ), -// { u: 'user', c: 8, s: [[5, 8]] } -// ); -// -// // Multiple characters -// assert.deepEqual( -// transformPresence( -// { u: 'user', c: 8, s: [[5, 7]] }, -// [{ p: [6], si: 'abc' }], -// true -// ), -// { u: 'user', c: 8, s: [[5, 10]] } -// ); -// -// // String deletion -// assert.deepEqual( -// transformPresence( -// { u: 'user', c: 8, s: [[5, 7]] }, -// [{ p: [5], sd: 'abc' }], -// true -// ), -// { u: 'user', c: 8, s: [[5, 5]] } -// ); -// -// // After selection -// assert.deepEqual( -// transformPresence( -// { u: 'user', c: 8, s: [[5, 7]] }, -// [{ p: [8], si: 'a' }], -// true -// ), -// { u: 'user', c: 8, s: [[5, 7]] } -// ); -// }); -// -// it('nested string operations', () => { -// // Single level -// assert.deepEqual( -// transformPresence( -// { u: 'user', c: 8, s: [['content', 5, 7]] }, -// [{ p: ['content', 0], si: 'a' }], // Insert the 'a' character at position 0. -// true -// ), -// { u: 'user', c: 8, s: [['content', 6, 8]] } -// ); -// -// // Multiple level -// assert.deepEqual( -// transformPresence( -// { u: 'user', c: 8, s: [['content', 'deeply', 'nested', 5, 7]] }, -// [{ p: ['content', 'deeply', 'nested', 0], si: 'a' }], // Insert the 'a' character at position 0. -// true -// ), -// { u: 'user', c: 8, s: [['content', 'deeply', 'nested', 6, 8]] } -// ); -// -// // Op not matching path -// assert.deepEqual( -// transformPresence( -// { u: 'user', c: 8, s: [['content', 'deeply', 'nested', 5, 7]] }, -// [{ p: ['content', 'somewhere', 'else', 0], si: 'a' }], // Insert the 'a' character at position 0. -// true -// ), -// { u: 'user', c: 8, s: [['content', 'deeply', 'nested', 5, 7]] } -// ); -// -// // Multiple selections -// assert.deepEqual( -// transformPresence( -// { -// u: 'user', -// c: 8, -// s: [ -// ['content', 'deeply', 'nested', 5, 7], -// ['content', 'somewhere', 'else', 5, 7] -// ] -// }, -// [{ p: ['content', 'somewhere', 'else', 0], si: 'a' }], // Insert the 'a' character at position 0. -// true -// ), -// { -// u: 'user', -// c: 8, -// s: [ -// ['content', 'deeply', 'nested', 5, 7], -// ['content', 'somewhere', 'else', 6, 8] -// ] -// } -// ); -// }); -// - // TODO get to this point - - //assert.deepEqual( - // transformPresence( - // {u: 'user', c: 8, s: [[5, 7]]}, - // [createRetain(3), createDelete(2), createInsertText('a')], - // true, - // ), - // { - // u: 'user', - // c: 8, - // s: [[4, 6]], - // }, - //); - //assert.deepEqual( - // transformPresence( - // { - // u: 'user', - // c: 8, - // s: [[5, 7]], - // }, - // [createRetain(3), createDelete(2), createInsertText('a')], - // false, - // ), - // { - // u: 'user', - // c: 8, - // s: [[3, 6]], - // }, - //); - - //assert.deepEqual( - // transformPresence( - // { - // u: 'user', - // c: 8, - // s: [[5, 7]], - // }, - // [createRetain(5), createDelete(2), createInsertText('a')], - // true, - // ), - // { - // u: 'user', - // c: 8, - // s: [[6, 6]], - // }, - //); - //assert.deepEqual( - // transformPresence( - // { - // u: 'user', - // c: 8, - // s: [[5, 7]], - // }, - // [createRetain(5), createDelete(2), createInsertText('a')], - // false, - // ), - // { - // u: 'user', - // c: 8, - // s: [[5, 5]], - // }, - //); - //assert.deepEqual( - // transformPresence( - // { - // u: 'user', - // c: 8, - // s: [[5, 7], [8, 2]], - // }, - // [createInsertText('a')], - // false, - // ), - // { - // u: 'user', - // c: 8, - // s: [[6, 8], [9, 3]], - // }, - //); + it('should transform against string deletion', () => { + assert.deepEqual( + transformPresence( + sampleTextPresence, + [{ p: 0, d: 'a' }], + true + ), + Object.assign({}, sampleTextPresence, { + s: [ [ 0, 0 ], [ 4, 6 ]] + }) + ); + }); - //assert.deepEqual( - // transformPresence( - // { - // u: 'user', - // c: 8, - // s: [[1, 1], [2, 2]], - // }, - // [createInsertText('a')], - // false, - // ), - // { - // u: 'user', - // c: 8, - // s: [[2, 2], [3, 3]], - // }, - //); -//}); -// -// describe('comparePresence', () => { -// it('basic tests', () => { -// assert.strictEqual(comparePresence(), true); -// assert.strictEqual(comparePresence(undefined, undefined), true); -// assert.strictEqual(comparePresence(null, null), true); -// assert.strictEqual(comparePresence(null, undefined), false); -// assert.strictEqual(comparePresence(undefined, null), false); -// assert.strictEqual( -// comparePresence(undefined, { u: '', c: 0, s: [] }), -// false -// ); -// assert.strictEqual(comparePresence(null, { u: '', c: 0, s: [] }), false); -// assert.strictEqual( -// comparePresence({ u: '', c: 0, s: [] }, undefined), -// false -// ); -// assert.strictEqual(comparePresence({ u: '', c: 0, s: [] }, null), false); -// -// assert.strictEqual( -// comparePresence( -// { u: 'user', c: 8, s: [[1, 2]] }, -// { u: 'user', c: 8, s: [[1, 2]] } -// ), -// true -// ); -// assert.strictEqual( -// comparePresence( -// { u: 'user', c: 8, s: [[1, 2], [4, 6]] }, -// { u: 'user', c: 8, s: [[1, 2], [4, 6]] } -// ), -// true -// ); -// assert.strictEqual( -// comparePresence( -// { u: 'user', c: 8, s: [[1, 2]], unknownProperty: 5 }, -// { u: 'user', c: 8, s: [[1, 2]] } -// ), -// true -// ); -// assert.strictEqual( -// comparePresence( -// { u: 'user', c: 8, s: [[1, 2]] }, -// { u: 'user', c: 8, s: [[1, 2]], unknownProperty: 5 } -// ), -// true -// ); -// assert.strictEqual( -// comparePresence( -// { u: 'user', c: 8, s: [[1, 2]] }, -// { u: 'userX', c: 8, s: [[1, 2]] } -// ), -// false -// ); -// assert.strictEqual( -// comparePresence( -// { u: 'user', c: 8, s: [[1, 2]] }, -// { u: 'user', c: 9, s: [[1, 2]] } -// ), -// false -// ); -// assert.strictEqual( -// comparePresence( -// { u: 'user', c: 8, s: [[1, 2]] }, -// { u: 'user', c: 8, s: [[3, 2]] } -// ), -// false -// ); -// assert.strictEqual( -// comparePresence( -// { u: 'user', c: 8, s: [[1, 2]] }, -// { u: 'user', c: 8, s: [[1, 3]] } -// ), -// false -// ); -// assert.strictEqual( -// comparePresence( -// { u: 'user', c: 8, s: [[9, 8], [1, 2]] }, -// { u: 'user', c: 8, s: [[9, 8], [3, 2]] } -// ), -// false -// ); -// assert.strictEqual( -// comparePresence( -// { u: 'user', c: 8, s: [[9, 8], [1, 2]] }, -// { u: 'user', c: 8, s: [[9, 8], [1, 3]] } -// ), -// false -// ); -// assert.strictEqual( -// comparePresence( -// { u: 'user', c: 8, s: [[9, 8], [1, 2]] }, -// { u: 'user', c: 8, s: [[9, 8]] } -// ), -// false -// ); -// }); -// }); -// -// describe('isValidPresence', () => { -// it('basic tests', () => { -// assert.strictEqual(isValidPresence(), false); -// assert.strictEqual(isValidPresence(null), false); -// assert.strictEqual(isValidPresence([]), false); -// assert.strictEqual(isValidPresence({}), false); -// assert.strictEqual(isValidPresence({ u: 5, c: 8, s: [] }), false); -// assert.strictEqual(isValidPresence({ u: '5', c: '8', s: [] }), false); -// assert.strictEqual(isValidPresence({ u: '5', c: 8.5, s: [] }), false); -// assert.strictEqual(isValidPresence({ u: '5', c: Infinity, s: [] }), false); -// assert.strictEqual(isValidPresence({ u: '5', c: NaN, s: [] }), false); -// assert.strictEqual(isValidPresence({ u: '5', c: 8, s: {} }), false); -// assert.strictEqual(isValidPresence({ u: '5', c: 8, s: [] }), true); -// assert.strictEqual(isValidPresence({ u: '5', c: 8, s: [[]] }), false); -// assert.strictEqual(isValidPresence({ u: '5', c: 8, s: [[1]] }), false); -// assert.strictEqual(isValidPresence({ u: '5', c: 8, s: [[1, 2]] }), true); -// assert.strictEqual( -// isValidPresence({ u: '5', c: 8, s: [[1, 2, 3]] }), -// false -// ); -// assert.strictEqual( -// isValidPresence({ u: '5', c: 8, s: [[1, 2], []] }), -// false -// ); -// assert.strictEqual( -// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, 6]] }), -// true -// ); -// assert.strictEqual( -// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, '6']] }), -// false -// ); -// assert.strictEqual( -// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, 6.1]] }), -// false -// ); -// assert.strictEqual( -// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, Infinity]] }), -// false -// ); -// assert.strictEqual( -// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, NaN]] }), -// false -// ); -// assert.strictEqual( -// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, -0]] }), -// true -// ); -// assert.strictEqual( -// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, -1]] }), -// true -// ); -// assert.strictEqual( -// isValidPresence({ u: '5', c: 8, s: [[1, 2], ['3', 0]] }), -// false -// ); -// assert.strictEqual( -// isValidPresence({ u: '5', c: 8, s: [[1, '2'], [4, 0]] }), -// false -// ); -// assert.strictEqual( -// isValidPresence({ u: '5', c: 8, s: [['1', 2], [4, 0]] }), -// false -// ); -// }); -// }); + it('should transform against ops with multiple components deletion', () => { + assert.deepEqual( + transformPresence( + sampleTextPresence, + [ + { p: 0, i: 'a' }, + { p: 6, d: 'b' } + ], + true + ), + Object.assign({}, sampleTextPresence, { + s: [ [ 2, 2 ], [ 6, 7 ]] + }) + ); + }); + }); +}); From 19431c7c277586308c848145425e1da7d96d544b Mon Sep 17 00:00:00 2001 From: curran Date: Sat, 13 Apr 2019 15:17:41 +0530 Subject: [PATCH 38/44] Add transformPresence tests for embedded text0 ops --- lib/json0.js | 11 +- test/json0-presence.js | 436 +++++------------------------------------ test/text0-presence.js | 2 +- 3 files changed, 55 insertions(+), 394 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index 631dbf4..85c01b4 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -759,9 +759,9 @@ json.transformPresence = function(presence, op, isOwnOp) { var c = op[i]; // convert old string ops to use subtype for backwards compatibility - // TODO cover with tests - // if (c.si != null || c.sd != null) - // convertFromText(c); + if (c.si != null || c.sd != null) { + convertFromText(c); + } // Transform against subtype ops. if (c.t && c.t === presence.t && json.pathMatches(c.p, presence.p)) { @@ -770,6 +770,11 @@ json.transformPresence = function(presence, op, isOwnOp) { }); } + // convert back to old string ops + if (c.t === 'text0') { + convertToText(c); + } + // TODO transform against non-subtype ops. }; return presence; diff --git a/test/json0-presence.js b/test/json0-presence.js index 5ba1f6f..28ceb51 100644 --- a/test/json0-presence.js +++ b/test/json0-presence.js @@ -1,13 +1,15 @@ const assert = require('assert'); const json = require('../lib/json0'); +const text = require('../lib/text0'); const otRichText = require('@teamwork/ot-rich-text') const { createInsertText, createRetain, createDelete } = otRichText.Action json.registerSubtype(otRichText.type); -const { createPresence, transformPresence, unpackPresence } = json; +const { createPresence, comparePresence, transformPresence } = json; +// Sample presence object using ot-rich-text sub-presence. const samplePresence = { p: ['some', 'path'], // Path of the presence. t: 'ot-rich-text', // Subtype of the presence (a registered subtype). @@ -18,6 +20,11 @@ const samplePresence = { } } +// Sample presence object using text0 sub-presence. +const sampleTextPresence = Object.assign({}, samplePresence, { + t: 'text0' +}); + //// These tests are inspired by the ones found here: //// https://github.com/Teamwork/ot-rich-text/blob/master/test/Operation.js describe.only('json0 presence', () => { @@ -26,6 +33,17 @@ describe.only('json0 presence', () => { assert.strictEqual(createPresence(samplePresence), samplePresence); }); }); + + describe('comparePresence', () => { + it('should return true if equal', () => { + assert(comparePresence(samplePresence, samplePresence)); + }); + + it('should return false if not equal', () => { + assert(!comparePresence(samplePresence, sampleTextPresence)); + }); + }); + describe('transformPresence', () => { it('should preserve original presence in case of no-op', () => { assert.deepEqual( @@ -37,16 +55,10 @@ describe.only('json0 presence', () => { samplePresence ); }); - it('should transform by subtype op with matching path and subtype', () => { + it('should transform by subtype op with matching path and subtype', () => { const o = [ createInsertText('a') ]; - - const op = [{ - p: ['some', 'path'], - t: otRichText.type.name, - o - }]; - + const op = [{ p: ['some', 'path'], t: otRichText.type.name, o }]; const isOwnOp = true; assert.deepEqual( @@ -95,388 +107,32 @@ describe.only('json0 presence', () => { samplePresence ); }); - }); - //it('should transform by text op with matching path and subtype', () => { - // const op = [{ p: 0, si: 'a' }]; - // const isOwnOp = true; - // assert.deepEqual( - // transformPresence( samplePresence, op, isOwnOp), - // Object.assign({}, samplePresence, { - // s: otRichText.type.transformPresence(samplePresence.s, o, isOwnOp) - // }) - // ); - //}); -}); -// -//describe('transformPresence', () => { -// it('top level string operations', () => { -// // Before selection -// assert.deepEqual( -// transformPresence( -// { u: 'user', c: 8, s: [[5, 7]] }, -// [{ p: [0], si: 'a' }], // Insert the 'a' character at position 0. -// true -// ), -// { u: 'user', c: 8, s: [[6, 8]] } -// ); -// -// // Inside selection -// assert.deepEqual( -// transformPresence( -// { u: 'user', c: 8, s: [[5, 7]] }, -// [{ p: [6], si: 'a' }], -// true -// ), -// { u: 'user', c: 8, s: [[5, 8]] } -// ); -// -// // Multiple characters -// assert.deepEqual( -// transformPresence( -// { u: 'user', c: 8, s: [[5, 7]] }, -// [{ p: [6], si: 'abc' }], -// true -// ), -// { u: 'user', c: 8, s: [[5, 10]] } -// ); -// -// // String deletion -// assert.deepEqual( -// transformPresence( -// { u: 'user', c: 8, s: [[5, 7]] }, -// [{ p: [5], sd: 'abc' }], -// true -// ), -// { u: 'user', c: 8, s: [[5, 5]] } -// ); -// -// // After selection -// assert.deepEqual( -// transformPresence( -// { u: 'user', c: 8, s: [[5, 7]] }, -// [{ p: [8], si: 'a' }], -// true -// ), -// { u: 'user', c: 8, s: [[5, 7]] } -// ); -// }); -// -// it('nested string operations', () => { -// // Single level -// assert.deepEqual( -// transformPresence( -// { u: 'user', c: 8, s: [['content', 5, 7]] }, -// [{ p: ['content', 0], si: 'a' }], // Insert the 'a' character at position 0. -// true -// ), -// { u: 'user', c: 8, s: [['content', 6, 8]] } -// ); -// -// // Multiple level -// assert.deepEqual( -// transformPresence( -// { u: 'user', c: 8, s: [['content', 'deeply', 'nested', 5, 7]] }, -// [{ p: ['content', 'deeply', 'nested', 0], si: 'a' }], // Insert the 'a' character at position 0. -// true -// ), -// { u: 'user', c: 8, s: [['content', 'deeply', 'nested', 6, 8]] } -// ); -// -// // Op not matching path -// assert.deepEqual( -// transformPresence( -// { u: 'user', c: 8, s: [['content', 'deeply', 'nested', 5, 7]] }, -// [{ p: ['content', 'somewhere', 'else', 0], si: 'a' }], // Insert the 'a' character at position 0. -// true -// ), -// { u: 'user', c: 8, s: [['content', 'deeply', 'nested', 5, 7]] } -// ); -// -// // Multiple selections -// assert.deepEqual( -// transformPresence( -// { -// u: 'user', -// c: 8, -// s: [ -// ['content', 'deeply', 'nested', 5, 7], -// ['content', 'somewhere', 'else', 5, 7] -// ] -// }, -// [{ p: ['content', 'somewhere', 'else', 0], si: 'a' }], // Insert the 'a' character at position 0. -// true -// ), -// { -// u: 'user', -// c: 8, -// s: [ -// ['content', 'deeply', 'nested', 5, 7], -// ['content', 'somewhere', 'else', 6, 8] -// ] -// } -// ); -// }); -// - // TODO get to this point - //assert.deepEqual( - // transformPresence( - // {u: 'user', c: 8, s: [[5, 7]]}, - // [createRetain(3), createDelete(2), createInsertText('a')], - // true, - // ), - // { - // u: 'user', - // c: 8, - // s: [[4, 6]], - // }, - //); - //assert.deepEqual( - // transformPresence( - // { - // u: 'user', - // c: 8, - // s: [[5, 7]], - // }, - // [createRetain(3), createDelete(2), createInsertText('a')], - // false, - // ), - // { - // u: 'user', - // c: 8, - // s: [[3, 6]], - // }, - //); + it('should transform by text0 op', () => { + const o = [{ p: 0, i: 'a' }]; + const op = [{ p: ['some', 'path'], t: text.name, o }]; // text0 op + assert.deepEqual( + transformPresence(sampleTextPresence, op), + Object.assign({}, sampleTextPresence, { + s: text.transformPresence(sampleTextPresence.s, o) + }) + ); + }); - //assert.deepEqual( - // transformPresence( - // { - // u: 'user', - // c: 8, - // s: [[5, 7]], - // }, - // [createRetain(5), createDelete(2), createInsertText('a')], - // true, - // ), - // { - // u: 'user', - // c: 8, - // s: [[6, 6]], - // }, - //); - //assert.deepEqual( - // transformPresence( - // { - // u: 'user', - // c: 8, - // s: [[5, 7]], - // }, - // [createRetain(5), createDelete(2), createInsertText('a')], - // false, - // ), - // { - // u: 'user', - // c: 8, - // s: [[5, 5]], - // }, - //); + it('should transform by text op (auto-convert to & from internal text0 type)', () => { + const o = [{ p: 0, i: 'a' }]; + const op = [{ p: ['some', 'path', 0], si: 'a' }]; // json0 text op + const opClone = JSON.parse(JSON.stringify(op)); - //assert.deepEqual( - // transformPresence( - // { - // u: 'user', - // c: 8, - // s: [[5, 7], [8, 2]], - // }, - // [createInsertText('a')], - // false, - // ), - // { - // u: 'user', - // c: 8, - // s: [[6, 8], [9, 3]], - // }, - //); + assert.deepEqual( + transformPresence(sampleTextPresence, op), + Object.assign({}, sampleTextPresence, { + s: text.transformPresence(sampleTextPresence.s, o) + }) + ); - //assert.deepEqual( - // transformPresence( - // { - // u: 'user', - // c: 8, - // s: [[1, 1], [2, 2]], - // }, - // [createInsertText('a')], - // false, - // ), - // { - // u: 'user', - // c: 8, - // s: [[2, 2], [3, 3]], - // }, - //); -//}); -// -// describe('comparePresence', () => { -// it('basic tests', () => { -// assert.strictEqual(comparePresence(), true); -// assert.strictEqual(comparePresence(undefined, undefined), true); -// assert.strictEqual(comparePresence(null, null), true); -// assert.strictEqual(comparePresence(null, undefined), false); -// assert.strictEqual(comparePresence(undefined, null), false); -// assert.strictEqual( -// comparePresence(undefined, { u: '', c: 0, s: [] }), -// false -// ); -// assert.strictEqual(comparePresence(null, { u: '', c: 0, s: [] }), false); -// assert.strictEqual( -// comparePresence({ u: '', c: 0, s: [] }, undefined), -// false -// ); -// assert.strictEqual(comparePresence({ u: '', c: 0, s: [] }, null), false); -// -// assert.strictEqual( -// comparePresence( -// { u: 'user', c: 8, s: [[1, 2]] }, -// { u: 'user', c: 8, s: [[1, 2]] } -// ), -// true -// ); -// assert.strictEqual( -// comparePresence( -// { u: 'user', c: 8, s: [[1, 2], [4, 6]] }, -// { u: 'user', c: 8, s: [[1, 2], [4, 6]] } -// ), -// true -// ); -// assert.strictEqual( -// comparePresence( -// { u: 'user', c: 8, s: [[1, 2]], unknownProperty: 5 }, -// { u: 'user', c: 8, s: [[1, 2]] } -// ), -// true -// ); -// assert.strictEqual( -// comparePresence( -// { u: 'user', c: 8, s: [[1, 2]] }, -// { u: 'user', c: 8, s: [[1, 2]], unknownProperty: 5 } -// ), -// true -// ); -// assert.strictEqual( -// comparePresence( -// { u: 'user', c: 8, s: [[1, 2]] }, -// { u: 'userX', c: 8, s: [[1, 2]] } -// ), -// false -// ); -// assert.strictEqual( -// comparePresence( -// { u: 'user', c: 8, s: [[1, 2]] }, -// { u: 'user', c: 9, s: [[1, 2]] } -// ), -// false -// ); -// assert.strictEqual( -// comparePresence( -// { u: 'user', c: 8, s: [[1, 2]] }, -// { u: 'user', c: 8, s: [[3, 2]] } -// ), -// false -// ); -// assert.strictEqual( -// comparePresence( -// { u: 'user', c: 8, s: [[1, 2]] }, -// { u: 'user', c: 8, s: [[1, 3]] } -// ), -// false -// ); -// assert.strictEqual( -// comparePresence( -// { u: 'user', c: 8, s: [[9, 8], [1, 2]] }, -// { u: 'user', c: 8, s: [[9, 8], [3, 2]] } -// ), -// false -// ); -// assert.strictEqual( -// comparePresence( -// { u: 'user', c: 8, s: [[9, 8], [1, 2]] }, -// { u: 'user', c: 8, s: [[9, 8], [1, 3]] } -// ), -// false -// ); -// assert.strictEqual( -// comparePresence( -// { u: 'user', c: 8, s: [[9, 8], [1, 2]] }, -// { u: 'user', c: 8, s: [[9, 8]] } -// ), -// false -// ); -// }); -// }); -// -// describe('isValidPresence', () => { -// it('basic tests', () => { -// assert.strictEqual(isValidPresence(), false); -// assert.strictEqual(isValidPresence(null), false); -// assert.strictEqual(isValidPresence([]), false); -// assert.strictEqual(isValidPresence({}), false); -// assert.strictEqual(isValidPresence({ u: 5, c: 8, s: [] }), false); -// assert.strictEqual(isValidPresence({ u: '5', c: '8', s: [] }), false); -// assert.strictEqual(isValidPresence({ u: '5', c: 8.5, s: [] }), false); -// assert.strictEqual(isValidPresence({ u: '5', c: Infinity, s: [] }), false); -// assert.strictEqual(isValidPresence({ u: '5', c: NaN, s: [] }), false); -// assert.strictEqual(isValidPresence({ u: '5', c: 8, s: {} }), false); -// assert.strictEqual(isValidPresence({ u: '5', c: 8, s: [] }), true); -// assert.strictEqual(isValidPresence({ u: '5', c: 8, s: [[]] }), false); -// assert.strictEqual(isValidPresence({ u: '5', c: 8, s: [[1]] }), false); -// assert.strictEqual(isValidPresence({ u: '5', c: 8, s: [[1, 2]] }), true); -// assert.strictEqual( -// isValidPresence({ u: '5', c: 8, s: [[1, 2, 3]] }), -// false -// ); -// assert.strictEqual( -// isValidPresence({ u: '5', c: 8, s: [[1, 2], []] }), -// false -// ); -// assert.strictEqual( -// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, 6]] }), -// true -// ); -// assert.strictEqual( -// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, '6']] }), -// false -// ); -// assert.strictEqual( -// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, 6.1]] }), -// false -// ); -// assert.strictEqual( -// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, Infinity]] }), -// false -// ); -// assert.strictEqual( -// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, NaN]] }), -// false -// ); -// assert.strictEqual( -// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, -0]] }), -// true -// ); -// assert.strictEqual( -// isValidPresence({ u: '5', c: 8, s: [[1, 2], [3, -1]] }), -// true -// ); -// assert.strictEqual( -// isValidPresence({ u: '5', c: 8, s: [[1, 2], ['3', 0]] }), -// false -// ); -// assert.strictEqual( -// isValidPresence({ u: '5', c: 8, s: [[1, '2'], [4, 0]] }), -// false -// ); -// assert.strictEqual( -// isValidPresence({ u: '5', c: 8, s: [['1', 2], [4, 0]] }), -// false -// ); -// }); -// }); + // Ensure the original op survives. + assert.deepEqual(op, opClone); + }); + }); +}); diff --git a/test/text0-presence.js b/test/text0-presence.js index 7983722..b6abe86 100644 --- a/test/text0-presence.js +++ b/test/text0-presence.js @@ -12,7 +12,7 @@ const sampleTextPresence = { // These tests are inspired by the ones found here: // https://github.com/Teamwork/ot-rich-text/blob/master/test/Operation.js -describe.only('text0 presence', () => { +describe('text0 presence', () => { describe('createPresence', () => { it('should return the passed in presence object', () => { assert.strictEqual(createPresence(sampleTextPresence), sampleTextPresence); From 0f8290eac58a70123357ce4e990daf56e5f90fdd Mon Sep 17 00:00:00 2001 From: curran Date: Sat, 13 Apr 2019 15:29:54 +0530 Subject: [PATCH 39/44] Pass through path-only presence objects --- lib/json0.js | 3 +++ test/json0-presence.js | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/json0.js b/lib/json0.js index 85c01b4..08367b2 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -755,6 +755,9 @@ json.comparePresence = function(pres1, pres2) { // json.transformPresence = function(presence, op, isOwnOp) { + // Don't transform path-only presence objects. + if(!presence.t) return presence; + for (var i = 0; i < op.length; i++) { var c = op[i]; diff --git a/test/json0-presence.js b/test/json0-presence.js index 28ceb51..03e5f19 100644 --- a/test/json0-presence.js +++ b/test/json0-presence.js @@ -25,9 +25,13 @@ const sampleTextPresence = Object.assign({}, samplePresence, { t: 'text0' }); +// Sample presence object indicating only that +// the user has "joined" the document at the top level. +const samplePathOnlyPresence = { p: [] }; + //// These tests are inspired by the ones found here: //// https://github.com/Teamwork/ot-rich-text/blob/master/test/Operation.js -describe.only('json0 presence', () => { +describe('json0 presence', () => { describe('createPresence', () => { it('should return the passed in presence object', () => { assert.strictEqual(createPresence(samplePresence), samplePresence); @@ -134,5 +138,15 @@ describe.only('json0 presence', () => { // Ensure the original op survives. assert.deepEqual(op, opClone); }); + + it('should not break when given path-only presence', () => { + assert.deepEqual( + transformPresence(samplePathOnlyPresence, [{ + p: ['some', 'path', 0], + si: 'a' + }]), + samplePathOnlyPresence + ); + }); }); }); From 94540e1fac81fdc815a7f7934527b02117fd0504 Mon Sep 17 00:00:00 2001 From: curran Date: Mon, 15 Apr 2019 13:23:42 +0530 Subject: [PATCH 40/44] Remove dead code, minor cleanup --- lib/json0.js | 95 ------------------------------------------ test/json0-presence.js | 4 +- 2 files changed, 2 insertions(+), 97 deletions(-) diff --git a/lib/json0.js b/lib/json0.js index 08367b2..e0558ee 100644 --- a/lib/json0.js +++ b/lib/json0.js @@ -659,101 +659,6 @@ json.comparePresence = function(pres1, pres2) { return JSON.stringify(pres1) === JSON.stringify(pres2); }; -// var transformPosition = function(cursor, op, isOwnOp) { -// var cursor = clone(cursor); -// -// var opIsAncestor = cursor.length >= op.p.length; // true also if op is self -// var opIsSibling = cursor.length === op.p.length; // true also if op is self -// var opIsAncestorSibling = cursor.length >= op.p.length; // true also if op is self or sibling of self -// var equalUpTo = -1; -// for (var i = 0; i < op.p.length; i++) { -// if (op.p[i] !== cursor[i]) { -// opIsAncestor = false; -// if (i < op.p.length - 1) { -// opIsSibling = false; -// opIsAncestorSibling = false; -// } -// } -// if (equalUpTo === i - 1 && op.p[i] === cursor[i]) { -// equalUpTo += 1; -// } -// } -// -// if (opIsSibling) { -// if (op.sd) { -// cursor[cursor.length - 1] = text.transformCursor( -// cursor[cursor.length - 1], -// [{ p: op.p[op.p.length - 1], d: op.sd }], -// isOwnOp ? 'right' : 'left' -// ); -// } -// if (op.si) { -// cursor[cursor.length - 1] = text.transformCursor( -// cursor[cursor.length - 1], -// [{ p: op.p[op.p.length - 1], i: op.si }], -// isOwnOp ? 'right' : 'left' -// ); -// } -// } -// -// if (opIsAncestor) { -// if (op.lm !== undefined) { -// cursor[equalUpTo] = op.lm; -// } -// if (op.od && op.oi) { -// cursor = op.p.slice(0, op.p.length); -// } else if (op.od) { -// cursor = op.p.slice(0, op.p.length - 1); -// } else if (op.ld && op.li) { -// cursor = op.p.slice(0, op.p.length); -// } else if (op.ld) { -// cursor = op.p.slice(0, op.p.length - 1); -// } -// } -// -// if (opIsAncestorSibling) { -// var lastPathIdx = op.p.length - 1; -// if ( -// !opIsAncestor && -// op.ld && -// !op.li && -// op.p[lastPathIdx] < cursor[lastPathIdx] -// ) { -// cursor[lastPathIdx] -= 1; -// } else if (!op.ld && op.li && op.p[lastPathIdx] <= cursor[lastPathIdx]) { -// cursor[lastPathIdx] += 1; -// } -// -// // if move item in list from after to before -// if ( -// !opIsAncestor && -// op.lm !== undefined && -// op.p[lastPathIdx] > cursor[lastPathIdx] && -// op.lm <= cursor[lastPathIdx] -// ) { -// cursor[lastPathIdx] += 1; -// // if move item in list from before to after -// } else if ( -// !opIsAncestor && -// op.lm !== undefined && -// op.p[lastPathIdx] < cursor[lastPathIdx] && -// op.lm >= cursor[lastPathIdx] -// ) { -// cursor[lastPathIdx] -= 1; -// } -// } -// -// return cursor; -// }; -// -// json.transformCursor = function(cursor, op, isOwnOp) { -// for (var i = 0; i < op.length; i++) { -// cursor = transformPosition(cursor, op[i], isOwnOp); -// } -// return cursor; -// }; -// - json.transformPresence = function(presence, op, isOwnOp) { // Don't transform path-only presence objects. if(!presence.t) return presence; diff --git a/test/json0-presence.js b/test/json0-presence.js index 03e5f19..e819fc0 100644 --- a/test/json0-presence.js +++ b/test/json0-presence.js @@ -29,8 +29,8 @@ const sampleTextPresence = Object.assign({}, samplePresence, { // the user has "joined" the document at the top level. const samplePathOnlyPresence = { p: [] }; -//// These tests are inspired by the ones found here: -//// https://github.com/Teamwork/ot-rich-text/blob/master/test/Operation.js +// These tests are inspired by the ones found here: +// https://github.com/Teamwork/ot-rich-text/blob/master/test/Operation.js describe('json0 presence', () => { describe('createPresence', () => { it('should return the passed in presence object', () => { From d9d32a4f1a256f689fd6aa02c066d4dc793286cc Mon Sep 17 00:00:00 2001 From: curran Date: Mon, 15 Apr 2019 13:26:22 +0530 Subject: [PATCH 41/44] Namespace package and increment minor version --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b3cae36..5c84c5f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "ot-json0", - "version": "1.1.0", + "name": "@datavis-tech/ot-json0", + "version": "1.2.0", "description": "JSON OT type", "main": "lib/index.js", "directories": { From 95f20d82b07794f11fe7526c73923303a1c91da5 Mon Sep 17 00:00:00 2001 From: Curran Kelleher Date: Mon, 15 Apr 2019 19:01:32 +0530 Subject: [PATCH 42/44] Add link to presence demo --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 707ad36..a79f451 100644 --- a/README.md +++ b/README.md @@ -312,6 +312,13 @@ The shape of our presence data is as follows: } ``` +Here's a demo https://github.com/datavis-tech/json0-presence-demo + +![presence](https://user-images.githubusercontent.com/68416/56134824-ffac3400-5fac-11e9-89a1-c60064c3eb67.gif) + +![selections](https://user-images.githubusercontent.com/68416/56134832-033fbb00-5fad-11e9-9274-a19b2287c5b1.gif) + + # Commentary This library was written a couple of years ago by [Jeremy Apthorp](https://github.com/nornagon). It was From 90a06fead9a50c3ea53dfbaa9670a9c05ed9c3b8 Mon Sep 17 00:00:00 2001 From: Curran Kelleher Date: Tue, 16 Apr 2019 12:48:03 +0530 Subject: [PATCH 43/44] Add usage instructions and context. --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index 707ad36..91584f1 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,27 @@ The JSON OT type can be used to edit arbitrary JSON documents. +Forked from [ottypes/json0](https://github.com/ottypes/json0) to implement [presence](https://github.com/ottypes/docs/issues/29). + +Current status: Presence is basically working, but it's only transformed by subtype ops. Remaining work includes transforming presence by ops that are not text (`si`, `sd`) or subtype ops. Includes `li`, `ld`, `lm`, `oi`, `od`. The goal is that one day this fork will be merged into ottypes/json0 via this PR: [ottypes/json0: Presence](https://github.com/ottypes/json0/pull/31). + +In the mean time, this fork is published on NPM as [@datavis-tech/ot-json0](https://www.npmjs.com/package/@datavis-tech/ot-json0). If you want to try it out: + +``` +npm install -S @datavis-tech/ot-json0 +``` + +To [use it as the default ShareDB OT Type](https://github.com/share/sharedb/issues/284), you'll need to do the following (in both client and server): + +```js +const json0 = require('fork-of-ot-json0'); +const ShareDB = require('sharedb'); // or require('sharedb/lib/client'); +ShareDB.types.register(json0.type); +ShareDB.types.defaultType = json0.type; +``` + +To use the presence feature, you'll need to use the [Teamwork fork of ShareDB](https://github.com/teamwork/sharedb#readme) until the [ShareDB Presence PR](https://github.com/share/sharedb/pull/207) is merged. + ## Features The JSON OT type supports the following operations: From 3cd4ef62e6da971123678b47fb80455f364cea7d Mon Sep 17 00:00:00 2001 From: curran Date: Tue, 16 Apr 2019 12:57:52 +0530 Subject: [PATCH 44/44] Reset package.json for upstream PR --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5c84c5f..d0ca82d 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@datavis-tech/ot-json0", + "name": "ot-json0", "version": "1.2.0", "description": "JSON OT type", "main": "lib/index.js",