diff options
Diffstat (limited to 'trunk/etherpad/src/static/js/broadcast.js')
-rw-r--r-- | trunk/etherpad/src/static/js/broadcast.js | 607 |
1 files changed, 0 insertions, 607 deletions
diff --git a/trunk/etherpad/src/static/js/broadcast.js b/trunk/etherpad/src/static/js/broadcast.js deleted file mode 100644 index 9fa8141..0000000 --- a/trunk/etherpad/src/static/js/broadcast.js +++ /dev/null @@ -1,607 +0,0 @@ -/** - * Copyright 2009 Google Inc. - * - * 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. - */ - -// just in case... (todo: this must be somewhere else in the client code.) -if (!Array.prototype.map) -{ - Array.prototype.map = function(fun /*, thisp*/) - { - var len = this.length >>> 0; - if (typeof fun != "function") - throw new TypeError(); - - var res = new Array(len); - var thisp = arguments[1]; - for (var i = 0; i < len; i++) - { - if (i in this) - res[i] = fun.call(thisp, this[i], i, this); - } - - return res; - }; -} - -if (!Array.prototype.forEach) -{ - Array.prototype.forEach = function(fun /*, thisp*/) - { - var len = this.length >>> 0; - if (typeof fun != "function") - throw new TypeError(); - - var thisp = arguments[1]; - for (var i = 0; i < len; i++) - { - if (i in this) - fun.call(thisp, this[i], i, this); - } - }; -} - -if (!Array.prototype.indexOf) -{ - Array.prototype.indexOf = function(elt /*, from*/) - { - var len = this.length >>> 0; - - var from = Number(arguments[1]) || 0; - from = (from < 0) - ? Math.ceil(from) - : Math.floor(from); - if (from < 0) - from += len; - - for (; from < len; from++) - { - if (from in this && - this[from] === elt) - return from; - } - return -1; - }; -} - -function debugLog() { - try { - // console.log.apply(console, arguments); - } catch (e) {console.log("error printing: ",e);} -} - -function randomString() { - return "_"+Math.floor(Math.random() * 1000000); -} - -// for IE -if ($.browser.msie) { - try { - document.execCommand("BackgroundImageCache", false, true); - } catch (e) {} -} - -var userId = "hiddenUser" + randomString(); -var socketId; -var socket; - -var channelState = "DISCONNECTED"; - -var appLevelDisconnectReason = null; - -var padContents = { - currentRevision: clientVars.revNum, - currentTime : clientVars.currentTime, - currentLines: Changeset.splitTextLines(clientVars.initialStyledContents.atext.text), - currentDivs : null, // to be filled in once the dom loads - apool: (new AttribPool()).fromJsonable(clientVars.initialStyledContents.apool), - alines: Changeset.splitAttributionLines( - clientVars.initialStyledContents.atext.attribs, - clientVars.initialStyledContents.atext.text), - - // generates a jquery element containing HTML for a line - lineToElement: function(line, aline) { - var element = document.createElement("div"); - var emptyLine = (line == '\n'); - var domInfo = domline.createDomLine(! emptyLine, true); - linestylefilter.populateDomLine(line, aline, this.apool, - domInfo); - domInfo.prepareForAdd(); - element.className = domInfo.node.className; - element.innerHTML = domInfo.node.innerHTML; - element.id = Math.random(); - return $(element); - }, - - applySpliceToDivs: function(start, numRemoved, newLines) { - // remove spliced-out lines from DOM - for(var i=start; i<start+numRemoved && i<this.currentDivs.length; i++) { - debugLog("removing", this.currentDivs[i].attr('id')); - this.currentDivs[i].remove(); - } - - // remove spliced-out line divs from currentDivs array - this.currentDivs.splice(start, numRemoved); - - var newDivs = []; - for(var i=0;i<newLines.length;i++) { - newDivs.push(this.lineToElement(newLines[i], - this.alines[start+i])); - } - - // grab the div just before the first one - var startDiv = this.currentDivs[start-1] || null; - - // insert the div elements into the correct place, in the correct order - for(var i=0; i<newDivs.length; i++) { - if (startDiv) { - startDiv.after(newDivs[i]); - } - else { - $("#padcontent").prepend(newDivs[i]); - } - startDiv = newDivs[i]; - } - - // insert new divs into currentDivs array - newDivs.unshift(0); // remove 0 elements - newDivs.unshift(start); - this.currentDivs.splice.apply(this.currentDivs, newDivs); - return this; - }, - - // splice the lines - splice: function(start, numRemoved, newLinesVA) { - var newLines = Array.prototype.slice.call(arguments, 2).map( - function(s) { return s; }); - - // apply this splice to the divs - this.applySpliceToDivs(start, numRemoved, newLines); - - // call currentLines.splice, to keep the currentLines array up to date - newLines.unshift(numRemoved); - newLines.unshift(start); - this.currentLines.splice.apply(this.currentLines, arguments); - }, - // returns the contents of the specified line I - get: function(i) { - return this.currentLines[i]; - }, - // returns the number of lines in the document - length: function() { - return this.currentLines.length; - }, - - getActiveAuthors: function() { - var self = this; - var authors = []; - var seenNums = {}; - var alines = self.alines; - for(var i=0;i<alines.length;i++) { - Changeset.eachAttribNumber(alines[i], function(n) { - if (! seenNums[n]) { - seenNums[n] = true; - if (self.apool.getAttribKey(n) == 'author') { - var a = self.apool.getAttribValue(n); - if (a) { - authors.push(a); - } - } - } - }); - } - authors.sort(); - return authors; - } -}; - -function callCatchingErrors(catcher, func) { - try { - wrapRecordingErrors(catcher, func)(); - } - catch (e) { /*absorb*/ } -} - -function wrapRecordingErrors(catcher, func) { - return function() { - try { - return func.apply(this, Array.prototype.slice.call(arguments)); - } - catch (e) { - // caughtErrors.push(e); - // caughtErrorCatchers.push(catcher); - // caughtErrorTimes.push(+new Date()); - // console.dir({catcher: catcher, e: e}); - debugLog(e); // TODO(kroo): added temporary, to catch errors - throw e; - } - }; -} - -function loadedNewChangeset(changesetForward, changesetBackward, revision, timeDelta) { - var broadcasting = (BroadcastSlider.getSliderPosition() == revisionInfo.latest); - debugLog("broadcasting:", broadcasting, BroadcastSlider.getSliderPosition(), revisionInfo.latest, revision); - revisionInfo.addChangeset(revision, revision+1, changesetForward, changesetBackward, timeDelta); - BroadcastSlider.setSliderLength(revisionInfo.latest); - if(broadcasting) - applyChangeset(changesetForward, revision+1, false, timeDelta); -} - -/* - At this point, we must be certain that the changeset really does map from - the current revision to the specified revision. Any mistakes here will - cause the whole slider to get out of sync. - */ -function applyChangeset(changeset, revision, preventSliderMovement, timeDelta) { - // disable the next 'gotorevision' call handled by a timeslider update - if(!preventSliderMovement) { - goToRevisionIfEnabledCount ++; - BroadcastSlider.setSliderPosition(revision); - } - - try { - // must mutate attribution lines before text lines - Changeset.mutateAttributionLines(changeset, padContents.alines, padContents.apool); - }catch(e) { debugLog(e); } - - Changeset.mutateTextLines(changeset, padContents); - padContents.currentRevision = revision; - padContents.currentTime += timeDelta * 1000; - debugLog('Time Delta: ',timeDelta) - updateTimer(); - BroadcastSlider.setAuthors(padContents.getActiveAuthors().map(function(name) {return authorData[name];})); -} - -function updateTimer() { - var zpad = function(str, length) { - str = str+""; - while(str.length < length) - str = '0'+str; - return str; - } - var date = new Date(padContents.currentTime); - var dateFormat = function() { - var month = zpad(date.getMonth()+1,2); - var day = zpad(date.getDate(),2); - var year = (date.getFullYear()); - var hours = zpad(date.getHours(),2); - var minutes = zpad(date.getMinutes(),2); - var seconds = zpad(date.getSeconds(),2); - return ([month,'/',day,'/',year,' ',hours,':',minutes,':',seconds].join("")); - } - - $('#timer').html(dateFormat()); - - var revisionDate = [ - "Saved", - [ - "Jan", "Feb", "March", "April", "May", "June", - "July", "Aug", "Sept", "Oct", "Nov", "Dec" - ][date.getMonth()], - date.getDate()+",", - date.getFullYear() - ].join(" ") - $('#revision_date').html(revisionDate) - -} - -function goToRevision(newRevision) { - padContents.targetRevision = newRevision; - var self = this; - var path = revisionInfo.getPath(padContents.currentRevision, newRevision); - debugLog('newRev: ', padContents.currentRevision, path); - if( path.status == 'complete') { - var cs = path.changesets; - debugLog("status: complete, changesets: ",cs, "path:", path); - var changeset = cs[0]; - var timeDelta = path.times[0]; - for(var i=1; i<cs.length; i++) { - changeset = Changeset.compose(changeset, cs[i], padContents.apool); - timeDelta += path.times[i]; - } - if(changeset) - applyChangeset(changeset, path.rev, true, timeDelta); - } else if(path.status == "partial") { - debugLog('partial'); - var sliderLocation = padContents.currentRevision; - // callback is called after changeset information is pulled from server - // this may never get called, if the changeset has already been loaded - var update = function(start, end) { - // if we've called goToRevision in the time since, don't goToRevision - goToRevision(padContents.targetRevision); - }; - - // do our best with what we have... - var cs = path.changesets; - - var changeset = cs[0]; - var timeDelta = path.times[0]; - for(var i=1; i<cs.length; i++) { - changeset = Changeset.compose(changeset, cs[i], padContents.apool); - timeDelta += path.times[i]; - } - if(changeset) - applyChangeset(changeset, path.rev, true, timeDelta); - - - if(BroadcastSlider.getSliderLength() > 10000) { - var start = (Math.floor((newRevision) / 10000) * 10000); // revision 0 to 10 - changesetLoader.queueUp(start, 100); - } - - if(BroadcastSlider.getSliderLength() > 1000) { - var start = (Math.floor((newRevision) / 1000) * 1000); // (start from -1, go to 19) + 1 - changesetLoader.queueUp(start, 10); - } - - start = (Math.floor((newRevision) / 100) * 100); - - changesetLoader.queueUp(start, 1, update); - } - BroadcastSlider.setAuthors(padContents.getActiveAuthors().map(function(name) {return authorData[name];})); -} - -var changesetLoader = { - running: false, - resolved: [], - requestQueue1: [], - requestQueue2: [], - requestQueue3: [], - queueUp: function(revision, width, callback) { - if(revision < 0) revision = 0; - // if(changesetLoader.requestQueue.indexOf(revision) != -1) - // return; // already in the queue. - if(changesetLoader.resolved.indexOf(revision+"_"+width) != -1) - return; // already loaded from the server - changesetLoader.resolved.push(revision+"_"+width); - - var requestQueue = width == 1 ? changesetLoader.requestQueue3 : - width == 10 ? changesetLoader.requestQueue2 : - changesetLoader.requestQueue1; - requestQueue.push({'rev': revision, 'res': width, 'callback': callback}); - if(!changesetLoader.running) { - changesetLoader.running = true; - setTimeout(changesetLoader.loadFromQueue, 10); - } - }, - loadFromQueue: function() { - var self = changesetLoader; - var requestQueue = self.requestQueue1.length > 0 ? self.requestQueue1 : - self.requestQueue2.length > 0 ? self.requestQueue2 : - self.requestQueue3.length > 0 ? self.requestQueue3 : null; - - if(!requestQueue) { - self.running = false; - return; - } - - var request = requestQueue.pop(); - var granularity = request.res; - var callback = request.callback; - var start = request.rev; - debugLog("loadinging revision", start, "through ajax"); - $.getJSON( - "/ep/pad/changes/"+clientVars.padIdForUrl+"?s="+start + "&g="+granularity, - function(data, textStatus) { - if(textStatus !== "success") { - console.log(textStatus); - BroadcastSlider.showReconnectUI(); - } - self.handleResponse(data, start, granularity, callback); - - setTimeout(self.loadFromQueue, 10); // load the next ajax function - } - ); - }, - handleResponse: function(data, start, granularity, callback) { - debugLog("response: ", data); - var pool = (new AttribPool()).fromJsonable(data.apool); - for(var i=0; i<data.forwardsChangesets.length; i++) { - var astart = start + i * granularity - 1; // rev -1 is a blank single line - var aend = start + (i+1) * granularity - 1;// totalRevs is the most recent revision - if(aend > data.actualEndNum - 1) aend = data.actualEndNum - 1; - debugLog("adding changeset:", astart, aend); - var forwardcs = Changeset.moveOpsToNewPool(data.forwardsChangesets[i], pool, padContents.apool); - var backwardcs = Changeset.moveOpsToNewPool(data.backwardsChangesets[i], pool, padContents.apool); - revisionInfo.addChangeset(astart, aend, forwardcs, backwardcs, data.timeDeltas[i]); - } - if(callback)callback(start - 1, start + data.forwardsChangesets.length * granularity - 1); - } -}; - -function handleMessageFromServer() { - debugLog("handleMessage:", arguments); - var obj = arguments[0]['data']; - var expectedType = "COLLABROOM"; - - obj = JSON.parse(obj); - if (obj['type'] == expectedType) { - obj = obj['data']; - - if (obj['type'] == "NEW_CHANGES") { - debugLog(obj); - var changeset = Changeset.moveOpsToNewPool( - obj.changeset, (new AttribPool()).fromJsonable(obj.apool), - padContents.apool); - - var changesetBack = Changeset.moveOpsToNewPool( - obj.changesetBack, (new AttribPool()).fromJsonable(obj.apool), - padContents.apool); - - loadedNewChangeset(changeset, changesetBack, obj.newRev-1, obj.timeDelta); - } - else if (obj['type'] == "NEW_AUTHORDATA") { - var authorMap = {}; - authorMap[obj.author] = obj.data; - receiveAuthorData(authorMap); - BroadcastSlider.setAuthors(padContents.getActiveAuthors().map(function(name) {return authorData[name];})); - } else if (obj['type'] == "NEW_SAVEDREV") { - var savedRev = obj.savedRev; - BroadcastSlider.addSavedRevision(savedRev.revNum, savedRev); - } - } else { - debugLog("incorrect message type: " + obj['type'] + ", expected " + expectedType); - } -} - -function handleSocketClosed(params) { - debugLog("socket closed!", params); - socket = null; - - BroadcastSlider.showReconnectUI(); - // var reason = appLevelDisconnectReason || params.reason; - // var shouldReconnect = params.reconnect; - // if (shouldReconnect) { - // // determine if this is a tight reconnect loop due to weird connectivity problems - // // reconnectTimes.push(+new Date()); - // var TOO_MANY_RECONNECTS = 8; - // var TOO_SHORT_A_TIME_MS = 10000; - // if (reconnectTimes.length >= TOO_MANY_RECONNECTS && - // ((+new Date()) - reconnectTimes[reconnectTimes.length-TOO_MANY_RECONNECTS]) < - // TOO_SHORT_A_TIME_MS) { - // setChannelState("DISCONNECTED", "looping"); - // } - // else { - // setChannelState("RECONNECTING", reason); - // setUpSocket(); - // } - // } - // else { - // BroadcastSlider.showReconnectUI(); - // setChannelState("DISCONNECTED", reason); - // } -} - -function sendMessage(msg) { - socket.postMessage(JSON.stringify({type: "COLLABROOM", data: msg})); -} - -function setUpSocket() { - // required for Comet - if ((! $.browser.msie) && - (! ($.browser.mozilla && $.browser.version.indexOf("1.8.") == 0))) { - document.domain = document.domain; // for comet - } - - var success = false; - callCatchingErrors("setUpSocket", function() { - appLevelDisconnectReason = null; - - socketId = String(Math.floor(Math.random()*1e12)); - socket = new WebSocket(socketId); - socket.onmessage = wrapRecordingErrors("socket.onmessage", handleMessageFromServer); - socket.onclosed = wrapRecordingErrors("socket.onclosed", handleSocketClosed); - socket.onopen = wrapRecordingErrors("socket.onopen", function() { - setChannelState("CONNECTED"); - var msg = { type:"CLIENT_READY", roomType:'padview', - roomName:'padview/'+clientVars.viewId, - data: { lastRev:clientVars.revNum, - userInfo:{userId: userId} } }; - sendMessage(msg); - }); - // socket.onhiccup = wrapRecordingErrors("socket.onhiccup", handleCometHiccup); - // socket.onlogmessage = function(x) {debugLog(x); }; - socket.connect(); - success = true; - }); - if (success) { - //initialStartConnectTime = +new Date(); - } - else { - abandonConnection("initsocketfail"); - } -} - -function setChannelState(newChannelState, moreInfo) { - if (newChannelState != channelState) { - channelState = newChannelState; - // callbacks.onChannelStateChange(channelState, moreInfo); - } -} - -function abandonConnection(reason) { - if (socket) { - socket.onclosed = function() {}; - socket.onhiccup = function() {}; - socket.disconnect(); - } - socket = null; - setChannelState("DISCONNECTED", reason); -} - -window['onloadFuncts'] = []; -window.onload = function() { - window['isloaded'] = true; - window['onloadFuncts'].forEach(function(funct) { - funct(); - }); -}; - -// to start upon window load, just push a function onto this array -window['onloadFuncts'].push(setUpSocket); -window['onloadFuncts'].push(function() { - // set up the currentDivs and DOM - padContents.currentDivs = []; - $("#padcontent").html(""); - for(var i=0; i<padContents.currentLines.length; i++) { - var div = padContents.lineToElement(padContents.currentLines[i], - padContents.alines[i]); - padContents.currentDivs.push(div); - $("#padcontent").append(div); - } - debugLog(padContents.currentDivs); -}); - -// this is necessary to keep infinite loops of events firing, -// since goToRevision changes the slider position -var goToRevisionIfEnabledCount = 0; -var goToRevisionIfEnabled = function() { - if(goToRevisionIfEnabledCount > 0) { - goToRevisionIfEnabledCount --; - } else { - goToRevision.apply(goToRevision, arguments); - } -} - -BroadcastSlider.onSlider(goToRevisionIfEnabled); - -(function() { - for(var i=0; i<clientVars.initialChangesets.length; i++) { - var csgroup = clientVars.initialChangesets[i]; - var start = clientVars.initialChangesets[i].start; - var granularity = clientVars.initialChangesets[i].granularity; - debugLog("loading changest on startup: ", start, granularity, csgroup); - changesetLoader.handleResponse(csgroup, start, granularity, null); - } -})(); - -var dynamicCSS = makeCSSManager('dynamicsyntax'); -var authorData = {}; - -function receiveAuthorData(newAuthorData) { - for(var author in newAuthorData) { - var data = newAuthorData[author]; - if ((typeof data.colorId) == 'number') { - var bgcolor = clientVars.colorPalette[data.colorId]; - if (bgcolor && dynamicCSS) { - dynamicCSS.selectorStyle( - '.'+linestylefilter.getAuthorClassName(author)).backgroundColor = - bgcolor; - } - } - authorData[author] = data; - } -} - -receiveAuthorData(clientVars.historicalAuthorData); |