mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
280 lines
9.3 KiB
JavaScript
280 lines
9.3 KiB
JavaScript
/* eslint-disable
|
|
camelcase,
|
|
no-undef,
|
|
*/
|
|
// TODO: This file was created by bulk-decaffeinate.
|
|
// Fix any style issues and re-enable lint.
|
|
/*
|
|
* 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 (const 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 (const 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 (const {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;
|
|
})();
|
|
});
|
|
}
|
|
};
|