Browse files

Added closure compiler, and made the client build using it.

  • Loading branch information...
1 parent 1c40e8b commit 067329c9bf24813ba386d06e841e8fa294cfbb62 @josephg committed Apr 20, 2011
View
24 Cakefile
@@ -1,4 +1,6 @@
{exec} = require 'child_process'
+closure = require './thirdparty/closure'
+fs = require 'fs'
task 'test', 'Run all tests', ->
require './tests'
@@ -12,6 +14,7 @@ lib = [
]
client = [
+ 'client/web-prelude'
'client/microevent'
'types/text'
'client/opstream'
@@ -26,9 +29,26 @@ e = (str, callback) ->
console.log out if out != ''
callback() if callback?
-task 'webclient', 'Assemble the web client into one file', ->
+compile = (infile, outfile) ->
+ # Closure compile the JS
+ file = fs.readFileSync infile
+
+ closure.compile file, (err, code) ->
+ throw err if err?
+
+ smaller = Math.round((1 - (code.length / file.length)) * 100)
+
+ output = outfile
+ fs.writeFileSync output, code
+
+ console.log "Closure compiled: #{smaller}% smaller (#{code.length} bytes} written to #{output}"
+
+
+task 'webclient', 'Build the web client into one file', ->
clientfiles = ("src/#{c}.coffee" for c in client).join ' '
# I would really rather do this in pure JS.
e "coffee -cj #{clientfiles}", ->
- e "cat #{lib.join ' '} concatenation.js >share.js", ->
+ e "cat #{lib.join ' '} concatenation.js >share.uncompressed.js", ->
e 'rm concatenation.js'
+ compile 'share.uncompressed.js', 'share.js'
+
View
21 LICENSE
@@ -0,0 +1,21 @@
+Licensed under the standard MIT license:
+
+Copyright 2011 Joseph Gentle.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
View
2 examples/_wiki/wiki.html.mu
@@ -17,7 +17,7 @@
<script src="/lib/ace/ace.js" type="text/javascript" charset="utf-8"></script>
<script src="/socket.io/socket.io.js"></script>
<script src="/share.js"></script>
- <script src="../lib/ace.js"></script>
+ <script src="/lib/share.ace.js"></script>
<script>
window.onload = function() {
View
2 examples/ace/index.html
@@ -12,7 +12,7 @@
</div>
<div id="editor">Connecting...</div>
- <script src="/lib/ace/ace.js" type="text/javascript" charset="utf-8"></script>
+ <script src="/lib/ace/ace-uncompressed.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="/share.js"></script>
View
736 share.js
@@ -1,707 +1,29 @@
-(function() {
- var Connection, Document, MicroEvent, OpStream, append, checkValidComponent, checkValidOp, compose, compress, connections, getConnection, i, inject, invertComponent, io, open, p, transformComponent, transformComponentX, transformPosition, transformX, types, _base;
- var __slice = Array.prototype.slice, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
- MicroEvent = (function() {
- function MicroEvent() {}
- MicroEvent.prototype.on = function(event, fct) {
- var _base;
- this._events || (this._events = {});
- (_base = this._events)[event] || (_base[event] = []);
- this._events[event].push(fct);
- return this;
- };
- MicroEvent.prototype.removeListener = function(event, fct) {
- var idx, _ref;
- this._events || (this._events = {});
- idx = (_ref = this._events[event]) != null ? _ref.indexOf(fct) : void 0;
- if ((idx != null) && idx >= 0) {
- this._events[event].splice(idx, 1);
- }
- 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;
- }
- _ref2 = this._events[event];
- for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
- fn = _ref2[_i];
- fn.apply(this, args);
- }
- return this;
- };
- return MicroEvent;
- })();
- MicroEvent.mixin = function(obj) {
- var fname, proto, _i, _len, _ref;
- proto = obj.prototype || obj;
- _ref = ['on', 'removeListener', 'emit'];
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
- fname = _ref[_i];
- proto[fname] = MicroEvent.prototype[fname];
- }
- return obj;
- };
- if (typeof module != "undefined" && module !== null ? module.exports : void 0) {
- module.exports = MicroEvent;
- }
- typeof exports != "undefined" && exports !== null ? exports : exports = {};
- exports.name = 'text';
- exports.initialVersion = function() {
- return '';
- };
- inject = function(s1, pos, s2) {
- return s1.slice(0, pos) + s2 + s1.slice(pos);
- };
- checkValidComponent = function(c) {
- var d_type, i_type;
- if (typeof c.p !== 'number') {
- throw new Error('component missing position field');
- }
- i_type = typeof c.i;
- d_type = typeof c.d;
- if (!((i_type === 'string') ^ (d_type === 'string'))) {
- throw new Error('component needs an i or d field');
- }
- if (!(c.p >= 0)) {
- throw new Error('position cannot be negative');
- }
- };
- checkValidOp = function(op) {
- var c, _i, _len;
- for (_i = 0, _len = op.length; _i < _len; _i++) {
- c = op[_i];
- checkValidComponent(c);
- }
- return true;
- };
- exports.apply = function(snapshot, op) {
- var component, deleted, _i, _len;
- checkValidOp(op);
- for (_i = 0, _len = op.length; _i < _len; _i++) {
- component = op[_i];
- if (component.i != null) {
- snapshot = inject(snapshot, component.p, component.i);
- } else {
- deleted = snapshot.slice(component.p, component.p + component.d.length);
- if (component.d !== deleted) {
- throw new Error("Delete component '" + component.d + "' does not match deleted text '" + deleted + "'");
- }
- snapshot = snapshot.slice(0, component.p) + snapshot.slice(component.p + component.d.length);
- }
- }
- return snapshot;
- };
- exports._append = append = function(newOp, c) {
- var last, _ref, _ref2;
- if (c.i === '' || c.d === '') {
- return;
- }
- if (newOp.length === 0) {
- return newOp.push(c);
- } else {
- last = newOp[newOp.length - 1];
- if ((last.i != null) && (c.i != null) && (last.p <= (_ref = c.p) && _ref <= (last.p + last.i.length))) {
- return newOp[newOp.length - 1] = {
- i: inject(last.i, c.p - last.p, c.i),
- p: last.p
- };
- } else if ((last.d != null) && (c.d != null) && (c.p <= (_ref2 = last.p) && _ref2 <= (c.p + c.d.length))) {
- return newOp[newOp.length - 1] = {
- d: inject(c.d, last.p - c.p, last.d),
- p: c.p
- };
- } else {
- return newOp.push(c);
- }
- }
- };
- exports.compose = compose = function(op1, op2) {
- var c, newOp, _i, _len;
- checkValidOp(op1);
- checkValidOp(op2);
- newOp = op1.slice();
- for (_i = 0, _len = op2.length; _i < _len; _i++) {
- c = op2[_i];
- append(newOp, c);
- }
- checkValidOp(newOp);
- return newOp;
- };
- exports.compress = compress = function(op) {
- return compose([], op);
- };
- exports.normalize = function(op) {
- var c, newOp, _i, _len, _ref;
- newOp = [];
- if ((op.i != null) || (op.p != null)) {
- op = [op];
- }
- for (_i = 0, _len = op.length; _i < _len; _i++) {
- c = op[_i];
- (_ref = c.p) != null ? _ref : c.p = 0;
- append(newOp, c);
- }
- return newOp;
- };
- transformPosition = function(pos, c, insertAfter) {
- if (c.i != null) {
- if (c.p < pos || (c.p === pos && insertAfter)) {
- return pos + c.i.length;
- } else {
- return pos;
- }
- } else {
- if (pos <= c.p) {
- return pos;
- } else if (pos <= c.p + c.d.length) {
- return c.p;
- } else {
- return pos - c.d.length;
- }
- }
- };
- exports.transformCursor = function(position, op, insertAfter) {
- var c, _i, _len;
- for (_i = 0, _len = op.length; _i < _len; _i++) {
- c = op[_i];
- position = transformPosition(position, c, insertAfter);
- }
- return position;
- };
- transformComponent = function(dest, c, otherC, type) {
- var cIntersect, intersectEnd, intersectStart, newC, otherIntersect, s;
- checkValidOp([c]);
- checkValidOp([otherC]);
- if (c.i != null) {
- return append(dest, {
- i: c.i,
- p: transformPosition(c.p, otherC, type === 'server')
- });
- } else {
- if (otherC.i != null) {
- s = c.d;
- if (c.p < otherC.p) {
- append(dest, {
- d: s.slice(0, otherC.p - c.p),
- p: c.p
- });
- s = s.slice(otherC.p - c.p);
- }
- if (s !== '') {
- return append(dest, {
- d: s,
- p: c.p + otherC.i.length
- });
- }
- } else {
- if (c.p >= otherC.p + otherC.d.length) {
- return append(dest, {
- d: c.d,
- p: c.p - otherC.d.length
- });
- } else if (c.p + c.d.length <= otherC.p) {
- return append(dest, c);
- } else {
- newC = {
- d: '',
- p: c.p
- };
- if (c.p < otherC.p) {
- newC.d = c.d.slice(0, otherC.p - c.p);
- }
- if (c.p + c.d.length > otherC.p + otherC.d.length) {
- newC.d += c.d.slice(otherC.p + otherC.d.length - c.p);
- }
- intersectStart = Math.max(c.p, otherC.p);
- intersectEnd = Math.min(c.p + c.d.length, otherC.p + otherC.d.length);
- cIntersect = c.d.slice(intersectStart - c.p, intersectEnd - c.p);
- otherIntersect = otherC.d.slice(intersectStart - otherC.p, intersectEnd - otherC.p);
- if (cIntersect !== otherIntersect) {
- throw new Error('Delete ops delete different text in the same region of the document');
- }
- if (newC.d !== '') {
- newC.p = transformPosition(newC.p, otherC);
- return append(dest, newC);
- }
- }
- }
- }
- };
- transformComponentX = function(server, client, destServer, destClient) {
- transformComponent(destServer, server, client, 'server');
- return transformComponent(destClient, client, server, 'client');
- };
- exports.transformX = transformX = function(serverOp, clientOp) {
- var c, c_, clientComponent, k, newClientOp, newServerOp, nextC, s, s_, _i, _j, _k, _l, _len, _len2, _len3, _len4, _ref, _ref2;
- checkValidOp(serverOp);
- checkValidOp(clientOp);
- newClientOp = [];
- for (_i = 0, _len = clientOp.length; _i < _len; _i++) {
- clientComponent = clientOp[_i];
- newServerOp = [];
- k = 0;
- while (k < serverOp.length) {
- nextC = [];
- transformComponentX(serverOp[k], clientComponent, newServerOp, nextC);
- k++;
- if (nextC.length === 1) {
- clientComponent = nextC[0];
- } else if (nextC.length === 0) {
- _ref = serverOp.slice(k);
- for (_j = 0, _len2 = _ref.length; _j < _len2; _j++) {
- s = _ref[_j];
- append(newServerOp, s);
- }
- clientComponent = null;
- break;
- } else {
- _ref2 = transformX(serverOp.slice(k), nextC), s_ = _ref2[0], c_ = _ref2[1];
- for (_k = 0, _len3 = s_.length; _k < _len3; _k++) {
- s = s_[_k];
- append(newServerOp, s);
- }
- for (_l = 0, _len4 = c_.length; _l < _len4; _l++) {
- c = c_[_l];
- append(newClientOp, c);
- }
- clientComponent = null;
- break;
- }
- }
- if (clientComponent != null) {
- append(newClientOp, clientComponent);
- }
- serverOp = newServerOp;
- }
- return [serverOp, newClientOp];
- };
- exports.transform = function(op, otherOp, type) {
- var client, server, _, _ref, _ref2;
- if (!(type === 'server' || type === 'client')) {
- throw new Error("type must be 'server' or 'client'");
- }
- if (type === 'server') {
- _ref = transformX(op, otherOp), server = _ref[0], _ = _ref[1];
- return server;
- } else {
- _ref2 = transformX(otherOp, op), _ = _ref2[0], client = _ref2[1];
- return client;
- }
- };
- invertComponent = function(c) {
- if (c.i != null) {
- return {
- d: c.i,
- p: c.p
- };
- } else {
- return {
- i: c.d,
- p: c.p
- };
- }
- };
- exports.invert = function(op) {
- var c, _i, _len, _ref, _results;
- _ref = op.slice().reverse();
- _results = [];
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
- c = _ref[_i];
- _results.push(invertComponent(c));
- }
- return _results;
- };
- if (typeof window != "undefined" && window !== null) {
- window.sharejs || (window.sharejs = {});
- (_base = window.sharejs).types || (_base.types = {});
- window.sharejs.types.text = exports;
- }
- if (typeof window != "undefined" && window !== null) {
- if (window.io == null) {
- throw new Error('Must load socket.io before this library');
- }
- io = window.io;
- } else {
- io = require('../../thirdparty/Socket.io-node-client').io;
- }
- p = function() {};
- OpStream = (function() {
- function OpStream(hostname, port, path) {
- var resource;
- this.hostname = hostname;
- this.port = port;
- this.onMessage = __bind(this.onMessage, this);;
- resource = path ? path + '/socket.io' : 'socket.io';
- this.socket = new io.Socket(this.hostname, {
- port: this.port,
- resource: resource
- });
- this.socket.on('connect', this.onConnect);
- this.socket.on('message', this.onMessage);
- this.socket.connect();
- this.callbacks = {};
- this.lastReceivedDoc = null;
- this.lastSentDoc = null;
- }
- OpStream.prototype.onConnect = function() {
- return p('connected');
- };
- OpStream.prototype.on = function(docName, type, callback) {
- var _base;
- (_base = this.callbacks)[docName] || (_base[docName] = {});
- if (this.callbacks[docName][type] != null) {
- throw new Error("Callback already exists for " + docName + ", " + type);
- }
- return this.callbacks[docName][type] = callback;
- };
- OpStream.prototype.removeListener = function(docName, type, listener) {
- var _ref;
- return (_ref = this.callbacks[docName]) != null ? delete _ref[type] : void 0;
- };
- OpStream.prototype.onMessage = function(data) {
- var emit;
- p('message');
- p(data);
- if (data.doc != null) {
- this.lastReceivedDoc = data.doc;
- } else {
- data.doc = this.lastReceivedDoc;
- }
- emit = __bind(function(type, clear) {
- var callback, _ref;
- p("emit " + data.doc + " " + type);
- callback = (_ref = this.callbacks[data.doc]) != null ? _ref[type] : void 0;
- if (callback != null) {
- if (clear) {
- this.callbacks[data.doc][type] = null;
- }
- return callback(data);
- }
- }, this);
- if (data.snapshot !== void 0) {
- return emit('snapshot', true);
- } else if (data.follow != null) {
- if (data.follow) {
- return emit('follow', true);
- } else {
- return emit('unfollow', true);
- }
- } else if (data.v !== void 0) {
- if (data.op != null) {
- return emit('op', false);
- } else {
- return emit('localop', true);
- }
- }
- };
- OpStream.prototype.send = function(msg) {
- if (msg.doc === this.lastSentDoc) {
- delete msg.doc;
- } else {
- this.lastSentDoc = msg.doc;
- }
- return this.socket.send(msg);
- };
- OpStream.prototype.follow = function(docName, v, callback) {
- var request;
- p("follow " + docName);
- request = {
- doc: docName,
- follow: true
- };
- if (v != null) {
- request.v = v;
- }
- this.send(request);
- return this.on(docName, 'follow', callback);
- };
- OpStream.prototype.get = function(docName, callback) {
- p("get " + docName);
- this.send({
- doc: docName,
- snapshot: null
- });
- return this.on(docName, 'snapshot', callback);
- };
- OpStream.prototype.submit = function(docName, op, version, callback) {
- p("submit");
- this.send({
- doc: docName,
- v: version,
- op: op
- });
- return this.on(docName, 'localop', callback);
- };
- OpStream.prototype.unfollow = function(docName, callback) {
- p("unfollow " + docName);
- this.send({
- doc: docName,
- follow: false
- });
- return this.on(docName, 'unfollow', callback);
- };
- OpStream.prototype.disconnect = function() {
- this.socket.disconnect();
- return this.socket = null;
- };
- return OpStream;
- })();
- if (typeof window != "undefined" && window !== null) {
- window.sharejs || (window.sharejs = {});
- window.sharejs.OpStream = OpStream;
- } else {
- exports.OpStream = OpStream;
- }
- if (typeof window != "undefined" && window !== null) {
- types || (types = window.sharejs.types);
- } else {
- OpStream = require('./opstream').OpStream;
- types = require('../types');
- MicroEvent = require('./microevent');
- }
- exports || (exports = {});
- p = function() {};
- i = function() {};
- Document = (function() {
- function Document(stream, name, version, type, snapshot) {
- this.stream = stream;
- this.name = name;
- this.version = version;
- this.type = type;
- this.snapshot = snapshot;
- this.onOpReceived = __bind(this.onOpReceived, this);;
- this.tryFlushPendingOp = __bind(this.tryFlushPendingOp, this);;
- if (this.type.compose == null) {
- throw new Error('Handling types without compose() defined is not currently implemented');
- }
- this.inflightOp = null;
- this.inflightCallbacks = [];
- this.pendingOp = null;
- this.pendingCallbacks = [];
- this.serverOps = {};
- this.listeners = [];
- this.created = false;
- this.follow();
- }
- Document.prototype.follow = function(callback) {
- this.stream.on(this.name, 'op', this.onOpReceived);
- return this.stream.follow(this.name, this.version, __bind(function(msg) {
- if (msg.v !== this.version) {
- throw new Error("Expected version " + this.version + " but got " + msg.v);
- }
- if (callback != null) {
- return callback();
- }
- }, this));
- };
- Document.prototype.unfollow = function(callback) {
- this.stream.removeListener(this.name, 'op', this.onOpReceived);
- return this.stream.unfollow(this.name, callback);
- };
- Document.prototype.tryFlushPendingOp = function() {
- if (this.inflightOp === null && this.pendingOp !== null) {
- this.inflightOp = this.pendingOp;
- this.inflightCallbacks = this.pendingCallbacks;
- this.pendingOp = null;
- this.pendingCallbacks = [];
- return this.stream.submit(this.name, this.inflightOp, this.version, __bind(function(response) {
- var callback, _i, _j, _len, _len2, _ref, _ref2;
- if (response.v === null) {
- _ref = this.inflightCallbacks;
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
- callback = _ref[_i];
- callback(null);
- }
- this.inflightOp = null;
- throw new Error(response.error);
- }
- if (response.v !== this.version) {
- throw new Error('Invalid version from server');
- }
- this.serverOps[this.version] = this.inflightOp;
- this.version++;
- _ref2 = this.inflightCallbacks;
- for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
- callback = _ref2[_j];
- callback(this.inflightOp, null);
- }
- this.inflightOp = null;
- return this.tryFlushPendingOp();
- }, this));
- }
- };
- Document.prototype.onOpReceived = function(msg) {
- var docOp, op, xf, _ref, _ref2;
- if (msg.v < this.version) {
- return;
- }
- if (msg.doc !== this.name) {
- throw new Error("Expected docName " + this.name + " but got " + msg.doc);
- }
- if (msg.v !== this.version) {
- throw new Error("Expected version " + this.version + " but got " + msg.v);
- }
- op = msg.op;
- this.serverOps[this.version] = op;
- xf = this.type.transformX || __bind(function(server, client) {
- var client_, server_;
- server_ = this.type.transform(server, client, 'server');
- client_ = this.type.transform(client, server, 'client');
- return [server_, client_];
- }, this);
- docOp = op;
- if (this.inflightOp !== null) {
- _ref = xf(docOp, this.inflightOp), docOp = _ref[0], this.inflightOp = _ref[1];
- }
- if (this.pendingOp !== null) {
- _ref2 = xf(docOp, this.pendingOp), docOp = _ref2[0], this.pendingOp = _ref2[1];
- }
- this.snapshot = this.type.apply(this.snapshot, docOp);
- this.version++;
- this.emit('remoteop', docOp);
- return this.emit('change', docOp);
- };
- Document.prototype.submitOp = function(op, v, callback) {
- var realOp, _ref;
- if (v == null) {
- v = this.version;
- }
- if (typeof v === 'function') {
- callback = v;
- v = this.version;
- }
- if (((_ref = this.type) != null ? _ref.normalize : void 0) != null) {
- op = this.type.normalize(op);
- }
- while (v < this.version) {
- realOp = this.recentOps[v];
- if (!realOp) {
- throw new Error('Op version too old');
- }
- op = this.type.transform(op, realOp, 'client');
- v++;
- }
- this.snapshot = this.type.apply(this.snapshot, op);
- if (this.pendingOp !== null) {
- this.pendingOp = this.type.compose(this.pendingOp, op);
- } else {
- this.pendingOp = op;
- }
- if (callback != null) {
- this.pendingCallbacks.push(callback);
- }
- this.emit('change', op);
- return setTimeout(this.tryFlushPendingOp, 0);
- };
- return Document;
- })();
- MicroEvent.mixin(Document);
- Connection = (function() {
- Connection.prototype.makeDoc = function(name, version, type, snapshot) {
- if (this.docs[name]) {
- throw new Error("Document " + name + " already followed");
- }
- return this.docs[name] = new Document(this.stream, name, version, type, snapshot);
- };
- function Connection(hostname, port, basePath) {
- this.stream = new OpStream(hostname, port, basePath);
- this.docs = {};
- }
- Connection.prototype.openExisting = function(docName, callback) {
- if (this.docs[docName] != null) {
- return this.docs[docName];
- }
- return this.stream.get(docName, __bind(function(response) {
- var type;
- if (response.snapshot === null) {
- return callback(null);
- } else {
- type = types[response.type];
- return callback(this.makeDoc(response.doc, response.v, type, response.snapshot));
- }
- }, this));
- };
- Connection.prototype.open = function(docName, type, callback) {
- var doc;
- if (typeof type === 'function') {
- callback = type;
- type = 'text';
- }
- callback || (callback = function() {});
- if (typeof type === 'string') {
- type = types[type];
- }
- if (this.docs[docName] != null) {
- doc = this.docs[docName];
- if (doc.type === type) {
- callback(doc);
- } else {
- callback(doc, 'Document already exists with type ' + doc.type.name);
- }
- return;
- }
- return this.stream.get(docName, __bind(function(response) {
- if (response.snapshot === null) {
- return this.stream.submit(docName, {
- type: type.name
- }, 0, __bind(function(response) {
- if (response.v != null) {
- doc = this.makeDoc(docName, 1, type, type.initialVersion());
- doc.created = true;
- return callback(doc);
- } else if (response.v === null && response.error === 'Type already set') {
- return this.open(docName, type, callback);
- } else {
- return callback(null, response.error);
- }
- }, this));
- } else if (response.type === type.name) {
- return callback(this.makeDoc(docName, response.v, type, response.snapshot));
- } else {
- return callback(null, "Document already exists with type " + response.type);
- }
- }, this));
- };
- Connection.prototype.create = function(type, prefix) {
- throw new Error('Not implemented');
- };
- Connection.prototype.disconnect = function() {
- if (this.stream != null) {
- this.stream.disconnect();
- return this.stream = null;
- }
- };
- return Connection;
- })();
- connections = {};
- getConnection = function(hostname, port, basePath) {
- var address;
- if (typeof window != "undefined" && window !== null) {
- hostname != null ? hostname : hostname = window.location.hostname;
- port != null ? port : port = window.location.port;
- }
- address = "" + hostname + ":" + port;
- return connections[address] || (connections[address] = new Connection(hostname, port, basePath));
- };
- open = function(docName, type, options, callback) {
- var c;
- if (typeof options === 'function') {
- callback = options;
- options = null;
- }
- options != null ? options : options = {};
- c = getConnection(options.hostname, options.port, options.basePath);
- return c.open(docName, type, callback);
- };
- if (typeof window != "undefined" && window !== null) {
- window.sharejs.Connection = Connection;
- window.sharejs.Document = Document;
- window.sharejs.open = open;
- } else {
- exports.Connection = Connection;
- exports.open = open;
- }
-}).call(this);
+/*
+ ShareJS v0.2
+ http://sharejs.org
+
+ Copyright 2011 Joseph Gentle
+
+ BSD licensed:
+ https://github.com/josephg/ShareJS/raw/master/LICENSE
+*/
+function f(i){throw i;}var k=void 0,r=null;
+(function(){function i(b,a){return function(){return b.apply(a,arguments)}}var n,p,q,x,l,A,o,B,s,m,C,t,D,E,y,h,z,F,u,v,w,G=Array.prototype.slice;m={};q=function(){function b(){}b.prototype.b=function(a,c){var b;this.e||(this.e={});(b=this.e)[a]||(b[a]=[]);this.e[a].push(c);return this};b.prototype.o=function(a,c){var b,e;this.e||(this.e={});b=(e=this.e[a])!=r?e.indexOf(c):k;b!=r&&b>=0&&this.e[a].splice(b,1);return this};b.prototype.j=function(){var a,c,b,e,g;c=arguments[0];a=2<=arguments.length?G.call(arguments,
+1):[];if(!((b=this.e)!=r&&b[c]))return this;g=this.e[c];b=0;for(e=g.length;b<e;b++)c=g[b],c.apply(this,a);return this};return b}();q.H=function(b){b=b.prototype||b;b.b=b.on=q.prototype.b;b.o=b.removeListener=q.prototype.o;b.j=q.prototype.j};if(typeof module!="undefined"&&module!==r&&module.K)module.K=q;h={name:"text",T:function(){return""}};t=function(b,a,c){return b.slice(0,a)+c+b.slice(a)};A=function(b){typeof b.p!=="number"&&f(Error("component missing position field"));typeof b.i==="string"^typeof b.d===
+"string"||f(Error("component needs an i or d field"));b.p>=0||f(Error("position cannot be negative"))};o=function(b){var a,c,d;c=0;for(d=b.length;c<d;c++)a=b[c],A(a)};h.apply=function(b,a){var c,d,e,g;o(a);e=0;for(g=a.length;e<g;e++)c=a[e],c.i!=r?b=t(b,c.p,c.i):(d=b.slice(c.p,c.p+c.d.length),c.d!==d&&f(Error("Delete component '"+c.d+"' does not match deleted text '"+d+"'")),b=b.slice(0,c.p)+b.slice(c.p+c.d.length));return b};h.Q=l=function(b,a){var c,d,e;if(!(a.i===""||a.d===""))return b.length===
+0?b.push(a):(c=b[b.length-1],c.i!=r&&a.i!=r&&c.p<=(d=a.p)&&d<=c.p+c.i.length?b[b.length-1]={D:t(c.i,a.p-c.p,a.i),g:c.p}:c.d!=r&&a.d!=r&&a.p<=(e=c.p)&&e<=a.p+a.d.length?b[b.length-1]={m:t(a.d,c.p-a.p,c.d),g:a.p}:b.push(a))};h.C=B=function(b,a){var c,d,e,g;o(b);o(a);d=b.slice();e=0;for(g=a.length;e<g;e++)c=a[e],l(d,c);o(d);return d};h.S=function(b){return B([],b)};h.normalize=function(b){var a,c,d,e;c=[];if(b.i!=r||b.p!=r)b=[b];d=0;for(e=b.length;d<e;d++)a=b[d],a.p!=r||(a.p=0),l(c,a);return c};u=function(b,
+a,c){return a.i!=r?a.p<b||a.p===b&&c?b+a.i.length:b:b<=a.p?b:b<=a.p+a.d.length?a.p:b-a.d.length};h.Y=function(b,a,c){var d,e,g;e=0;for(g=a.length;e<g;e++)d=a[e],b=u(b,d,c);return b};z=function(b,a,c,d){var e,g;o([a]);o([c]);if(a.i!=r)l(b,{D:a.i,g:u(a.p,c,d==="server")});else if(c.i!=r)d=a.d,a.p<c.p&&(l(b,{m:d.slice(0,c.p-a.p),g:a.p}),d=d.slice(c.p-a.p)),d!==""&&l(b,{m:d,g:a.p+c.i.length});else if(a.p>=c.p+c.d.length)l(b,{m:a.d,g:a.p-c.d.length});else if(a.p+a.d.length<=c.p)l(b,a);else if(d={m:"",
+g:a.p},a.p<c.p&&(d.d=a.d.slice(0,c.p-a.p)),a.p+a.d.length>c.p+c.d.length&&(d.d+=a.d.slice(c.p+c.d.length-a.p)),g=Math.max(a.p,c.p),e=Math.min(a.p+a.d.length,c.p+c.d.length),a=a.d.slice(g-a.p,e-a.p),e=c.d.slice(g-c.p,e-c.p),a!==e&&f(Error("Delete ops delete different text in the same region of the document")),d.d!=="")d.p=u(d.p,c),l(b,d)};F=function(b,a,c,d){z(c,b,a,"server");z(d,a,b,"client")};h.O=v=function(b,a){var c,d,e,g,j,i,h,m,n;o(b);o(a);e=[];i=0;for(m=a.length;i<m;i++){d=a[i];g=[];for(c=0;c<
+b.length;)if(j=[],F(b[c],d,g,j),c++,j.length===1)d=j[0];else{if(j.length===0){h=b.slice(c);d=0;for(j=h.length;d<j;d++)c=h[d],l(g,c)}else{d=v(b.slice(c),j);j=d[0];d=d[1];h=0;for(n=j.length;h<n;h++)c=j[h],l(g,c);j=0;for(h=d.length;j<h;j++)c=d[j],l(e,c)}d=r;break}d!=r&&l(e,d);b=g}return[b,e]};h.transform=function(b,a,c){c==="server"||c==="client"||f(Error("type must be 'server' or 'client'"));c==="server"?(b=v(b,a),b=b[0]):(b=v(a,b),b=b[1]);return b};D=function(b){return b.i!=r?{m:b.i,g:b.p}:{D:b.d,
+g:b.p}};h.U=function(b){var a,c,d,e;d=b.slice().reverse();e=[];a=0;for(c=d.length;a<c;a++)b=d[a],e.push(D(b));return e};m.types||(m.types={});m.types.text=h;window.io||f(Error("Must load socket.io before this library"));E=window.io;y=function(){};x=function(){function b(a,c,b){this.z=i(this.z,this);this.l=new E.Socket(a,{port:c,X:b?b+"/socket.io":"socket.io"});this.l.on("connect",this.L);this.l.on("message",this.z);this.l.connect();this.k={};this.G=this.F=r}b.prototype.L=function(){return y("connected")};
+b.prototype.b=function(a,c,b){var e;(e=this.k)[a]||(e[a]={});this.k[a][c]!=r&&f(Error("Callback already exists for "+a+", "+c));return this.k[a][c]=b};b.prototype.o=function(a,c){var b;return(b=this.k[a])!=r?delete b[c]:k};b.prototype.z=function(a){var c;a.doc!=r?this.F=a.doc:a.doc=this.F;c=i(function(c,b){var g,j;g=(j=this.k[a.doc])!=r?j[c]:k;if(g!=r)return b&&(this.k[a.doc][c]=r),g(a)},this);if(a.snapshot!==k)return c("snapshot",!0);else if(a.follow!=r)return a.follow?c("follow",!0):c("unfollow",
+!0);else if(a.v!==k)return a.op!=r?c("op",!1):c("localop",!0)};b.prototype.send=function(a){a.doc===this.G?delete a.doc:this.G=a.doc;return this.l.send(a)};b.prototype.s=function(a,c,b){var e;e={doc:a,follow:!0};c!=r&&(e.v=c);this.send(e);return this.b(a,"follow",b)};b.prototype.get=function(a,c){this.send({doc:a,snapshot:r});return this.b(a,"snapshot",c)};b.prototype.submit=function(a,c,b,e){this.send({doc:a,v:b,op:c});return this.b(a,"localop",e)};b.prototype.B=function(a,c){this.send({doc:a,follow:!1});
+return this.b(a,"unfollow",c)};b.prototype.r=function(){this.l.disconnect();return this.l=r};return b}();m.P=x;w||(w=m.types);y=function(){};p=function(){function b(a,c,b,e,g){this.a=a;this.name=c;this.version=b;this.type=e;this.n=i(this.n,this);this.q=i(this.q,this);this.type.C==r&&f(Error("Handling types without compose() defined is not currently implemented"));this.snapshot=g;this.c=r;this.t=[];this.f=r;this.A=[];this.I={};this.V=[];this.J=!1;this.s()}b.prototype.s=function(a){this.a.b(this.name,
+"op",this.n);return this.a.s(this.name,this.version,i(function(c){c.v!==this.version&&f(Error("Expected version "+this.version+" but got "+c.v));if(a!=r)return a()},this))};b.prototype.B=function(a){this.a.o(this.name,"op",this.n);return this.a.B(this.name,a)};b.prototype.q=function(){if(this.c===r&&this.f!==r)return this.c=this.f,this.t=this.A,this.f=r,this.A=[],this.a.submit(this.name,this.c,this.version,i(function(a){var c,b,e,g;if(a.v===r){g=this.t;b=0;for(e=g.length;b<e;b++)c=g[b],c(r);this.c=
+r;f(Error(a.error))}a.v!==this.version&&f(Error("Invalid version from server"));this.I[this.version]=this.c;this.version++;e=this.t;a=0;for(b=e.length;a<b;a++)c=e[a],c(this.c,r);this.c=r;return this.q()},this))};b.prototype.n=function(a){var c,b;if(!(a.v<this.version)){a.doc!==this.name&&f(Error("Expected docName "+this.name+" but got "+a.doc));a.v!==this.version&&f(Error("Expected version "+this.version+" but got "+a.v));a=a.op;this.I[this.version]=a;c=this.type.O||i(function(a,c){var b,d;d=this.type.transform(a,
+c,"server");b=this.type.transform(c,a,"client");return[d,b]},this);if(this.c!==r)b=c(a,this.c),a=b[0],this.c=b[1];if(this.f!==r)c=c(a,this.f),a=c[0],this.f=c[1];this.snapshot=this.type.apply(this.snapshot,a);this.version++;this.j("remoteop",a);return this.j("change",a)}};b.prototype.N=function(a,c,b){var e;if(c==r)c=this.version;if(typeof c==="function")b=c,c=this.version;if(((e=this.type)!=r?e.normalize:k)!=r)a=this.type.normalize(a);for(;c<this.version;)(e=this.W[c])||f(Error("Op version too old")),
+a=this.type.transform(a,e,"client"),c++;this.snapshot=this.type.apply(this.snapshot,a);this.f=this.f!==r?this.type.C(this.f,a):a;b&&this.A.push(b);this.j("change",a);return setTimeout(this.q,0)};b.prototype.close=function(a){return this.B(i(function(){a&&a();this.j("closed")},this))};return b}();q.H(p);p.prototype.submitOp=p.prototype.N;p.prototype.close=p.prototype.close;n=function(){function b(a,b,d){this.a=new x(a,b,d);this.h={};this.w=0}b.prototype.u=function(a,b,d,e){this.h[a]&&f(Error("Document "+
+a+" already followed"));b=new p(this.a,a,b,d,e);this.h[a]=b;this.w++;b.b("closed",i(function(){delete this.h[a];return this.w--},this));return b};b.prototype.M=function(a,b){if(this.h[a]!=r)return this.h[a];return this.a.get(a,i(function(a){var e;return a.snapshot===r?b(r):(e=w[a.type],b(this.u(a.doc,a.v,e,a.snapshot)))},this))};b.prototype.open=function(a,b,d){var e;typeof b==="function"&&(d=b,b="text");d||(d=function(){});typeof b==="string"&&(b=w[b]);if(this.h[a]!=r)e=this.h[a],e.type===b?d(e):
+d(e,"Document already exists with type "+e.type.name);else return this.a.get(a,i(function(g){return g.snapshot===r?this.a.submit(a,{type:b.name},0,i(function(g){return g.v!=r?(e=this.u(a,1,b,""),e.J=!0,d(e)):g.v===r&&g.error==="Type already set"?this.open(a,b,d):d(r,g.error)},this)):g.type===b.name?d(this.u(a,g.v,b,g.snapshot)):d(r,"Document already exists with type "+g.type)},this))};b.prototype.r=function(){if(this.a!=r)return this.j("disconnected"),this.a.r(),this.a=r};return b}();n.prototype.openExisting=
+n.prototype.M;n.prototype.open=n.prototype.open;q.H(n);s={};C=function(b,a,c){var d;b!=r||(b=window.location.hostname);a!=r||(a=window.location.port);d=b;a!=r&&(d+=":"+a);s[d]||(b=new n(b,a,c),b.b("disconnected",function(){return delete s[d]}),s[d]=b);return s[d]};m.Connection=n;m.Document=p;m.open=function(b,a,c,d){var e;typeof c==="function"&&(d=c,c=r);c!=r||(c={});e=C(c.host,c.port,c.R);return e.open(b,a,function(a){a.b("closed",function(){return setTimeout(function(){if(e.w===0)return e.r()},
+0)});return d(a)})};window.sharejs=m}).call(this);
View
755 share.uncompressed.js
@@ -0,0 +1,755 @@
+(function() {
+
+/** @preserve ShareJS v0.2
+http://sharejs.org
+
+Copyright 2011 Joseph Gentle
+
+BSD licensed:
+https://github.com/josephg/ShareJS/raw/master/LICENSE
+*/
+; var Connection, Document, MicroEvent, OpStream, WEB, append, checkValidComponent, checkValidOp, compose, compress, connections, exports, getConnection, i, inject, invertComponent, io, open, p, text, transformComponent, transformComponentX, transformPosition, transformX, types;
+ var __slice = Array.prototype.slice, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ exports = {};
+ /**
+ @const
+ @type {boolean}
+*/;
+ WEB = true;
+ MicroEvent = (function() {
+ function MicroEvent() {}
+ MicroEvent.prototype.on = function(event, fct) {
+ var _base;
+ this._events || (this._events = {});
+ (_base = this._events)[event] || (_base[event] = []);
+ this._events[event].push(fct);
+ return this;
+ };
+ MicroEvent.prototype.removeListener = function(event, fct) {
+ var idx, _ref;
+ this._events || (this._events = {});
+ idx = (_ref = this._events[event]) != null ? _ref.indexOf(fct) : void 0;
+ if ((idx != null) && idx >= 0) {
+ this._events[event].splice(idx, 1);
+ }
+ 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;
+ }
+ _ref2 = this._events[event];
+ for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
+ fn = _ref2[_i];
+ fn.apply(this, args);
+ }
+ return this;
+ };
+ return MicroEvent;
+ })();
+ MicroEvent.mixin = function(obj) {
+ var proto;
+ proto = obj.prototype || obj;
+ proto.on = proto['on'] = MicroEvent.prototype.on;
+ proto.removeListener = proto['removeListener'] = MicroEvent.prototype.removeListener;
+ proto.emit = MicroEvent.prototype.emit;
+ return obj;
+ };
+ if (typeof module != "undefined" && module !== null ? module.exports : void 0) {
+ module.exports = MicroEvent;
+ }
+ text = {};
+ text.name = 'text';
+ text.initialVersion = function() {
+ return '';
+ };
+ inject = function(s1, pos, s2) {
+ return s1.slice(0, pos) + s2 + s1.slice(pos);
+ };
+ checkValidComponent = function(c) {
+ var d_type, i_type;
+ if (typeof c['p'] !== 'number') {
+ throw new Error('component missing position field');
+ }
+ i_type = typeof c['i'];
+ d_type = typeof c['d'];
+ if (!((i_type === 'string') ^ (d_type === 'string'))) {
+ throw new Error('component needs an i or d field');
+ }
+ if (!(c['p'] >= 0)) {
+ throw new Error('position cannot be negative');
+ }
+ };
+ checkValidOp = function(op) {
+ var c, _i, _len;
+ for (_i = 0, _len = op.length; _i < _len; _i++) {
+ c = op[_i];
+ checkValidComponent(c);
+ }
+ return true;
+ };
+ text.apply = function(snapshot, op) {
+ var component, deleted, _i, _len;
+ checkValidOp(op);
+ for (_i = 0, _len = op.length; _i < _len; _i++) {
+ component = op[_i];
+ if (component['i'] != null) {
+ snapshot = inject(snapshot, component['p'], component['i']);
+ } else {
+ deleted = snapshot.slice(component['p'], component['p'] + component['d'].length);
+ if (component['d'] !== deleted) {
+ throw new Error("Delete component '" + component['d'] + "' does not match deleted text '" + deleted + "'");
+ }
+ snapshot = snapshot.slice(0, component['p']) + snapshot.slice(component['p'] + component['d'].length);
+ }
+ }
+ return snapshot;
+ };
+ text._append = append = function(newOp, c) {
+ var last, _ref, _ref2;
+ if (c['i'] === '' || c['d'] === '') {
+ return;
+ }
+ if (newOp.length === 0) {
+ return newOp.push(c);
+ } else {
+ last = newOp[newOp.length - 1];
+ if ((last['i'] != null) && (c['i'] != null) && (last['p'] <= (_ref = c['p']) && _ref <= (last['p'] + last['i'].length))) {
+ return newOp[newOp.length - 1] = {
+ i: inject(last['i'], c['p'] - last['p'], c['i']),
+ p: last['p']
+ };
+ } else if ((last['d'] != null) && (c['d'] != null) && (c['p'] <= (_ref2 = last['p']) && _ref2 <= (c['p'] + c['d'].length))) {
+ return newOp[newOp.length - 1] = {
+ d: inject(c['d'], last['p'] - c['p'], last['d']),
+ p: c['p']
+ };
+ } else {
+ return newOp.push(c);
+ }
+ }
+ };
+ text.compose = compose = function(op1, op2) {
+ var c, newOp, _i, _len;
+ checkValidOp(op1);
+ checkValidOp(op2);
+ newOp = op1.slice();
+ for (_i = 0, _len = op2.length; _i < _len; _i++) {
+ c = op2[_i];
+ append(newOp, c);
+ }
+ checkValidOp(newOp);
+ return newOp;
+ };
+ text.compress = compress = function(op) {
+ return compose([], op);
+ };
+ text.normalize = function(op) {
+ var c, newOp, _i, _len, _ref;
+ newOp = [];
+ if ((op['i'] != null) || (op['p'] != null)) {
+ op = [op];
+ }
+ for (_i = 0, _len = op.length; _i < _len; _i++) {
+ c = op[_i];
+ (_ref = c['p']) != null ? _ref : c['p'] = 0;
+ append(newOp, c);
+ }
+ return newOp;
+ };
+ transformPosition = function(pos, c, insertAfter) {
+ if (c['i'] != null) {
+ if (c['p'] < pos || (c['p'] === pos && insertAfter)) {
+ return pos + c['i'].length;
+ } else {
+ return pos;
+ }
+ } else {
+ if (pos <= c['p']) {
+ return pos;
+ } else if (pos <= c['p'] + c['d'].length) {
+ return c['p'];
+ } else {
+ return pos - c['d'].length;
+ }
+ }
+ };
+ text.transformCursor = function(position, op, insertAfter) {
+ var c, _i, _len;
+ for (_i = 0, _len = op.length; _i < _len; _i++) {
+ c = op[_i];
+ position = transformPosition(position, c, insertAfter);
+ }
+ return position;
+ };
+ transformComponent = function(dest, c, otherC, type) {
+ var cIntersect, intersectEnd, intersectStart, newC, otherIntersect, s;
+ checkValidOp([c]);
+ checkValidOp([otherC]);
+ if (c['i'] != null) {
+ return append(dest, {
+ i: c['i'],
+ p: transformPosition(c['p'], otherC, type === 'server')
+ });
+ } else {
+ if (otherC['i'] != null) {
+ s = c['d'];
+ if (c['p'] < otherC['p']) {
+ append(dest, {
+ d: s.slice(0, otherC['p'] - c['p']),
+ p: c['p']
+ });
+ s = s.slice(otherC['p'] - c['p']);
+ }
+ if (s !== '') {
+ return append(dest, {
+ d: s,
+ p: c['p'] + otherC['i'].length
+ });
+ }
+ } else {
+ if (c['p'] >= otherC['p'] + otherC['d'].length) {
+ return append(dest, {
+ d: c['d'],
+ p: c['p'] - otherC['d'].length
+ });
+ } else if (c['p'] + c['d'].length <= otherC['p']) {
+ return append(dest, c);
+ } else {
+ newC = {
+ d: '',
+ p: c['p']
+ };
+ if (c['p'] < otherC['p']) {
+ newC['d'] = c['d'].slice(0, otherC['p'] - c['p']);
+ }
+ if (c['p'] + c['d'].length > otherC['p'] + otherC['d'].length) {
+ newC['d'] += c['d'].slice(otherC['p'] + otherC['d'].length - c['p']);
+ }
+ intersectStart = Math.max(c['p'], otherC['p']);
+ intersectEnd = Math.min(c['p'] + c['d'].length, otherC['p'] + otherC['d'].length);
+ cIntersect = c['d'].slice(intersectStart - c['p'], intersectEnd - c['p']);
+ otherIntersect = otherC['d'].slice(intersectStart - otherC['p'], intersectEnd - otherC['p']);
+ if (cIntersect !== otherIntersect) {
+ throw new Error('Delete ops delete different text in the same region of the document');
+ }
+ if (newC['d'] !== '') {
+ newC['p'] = transformPosition(newC['p'], otherC);
+ return append(dest, newC);
+ }
+ }
+ }
+ }
+ };
+ transformComponentX = function(server, client, destServer, destClient) {
+ transformComponent(destServer, server, client, 'server');
+ return transformComponent(destClient, client, server, 'client');
+ };
+ text.transformX = transformX = function(serverOp, clientOp) {
+ var c, c_, clientComponent, k, newClientOp, newServerOp, nextC, s, s_, _i, _j, _k, _l, _len, _len2, _len3, _len4, _ref, _ref2;
+ checkValidOp(serverOp);
+ checkValidOp(clientOp);
+ newClientOp = [];
+ for (_i = 0, _len = clientOp.length; _i < _len; _i++) {
+ clientComponent = clientOp[_i];
+ newServerOp = [];
+ k = 0;
+ while (k < serverOp.length) {
+ nextC = [];
+ transformComponentX(serverOp[k], clientComponent, newServerOp, nextC);
+ k++;
+ if (nextC.length === 1) {
+ clientComponent = nextC[0];
+ } else if (nextC.length === 0) {
+ _ref = serverOp.slice(k);
+ for (_j = 0, _len2 = _ref.length; _j < _len2; _j++) {
+ s = _ref[_j];
+ append(newServerOp, s);
+ }
+ clientComponent = null;
+ break;
+ } else {
+ _ref2 = transformX(serverOp.slice(k), nextC), s_ = _ref2[0], c_ = _ref2[1];
+ for (_k = 0, _len3 = s_.length; _k < _len3; _k++) {
+ s = s_[_k];
+ append(newServerOp, s);
+ }
+ for (_l = 0, _len4 = c_.length; _l < _len4; _l++) {
+ c = c_[_l];
+ append(newClientOp, c);
+ }
+ clientComponent = null;
+ break;
+ }
+ }
+ if (clientComponent != null) {
+ append(newClientOp, clientComponent);
+ }
+ serverOp = newServerOp;
+ }
+ return [serverOp, newClientOp];
+ };
+ text.transform = function(op, otherOp, type) {
+ var client, server, _, _ref, _ref2;
+ if (!(type === 'server' || type === 'client')) {
+ throw new Error("type must be 'server' or 'client'");
+ }
+ if (type === 'server') {
+ _ref = transformX(op, otherOp), server = _ref[0], _ = _ref[1];
+ return server;
+ } else {
+ _ref2 = transformX(otherOp, op), _ = _ref2[0], client = _ref2[1];
+ return client;
+ }
+ };
+ invertComponent = function(c) {
+ if (c['i'] != null) {
+ return {
+ d: c['i'],
+ p: c['p']
+ };
+ } else {
+ return {
+ i: c['d'],
+ p: c['p']
+ };
+ }
+ };
+ text.invert = function(op) {
+ var c, _i, _len, _ref, _results;
+ _ref = op.slice().reverse();
+ _results = [];
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ c = _ref[_i];
+ _results.push(invertComponent(c));
+ }
+ return _results;
+ };
+ if (WEB != null) {
+ exports.types || (exports.types = {});
+ exports.types['text'] = text;
+ } else {
+ module.exports = text;
+ }
+ if (WEB != null) {
+ if (!window['io']) {
+ throw new Error('Must load socket.io before this library');
+ }
+ io = window['io'];
+ } else {
+ io = require('../../thirdparty/Socket.io-node-client').io;
+ }
+ p = function() {};
+ OpStream = (function() {
+ function OpStream(host, port, path) {
+ this.onMessage = __bind(this.onMessage, this);; var resource;
+ resource = path ? path + '/socket.io' : 'socket.io';
+ this.socket = new io['Socket'](host, {
+ port: port,
+ resource: resource
+ });
+ this.socket['on']('connect', this.onConnect);
+ this.socket['on']('message', this.onMessage);
+ this.socket['connect']();
+ this.callbacks = {};
+ this.lastReceivedDoc = null;
+ this.lastSentDoc = null;
+ }
+ OpStream.prototype.onConnect = function() {
+ return p('connected');
+ };
+ OpStream.prototype.on = function(docName, type, callback) {
+ var _base;
+ (_base = this.callbacks)[docName] || (_base[docName] = {});
+ if (this.callbacks[docName][type] != null) {
+ throw new Error("Callback already exists for " + docName + ", " + type);
+ }
+ return this.callbacks[docName][type] = callback;
+ };
+ OpStream.prototype.removeListener = function(docName, type, listener) {
+ var _ref;
+ return (_ref = this.callbacks[docName]) != null ? delete _ref[type] : void 0;
+ };
+ OpStream.prototype.onMessage = function(data) {
+ var emit;
+ p('message');
+ p(data);
+ if (data['doc'] != null) {
+ this.lastReceivedDoc = data['doc'];
+ } else {
+ data['doc'] = this.lastReceivedDoc;
+ }
+ emit = __bind(function(type, clear) {
+ var callback, _ref;
+ p("emit " + data.doc + " " + type);
+ callback = (_ref = this.callbacks[data['doc']]) != null ? _ref[type] : void 0;
+ if (callback != null) {
+ if (clear) {
+ this.callbacks[data['doc']][type] = null;
+ }
+ return callback(data);
+ }
+ }, this);
+ if (data['snapshot'] !== void 0) {
+ return emit('snapshot', true);
+ } else if (data['follow'] != null) {
+ if (data['follow']) {
+ return emit('follow', true);
+ } else {
+ return emit('unfollow', true);
+ }
+ } else if (data['v'] !== void 0) {
+ if (data['op'] != null) {
+ return emit('op', false);
+ } else {
+ return emit('localop', true);
+ }
+ }
+ };
+ OpStream.prototype.send = function(msg) {
+ if (msg['doc'] === this.lastSentDoc) {
+ delete msg['doc'];
+ } else {
+ this.lastSentDoc = msg['doc'];
+ }
+ return this.socket['send'](msg);
+ };
+ OpStream.prototype.follow = function(docName, v, callback) {
+ var request;
+ p("follow " + docName);
+ request = {
+ 'doc': docName,
+ 'follow': true
+ };
+ if (v != null) {
+ request['v'] = v;
+ }
+ this.send(request);
+ return this.on(docName, 'follow', callback);
+ };
+ OpStream.prototype.get = function(docName, callback) {
+ p("get " + docName);
+ this.send({
+ 'doc': docName,
+ 'snapshot': null
+ });
+ return this.on(docName, 'snapshot', callback);
+ };
+ OpStream.prototype.submit = function(docName, op, version, callback) {
+ p("submit");
+ this.send({
+ 'doc': docName,
+ 'v': version,
+ 'op': op
+ });
+ return this.on(docName, 'localop', callback);
+ };
+ OpStream.prototype.unfollow = function(docName, callback) {
+ p("unfollow " + docName);
+ this.send({
+ 'doc': docName,
+ 'follow': false
+ });
+ return this.on(docName, 'unfollow', callback);
+ };
+ OpStream.prototype.disconnect = function() {
+ this.socket['disconnect']();
+ return this.socket = null;
+ };
+ return OpStream;
+ })();
+ exports.OpStream = OpStream;
+ if (WEB != null) {
+ types || (types = exports.types);
+ } else {
+ OpStream = require('./opstream').OpStream;
+ types = require('../types');
+ MicroEvent = require('./microevent');
+ }
+ p = function() {};
+ i = function() {};
+ Document = (function() {
+ function Document(stream, name, version, type, snapshot) {
+ this.stream = stream;
+ this.name = name;
+ this.version = version;
+ this.type = type;
+ this.onOpReceived = __bind(this.onOpReceived, this);;
+ this.tryFlushPendingOp = __bind(this.tryFlushPendingOp, this);;
+ if (this.type.compose == null) {
+ throw new Error('Handling types without compose() defined is not currently implemented');
+ }
+ this['snapshot'] = snapshot;
+ this.inflightOp = null;
+ this.inflightCallbacks = [];
+ this.pendingOp = null;
+ this.pendingCallbacks = [];
+ this.serverOps = {};
+ this.listeners = [];
+ this.created = false;
+ this.follow();
+ }
+ Document.prototype.follow = function(callback) {
+ this.stream.on(this.name, 'op', this.onOpReceived);
+ return this.stream.follow(this.name, this.version, __bind(function(msg) {
+ if (msg['v'] !== this.version) {
+ throw new Error("Expected version " + this.version + " but got " + msg['v']);
+ }
+ if (callback != null) {
+ return callback();
+ }
+ }, this));
+ };
+ Document.prototype.unfollow = function(callback) {
+ this.stream.removeListener(this.name, 'op', this.onOpReceived);
+ return this.stream.unfollow(this.name, callback);
+ };
+ Document.prototype.tryFlushPendingOp = function() {
+ if (this.inflightOp === null && this.pendingOp !== null) {
+ this.inflightOp = this.pendingOp;
+ this.inflightCallbacks = this.pendingCallbacks;
+ this.pendingOp = null;
+ this.pendingCallbacks = [];
+ return this.stream.submit(this.name, this.inflightOp, this.version, __bind(function(response) {
+ var callback, _i, _j, _len, _len2, _ref, _ref2;
+ if (response['v'] === null) {
+ _ref = this.inflightCallbacks;
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ callback = _ref[_i];
+ callback(null);
+ }
+ this.inflightOp = null;
+ throw new Error(response['error']);
+ }
+ if (response['v'] !== this.version) {
+ throw new Error('Invalid version from server');
+ }
+ this.serverOps[this.version] = this.inflightOp;
+ this.version++;
+ _ref2 = this.inflightCallbacks;
+ for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
+ callback = _ref2[_j];
+ callback(this.inflightOp, null);
+ }
+ this.inflightOp = null;
+ return this.tryFlushPendingOp();
+ }, this));
+ }
+ };
+ Document.prototype.onOpReceived = function(msg) {
+ var docOp, op, xf, _ref, _ref2;
+ if (msg['v'] < this.version) {
+ return;
+ }
+ if (msg['doc'] !== this.name) {
+ throw new Error("Expected docName " + this.name + " but got " + msg['doc']);
+ }
+ if (msg['v'] !== this.version) {
+ throw new Error("Expected version " + this.version + " but got " + msg['v']);
+ }
+ op = msg['op'];
+ this.serverOps[this.version] = op;
+ xf = this.type.transformX || __bind(function(server, client) {
+ var client_, server_;
+ server_ = this.type.transform(server, client, 'server');
+ client_ = this.type.transform(client, server, 'client');
+ return [server_, client_];
+ }, this);
+ docOp = op;
+ if (this.inflightOp !== null) {
+ _ref = xf(docOp, this.inflightOp), docOp = _ref[0], this.inflightOp = _ref[1];
+ }
+ if (this.pendingOp !== null) {
+ _ref2 = xf(docOp, this.pendingOp), docOp = _ref2[0], this.pendingOp = _ref2[1];
+ }
+ this['snapshot'] = this.type.apply(this['snapshot'], docOp);
+ this.version++;
+ this.emit('remoteop', docOp);
+ return this.emit('change', docOp);
+ };
+ Document.prototype.submitOp = function(op, v, callback) {
+ var realOp, _ref;
+ if (v == null) {
+ v = this.version;
+ }
+ if (typeof v === 'function') {
+ callback = v;
+ v = this.version;
+ }
+ if (((_ref = this.type) != null ? _ref.normalize : void 0) != null) {
+ op = this.type.normalize(op);
+ }
+ while (v < this.version) {
+ realOp = this.recentOps[v];
+ if (!realOp) {
+ throw new Error('Op version too old');
+ }
+ op = this.type.transform(op, realOp, 'client');
+ v++;
+ }
+ this['snapshot'] = this.type.apply(this['snapshot'], op);
+ if (this.pendingOp !== null) {
+ this.pendingOp = this.type.compose(this.pendingOp, op);
+ } else {
+ this.pendingOp = op;
+ }
+ if (callback) {
+ this.pendingCallbacks.push(callback);
+ }
+ this.emit('change', op);
+ return setTimeout(this.tryFlushPendingOp, 0);
+ };
+ Document.prototype.close = function(callback) {
+ return this.unfollow(__bind(function() {
+ if (callback) {
+ callback();
+ }
+ this.emit('closed');
+ }, this));
+ };
+ return Document;
+ })();
+ MicroEvent.mixin(Document);
+ Document.prototype['submitOp'] = Document.prototype.submitOp;
+ Document.prototype['close'] = Document.prototype.close;
+ Connection = (function() {
+ function Connection(host, port, basePath) {
+ this.stream = new OpStream(host, port, basePath);
+ this.docs = {};
+ this.numDocs = 0;
+ }
+ Connection.prototype.makeDoc = function(name, version, type, snapshot) {
+ var doc;
+ if (this.docs[name]) {
+ throw new Error("Document " + name + " already followed");
+ }
+ doc = new Document(this.stream, name, version, type, snapshot);
+ this.docs[name] = doc;
+ this.numDocs++;
+ doc.on('closed', __bind(function() {
+ delete this.docs[name];
+ return this.numDocs--;
+ }, this));
+ return doc;
+ };
+ Connection.prototype.openExisting = function(docName, callback) {
+ if (this.docs[docName] != null) {
+ return this.docs[docName];
+ }
+ return this.stream.get(docName, __bind(function(response) {
+ var type;
+ if (response['snapshot'] === null) {
+ return callback(null);
+ } else {
+ type = types[response['type']];
+ return callback(this.makeDoc(response['doc'], response['v'], type, response['snapshot']));
+ }
+ }, this));
+ };
+ Connection.prototype.open = function(docName, type, callback) {
+ var doc;
+ if (typeof type === 'function') {
+ callback = type;
+ type = 'text';
+ }
+ callback || (callback = function() {});
+ if (typeof type === 'string') {
+ type = types[type];
+ }
+ if (this.docs[docName] != null) {
+ doc = this.docs[docName];
+ if (doc.type === type) {
+ callback(doc);
+ } else {
+ callback(doc, 'Document already exists with type ' + doc.type.name);
+ }
+ return;
+ }
+ return this.stream.get(docName, __bind(function(response) {
+ if (response['snapshot'] === null) {
+ return this.stream.submit(docName, {
+ 'type': type.name
+ }, 0, __bind(function(response) {
+ if (response['v'] != null) {
+ doc = this.makeDoc(docName, 1, type, type.initialVersion());
+ doc.created = true;
+ return callback(doc);
+ } else if (response['v'] === null && response['error'] === 'Type already set') {
+ return this.open(docName, type, callback);
+ } else {
+ return callback(null, response['error']);
+ }
+ }, this));
+ } else if (response['type'] === type.name) {
+ return callback(this.makeDoc(docName, response['v'], type, response['snapshot']));
+ } else {
+ return callback(null, "Document already exists with type " + response['type']);
+ }
+ }, this));
+ };
+ Connection.prototype.create = function(type, prefix) {
+ throw new Error('Not implemented');
+ };
+ Connection.prototype.disconnect = function() {
+ if (this.stream != null) {
+ this.emit('disconnected');
+ this.stream.disconnect();
+ return this.stream = null;
+ }
+ };
+ return Connection;
+ })();
+ Connection.prototype['openExisting'] = Connection.prototype.openExisting;
+ Connection.prototype['open'] = Connection.prototype.open;
+ MicroEvent.mixin(Connection);
+ connections = {};
+ getConnection = function(host, port, basePath) {
+ var address, c;
+ if (WEB != null) {
+ host != null ? host : host = window.location.hostname;
+ port != null ? port : port = window.location.port;
+ }
+ address = host;
+ if (port != null) {
+ address += ":" + port;
+ }
+ if (!connections[address]) {
+ c = new Connection(host, port, basePath);
+ c.on('disconnected', function() {
+ return delete connections[address];
+ });
+ connections[address] = c;
+ }
+ return connections[address];
+ };
+ open = function(docName, type, options, callback) {
+ var c;
+ if (typeof options === 'function') {
+ callback = options;
+ options = null;
+ }
+ options != null ? options : options = {};
+ c = getConnection(options.host, options.port, options.basePath);
+ return c.open(docName, type, function(doc) {
+ doc.on('closed', function() {
+ return setTimeout(function() {
+ if (c.numDocs === 0) {
+ return c.disconnect();
+ }
+ }, 0);
+ });
+ return callback(doc);
+ });
+ };
+ if (WEB != null) {
+ exports['Connection'] = Connection;
+ exports['Document'] = Document;
+ exports['open'] = open;
+ window['sharejs'] = exports;
+ } else {
+ exports.Connection = Connection;
+ exports.open = open;
+ }
+}).call(this);
View
83 src/client/client.coffee
@@ -1,14 +1,12 @@
# Abstraction over raw net stream, for use by a client.
-if window?
- types ||= window.sharejs.types
+if WEB?
+ types ||= exports.types
else
OpStream = require('./opstream').OpStream
types = require('../types')
MicroEvent = require './microevent'
-exports ||= {}
-
p = -> #require('util').debug
i = -> #require('util').inspect
@@ -23,9 +21,12 @@ class Document
# stream is a OpStream object.
# name is the documents' docName.
# version is the version of the document _on the server_
- constructor: (@stream, @name, @version, @type, @snapshot) ->
+ constructor: (@stream, @name, @version, @type, snapshot) ->
throw new Error('Handling types without compose() defined is not currently implemented') unless @type.compose?
+ # Gotta figure out a better way to make this work with closure.
+ @['snapshot'] = snapshot
+
# The op that is currently roundtripping to the server, or null.
@inflightOp = null
@inflightCallbacks = []
@@ -49,7 +50,7 @@ class Document
@stream.on @name, 'op', @onOpReceived
@stream.follow @name, @version, (msg) =>
- throw new Error("Expected version #{@version} but got #{msg.v}") unless msg.v == @version
+ throw new Error("Expected version #{@version} but got #{msg['v']}") unless msg['v'] == @version
callback() if callback?
# Internal.
@@ -71,16 +72,16 @@ class Document
@pendingCallbacks = []
@stream.submit @name, @inflightOp, @version, (response) =>
- if response.v == null
+ if response['v'] == null
# Currently, it should be impossible to reach this case.
# This case is currently untested.
callback(null) for callback in @inflightCallbacks
@inflightOp = null
# Perhaps the op should be removed from the local document...
# @snapshot = @type.apply @snapshot, type.invert(@inflightOp) if type.invert?
- throw new Error(response.error)
+ throw new Error(response['error'])
- throw new Error('Invalid version from server') unless response.v == @version
+ throw new Error('Invalid version from server') unless response['v'] == @version
@serverOps[@version] = @inflightOp
@version++
@@ -98,14 +99,14 @@ class Document
# 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 if msg['v'] < @version
- 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
+ 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
# p "if: #{i @inflightOp} pending: #{i @pendingOp} doc '#{@snapshot}' op: #{i msg.op}"
- op = msg.op
+ op = msg['op']
@serverOps[@version] = op
# Transform a server op by a client op, and vice versa.
@@ -120,7 +121,7 @@ class Document
if @pendingOp != null
[docOp, @pendingOp] = xf docOp, @pendingOp
- @snapshot = @type.apply @snapshot, docOp
+ @['snapshot'] = @type.apply @['snapshot'], docOp
@version++
@emit 'remoteop', docOp
@@ -142,7 +143,7 @@ class Document
v++
# If this throws an exception, no changes should have been made to the doc
- @snapshot = @type.apply @snapshot, op
+ @['snapshot'] = @type.apply @['snapshot'], op
if @pendingOp != null
@pendingOp = @type.compose(@pendingOp, op)
@@ -167,7 +168,18 @@ class Document
MicroEvent.mixin Document
+# Export the functions for the closure compiler
+Document.prototype['submitOp'] = Document.prototype.submitOp
+Document.prototype['close'] = Document.prototype.close
+
+
+# A connection to a sharejs server
class Connection
+ constructor: (host, port, basePath) ->
+ @stream = new OpStream(host, port, basePath)
+ @docs = {}
+ @numDocs = 0
+
makeDoc: (name, version, type, snapshot) ->
throw new Error("Document #{name} already followed") if @docs[name]
@@ -181,23 +193,18 @@ class Connection
doc
- constructor: (host, port, basePath) ->
- @stream = new OpStream(host, port, basePath)
- @docs = {}
- @numDocs = 0
-
# Open a document that already exists
# callback is passed a Document or null
# callback(doc)
openExisting: (docName, callback) ->
return @docs[docName] if @docs[docName]?
@stream.get docName, (response) =>
- if response.snapshot == null
+ if response['snapshot'] == null
callback(null)
else
- type = types[response.type]
- callback @makeDoc(response.doc, response.v, type, response.snapshot)
+ type = types[response['type']]
+ callback @makeDoc(response['doc'], response['v'], type, response['snapshot'])
# Open a document. It will be created if it doesn't already exist.
# Callback is passed a document or an error
@@ -223,21 +230,21 @@ class Connection
return
@stream.get docName, (response) =>
- if response.snapshot == null
- @stream.submit docName, {type: type.name}, 0, (response) =>
- if response.v?
+ if response['snapshot'] == null
+ @stream.submit docName, {'type': type.name}, 0, (response) =>
+ if response['v']?
doc = @makeDoc(docName, 1, type, type.initialVersion())
doc.created = yes
callback doc
- else if response.v == null and response.error == 'Type already set'
+ else if response['v'] == null and response['error'] == 'Type already set'
# Somebody else has created the document. Get the snapshot again..
@open docName, type, callback
else
- callback null, response.error
- else if response.type == type.name
- callback @makeDoc(docName, response.v, type, response.snapshot)
+ callback null, response['error']
+ else if response['type'] == type.name
+ callback @makeDoc(docName, response['v'], type, response['snapshot'])
else
- callback null, "Document already exists with type #{response.type}"
+ callback null, "Document already exists with type #{response['type']}"
# To be written. Create a new document with a random name.
# Prefix is an optional string to put on the front of the document name.
@@ -250,13 +257,16 @@ class Connection
@stream.disconnect()
@stream = null
+Connection.prototype['openExisting'] = Connection.prototype.openExisting
+Connection.prototype['open'] = Connection.prototype.open
+
MicroEvent.mixin Connection
# This is a private connection pool for implicitly created connections.
connections = {}
getConnection = (host, port, basePath) ->
- if window?
+ if WEB?
host ?= window.location.hostname
port ?= window.location.port
@@ -291,10 +301,11 @@ open = (docName, type, options, callback) ->
callback(doc)
-if window?
- window.sharejs.Connection = Connection
- window.sharejs.Document = Document
- window.sharejs.open = open
+if WEB?
+ exports['Connection'] = Connection
+ exports['Document'] = Document
+ exports['open'] = open
+ window['sharejs'] = exports
else
exports.Connection = Connection
exports.open = open
View
6 src/client/microevent.coffee
@@ -25,7 +25,11 @@ class MicroEvent
# mixin will delegate all MicroEvent.js function in the destination object
MicroEvent.mixin = (obj) ->
proto = obj.prototype || obj
- proto[fname] = MicroEvent.prototype[fname] for fname in ['on', 'removeListener', 'emit']
+
+ # Damn closure compiler :/
+ proto.on = proto['on'] = MicroEvent.prototype.on
+ proto.removeListener = proto['removeListener'] = MicroEvent.prototype.removeListener
+ proto.emit = MicroEvent.prototype.emit
obj
module.exports = MicroEvent if module?.exports
View
62 src/client/opstream.coffee
@@ -1,25 +1,25 @@
# A wrapper around the raw network IO.
# SocketIO's 'io' must be defined prior to this file being loaded.
-if window?
- throw new Error 'Must load socket.io before this library' unless window.io?
- io = window.io
+if WEB?
+ throw new Error 'Must load socket.io before this library' unless window['io']
+ io = window['io']
else
io = require('../../thirdparty/Socket.io-node-client').io
p = -> #(x) -> console.log x
# Make 1 per server.
#
-# Consider refactoring this to use microevent.
+# Refactor this to use microevent.
class OpStream
constructor: (host, port, path) ->
resource = if path then path + '/socket.io' else 'socket.io'
- @socket = new io.Socket host, {port:port, resource:resource}
- @socket.on 'connect', @onConnect
- @socket.on 'message', @onMessage
- @socket.connect()
+ @socket = new io['Socket'] host, {port:port, resource:resource}
+ @socket['on'] 'connect', @onConnect
+ @socket['on'] 'message', @onMessage
+ @socket['connect']()
# A hash from docName -> {'follow': fn, 'op': fn, 'snapshot': fn, ...}
@callbacks = {}
@@ -40,81 +40,77 @@ class OpStream
onMessage: (data) =>
p 'message'
p data
- if data.doc?
- @lastReceivedDoc = data.doc
+ if data['doc']?
+ @lastReceivedDoc = data['doc']
else
- data.doc = @lastReceivedDoc
+ data['doc'] = @lastReceivedDoc
# Calls the registered callback for this event. If clear is truthy, remove the callback handler
# afterwards.
emit = (type, clear) =>
p "emit #{data.doc} #{type}"
- callback = @callbacks[data.doc]?[type]
+ callback = @callbacks[data['doc']]?[type]
if callback?
- @callbacks[data.doc][type] = null if clear
+ @callbacks[data['doc']][type] = null if clear
callback(data)
- if data.snapshot != undefined
+ if data['snapshot'] != undefined
emit 'snapshot', yes
- else if data.follow?
- if data.follow
+ else if data['follow']?
+ if data['follow']
emit 'follow', yes
else
emit 'unfollow', yes
- else if data.v != undefined # Result of sending an op
- if data.op?
+ else if data['v'] != undefined # Result of sending an op
+ if data['op']?
# Remote op
emit 'op', no
else
emit 'localop', yes
send: (msg) ->
- if msg.doc == @lastSentDoc
- delete msg.doc
+ if msg['doc'] == @lastSentDoc
+ delete msg['doc']
else
- @lastSentDoc = msg.doc
+ @lastSentDoc = msg['doc']
- @socket.send msg
+ @socket['send'] msg
# Send follow request, queue up callback.
follow: (docName, v, callback) ->
p "follow #{docName}"
- request = {doc:docName, follow:true}
- request.v = v if v?
+ request = {'doc':docName, 'follow':true}
+ request['v'] = v if v?
@send request
@on docName, 'follow', callback
# Get a document snapshot at the current version
get: (docName, callback) ->
p "get #{docName}"
- @send {doc:docName, snapshot:null}
+ @send {'doc':docName, 'snapshot':null}
@on docName, 'snapshot', callback
# Submit an op to the named document
submit: (docName, op, version, callback) ->
p "submit"
#console.log "submit v #{version} on #{docName}", op
- @send {doc:docName, v:version, op:op}
+ @send {'doc':docName, 'v':version, 'op':op}
@on docName, 'localop', callback
# Unfollow a document
unfollow: (docName, callback) ->
p "unfollow #{docName}"
- @send {doc:docName, follow:false}
+ @send {'doc':docName, 'follow':false}
@on docName, 'unfollow', callback
disconnect: ->
- @socket.disconnect()
+ @socket['disconnect']()
@socket = null
#{follow: follow, connect: connect, get: get, submit: submit}
-if window?
- window.sharejs ||= {}
- window.sharejs.OpStream = OpStream
-else
- exports.OpStream = OpStream
+exports.OpStream = OpStream
View
21 src/client/web-prelude.coffee
@@ -0,0 +1,21 @@
+`
+/** @preserve ShareJS v0.2
+http://sharejs.org
+
+Copyright 2011 Joseph Gentle
+