json-api.js 9.8 KB

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