Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Rewrote the entire client to use browserchannel.

  • Loading branch information...
commit 692865cd25b2d51d6ceb9d2c76424f2a86a2bdc3 1 parent 4279098
@josephg josephg authored
Showing with 1,000 additions and 882 deletions.
  1. +2 −2 examples/_wiki/wiki.html.mu
  2. +1 −1  examples/ace/index.html
  3. +1 −1  examples/clobber-ace.html
  4. +1 −1  examples/code.html
  5. +1 −1  examples/demos.html
  6. +1 −1  examples/hello-ace.html
  7. +1 −1  examples/hello-node.js
  8. +1 −1  examples/hello-readonly.html
  9. +1 −1  examples/hello-stream.js
  10. +1 −1  examples/hello-tp2.html
  11. +1 −1  examples/hex.html
  12. +2 −1  examples/index.html
  13. +1 −1  examples/pad/pad.html
  14. +1 −1  examples/readonly/html.html
  15. +1 −1  examples/readonly/markdown.html
  16. +1 −1  examples/sharefile.coffee
  17. +1 −1  examples/textarea.html
  18. +96 −146 src/client/connection.coffee
  19. +250 −121 src/client/doc.coffee
  20. +1 −1  src/client/index.coffee
  21. +4 −1 src/server/index.coffee
  22. +6 −4 test/client.coffee
  23. +1 −0  test/helpers/webclient.coffee
  24. +0 −1  test/microevent.coffee
  25. +6 −6 webclient/ace.js
  26. +1 −1  webclient/json.js
  27. +157 −189 webclient/json.uncompressed.js
  28. +6 −6 webclient/share-ace.js
  29. +1 −1  webclient/share.js
  30. +402 −338 webclient/share.uncompressed.js
  31. +34 −38 webclient/text-tp2.uncompressed.js
  32. +16 −11 webclient/textarea.js
