/** * 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 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.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 0) { goToRevisionIfEnabledCount --; } else { goToRevision.apply(goToRevision, arguments); } } BroadcastSlider.onSlider(goToRevisionIfEnabled); (function() { for(var i=0; i