| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- # API for JSON OT
- json = require './json' if typeof WEB is 'undefined'
- if WEB?
- extendDoc = exports.extendDoc
- exports.extendDoc = (name, fn) ->
- SubDoc::[name] = fn
- extendDoc name, fn
- depath = (path) ->
- if path.length == 1 and path[0].constructor == Array
- path[0]
- else path
- class SubDoc
- constructor: (@doc, @path) ->
- at: (path...) -> @doc.at @path.concat depath path
- get: -> @doc.getAt @path
- # for objects and lists
- set: (value, cb) -> @doc.setAt @path, value, cb
- # for strings and lists.
- insert: (pos, value, cb) -> @doc.insertAt @path, pos, value, cb
- # for strings
- del: (pos, length, cb) -> @doc.deleteTextAt @path, length, pos, cb
- # for objects and lists
- remove: (cb) -> @doc.removeAt @path, cb
- push: (value, cb) -> @insert @get().length, value, cb
- move: (from, to, cb) -> @doc.moveAt @path, from, to, cb
- add: (amount, cb) -> @doc.addAt @path, amount, cb
- on: (event, cb) -> @doc.addListener @path, event, cb
- removeListener: (l) -> @doc.removeListener l
- # text API compatibility
- getLength: -> @get().length
- getText: -> @get()
- traverse = (snapshot, path) ->
- container = data:snapshot
- key = 'data'
- elem = container
- for p in path
- elem = elem[key]
- key = p
- throw new Error 'bad path' if typeof elem == 'undefined'
- {elem, key}
- pathEquals = (p1, p2) ->
- return false if p1.length != p2.length
- for e,i in p1
- return false if e != p2[i]
- true
- json.api =
- provides: {json:true}
- at: (path...) -> new SubDoc this, depath path
- get: -> @snapshot
- set: (value, cb) -> @setAt [], value, cb
- getAt: (path) ->
- {elem, key} = traverse @snapshot, path
- return elem[key]
- setAt: (path, value, cb) ->
- {elem, key} = traverse @snapshot, path
- op = {p:path}
- if elem.constructor == Array
- op.li = value
- op.ld = elem[key] if typeof elem[key] != 'undefined'
- else if typeof elem == 'object'
- op.oi = value
- op.od = elem[key] if typeof elem[key] != 'undefined'
- else throw new Error 'bad path'
- @submitOp [op], cb
- removeAt: (path, cb) ->
- {elem, key} = traverse @snapshot, path
- throw new Error 'no element at that path' unless typeof elem[key] != 'undefined'
- 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'
- @submitOp [op], cb
- insertAt: (path, pos, value, cb) ->
- {elem, key} = traverse @snapshot, path
- op = {p:path.concat pos}
- if elem[key].constructor == Array
- op.li = value
- else if typeof elem[key] == 'string'
- op.si = value
- @submitOp [op], cb
- moveAt: (path, from, to, cb) ->
- op = [{p:path.concat(from), lm:to}]
- @submitOp op, cb
- addAt: (path, amount, cb) ->
- op = [{p:path, na:amount}]
- @submitOp op, cb
- deleteTextAt: (path, length, pos, cb) ->
- {elem, key} = traverse @snapshot, path
- op = [{p:path.concat(pos), sd:elem[key][pos...(pos + length)]}]
- @submitOp op, cb
- addListener: (path, event, cb) ->
- l = {path, event, cb}
- @_listeners.push l
- l
- removeListener: (l) ->
- i = @_listeners.indexOf l
- return false if i < 0
- @_listeners.splice i, 1
- return true
- _register: ->
- @_listeners = []
- @on 'change', (op) ->
- for c in op
- if c.na != undefined or c.si != undefined or c.sd != undefined
- # no change to structure
- continue
- to_remove = []
- for l, i in @_listeners
- # Transform a dummy op by the incoming op to work out what
- # should happen to the listener.
- dummy = {p:l.path, na:0}
- xformed = @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
- for i in to_remove
- @_listeners.splice i, 1
- @on 'remoteop', (op) ->
- for c in op
- match_path = if c.na == undefined then c.p[...c.p.length-1] else c.p
- for {path, event, cb} in @_listeners
- if pathEquals path, match_path
- switch event
- when 'insert'
- if c.li != undefined and c.ld == undefined
- cb(c.p[c.p.length-1], c.li)
- else if c.oi != undefined and c.od == undefined
- cb(c.p[c.p.length-1], c.oi)
- else if c.si != undefined
- cb(c.p[c.p.length-1], c.si)
- when 'delete'
- if c.li == undefined and c.ld != undefined
- cb(c.p[c.p.length-1], c.ld)
- else if c.oi == undefined and c.od != undefined
- cb(c.p[c.p.length-1], c.od)
- else if c.sd != undefined
- cb(c.p[c.p.length-1], c.sd)
- when 'replace'
- if c.li != undefined and c.ld != undefined
- cb(c.p[c.p.length-1], c.ld, c.li)
- else if c.oi != undefined and c.od != undefined
- cb(c.p[c.p.length-1], c.od, c.oi)
- when 'move'
- if c.lm != undefined
- cb(c.p[c.p.length-1], c.lm)
- when 'add'
- if c.na != undefined
- cb(c.na)
- else if (common = @type.commonPath match_path, path)?
- if event == 'child op'
- if match_path.length == path.length == common
- throw new Error "paths match length and have commonality, but aren't equal?"
- child_path = c.p[common+1..]
- cb(child_path, c)
|