/* * decaffeinate suggestions: * DS101: Remove unnecessary use of Array.from * DS102: Remove unnecessary code created because of implicit returns * DS205: Consider reworking code to avoid use of IIFEs * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ // API for JSON OT let json; if (typeof WEB === 'undefined') { json = require('./json'); } if (typeof WEB !== 'undefined' && WEB !== null) { const { extendDoc } = exports; exports.extendDoc = function(name, fn) { SubDoc.prototype[name] = fn; return extendDoc(name, fn); }; } const depath = function(path) { if ((path.length === 1) && (path[0].constructor === Array)) { return path[0]; } else { return path; } }; class SubDoc { constructor(doc, path) { this.doc = doc; this.path = path; } at(...path) { return this.doc.at(this.path.concat(depath(path))); } get() { return this.doc.getAt(this.path); } // for objects and lists set(value, cb) { return this.doc.setAt(this.path, value, cb); } // for strings and lists. insert(pos, value, cb) { return this.doc.insertAt(this.path, pos, value, cb); } // for strings del(pos, length, cb) { return this.doc.deleteTextAt(this.path, length, pos, cb); } // for objects and lists remove(cb) { return this.doc.removeAt(this.path, cb); } push(value, cb) { return this.insert(this.get().length, value, cb); } move(from, to, cb) { return this.doc.moveAt(this.path, from, to, cb); } add(amount, cb) { return this.doc.addAt(this.path, amount, cb); } on(event, cb) { return this.doc.addListener(this.path, event, cb); } removeListener(l) { return this.doc.removeListener(l); } // text API compatibility getLength() { return this.get().length; } getText() { return this.get(); } } const traverse = function(snapshot, path) { const container = {data:snapshot}; let key = 'data'; let elem = container; for (let p of Array.from(path)) { elem = elem[key]; key = p; if (typeof elem === 'undefined') { throw new Error('bad path'); } } return {elem, key}; }; const pathEquals = function(p1, p2) { if (p1.length !== p2.length) { return false; } for (let i = 0; i < p1.length; i++) { const e = p1[i]; if (e !== p2[i]) { return false; } } return true; }; json.api = { provides: {json:true}, at(...path) { return new SubDoc(this, depath(path)); }, get() { return this.snapshot; }, set(value, cb) { return this.setAt([], value, cb); }, getAt(path) { const {elem, key} = traverse(this.snapshot, path); return elem[key]; }, setAt(path, value, cb) { const {elem, key} = traverse(this.snapshot, path); const op = {p:path}; if (elem.constructor === Array) { op.li = value; if (typeof elem[key] !== 'undefined') { op.ld = elem[key]; } } else if (typeof elem === 'object') { op.oi = value; if (typeof elem[key] !== 'undefined') { op.od = elem[key]; } } else { throw new Error('bad path'); } return this.submitOp([op], cb); }, removeAt(path, cb) { const {elem, key} = traverse(this.snapshot, path); if (typeof elem[key] === 'undefined') { throw new Error('no element at that path'); } const op = {p:path}; if (elem.constructor === Array) { op.ld = elem[key]; } else if (typeof elem === 'object') { op.od = elem[key]; } else { throw new Error('bad path'); } return this.submitOp([op], cb); }, insertAt(path, pos, value, cb) { const {elem, key} = traverse(this.snapshot, path); const op = {p:path.concat(pos)}; if (elem[key].constructor === Array) { op.li = value; } else if (typeof elem[key] === 'string') { op.si = value; } return this.submitOp([op], cb); }, moveAt(path, from, to, cb) { const op = [{p:path.concat(from), lm:to}]; return this.submitOp(op, cb); }, addAt(path, amount, cb) { const op = [{p:path, na:amount}]; return this.submitOp(op, cb); }, deleteTextAt(path, length, pos, cb) { const {elem, key} = traverse(this.snapshot, path); const op = [{p:path.concat(pos), sd:elem[key].slice(pos, (pos + length))}]; return this.submitOp(op, cb); }, addListener(path, event, cb) { const l = {path, event, cb}; this._listeners.push(l); return l; }, removeListener(l) { const i = this._listeners.indexOf(l); if (i < 0) { return false; } this._listeners.splice(i, 1); return true; }, _register() { this._listeners = []; this.on('change', function(op) { return (() => { const result = []; for (let c of Array.from(op)) { var i; if ((c.na !== undefined) || (c.si !== undefined) || (c.sd !== undefined)) { // no change to structure continue; } var to_remove = []; for (i = 0; i < this._listeners.length; i++) { // Transform a dummy op by the incoming op to work out what // should happen to the listener. const l = this._listeners[i]; const dummy = {p:l.path, na:0}; const xformed = this.type.transformComponent([], dummy, c, 'left'); if (xformed.length === 0) { // The op was transformed to noop, so we should delete the listener. to_remove.push(i); } else if (xformed.length === 1) { // The op remained, so grab its new path into the listener. l.path = xformed[0].p; } else { throw new Error("Bad assumption in json-api: xforming an 'si' op will always result in 0 or 1 components."); } } to_remove.sort((a, b) => b - a); result.push((() => { const result1 = []; for (i of Array.from(to_remove)) { result1.push(this._listeners.splice(i, 1)); } return result1; })()); } return result; })(); }); return this.on('remoteop', function(op) { return (() => { const result = []; for (var c of Array.from(op)) { var match_path = c.na === undefined ? c.p.slice(0, c.p.length-1) : c.p; result.push((() => { const result1 = []; for (let {path, event, cb} of Array.from(this._listeners)) { var common; if (pathEquals(path, match_path)) { switch (event) { case 'insert': if ((c.li !== undefined) && (c.ld === undefined)) { result1.push(cb(c.p[c.p.length-1], c.li)); } else if ((c.oi !== undefined) && (c.od === undefined)) { result1.push(cb(c.p[c.p.length-1], c.oi)); } else if (c.si !== undefined) { result1.push(cb(c.p[c.p.length-1], c.si)); } else { result1.push(undefined); } break; case 'delete': if ((c.li === undefined) && (c.ld !== undefined)) { result1.push(cb(c.p[c.p.length-1], c.ld)); } else if ((c.oi === undefined) && (c.od !== undefined)) { result1.push(cb(c.p[c.p.length-1], c.od)); } else if (c.sd !== undefined) { result1.push(cb(c.p[c.p.length-1], c.sd)); } else { result1.push(undefined); } break; case 'replace': if ((c.li !== undefined) && (c.ld !== undefined)) { result1.push(cb(c.p[c.p.length-1], c.ld, c.li)); } else if ((c.oi !== undefined) && (c.od !== undefined)) { result1.push(cb(c.p[c.p.length-1], c.od, c.oi)); } else { result1.push(undefined); } break; case 'move': if (c.lm !== undefined) { result1.push(cb(c.p[c.p.length-1], c.lm)); } else { result1.push(undefined); } break; case 'add': if (c.na !== undefined) { result1.push(cb(c.na)); } else { result1.push(undefined); } break; default: result1.push(undefined); } } else if ((common = this.type.commonPath(match_path, path)) != null) { if (event === 'child op') { if (match_path.length === path.length && path.length === common) { throw new Error("paths match length and have commonality, but aren't equal?"); } const child_path = c.p.slice(common+1); result1.push(cb(child_path, c)); } else { result1.push(undefined); } } else { result1.push(undefined); } } return result1; })()); } return result; })(); }); } };