From 4ab218a6101176662059af5e9c64c4170a13026d 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 1/4] 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 ac8210f3788092803f248be28e2893c265f8daf7 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 2/4] 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 cc705bf0ed5b1e96f7193c3529968ae4c04afa22 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 3/4] 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 539733aa27c05515906c93e648e2c8f4ff837d87 Mon Sep 17 00:00:00 2001 From: curran Date: Wed, 10 Apr 2019 06:32:54 +0530 Subject: [PATCH 4/4] Add README content for presence --- README.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/README.md b/README.md index cc1de54..6314a10 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,42 @@ The JSON OT type can be used to edit arbitrary JSON documents. +It has been forked from https://github.com/ottypes/json0 and modified to add Presence. + +## 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, // number of changes made by this user + s: [ // list of selections + [ 1, 1 ], // collapsed selection + [ 5, 7 ], // forward selection + [ 9, 4 ] // backward selection + ] +} +``` + +Each selection listed in `s` ends with a 2-element array containing the selection start index and the selection end index. The elements in the array preceeding the last two represent the path of a `text0` entry within the `json0` data structure. + +For example, the following entry in the `s` array represents the user's cursor position within the `content` field (`data.content`): + +``` +['content', 2, 2] +``` + +We can access deeply nested entries with this structure as well. For example, the following `s` entry represents a text selection in `data.files[3].text`: + +``` +['files', 3, 'text', 4, 7] +``` + +The rest of the README content is from the original repo https://github.com/ottypes/json0. + ## Features The JSON OT type supports the following operations: