Adding Etherpad/Etherpad-lite document type to ShareJS #112

Closed
wants to merge 6 commits into
from
View
@@ -19,6 +19,8 @@ client = [
'types/helpers'
'types/text'
'types/text-api'
+ 'types/etherpad'
+ 'types/etherpad-api'
'client/doc'
'client/connection'
'client/index'
@@ -83,13 +85,15 @@ buildtype = (name) ->
task 'webclient', 'Build the web client into one file', ->
compile client, 'webclient/share'
buildtype 'json'
+ buildtype 'etherpad'
buildtype 'text-tp2'
# TODO: This should also be closure compiled.
extrafiles = expandNames extras
e "coffee --compile --output webclient/ #{extrafiles}", ->
- # For backwards compatibility. (The ace.js file used to be called share-ace.js)
+ # For backwards compatibility. (The ace.js file used to be called share-ace.js)
e "cp webclient/ace.js webclient/share-ace.js"
+ e "cp src/lib-etherpad/* webclient/"
#task 'lightwave', ->
# buildclosure ['client/web-prelude', 'client/microevent', 'types/text-tp2'], 'lightwave'
View
@@ -1,128 +1,131 @@
-# This is some utility code to connect an ace editor to a sharejs document.
-
-Range = require("ace/range").Range
-
-# Convert an ace delta into an op understood by share.js
-applyToShareJS = (editorDoc, delta, doc) ->
- # Get the start position of the range, in no. of characters
- getStartOffsetPosition = (range) ->
- # This is quite inefficient - getLines makes a copy of the entire
- # lines array in the document. It would be nice if we could just
- # access them directly.
- lines = editorDoc.getLines 0, range.start.row
-
- offset = 0
-
- for line, i in lines
- offset += if i < range.start.row
- line.length
- else
- range.start.column
-
- # Add the row number to include newlines.
- offset + range.start.row
-
- pos = getStartOffsetPosition(delta.range)
-
- switch delta.action
- when 'insertText' then doc.insert pos, delta.text
- when 'removeText' then doc.del pos, delta.text.length
-
- when 'insertLines'
- text = delta.lines.join('\n') + '\n'
- doc.insert pos, text
-
- when 'removeLines'
- text = delta.lines.join('\n') + '\n'
- doc.del pos, text.length
-
- else throw new Error "unknown action: #{delta.action}"
-
- return
-
-# Attach an ace editor to the document. The editor's contents are replaced
-# with the document's contents unless keepEditorContents is true. (In which case the document's
-# contents are nuked and replaced with the editor's).
-window.sharejs.extendDoc 'attach_ace', (editor, keepEditorContents) ->
- throw new Error 'Only text documents can be attached to ace' unless @provides['text']
-
- doc = this
- editorDoc = editor.getSession().getDocument()
- editorDoc.setNewLineMode 'unix'
-
- check = ->
- window.setTimeout ->
- editorText = editorDoc.getValue()
- otText = doc.getText()
-
- if editorText != otText
- console.error "Text does not match!"
- console.error "editor: #{editorText}"
- console.error "ot: #{otText}"
- # Should probably also replace the editor text with the doc snapshot.
- , 0
-
- if keepEditorContents
- doc.del 0, doc.getText().length
- doc.insert 0, editorDoc.getValue()
- else
- editorDoc.setValue doc.getText()
-
- check()
-
- # When we apply ops from sharejs, ace emits edit events. We need to ignore those
- # to prevent an infinite typing loop.
- suppress = false
-
- # Listen for edits in ace
- editorListener = (change) ->
- return if suppress
- applyToShareJS editorDoc, change.data, doc
-
- check()
-
- editorDoc.on 'change', editorListener
-
- # Listen for remote ops on the sharejs document
- docListener = (op) ->
- suppress = true
- applyToDoc editorDoc, op
- suppress = false
-
- check()
-
-
- # Horribly inefficient.
- offsetToPos = (offset) ->
- # Again, very inefficient.
- lines = editorDoc.getAllLines()
-
- row = 0
- for line, row in lines
- break if offset <= line.length
-
- # +1 for the newline.
- offset -= lines[row].length + 1
-
- row:row, column:offset
-
- doc.on 'insert', (pos, text) ->
- suppress = true
- editorDoc.insert offsetToPos(pos), text
- suppress = false
- check()
-
- doc.on 'delete', (pos, text) ->
- suppress = true
- range = Range.fromPoints offsetToPos(pos), offsetToPos(pos + text.length)
- editorDoc.remove range
- suppress = false
- check()
-
- doc.detach_ace = ->
- doc.removeListener 'remoteop', docListener
- editorDoc.removeListener 'change', editorListener
- delete doc.detach_ace
-
- return
-
+# This is some utility code to connect an ace editor to a sharejs document.
+
+if (!(require?) && ace.require?)
+ require = ace.require
+
+Range = require("ace/range").Range
+
+# Convert an ace delta into an op understood by share.js
+applyToShareJS = (editorDoc, delta, doc) ->
+ # Get the start position of the range, in no. of characters
+ getStartOffsetPosition = (range) ->
+ # This is quite inefficient - getLines makes a copy of the entire
+ # lines array in the document. It would be nice if we could just
+ # access them directly.
+ lines = editorDoc.getLines 0, range.start.row
+
+ offset = 0
+
+ for line, i in lines
+ offset += if i < range.start.row
+ line.length
+ else
+ range.start.column
+
+ # Add the row number to include newlines.
+ offset + range.start.row
+
+ pos = getStartOffsetPosition(delta.range)
+
+ switch delta.action
+ when 'insertText' then doc.insert pos, delta.text
+ when 'removeText' then doc.del pos, delta.text.length
+
+ when 'insertLines'
+ text = delta.lines.join('\n') + '\n'
+ doc.insert pos, text
+
+ when 'removeLines'
+ text = delta.lines.join('\n') + '\n'
+ doc.del pos, text.length
+
+ else throw new Error "unknown action: #{delta.action}"
+
+ return
+
+# Attach an ace editor to the document. The editor's contents are replaced
+# with the document's contents unless keepEditorContents is true. (In which case the document's
+# contents are nuked and replaced with the editor's).
+window.sharejs.extendDoc 'attach_ace', (editor, keepEditorContents) ->
+ throw new Error 'Only text documents can be attached to ace' unless @provides['text']
+
+ doc = this
+ editorDoc = editor.getSession().getDocument()
+ editorDoc.setNewLineMode 'unix'
+
+ check = ->
+ window.setTimeout ->
+ editorText = editorDoc.getValue()
+ otText = doc.getText()
+
+ if editorText != otText
+ console.error "Text does not match!"
+ console.error "editor: #{editorText}"
+ console.error "ot: #{otText}"
+ # Should probably also replace the editor text with the doc snapshot.
+ , 0
+
+ if keepEditorContents
+ doc.del 0, doc.getText().length
+ doc.insert 0, editorDoc.getValue()
+ else
+ editorDoc.setValue doc.getText()
+
+ check()
+
+ # When we apply ops from sharejs, ace emits edit events. We need to ignore those
+ # to prevent an infinite typing loop.
+ suppress = false
+
+ # Listen for edits in ace
+ editorListener = (change) ->
+ return if suppress
+ applyToShareJS editorDoc, change.data, doc
+
+ check()
+
+ editorDoc.on 'change', editorListener
+
+ # Listen for remote ops on the sharejs document
+ docListener = (op) ->
+ suppress = true
+ applyToDoc editorDoc, op
+ suppress = false
+
+ check()
+
+
+ # Horribly inefficient.
+ offsetToPos = (offset) ->
+ # Again, very inefficient.
+ lines = editorDoc.getAllLines()
+
+ row = 0
+ for line, row in lines
+ break if offset <= line.length
+
+ # +1 for the newline.
+ offset -= lines[row].length + 1
+
+ row:row, column:offset
+
+ doc.on 'insert', (pos, text) ->
+ suppress = true
+ editorDoc.insert offsetToPos(pos), text
+ suppress = false
+ check()
+
+ doc.on 'delete', (pos, text) ->
+ suppress = true
+ range = Range.fromPoints offsetToPos(pos), offsetToPos(pos + text.length)
+ editorDoc.remove range
+ suppress = false
+ check()
+
+ doc.detach_ace = ->
+ doc.removeListener 'remoteop', docListener
+ editorDoc.removeListener 'change', editorListener
+ delete doc.detach_ace
+
+ return
+
@@ -0,0 +1,112 @@
+/**
+ * This code represents the Attribute Pool Object of the original Etherpad.
+ * 90% of the code is still like in the original Etherpad
+ * Look at https://github.com/ether/pad/blob/master/infrastructure/ace/www/easysync2.js
+ * You can find a explanation what a attribute pool is here:
+ * https://github.com/Pita/etherpad-lite/blob/master/doc/easysync/easysync-notes.txt
+ */
+
+/*
+ * Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ An AttributePool maintains a mapping from [key,value] Pairs called
+ Attributes to Numbers (unsigened integers) and vice versa. These numbers are
+ used to reference Attributes in Changesets.
+*/
+
+var AttributePool = function () {
+ this.numToAttrib = {}; // e.g. {0: ['foo','bar']}
+ this.attribToNum = {}; // e.g. {'foo,bar': 0}
+ this.nextNum = 0;
+};
+
+AttributePool.prototype.putAttrib = function (attrib, dontAddIfAbsent) {
+ var str = String(attrib);
+ if (str in this.attribToNum) {
+ return this.attribToNum[str];
+ }
+ if (dontAddIfAbsent) {
+ return -1;
+ }
+ var num = this.nextNum++;
+ this.attribToNum[str] = num;
+ this.numToAttrib[num] = [String(attrib[0] || ''), String(attrib[1] || '')];
+ return num;
+};
+
+AttributePool.prototype.getAttrib = function (num) {
+ var pair = this.numToAttrib[num];
+ if (!pair) {
+ return pair;
+ }
+ return [pair[0], pair[1]]; // return a mutable copy
+};
+
+AttributePool.prototype.getAttribKey = function (num) {
+ var pair = this.numToAttrib[num];
+ if (!pair) return '';
+ return pair[0];
+};
+
+AttributePool.prototype.clone = function() {
+ var result = new AttributePool();
+ result.numToAttrib = this.numToAttrib;
+ result.attribToNum = this.attribToNum;
+ result.nextNum = this.nextNum;
+ return result;
+}
+
+AttributePool.prototype.getAttribValue = function (num) {
+ var pair = this.numToAttrib[num];
+ if (!pair) return '';
+ return pair[1];
+};
+
+AttributePool.prototype.eachAttrib = function (func) {
+ for (var n in this.numToAttrib) {
+ var pair = this.numToAttrib[n];
+ func(pair[0], pair[1]);
+ }
+};
+
+AttributePool.prototype.toJsonable = function () {
+ return {
+ numToAttrib: this.numToAttrib,
+ nextNum: this.nextNum
+ };
+};
+
+AttributePool.prototype.fromJsonable = function (obj) {
+ this.numToAttrib = obj.numToAttrib;
+ this.nextNum = obj.nextNum;
+ this.attribToNum = {};
+ for (var n in this.numToAttrib) {
+ this.attribToNum[String(this.numToAttrib[n])] = Number(n);
+ }
+ return this;
+};
+
+if (typeof window !== "undefined") {
+ if (typeof window.ShareJS === "undefined") {
+ window.ShareJS = {};
+ }
+ window.ShareJS.AttributePool = AttributePool
+} else {
+ exports.AttributePool = AttributePool;
+ module.exports = AttributePool;
+}
+
Oops, something went wrong.