View
4 examples/_wiki/wiki.html.mu
@@ -16,7 +16,7 @@
<div id="editor">{{{content}}}</div>
<script src="/lib/markdown/showdown.js" type="text/javascript"></script>
<script src="/lib/ace/ace.js" type="text/javascript" charset="utf-8"></script>
- <script src="/socket.io/socket.io.js"></script>
+ <script src="/channel/bcsocket.js"></script>
<script src="/share/share.js"></script>
<script src="/share/ace.js"></script>
<script>
@@ -33,7 +33,7 @@ window.onload = function() {
// sharejs.open('{{{docName}}}', function(doc, error) {
// ...
- var connection = new sharejs.Connection('http://' + window.location.hostname + ':' + 8000 + '/sjs');
+ var connection = new sharejs.Connection('http://' + window.location.hostname + ':' + 8000 + '/channel');
connection.open('{{{docName}}}', function(error, doc) {
if (error) {
View
2  examples/ace/index.html
@@ -14,7 +14,7 @@
<div id="editor">Connecting...</div>
<script src="/lib/ace/ace.js" type="text/javascript" charset="utf-8"></script>
<script src="/lib/ace/mode-coffee.js" type="text/javascript" charset="utf-8"></script>
- <script src="/socket.io/socket.io.js"></script>
+ <script src="/channel/bcsocket.js"></script>
<script src="/share/share.js"></script>
<script src="/share/ace.js"></script>
<script>
View
2  examples/clobber-ace.html
@@ -15,7 +15,7 @@
<div id="editor">Some content. The document will be replaced with this contents when its opened.</div>
<script src="/lib/ace/ace.js" type="text/javascript" charset="utf-8"></script>
- <script src="/socket.io/socket.io.js"></script>
+ <script src="/channel/bcsocket.js"></script>
<script src="/share/share.js"></script>
<script src="/share/ace.js"></script>
View
2  examples/code.html
@@ -17,7 +17,7 @@
<script src="/lib/ace/ace.js"></script>
<script src="/lib/ace/mode-coffee.js"></script>
<script src="/lib/ace/theme-idle_fingers.js"></script>
- <script src="/socket.io/socket.io.js"></script>
+ <script src="/channel/bcsocket.js"></script>
<script src="/share/share.js"></script>
<script src="/share/ace.js"></script>
<script>
View
2  examples/demos.html
@@ -120,7 +120,7 @@
<p>When you open this page, it creates a new empty editing pad with a random name (pad:XXXX).
You can share the URL with someone else and edit with them.
</p>
- <p><a class="btn primary" href="static/html">Edit a new pad &raquo;</a></p>
+ <p><a class="btn primary" href="pad/">Edit a new pad &raquo;</a></p>
</div>
<div class="span6 demo">
View
2  examples/hello-ace.html
@@ -15,7 +15,7 @@
<div id="editor"></div>
<script src="/lib/ace/ace.js" type="text/javascript" charset="utf-8"></script>
- <script src="/socket.io/socket.io.js"></script>
+ <script src="/channel/bcsocket.js"></script>
<script src="/share/share.js"></script>
<script src="/share/ace.js"></script>
View
2  examples/hello-node.js
@@ -3,7 +3,7 @@
var client = require('..').client;
-client.open('hello', 'text', 'http://localhost:8000/sjs', function(error, doc) {
+client.open('hello', 'text', 'http://localhost:8000/channel', function(error, doc) {
doc.insert('Hi there\n', 0);
console.log(doc.snapshot);
View
2  examples/hello-readonly.html
@@ -10,7 +10,7 @@
<div id='text' class='content'></div>
</div>
- <script src="/socket.io/socket.io.js"></script>
+ <script src="/channel/bcsocket.js"></script>
<script src="/share/share.js"></script>
<script>
View
2  examples/hello-stream.js
@@ -3,7 +3,7 @@
var client = require('..').client;
-client.open('hello', 'text', 'http://localhost:8000/sjs', function(error, doc) {
+client.open('hello', 'text', 'http://localhost:8000/channel', function(error, doc) {
if (error) {
throw error;
}
View
2  examples/hello-tp2.html
@@ -15,7 +15,7 @@
<div id="editor"></div>
<script src="/lib/ace/ace.js" type="text/javascript" charset="utf-8"></script>
- <script src="/socket.io/socket.io.js"></script>
+ <script src="/channel/bcsocket.js"></script>
<script src="/share/share.js"></script>
<script src="/share/text-tp2.uncompressed.js"></script>
<script src="/share/ace.js"></script>
View
2  examples/hex.html
@@ -58,7 +58,7 @@
}
</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
- <script src="/socket.io/socket.io.js"></script>
+ <script src="/channel/bcsocket.js"></script>
<script src="/share/share.js"></script>
<script src="/share/json.js"></script>
</head>
View
3  examples/index.html
@@ -191,7 +191,8 @@
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js"></script>
-<script src="/socket.io/socket.io.js"></script>
+<!--<script src="/socket.io/socket.io.js"></script>-->
+<script src="/channel/bcsocket.js"></script>
<script src="/share/share.js"></script>
<script src="/share/textarea.js"></script>
<script src="/lib/prettify.js"></script>
View
2  examples/pad/pad.html
@@ -20,7 +20,7 @@
<body>
<div id="editor">Connecting...</div>
<script src="/lib/ace/ace.js" type="text/javascript" charset="utf-8"></script>
- <script src="/socket.io/socket.io.js"></script>
+ <script src="/channel/bcsocket.js"></script>
<script src="/share/share.js"></script>
<script src="/share/ace.js"></script>
<script>
View
2  examples/readonly/html.html
@@ -6,7 +6,7 @@
<div id="container">
<div id='text' class='content'></div>
</div>
- <script src="/socket.io/socket.io.js" type="text/javascript"></script>
+ <script src="/channel/bcsocket.js"></script>
<script src="/share/share.js" type="text/javascript"></script>
<script type="text/javascript">
View
2  examples/readonly/markdown.html
@@ -7,7 +7,7 @@
<div id="container">
<div id='text' class='content'></div>
</div>
- <script src="/socket.io/socket.io.js" type="text/javascript"></script>
+ <script src="/channel/bcsocket.js"></script>
<script src="../lib/markdown/showdown.js" type="text/javascript"></script>
<script src="/share/share.js" type="text/javascript"></script>
<script type="text/javascript">
View
2  examples/sharefile.coffee
@@ -8,7 +8,7 @@ fs = require('fs')
argv = require('optimist')
.usage('Usage: $0 -d docname [--url URL] [-f filename]')
.default('d', 'hello')
- .default('url', 'http://localhost:8000/sjs')
+ .default('url', 'http://localhost:8000/channel')
.argv
filename = argv.f || argv.d
View
2  examples/textarea.html
@@ -18,7 +18,7 @@
</div>
<textarea id="editor" disabled>Loading...</textarea>
- <script src="/socket.io/socket.io.js"></script>
+ <script src="/channel/bcsocket.js"></script>
<script src="/share/share.js"></script>
<script src="/share/textarea.js"></script>
<script>
View
242 src/client/connection.coffee
@@ -1,9 +1,9 @@
-# A Connection manages a socket.io connection to a sharejs server.
+# A Connection wraps a persistant BC connection to a sharejs server.
#
# This class implements the client side of the protocol defined here:
# https://github.com/josephg/ShareJS/wiki/Wire-Protocol
#
-# The equivalent server code is in src/server/socketio.coffee.
+# The equivalent server code is in src/server/browserchannel.coffee.
#
# This file is a bit of a mess. I'm dreadfully sorry about that. It passes all the tests,
# so I have hope that its *correct* even if its not clean.
@@ -13,148 +13,111 @@
if WEB?
types ||= exports.types
- throw new Error 'Must load socket.io before this library' unless window.io
- io = window.io
+ throw new Error 'Must load browserchannel before this library' unless window.BCSocket
+ {BCSocket} = window
else
types = require '../types'
- io = require 'socket.io-client'
+ {BCSocket} = require 'browserchannel'
Doc = require('./doc').Doc
class Connection
- constructor: (origin) ->
+ constructor: (host) ->
+ # Map of docname -> doc
@docs = {}
- # Map of docName -> map of type -> function(data, error)
- #
- # Once socket.io isn't buggy, this will be rewritten to use socket.io's RPC.
- @handlers = {}
-
+ # States:
+ # - 'connecting': The connection is being established
+ # - 'handshaking': The connection has been established, but we don't have the auth ID yet
+ # - 'ok': We have connected and recieved our client ID. Ready for data.
+ # - 'disconnected': The connection is closed, but it will not reconnect automatically.
+ # - 'stopped': The connection is closed, and will not reconnect.
@state = 'connecting'
+ @socket = new BCSocket host, reconnect:true
+
+ @socket.onmessage = (msg) =>
+ if msg.auth is null
+ # Auth failed.
+ @lastError = msg.error # 'forbidden'
+ @disconnect()
+ return @emit 'connect failed', msg.error
+ else if msg.auth
+ # Our very own client id.
+ @id = msg.auth
+ @setState 'ok'
+ return
+
+ docName = msg.doc
+
+ if docName isnt undefined
+ @lastReceivedDoc = docName
+ else
+ msg.doc = docName = @lastReceivedDoc
- # We can't reuse connections because the socket.io server doesn't
- # emit connected events when a new connection comes in. Multiple documents
- # are already multiplexed over the connection by socket.io anyway, so it
- # shouldn't matter too much unless you're doing something particularly wacky.
- @socket = io.connect origin, 'force new connection': true
-
- @socket.on 'connect', @connected
- @socket.on 'disconnect', @disconnected
- @socket.on 'message', @onMessage
- @socket.on 'connect_failed', (error) =>
- error = 'forbidden' if error == 'unauthorized' # For consistency with the server
- @socket = null
- @emit 'connect failed', error
- # Cancel all hanging messages
- for docName, h of @handlers
- for t, callbacks of h
- callback error for callback in callbacks
-
- # This avoids a bug in socket.io-client (v0.7.9) which causes
- # subsequent connections on the same host to not fire a .connect event
- #if @socket.socket.connected
- # setTimeout (=> @connected()), 0
-
- disconnected: =>
- # Start reconnect sequence
- @emit 'disconnect'
- @socket = null
-
- connected: =>
- # Stop reconnect sequence
- @emit 'connect'
-
- # Send the specified message to the server. The server's response will be passed
- # to callback. If the message is 'open', ops will be sent to follower()
- #
- # The callback is optional. It takes (error, data). Data might be missing if the
- # error was a connection error.
- send: (msg, callback) ->
- throw new Error "Cannot send message #{JSON.stringify msg} to a closed connection" if @socket == null
-
- docName = msg.doc
-
- if docName == @lastSentDoc
- delete msg.doc
- else
- @lastSentDoc = docName
+ if @docs[docName]
+ @docs[docName]._onMessage msg
+ else
+ console?.error 'Unhandled message', msg
- @socket.json.send msg
-
- if callback
- type = if msg.open == true then 'open'
- else if msg.open == false then 'close'
- else if msg.create then 'create'
- else if msg.snapshot == null then 'snapshot'
- else if msg.op then 'op response'
-
- #cb = (response) =>
- # if response.doc == docName
- # @removeListener type, cb
- # callback response, response.error
-
- docHandlers = (@handlers[docName] ||= {})
- callbacks = (docHandlers[type] ||= [])
- callbacks.push callback
-
- onMessage: (msg) =>
- docName = msg.doc
-
- if docName != undefined
- @lastReceivedDoc = docName
- else
- msg.doc = docName = @lastReceivedDoc
+ @connected = false
+ @socket.onclose = (reason) =>
+ @setState 'disconnected', reason
+ if reason in ['Closed', 'Stopped by server']
+ @setState 'stopped', @lastError or reason
- @emit 'message', msg
+ @socket.onerror = (e) =>
+ @emit 'error', e
- # This should probably be rewritten to use socketio's message response stuff instead.
- # (it was originally written for socket.io 0.6)
- type = if msg.open == true or (msg.open == false and msg.error) then 'open'
- else if msg.open == false then 'close'
- else if msg.snapshot != undefined then 'snapshot'
- else if msg.create then 'create'
- else if msg.op then 'op'
- else if msg.v != undefined then 'op response'
+ @socket.onopen = =>
+ @lastError = null
+ @setState 'handshaking'
- callbacks = @handlers[docName]?[type]
- if callbacks
- delete @handlers[docName][type]
- c msg.error, msg for c in callbacks
+ @socket.onconnecting = =>
+ @setState 'connecting'
- if type == 'op'
- doc = @docs[docName]
- doc._onOpReceived msg if doc
+ setState: (state, data) ->
+ return if @state is state
+ @state = state
- makeDoc: (params) ->
- name = params.doc
- throw new Error("Doc #{name} already open") if @docs[name]
+ delete @id if state is 'disconnected'
+ @emit state, data
- type = params.type
- type = types[type] if typeof type == 'string'
- doc = new Doc(@, name, params.v, type, params.snapshot)
- doc.created = !!params.create
- @docs[name] = doc
+ # Documents could just subscribe to the state change events, but there's less state to
+ # clean up when you close a document if I just notify the doucments directly.
+ for docName, doc of @docs
+ doc._connectionStateChanged state, data
+
+ send: (data) ->
+ docName = data.doc
+
+ if docName is @lastSentDoc
+ delete data.doc
+ else
+ @lastSentDoc = docName
- doc.on 'closing', =>
- delete @docs[name]
+ #console.warn 'c->s', data
+ @socket.send data
- doc
+ disconnect: ->
+ # This will call @socket.onclose(), which in turn will emit the 'disconnected' event.
+ @socket.close()
+
+ # *** Doc management
+
+ makeDoc: (name, data, callback) ->
+ throw new Error("Doc #{name} already open") if @docs[name]
+ doc = new Doc(@, name, data)
+ @docs[name] = doc
+
+ doc.open (error) =>
+ delete @docs[name] if error
+ callback error, (doc unless error)
# Open a document that already exists
# callback(error, doc)
openExisting: (docName, callback) ->
- if @socket == null # The connection is perminantly disconnected
- callback 'connection closed'
- return
-
- return @docs[docName] if @docs[docName]?
-
- @send {doc:docName, open:true, snapshot:null}, (error, response) =>
- if error
- callback error
- else
- # response.doc is used instead of docName to allow docName to be null.
- # In that case, the server generates a random docName to use.
- callback null, @makeDoc(response)
+ return callback 'connection closed' if @state is 'stopped'
+ return callback null, @docs[docName] if @docs[docName]
+ doc = @makeDoc docName, {}, callback
# Open a document. It will be created if it doesn't already exist.
# Callback is passed a document or an error
@@ -162,46 +125,33 @@ class Connection
# Types must be supported by the server.
# callback(error, doc)
open: (docName, type, callback) ->
- if @socket == null # The connection is perminantly disconnected
- callback 'connection closed'
- return
+ return callback 'connection closed' if @state is 'stopped'
- if typeof type == 'function'
+ if typeof type is 'function'
callback = type
type = 'text'
callback ||= ->
- type = types[type] if typeof type == 'string'
+ type = types[type] if typeof type is 'string'
throw new Error "OT code for document type missing" unless type
- if docName? and @docs[docName]?
+ throw new Error 'Server-generated random doc names are not currently supported' unless docName?
+
+ if @docs[docName]
doc = @docs[docName]
if doc.type == type
callback null, doc
else
callback 'Type mismatch', doc
-
return
- @send {doc:docName, open:true, create:true, snapshot:null, type:type.name}, (error, response) =>
- if error
- callback error
- else
- response.snapshot = type.create() unless response.snapshot != undefined
- response.type = type
- callback null, @makeDoc(response)
-
- # To be written. Create a new document with a random name.
- create: (type, callback) ->
- open null, type, callback
-
- disconnect: () ->
- if @socket
- @emit 'disconnecting'
- @socket.disconnect()
- @socket = null
+ @makeDoc docName, {create:true, type:type.name}, callback
+
+# Not currently working.
+# create: (type, callback) ->
+# open null, type, callback
# Make connections event emitters.
unless WEB?
View
371 src/client/doc.coffee
@@ -1,7 +1,9 @@
+unless WEB?
+ types = require '../types'
+
# A Doc is a client's view on a sharejs document.
#
-# Documents are created by calling Connection.open(). They are only instantiated
-# once the client has the document snapshot.
+# Documents are created by calling Connection.open().
#
# Documents are event emitters - use doc.on(eventname, fn) to subscribe.
#
@@ -11,36 +13,53 @@
# Events:
# - remoteop (op)
# - changed (op)
-#
-# connection is a Connection object.
-# name is the documents' docName.
-# version is the version number of the document _on the server_
-# type is the OT type of the document, which defines .compose(), .tranform(), etc.
-# snapshot is the current state of the document.
-
-Doc = (connection, @name, @version, @type, @snapshot) ->
- throw new Error('Handling types without compose() defined is not currently implemented') unless @type.compose?
-
- # Gotta figure out a cleaner way to make this work with closure.
-
- # The op that is currently roundtripping to the server, or null.
- inflightOp = null
- inflightCallbacks = []
-
- # All ops that are waiting for the server to acknowledge @inflightOp
- pendingOp = null
- pendingCallbacks = []
-
- # Some recent ops, incase submitOp is called with an old op version number.
- serverOps = {}
+# - error
+# - open, closing, closed. 'closing' is not guaranteed to fire before closed.
+class Doc
+ # connection is a Connection object.
+ # name is the documents' docName.
+ # data can optionally contain known document data, and initial open() call arguments:
+ # {v[erson], snapshot={...}, type, create=true/false/undefined}
+ # callback will be called once the document is first opened.
+ constructor: (@connection, @name, openData) ->
+ # Any of these can be null / undefined at this stage.
+ openData ||= {}
+ @version = openData.v
+ @snapshot = openData.snaphot
+ @_setType openData.type if openData.type
+
+ @state = 'closed'
+ @autoOpen = false
+
+ # Has the document already been created?
+ @_create = openData.create
+
+ # The op that is currently roundtripping to the server, or null.
+ #
+ # When the connection reconnects, the inflight op is resubmitted.
+ @inflightOp = null
+ @inflightCallbacks = []
+ # The auth ids which the client has previously used to attempt to send inflightOp. This is
+ # usually empty.
+ @inflightSubmittedIds = []
+
+ # All ops that are waiting for the server to acknowledge @inflightOp
+ @pendingOp = null
+ @pendingCallbacks = []
+
+ # Some recent ops, incase submitOp is called with an old op version number.
+ @serverOps = {}
# Transform a server op by a client op, and vice versa.
- xf = @type.transformX or (client, server) =>
- client_ = @type.transform client, server, 'left'
- server_ = @type.transform server, client, 'right'
- return [client_, server_]
+ _xf: (client, server) ->
+ if @type.transformX
+ @type.transformX(client, server)
+ else
+ client_ = @type.transform client, server, 'left'
+ server_ = @type.transform server, client, 'right'
+ return [client_, server_]
- otApply = (docOp, isRemote) =>
+ _otApply: (docOp, isRemote) ->
oldSnapshot = @snapshot
@snapshot = @type.apply(@snapshot, docOp)
@@ -50,126 +69,236 @@ Doc = (connection, @name, @version, @type, @snapshot) ->
@emit 'change', docOp, oldSnapshot
@emit 'remoteop', docOp, oldSnapshot if isRemote
- # Send ops to the server, if appropriate.
- #
- # Only one op can be in-flight at a time, so if an op is already on its way then
- # this method does nothing.
- @flush = =>
- if inflightOp == null && pendingOp != null
- # Rotate null -> pending -> inflight,
- inflightOp = pendingOp
- inflightCallbacks = pendingCallbacks
-
- pendingOp = null
- pendingCallbacks = []
-
- connection.send {'doc':@name, 'op':inflightOp, 'v':@version}, (error, response) =>
- oldInflightOp = inflightOp
- inflightOp = null
-
- if error
- # This will happen if the server rejects edits from the client.
- # We'll send the error message to the user and roll back the change.
- #
- # If the server isn't going to allow edits anyway, we should probably
- # figure out some way to flag that (readonly:true in the open request?)
-
- if type.invert
-
- undo = @type.invert oldInflightOp
-
- # Now we have to transform the undo operation by any server ops & pending ops
- if pendingOp
- [pendingOp, undo] = xf pendingOp, undo
-
- # ... and apply it locally, reverting the changes.
- #
- # This call will also call @emit 'remoteop'. I'm still not 100% sure about this
- # functionality, because its really a local op. Basically, the problem is that
- # if the client's op is rejected by the server, the editor window should update
- # to reflect the undo.
- otApply undo, true
- else
- throw new Error "Op apply failed (#{response.error}) and the OT type does not define an invert function."
-
- callback error for callback in inflightCallbacks
- else
- throw new Error('Invalid version from server') unless response.v == @version
+ _connectionStateChanged: (state, data) ->
+ switch state
+ when 'disconnected'
+ @state = 'closed'
+ # This is used by the server to make sure that when an op is resubmitted it
+ # doesn't end up getting applied twice.
+ @inflightSubmittedIds.push @connection.id if @inflightOp
- serverOps[@version] = oldInflightOp
- @version++
- callback null, oldInflightOp for callback in inflightCallbacks
+ @emit 'closed'
- @flush()
+ when 'ok' # Might be able to do this when we're connecting... that would save a roundtrip.
+ @open() if @autoOpen
- # Internal API
- # The connection uses this method to notify a document that an op is received from the server.
- @_onOpReceived = (msg) ->
- # msg is {doc:, op:, v:}
+ when 'stopped'
+ @_openCallback? data
- # There is a bug in socket.io (produced on firefox 3.6) which causes messages
- # to be duplicated sometimes.
- # We'll just silently drop subsequent messages.
- return if msg.v < @version
+ @emit state, data
- throw new Error("Expected docName '#{@name}' but got #{msg.doc}") unless msg.doc == @name
- throw new Error("Expected version #{@version} but got #{msg.v}") unless msg.v == @version
+ _setType: (type) ->
+ if typeof type is 'string'
+ type = types[type]
-# p "if: #{i @inflightOp} pending: #{i @pendingOp} doc '#{@snapshot}' op: #{i msg.op}"
+ throw new Error 'Support for types without compose() is not implemented' unless type and type.compose
- op = msg.op
- serverOps[@version] = op
+ @type = type
+ if type.api
+ this[k] = v for k, v of type.api
+ @_register?()
+ else
+ @provides = {}
+
+ _onMessage: (msg) ->
+ #console.warn 's->c', msg
+ if msg.open == true
+ # The document has been successfully opened.
+ @state = 'open'
+ @_create = false # Don't try and create the document again next time open() is called.
+ unless @created?
+ @created = !!msg.create
+
+ @_setType msg.type if msg.type
+ if msg.create
+ @created = true
+ @snapshot = @type.create()
+ else
+ @created = false unless @created is true
+ @snapshot = msg.snapshot if msg.snapshot isnt undefined
+
+ @version = msg.v if msg.v?
+
+ # Resend any previously queued operation.
+ if @inflightOp
+ response =
+ doc: @name
+ op: @inflightOp
+ v: @version
+ response.dupIfSource = @inflightSubmittedIds if @inflightSubmittedIds.length
+ @connection.send response
+ else
+ @flush()
- docOp = op
- if inflightOp != null
- [inflightOp, docOp] = xf inflightOp, docOp
- if pendingOp != null
- [pendingOp, docOp] = xf pendingOp, docOp
+ @emit 'open'
- @version++
- # Finally, apply the op to @snapshot and trigger any event listeners
- otApply docOp, true
+ @_openCallback? null
+
+ else if msg.open == false
+ # The document has either been closed, or an open request has failed.
+ if msg.error
+ # An error occurred opening the document.
+ console?.error "Could not open document: #{msg.error}"
+ @emit 'error', msg.error
+ @_openCallback? msg.error
+
+ @state = 'closed'
+ @emit 'closed'
+
+ @_closeCallback?()
+ @_closeCallback = null
+
+ else if msg.op is null and error is 'Op already submitted'
+ # We've tried to resend an op to the server, which has already been received successfully. Do nothing.
+ # The op will be confirmed normally when we get the op itself was echoed back from the server
+ # (handled below).
+
+ else if (msg.op is undefined and msg.v isnt undefined) or (msg.op and msg.meta.source in @inflightSubmittedIds)
+ # Our inflight op has been acknowledged.
+ oldInflightOp = @inflightOp
+ @inflightOp = null
+ @inflightSubmittedIds.length = 0
+
+ error = msg.error
+ if error
+ # The server has rejected an op from the client for some reason.
+ # We'll send the error message to the user and roll back the change.
+ #
+ # If the server isn't going to allow edits anyway, we should probably
+ # figure out some way to flag that (readonly:true in the open request?)
+
+ if @type.invert
+ undo = @type.invert oldInflightOp
+
+ # Now we have to transform the undo operation by any server ops & pending ops
+ if @pendingOp
+ [@pendingOp, undo] = @_xf @pendingOp, undo
+
+ # ... and apply it locally, reverting the changes.
+ #
+ # This call will also call @emit 'remoteop'. I'm still not 100% sure about this
+ # functionality, because its really a local op. Basically, the problem is that
+ # if the client's op is rejected by the server, the editor window should update
+ # to reflect the undo.
+ @_otApply undo, true
+ else
+ @emit 'error', "Op apply failed (#{error}) and the op could not be reverted"
+
+ callback error for callback in @inflightCallbacks
+ else
+ # The op applied successfully.
+ throw new Error('Invalid version from server') unless msg.v == @version
+
+ @serverOps[@version] = oldInflightOp
+ @version++
+ callback null, oldInflightOp for callback in @inflightCallbacks
+
+ # Send the next op.
+ @flush()
+
+ else if msg.op
+ # We got a new op from the server.
+ # msg is {doc:, op:, v:}
+
+ # There is a bug in socket.io (produced on firefox 3.6) which causes messages
+ # to be duplicated sometimes.
+ # We'll just silently drop subsequent messages.
+ return if msg.v < @version
+
+ return @emit 'error', "Expected docName '#{@name}' but got #{msg.doc}" unless msg.doc == @name
+ return @emit 'error', "Expected version #{@version} but got #{msg.v}" unless msg.v == @version
+
+ # p "if: #{i @inflightOp} pending: #{i @pendingOp} doc '#{@snapshot}' op: #{i msg.op}"
+
+ op = msg.op
+ @serverOps[@version] = op
+
+ docOp = op
+ if @inflightOp != null
+ [@inflightOp, docOp] = @_xf @inflightOp, docOp
+ if @pendingOp != null
+ [@pendingOp, docOp] = @_xf @pendingOp, docOp
+
+ @version++
+ # Finally, apply the op to @snapshot and trigger any event listeners
+ @_otApply docOp, true
+
+ else
+ console?.warn 'Unhandled document message:', msg
+
+
+ # Send ops to the server, if appropriate.
+ #
+ # Only one op can be in-flight at a time, so if an op is already on its way then
+ # this method does nothing.
+ flush: =>
+ return unless @connection.state == 'ok' and @inflightOp == null and @pendingOp != null
+
+ # Rotate null -> pending -> inflight
+ @inflightOp = @pendingOp
+ @inflightCallbacks = @pendingCallbacks
+
+ @pendingOp = null
+ @pendingCallbacks = []
+
+ @connection.send {doc:@name, op:@inflightOp, v:@version}
# Submit an op to the server. The op maybe held for a little while before being sent, as only one
# op can be inflight at any time.
- @submitOp = (op, callback) ->
+ submitOp: (op, callback) ->
op = @type.normalize(op) if @type.normalize?
# If this throws an exception, no changes should have been made to the doc
@snapshot = @type.apply @snapshot, op
- if pendingOp != null
- pendingOp = @type.compose(pendingOp, op)
+ if @pendingOp != null
+ @pendingOp = @type.compose(@pendingOp, op)
else
- pendingOp = op
+ @pendingOp = op
- pendingCallbacks.push callback if callback
+ @pendingCallbacks.push callback if callback
@emit 'change', op
# A timeout is used so if the user sends multiple ops at the same time, they'll be composed
- # together and sent together.
+ # & sent together.
setTimeout @flush, 0
-
+
+ # Open a document. The document starts closed.
+ open: (callback) ->
+ @autoOpen = true
+ return unless @state is 'closed'
+
+ message =
+ doc: @name
+ open: true
+
+ message.snapshot = null if @snapshot is undefined
+ message.type = @type.name if @type
+ message.v = @version if @version?
+ message.create = true if @_create
+
+ @connection.send message
+
+ @state = 'opening'
+
+ @_openCallback = (error) =>
+ @_openCallback = null
+ callback? error
+
# Close a document.
- # No unit tests for this so far.
- @close = (callback) ->
- return callback?() if connection.socket == null
+ close: (callback) ->
+ @autoOpen = false
+ return callback?() if @state is 'closed'
- connection.send {'doc':@name, open:false}, =>
- callback?()
- @emit 'closed'
- return
- @emit 'closing'
-
- if @type.api
- this[k] = v for k, v of @type.api
- @_register?()
- else
- @provides = {}
+ @connection.send {doc:@name, open:false}
- this
+ # Should this happen immediately or when we get open:false back from the server?
+ @state = 'closed'
+ @emit 'closing'
+ @_closeCallback = callback
+
# Make documents event emitters
unless WEB?
MicroEvent = require './microevent'
View
2  src/client/index.coffee
@@ -25,7 +25,7 @@ exports.open = do ->
getConnection = (origin) ->
if WEB?
location = window.location
- origin ?= "#{location.protocol}//#{location.hostname}/sjs"
+ origin ?= "#{location.protocol}//#{location.host}/channel"
unless connections[origin]
c = new Connection origin
View
5 src/server/index.coffee
@@ -42,10 +42,13 @@ create.attach = attach = (server, options, model = createModel(options)) ->
# The client frontend doesn't get access to the model at all, to make sure security stuff is
# done properly.
server.use rest(createClient, options.rest) if options.rest != null
- socketio.attach(server, createClient, options.socketio or {}) if options.socketio != null
+
+ # Socketio frontend is now disabled by default.
+ socketio.attach(server, createClient, options.socketio or {}) if options.socketio?
if options.browserChannel != null
options.browserChannel ?= {}
+ #options.browserChannel.base ?= '/sjs'
options.browserChannel.server = server
server.use browserChannel(createClient, options.browserChannel)
View
10 test/client.coffee
@@ -17,8 +17,9 @@ genTests = (client) -> testCase
@auth = (client, action) -> action.accept()
options =
- socketio: {}
+ socketio: null
rest: null
+ browserChannel: {base: '/sjs'}
db: {type: 'none'}
auth: (client, action) => @auth client, action
@@ -28,14 +29,14 @@ genTests = (client) -> testCase
@server.listen =>
@port = @server.address().port
@c = new client.Connection "http://localhost:#{@port}/sjs"
- @c.on 'connect', callback
+ @c.on 'ok', callback
tearDown: (callback) ->
@c.disconnect()
@server.on 'close', callback
@server.close()
-
+
'open using the bare API': (test) ->
client.open @name, 'text', "http://localhost:#{@port}/sjs", (error, doc) =>
test.ok doc
@@ -291,7 +292,7 @@ genTests = (client) -> testCase
c.on 'connect failed', (error) ->
test.strictEqual error, 'forbidden'
test.done()
-
+
'(new Connection).open() fails if auth rejects the connection': (test) ->
@auth = (client, action) -> action.reject()
@@ -319,6 +320,7 @@ genTests = (client) -> testCase
c.open @name, 'text', (error, doc) =>
test.fail doc if doc
test.strictEqual error, 'forbidden'
+ c.disconnect()
test.done()
'client.open fails if auth rejects the connection': (test) ->
View
1  test/helpers/webclient.coffee
@@ -11,6 +11,7 @@ fs = require 'fs'
window = {}
window.io = require 'socket.io-client'
+window.BCSocket = require('browserchannel').BCSocket
for script in ['share', 'json']
script = "#{script}.uncompressed" if TEST_UNCOMPRESSED
View
1  test/microevent.coffee
@@ -91,7 +91,6 @@ tests =
@e.emit 'bar'
-
# The tests above are run both with a new MicroEvent and with an object with
# microevent mixed in.
View
12 webclient/ace.js
@@ -1,6 +1,8 @@
(function() {
var Range, applyToShareJS;
+
Range = require("ace/range").Range;
+
applyToShareJS = function(editorDoc, delta, doc) {
var getStartOffsetPosition, pos, text;
getStartOffsetPosition = function(range) {
@@ -33,6 +35,7 @@
throw new Error("unknown action: " + delta.action);
}
};
+
window.sharejs.Doc.prototype.attach_ace = function(editor, keepEditorContents) {
var check, doc, docListener, editorDoc, editorListener, offsetToPos, suppress;
if (!this.provides['text']) {
@@ -62,9 +65,7 @@
check();
suppress = false;
editorListener = function(change) {
- if (suppress) {
- return;
- }
+ if (suppress) return;
applyToShareJS(editorDoc, change.data, doc);
return check();
};
@@ -81,9 +82,7 @@
row = 0;
for (row = 0, _len = lines.length; row < _len; row++) {
line = lines[row];
- if (offset <= line.length) {
- break;
- }
+ if (offset <= line.length) break;
offset -= lines[row].length + 1;
}
return {
@@ -111,4 +110,5 @@
return delete doc.detach_ace;
};
};
+
}).call(this);
View
2  webclient/json.js
@@ -1 +1 @@
-((function(){var a,b,c,d,e,f,g,h,i=!0,j=Array.prototype.slice,k=window.sharejs;typeof i!="undefined"&&i!==null?g=k.types.text:g=require("./text"),e={},e.name="json",e.create=function(){return null},e.invertComponent=function(a){var b={p:a.p};return a.si!==void 0&&(b.sd=a.si),a.sd!==void 0&&(b.si=a.sd),a.oi!==void 0&&(b.od=a.oi),a.od!==void 0&&(b.oi=a.od),a.li!==void 0&&(b.ld=a.li),a.ld!==void 0&&(b.li=a.ld),a.na!==void 0&&(b.na=-a.na),a.lm!==void 0&&(b.lm=a.p[a.p.length-1],b.p=a.p.slice(0,a.p.length-1).concat([a.lm])),b},e.invert=function(a){var b,c,d,f=a.slice().reverse(),g=[];for(c=0,d=f.length;c<d;c++)b=f[c],g.push(e.invertComponent(b));return g},e.checkValidOp=function(){},d=function(a){return Object.prototype.toString.call(a)==="[object Array]"},e.checkList=function(a){if(!d(a))throw new Error("Referenced element not a list")},e.checkObj=function(a){if(a.constructor!==Object)throw new Error("Referenced element not an object (it was "+JSON.stringify(a)+")")},e.apply=function(a,c){var d,f,g,h,i,j,k,l,m,n,o,p,q;e.checkValidOp(c),c=b(c),f={data:b(a)};try{for(i=0,o=c.length;i<o;i++){d=c[i],l=null,m=null,h=f,j="data",q=d.p;for(n=0,p=q.length;n<p;n++){k=q[n],l=h,m=j,h=h[j],j=k;if(l==null)throw new Error("Path invalid")}if(d.na!==void 0){if(typeof h[j]!="number")throw new Error("Referenced element not a number");h[j]+=d.na}else if(d.si!==void 0){if(typeof h!="string")throw new Error("Referenced element not a string (it was "+JSON.stringify(h)+")");l[m]=h.slice(0,j)+d.si+h.slice(j)}else if(d.sd!==void 0){if(typeof h!="string")throw new Error("Referenced element not a string");if(h.slice(j,j+d.sd.length)!==d.sd)throw new Error("Deleted string does not match");l[m]=h.slice(0,j)+h.slice(j+d.sd.length)}else if(d.li!==void 0&&d.ld!==void 0)e.checkList(h),h[j]=d.li;else if(d.li!==void 0)e.checkList(h),h.splice(j,0,d.li);else if(d.ld!==void 0)e.checkList(h),h.splice(j,1);else if(d.lm!==void 0)e.checkList(h),d.lm!==j&&(g=h[j],h.splice(j,1),h.splice(d.lm,0,g));else if(d.oi!==void 0)e.checkObj(h),h[j]=d.oi;else if(d.od!==void 0)e.checkObj(h),delete h[j];else throw new Error("invalid / missing instruction in op")}}catch(r){throw r}return f.data},e.pathMatches=function(a,b,c){var d,e,f;if(a.length!==b.length)return!1;for(d=0,f=a.length;d<f;d++){e=a[d];if(e!==b[d]&&(!c||d!==a.length-1))return!1}return!0},e.append=function(a,c){var d;return c=b(c),a.length!==0&&e.pathMatches(c.p,(d=a[a.length-1]).p)?d.na!==void 0&&c.na!==void 0?a[a.length-1]={p:d.p,na:d.na+c.na}:d.li!==void 0&&c.li===void 0&&c.ld===d.li?d.ld!==void 0?delete d.li:a.pop():d.od!==void 0&&d.oi===void 0&&c.oi!==void 0&&c.od===void 0?d.oi=c.oi:c.lm!==void 0&&c.p[c.p.length-1]===c.lm?null:a.push(c):a.push(c)},e.compose=function(a,c){var d,f,g,h;e.checkValidOp(a),e.checkValidOp(c),f=b(a);for(g=0,h=c.length;g<h;g++)d=c[g],e.append(f,d);return f},e.normalize=function(a){var b,c,f,g,h=[];d(a)||(a=[a]);for(c=0,f=a.length;c<f;c++)b=a[c],(g=b.p)==null&&(b.p=[]),e.append(h,b);return h},b=function(a){return JSON.parse(JSON.stringify(a))},e.commonPath=function(a,b){var c;a=a.slice(),b=b.slice(),a.unshift("data"),b.unshift("data"),a=a.slice(0,a.length-1),b=b.slice(0,b.length-1);if(b.length===0)return-1;c=0;while(a[c]===b[c]&&c<a.length){c++;if(c===b.length)return c-1}},e.transformComponent=function(a,c,d,f){var h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z;c=b(c),c.na!==void 0&&c.p.push(0),d.na!==void 0&&d.p.push(0),h=e.commonPath(c.p,d.p),i=e.commonPath(d.p,c.p),l=c.p.length,p=d.p.length,c.na!==void 0&&c.p.pop(),d.na!==void 0&&d.p.pop();if(d.na)return i!=null&&p>=l&&d.p[i]===c.p[i]&&(c.ld!==void 0?(o=b(d),o.p=o.p.slice(l),c.ld=e.apply(b(c.ld),[o])):c.od!==void 0&&(o=b(d),o.p=o.p.slice(l),c.od=e.apply(b(c.od),[o]))),e.append(a,c),a;i!=null&&p>l&&c.p[i]===d.p[i]&&(c.ld!==void 0?(o=b(d),o.p=o.p.slice(l),c.ld=e.apply(b(c.ld),[o])):c.od!==void 0&&(o=b(d),o.p=o.p.slice(l),c.od=e.apply(b(c.od),[o])));if(h!=null){j=l===p;if(d.na===void 0)if(d.si!==void 0||d.sd!==void 0){if(c.si!==void 0||c.sd!==void 0){if(!j)throw new Error("must be a string?");k=function(a){var b={p:a.p[a.p.length-1]};return a.si?b.i=a.si:b.d=a.sd,b},v=k(c),w=k(d),t=[],g._tc(t,v,w,f);for(y=0,z=t.length;y<z;y++)u=t[y],n={p:c.p.slice(0,h)},n.p.push(u.p),u.i!=null&&(n.si=u.i),u.d!=null&&(n.sd=u.d),e.append(a,n);return a}}else if(d.li!==void 0&&d.ld!==void 0){if(d.p[h]===c.p[h]){if(!j)return a;if(c.ld!==void 0)if(c.li!==void 0&&f==="left")c.ld=b(d.li);else return a}}else if(d.li!==void 0)c.li!==void 0&&c.ld===void 0&&j&&c.p[h]===d.p[h]?f==="right"&&c.p[h]++:d.p[h]<=c.p[h]&&c.p[h]++,c.lm!==void 0&&j&&d.p[h]<=c.lm&&c.lm++;else if(d.ld!==void 0){if(c.lm!==void 0&&j){if(d.p[h]===c.p[h])return a;s=d.p[h],m=c.p[h],x=c.lm,(s<x||s===x&&m<x)&&c.lm--}if(d.p[h]<c.p[h])c.p[h]--;else if(d.p[h]===c.p[h]){if(p<l)return a;if(c.ld!==void 0)if(c.li!==void 0)delete c.ld;else return a}}else if(d.lm!==void 0)if(c.lm!==void 0&&l===p){m=c.p[h],x=c.lm,q=d.p[h],r=d.lm;if(q!==r)if(m===q)if(f==="left")c.p[h]=r,m===x&&(c.lm=r);else return a;else m>q&&c.p[h]--,m>r?c.p[h]++:m===r&&q>r&&(c.p[h]++,m===x&&c.lm++),x>q?c.lm--:x===q&&x>m&&c.lm--,x>r?c.lm++:x===r&&(r>q&&x>m||r<q&&x<m?f==="right"&&c.lm++:x>m?c.lm++:x===q&&c.lm--)}else c.li!==void 0&&c.ld===void 0&&j?(m=d.p[h],x=d.lm,s=c.p[h],s>m&&c.p[h]--,s>x&&c.p[h]++):(m=d.p[h],x=d.lm,s=c.p[h],s===m?c.p[h]=x:(s>m&&c.p[h]--,s>x?c.p[h]++:s===x&&m>x&&c.p[h]++));else if(d.oi!==void 0&&d.od!==void 0){if(c.p[h]===d.p[h]){if(c.oi===void 0||!j)return a;if(f==="right")return a;c.od=d.oi}}else if(d.oi!==void 0){if(c.oi!==void 0&&c.p[h]===d.p[h])if(f==="left")e.append(a,{p:c.p,od:d.oi});else return a}else if(d.od!==void 0&&c.p[h]===d.p[h]){if(!j)return a;if(c.oi!==void 0)delete c.od;else return a}}return e.append(a,c),a},typeof i!="undefined"&&i!==null?(k.types||(k.types={}),k._bt(e,e.transformComponent,e.checkValidOp,e.append),k.types.json=e):(module.exports=e,require("./helpers").bootstrapTransform(e,e.transformComponent,e.checkValidOp,e.append)),typeof i=="undefined"&&(e=require("./json")),c=function(a){return a.length===1&&a[0].constructor===Array?a[0]:a},a=function(){function a(a,b){this.doc=a,this.path=b}return a.prototype.at=function(){var a=1<=arguments.length?j.call(arguments,0):[];return this.doc.at(this.path.concat(c(a)))},a.prototype.get=function(){return this.doc.getAt(this.path)},a.prototype.set=function(a,b){return this.doc.setAt(this.path,a,b)},a.prototype.insert=function(a,b,c){return this.doc.insertAt(this.path,a,b,c)},a.prototype.del=function(a,b,c){return this.doc.deleteTextAt(this.path,b,a,c)},a.prototype.remove=function(a){return this.doc.removeAt(this.path,a)},a.prototype.push=function(a,b){return this.insert(this.get().length,a,b)},a.prototype.move=function(a,b,c){return this.doc.moveAt(this.path,a,b,c)},a.prototype.add=function(a,b){return this.doc.addAt(this.path,a,b)},a.prototype.on=function(a,b){return this.doc.addListener(this.path,a,b)},a.prototype.removeListener=function(a){return this.doc.removeListener(a)},a.prototype.getLength=function(){return this.get().length},a.prototype.getText=function(){return this.get()},a}(),h=function(a,b){var c,d,e,f={data:a},g="data",h=f;for(d=0,e=b.length;d<e;d++){c=b[d],h=h[g],g=c;if(typeof h=="undefined")throw new Error("bad path")}return{elem:h,key:g}},f=function(a,b){var c,d,e;if(a.length!==b.length)return!1;for(d=0,e=a.length;d<e;d++){c=a[d];if(c!==b[d])return!1}return!0},e.api={provides:{json:!0},at:function(){var b=1<=arguments.length?j.call(arguments,0):[];return new a(this,c(b))},get:function(){return this.snapshot},set:function(a,b){return this.setAt([],a,b)},getAt:function(a){var b=h(this.snapshot,a),c=b.elem,d=b.key;return c[d]},setAt:function(a,b,c){var d=h(this.snapshot,a),e=d.elem,f=d.key,g={p:a};if(e.constructor===Array)g.li=b,typeof e[f]!="undefined"&&(g.ld=e[f]);else if(typeof e=="object")g.oi=b,typeof e[f]!="undefined"&&(g.od=e[f]);else throw new Error("bad path");return this.submitOp([g],c)},removeAt:function(a,b){var c,d=h(this.snapshot,a),e=d.elem,f=d.key;if(typeof e[f]=="undefined")throw new Error("no element at that path");c={p:a};if(e.constructor===Array)c.ld=e[f];else if(typeof e=="object")c.od=e[f];else throw new Error("bad path");return this.submitOp([c],b)},insertAt:function(a,b,c,d){var e=h(this.snapshot,a),f=e.elem,g=e.key,i={p:a.concat(b)};return f[g].constructor===Array?i.li=c:typeof f[g]=="string"&&(i.si=c),this.submitOp([i],d)},moveAt:function(a,b,c,d){var e=[{p:a.concat(b),lm:c}];return this.submitOp(e,d)},addAt:function(a,b,c){var d=[{p:a,na:b}];return this.submitOp(d,c)},deleteTextAt:function(a,b,c,d){var e=h(this.snapshot,a),f=e.elem,g=e.key,i=[{p:a.concat(c),sd:f[g].slice(c,c+b)}];return this.submitOp(i,d)},addListener:function(a,b,c){var d={path:a,event:b,cb:c};return this._listeners.push(d),d},removeListener:function(a){var b=this._listeners.indexOf(a);return b<0?!1:(this._listeners.splice(b,1),!0)},_register:function(){return this._listeners=[],this.on("change",function(a){var b,c,d,e,f,g,h,i,j,k,l=[];for(h=0,i=a.length;h<i;h++){b=a[h];if(b.na!==void 0||b.si!==void 0||b.sd!==void 0)continue;f=[],k=this._listeners;for(d=0,j=k.length;d<j;d++){e=k[d],c={p:e.path,na:0},g=this.type.transformComponent([],c,b,"left");if(g.length===0)f.push(d);else if(g.length===1)e.path=g[0].p;else throw new Error("Bad assumption in json-api: xforming an 'si' op will always result in 0 or 1 components.")}f.sort(function(a,b){return b-a}),l.push(function(){var a,b,c=[];for(a=0,b=f.length;a<b;a++)d=f[a],c.push(this._listeners.splice(d,1));return c}.call(this))}return l}),this.on("remoteop",function(a){var b,c,d,e,g,h,i,j,k,l=[];for(j=0,k=a.length;j<k;j++)b=a[j],h=b.na===void 0?b.p.slice(0,b.p.length-1):b.p,l.push(function(){var a,j,k,l=this._listeners,m=[];for(a=0,j=l.length;a<j;a++)k=l[a],i=k.path,g=k.event,c=k.cb,m.push(function(){if(f(i,h))switch(g){case"insert":if(b.li!==void 0&&b.ld===void 0)return c(b.p[b.p.length-1],b.li);if(b.oi!==void 0&&b.od===void 0)return c(b.p[b.p.length-1],b.oi);if(b.si!==void 0)return c(b.p[b.p.length-1],b.si);break;case"delete":if(b.li===void 0&&b.ld!==void 0)return c(b.p[b.p.length-1],b.ld);if(b.oi===void 0&&b.od!==void 0)return c(b.p[b.p.length-1],b.od);if(b.sd!==void 0)return c(b.p[b.p.length-1],b.sd);break;case"replace":if(b.li!==void 0&&b.ld!==void 0)return c(b.p[b.p.length-1],b.ld,b.li);if(b.oi!==void 0&&b.od!==void 0)return c(b.p[b.p.length-1],b.od,b.oi);break;case"move":if(b.lm!==void 0)return c(b.p[b.p.length-1],b.lm);break;case"add":if(b.na!==void 0)return c(b.na)}else if((e=this.type.commonPath(h,i))!=null&&g==="child op"){if(h.length===i.length)throw new Error("paths match length and have commonality, but aren't equal?");return d=b.p.slice(e+1),c(d,b)}}.call(this));return m}.call(this));return l})}}})).call(this)
+((function(){var a,b,c,d,e,f,g,h,i=!0,j=Array.prototype.slice,k=window.sharejs;typeof i!="undefined"&&i!==null?g=k.types.text:g=require("./text"),e={},e.name="json",e.create=function(){return null},e.invertComponent=function(a){var b={p:a.p};return a.si!==void 0&&(b.sd=a.si),a.sd!==void 0&&(b.si=a.sd),a.oi!==void 0&&(b.od=a.oi),a.od!==void 0&&(b.oi=a.od),a.li!==void 0&&(b.ld=a.li),a.ld!==void 0&&(b.li=a.ld),a.na!==void 0&&(b.na=-a.na),a.lm!==void 0&&(b.lm=a.p[a.p.length-1],b.p=a.p.slice(0,a.p.length-1).concat([a.lm])),b},e.invert=function(a){var b,c,d,f=a.slice().reverse(),g=[];for(c=0,d=f.length;c<d;c++)b=f[c],g.push(e.invertComponent(b));return g},e.checkValidOp=function(){},d=function(a){return Object.prototype.toString.call(a)==="[object Array]"},e.checkList=function(a){if(!d(a))throw new Error("Referenced element not a list")},e.checkObj=function(a){if(a.constructor!==Object)throw new Error("Referenced element not an object (it was "+JSON.stringify(a)+")")},e.apply=function(a,c){var d,f,g,h,i,j,k,l,m,n,o,p,q;e.checkValidOp(c),c=b(c),f={data:b(a)};try{for(i=0,o=c.length;i<o;i++){d=c[i],l=null,m=null,h=f,j="data",q=d.p;for(n=0,p=q.length;n<p;n++){k=q[n],l=h,m=j,h=h[j],j=k;if(l==null)throw new Error("Path invalid")}if(d.na!==void 0){if(typeof h[j]!="number")throw new Error("Referenced element not a number");h[j]+=d.na}else if(d.si!==void 0){if(typeof h!="string")throw new Error("Referenced element not a string (it was "+JSON.stringify(h)+")");l[m]=h.slice(0,j)+d.si+h.slice(j)}else if(d.sd!==void 0){if(typeof h!="string")throw new Error("Referenced element not a string");if(h.slice(j,j+d.sd.length)!==d.sd)throw new Error("Deleted string does not match");l[m]=h.slice(0,j)+h.slice(j+d.sd.length)}else if(d.li!==void 0&&d.ld!==void 0)e.checkList(h),h[j]=d.li;else if(d.li!==void 0)e.checkList(h),h.splice(j,0,d.li);else if(d.ld!==void 0)e.checkList(h),h.splice(j,1);else if(d.lm!==void 0)e.checkList(h),d.lm!==j&&(g=h[j],h.splice(j,1),h.splice(d.lm,0,g));else if(d.oi!==void 0)e.checkObj(h),h[j]=d.oi;else{if(d.od===void 0)throw new Error("invalid / missing instruction in op");e.checkObj(h),delete h[j]}}}catch(r){throw r}return f.data},e.pathMatches=function(a,b,c){var d,e,f;if(a.length!==b.length)return!1;for(d=0,f=a.length;d<f;d++){e=a[d];if(e!==b[d]&&(!c||d!==a.length-1))return!1}return!0},e.append=function(a,c){var d;return c=b(c),a.length!==0&&e.pathMatches(c.p,(d=a[a.length-1]).p)?d.na!==void 0&&c.na!==void 0?a[a.length-1]={p:d.p,na:d.na+c.na}:d.li!==void 0&&c.li===void 0&&c.ld===d.li?d.ld!==void 0?delete d.li:a.pop():d.od!==void 0&&d.oi===void 0&&c.oi!==void 0&&c.od===void 0?d.oi=c.oi:c.lm!==void 0&&c.p[c.p.length-1]===c.lm?null:a.push(c):a.push(c)},e.compose=function(a,c){var d,f,g,h;e.checkValidOp(a),e.checkValidOp(c),f=b(a);for(g=0,h=c.length;g<h;g++)d=c[g],e.append(f,d);return f},e.normalize=function(a){var b,c,f,g=[];d(a)||(a=[a]);for(c=0,f=a.length;c<f;c++)b=a[c],b.p==null&&(b.p=[]),e.append(g,b);return g},b=function(a){return JSON.parse(JSON.stringify(a))},e.commonPath=function(a,b){var c;a=a.slice(),b=b.slice(),a.unshift("data"),b.unshift("data"),a=a.slice(0,a.length-1),b=b.slice(0,b.length-1);if(b.length===0)return-1;c=0;while(a[c]===b[c]&&c<a.length){c++;if(c===b.length)return c-1}},e.transformComponent=function(a,c,d,f){var h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z;c=b(c),c.na!==void 0&&c.p.push(0),d.na!==void 0&&d.p.push(0),h=e.commonPath(c.p,d.p),i=e.commonPath(d.p,c.p),l=c.p.length,p=d.p.length,c.na!==void 0&&c.p.pop(),d.na!==void 0&&d.p.pop();if(d.na)return i!=null&&p>=l&&d.p[i]===c.p[i]&&(c.ld!==void 0?(o=b(d),o.p=o.p.slice(l),c.ld=e.apply(b(c.ld),[o])):c.od!==void 0&&(o=b(d),o.p=o.p.slice(l),c.od=e.apply(b(c.od),[o]))),e.append(a,c),a;i!=null&&p>l&&c.p[i]===d.p[i]&&(c.ld!==void 0?(o=b(d),o.p=o.p.slice(l),c.ld=e.apply(b(c.ld),[o])):c.od!==void 0&&(o=b(d),o.p=o.p.slice(l),c.od=e.apply(b(c.od),[o])));if(h!=null){j=l===p;if(d.na===void 0)if(d.si!==void 0||d.sd!==void 0){if(c.si!==void 0||c.sd!==void 0){if(!j)throw new Error("must be a string?");k=function(a){var b={p:a.p[a.p.length-1]};return a.si?b.i=a.si:b.d=a.sd,b},v=k(c),w=k(d),t=[],g._tc(t,v,w,f);for(y=0,z=t.length;y<z;y++)u=t[y],n={p:c.p.slice(0,h)},n.p.push(u.p),u.i!=null&&(n.si=u.i),u.d!=null&&(n.sd=u.d),e.append(a,n);return a}}else if(d.li!==void 0&&d.ld!==void 0){if(d.p[h]===c.p[h]){if(!j)return a;if(c.ld!==void 0){if(c.li===void 0||f!=="left")return a;c.ld=b(d.li)}}}else if(d.li!==void 0)c.li!==void 0&&c.ld===void 0&&j&&c.p[h]===d.p[h]?f==="right"&&c.p[h]++:d.p[h]<=c.p[h]&&c.p[h]++,c.lm!==void 0&&j&&d.p[h]<=c.lm&&c.lm++;else if(d.ld!==void 0){if(c.lm!==void 0&&j){if(d.p[h]===c.p[h])return a;s=d.p[h],m=c.p[h],x=c.lm,(s<x||s===x&&m<x)&&c.lm--}if(d.p[h]<c.p[h])c.p[h]--;else if(d.p[h]===c.p[h]){if(p<l)return a;if(c.ld!==void 0){if(c.li===void 0)return a;delete c.ld}}}else if(d.lm!==void 0)if(c.lm!==void 0&&l===p){m=c.p[h],x=c.lm,q=d.p[h],r=d.lm;if(q!==r)if(m===q){if(f!=="left")return a;c.p[h]=r,m===x&&(c.lm=r)}else m>q&&c.p[h]--,m>r?c.p[h]++:m===r&&q>r&&(c.p[h]++,m===x&&c.lm++),x>q?c.lm--:x===q&&x>m&&c.lm--,x>r?c.lm++:x===r&&(r>q&&x>m||r<q&&x<m?f==="right"&&c.lm++:x>m?c.lm++:x===q&&c.lm--)}else c.li!==void 0&&c.ld===void 0&&j?(m=d.p[h],x=d.lm,s=c.p[h],s>m&&c.p[h]--,s>x&&c.p[h]++):(m=d.p[h],x=d.lm,s=c.p[h],s===m?c.p[h]=x:(s>m&&c.p[h]--,s>x?c.p[h]++:s===x&&m>x&&c.p[h]++));else if(d.oi!==void 0&&d.od!==void 0){if(c.p[h]===d.p[h]){if(c.oi===void 0||!j)return a;if(f==="right")return a;c.od=d.oi}}else if(d.oi!==void 0){if(c.oi!==void 0&&c.p[h]===d.p[h]){if(f!=="left")return a;e.append(a,{p:c.p,od:d.oi})}}else if(d.od!==void 0&&c.p[h]===d.p[h]){if(!j)return a;if(c.oi===void 0)return a;delete c.od}}return e.append(a,c),a},typeof i!="undefined"&&i!==null?(k.types||(k.types={}),k._bt(e,e.transformComponent,e.checkValidOp,e.append),k.types.json=e):(module.exports=e,require("./helpers").bootstrapTransform(e,e.transformComponent,e.checkValidOp,e.append)),typeof i=="undefined"&&(e=require("./json")),c=function(a){return a.length===1&&a[0].constructor===Array?a[0]:a},a=function(){function a(a,b){this.doc=a,this.path=b}return a.prototype.at=function(){var a=1<=arguments.length?j.call(arguments,0):[];return this.doc.at(this.path.concat(c(a)))},a.prototype.get=function(){return this.doc.getAt(this.path)},a.prototype.set=function(a,b){return this.doc.setAt(this.path,a,b)},a.prototype.insert=function(a,b,c){return this.doc.insertAt(this.path,a,b,c)},a.prototype.del=function(a,b,c){return this.doc.deleteTextAt(this.path,b,a,c)},a.prototype.remove=function(a){return this.doc.removeAt(this.path,a)},a.prototype.push=function(a,b){return this.insert(this.get().length,a,b)},a.prototype.move=function(a,b,c){return this.doc.moveAt(this.path,a,b,c)},a.prototype.add=function(a,b){return this.doc.addAt(this.path,a,b)},a.prototype.on=function(a,b){return this.doc.addListener(this.path,a,b)},a.prototype.removeListener=function(a){return this.doc.removeListener(a)},a.prototype.getLength=function(){return this.get().length},a.prototype.getText=function(){return this.get()},a}(),h=function(a,b){var c,d,e,f={data:a},g="data",h=f;for(d=0,e=b.length;d<e;d++){c=b[d],h=h[g],g=c;if(typeof h=="undefined")throw new Error("bad path")}return{elem:h,key:g}},f=function(a,b){var c,d,e;if(a.length!==b.length)return!1;for(d=0,e=a.length;d<e;d++){c=a[d];if(c!==b[d])return!1}return!0},e.api={provides:{json:!0},at:function(){var b=1<=arguments.length?j.call(arguments,0):[];return new a(this,c(b))},get:function(){return this.snapshot},set:function(a,b){return this.setAt([],a,b)},getAt:function(a){var b=h(this.snapshot,a),c=b.elem,d=b.key;return c[d]},setAt:function(a,b,c){var d=h(this.snapshot,a),e=d.elem,f=d.key,g={p:a};if(e.constructor===Array)g.li=b,typeof e[f]!="undefined"&&(g.ld=e[f]);else{if(typeof e!="object")throw new Error("bad path");g.oi=b,typeof e[f]!="undefined"&&(g.od=e[f])}return this.submitOp([g],c)},removeAt:function(a,b){var c,d=h(this.snapshot,a),e=d.elem,f=d.key;if(typeof e[f]=="undefined")throw new Error("no element at that path");c={p:a};if(e.constructor===Array)c.ld=e[f];else{if(typeof e!="object")throw new Error("bad path");c.od=e[f]}return this.submitOp([c],b)},insertAt:function(a,b,c,d){var e=h(this.snapshot,a),f=e.elem,g=e.key,i={p:a.concat(b)};return f[g].constructor===Array?i.li=c:typeof f[g]=="string"&&(i.si=c),this.submitOp([i],d)},moveAt:function(a,b,c,d){var e=[{p:a.concat(b),lm:c}];return this.submitOp(e,d)},addAt:function(a,b,c){var d=[{p:a,na:b}];return this.submitOp(d,c)},deleteTextAt:function(a,b,c,d){var e=h(this.snapshot,a),f=e.elem,g=e.key,i=[{p:a.concat(c),sd:f[g].slice(c,c+b)}];return this.submitOp(i,d)},addListener:function(a,b,c){var d={path:a,event:b,cb:c};return this._listeners.push(d),d},removeListener:function(a){var b=this._listeners.indexOf(a);return b<0?!1:(this._listeners.splice(b,1),!0)},_register:function(){return this._listeners=[],this.on("change",function(a){var b,c,d,e,f,g,h,i,j,k,l=[];for(h=0,i=a.length;h<i;h++){b=a[h];if(b.na!==void 0||b.si!==void 0||b.sd!==void 0)continue;f=[],k=this._listeners;for(d=0,j=k.length;d<j;d++){e=k[d],c={p:e.path,na:0},g=this.type.transformComponent([],c,b,"left");if(g.length===0)f.push(d);else{if(g.length!==1)throw new Error("Bad assumption in json-api: xforming an 'si' op will always result in 0 or 1 components.");e.path=g[0].p}}f.sort(function(a,b){return b-a}),l.push(function(){var a,b,c=[];for(a=0,b=f.length;a<b;a++)d=f[a],c.push(this._listeners.splice(d,1));return c}.call(this))}return l}),this.on("remoteop",function(a){var b,c,d,e,g,h,i,j,k,l=[];for(j=0,k=a.length;j<k;j++)b=a[j],h=b.na===void 0?b.p.slice(0,b.p.length-1):b.p,l.push(function(){var a,j,k,l,m=this._listeners,n=[];for(a=0,j=m.length;a<j;a++){k=m[a],i=k.path,g=k.event,c=k.cb;if(f(i,h))switch(g){case"insert":b.li!==void 0&&b.ld===void 0?n.push(c(b.p[b.p.length-1],b.li)):b.oi!==void 0&&b.od===void 0?n.push(c(b.p[b.p.length-1],b.oi)):b.si!==void 0?n.push(c(b.p[b.p.length-1],b.si)):n.push(void 0);break;case"delete":b.li===void 0&&b.ld!==void 0?n.push(c(b.p[b.p.length-1],b.ld)):b.oi===void 0&&b.od!==void 0?n.push(c(b.p[b.p.length-1],b.od)):b.sd!==void 0?n.push(c(b.p[b.p.length-1],b.sd)):n.push(void 0);break;case"replace":b.li!==void 0&&b.ld!==void 0?n.push(c(b.p[b.p.length-1],b.ld,b.li)):b.oi!==void 0&&b.od!==void 0?n.push(c(b.p[b.p.length-1],b.od,b.oi)):n.push(void 0);break;case"move":b.lm!==void 0?n.push(c(b.p[b.p.length-1],b.lm)):n.push(void 0);break;case"add":b.na!==void 0?n.push(c(b.na)):n.push(void 0);break;default:n.push(void 0)}else if((e=this.type.commonPath(h,i))!=null)if(g==="child op"){if(h.length===(l=i.length)&&l===e)throw new Error("paths match length and have commonality, but aren't equal?");d=b.p.slice(e+1),n.push(c(d,b))}else n.push(void 0);else n.push(void 0)}return n}.call(this));return l})}}})).call(this)
View
346 webclient/json.uncompressed.js
@@ -5,51 +5,44 @@
*/
var WEB = true;
;
- var SubDoc, clone, depath, exports, isArray, json, pathEquals, text, traverse;
- var __slice = Array.prototype.slice;
+ var SubDoc, clone, depath, exports, isArray, json, pathEquals, text, traverse,
+ __slice = Array.prototype.slice;
+
exports = window['sharejs'];
+
if (typeof WEB !== "undefined" && WEB !== null) {
text = exports.types.text;
} else {
text = require('./text');
}
+
json = {};
+
json.name = 'json';
+
json.create = function() {
return null;
};
+
json.invertComponent = function(c) {
var c_;
c_ = {
p: c.p
};
- if (c.si !== void 0) {
- c_.sd = c.si;
- }
- if (c.sd !== void 0) {
- c_.si = c.sd;
- }
- if (c.oi !== void 0) {
- c_.od = c.oi;
- }
- if (c.od !== void 0) {
- c_.oi = c.od;
- }
- if (c.li !== void 0) {
- c_.ld = c.li;
- }
- if (c.ld !== void 0) {
- c_.li = c.ld;
- }
- if (c.na !== void 0) {
- c_.na = -c.na;
- }
+ if (c.si !== void 0) c_.sd = c.si;
+ if (c.sd !== void 0) c_.si = c.sd;
+ if (c.oi !== void 0) c_.od = c.oi;
+ if (c.od !== void 0) c_.oi = c.od;
+ if (c.li !== void 0) c_.ld = c.li;
+ if (c.ld !== void 0) c_.li = c.ld;
+ if (c.na !== void 0) c_.na = -c.na;
if (c.lm !== void 0) {
c_.lm = c.p[c.p.length - 1];
- c_.p = c.p.slice(0, c.p.length - 1).concat([c.lm]);
+ c_.p = c.p.slice(0, (c.p.length - 1)).concat([c.lm]);
}
return c_;
};
+
json.invert = function(op) {
var c, _i, _len, _ref, _results;
_ref = op.slice().reverse();
@@ -60,20 +53,23 @@ var WEB = true;
}
return _results;
};
+
json.checkValidOp = function(op) {};
+
isArray = function(o) {
return Object.prototype.toString.call(o) === '[object Array]';
};
+
json.checkList = function(elem) {
- if (!isArray(elem)) {
- throw new Error('Referenced element not a list');
- }
+ if (!isArray(elem)) throw new Error('Referenced element not a list');
};
+
json.checkObj = function(elem) {
if (elem.constructor !== Object) {
throw new Error("Referenced element not an object (it was " + (JSON.stringify(elem)) + ")");
}
};
+
json.apply = function(snapshot, op) {
var c, container, e, elem, i, key, p, parent, parentkey, _i, _len, _len2, _ref;
json.checkValidOp(op);
@@ -95,9 +91,7 @@ var WEB = true;
parentkey = key;
elem = elem[key];
key = p;
- if (parent == null) {
- throw new Error('Path invalid');
- }
+ if (parent == null) throw new Error('Path invalid');
}
if (c.na !== void 0) {
if (typeof elem[key] !== 'number') {
@@ -113,7 +107,7 @@ var WEB = true;
if (typeof elem !== 'string') {
throw new Error('Referenced element not a string');
}
- if (elem.slice(key, key + c.sd.length) !== c.sd) {
+ if (elem.slice(key, (key + c.sd.length)) !== c.sd) {
throw new Error('Deleted string does not match');
}
parent[parentkey] = elem.slice(0, key) + elem.slice(key + c.sd.length);
@@ -148,19 +142,17 @@ var WEB = true;
}
return container.data;
};
+
json.pathMatches = function(p1, p2, ignoreLast) {
var i, p, _len;
- if (p1.length !== p2.length) {
- return false;
- }
+ if (p1.length !== p2.length) return false;
for (i = 0, _len = p1.length; i < _len; i++) {
p = p1[i];
- if (p !== p2[i] && (!ignoreLast || i !== p1.length - 1)) {
- return false;
- }
+ if (p !== p2[i] && (!ignoreLast || i !== p1.length - 1)) return false;
}
return true;
};
+
json.append = function(dest, c) {
var last;
c = clone(c);
@@ -187,6 +179,7 @@ var WEB = true;
return dest.push(c);
}
};
+
json.compose = function(op1, op2) {
var c, newOp, _i, _len;
json.checkValidOp(op1);
@@ -198,62 +191,50 @@ var WEB = true;
}
return newOp;
};
+
json.normalize = function(op) {
- var c, newOp, _i, _len, _ref;
+ var c, newOp, _i, _len;
newOp = [];
- if (!isArray(op)) {
- op = [op];
- }
+ if (!isArray(op)) op = [op];
for (_i = 0, _len = op.length; _i < _len; _i++) {
c = op[_i];
- if ((_ref = c.p) == null) {
- c.p = [];
- }
+ if (c.p == null) c.p = [];
json.append(newOp, c);
}
return newOp;
};
+
clone = function(o) {
return JSON.parse(JSON.stringify(o));
};
+
json.commonPath = function(p1, p2) {
var i;
p1 = p1.slice();
p2 = p2.slice();
p1.unshift('data');
p2.unshift('data');
- p1 = p1.slice(0, p1.length - 1);
- p2 = p2.slice(0, p2.length - 1);
- if (p2.length === 0) {
- return -1;
- }
+ p1 = p1.slice(0, (p1.length - 1));
+ p2 = p2.slice(0, (p2.length - 1));
+ if (p2.length === 0) return -1;
i = 0;
while (p1[i] === p2[i] && i < p1.length) {
i++;
- if (i === p2.length) {
- return i - 1;
- }
+ if (i === p2.length) return i - 1;
}
};
+
json.transformComponent = function(dest, c, otherC, type) {
var common, common2, commonOperand, convert, cplength, from, jc, oc, otherCplength, otherFrom, otherTo, p, res, tc, tc1, tc2, to, _i, _len;
c = clone(c);
- if (c.na !== void 0) {
- c.p.push(0);
- }
- if (otherC.na !== void 0) {
- otherC.p.push(0);
- }
+ if (c.na !== void 0) c.p.push(0);
+ if (otherC.na !== void 0) otherC.p.push(0);
common = json.commonPath(c.p, otherC.p);
common2 = json.commonPath(otherC.p, c.p);
cplength = c.p.length;
otherCplength = otherC.p.length;
- if (c.na !== void 0) {
- c.p.pop();
- }
- if (otherC.na !== void 0) {
- otherC.p.pop();
- }
+ if (c.na !== void 0) c.p.pop();
+ if (otherC.na !== void 0) otherC.p.pop();
if (otherC.na) {
if ((common2 != null) && otherCplength >= cplength && otherC.p[common2] === c.p[common2]) {
if (c.ld !== void 0) {
@@ -284,9 +265,7 @@ var WEB = true;
commonOperand = cplength === otherCplength;
if (otherC.na !== void 0) {} else if (otherC.si !== void 0 || otherC.sd !== void 0) {
if (c.si !== void 0 || c.sd !== void 0) {
- if (!commonOperand) {
- throw new Error("must be a string?");
- }
+ if (!commonOperand) throw new Error("must be a string?");
convert = function(component) {
var newC;
newC = {
@@ -309,12 +288,8 @@ var WEB = true;
p: c.p.slice(0, common)
};
jc.p.push(tc.p);
- if (tc.i != null) {
- jc.si = tc.i;
- }
- if (tc.d != null) {
- jc.sd = tc.d;
- }
+ if (tc.i != null) jc.si = tc.i;
+ if (tc.d != null) jc.sd = tc.d;
json.append(dest, jc);
}
return dest;
@@ -333,31 +308,21 @@ var WEB = true;
}
} else if (otherC.li !== void 0) {
if (c.li !== void 0 && c.ld === void 0 && commonOperand && c.p[common] === otherC.p[common]) {
- if (type === 'right') {
- c.p[common]++;
- }
+ if (type === 'right') c.p[common]++;
} else if (otherC.p[common] <= c.p[common]) {
c.p[common]++;
}
if (c.lm !== void 0) {
- if (commonOperand) {
- if (otherC.p[common] <= c.lm) {
- c.lm++;
- }
- }
+ if (commonOperand) if (otherC.p[common] <= c.lm) c.lm++;
}
} else if (otherC.ld !== void 0) {
if (c.lm !== void 0) {
if (commonOperand) {
- if (otherC.p[common] === c.p[common]) {
- return dest;
- }
+ if (otherC.p[common] === c.p[common]) return dest;
p = otherC.p[common];
from = c.p[common];
to = c.lm;
- if (p < to || (p === to && from < to)) {
- c.lm--;
- }
+ if (p < to || (p === to && from < to)) c.lm--;
}
}
if (otherC.p[common] < c.p[common]) {
@@ -383,40 +348,30 @@ var WEB = true;
if (from === otherFrom) {
if (type === 'left') {
c.p[common] = otherTo;
- if (from === to) {
- c.lm = otherTo;
- }
+ if (from === to) c.lm = otherTo;
} else {
return dest;
}
} else {
- if (from > otherFrom) {
- c.p[common]--;
- }
+ if (from > otherFrom) c.p[common]--;
if (from > otherTo) {
c.p[common]++;
} else if (from === otherTo) {
if (otherFrom > otherTo) {
c.p[common]++;
- if (from === to) {
- c.lm++;
- }
+ if (from === to) c.lm++;
}
}
if (to > otherFrom) {
c.lm--;
} else if (to === otherFrom) {
- if (to > from) {
- c.lm--;
- }
+ if (to > from) c.lm--;
}
if (to > otherTo) {
c.lm++;
} else if (to === otherTo) {
if ((otherTo > otherFrom && to > from) || (otherTo < otherFrom && to < from)) {
- if (type === 'right') {
- c.lm++;
- }
+ if (type === 'right') c.lm++;
} else {
if (to > from) {
c.lm++;
@@ -431,12 +386,8 @@ var WEB = true;
from = otherC.p[common];
to = otherC.lm;
p = c.p[common];
- if (p > from) {
- c.p[common]--;
- }
- if (p > to) {
- c.p[common]++;
- }
+ if (p > from) c.p[common]--;
+ if (p > to) c.p[common]++;
} else {
from = otherC.p[common];
to = otherC.lm;
@@ -444,15 +395,11 @@ var WEB = true;
if (p === from) {
c.p[common] = to;
} else {
- if (p > from) {
- c.p[common]--;
- }
+ if (p > from) c.p[common]--;
if (p > to) {
c.p[common]++;
} else if (p === to) {
- if (from > to) {
- c.p[common]++;
- }
+ if (from > to) c.p[common]++;
}
}
}
@@ -481,9 +428,7 @@ var WEB = true;
}
} else if (otherC.od !== void 0) {
if (c.p[common] === otherC.p[common]) {
- if (!commonOperand) {
- return dest;
- }
+ if (!commonOperand) return dest;
if (c.oi !== void 0) {
delete c.od;
} else {
@@ -495,6 +440,7 @@ var WEB = true;
json.append(dest, c);
return dest;
};
+
if (typeof WEB !== "undefined" && WEB !== null) {
exports.types || (exports.types = {});
exports._bt(json, json.transformComponent, json.checkValidOp, json.append);
@@ -503,9 +449,9 @@ var WEB = true;
module.exports = json;
require('./helpers').bootstrapTransform(json, json.transformComponent, json.checkValidOp, json.append);
}
- if (typeof WEB === 'undefined') {
- json = require('./json');
- }
+
+ if (typeof WEB === 'undefined') json = require('./json');
+
depath = function(path) {
if (path.length === 1 && path[0].constructor === Array) {
return path[0];
@@ -513,54 +459,72 @@ var WEB = true;
return path;
}
};
+
SubDoc = (function() {
+
function SubDoc(doc, path) {
this.doc = doc;
this.path = path;
}
+
SubDoc.prototype.at = function() {
var path;
path = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
return this.doc.at(this.path.concat(depath(path)));
};
+
SubDoc.prototype.get = function() {
return this.doc.getAt(this.path);
};
+
SubDoc.prototype.set = function(value, cb) {
return this.doc.setAt(this.path, value, cb);
};
+
SubDoc.prototype.insert = function(pos, value, cb) {
return this.doc.insertAt(this.path, pos, value, cb);
};
+
SubDoc.prototype.del = function(pos, length, cb) {
return this.doc.deleteTextAt(this.path, length, pos, cb);
};
+
SubDoc.prototype.remove = function(cb) {
return this.doc.removeAt(this.path, cb);
};
+
SubDoc.prototype.push = function(value, cb) {
return this.insert(this.get().length, value, cb);
};
+
SubDoc.prototype.move = function(from, to, cb) {
return this.doc.moveAt(this.path, from, to, cb);
};
+
SubDoc.prototype.add = function(amount, cb) {
return this.doc.addAt(this.path, amount, cb);
};
+
SubDoc.prototype.on = function(event, cb) {
return this.doc.addListener(this.path, event, cb);
};
+
SubDoc.prototype.removeListener = function(l) {
return this.doc.removeListener(l);
};
+
SubDoc.prototype.getLength = function() {
return this.get().length;
};
+
SubDoc.prototype.getText = function() {
return this.get();
};
+
return SubDoc;
+
})();
+
traverse = function(snapshot, path) {
var container, elem, key, p, _i, _len;
container = {
@@ -572,28 +536,24 @@ var WEB = true;
p = path[_i];
elem = elem[key];
key = p;
- if (typeof elem === 'undefined') {
- throw new Error('bad path');
- }
+ if (typeof elem === 'undefined') throw new Error('bad path');
}
return {
elem: elem,
key: key
};
};
+
pathEquals = function(p1, p2) {
var e, i, _len;
- if (p1.length !== p2.length) {
- return false;
- }
+ if (p1.length !== p2.length) return false;
for (i = 0, _len = p1.length; i < _len; i++) {
e = p1[i];
- if (e !== p2[i]) {
- return false;
- }
+ if (e !== p2[i]) return false;
}
return true;
};
+
json.api = {
provides: {
json: true
@@ -622,14 +582,10 @@ var WEB = true;
};
if (elem.constructor === Array) {
op.li = value;
- if (typeof elem[key] !== 'undefined') {
- op.ld = elem[key];
- }
+ 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];
- }
+ if (typeof elem[key] !== 'undefined') op.od = elem[key];
} else {
throw new Error('bad path');
}
@@ -692,7 +648,7 @@ var WEB = true;
op = [
{
p: path.concat(pos),
- sd: elem[key].slice(pos, pos + length)
+ sd: elem[key].slice(pos, (pos + length))
}
];
return this.submitOp(op, cb);
@@ -710,9 +666,7 @@ var WEB = true;
removeListener: function(l) {
var i;
i = this._listeners.indexOf(l);
- if (i < 0) {
- return false;
- }
+ if (i < 0) return false;
this._listeners.splice(i, 1);
return true;
},
@@ -723,9 +677,7 @@ var WEB = true;
_results = [];
for (_i = 0, _len = op.length; _i < _len; _i++) {
c = op[_i];
- if (c.na !== void 0 || c.si !== void 0 || c.sd !== void 0) {
- continue;
- }
+ if (c.na !== void 0 || c.si !== void 0 || c.sd !== void 0) continue;
to_remove = [];
_ref = this._listeners;
for (i = 0, _len2 = _ref.length; i < _len2; i++) {
@@ -763,61 +715,76 @@ var WEB = true;
_results = [];
for (_i = 0, _len = op.length; _i < _len; _i++) {
c = op[_i];
- match_path = c.na === void 0 ? c.p.slice(0, c.p.length - 1) : c.p;
+ match_path = c.na === void 0 ? c.p.slice(0, (c.p.length - 1)) : c.p;
_results.push((function() {
- var _j, _len2, _ref, _ref2, _results2;
+ var _j, _len2, _ref, _ref2, _ref3, _results2;
_ref = this._listeners;
_results2 = [];
for (_j = 0, _len2 = _ref.length; _j < _len2; _j++) {
_ref2 = _ref[_j], path = _ref2.path, event = _ref2.event, cb = _ref2.cb;
- _results2.push((function() {
- if (pathEquals(path, match_path)) {
- switch (event) {
- case 'insert':
- if (c.li !== void 0 && c.ld === void 0) {
- return cb(c.p[c.p.length - 1], c.li);
- } else if (c.oi !== void 0 && c.od === void 0) {
- return cb(c.p[c.p.length - 1], c.oi);
- } else if (c.si !== void 0) {
- return cb(c.p[c.p.length - 1], c.si);
- }
- break;
- case 'delete':
- if (c.li === void 0 && c.ld !== void 0) {
- return cb(c.p[c.p.length - 1], c.ld);
- } else if (c.oi === void 0 && c.od !== void 0) {
- return cb(c.p[c.p.length - 1], c.od);
- } else if (c.sd !== void 0) {
- return cb(c.p[c.p.length - 1], c.sd);
- }
- break;
- case 'replace':
- if (c.li !== void 0 && c.ld !== void 0) {
- return cb(c.p[c.p.length - 1], c.ld, c.li);
- } else if (c.oi !== void 0 && c.od !== void 0) {
- return cb(c.p[c.p.length - 1], c.od, c.oi);
- }
- break;
- case 'move':
- if (c.lm !== void 0) {
- return cb(c.p[c.p.length - 1], c.lm);
- }
- break;
- case 'add':
- if (c.na !== void 0) {
- return cb(c.na);
- }
- }
- } else if ((common = this.type.commonPath(match_path, path)) != null) {
- if (event === 'child op') {
- if (match_path.length === path.length) {
- throw new Error("paths match length and have commonality, but aren't equal?");
+ if (pathEquals(path, match_path)) {
+ switch (event) {
+ case 'insert':
+ if (c.li !== void 0 && c.ld === void 0) {
+ _results2.push(cb(c.p[c.p.length - 1], c.li));
+ } else if (c.oi !== void 0 && c.od === void 0) {
+ _results2.push(cb(c.p[c.p.length - 1], c.oi));
+ } else if (c.si !== void 0) {
+ _results2.push(cb(c.p[c.p.length - 1], c.si));
+ } else {
+ _results2.push(void 0);
}
- child_path = c.p.slice(common + 1);
- return cb(child_path, c);
+ break;
+ case 'delete':
+ if (c.li === void 0 && c.ld !== void 0) {
+ _results2.push(cb(c.p[c.p.length - 1], c.ld));
+ } else if (c.oi === void 0 && c.od !== void 0) {
+ _results2.push(cb(c.p[c.p.length - 1], c.od));
+ } else if (c.sd !== void 0) {
+ _results2.push(cb(c.p[c.p.length - 1], c.sd));
+ } else {
+ _results2.push(void 0);
+ }
+ break;
+ case 'replace':
+ if (c.li !== void 0 && c.ld !== void 0) {
+ _results2.push(cb(c.p[c.p.length - 1], c.ld, c.li));
+ } else if (c.oi !== void 0 && c.od !== void 0) {
+ _results2.push(cb(c.p[c.p.length - 1], c.od, c.oi));
+ } else {
+ _results2.push(void 0);
+ }
+ break;
+ case 'move':
+ if (c.lm !== void 0) {
+ _results2.push(cb(c.p[c.p.length - 1], c.lm));
+ } else {
+ _results2.push(void 0);
+ }
+ break;
+ case 'add':
+ if (c.na !== void 0) {
+ _results2.push(cb(c.na));
+ } else {
+ _results2.push(void 0);
+ }
+ break;
+ default:
+ _results2.push(void 0);
+ }
+ } else if ((common = this.type.commonPath(match_path, path)) != null) {
+ if (event === 'child op') {
+ if ((match_path.length === (_ref3 = path.length) && _ref3 === common)) {
+ throw new Error("paths match length and have commonality, but aren't equal?");
}
+ child_path = c.p.slice(common + 1);
+ _results2.push(cb(child_path, c));
+ } else {
+ _results2.push(void 0);
}
- }).call(this));
+ } else {
+ _results2.push(void 0);
+ }
}
return _results2;
}).call(this));
@@ -826,4 +793,5 @@ var WEB = true;
});
}
};
+
}).call(this);
View
12 webclient/share-ace.js
@@ -1,6 +1,8 @@
(function() {
var Range, applyToShareJS;
+
Range = require("ace/range").Range;
+
applyToShareJS = function(editorDoc, delta, doc) {
var getStartOffsetPosition, pos, text;
getStartOffsetPosition = function(range) {
@@ -33,6 +35,7 @@
throw new Error("unknown action: " + delta.action);
}
};
+
window.sharejs.Doc.prototype.attach_ace = function(editor, keepEditorContents) {
var check, doc, docListener, editorDoc, editorListener, offsetToPos, suppress;
if (!this.provides['text']) {
@@ -62,9 +65,7 @@
check();
suppress = false;
editorListener = function(change) {
- if (suppress) {
- return;
- }
+ if (suppress) return;
applyToShareJS(editorDoc, change.data, doc);
return check();
};
@@ -81,9 +82,7 @@
row = 0;
for (row = 0, _len = lines.length; row < _len; row++) {
line = lines[row];
- if (offset <= line.length) {
- break;
- }
+ if (offset <= line.length) break;
offset -= lines[row].length + 1;
}
return {
@@ -111,4 +110,5 @@
return delete doc.detach_ace;
};
};
+
}).call(this);
View
2  webclient/share.js
@@ -1 +1 @@
-((function(){var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q=function(a,b){return function(){return a.apply(b,arguments)}},r=Array.prototype.slice;window.sharejs=h={version:"0.5.0-pre"},k=function(a){return setTimeout(a,0)},c=function(){function a(){}return a.prototype.on=function(a,b){var c;return this._events||(this._events={}),(c=this._events)[a]||(c[a]=[]),this._events[a].push(b),this},a.prototype.removeListener=function(a,b){var c,d,e;this._events||(this._events={}),d=(e=this._events)[a]||(e[a]=[]),c=0;while(c<d.length)d[c]===b&&(d[c]=void 0),c++;return k(q(function(){var b;return this._events[a]=function(){var c,d,e=this._events[a],f=[];for(c=0,d=e.length;c<d;c++)b=e[c],b&&f.push(b);return f}.call(this)},this)),this},a.prototype.emit=function(){var a,b,c,d,e,f=arguments[0],g=2<=arguments.length?r.call(arguments,1):[];if((d=this._events)!=null?!d[f]:!void 0)return this;e=this._events[f];for(b=0,c=e.length;b<c;b++)a=e[b],a&&a.apply(this,g);return this},a}(),c.mixin=function(a){var b=a.prototype||a;return b.on=c.prototype.on,b.removeListener=c.prototype.removeListener,b.emit=c.prototype.emit,a},h._bt=e=function(a,b,c,d){var e,f=function(a,c,d,e){return b(d,a,c,"left"),b(e,c,a,"right")};return a.transformX=a.transformX=e=function(a,b){var g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y;c(a),c(b),k=[];for(p=0,t=b.length;p<t;p++){o=b[p],j=[],g=0;while(g<a.length){l=[],f(a[g],o,j,l),g++;if(l.length===1)o=l[0];else{if(l.length===0){x=a.slice(g);for(q=0,u=x.length;q<u;q++)h=x[q],d(j,h);o=null;break}y=e(a.slice(g),l),i=y[0],n=y[1];for(r=0,v=i.length;r<v;r++)h=i[r],d(j,h);for(s=0,w=n.length;s<w;s++)m=n[s],d(k,m);o=null;break}}o!=null&&d(k,o),a=j}return[a,k]},a.transform=a.transform=function(a,c,d){var f,g,h,i,j;if(d!=="left"&&d!=="right")throw new Error("type must be 'left' or 'right'");return c.length===0?a:a.length===1&&c.length===1?b([],a[0],c[0],d):d==="left"?(i=e(a,c),f=i[0],h=i[1],f):(j=e(c,a),h=j[0],g=j[1],g)}},m={},m.name="text",m.create=m.create=function(){return""},l=function(a,b,c){return a.slice(0,b)+c+a.slice(b)},f=function(a){var b,c;if(typeof a.p!="number")throw new Error("component missing position field");c=typeof a.i,b=typeof a.d;if(!(c==="string"^b==="string"))throw new Error("component needs an i or d field");if(!(a.p>=0))throw new Error("position cannot be negative")},g=function(a){var b,c,d;for(c=0,d=a.length;c<d;c++)b=a[c],f(b);return!0},m.apply=function(a,b){var c,d,e,f;g(b);for(e=0,f=b.length;e<f;e++){c=b[e];if(c.i!=null)a=l(a,c.p,c.i);else{d=a.slice(c.p,c.p+c.d.length);if(c.d!==d)throw new Error("Delete component '"+c.d+"' does not match deleted text '"+d+"'");a=a.slice(0,c.p)+a.slice(c.p+c.d.length)}}return a},m._append=d=function(a,b){var c,d,e;if(b.i===""||b.d==="")return;return a.length===0?a.push(b):(c=a[a.length-1],c.i!=null&&b.i!=null&&c.p<=(d=b.p)&&d<=c.p+c.i.length?a[a.length-1]={i:l(c.i,b.p-c.p,b.i),p:c.p}:c.d!=null&&b.d!=null&&b.p<=(e=c.p)&&e<=b.p+b.d.length?a[a.length-1]={d:l(b.d,c.p-b.p,c.d),p:b.p}:a.push(b))},m.compose=function(a,b){var c,e,f,h;g(a),g(b),e=a.slice();for(f=0,h=b.length;f<h;f++)c=b[f],d(e,c);return e},m.compress=function(a){return m.compose([],a)},m.normalize=function(a){var b,c,e,f,g=[];if(a.i!=null||a.p!=null)a=[a];for(c=0,e=a.length;c<e;c++)b=a[c],(f=b.p)==null&&(b.p=0),d(g,b);return g},o=function(a,b,c){return b.i!=null?b.p<a||b.p===a&&c?a+b.i.length:a:a<=b.p?a:a<=b.p+b.d.length?b.p:a-b.d.length},m.transformCursor=function(a,b,c){var d,e,f;for(e=0,f=b.length;e<f;e++)d=b[e],a=o(a,d,c);return a},m._tc=n=function(a,b,c,e){var f,h,i,j,k,l;g([b]),g([c]);if(b.i!=null)d(a,{i:b.i,p:o(b.p,c,e==="right")});else if(c.i!=null)l=b.d,b.p<c.p&&(d(a,{d:l.slice(0,c.p-b.p),p:b.p}),l=l.slice(c.p-b.p)),l!==""&&d(a,{d:l,p:b.p+c.i.length});else if(b.p>=c.p+c.d.length)d(a,{d:b.d,p:b.p-c.d.length});else if(b.p+b.d.length<=c.p)d(a,b);else{j={d:"",p:b.p},b.p<c.p&&(j.d=b.d.slice(0,c.p-b.p)),b.p+b.d.length>c.p+c.d.length&&(j.d+=b.d.slice(c.p+c.d.length-b.p)),i=Math.max(b.p,c.p),h=Math.min(b.p+b.d.length,c.p+c.d.length),f=b.d.slice(i-b.p,h-b.p),k=c.d.slice(i-c.p,h-c.p);if(f!==k)throw new Error("Delete ops delete different text in the same region of the document");j.d!==""&&(j.p=o(j.p,c),d(a,j))}return a},i=function(a){return a.i!=null?{d:a.i,p:a.p}:{i:a.d,p:a.p}},m.invert=function(a){var b,c,d,e=a.slice().reverse(),f=[];for(c=0,d=e.length;c<d;c++)b=e[c],f.push(i(b));return f},h.types||(h.types={}),e(m,n,g,d),h.types.text=m,m.api={provides:{text:!0},getLength:function(){return this.snapshot.length},getText:function(){return this.snapshot},insert:function(a,b,c){var d=[{p:a,i:b}];return this.submitOp(d,c),d},del:function(a,b,c){var d=[{p:a,d:this.snapshot.slice(a,a+b)}];return this.submitOp(d,c),d},_register:function(){return this.on("remoteop",function(a){var b,c,d,e=[];for(c=0,d=a.length;c<d;c++)b=a[c],e.push(b.i!==void 0?this.emit("insert",b.p,b.i):this.emit("delete",b.p,b.d));return e})}},b=function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o;this.name=b,this.version=c,this.type=d,this.snapshot=e;if(this.type.compose==null)throw new Error("Handling types without compose() defined is not currently implemented");g=null,f=[],k=null,j=[],l={},n=this.type.transformX||q(function(a,b){var c=this.type.transform(a,b,"left"),d=this.type.transform(b,a,"right");return[c,d]},this),i=q(function(a,b){var c=this.snapshot;this.snapshot=this.type.apply(this.snapshot,a),this.emit("change",a,c);if(b)return this.emit("remoteop",a,c)},this),this.flush=q(function(){if(g===null&&k!==null)return g=k,f=j,k=null,j=[],a.send({doc:this.name,op:g,v:this.version},q(function(a,b){var c,e,h,j,m,o,p,q=g;g=null;if(a){if(d.invert)e=this.type.invert(q),k&&(p=n(k,e),k=p[0],e=p[1]),i(e,!0);else throw new Error("Op apply failed ("+b.error+") and the OT type does not define an invert function.");for(h=0,m=f.length;h<m;h++)c=f[h],c(a)}else{if(b.v!==this.version)throw new Error("Invalid version from server");l[this.version]=q,this.version++;for(j=0,o=f.length;j<o;j++)c=f[j],c(null,q)}return this.flush()},this))},this),this._onOpReceived=function(a){var b,c,d,e;if(a.v<this.version)return;if(a.doc!==this.name)throw new Error("Expected docName '"+this.name+"' but got "+a.doc);if(a.v!==this.version)throw new Error("Expected version "+this.version+" but got "+a.v);return c=a.op,l[this.version]=c,b=c,g!==null&&(d=n(g,b),g=d[0],b=d[1]),k!==null&&(e=n(k,b),k=e[0],b=e[1]),this.version++,i(b,!0)},this.submitOp=function(a,b){return this.type.normalize!=null&&(a=this.type.normalize(a)),this.snapshot=this.type.apply(this.snapshot,a),k!==null?k=this.type.compose(k,a):k=a,b&&j.push(b),this.emit("change",a),setTimeout(this.flush,0)},this.close=function(b){return a.socket===null?typeof b=="function"?b():void 0:(a.send({doc:this.name,open:!1},q(function(){typeof b=="function"&&b(),this.emit("closed")},this)),this.emit("closing"))};if(this.type.api){o=this.type.api;for(h in o)m=o[h],this[h]=m;typeof this._register=="function"&&this._register()}else this.provides={};return this},c.mixin(b),h.Doc=b,p||(p=h.types);if(!window.io)throw new Error("Must load socket.io before this library");j=window.io,a=function(){function a(a){this.onMessage=q(this.onMessage,this),this.connected=q(this.connected,this),this.disconnected=q(this.disconnected,this),this.docs={},this.handlers={},this.state="connecting",this.socket=j.connect(a,{"force new connection":!0}),this.socket.on("connect",this.connected),this.socket.on("disconnect",this.disconnected),this.socket.on("message",this.onMessage),this.socket.on("connect_failed",q(function(a){var b,c,d,e,f,g,h;a==="unauthorized"&&(a="forbidden"),this.socket=null,this.emit("connect failed",a),g=this.handlers,h=[];for(d in g)e=g[d],h.push(function(){var d=[];for(f in e)c=e[f],d.push(function(){var d,e,f=[];for(d=0,e=c.length;d<e;d++)b=c[d],f.push(b(a));return f}());return d}());return h},this))}return a.prototype.disconnected=function(){return this.emit("disconnect"),this.socket=null},a.prototype.connected=function(){return this.emit("connect")},a.prototype.send=function(a,b){var c,d,e,f,g;if(this.socket===null)throw new Error("Cannot send message "+JSON.stringify(a)+" to a closed connection");e=a.doc,e===this.lastSentDoc?delete a.doc:this.lastSentDoc=e,this.socket.json.send(a);if(b)return f=a.open===!0?"open":a.open===!1?"close":a.create?"create":a.snapshot===null?"snapshot":a.op?"op response":void 0,d=(g=this.handlers)[e]||(g[e]={}),c=d[f]||(d[f]=[]),c.push(b)},a.prototype.onMessage=function(a){var b,c,d,e,f,g,h,i=a.doc;i!==void 0?this.lastReceivedDoc=i:a.doc=i=this.lastReceivedDoc,this.emit("message",a),e=a.open===!0||a.open===!1&&a.error?"open":a.open===!1?"close":a.snapshot!==void 0?"snapshot":a.create?"create":a.op?"op":a.v!==void 0?"op response":void 0,c=(h=this.handlers[i])!=null?h[e]:void 0;if(c){delete this.handlers[i][e];for(f=0,g=c.length;f<g;f++)b=c[f],b(a.error,a)}if(e==="op"){d=this.docs[i];if(d)return d._onOpReceived(a)}},a.prototype.makeDoc=function(a){var c,d,e=a.doc;if(this.docs[e])throw new Error("Doc "+e+" already open");return d=a.type,typeof d=="string"&&(d=p[d]),c=new b(this,e,a.v,d,a.snapshot),c.created=!!a.create,this.docs[e]=c,c.on("closing",q(function(){return delete this.docs[e]},this)),c},a.prototype.openExisting=function(a,b){if(this.socket===null){b("connection closed");return}return this.docs[a]!=null?this.docs[a]:this.send({doc:a,open:!0,snapshot:null},q(function(a,c){return a?b(a):b(null,this.makeDoc(c))},this))},a.prototype.open=function(a,b,c){var d;if(this.socket===null){c("connection closed");return}typeof b=="function"&&(c=b,b="text"),c||(c=function(){}),typeof b=="string"&&(b=p[b]);if(!b)throw new Error("OT code for document type missing");if(a!=null&&this.docs[a]!=null){d=this.docs[a],d.type===b?c(null,d):c("Type mismatch",d);return}return this.send({doc:a,open:!0,create:!0,snapshot:null,type:b.name},q(function(a,d){return a?c(a):(d.snapshot===void 0&&(d.snapshot=b.create()),d.type=b,c(null,this.makeDoc(d)))},this))},a.prototype.create=function(a,b){return open(null,a,b)},a.prototype.disconnect=function(){if(this.socket)return this.emit("disconnecting"),this.socket.disconnect(),this.socket=null},a}(),c.mixin(a),h.Connection=a,h.open=function(){var b={},c=function(c){var d,e,f;return f=window.location,c==null&&(c=""+f.protocol+"//"+f.hostname+"/sjs"),b[c]||(d=new a(c),d.numDocs=0,e=function(){return delete b[c]},d.on("disconnecting",e),d.on("connect failed",e),b[c]=d),b[c]};return function(a,b,d,e){var f;return typeof d=="function"&&(e=d,d=null),f=c(d),f.numDocs++,f.open(a,b,function(a,b){return a?(f.numDocs--,f.numDocs===0&&f.disconnect(),e(a)):(b.on("closed",function(){f.numDocs--;if(f.numDocs===0)return f.disconnect()}),e(null,b))}),f.on("connect failed")}}()})).call(this)
+((function(){var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q=Array.prototype.slice,r=function(a,b){return function(){return a.apply(b,arguments)}},s=Array.prototype.indexOf||function(a){for(var b=0,c=this.length;b<c;b++)if(b in this&&this[b]===a)return b;return-1};window.sharejs=i={version:"0.5.0-pre"},k=function(a){return setTimeout(a,0)},d=function(){function a(){}return a.prototype.on=function(a,b){var c;return this._events||(this._events={}),(c=this._events)[a]||(c[a]=[]),this._events[a].push(b),this},a.prototype.removeListener=function(a,b){var c,d,e,f=this;this._events||(this._events={}),d=(e=this._events)[a]||(e[a]=[]),c=0;while(c<d.length)d[c]===b&&(d[c]=void 0),c++;return k(function(){var b;return f._events[a]=function(){var c,d,e=this._events[a],f=[];for(c=0,d=e.length;c<d;c++)b=e[c],b&&f.push(b);return f}.call(f)}),this},a.prototype.emit=function(){var a,b,c,d,e,f=arguments[0],g=2<=arguments.length?q.call(arguments,1):[];if((d=this._events)!=null?!d[f]:!void 0)return this;e=this._events[f];for(b=0,c=e.length;b<c;b++)a=e[b],a&&a.apply(this,g);return this},a}(),d.mixin=function(a){var b=a.prototype||a;return b.on=d.prototype.on,b.removeListener=d.prototype.removeListener,b.emit=d.prototype.emit,a},i._bt=f=function(a,b,c,d){var e,f=function(a,c,d,e){return b(d,a,c,"left"),b(e,c,a,"right")};return a.transformX=a.transformX=e=function(a,b){var g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y;c(a),c(b),k=[];for(p=0,t=b.length;p<t;p++){o=b[p],j=[],g=0;while(g<a.length){l=[],f(a[g],o,j,l),g++;if(l.length!==1){if(l.length===0){x=a.slice(g);for(q=0,u=x.length;q<u;q++)h=x[q],d(j,h);o=null;break}y=e(a.slice(g),l),i=y[0],n=y[1];for(r=0,v=i.length;r<v;r++)h=i[r],d(j,h);for(s=0,w=n.length;s<w;s++)m=n[s],d(k,m);o=null;break}o=l[0]}o!=null&&d(k,o),a=j}return[a,k]},a.transform=a.transform=function(a,c,d){var f,g,h,i,j;if(d!=="left"&&d!=="right")throw new Error("type must be 'left' or 'right'");return c.length===0?a:a.length===1&&c.length===1?b([],a[0],c[0],d):d==="left"?(i=e(a,c),f=i[0],h=i[1],f):(j=e(c,a),h=j[0],g=j[1],g)}},m={},m.name="text",m.create=m.create=function(){return""},l=function(a,b,c){return a.slice(0,b)+c+a.slice(b)},g=function(a){var b,c;if(typeof a.p!="number")throw new Error("component missing position field");c=typeof a.i,b=typeof a.d;if(!(c==="string"^b==="string"))throw new Error("component needs an i or d field");if(!(a.p>=0))throw new Error("position cannot be negative")},h=function(a){var b,c,d;for(c=0,d=a.length;c<d;c++)b=a[c],g(b);return!0},m.apply=function(a,b){var c,d,e,f;h(b);for(e=0,f=b.length;e<f;e++){c=b[e];if(c.i!=null)a=l(a,c.p,c.i);else{d=a.slice(c.p,c.p+c.d.length);if(c.d!==d)throw new Error("Delete component '"+c.d+"' does not match deleted text '"+d+"'");a=a.slice(0,c.p)+a.slice(c.p+c.d.length)}}return a},m._append=e=function(a,b){var c,d,e;if(b.i===""||b.d==="")return;return a.length===0?a.push(b):(c=a[a.length-1],c.i!=null&&b.i!=null&&c.p<=(d=b.p)&&d<=c.p+c.i.length?a[a.length-1]={i:l(c.i,b.p-c.p,b.i),p:c.p}:c.d!=null&&b.d!=null&&b.p<=(e=c.p)&&e<=b.p+b.d.length?a[a.length-1]={d:l(b.d,c.p-b.p,c.d),p:b.p}:a.push(b))},m.compose=function(a,b){var c,d,f,g;h(a),h(b),d=a.slice();for(f=0,g=b.length;f<g;f++)c=b[f],e(d,c);return d},m.compress=function(a){return m.compose([],a)},m.normalize=function(a){var b,c,d,f=[];if(a.i!=null||a.p!=null)a=[a];for(c=0,d=a.length;c<d;c++)b=a[c],b.p==null&&(b.p=0),e(f,b);return f},o=function(a,b,c){return b.i!=null?b.p<a||b.p===a&&c?a+b.i.length:a:a<=b.p?a:a<=b.p+b.d.length?b.p:a-b.d.length},m.transformCursor=function(a,b,c){var d,e,f;for(e=0,f=b.length;e<f;e++)d=b[e],a=o(a,d,c);return a},m._tc=n=function(a,b,c,d){var f,g,i,j,k,l;h([b]),h([c]);if(b.i!=null)e(a,{i:b.i,p:o(b.p,c,d==="right")});else if(c.i!=null)l=b.d,b.p<c.p&&(e(a,{d:l.slice(0,c.p-b.p),p:b.p}),l=l.slice(c.p-b.p)),l!==""&&e(a,{d:l,p:b.p+c.i.length});else if(b.p>=c.p+c.d.length)e(a,{d:b.d,p:b.p-c.d.length});else if(b.p+b.d.length<=c.p)e(a,b);else{j={d:"",p:b.p},b.p<c.p&&(j.d=b.d.slice(0,c.p-b.p)),b.p+b.d.length>c.p+c.d.length&&(j.d+=b.d.slice(c.p+c.d.length-b.p)),i=Math.max(b.p,c.p),g=Math.min(b.p+b.d.length,c.p+c.d.length),f=b.d.slice(i-b.p,g-b.p),k=c.d.slice(i-c.p,g-c.p);if(f!==k)throw new Error("Delete ops delete different text in the same region of the document");j.d!==""&&(j.p=o(j.p,c),e(a,j))}return a},j=function(a){return a.i!=null?{d:a.i,p:a.p}:{i:a.d,p:a.p}},m.invert=function(a){var b,c,d,e=a.slice().reverse(),f=[];for(c=0,d=e.length;c<d;c++)b=e[c],f.push(j(b));return f},i.types||(i.types={}),f(m,n,h,e),i.types.text=m,m.api={provides:{text:!0},getLength:function(){return this.snapshot.length},getText:function(){return this.snapshot},insert:function(a,b,c){var d=[{p:a,i:b}];return this.submitOp(d,c),d},del:function(a,b,c){var d=[{p:a,d:this.snapshot.slice(a,a+b)}];return this.submitOp(d,c),d},_register:function(){return this.on("remoteop",function(a){var b,c,d,e=[];for(c=0,d=a.length;c<d;c++)b=a[c],b.i!==void 0?e.push(this.emit("insert",b.p,b.i)):e.push(this.emit("delete",b.p,b.d));return e})}},c=function(){function a(a,b,c){this.connection=a,this.name=b,this.flush=r(this.flush,this),c||(c={}),this.version=c.v,this.snapshot=c.snaphot,c.type&&this._setType(c.type),this.state="closed",this.autoOpen=!1,this._create=c.create,this.inflightOp=null,this.inflightCallbacks=[],this.inflightSubmittedIds=[],this.pendingOp=null,this.pendingCallbacks=[],this.serverOps={}}return a.prototype._xf=function(a,b){var c,d;return this.type.transformX?this.type.transformX(a,b):(c=this.type.transform(a,b,"left"),d=this.type.transform(b,a,"right"),[c,d])},a.prototype._otApply=function(a,b){var c=this.snapshot;this.snapshot=this.type.apply(this.snapshot,a),this.emit("change",a,c);if(b)return this.emit("remoteop",a,c)},a.prototype._connectionStateChanged=function(a,b){switch(a){case"disconnected":this.state="closed",this.inflightOp&&this.inflightSubmittedIds.push(this.connection.id),this.emit("closed");break;case"ok":this.autoOpen&&this.open();break;case"stopped":typeof this._openCallback=="function"&&this._openCallback(b)}return this.emit(a,b)},a.prototype._setType=function(a){var b,c,d;typeof a=="string"&&(a=p[a]);if(!a||!a.compose)throw new Error("Support for types without compose() is not implemented");this.type=a;if(a.api){d=a.api;for(b in d)c=d[b],this[b]=c;return typeof this._register=="function"?this._register():void 0}return this.provides={}},a.prototype._onMessage=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r;if(a.open===!0)return this.state="open",this._create=!1,this.created==null&&(this.created=!!a.create),a.type&&this._setType(a.type),a.create?(this.created=!0,this.snapshot=this.type.create()):(this.created!==!0&&(this.created=!1),a.snapshot!==void 0&&(this.snapshot=a.snapshot)),a.v!=null&&(this.version=a.v),this.inflightOp?(g={doc:this.name,op:this.inflightOp,v:this.version},this.inflightSubmittedIds.length&&(g.dupIfSource=this.inflightSubmittedIds),this.connection.send(g)):this.flush(),this.emit("open"),typeof this._openCallback=="function"?this._openCallback(null):void 0;if(a.open===!1)return a.error&&(typeof console!="undefined"&&console!==null&&console.error("Could not open document: "+a.error),this.emit("error",a.error),typeof this._openCallback=="function"&&this._openCallback(a.error)),this.state="closed",this.emit("closed"),typeof this._closeCallback=="function"&&this._closeCallback(),this._closeCallback=null;if(a.op!==null||d!=="Op already submitted"){if(a.op===void 0&&a.v!==void 0||a.op&&(m=a.meta.source,s.call(this.inflightSubmittedIds,m)>=0)){e=this.inflightOp,this.inflightOp=null,this.inflightSubmittedIds.length=0,d=a.error;if(d){this.type.invert?(h=this.type.invert(e),this.pendingOp&&(n=this._xf(this.pendingOp,h),this.pendingOp=n[0],h=n[1]),this._otApply(h,!0)):this.emit("error","Op apply failed ("+d+") and the op could not be reverted"),o=this.inflightCallbacks;for(i=0,k=o.length;i<k;i++)b=o[i],b(d)}else{if(a.v!==this.version)throw new Error("Invalid version from server");this.serverOps[this.version]=e,this.version++,p=this.inflightCallbacks;for(j=0,l=p.length;j<l;j++)b=p[j],b(null,e)}return this.flush()}if(a.op){if(a.v<this.version)return;return a.doc!==this.name?this.emit("error","Expected docName '"+this.name+"' but got "+a.doc):a.v!==this.version?this.emit("error","Expected version "+this.version+" but got "+a.v):(f=a.op,this.serverOps[this.version]=f,c=f,this.inflightOp!==null&&(q=this._xf(this.inflightOp,c),this.inflightOp=q[0],c=q[1]),this.pendingOp!==null&&(r=this._xf(this.pendingOp,c),this.pendingOp=r[0],c=r[1]),this.version++,this._otApply(c,!0))}return typeof console!="undefined"&&console!==null?console.warn("Unhandled document message:",a):void 0}},a.prototype.flush=function(){if(this.connection.state!=="ok"||this.inflightOp!==null||this.pendingOp===null)return;return this.inflightOp=this.pendingOp,this.inflightCallbacks=this.pendingCallbacks,this.pendingOp=null,this.pendingCallbacks=[],this.connection.send({doc:this.name,op:this.inflightOp,v:this.version})},a.prototype.submitOp=function(a,b){return this.type.normalize!=null&&(a=this.type.normalize(a)),this.snapshot=this.type.apply(this.snapshot,a),this.pendingOp!==null?this.pendingOp=this.type.compose(this.pendingOp,a):this.pendingOp=a,b&&this.pendingCallbacks.push(b),this.emit("change",a),setTimeout(this.flush,0)},a.prototype.open=function(a){var b,c=this;this.autoOpen=!0;if(this.state!=="closed")return;return b={doc:this.name,open:!0},this.snapshot===void 0&&(b.snapshot=null),this.type&&(b.type=this.type.name),this.version!=null&&(b.v=this.version),this._create&&(b.create=!0),this.connection.send(b),this.state="opening",this._openCallback=function(b){return c._openCallback=null,typeof a=="function"?a(b):void 0}},a.prototype.close=function(a){return this.autoOpen=!1,this.state==="closed"?typeof a=="function"?a():void 0:(this.connection.send({doc:this.name,open:!1}),this.state="closed",this.emit("closing"),this._closeCallback=a)},a}(),d.mixin(c),i.Doc=c,p||(p=i.types);if(!window.BCSocket)throw new Error("Must load browserchannel before this library");a=window.BCSocket,b=function(){function b(b){var c=this;this.docs={},this.state="connecting",this.socket=new a(b,{reconnect:!0}),this.socket.onmessage=function(a){var b;if(a.auth===null)return c.lastError=a.error,c.disconnect(),c.emit("connect failed",a.error);if(a.auth){c.id=a.auth,c.setState("ok");return}return b=a.doc,b!==void 0?c.lastReceivedDoc=b:a.doc=b=c.lastReceivedDoc,c.docs[b]?c.docs[b]._onMessage(a):typeof console!="undefined"&&console!==null?console.error("Unhandled message",a):void 0},this.connected=!1,this.socket.onclose=function(a){c.setState("disconnected",a);if(a==="Closed"||a==="Stopped by server")return c.setState("stopped",c.lastError||a)},this.socket.onerror=function(a){return c.emit("error",a)},this.socket.onopen=function(){return c.lastError=null,c.setState("handshaking")},this.socket.onconnecting=function(){return c.setState("connecting")}}return b.prototype.setState=function(a,b){var c,d,e,f;if(this.state===a)return;this.state=a,a==="disconnected"&&delete this.id,this.emit(a,b),e=this.docs,f=[];for(d in e)c=e[d],f.push(c._connectionStateChanged(a,b));return f},b.prototype.send=function(a){var b=a.doc;return b===this.lastSentDoc?delete a.doc:this.lastSentDoc=b,this.socket.send(a)},b.prototype.disconnect=function(){return this.socket.close()},b.prototype.makeDoc=function(a,b,d){var e,f=this;if(this.docs[a])throw new Error("Doc "+a+" already open");return e=new c(this,a,b),this.docs[a]=e,e.open(function(b){return b&&delete f.docs[a],d(b,b?void 0:e)})},b.prototype.openExisting=function(a,b){var c;return this.state==="stopped"?b("connection closed"):this.docs[a]?b(null,this.docs[a]):c=this.makeDoc(a,{},b)},b.prototype.open=function(a,b,c){var d;if(this.state==="stopped")return c("connection closed");typeof b=="function"&&(c=b,b="text"),c||(c=function(){}),typeof b=="string"&&(b=p[b]);if(!b)throw new Error("OT code for document type missing");if(a==null)throw new Error("Server-generated random doc names are not currently supported");if(this.docs[a]){d=this.docs[a],d.type===b?c(null,d):c("Type mismatch",d);return}return this.makeDoc(a,{create:!0,type:b.name},c)},b}(),d.mixin(b),i.Connection=b,i.open=function(){var a={},c=function(c){var d,e,f;return f=window.location,c==null&&(c=""+f.protocol+"//"+f.host+"/channel"),a[c]||(d=new b(c),d.numDocs=0,e=function(){return delete a[c]},d.on("disconnecting",e),d.on("connect failed",e),a[c]=d),a[c]};return function(a,b,d,e){var f;return typeof d=="function"&&(e=d,d=null),f=c(d),f.numDocs++,f.open(a,b,function(a,b){return a?(f.numDocs--,f.numDocs===0&&f.disconnect(),e(a)):(b.on("closed",function(){f.numDocs--;if(f.numDocs===0)return f.disconnect()}),e(null,b))}),f.on("connect failed")}}()})).call(this)
View
740 webclient/share.uncompressed.js
@@ -1,17 +1,23 @@
(function() {
- var Connection, Doc, MicroEvent, append, bootstrapTransform, checkValidComponent, checkValidOp, exports, invertComponent, io, nextTick, strInject, text, transformComponent, transformPosition, types;
- var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __slice = Array.prototype.slice;
+ var BCSocket, Connection, Doc, MicroEvent, append, bootstrapTransform, checkValidComponent, checkValidOp, exports, invertComponent, nextTick, strInject, text, transformComponent, transformPosition, types,
+ __slice = Array.prototype.slice,
+ __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
+ __indexOf = Array.prototype.indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
window.sharejs = exports = {
'version': '0.5.0-pre'
};
- if (typeof WEB === 'undefined') {
- window.WEB = true;
- }
+
+ if (typeof WEB === 'undefined') window.WEB = true;
+
nextTick = typeof WEB !== "undefined" && WEB !== null ? function(fn) {
return setTimeout(fn, 0);
} : process['nextTick'];
+
MicroEvent = (function() {
+
function MicroEvent() {}
+
MicroEvent.prototype.on = function(event, fct) {
var _base;
this._events || (this._events = {});
@@ -19,51 +25,49 @@
this._events[event].push(fct);
return this;
};
+
MicroEvent.prototype.removeListener = function(event, fct) {
- var i, listeners, _base;
+ var i, listeners, _base,
+ _this = this;
this._events || (this._events = {});
listeners = ((_base = this._events)[event] || (_base[event] = []));
i = 0;
while (i < listeners.length) {
- if (listeners[i] === fct) {
- listeners[i] = void 0;
- }
+ if (listeners[i] === fct) listeners[i] = void 0;
i++;
}
- nextTick(__bind(function() {
+ nextTick(function() {
var x;
- return this._events[event] = (function() {
+ return _this._events[event] = (function() {
var _i, _len, _ref, _results;
_ref = this._events[event];
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
x = _ref[_i];
- if (x) {
- _results.push(x);
- }
+ if (x) _results.push(x);
}
return _results;
- }).call(this);
- }, this));
+ }).call(_this);
+ });
return this;
};
+
MicroEvent.prototype.emit = function() {
var args, event, fn, _i, _len, _ref, _ref2;
event = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
- if (!((_ref = this._events) != null ? _ref[event] : void 0)) {
- return this;
- }
+ if (!((_ref = this._events) != null ? _ref[event] : void 0)) return this;
_ref2 = this._events[event];
for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
fn = _ref2[_i];
- if (fn) {
- fn.apply(this, args);
- }
+ if (fn) fn.apply(this, args);
}
return this;
};
+
return MicroEvent;
+
})();
+
MicroEvent.mixin = function(obj) {
var proto;