mirror of
https://github.com/overleaf/overleaf.git
synced 2024-12-27 20:10:47 +00:00
357 lines
9.8 KiB
JavaScript
357 lines
9.8 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
|
|
})()
|
|
})
|
|
}
|
|
}
|