json-api.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. /* eslint-disable
  2. camelcase,
  3. no-undef,
  4. */
  5. // TODO: This file was created by bulk-decaffeinate.
  6. // Fix any style issues and re-enable lint.
  7. /*
  8. * decaffeinate suggestions:
  9. * DS101: Remove unnecessary use of Array.from
  10. * DS102: Remove unnecessary code created because of implicit returns
  11. * DS205: Consider reworking code to avoid use of IIFEs
  12. * DS207: Consider shorter variations of null checks
  13. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  14. */
  15. // API for JSON OT
  16. let json
  17. if (typeof WEB === 'undefined') {
  18. json = require('./json')
  19. }
  20. if (typeof WEB !== 'undefined' && WEB !== null) {
  21. const { extendDoc } = exports
  22. exports.extendDoc = function (name, fn) {
  23. SubDoc.prototype[name] = fn
  24. return extendDoc(name, fn)
  25. }
  26. }
  27. const depath = function (path) {
  28. if (path.length === 1 && path[0].constructor === Array) {
  29. return path[0]
  30. } else {
  31. return path
  32. }
  33. }
  34. class SubDoc {
  35. constructor(doc, path) {
  36. this.doc = doc
  37. this.path = path
  38. }
  39. at(...path) {
  40. return this.doc.at(this.path.concat(depath(path)))
  41. }
  42. get() {
  43. return this.doc.getAt(this.path)
  44. }
  45. // for objects and lists
  46. set(value, cb) {
  47. return this.doc.setAt(this.path, value, cb)
  48. }
  49. // for strings and lists.
  50. insert(pos, value, cb) {
  51. return this.doc.insertAt(this.path, pos, value, cb)
  52. }
  53. // for strings
  54. del(pos, length, cb) {
  55. return this.doc.deleteTextAt(this.path, length, pos, cb)
  56. }
  57. // for objects and lists
  58. remove(cb) {
  59. return this.doc.removeAt(this.path, cb)
  60. }
  61. push(value, cb) {
  62. return this.insert(this.get().length, value, cb)
  63. }
  64. move(from, to, cb) {
  65. return this.doc.moveAt(this.path, from, to, cb)
  66. }
  67. add(amount, cb) {
  68. return this.doc.addAt(this.path, amount, cb)
  69. }
  70. on(event, cb) {
  71. return this.doc.addListener(this.path, event, cb)
  72. }
  73. removeListener(l) {
  74. return this.doc.removeListener(l)
  75. }
  76. // text API compatibility
  77. getLength() {
  78. return this.get().length
  79. }
  80. getText() {
  81. return this.get()
  82. }
  83. }
  84. const traverse = function (snapshot, path) {
  85. const container = { data: snapshot }
  86. let key = 'data'
  87. let elem = container
  88. for (const p of Array.from(path)) {
  89. elem = elem[key]
  90. key = p
  91. if (typeof elem === 'undefined') {
  92. throw new Error('bad path')
  93. }
  94. }
  95. return { elem, key }
  96. }
  97. const pathEquals = function (p1, p2) {
  98. if (p1.length !== p2.length) {
  99. return false
  100. }
  101. for (let i = 0; i < p1.length; i++) {
  102. const e = p1[i]
  103. if (e !== p2[i]) {
  104. return false
  105. }
  106. }
  107. return true
  108. }
  109. json.api = {
  110. provides: { json: true },
  111. at(...path) {
  112. return new SubDoc(this, depath(path))
  113. },
  114. get() {
  115. return this.snapshot
  116. },
  117. set(value, cb) {
  118. return this.setAt([], value, cb)
  119. },
  120. getAt(path) {
  121. const { elem, key } = traverse(this.snapshot, path)
  122. return elem[key]
  123. },
  124. setAt(path, value, cb) {
  125. const { elem, key } = traverse(this.snapshot, path)
  126. const op = { p: path }
  127. if (elem.constructor === Array) {
  128. op.li = value
  129. if (typeof elem[key] !== 'undefined') {
  130. op.ld = elem[key]
  131. }
  132. } else if (typeof elem === 'object') {
  133. op.oi = value
  134. if (typeof elem[key] !== 'undefined') {
  135. op.od = elem[key]
  136. }
  137. } else {
  138. throw new Error('bad path')
  139. }
  140. return this.submitOp([op], cb)
  141. },
  142. removeAt(path, cb) {
  143. const { elem, key } = traverse(this.snapshot, path)
  144. if (typeof elem[key] === 'undefined') {
  145. throw new Error('no element at that path')
  146. }
  147. const op = { p: path }
  148. if (elem.constructor === Array) {
  149. op.ld = elem[key]
  150. } else if (typeof elem === 'object') {
  151. op.od = elem[key]
  152. } else {
  153. throw new Error('bad path')
  154. }
  155. return this.submitOp([op], cb)
  156. },
  157. insertAt(path, pos, value, cb) {
  158. const { elem, key } = traverse(this.snapshot, path)
  159. const op = { p: path.concat(pos) }
  160. if (elem[key].constructor === Array) {
  161. op.li = value
  162. } else if (typeof elem[key] === 'string') {
  163. op.si = value
  164. }
  165. return this.submitOp([op], cb)
  166. },
  167. moveAt(path, from, to, cb) {
  168. const op = [{ p: path.concat(from), lm: to }]
  169. return this.submitOp(op, cb)
  170. },
  171. addAt(path, amount, cb) {
  172. const op = [{ p: path, na: amount }]
  173. return this.submitOp(op, cb)
  174. },
  175. deleteTextAt(path, length, pos, cb) {
  176. const { elem, key } = traverse(this.snapshot, path)
  177. const op = [{ p: path.concat(pos), sd: elem[key].slice(pos, pos + length) }]
  178. return this.submitOp(op, cb)
  179. },
  180. addListener(path, event, cb) {
  181. const l = { path, event, cb }
  182. this._listeners.push(l)
  183. return l
  184. },
  185. removeListener(l) {
  186. const i = this._listeners.indexOf(l)
  187. if (i < 0) {
  188. return false
  189. }
  190. this._listeners.splice(i, 1)
  191. return true
  192. },
  193. _register() {
  194. this._listeners = []
  195. this.on('change', function (op) {
  196. return (() => {
  197. const result = []
  198. for (const c of Array.from(op)) {
  199. var i
  200. if (c.na !== undefined || c.si !== undefined || c.sd !== undefined) {
  201. // no change to structure
  202. continue
  203. }
  204. var to_remove = []
  205. for (i = 0; i < this._listeners.length; i++) {
  206. // Transform a dummy op by the incoming op to work out what
  207. // should happen to the listener.
  208. const l = this._listeners[i]
  209. const dummy = { p: l.path, na: 0 }
  210. const xformed = this.type.transformComponent([], dummy, c, 'left')
  211. if (xformed.length === 0) {
  212. // The op was transformed to noop, so we should delete the listener.
  213. to_remove.push(i)
  214. } else if (xformed.length === 1) {
  215. // The op remained, so grab its new path into the listener.
  216. l.path = xformed[0].p
  217. } else {
  218. throw new Error(
  219. "Bad assumption in json-api: xforming an 'si' op will always result in 0 or 1 components."
  220. )
  221. }
  222. }
  223. to_remove.sort((a, b) => b - a)
  224. result.push(
  225. (() => {
  226. const result1 = []
  227. for (i of Array.from(to_remove)) {
  228. result1.push(this._listeners.splice(i, 1))
  229. }
  230. return result1
  231. })()
  232. )
  233. }
  234. return result
  235. })()
  236. })
  237. return this.on('remoteop', function (op) {
  238. return (() => {
  239. const result = []
  240. for (var c of Array.from(op)) {
  241. var match_path =
  242. c.na === undefined ? c.p.slice(0, c.p.length - 1) : c.p
  243. result.push(
  244. (() => {
  245. const result1 = []
  246. for (const { path, event, cb } of Array.from(this._listeners)) {
  247. var common
  248. if (pathEquals(path, match_path)) {
  249. switch (event) {
  250. case 'insert':
  251. if (c.li !== undefined && c.ld === undefined) {
  252. result1.push(cb(c.p[c.p.length - 1], c.li))
  253. } else if (c.oi !== undefined && c.od === undefined) {
  254. result1.push(cb(c.p[c.p.length - 1], c.oi))
  255. } else if (c.si !== undefined) {
  256. result1.push(cb(c.p[c.p.length - 1], c.si))
  257. } else {
  258. result1.push(undefined)
  259. }
  260. break
  261. case 'delete':
  262. if (c.li === undefined && c.ld !== undefined) {
  263. result1.push(cb(c.p[c.p.length - 1], c.ld))
  264. } else if (c.oi === undefined && c.od !== undefined) {
  265. result1.push(cb(c.p[c.p.length - 1], c.od))
  266. } else if (c.sd !== undefined) {
  267. result1.push(cb(c.p[c.p.length - 1], c.sd))
  268. } else {
  269. result1.push(undefined)
  270. }
  271. break
  272. case 'replace':
  273. if (c.li !== undefined && c.ld !== undefined) {
  274. result1.push(cb(c.p[c.p.length - 1], c.ld, c.li))
  275. } else if (c.oi !== undefined && c.od !== undefined) {
  276. result1.push(cb(c.p[c.p.length - 1], c.od, c.oi))
  277. } else {
  278. result1.push(undefined)
  279. }
  280. break
  281. case 'move':
  282. if (c.lm !== undefined) {
  283. result1.push(cb(c.p[c.p.length - 1], c.lm))
  284. } else {
  285. result1.push(undefined)
  286. }
  287. break
  288. case 'add':
  289. if (c.na !== undefined) {
  290. result1.push(cb(c.na))
  291. } else {
  292. result1.push(undefined)
  293. }
  294. break
  295. default:
  296. result1.push(undefined)
  297. }
  298. } else if (
  299. (common = this.type.commonPath(match_path, path)) != null
  300. ) {
  301. if (event === 'child op') {
  302. if (
  303. match_path.length === path.length &&
  304. path.length === common
  305. ) {
  306. throw new Error(
  307. "paths match length and have commonality, but aren't equal?"
  308. )
  309. }
  310. const child_path = c.p.slice(common + 1)
  311. result1.push(cb(child_path, c))
  312. } else {
  313. result1.push(undefined)
  314. }
  315. } else {
  316. result1.push(undefined)
  317. }
  318. }
  319. return result1
  320. })()
  321. )
  322. }
  323. return result
  324. })()
  325. })
  326. },
  327. }