diff --git a/src/node/db/AuthorManager.js b/src/node/db/AuthorManager.js index e0f569efb..c1f669ec8 100644 --- a/src/node/db/AuthorManager.js +++ b/src/node/db/AuthorManager.js @@ -22,7 +22,7 @@ var ERR = require("async-stacktrace"); var db = require("./DB").db; var customError = require("../utils/customError"); -var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString; +var randomString = require('ep_etherpad-lite/static/js/random_utils').randomString; exports.getColorPalette = function(){ return ["#ffc7c7", "#fff1c7", "#e3ffc7", "#c7ffd5", "#c7ffff", "#c7d5ff", "#e3c7ff", "#ffc7f1", "#ff8f8f", "#ffe38f", "#c7ff8f", "#8fffab", "#8fffff", "#8fabff", "#c78fff", "#ff8fe3", "#d97979", "#d9c179", "#a9d979", "#79d991", "#79d9d9", "#7991d9", "#a979d9", "#d979c1", "#d9a9a9", "#d9cda9", "#c1d9a9", "#a9d9b5", "#a9d9d9", "#a9b5d9", "#c1a9d9", "#d9a9cd", "#4c9c82", "#12d1ad", "#2d8e80", "#7485c3", "#a091c7", "#3185ab", "#6818b4", "#e6e76d", "#a42c64", "#f386e5", "#4ecc0c", "#c0c236", "#693224", "#b5de6a", "#9b88fd", "#358f9b", "#496d2f", "#e267fe", "#d23056", "#1a1a64", "#5aa335", "#d722bb", "#86dc6c", "#b5a714", "#955b6a", "#9f2985", "#4b81c8", "#3d6a5b", "#434e16", "#d16084", "#af6a0e", "#8c8bd8"]; diff --git a/src/node/db/GroupManager.js b/src/node/db/GroupManager.js index 82c14c39d..5fa8a1496 100644 --- a/src/node/db/GroupManager.js +++ b/src/node/db/GroupManager.js @@ -21,7 +21,7 @@ var ERR = require("async-stacktrace"); var customError = require("../utils/customError"); -var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString; +var randomString = require('ep_etherpad-lite/static/js/random_utils').randomString; var db = require("./DB").db; var async = require("async"); var padManager = require("./PadManager"); diff --git a/src/static/js/broadcast_slider.js b/src/static/js/broadcast_slider.js index 2299bba32..020af0543 100644 --- a/src/static/js/broadcast_slider.js +++ b/src/static/js/broadcast_slider.js @@ -22,481 +22,490 @@ // These parameters were global, now they are injected. A reference to the // Timeslider controller would probably be more appropriate. -var _ = require('./underscore'); -var padmodals = require('./pad_modals').padmodals; -function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) -{ - var BroadcastSlider; +define([ + 'ep_etherpad-lite/static/js/pad_modals', + 'underscore' +], function(padModalsModule, _) { + var exports = {}; - // Hack to ensure timeslider i18n values are in - $("[data-key='timeslider_returnToPad'] > a > span").html(html10n.get("timeslider.toolbar.returnbutton")); + var padmodals = padModalsModule.padmodals; - (function() - { // wrap this code in its own namespace - var sliderLength = 1000; - var sliderPos = 0; - var sliderActive = false; - var slidercallbacks = []; - var savedRevisions = []; - var sliderPlaying = false; - - function disableSelection(element) - { - element.onselectstart = function() - { - return false; - }; - element.unselectable = "on"; - element.style.MozUserSelect = "none"; - element.style.cursor = "default"; - } - var _callSliderCallbacks = function(newval) - { - sliderPos = newval; - for (var i = 0; i < slidercallbacks.length; i++) - { - slidercallbacks[i](newval); - } - } - - var updateSliderElements = function() - { - for (var i = 0; i < savedRevisions.length; i++) - { - var position = parseInt(savedRevisions[i].attr('pos')); - savedRevisions[i].css('left', (position * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0)) - 1); - } - $("#ui-slider-handle").css('left', sliderPos * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0)); - } - - var addSavedRevision = function(position, info) - { - var newSavedRevision = $('
'); - newSavedRevision.addClass("star"); - - newSavedRevision.attr('pos', position); - newSavedRevision.css('position', 'absolute'); - newSavedRevision.css('left', (position * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0)) - 1); - $("#timeslider-slider").append(newSavedRevision); - newSavedRevision.mouseup(function(evt) - { - BroadcastSlider.setSliderPosition(position); - }); - savedRevisions.push(newSavedRevision); - }; - - var removeSavedRevision = function(position) - { - var element = $("div.star [pos=" + position + "]"); - savedRevisions.remove(element); - element.remove(); - return element; - }; - - /* Begin small 'API' */ - - function onSlider(callback) - { - slidercallbacks.push(callback); - } - - function getSliderPosition() - { - return sliderPos; - } - - function setSliderPosition(newpos) - { - newpos = Number(newpos); - if (newpos < 0 || newpos > sliderLength) return; - if(!newpos){ - newpos = 0; // stops it from displaying NaN if newpos isn't set - } - window.location.hash = "#" + newpos; - $("#ui-slider-handle").css('left', newpos * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0)); - $("a.tlink").map(function() - { - $(this).attr('href', $(this).attr('thref').replace("%revision%", newpos)); - }); - - $("#revision_label").html(html10n.get("timeslider.version", { "version": newpos})); - - if (newpos == 0) - { - $("#leftstar").css('opacity', .5); - $("#leftstep").css('opacity', .5); - } - else - { - $("#leftstar").css('opacity', 1); - $("#leftstep").css('opacity', 1); - } - - if (newpos == sliderLength) - { - $("#rightstar").css('opacity', .5); - $("#rightstep").css('opacity', .5); - } - else - { - $("#rightstar").css('opacity', 1); - $("#rightstep").css('opacity', 1); - } - - sliderPos = newpos; - _callSliderCallbacks(newpos); - } - - function getSliderLength() - { - return sliderLength; - } - - function setSliderLength(newlength) - { - sliderLength = newlength; - updateSliderElements(); - } - - // just take over the whole slider screen with a reconnect message - - function showReconnectUI() - { - padmodals.showModal("disconnected"); - } - - // Throttle seems like overkill here... Not sure why we do it! - var fixPadHeight = _.throttle(function(){ - var height = $('#timeslider-top').height(); - $('#editorcontainerbox').css({marginTop: height}); - }, 600); - - function setAuthors(authors) - { - var authorsList = $("#authorsList"); - authorsList.empty(); - var numAnonymous = 0; - var numNamed = 0; - var colorsAnonymous = []; - _.each(authors, function(author) - { - if(author) - { - var authorColor = clientVars.colorPalette[author.colorId] || author.colorId; - if (author.name) - { - if (numNamed !== 0) authorsList.append(', '); - - $('') - .text(author.name || "unnamed") - .css('background-color', authorColor) - .addClass('author') - .appendTo(authorsList); - - numNamed++; - } - else - { - numAnonymous++; - if(authorColor) colorsAnonymous.push(authorColor); - } - } - }); - if (numAnonymous > 0) - { - var anonymousAuthorString = html10n.get("timeslider.unnamedauthors", { num: numAnonymous }); - - if (numNamed !== 0){ - authorsList.append(' + ' + anonymousAuthorString); - } else { - authorsList.append(anonymousAuthorString); - } - - if(colorsAnonymous.length > 0){ - authorsList.append(' ('); - _.each(colorsAnonymous, function(color, i){ - if( i > 0 ) authorsList.append(' '); - $(' ') - .css('background-color', color) - .addClass('author author-anonymous') - .appendTo(authorsList); - }); - authorsList.append(')'); - } - - } - if (authors.length == 0) - { - authorsList.append(html10n.get("timeslider.toolbar.authorsList")); - } - - fixPadHeight(); - } - - BroadcastSlider = { - onSlider: onSlider, - getSliderPosition: getSliderPosition, - setSliderPosition: setSliderPosition, - getSliderLength: getSliderLength, - setSliderLength: setSliderLength, - isSliderActive: function() - { - return sliderActive; - }, - playpause: playpause, - addSavedRevision: addSavedRevision, - showReconnectUI: showReconnectUI, - setAuthors: setAuthors - } - - function playButtonUpdater() - { - if (sliderPlaying) - { - if (getSliderPosition() + 1 > sliderLength) - { - $("#playpause_button_icon").toggleClass('pause'); - sliderPlaying = false; - return; - } - setSliderPosition(getSliderPosition() + 1); - - setTimeout(playButtonUpdater, 100); - } - } - - function playpause() - { - $("#playpause_button_icon").toggleClass('pause'); - - if (!sliderPlaying) - { - if (getSliderPosition() == sliderLength) setSliderPosition(0); - sliderPlaying = true; - playButtonUpdater(); - } - else - { - sliderPlaying = false; - } - } - - // assign event handlers to html UI elements after page load - //$(window).load(function () - fireWhenAllScriptsAreLoaded.push(function() - { - disableSelection($("#playpause_button")[0]); - disableSelection($("#timeslider")[0]); - - $(document).keyup(function(e) - { - // If focus is on editbar, don't do anything - var target = $(':focus'); - if($(target).parents(".toolbar").length === 1){ - return; - } - var code = -1; - if (!e) var e = window.event; - if (e.keyCode) code = e.keyCode; - else if (e.which) code = e.which; - - if (code == 37) - { // left - if (!e.shiftKey) - { - setSliderPosition(getSliderPosition() - 1); - } - else - { - var nextStar = 0; // default to first revision in document - for (var i = 0; i < savedRevisions.length; i++) - { - var pos = parseInt(savedRevisions[i].attr('pos')); - if (pos < getSliderPosition() && nextStar < pos) nextStar = pos; - } - setSliderPosition(nextStar); - } - } - else if (code == 39) - { - if (!e.shiftKey) - { - setSliderPosition(getSliderPosition() + 1); - } - else - { - var nextStar = sliderLength; // default to last revision in document - for (var i = 0; i < savedRevisions.length; i++) - { - var pos = parseInt(savedRevisions[i].attr('pos')); - if (pos > getSliderPosition() && nextStar > pos) nextStar = pos; - } - setSliderPosition(nextStar); - } - } - else if (code == 32) playpause(); - }); - - $(window).resize(function() - { - updateSliderElements(); - }); - - $("#ui-slider-bar").mousedown(function(evt) - { - $("#ui-slider-handle").css('left', (evt.clientX - $("#ui-slider-bar").offset().left)); - $("#ui-slider-handle").trigger(evt); - }); - - // Slider dragging - $("#ui-slider-handle").mousedown(function(evt) - { - this.startLoc = evt.clientX; - this.currentLoc = parseInt($(this).css('left')); - var self = this; - sliderActive = true; - $(document).mousemove(function(evt2) - { - $(self).css('pointer', 'move') - var newloc = self.currentLoc + (evt2.clientX - self.startLoc); - if (newloc < 0) newloc = 0; - if (newloc > ($("#ui-slider-bar").width() - 2)) newloc = ($("#ui-slider-bar").width() - 2); - $("#revision_label").html(html10n.get("timeslider.version", { "version": Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width() - 2))})); - $(self).css('left', newloc); - if (getSliderPosition() != Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width() - 2))) _callSliderCallbacks(Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width() - 2))) - }); - $(document).mouseup(function(evt2) - { - $(document).unbind('mousemove'); - $(document).unbind('mouseup'); - sliderActive = false; - var newloc = self.currentLoc + (evt2.clientX - self.startLoc); - if (newloc < 0) newloc = 0; - if (newloc > ($("#ui-slider-bar").width() - 2)) newloc = ($("#ui-slider-bar").width() - 2); - $(self).css('left', newloc); - // if(getSliderPosition() != Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width()-2))) - setSliderPosition(Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width() - 2))) - if(parseInt($(self).css('left')) < 2){ - $(self).css('left', '2px'); - }else{ - self.currentLoc = parseInt($(self).css('left')); - } - }); - }) - - // play/pause toggling - $("#playpause_button").mousedown(function(evt) - { - var self = this; - - // $(self).css('background-image', 'url(/static/img/crushed_button_depressed.png)'); - $(self).mouseup(function(evt2) - { - // $(self).css('background-image', 'url(/static/img/crushed_button_undepressed.png)'); - $(self).unbind('mouseup'); - BroadcastSlider.playpause(); - }); - $(document).mouseup(function(evt2) - { - // $(self).css('background-image', 'url(/static/img/crushed_button_undepressed.png)'); - $(document).unbind('mouseup'); - }); - }); - - // next/prev saved revision and changeset - $('.stepper').mousedown(function(evt) - { - var self = this; - var origcss = $(self).css('background-position'); - if (!origcss) - { - origcss = $(self).css('background-position-x') + " " + $(self).css('background-position-y'); - } - var origpos = parseInt(origcss.split(" ")[1]); - var newpos = (origpos - 43); - if (newpos < 0) newpos += 87; - - var newcss = (origcss.split(" ")[0] + " " + newpos + "px"); - if ($(self).css('opacity') != 1.0) newcss = origcss; - - $(self).css('background-position', newcss) - - $(self).mouseup(function(evt2) - { - $(self).css('background-position', origcss); - $(self).unbind('mouseup'); - $(document).unbind('mouseup'); - if ($(self).attr("id") == ("leftstep")) - { - setSliderPosition(getSliderPosition() - 1); - } - else if ($(self).attr("id") == ("rightstep")) - { - setSliderPosition(getSliderPosition() + 1); - } - else if ($(self).attr("id") == ("leftstar")) - { - var nextStar = 0; // default to first revision in document - for (var i = 0; i < savedRevisions.length; i++) - { - var pos = parseInt(savedRevisions[i].attr('pos')); - if (pos < getSliderPosition() && nextStar < pos) nextStar = pos; - } - setSliderPosition(nextStar); - } - else if ($(self).attr("id") == ("rightstar")) - { - var nextStar = sliderLength; // default to last revision in document - for (var i = 0; i < savedRevisions.length; i++) - { - var pos = parseInt(savedRevisions[i].attr('pos')); - if (pos > getSliderPosition() && nextStar > pos) nextStar = pos; - } - setSliderPosition(nextStar); - } - }); - $(document).mouseup(function(evt2) - { - $(self).css('background-position', origcss); - $(self).unbind('mouseup'); - $(document).unbind('mouseup'); - }); - }) - - if (clientVars) - { - $("#timeslider").show(); - - var startPos = clientVars.collab_client_vars.rev; - if(window.location.hash.length > 1) - { - var hashRev = Number(window.location.hash.substr(1)); - if(!isNaN(hashRev)) - { - // this is necessary because of the socket.io-event which loads the changesets - setTimeout(function() { setSliderPosition(hashRev); }, 1); - } - } - - setSliderLength(clientVars.collab_client_vars.rev); - setSliderPosition(clientVars.collab_client_vars.rev); - - _.each(clientVars.savedRevisions, function(revision) - { - addSavedRevision(revision.revNum, revision); - }) - - } - }); - })(); - - BroadcastSlider.onSlider(function(loc) + function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) { - $("#viewlatest").html(loc == BroadcastSlider.getSliderLength() ? "Viewing latest content" : "View latest content"); - }) + var BroadcastSlider; - return BroadcastSlider; -} + // Hack to ensure timeslider i18n values are in + $("[data-key='timeslider_returnToPad'] > a > span").html(html10n.get("timeslider.toolbar.returnbutton")); -exports.loadBroadcastSliderJS = loadBroadcastSliderJS; + (function() + { // wrap this code in its own namespace + var sliderLength = 1000; + var sliderPos = 0; + var sliderActive = false; + var slidercallbacks = []; + var savedRevisions = []; + var sliderPlaying = false; + + function disableSelection(element) + { + element.onselectstart = function() + { + return false; + }; + element.unselectable = "on"; + element.style.MozUserSelect = "none"; + element.style.cursor = "default"; + } + var _callSliderCallbacks = function(newval) + { + sliderPos = newval; + for (var i = 0; i < slidercallbacks.length; i++) + { + slidercallbacks[i](newval); + } + } + + var updateSliderElements = function() + { + for (var i = 0; i < savedRevisions.length; i++) + { + var position = parseInt(savedRevisions[i].attr('pos')); + savedRevisions[i].css('left', (position * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0)) - 1); + } + $("#ui-slider-handle").css('left', sliderPos * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0)); + } + + var addSavedRevision = function(position, info) + { + var newSavedRevision = $(''); + newSavedRevision.addClass("star"); + + newSavedRevision.attr('pos', position); + newSavedRevision.css('position', 'absolute'); + newSavedRevision.css('left', (position * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0)) - 1); + $("#timeslider-slider").append(newSavedRevision); + newSavedRevision.mouseup(function(evt) + { + BroadcastSlider.setSliderPosition(position); + }); + savedRevisions.push(newSavedRevision); + }; + + var removeSavedRevision = function(position) + { + var element = $("div.star [pos=" + position + "]"); + savedRevisions.remove(element); + element.remove(); + return element; + }; + + /* Begin small 'API' */ + + function onSlider(callback) + { + slidercallbacks.push(callback); + } + + function getSliderPosition() + { + return sliderPos; + } + + function setSliderPosition(newpos) + { + newpos = Number(newpos); + if (newpos < 0 || newpos > sliderLength) return; + if(!newpos){ + newpos = 0; // stops it from displaying NaN if newpos isn't set + } + window.location.hash = "#" + newpos; + $("#ui-slider-handle").css('left', newpos * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0)); + $("a.tlink").map(function() + { + $(this).attr('href', $(this).attr('thref').replace("%revision%", newpos)); + }); + + $("#revision_label").html(html10n.get("timeslider.version", { "version": newpos})); + + if (newpos == 0) + { + $("#leftstar").css('opacity', .5); + $("#leftstep").css('opacity', .5); + } + else + { + $("#leftstar").css('opacity', 1); + $("#leftstep").css('opacity', 1); + } + + if (newpos == sliderLength) + { + $("#rightstar").css('opacity', .5); + $("#rightstep").css('opacity', .5); + } + else + { + $("#rightstar").css('opacity', 1); + $("#rightstep").css('opacity', 1); + } + + sliderPos = newpos; + _callSliderCallbacks(newpos); + } + + function getSliderLength() + { + return sliderLength; + } + + function setSliderLength(newlength) + { + sliderLength = newlength; + updateSliderElements(); + } + + // just take over the whole slider screen with a reconnect message + + function showReconnectUI() + { + padmodals.showModal("disconnected"); + } + + // Throttle seems like overkill here... Not sure why we do it! + var fixPadHeight = _.throttle(function(){ + var height = $('#timeslider-top').height(); + $('#editorcontainerbox').css({marginTop: height}); + }, 600); + + function setAuthors(authors) + { + var authorsList = $("#authorsList"); + authorsList.empty(); + var numAnonymous = 0; + var numNamed = 0; + var colorsAnonymous = []; + _.each(authors, function(author) + { + if(author) + { + var authorColor = clientVars.colorPalette[author.colorId] || author.colorId; + if (author.name) + { + if (numNamed !== 0) authorsList.append(', '); + + $('') + .text(author.name || "unnamed") + .css('background-color', authorColor) + .addClass('author') + .appendTo(authorsList); + + numNamed++; + } + else + { + numAnonymous++; + if(authorColor) colorsAnonymous.push(authorColor); + } + } + }); + if (numAnonymous > 0) + { + var anonymousAuthorString = html10n.get("timeslider.unnamedauthors", { num: numAnonymous }); + + if (numNamed !== 0){ + authorsList.append(' + ' + anonymousAuthorString); + } else { + authorsList.append(anonymousAuthorString); + } + + if(colorsAnonymous.length > 0){ + authorsList.append(' ('); + _.each(colorsAnonymous, function(color, i){ + if( i > 0 ) authorsList.append(' '); + $(' ') + .css('background-color', color) + .addClass('author author-anonymous') + .appendTo(authorsList); + }); + authorsList.append(')'); + } + + } + if (authors.length == 0) + { + authorsList.append(html10n.get("timeslider.toolbar.authorsList")); + } + + fixPadHeight(); + } + + BroadcastSlider = { + onSlider: onSlider, + getSliderPosition: getSliderPosition, + setSliderPosition: setSliderPosition, + getSliderLength: getSliderLength, + setSliderLength: setSliderLength, + isSliderActive: function() + { + return sliderActive; + }, + playpause: playpause, + addSavedRevision: addSavedRevision, + showReconnectUI: showReconnectUI, + setAuthors: setAuthors + } + + function playButtonUpdater() + { + if (sliderPlaying) + { + if (getSliderPosition() + 1 > sliderLength) + { + $("#playpause_button_icon").toggleClass('pause'); + sliderPlaying = false; + return; + } + setSliderPosition(getSliderPosition() + 1); + + setTimeout(playButtonUpdater, 100); + } + } + + function playpause() + { + $("#playpause_button_icon").toggleClass('pause'); + + if (!sliderPlaying) + { + if (getSliderPosition() == sliderLength) setSliderPosition(0); + sliderPlaying = true; + playButtonUpdater(); + } + else + { + sliderPlaying = false; + } + } + + // assign event handlers to html UI elements after page load + //$(window).load(function () + fireWhenAllScriptsAreLoaded.push(function() + { + disableSelection($("#playpause_button")[0]); + disableSelection($("#timeslider")[0]); + + $(document).keyup(function(e) + { + // If focus is on editbar, don't do anything + var target = $(':focus'); + if($(target).parents(".toolbar").length === 1){ + return; + } + var code = -1; + if (!e) var e = window.event; + if (e.keyCode) code = e.keyCode; + else if (e.which) code = e.which; + + if (code == 37) + { // left + if (!e.shiftKey) + { + setSliderPosition(getSliderPosition() - 1); + } + else + { + var nextStar = 0; // default to first revision in document + for (var i = 0; i < savedRevisions.length; i++) + { + var pos = parseInt(savedRevisions[i].attr('pos')); + if (pos < getSliderPosition() && nextStar < pos) nextStar = pos; + } + setSliderPosition(nextStar); + } + } + else if (code == 39) + { + if (!e.shiftKey) + { + setSliderPosition(getSliderPosition() + 1); + } + else + { + var nextStar = sliderLength; // default to last revision in document + for (var i = 0; i < savedRevisions.length; i++) + { + var pos = parseInt(savedRevisions[i].attr('pos')); + if (pos > getSliderPosition() && nextStar > pos) nextStar = pos; + } + setSliderPosition(nextStar); + } + } + else if (code == 32) playpause(); + }); + + $(window).resize(function() + { + updateSliderElements(); + }); + + $("#ui-slider-bar").mousedown(function(evt) + { + $("#ui-slider-handle").css('left', (evt.clientX - $("#ui-slider-bar").offset().left)); + $("#ui-slider-handle").trigger(evt); + }); + + // Slider dragging + $("#ui-slider-handle").mousedown(function(evt) + { + this.startLoc = evt.clientX; + this.currentLoc = parseInt($(this).css('left')); + var self = this; + sliderActive = true; + $(document).mousemove(function(evt2) + { + $(self).css('pointer', 'move') + var newloc = self.currentLoc + (evt2.clientX - self.startLoc); + if (newloc < 0) newloc = 0; + if (newloc > ($("#ui-slider-bar").width() - 2)) newloc = ($("#ui-slider-bar").width() - 2); + $("#revision_label").html(html10n.get("timeslider.version", { "version": Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width() - 2))})); + $(self).css('left', newloc); + if (getSliderPosition() != Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width() - 2))) _callSliderCallbacks(Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width() - 2))) + }); + $(document).mouseup(function(evt2) + { + $(document).unbind('mousemove'); + $(document).unbind('mouseup'); + sliderActive = false; + var newloc = self.currentLoc + (evt2.clientX - self.startLoc); + if (newloc < 0) newloc = 0; + if (newloc > ($("#ui-slider-bar").width() - 2)) newloc = ($("#ui-slider-bar").width() - 2); + $(self).css('left', newloc); + // if(getSliderPosition() != Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width()-2))) + setSliderPosition(Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width() - 2))) + if(parseInt($(self).css('left')) < 2){ + $(self).css('left', '2px'); + }else{ + self.currentLoc = parseInt($(self).css('left')); + } + }); + }) + + // play/pause toggling + $("#playpause_button").mousedown(function(evt) + { + var self = this; + + // $(self).css('background-image', 'url(/static/img/crushed_button_depressed.png)'); + $(self).mouseup(function(evt2) + { + // $(self).css('background-image', 'url(/static/img/crushed_button_undepressed.png)'); + $(self).unbind('mouseup'); + BroadcastSlider.playpause(); + }); + $(document).mouseup(function(evt2) + { + // $(self).css('background-image', 'url(/static/img/crushed_button_undepressed.png)'); + $(document).unbind('mouseup'); + }); + }); + + // next/prev saved revision and changeset + $('.stepper').mousedown(function(evt) + { + var self = this; + var origcss = $(self).css('background-position'); + if (!origcss) + { + origcss = $(self).css('background-position-x') + " " + $(self).css('background-position-y'); + } + var origpos = parseInt(origcss.split(" ")[1]); + var newpos = (origpos - 43); + if (newpos < 0) newpos += 87; + + var newcss = (origcss.split(" ")[0] + " " + newpos + "px"); + if ($(self).css('opacity') != 1.0) newcss = origcss; + + $(self).css('background-position', newcss) + + $(self).mouseup(function(evt2) + { + $(self).css('background-position', origcss); + $(self).unbind('mouseup'); + $(document).unbind('mouseup'); + if ($(self).attr("id") == ("leftstep")) + { + setSliderPosition(getSliderPosition() - 1); + } + else if ($(self).attr("id") == ("rightstep")) + { + setSliderPosition(getSliderPosition() + 1); + } + else if ($(self).attr("id") == ("leftstar")) + { + var nextStar = 0; // default to first revision in document + for (var i = 0; i < savedRevisions.length; i++) + { + var pos = parseInt(savedRevisions[i].attr('pos')); + if (pos < getSliderPosition() && nextStar < pos) nextStar = pos; + } + setSliderPosition(nextStar); + } + else if ($(self).attr("id") == ("rightstar")) + { + var nextStar = sliderLength; // default to last revision in document + for (var i = 0; i < savedRevisions.length; i++) + { + var pos = parseInt(savedRevisions[i].attr('pos')); + if (pos > getSliderPosition() && nextStar > pos) nextStar = pos; + } + setSliderPosition(nextStar); + } + }); + $(document).mouseup(function(evt2) + { + $(self).css('background-position', origcss); + $(self).unbind('mouseup'); + $(document).unbind('mouseup'); + }); + }) + + if (clientVars) + { + $("#timeslider").show(); + + var startPos = clientVars.collab_client_vars.rev; + if(window.location.hash.length > 1) + { + var hashRev = Number(window.location.hash.substr(1)); + if(!isNaN(hashRev)) + { + // this is necessary because of the socket.io-event which loads the changesets + setTimeout(function() { setSliderPosition(hashRev); }, 1); + } + } + + setSliderLength(clientVars.collab_client_vars.rev); + setSliderPosition(clientVars.collab_client_vars.rev); + + _.each(clientVars.savedRevisions, function(revision) + { + addSavedRevision(revision.revNum, revision); + }) + + } + }); + })(); + + BroadcastSlider.onSlider(function(loc) + { + $("#viewlatest").html(loc == BroadcastSlider.getSliderLength() ? "Viewing latest content" : "View latest content"); + }) + + return BroadcastSlider; + } + + exports.loadBroadcastSliderJS = loadBroadcastSliderJS; + + return exports; +}); diff --git a/src/static/js/chat.js b/src/static/js/chat.js index 6479fd17d..2a76cbde5 100644 --- a/src/static/js/chat.js +++ b/src/static/js/chat.js @@ -14,258 +14,268 @@ * limitations under the License. */ -var padutils = require('./pad_utils').padutils; -var padcookie = require('./pad_cookie').padcookie; -var Tinycon = require('tinycon/tinycon'); -var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); -var padeditor = require('./pad_editor').padeditor; +define([ + 'ep_etherpad-lite/static/js/pad_utils', + 'ep_etherpad-lite/static/js/pad_cookie', + 'ep_etherpad-lite/static/js/pad_editor', + 'ep_etherpad-lite/static/js/pluginfw/hooks', +], function (padUtilsModule, padCookieModule, padEditorModule, hooks) { + var exports = {}; -var chat = (function() -{ - var isStuck = false; - var userAndChat = false; - var gotInitialMessages = false; - var historyPointer = 0; - var chatMentions = 0; - var self = { - show: function () - { - $("#chaticon").hide(); - $("#chatbox").show(); - $("#gritter-notice-wrapper").hide(); - self.scrollDown(); - chatMentions = 0; - Tinycon.setBubble(0); - }, - focus: function () - { - // I'm not sure why we need a setTimeout here but without it we don't get focus... - // Animation maybe? - setTimeout(function(){ - $("#chatinput").focus(); - },100); - }, - stickToScreen: function(fromInitialCall) // Make chat stick to right hand side of screen - { - chat.show(); - if(!isStuck || fromInitialCall) { // Stick it to - padcookie.setPref("chatAlwaysVisible", true); - $('#chatbox').addClass("stickyChat"); - $('#titlesticky').hide(); - $('#editorcontainer').css({"right":"192px"}); - $('.stickyChat').css("top",$('#editorcontainer').offset().top+"px"); - isStuck = true; - } else { // Unstick it - padcookie.setPref("chatAlwaysVisible", false); - $('.stickyChat').css("top", "auto"); - $('#chatbox').removeClass("stickyChat"); - $('#titlesticky').show(); - $('#editorcontainer').css({"right":"0px"}); - isStuck = false; - } - }, - chatAndUsers: function(fromInitialCall) - { - var toEnable = $('#options-chatandusers').is(":checked"); - if(toEnable || !userAndChat || fromInitialCall){ - padcookie.setPref("chatAndUsers", true); - chat.stickToScreen(true); - $('#options-stickychat').prop('checked', true) - $('#options-chatandusers').prop('checked', true) - $('#options-stickychat').prop("disabled", "disabled"); - $('#users').addClass("chatAndUsers"); - $("#chatbox").addClass("chatAndUsersChat"); - // redraw - userAndChat = true; - padeditbar.redrawHeight() - }else{ - padcookie.setPref("chatAndUsers", false); - $('#options-stickychat').prop("disabled", false); - $('#users').removeClass("chatAndUsers"); - $("#chatbox").removeClass("chatAndUsersChat"); - } - }, - hide: function () - { - // decide on hide logic based on chat window being maximized or not - if ($('#options-stickychat').prop('checked')) { - chat.stickToScreen(); - $('#options-stickychat').prop('checked', false); - } - else { - $("#chatcounter").text("0"); - $("#chaticon").show(); - $("#chatbox").hide(); - $.gritter.removeAll(); - $("#gritter-notice-wrapper").show(); - } - }, - scrollDown: function() - { - if($('#chatbox').css("display") != "none"){ - if(!self.lastMessage || !self.lastMessage.position() || self.lastMessage.position().top < $('#chattext').height()) { - // if we use a slow animate here we can have a race condition when a users focus can not be moved away - // from the last message recieved. - $('#chattext').animate({scrollTop: $('#chattext')[0].scrollHeight}, { duration: 400, queue: false }); - self.lastMessage = $('#chattext > p').eq(-1); - } - } - }, - send: function() - { - var text = $("#chatinput").val(); - if(text.replace(/\s+/,'').length == 0) - return; - this._pad.collabClient.sendMessage({"type": "CHAT_MESSAGE", "text": text}); - $("#chatinput").val(""); - }, - addMessage: function(msg, increment, isHistoryAdd) - { - //correct the time - msg.time += this._pad.clientTimeOffset; - - //create the time string - var minutes = "" + new Date(msg.time).getMinutes(); - var hours = "" + new Date(msg.time).getHours(); - if(minutes.length == 1) - minutes = "0" + minutes ; - if(hours.length == 1) - hours = "0" + hours ; - var timeStr = hours + ":" + minutes; - - //create the authorclass - var authorClass = "author-" + msg.userId.replace(/[^a-y0-9]/g, function(c) - { - if (c == ".") return "-"; - return 'z' + c.charCodeAt(0) + 'z'; - }); + var padutils = padUtilsModule.padutils; + var padcookie = padCookieModule.padcookie; + var padeditor = padEditorModule.padeditor; - var text = padutils.escapeHtmlWithClickableLinks(msg.text, "_blank"); + var Tinycon = window.requireKernel('tinycon/tinycon'); - var authorName = msg.userName == null ? _('pad.userlist.unnamed') : padutils.escapeHtml(msg.userName); - - // the hook args - var ctx = { - "authorName" : authorName, - "author" : msg.userId, - "text" : text, - "sticky" : false, - "timestamp" : msg.time, - "timeStr" : timeStr - } - - // is the users focus already in the chatbox? - var alreadyFocused = $("#chatinput").is(":focus"); - - // does the user already have the chatbox open? - var chatOpen = $("#chatbox").is(":visible"); - - // does this message contain this user's name? (is the curretn user mentioned?) - var myName = $('#myusernameedit').val(); - var wasMentioned = (text.toLowerCase().indexOf(myName.toLowerCase()) !== -1 && myName != "undefined"); - - if(wasMentioned && !alreadyFocused && !isHistoryAdd && !chatOpen) - { // If the user was mentioned show for twice as long and flash the browser window - chatMentions++; - Tinycon.setBubble(chatMentions); - ctx.sticky = true; - } - - // Call chat message hook - hooks.aCallAll("chatNewMessage", ctx, function() { - - var html = " "; - if(isHistoryAdd) - $(html).insertAfter('#chatloadmessagesbutton'); - else - $("#chattext").append(html); - - //should we increment the counter?? - if(increment && !isHistoryAdd) - { - // Update the counter of unread messages - var count = Number($("#chatcounter").text()); - count++; - $("#chatcounter").text(count); - - if(!chatOpen) { - $.gritter.add({ - // (string | mandatory) the heading of the notification - title: ctx.authorName, - // (string | mandatory) the text inside the notification - text: ctx.text, - // (bool | optional) if you want it to fade out on its own or just sit there - sticky: ctx.sticky, - // (int | optional) the time you want it to be alive for before fading out - time: '4000' - }); - } - } - }); - - // Clear the chat mentions when the user clicks on the chat input box - $('#chatinput').click(function(){ + var chat = (function() + { + var isStuck = false; + var userAndChat = false; + var gotInitialMessages = false; + var historyPointer = 0; + var chatMentions = 0; + var self = { + show: function () + { + $("#chaticon").hide(); + $("#chatbox").show(); + $("#gritter-notice-wrapper").hide(); + self.scrollDown(); chatMentions = 0; Tinycon.setBubble(0); - }); - if(!isHistoryAdd) - self.scrollDown(); - }, - init: function(pad) - { - this._pad = pad; - $("#chatinput").keyup(function(evt) + }, + focus: function () { - // If the event is Alt C or Escape & we're already in the chat menu - // Send the users focus back to the pad - if((evt.altKey == true && evt.which === 67) || evt.which === 27){ - // If we're in chat already.. - $(':focus').blur(); // required to do not try to remove! - padeditor.ace.focus(); // Sends focus back to pad - } - }); - - $('body:not(#chatinput)').on("keydown", function(evt){ - if (evt.altKey && evt.which == 67){ - // Alt c focuses on the Chat window - $(this).blur(); - parent.parent.chat.show(); - parent.parent.chat.focus(); - evt.preventDefault(); - } - }); - - $("#chatinput").keypress(function(evt){ - //if the user typed enter, fire the send - if(evt.which == 13 || evt.which == 10) - { - evt.preventDefault(); - self.send(); - } - }); - - // initial messages are loaded in pad.js' _afterHandshake - - $("#chatcounter").text(0); - $("#chatloadmessagesbutton").click(function() + // I'm not sure why we need a setTimeout here but without it we don't get focus... + // Animation maybe? + setTimeout(function(){ + $("#chatinput").focus(); + },100); + }, + stickToScreen: function(fromInitialCall) // Make chat stick to right hand side of screen { - var start = Math.max(self.historyPointer - 20, 0); - var end = self.historyPointer; - - if(start == end) // nothing to load + chat.show(); + if(!isStuck || fromInitialCall) { // Stick it to + padcookie.setPref("chatAlwaysVisible", true); + $('#chatbox').addClass("stickyChat"); + $('#titlesticky').hide(); + $('#editorcontainer').css({"right":"192px"}); + $('.stickyChat').css("top",$('#editorcontainer').offset().top+"px"); + isStuck = true; + } else { // Unstick it + padcookie.setPref("chatAlwaysVisible", false); + $('.stickyChat').css("top", "auto"); + $('#chatbox').removeClass("stickyChat"); + $('#titlesticky').show(); + $('#editorcontainer').css({"right":"0px"}); + isStuck = false; + } + }, + chatAndUsers: function(fromInitialCall) + { + var toEnable = $('#options-chatandusers').is(":checked"); + if(toEnable || !userAndChat || fromInitialCall){ + padcookie.setPref("chatAndUsers", true); + chat.stickToScreen(true); + $('#options-stickychat').prop('checked', true) + $('#options-chatandusers').prop('checked', true) + $('#options-stickychat').prop("disabled", "disabled"); + $('#users').addClass("chatAndUsers"); + $("#chatbox").addClass("chatAndUsersChat"); + // redraw + userAndChat = true; + padeditbar.redrawHeight() + }else{ + padcookie.setPref("chatAndUsers", false); + $('#options-stickychat').prop("disabled", false); + $('#users').removeClass("chatAndUsers"); + $("#chatbox").removeClass("chatAndUsersChat"); + } + }, + hide: function () + { + // decide on hide logic based on chat window being maximized or not + if ($('#options-stickychat').prop('checked')) { + chat.stickToScreen(); + $('#options-stickychat').prop('checked', false); + } + else { + $("#chatcounter").text("0"); + $("#chaticon").show(); + $("#chatbox").hide(); + $.gritter.removeAll(); + $("#gritter-notice-wrapper").show(); + } + }, + scrollDown: function() + { + if($('#chatbox').css("display") != "none"){ + if(!self.lastMessage || !self.lastMessage.position() || self.lastMessage.position().top < $('#chattext').height()) { + // if we use a slow animate here we can have a race condition when a users focus can not be moved away + // from the last message recieved. + $('#chattext').animate({scrollTop: $('#chattext')[0].scrollHeight}, { duration: 400, queue: false }); + self.lastMessage = $('#chattext > p').eq(-1); + } + } + }, + send: function() + { + var text = $("#chatinput").val(); + if(text.replace(/\s+/,'').length == 0) return; + this._pad.collabClient.sendMessage({"type": "CHAT_MESSAGE", "text": text}); + $("#chatinput").val(""); + }, + addMessage: function(msg, increment, isHistoryAdd) + { + //correct the time + msg.time += this._pad.clientTimeOffset; - $("#chatloadmessagesbutton").css("display", "none"); - $("#chatloadmessagesball").css("display", "block"); + //create the time string + var minutes = "" + new Date(msg.time).getMinutes(); + var hours = "" + new Date(msg.time).getHours(); + if(minutes.length == 1) + minutes = "0" + minutes ; + if(hours.length == 1) + hours = "0" + hours ; + var timeStr = hours + ":" + minutes; - pad.collabClient.sendMessage({"type": "GET_CHAT_MESSAGES", "start": start, "end": end}); - self.historyPointer = start; - }); + //create the authorclass + var authorClass = "author-" + msg.userId.replace(/[^a-y0-9]/g, function(c) + { + if (c == ".") return "-"; + return 'z' + c.charCodeAt(0) + 'z'; + }); + + var text = padutils.escapeHtmlWithClickableLinks(msg.text, "_blank"); + + var authorName = msg.userName == null ? _('pad.userlist.unnamed') : padutils.escapeHtml(msg.userName); + + // the hook args + var ctx = { + "authorName" : authorName, + "author" : msg.userId, + "text" : text, + "sticky" : false, + "timestamp" : msg.time, + "timeStr" : timeStr + } + + // is the users focus already in the chatbox? + var alreadyFocused = $("#chatinput").is(":focus"); + + // does the user already have the chatbox open? + var chatOpen = $("#chatbox").is(":visible"); + + // does this message contain this user's name? (is the curretn user mentioned?) + var myName = $('#myusernameedit').val(); + var wasMentioned = (text.toLowerCase().indexOf(myName.toLowerCase()) !== -1 && myName != "undefined"); + + if(wasMentioned && !alreadyFocused && !isHistoryAdd && !chatOpen) + { // If the user was mentioned show for twice as long and flash the browser window + chatMentions++; + Tinycon.setBubble(chatMentions); + ctx.sticky = true; + } + + // Call chat message hook + hooks.aCallAll("chatNewMessage", ctx, function() { + + var html = " "; + if(isHistoryAdd) + $(html).insertAfter('#chatloadmessagesbutton'); + else + $("#chattext").append(html); + + //should we increment the counter?? + if(increment && !isHistoryAdd) + { + // Update the counter of unread messages + var count = Number($("#chatcounter").text()); + count++; + $("#chatcounter").text(count); + + if(!chatOpen) { + $.gritter.add({ + // (string | mandatory) the heading of the notification + title: ctx.authorName, + // (string | mandatory) the text inside the notification + text: ctx.text, + // (bool | optional) if you want it to fade out on its own or just sit there + sticky: ctx.sticky, + // (int | optional) the time you want it to be alive for before fading out + time: '4000' + }); + } + } + }); + + // Clear the chat mentions when the user clicks on the chat input box + $('#chatinput').click(function(){ + chatMentions = 0; + Tinycon.setBubble(0); + }); + if(!isHistoryAdd) + self.scrollDown(); + }, + init: function(pad) + { + this._pad = pad; + $("#chatinput").keyup(function(evt) + { + // If the event is Alt C or Escape & we're already in the chat menu + // Send the users focus back to the pad + if((evt.altKey == true && evt.which === 67) || evt.which === 27){ + // If we're in chat already.. + $(':focus').blur(); // required to do not try to remove! + padeditor.ace.focus(); // Sends focus back to pad + } + }); + + $('body:not(#chatinput)').on("keydown", function(evt){ + if (evt.altKey && evt.which == 67){ + // Alt c focuses on the Chat window + $(this).blur(); + parent.parent.chat.show(); + parent.parent.chat.focus(); + evt.preventDefault(); + } + }); + + $("#chatinput").keypress(function(evt){ + //if the user typed enter, fire the send + if(evt.which == 13 || evt.which == 10) + { + evt.preventDefault(); + self.send(); + } + }); + + // initial messages are loaded in pad.js' _afterHandshake + + $("#chatcounter").text(0); + $("#chatloadmessagesbutton").click(function() + { + var start = Math.max(self.historyPointer - 20, 0); + var end = self.historyPointer; + + if(start == end) // nothing to load + return; + + $("#chatloadmessagesbutton").css("display", "none"); + $("#chatloadmessagesball").css("display", "block"); + + pad.collabClient.sendMessage({"type": "GET_CHAT_MESSAGES", "start": start, "end": end}); + self.historyPointer = start; + }); + } } - } - return self; -}()); + return self; + }()); -exports.chat = chat; + exports.chat = chat; + return exports; +}); diff --git a/src/static/js/collab_client.js b/src/static/js/collab_client.js index b1bc0c53c..a3257dfaf 100644 --- a/src/static/js/collab_client.js +++ b/src/static/js/collab_client.js @@ -20,641 +20,649 @@ * limitations under the License. */ -var chat = require('./chat').chat; -var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); +define([ + 'ep_etherpad-lite/static/js/pluginfw/hooks', + 'ep_etherpad-lite/static/js/chat' +], function(hooks, chatModule) { + var exports = {}; -// Dependency fill on init. This exists for `pad.socket` only. -// TODO: bind directly to the socket. -var pad = undefined; -function getSocket() { - return pad && pad.socket; -} + var chat = chatModule.chat; -/** Call this when the document is ready, and a new Ace2Editor() has been created and inited. - ACE's ready callback does not need to have fired yet. - "serverVars" are from calling doc.getCollabClientVars() on the server. */ -function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) -{ - var editor = ace2editor; - pad = _pad; // Inject pad to avoid a circular dependency. - - var rev = serverVars.rev; - var padId = serverVars.padId; - - var state = "IDLE"; - var stateMessage; - var channelState = "CONNECTING"; - var appLevelDisconnectReason = null; - - var lastCommitTime = 0; - var initialStartConnectTime = 0; - - var userId = initialUserInfo.userId; - //var socket; - var userSet = {}; // userId -> userInfo - userSet[userId] = initialUserInfo; - - var caughtErrors = []; - var caughtErrorCatchers = []; - var caughtErrorTimes = []; - var debugMessages = []; - var msgQueue = []; - - tellAceAboutHistoricalAuthors(serverVars.historicalAuthorData); - tellAceActiveAuthorInfo(initialUserInfo); - - var callbacks = { - onUserJoin: function() - {}, - onUserLeave: function() - {}, - onUpdateUserInfo: function() - {}, - onChannelStateChange: function() - {}, - onClientMessage: function() - {}, - onInternalAction: function() - {}, - onConnectionTrouble: function() - {}, - onServerMessage: function() - {} - }; - if (browser.firefox) - { - // Prevent "escape" from taking effect and canceling a comet connection; - // doesn't work if focus is on an iframe. - $(window).bind("keydown", function(evt) - { - if (evt.which == 27) - { - evt.preventDefault() - } - }); + // Dependency fill on init. This exists for `pad.socket` only. + // TODO: bind directly to the socket. + var pad = undefined; + function getSocket() { + return pad && pad.socket; } - editor.setProperty("userAuthor", userId); - editor.setBaseAttributedText(serverVars.initialAttributedText, serverVars.apool); - editor.setUserChangeNotificationCallback(wrapRecordingErrors("handleUserChanges", handleUserChanges)); - - function dmesg(str) + /** Call this when the document is ready, and a new Ace2Editor() has been created and inited. + ACE's ready callback does not need to have fired yet. + "serverVars" are from calling doc.getCollabClientVars() on the server. */ + function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) { - if (typeof window.ajlog == "string") window.ajlog += str + '\n'; - debugMessages.push(str); - } + var editor = ace2editor; + pad = _pad; // Inject pad to avoid a circular dependency. - function handleUserChanges() - { - if (editor.getInInternationalComposition()) return; - if ((!getSocket()) || channelState == "CONNECTING") + var rev = serverVars.rev; + var padId = serverVars.padId; + + var state = "IDLE"; + var stateMessage; + var channelState = "CONNECTING"; + var appLevelDisconnectReason = null; + + var lastCommitTime = 0; + var initialStartConnectTime = 0; + + var userId = initialUserInfo.userId; + //var socket; + var userSet = {}; // userId -> userInfo + userSet[userId] = initialUserInfo; + + var caughtErrors = []; + var caughtErrorCatchers = []; + var caughtErrorTimes = []; + var debugMessages = []; + var msgQueue = []; + + tellAceAboutHistoricalAuthors(serverVars.historicalAuthorData); + tellAceActiveAuthorInfo(initialUserInfo); + + var callbacks = { + onUserJoin: function() + {}, + onUserLeave: function() + {}, + onUpdateUserInfo: function() + {}, + onChannelStateChange: function() + {}, + onClientMessage: function() + {}, + onInternalAction: function() + {}, + onConnectionTrouble: function() + {}, + onServerMessage: function() + {} + }; + if (browser.firefox) { - if (channelState == "CONNECTING" && (((+new Date()) - initialStartConnectTime) > 20000)) + // Prevent "escape" from taking effect and canceling a comet connection; + // doesn't work if focus is on an iframe. + $(window).bind("keydown", function(evt) { - setChannelState("DISCONNECTED", "initsocketfail"); - } - else - { - // check again in a bit - setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), 1000); - } - return; + if (evt.which == 27) + { + evt.preventDefault() + } + }); } - var t = (+new Date()); + editor.setProperty("userAuthor", userId); + editor.setBaseAttributedText(serverVars.initialAttributedText, serverVars.apool); + editor.setUserChangeNotificationCallback(wrapRecordingErrors("handleUserChanges", handleUserChanges)); - if (state != "IDLE") + function dmesg(str) { - if (state == "COMMITTING" && msgQueue.length == 0 && (t - lastCommitTime) > 20000) + if (typeof window.ajlog == "string") window.ajlog += str + '\n'; + debugMessages.push(str); + } + + function handleUserChanges() + { + if (editor.getInInternationalComposition()) return; + if ((!getSocket()) || channelState == "CONNECTING") { - // a commit is taking too long - setChannelState("DISCONNECTED", "slowcommit"); + if (channelState == "CONNECTING" && (((+new Date()) - initialStartConnectTime) > 20000)) + { + setChannelState("DISCONNECTED", "initsocketfail"); + } + else + { + // check again in a bit + setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), 1000); + } + return; } - else if (state == "COMMITTING" && msgQueue.length == 0 && (t - lastCommitTime) > 5000) + + var t = (+new Date()); + + if (state != "IDLE") { - callbacks.onConnectionTrouble("SLOW"); + if (state == "COMMITTING" && msgQueue.length == 0 && (t - lastCommitTime) > 20000) + { + // a commit is taking too long + setChannelState("DISCONNECTED", "slowcommit"); + } + else if (state == "COMMITTING" && msgQueue.length == 0 && (t - lastCommitTime) > 5000) + { + callbacks.onConnectionTrouble("SLOW"); + } + else + { + // run again in a few seconds, to detect a disconnect + setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), 3000); + } + return; } - else + + var earliestCommit = lastCommitTime + 500; + if (t < earliestCommit) + { + setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), earliestCommit - t); + return; + } + + // apply msgQueue changeset. + if (msgQueue.length != 0) { + var msg; + while (msg = msgQueue.shift()) { + var newRev = msg.newRev; + rev=newRev; + if (msg.type == "ACCEPT_COMMIT") + { + editor.applyPreparedChangesetToBase(); + setStateIdle(); + callCatchingErrors("onInternalAction", function() + { + callbacks.onInternalAction("commitAcceptedByServer"); + }); + callCatchingErrors("onConnectionTrouble", function() + { + callbacks.onConnectionTrouble("OK"); + }); + handleUserChanges(); + } + else if (msg.type == "NEW_CHANGES") + { + var changeset = msg.changeset; + var author = (msg.author || ''); + var apool = msg.apool; + + editor.applyChangesToBase(changeset, author, apool); + } + } + } + + var sentMessage = false; + var userChangesData = editor.prepareUserChangeset(); + if (userChangesData.changeset) + { + lastCommitTime = t; + state = "COMMITTING"; + stateMessage = { + type: "USER_CHANGES", + baseRev: rev, + changeset: userChangesData.changeset, + apool: userChangesData.apool + }; + sendMessage(stateMessage); + sentMessage = true; + callbacks.onInternalAction("commitPerformed"); + } + + if (sentMessage) { // run again in a few seconds, to detect a disconnect setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), 3000); } - return; } - var earliestCommit = lastCommitTime + 500; - if (t < earliestCommit) + function setUpSocket() { - setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), earliestCommit - t); - return; + hiccupCount = 0; + setChannelState("CONNECTED"); + doDeferredActions(); + + initialStartConnectTime = +new Date(); } - // apply msgQueue changeset. - if (msgQueue.length != 0) { - var msg; - while (msg = msgQueue.shift()) { - var newRev = msg.newRev; - rev=newRev; - if (msg.type == "ACCEPT_COMMIT") - { - editor.applyPreparedChangesetToBase(); - setStateIdle(); - callCatchingErrors("onInternalAction", function() - { - callbacks.onInternalAction("commitAcceptedByServer"); - }); - callCatchingErrors("onConnectionTrouble", function() - { - callbacks.onConnectionTrouble("OK"); - }); - handleUserChanges(); - } - else if (msg.type == "NEW_CHANGES") - { - var changeset = msg.changeset; - var author = (msg.author || ''); - var apool = msg.apool; + var hiccupCount = 0; - editor.applyChangesToBase(changeset, author, apool); - } - } - } - - var sentMessage = false; - var userChangesData = editor.prepareUserChangeset(); - if (userChangesData.changeset) + function sendMessage(msg) { - lastCommitTime = t; - state = "COMMITTING"; - stateMessage = { - type: "USER_CHANGES", - baseRev: rev, - changeset: userChangesData.changeset, - apool: userChangesData.apool + getSocket().json.send( + { + type: "COLLABROOM", + component: "pad", + data: msg + }); + } + + 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}); + throw e; + } }; - sendMessage(stateMessage); - sentMessage = true; - callbacks.onInternalAction("commitPerformed"); } - if (sentMessage) - { - // run again in a few seconds, to detect a disconnect - setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), 3000); - } - } - - function setUpSocket() - { - hiccupCount = 0; - setChannelState("CONNECTED"); - doDeferredActions(); - - initialStartConnectTime = +new Date(); - } - - var hiccupCount = 0; - - function sendMessage(msg) - { - getSocket().json.send( - { - type: "COLLABROOM", - component: "pad", - data: msg - }); - } - - function wrapRecordingErrors(catcher, func) - { - return function() + function callCatchingErrors(catcher, func) { try { - return func.apply(this, Array.prototype.slice.call(arguments)); + wrapRecordingErrors(catcher, func)(); } catch (e) - { - caughtErrors.push(e); - caughtErrorCatchers.push(catcher); - caughtErrorTimes.push(+new Date()); - //console.dir({catcher: catcher, e: e}); - throw e; + { /*absorb*/ } - }; - } - - function callCatchingErrors(catcher, func) - { - try - { - wrapRecordingErrors(catcher, func)(); } - catch (e) - { /*absorb*/ - } - } - function handleMessageFromServer(evt) - { - if (window.console) console.log(evt); - - if (!getSocket()) return; - if (!evt.data) return; - var wrapper = evt; - if (wrapper.type != "COLLABROOM" && wrapper.type != "CUSTOM") return; - var msg = wrapper.data; - - if (msg.type == "NEW_CHANGES") + function handleMessageFromServer(evt) { - var newRev = msg.newRev; - var changeset = msg.changeset; - var author = (msg.author || ''); - var apool = msg.apool; + if (window.console) console.log(evt); - // When inInternationalComposition, msg pushed msgQueue. - if (msgQueue.length > 0 || editor.getInInternationalComposition()) { - if (msgQueue.length > 0) var oldRev = msgQueue[msgQueue.length - 1].newRev; - else oldRev = rev; + if (!getSocket()) return; + if (!evt.data) return; + var wrapper = evt; + if (wrapper.type != "COLLABROOM" && wrapper.type != "CUSTOM") return; + var msg = wrapper.data; - if (newRev != (oldRev + 1)) + if (msg.type == "NEW_CHANGES") + { + var newRev = msg.newRev; + var changeset = msg.changeset; + var author = (msg.author || ''); + var apool = msg.apool; + + // When inInternationalComposition, msg pushed msgQueue. + if (msgQueue.length > 0 || editor.getInInternationalComposition()) { + if (msgQueue.length > 0) var oldRev = msgQueue[msgQueue.length - 1].newRev; + else oldRev = rev; + + if (newRev != (oldRev + 1)) + { + parent.parent.console.warn("bad message revision on NEW_CHANGES: " + newRev + " not " + (oldRev + 1)); + // setChannelState("DISCONNECTED", "badmessage_newchanges"); + return; + } + msgQueue.push(msg); + return; + } + + if (newRev != (rev + 1)) { - parent.parent.console.warn("bad message revision on NEW_CHANGES: " + newRev + " not " + (oldRev + 1)); + parent.parent.console.warn("bad message revision on NEW_CHANGES: " + newRev + " not " + (rev + 1)); // setChannelState("DISCONNECTED", "badmessage_newchanges"); return; } - msgQueue.push(msg); - return; + rev = newRev; + editor.applyChangesToBase(changeset, author, apool); } - - if (newRev != (rev + 1)) + else if (msg.type == "ACCEPT_COMMIT") { - parent.parent.console.warn("bad message revision on NEW_CHANGES: " + newRev + " not " + (rev + 1)); - // setChannelState("DISCONNECTED", "badmessage_newchanges"); - return; - } - rev = newRev; - editor.applyChangesToBase(changeset, author, apool); - } - else if (msg.type == "ACCEPT_COMMIT") - { - var newRev = msg.newRev; - if (msgQueue.length > 0) - { - if (newRev != (msgQueue[msgQueue.length - 1].newRev + 1)) + var newRev = msg.newRev; + if (msgQueue.length > 0) { - parent.parent.console.warn("bad message revision on ACCEPT_COMMIT: " + newRev + " not " + (msgQueue[msgQueue.length - 1][0] + 1)); + if (newRev != (msgQueue[msgQueue.length - 1].newRev + 1)) + { + parent.parent.console.warn("bad message revision on ACCEPT_COMMIT: " + newRev + " not " + (msgQueue[msgQueue.length - 1][0] + 1)); + // setChannelState("DISCONNECTED", "badmessage_acceptcommit"); + return; + } + msgQueue.push(msg); + return; + } + + if (newRev != (rev + 1)) + { + parent.parent.console.warn("bad message revision on ACCEPT_COMMIT: " + newRev + " not " + (rev + 1)); // setChannelState("DISCONNECTED", "badmessage_acceptcommit"); return; } - msgQueue.push(msg); - return; - } - - if (newRev != (rev + 1)) - { - parent.parent.console.warn("bad message revision on ACCEPT_COMMIT: " + newRev + " not " + (rev + 1)); - // setChannelState("DISCONNECTED", "badmessage_acceptcommit"); - return; - } - rev = newRev; - editor.applyPreparedChangesetToBase(); - setStateIdle(); - callCatchingErrors("onInternalAction", function() - { - callbacks.onInternalAction("commitAcceptedByServer"); - }); - callCatchingErrors("onConnectionTrouble", function() - { - callbacks.onConnectionTrouble("OK"); - }); - handleUserChanges(); - } - else if (msg.type == "NO_COMMIT_PENDING") - { - if (state == "COMMITTING") - { - // server missed our commit message; abort that commit + rev = newRev; + editor.applyPreparedChangesetToBase(); setStateIdle(); + callCatchingErrors("onInternalAction", function() + { + callbacks.onInternalAction("commitAcceptedByServer"); + }); + callCatchingErrors("onConnectionTrouble", function() + { + callbacks.onConnectionTrouble("OK"); + }); handleUserChanges(); } - } - else if (msg.type == "USER_NEWINFO") - { - var userInfo = msg.userInfo; - var id = userInfo.userId; - - // Avoid a race condition when setting colors. If our color was set by a - // query param, ignore our own "new user" message's color value. - if (id === initialUserInfo.userId && initialUserInfo.globalUserColor) + else if (msg.type == "NO_COMMIT_PENDING") { - msg.userInfo.colorId = initialUserInfo.globalUserColor; - } - - - if (userSet[id]) - { - userSet[id] = userInfo; - callbacks.onUpdateUserInfo(userInfo); - } - else - { - userSet[id] = userInfo; - callbacks.onUserJoin(userInfo); - } - tellAceActiveAuthorInfo(userInfo); - } - else if (msg.type == "USER_LEAVE") - { - var userInfo = msg.userInfo; - var id = userInfo.userId; - if (userSet[id]) - { - delete userSet[userInfo.userId]; - fadeAceAuthorInfo(userInfo); - callbacks.onUserLeave(userInfo); - } - } - - else if (msg.type == "DISCONNECT_REASON") - { - appLevelDisconnectReason = msg.reason; - } - else if (msg.type == "CLIENT_MESSAGE") - { - callbacks.onClientMessage(msg.payload); - } - else if (msg.type == "CHAT_MESSAGE") - { - chat.addMessage(msg, true, false); - } - else if (msg.type == "CHAT_MESSAGES") - { - for(var i = msg.messages.length - 1; i >= 0; i--) - { - chat.addMessage(msg.messages[i], true, true); - } - if(!chat.gotInitalMessages) - { - chat.scrollDown(); - chat.gotInitalMessages = true; - chat.historyPointer = clientVars.chatHead - msg.messages.length; - } - - // messages are loaded, so hide the loading-ball - $("#chatloadmessagesball").css("display", "none"); - - // there are less than 100 messages or we reached the top - if(chat.historyPointer <= 0) - $("#chatloadmessagesbutton").css("display", "none"); - else // there are still more messages, re-show the load-button - $("#chatloadmessagesbutton").css("display", "block"); - } - else if (msg.type == "SERVER_MESSAGE") - { - callbacks.onServerMessage(msg.payload); - } - hooks.callAll('handleClientMessage_' + msg.type, {payload: msg.payload}); - } - - function updateUserInfo(userInfo) - { - userInfo.userId = userId; - userSet[userId] = userInfo; - tellAceActiveAuthorInfo(userInfo); - if (!getSocket()) return; - sendMessage( - { - type: "USERINFO_UPDATE", - userInfo: userInfo - }); - } - - function tellAceActiveAuthorInfo(userInfo) - { - tellAceAuthorInfo(userInfo.userId, userInfo.colorId); - } - - function tellAceAuthorInfo(userId, colorId, inactive) - { - if(typeof colorId == "number") - { - colorId = clientVars.colorPalette[colorId]; - } - - var cssColor = colorId; - if (inactive) - { - editor.setAuthorInfo(userId, { - bgcolor: cssColor, - fade: 0.5 - }); - } - else - { - editor.setAuthorInfo(userId, { - bgcolor: cssColor - }); - } - } - - function fadeAceAuthorInfo(userInfo) - { - tellAceAuthorInfo(userInfo.userId, userInfo.colorId, true); - } - - function getConnectedUsers() - { - return valuesArray(userSet); - } - - function tellAceAboutHistoricalAuthors(hadata) - { - for (var author in hadata) - { - var data = hadata[author]; - if (!userSet[author]) - { - tellAceAuthorInfo(author, data.colorId, true); - } - } - } - - function setChannelState(newChannelState, moreInfo) - { - if (newChannelState != channelState) - { - channelState = newChannelState; - callbacks.onChannelStateChange(channelState, moreInfo); - } - } - - function valuesArray(obj) - { - var array = []; - $.each(obj, function(k, v) - { - array.push(v); - }); - return array; - } - - // We need to present a working interface even before the socket - // is connected for the first time. - var deferredActions = []; - - function defer(func, tag) - { - return function() - { - var that = this; - var args = arguments; - - function action() - { - func.apply(that, args); - } - action.tag = tag; - if (channelState == "CONNECTING") - { - deferredActions.push(action); - } - else - { - action(); - } - } - } - - function doDeferredActions(tag) - { - var newArray = []; - for (var i = 0; i < deferredActions.length; i++) - { - var a = deferredActions[i]; - if ((!tag) || (tag == a.tag)) - { - a(); - } - else - { - newArray.push(a); - } - } - deferredActions = newArray; - } - - function sendClientMessage(msg) - { - sendMessage( - { - type: "CLIENT_MESSAGE", - payload: msg - }); - } - - function getCurrentRevisionNumber() - { - return rev; - } - - function getMissedChanges() - { - var obj = {}; - obj.userInfo = userSet[userId]; - obj.baseRev = rev; - if (state == "COMMITTING" && stateMessage) - { - obj.committedChangeset = stateMessage.changeset; - obj.committedChangesetAPool = stateMessage.apool; - editor.applyPreparedChangesetToBase(); - } - var userChangesData = editor.prepareUserChangeset(); - if (userChangesData.changeset) - { - obj.furtherChangeset = userChangesData.changeset; - obj.furtherChangesetAPool = userChangesData.apool; - } - return obj; - } - - function setStateIdle() - { - state = "IDLE"; - callbacks.onInternalAction("newlyIdle"); - schedulePerhapsCallIdleFuncs(); - } - - function callWhenNotCommitting(func) - { - idleFuncs.push(func); - schedulePerhapsCallIdleFuncs(); - } - - var idleFuncs = []; - - function schedulePerhapsCallIdleFuncs() - { - setTimeout(function() - { - if (state == "IDLE") - { - while (idleFuncs.length > 0) + if (state == "COMMITTING") { - var f = idleFuncs.shift(); - f(); + // server missed our commit message; abort that commit + setStateIdle(); + handleUserChanges(); } } - }, 0); + else if (msg.type == "USER_NEWINFO") + { + var userInfo = msg.userInfo; + var id = userInfo.userId; + + // Avoid a race condition when setting colors. If our color was set by a + // query param, ignore our own "new user" message's color value. + if (id === initialUserInfo.userId && initialUserInfo.globalUserColor) + { + msg.userInfo.colorId = initialUserInfo.globalUserColor; + } + + + if (userSet[id]) + { + userSet[id] = userInfo; + callbacks.onUpdateUserInfo(userInfo); + } + else + { + userSet[id] = userInfo; + callbacks.onUserJoin(userInfo); + } + tellAceActiveAuthorInfo(userInfo); + } + else if (msg.type == "USER_LEAVE") + { + var userInfo = msg.userInfo; + var id = userInfo.userId; + if (userSet[id]) + { + delete userSet[userInfo.userId]; + fadeAceAuthorInfo(userInfo); + callbacks.onUserLeave(userInfo); + } + } + + else if (msg.type == "DISCONNECT_REASON") + { + appLevelDisconnectReason = msg.reason; + } + else if (msg.type == "CLIENT_MESSAGE") + { + callbacks.onClientMessage(msg.payload); + } + else if (msg.type == "CHAT_MESSAGE") + { + chat.addMessage(msg, true, false); + } + else if (msg.type == "CHAT_MESSAGES") + { + for(var i = msg.messages.length - 1; i >= 0; i--) + { + chat.addMessage(msg.messages[i], true, true); + } + if(!chat.gotInitalMessages) + { + chat.scrollDown(); + chat.gotInitalMessages = true; + chat.historyPointer = clientVars.chatHead - msg.messages.length; + } + + // messages are loaded, so hide the loading-ball + $("#chatloadmessagesball").css("display", "none"); + + // there are less than 100 messages or we reached the top + if(chat.historyPointer <= 0) + $("#chatloadmessagesbutton").css("display", "none"); + else // there are still more messages, re-show the load-button + $("#chatloadmessagesbutton").css("display", "block"); + } + else if (msg.type == "SERVER_MESSAGE") + { + callbacks.onServerMessage(msg.payload); + } + hooks.callAll('handleClientMessage_' + msg.type, {payload: msg.payload}); + } + + function updateUserInfo(userInfo) + { + userInfo.userId = userId; + userSet[userId] = userInfo; + tellAceActiveAuthorInfo(userInfo); + if (!getSocket()) return; + sendMessage( + { + type: "USERINFO_UPDATE", + userInfo: userInfo + }); + } + + function tellAceActiveAuthorInfo(userInfo) + { + tellAceAuthorInfo(userInfo.userId, userInfo.colorId); + } + + function tellAceAuthorInfo(userId, colorId, inactive) + { + if(typeof colorId == "number") + { + colorId = clientVars.colorPalette[colorId]; + } + + var cssColor = colorId; + if (inactive) + { + editor.setAuthorInfo(userId, { + bgcolor: cssColor, + fade: 0.5 + }); + } + else + { + editor.setAuthorInfo(userId, { + bgcolor: cssColor + }); + } + } + + function fadeAceAuthorInfo(userInfo) + { + tellAceAuthorInfo(userInfo.userId, userInfo.colorId, true); + } + + function getConnectedUsers() + { + return valuesArray(userSet); + } + + function tellAceAboutHistoricalAuthors(hadata) + { + for (var author in hadata) + { + var data = hadata[author]; + if (!userSet[author]) + { + tellAceAuthorInfo(author, data.colorId, true); + } + } + } + + function setChannelState(newChannelState, moreInfo) + { + if (newChannelState != channelState) + { + channelState = newChannelState; + callbacks.onChannelStateChange(channelState, moreInfo); + } + } + + function valuesArray(obj) + { + var array = []; + $.each(obj, function(k, v) + { + array.push(v); + }); + return array; + } + + // We need to present a working interface even before the socket + // is connected for the first time. + var deferredActions = []; + + function defer(func, tag) + { + return function() + { + var that = this; + var args = arguments; + + function action() + { + func.apply(that, args); + } + action.tag = tag; + if (channelState == "CONNECTING") + { + deferredActions.push(action); + } + else + { + action(); + } + } + } + + function doDeferredActions(tag) + { + var newArray = []; + for (var i = 0; i < deferredActions.length; i++) + { + var a = deferredActions[i]; + if ((!tag) || (tag == a.tag)) + { + a(); + } + else + { + newArray.push(a); + } + } + deferredActions = newArray; + } + + function sendClientMessage(msg) + { + sendMessage( + { + type: "CLIENT_MESSAGE", + payload: msg + }); + } + + function getCurrentRevisionNumber() + { + return rev; + } + + function getMissedChanges() + { + var obj = {}; + obj.userInfo = userSet[userId]; + obj.baseRev = rev; + if (state == "COMMITTING" && stateMessage) + { + obj.committedChangeset = stateMessage.changeset; + obj.committedChangesetAPool = stateMessage.apool; + editor.applyPreparedChangesetToBase(); + } + var userChangesData = editor.prepareUserChangeset(); + if (userChangesData.changeset) + { + obj.furtherChangeset = userChangesData.changeset; + obj.furtherChangesetAPool = userChangesData.apool; + } + return obj; + } + + function setStateIdle() + { + state = "IDLE"; + callbacks.onInternalAction("newlyIdle"); + schedulePerhapsCallIdleFuncs(); + } + + function callWhenNotCommitting(func) + { + idleFuncs.push(func); + schedulePerhapsCallIdleFuncs(); + } + + var idleFuncs = []; + + function schedulePerhapsCallIdleFuncs() + { + setTimeout(function() + { + if (state == "IDLE") + { + while (idleFuncs.length > 0) + { + var f = idleFuncs.shift(); + f(); + } + } + }, 0); + } + + var self = { + setOnUserJoin: function(cb) + { + callbacks.onUserJoin = cb; + }, + setOnUserLeave: function(cb) + { + callbacks.onUserLeave = cb; + }, + setOnUpdateUserInfo: function(cb) + { + callbacks.onUpdateUserInfo = cb; + }, + setOnChannelStateChange: function(cb) + { + callbacks.onChannelStateChange = cb; + }, + setOnClientMessage: function(cb) + { + callbacks.onClientMessage = cb; + }, + setOnInternalAction: function(cb) + { + callbacks.onInternalAction = cb; + }, + setOnConnectionTrouble: function(cb) + { + callbacks.onConnectionTrouble = cb; + }, + setOnServerMessage: function(cb) + { + callbacks.onServerMessage = cb; + }, + updateUserInfo: defer(updateUserInfo), + handleMessageFromServer: handleMessageFromServer, + getConnectedUsers: getConnectedUsers, + sendClientMessage: sendClientMessage, + sendMessage: sendMessage, + getCurrentRevisionNumber: getCurrentRevisionNumber, + getMissedChanges: getMissedChanges, + callWhenNotCommitting: callWhenNotCommitting, + addHistoricalAuthors: tellAceAboutHistoricalAuthors, + setChannelState: setChannelState + }; + + $(document).ready(setUpSocket); + return self; } - var self = { - setOnUserJoin: function(cb) - { - callbacks.onUserJoin = cb; - }, - setOnUserLeave: function(cb) - { - callbacks.onUserLeave = cb; - }, - setOnUpdateUserInfo: function(cb) - { - callbacks.onUpdateUserInfo = cb; - }, - setOnChannelStateChange: function(cb) - { - callbacks.onChannelStateChange = cb; - }, - setOnClientMessage: function(cb) - { - callbacks.onClientMessage = cb; - }, - setOnInternalAction: function(cb) - { - callbacks.onInternalAction = cb; - }, - setOnConnectionTrouble: function(cb) - { - callbacks.onConnectionTrouble = cb; - }, - setOnServerMessage: function(cb) - { - callbacks.onServerMessage = cb; - }, - updateUserInfo: defer(updateUserInfo), - handleMessageFromServer: handleMessageFromServer, - getConnectedUsers: getConnectedUsers, - sendClientMessage: sendClientMessage, - sendMessage: sendMessage, - getCurrentRevisionNumber: getCurrentRevisionNumber, - getMissedChanges: getMissedChanges, - callWhenNotCommitting: callWhenNotCommitting, - addHistoricalAuthors: tellAceAboutHistoricalAuthors, - setChannelState: setChannelState - }; + exports.getCollabClient = getCollabClient; - $(document).ready(setUpSocket); - return self; -} - -exports.getCollabClient = getCollabClient; + return exports; +}); diff --git a/src/static/js/pad.js b/src/static/js/pad.js index 52219fe50..4072ca3c2 100644 --- a/src/static/js/pad.js +++ b/src/static/js/pad.js @@ -22,954 +22,951 @@ /* global $, window */ -var socket; +define([ + 'ep_etherpad-lite/static/js/rjquery', + 'ep_etherpad-lite/static/js/pluginfw/hooks', + 'ep_etherpad-lite/static/js/pad_utils', + 'ep_etherpad-lite/static/js/chat', + 'ep_etherpad-lite/static/js/pad_cookie', + 'ep_etherpad-lite/static/js/pad_editbar', + 'ep_etherpad-lite/static/js/pad_editor', + 'ep_etherpad-lite/static/js/pad_savedrevs', + 'ep_etherpad-lite/static/js/pad_userlist', + 'ep_etherpad-lite/static/js/pad_modals', + 'ep_etherpad-lite/static/js/collab_client', + 'ep_etherpad-lite/static/js/pad_connectionstatus' +], function($, hooks, padUtilsMod, chatMod, padCookieMod, padEditbarMod, padEditorMod, padsavedrevs, padUserListMod, padModalsMod, collabClientMod, padConnectionStatusMod) { + var exports = {}; -// These jQuery things should create local references, but for now `require()` -// assigns to the global `$` and augments it with plugins. -require('./jquery'); -require('./farbtastic'); -require('./excanvas'); -JSON = require('./json2'); + var socket; -var chat = require('./chat').chat; -var getCollabClient = require('./collab_client').getCollabClient; -var padconnectionstatus = require('./pad_connectionstatus').padconnectionstatus; -var padcookie = require('./pad_cookie').padcookie; -var padeditbar = require('./pad_editbar').padeditbar; -var padeditor = require('./pad_editor').padeditor; -var padimpexp = require('./pad_impexp').padimpexp; -var padmodals = require('./pad_modals').padmodals; -var padsavedrevs = require('./pad_savedrevs'); -var paduserlist = require('./pad_userlist').paduserlist; -var padutils = require('./pad_utils').padutils; -var colorutils = require('./colorutils').colorutils; -var createCookie = require('./pad_utils').createCookie; -var readCookie = require('./pad_utils').readCookie; -var randomString = require('./pad_utils').randomString; -var gritter = require('./gritter').gritter; + window.requireKernel('./farbtastic'); + window.requireKernel('./excanvas'); + JSON = window.requireKernel('./json2'); -var receivedClientVars = false; + var chat = chatMod.chat; + var getCollabClient = collabClientMod.getCollabClient; + var padconnectionstatus = padConnectionStatusMod.padconnectionstatus; + var padcookie = padCookieMod.padcookie; + var padeditbar = padEditbarMod.padeditbar; + var padeditor = padEditorMod.padeditor; + var padimpexp = window.requireKernel('./pad_impexp').padimpexp; + var padmodals = padModalsMod.padmodals; + var paduserlist = padUserListMod.paduserlist; + var padutils = padUtilsMod.padutils; + var colorutils = window.requireKernel('./colorutils').colorutils; + var createCookie = padUtilsMod.createCookie; + var readCookie = padUtilsMod.readCookie; + var randomString = padUtilsMod.randomString; + var gritter = window.requireKernel('./gritter').gritter; -function createCookie(name, value, days, path){ /* Warning Internet Explorer doesn't use this it uses the one from pad_utils.js */ - if (days) - { - var date = new Date(); - date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); - var expires = "; expires=" + date.toGMTString(); - } - else{ - var expires = ""; - } - - if(!path){ // If the path isn't set then just whack the cookie on the root path - path = "/"; - } - - //Check if the browser is IE and if so make sure the full path is set in the cookie - if((navigator.appName == 'Microsoft Internet Explorer') || ((navigator.appName == 'Netscape') && (new RegExp("Trident/.*rv:([0-9]{1,}[\.0-9]{0,})").exec(navigator.userAgent) != null))){ - document.cookie = name + "=" + value + expires + "; path="+document.location; - } - else{ - document.cookie = name + "=" + value + expires + "; path=" + path; - } -} + var receivedClientVars = false; -function readCookie(name) -{ - var nameEQ = name + "="; - var ca = document.cookie.split(';'); - for (var i = 0; i < ca.length; i++) - { - var c = ca[i]; - while (c.charAt(0) == ' ') c = c.substring(1, c.length); - if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length); - } - return null; -} + $.extend($.gritter.options, { + position: 'bottom-right', // defaults to 'top-right' but can be 'bottom-left', 'bottom-right', 'top-left', 'top-right' (added in 1.7.1) + fade: false, // dont fade, too jerky on mobile + time: 6000 // hang on the screen for... + }); -function randomString() -{ - var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - var string_length = 20; - var randomstring = ''; - for (var i = 0; i < string_length; i++) - { - var rnum = Math.floor(Math.random() * chars.length); - randomstring += chars.substring(rnum, rnum + 1); - } - return "t." + randomstring; -} - -// This array represents all GET-parameters which can be used to change a setting. -// name: the parameter-name, eg `?noColors=true` => `noColors` -// checkVal: the callback is only executed when -// * the parameter was supplied and matches checkVal -// * the parameter was supplied and checkVal is null -// callback: the function to call when all above succeeds, `val` is the value supplied by the user -var getParameters = [ - { name: "noColors", checkVal: "true", callback: function(val) { settings.noColors = true; $('#clearAuthorship').hide(); } }, - { name: "showControls", checkVal: "false", callback: function(val) { $('#editbar').addClass('hideControlsEditbar'); $('#editorcontainer').addClass('hideControlsEditor'); } }, - { name: "showChat", checkVal: "false", callback: function(val) { $('#chaticon').hide(); } }, - { name: "showLineNumbers", checkVal: "false", callback: function(val) { settings.LineNumbersDisabled = true; } }, - { name: "useMonospaceFont", checkVal: "true", callback: function(val) { settings.useMonospaceFontGlobal = true; } }, - // If the username is set as a parameter we should set a global value that we can call once we have initiated the pad. - { name: "userName", checkVal: null, callback: function(val) { settings.globalUserName = decodeURIComponent(val); } }, - // If the userColor is set as a parameter, set a global value to use once we have initiated the pad. - { name: "userColor", checkVal: null, callback: function(val) { settings.globalUserColor = decodeURIComponent(val); } }, - { name: "rtl", checkVal: "true", callback: function(val) { settings.rtlIsTrue = true } }, - { name: "alwaysShowChat", checkVal: "true", callback: function(val) { chat.stickToScreen(); } }, - { name: "chatAndUsers", checkVal: "true", callback: function(val) { chat.chatAndUsers(); } }, - { name: "lang", checkVal: null, callback: function(val) { window.html10n.localize([val, 'en']); } } -]; - -function getParams() -{ - var params = getUrlVars() - - for(var i = 0; i < getParameters.length; i++) - { - var setting = getParameters[i]; - var value = params[setting.name]; - - if(value && (value == setting.checkVal || setting.checkVal == null)) + function createCookie(name, value, days, path){ /* Warning Internet Explorer doesn't use this it uses the one from pad_utils.js */ + if (days) { - setting.callback(value); + var date = new Date(); + date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + var expires = "; expires=" + date.toGMTString(); } - } -} - -function getUrlVars() -{ - var vars = [], hash; - var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&'); - for(var i = 0; i < hashes.length; i++) - { - hash = hashes[i].split('='); - vars.push(hash[0]); - vars[hash[0]] = hash[1]; - } - return vars; -} - -function savePassword() -{ - //set the password cookie - createCookie("password",$("#passwordinput").val(),null,document.location.pathname); - //reload - document.location=document.location; - return false; -} - -function sendClientReady(isReconnect, messageType) -{ - messageType = typeof messageType !== 'undefined' ? messageType : 'CLIENT_READY'; - var padId = document.location.pathname.substring(document.location.pathname.lastIndexOf("/") + 1); - padId = decodeURIComponent(padId); // unescape neccesary due to Safari and Opera interpretation of spaces - - if(!isReconnect) - { - var titleArray = document.title.split('|'); - var title = titleArray[titleArray.length - 1]; - document.title = padId.replace(/_+/g, ' ') + " | " + title; - } - - var token = readCookie("token"); - if (token == null) - { - token = "t." + randomString(); - createCookie("token", token, 60); - } - - var sessionID = decodeURIComponent(readCookie("sessionID")); - var password = readCookie("password"); - - var msg = { - "component": "pad", - "type": messageType, - "padId": padId, - "sessionID": sessionID, - "password": password, - "token": token, - "protocolVersion": 2 - }; - - //this is a reconnect, lets tell the server our revisionnumber - if(isReconnect == true) - { - msg.client_rev=pad.collabClient.getCurrentRevisionNumber(); - msg.reconnect=true; - } - - socket.json.send(msg); -} - -function handshake() -{ - var loc = document.location; - //get the correct port - var port = loc.port == "" ? (loc.protocol == "https:" ? 443 : 80) : loc.port; - //create the url - var url = loc.protocol + "//" + loc.hostname + ":" + port + "/"; - //find out in which subfolder we are - var resource = exports.baseURL.substring(1) + "socket.io"; - //connect - socket = pad.socket = io.connect(url, { - // Allow deployers to host Etherpad on a non-root path - 'path': exports.baseURL + "socket.io", - 'resource': resource, - 'max reconnection attempts': 3, - 'sync disconnect on unload' : false - }); - - var disconnectTimeout; - - socket.once('connect', function () { - sendClientReady(false); - }); - - socket.on('reconnect', function () { - //reconnect is before the timeout, lets stop the timeout - if(disconnectTimeout) - { - clearTimeout(disconnectTimeout); + else{ + var expires = ""; } - pad.collabClient.setChannelState("CONNECTED"); - pad.sendClientReady(true); - }); - - socket.on('disconnect', function (reason) { - if(reason == "booted"){ - pad.collabClient.setChannelState("DISCONNECTED"); - } else { - function disconnectEvent() + if(!path){ // If the path isn't set then just whack the cookie on the root path + path = "/"; + } + + //Check if the browser is IE and if so make sure the full path is set in the cookie + if((navigator.appName == 'Microsoft Internet Explorer') || ((navigator.appName == 'Netscape') && (new RegExp("Trident/.*rv:([0-9]{1,}[\.0-9]{0,})").exec(navigator.userAgent) != null))){ + document.cookie = name + "=" + value + expires + "; path="+document.location; + } + else{ + document.cookie = name + "=" + value + expires + "; path=" + path; + } + } + + function readCookie(name) + { + var nameEQ = name + "="; + var ca = document.cookie.split(';'); + for (var i = 0; i < ca.length; i++) + { + var c = ca[i]; + while (c.charAt(0) == ' ') c = c.substring(1, c.length); + if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length); + } + return null; + } + + // This array represents all GET-parameters which can be used to change a setting. + // name: the parameter-name, eg `?noColors=true` => `noColors` + // checkVal: the callback is only executed when + // * the parameter was supplied and matches checkVal + // * the parameter was supplied and checkVal is null + // callback: the function to call when all above succeeds, `val` is the value supplied by the user + var getParameters = [ + { name: "noColors", checkVal: "true", callback: function(val) { settings.noColors = true; $('#clearAuthorship').hide(); } }, + { name: "showControls", checkVal: "false", callback: function(val) { $('#editbar').addClass('hideControlsEditbar'); $('#editorcontainer').addClass('hideControlsEditor'); } }, + { name: "showChat", checkVal: "false", callback: function(val) { $('#chaticon').hide(); } }, + { name: "showLineNumbers", checkVal: "false", callback: function(val) { settings.LineNumbersDisabled = true; } }, + { name: "useMonospaceFont", checkVal: "true", callback: function(val) { settings.useMonospaceFontGlobal = true; } }, + // If the username is set as a parameter we should set a global value that we can call once we have initiated the pad. + { name: "userName", checkVal: null, callback: function(val) { settings.globalUserName = decodeURIComponent(val); } }, + // If the userColor is set as a parameter, set a global value to use once we have initiated the pad. + { name: "userColor", checkVal: null, callback: function(val) { settings.globalUserColor = decodeURIComponent(val); } }, + { name: "rtl", checkVal: "true", callback: function(val) { settings.rtlIsTrue = true } }, + { name: "alwaysShowChat", checkVal: "true", callback: function(val) { chat.stickToScreen(); } }, + { name: "chatAndUsers", checkVal: "true", callback: function(val) { chat.chatAndUsers(); } }, + { name: "lang", checkVal: null, callback: function(val) { window.html10n.localize([val, 'en']); } } + ]; + + function getParams() + { + var params = getUrlVars() + + for(var i = 0; i < getParameters.length; i++) + { + var setting = getParameters[i]; + var value = params[setting.name]; + + if(value && (value == setting.checkVal || setting.checkVal == null)) { - pad.collabClient.setChannelState("DISCONNECTED", "reconnect_timeout"); + setting.callback(value); } - - pad.collabClient.setChannelState("RECONNECTING"); - - disconnectTimeout = setTimeout(disconnectEvent, 20000); } - }); + } - var initalized = false; - - socket.on('message', function(obj) + function getUrlVars() { - //the access was not granted, give the user a message - if(obj.accessStatus) + var vars = [], hash; + var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&'); + for(var i = 0; i < hashes.length; i++) { - if(!receivedClientVars){ - $('.passForm').submit(require(module.id).savePassword); + hash = hashes[i].split('='); + vars.push(hash[0]); + vars[hash[0]] = hash[1]; + } + return vars; + } + + function savePassword() + { + //set the password cookie + createCookie("password",$("#passwordinput").val(),null,document.location.pathname); + //reload + document.location=document.location; + return false; + } + + function sendClientReady(isReconnect, messageType) + { + messageType = typeof messageType !== 'undefined' ? messageType : 'CLIENT_READY'; + var padId = document.location.pathname.substring(document.location.pathname.lastIndexOf("/") + 1); + padId = decodeURIComponent(padId); // unescape neccesary due to Safari and Opera interpretation of spaces + + if(!isReconnect) + { + var titleArray = document.title.split('|'); + var title = titleArray[titleArray.length - 1]; + document.title = padId.replace(/_+/g, ' ') + " | " + title; + } + + var token = readCookie("token"); + if (token == null) + { + token = "t." + randomString(); + createCookie("token", token, 60); + } + + var sessionID = decodeURIComponent(readCookie("sessionID")); + var password = readCookie("password"); + + var msg = { + "component": "pad", + "type": messageType, + "padId": padId, + "sessionID": sessionID, + "password": password, + "token": token, + "protocolVersion": 2 + }; + + //this is a reconnect, lets tell the server our revisionnumber + if(isReconnect == true) + { + msg.client_rev=pad.collabClient.getCurrentRevisionNumber(); + msg.reconnect=true; + } + + socket.json.send(msg); + } + + function handshake() + { + var loc = document.location; + //get the correct port + var port = loc.port == "" ? (loc.protocol == "https:" ? 443 : 80) : loc.port; + //create the url + var url = loc.protocol + "//" + loc.hostname + ":" + port + "/"; + //find out in which subfolder we are + var resource = exports.baseURL.substring(1) + "socket.io"; + //connect + socket = pad.socket = io.connect(url, { + // Allow deployers to host Etherpad on a non-root path + 'path': exports.baseURL + "socket.io", + 'resource': resource, + 'max reconnection attempts': 3, + 'sync disconnect on unload' : false + }); + + var disconnectTimeout; + + socket.once('connect', function () { + sendClientReady(false); + }); + + socket.on('reconnect', function () { + //reconnect is before the timeout, lets stop the timeout + if(disconnectTimeout) + { + clearTimeout(disconnectTimeout); } - if(obj.accessStatus == "deny") - { - $('#loading').hide(); - $("#permissionDenied").show(); + pad.collabClient.setChannelState("CONNECTED"); + pad.sendClientReady(true); + }); - if(receivedClientVars) + socket.on('disconnect', function (reason) { + if(reason == "booted"){ + pad.collabClient.setChannelState("DISCONNECTED"); + } else { + function disconnectEvent() { - // got kicked - $("#editorcontainer").hide(); - $("#editorloadingbox").show(); + pad.collabClient.setChannelState("DISCONNECTED", "reconnect_timeout"); + } + + pad.collabClient.setChannelState("RECONNECTING"); + + disconnectTimeout = setTimeout(disconnectEvent, 20000); + } + }); + + var initalized = false; + + socket.on('message', function(obj) + { + //the access was not granted, give the user a message + if(obj.accessStatus) + { + if(!receivedClientVars){ + $('.passForm').submit(window.requireKernel(module.id).savePassword); + } + + if(obj.accessStatus == "deny") + { + $('#loading').hide(); + $("#permissionDenied").show(); + + if(receivedClientVars) + { + // got kicked + $("#editorcontainer").hide(); + $("#editorloadingbox").show(); + } + } + else if(obj.accessStatus == "needPassword") + { + $('#loading').hide(); + $('#passwordRequired').show(); + $("#passwordinput").focus(); + } + else if(obj.accessStatus == "wrongPassword") + { + $('#loading').hide(); + $('#wrongPassword').show(); + $('#passwordRequired').show(); + $("#passwordinput").focus(); } } - else if(obj.accessStatus == "needPassword") + + //if we haven't recieved the clientVars yet, then this message should it be + else if (!receivedClientVars && obj.type == "CLIENT_VARS") { - $('#loading').hide(); - $('#passwordRequired').show(); - $("#passwordinput").focus(); + //log the message + if (window.console) console.log(obj); + + receivedClientVars = true; + + //set some client vars + clientVars = obj.data; + clientVars.userAgent = "Anonymous"; + clientVars.collab_client_vars.clientAgent = "Anonymous"; + + //initalize the pad + pad._afterHandshake(); + initalized = true; + + $("body").addClass(clientVars.readonly ? "readonly" : "readwrite") + + padeditor.ace.callWithAce(function (ace) { + ace.ace_setEditable(!clientVars.readonly); + }); + + // If the LineNumbersDisabled value is set to true then we need to hide the Line Numbers + if (settings.LineNumbersDisabled == true) + { + pad.changeViewOption('showLineNumbers', false); + } + + // If the noColors value is set to true then we need to hide the background colors on the ace spans + if (settings.noColors == true) + { + pad.changeViewOption('noColors', true); + } + + if (settings.rtlIsTrue == true) + { + pad.changeViewOption('rtlIsTrue', true); + } + + // If the Monospacefont value is set to true then change it to monospace. + if (settings.useMonospaceFontGlobal == true) + { + pad.changeViewOption('useMonospaceFont', true); + } + // if the globalUserName value is set we need to tell the server and the client about the new authorname + if (settings.globalUserName !== false) + { + pad.notifyChangeName(settings.globalUserName); // Notifies the server + pad.myUserInfo.name = settings.globalUserName; + $('#myusernameedit').val(settings.globalUserName); // Updates the current users UI + } + if (settings.globalUserColor !== false && colorutils.isCssHex(settings.globalUserColor)) + { + + // Add a 'globalUserColor' property to myUserInfo, so collabClient knows we have a query parameter. + pad.myUserInfo.globalUserColor = settings.globalUserColor; + pad.notifyChangeColor(settings.globalUserColor); // Updates pad.myUserInfo.colorId + paduserlist.setMyUserInfo(pad.myUserInfo); + } } - else if(obj.accessStatus == "wrongPassword") + //This handles every Message after the clientVars + else { - $('#loading').hide(); - $('#wrongPassword').show(); - $('#passwordRequired').show(); - $("#passwordinput").focus(); + //this message advices the client to disconnect + if (obj.disconnect) + { + console.warn("FORCED TO DISCONNECT"); + console.warn(obj); + padconnectionstatus.disconnected(obj.disconnect); + socket.disconnect(); + return; + } + else + { + pad.collabClient.handleMessageFromServer(obj); + } } - } - - //if we haven't recieved the clientVars yet, then this message should it be - else if (!receivedClientVars && obj.type == "CLIENT_VARS") + }); + // Bind the colorpicker + var fb = $('#colorpicker').farbtastic({ callback: '#mycolorpickerpreview', width: 220}); + // Bind the read only button + $('#readonlyinput').on('click',function(){ + padeditbar.setEmbedLinks(); + }); + } + + var pad = { + // don't access these directly from outside this file, except + // for debugging + collabClient: null, + myUserInfo: null, + diagnosticInfo: {}, + initTime: 0, + clientTimeOffset: null, + padOptions: {}, + + // these don't require init; clientVars should all go through here + getPadId: function() { - //log the message - if (window.console) console.log(obj); + return clientVars.padId; + }, + getClientIp: function() + { + return clientVars.clientIp; + }, + getColorPalette: function() + { + return clientVars.colorPalette; + }, + getDisplayUserAgent: function() + { + return padutils.uaDisplay(clientVars.userAgent); + }, + getIsDebugEnabled: function() + { + return clientVars.debugEnabled; + }, + getPrivilege: function(name) + { + return clientVars.accountPrivs[name]; + }, + getUserIsGuest: function() + { + return clientVars.userIsGuest; + }, + getUserId: function() + { + return pad.myUserInfo.userId; + }, + getUserName: function() + { + return pad.myUserInfo.name; + }, + userList: function() + { + return paduserlist.users(); + }, + sendClientReady: function(isReconnect, messageType) + { + messageType = typeof messageType !== 'undefined' ? messageType : 'CLIENT_READY'; + sendClientReady(isReconnect, messageType); + }, + switchToPad: function(padId) + { + var options = document.location.href.split('?')[1]; + var newHref = "/p/" + padId; + if (options != null) + newHref = newHref + '?' + options; - receivedClientVars = true; - - //set some client vars - clientVars = obj.data; - clientVars.userAgent = "Anonymous"; - clientVars.collab_client_vars.clientAgent = "Anonymous"; - - //initalize the pad - pad._afterHandshake(); - initalized = true; - - $("body").addClass(clientVars.readonly ? "readonly" : "readwrite") - - padeditor.ace.callWithAce(function (ace) { - ace.ace_setEditable(!clientVars.readonly); - }); - - // If the LineNumbersDisabled value is set to true then we need to hide the Line Numbers - if (settings.LineNumbersDisabled == true) + if(window.history && window.history.pushState) { + $('#chattext p').remove(); //clear the chat messages + window.history.pushState("", "", newHref); + receivedClientVars = false; + sendClientReady(false, 'SWITCH_TO_PAD'); + } + else // fallback + { + window.location.href = newHref; + } + }, + sendClientMessage: function(msg) + { + pad.collabClient.sendClientMessage(msg); + }, + createCookie: createCookie, + + init: function() + { + padutils.setupGlobalExceptionHandler(); + + $(document).ready(function() + { + // start the custom js + if (typeof customStart == "function") customStart(); + getParams(); + + padeditor.init(function () { + handshake(); + }, pad.padOptions.view || {}, pad); + + // To use etherpad you have to allow cookies. + // This will check if the creation of a test-cookie has success. + // Otherwise it shows up a message to the user. + createCookie("test", "test"); + if (!readCookie("test")) + { + $('#loading').hide(); + $('#noCookie').show(); + } + }); + }, + _afterHandshake: function() + { + pad.clientTimeOffset = new Date().getTime() - clientVars.serverTimestamp; + + //initialize the chat + chat.init(this); + padcookie.init(); // initialize the cookies + pad.initTime = +(new Date()); + pad.padOptions = clientVars.initialOptions; + + if ((!browser.msie) && (!(browser.firefox && browser.version.indexOf("1.8.") == 0))) + { + document.domain = document.domain; // for comet + } + + // for IE + if (browser.msie) + { + try + { + document.execCommand("BackgroundImageCache", false, true); + } + catch (e) + {} + } + + // order of inits is important here: + pad.myUserInfo = { + userId: clientVars.userId, + name: clientVars.userName, + ip: pad.getClientIp(), + colorId: clientVars.userColor, + userAgent: pad.getDisplayUserAgent() + }; + + padimpexp.init(this); + padsavedrevs.init(this); + + paduserlist.init(pad.myUserInfo, this); + padconnectionstatus.init(); + padmodals.init(this); + pad.collabClient = getCollabClient(padeditor.ace, clientVars.collab_client_vars, pad.myUserInfo, { + colorPalette: pad.getColorPalette() + }, pad); + pad.collabClient.setOnUserJoin(pad.handleUserJoin); + pad.collabClient.setOnUpdateUserInfo(pad.handleUserUpdate); + pad.collabClient.setOnUserLeave(pad.handleUserLeave); + pad.collabClient.setOnClientMessage(pad.handleClientMessage); + pad.collabClient.setOnServerMessage(pad.handleServerMessage); + pad.collabClient.setOnChannelStateChange(pad.handleChannelStateChange); + pad.collabClient.setOnInternalAction(pad.handleCollabAction); + + // load initial chat-messages + if(clientVars.chatHead != -1) + { + var chatHead = clientVars.chatHead; + var start = Math.max(chatHead - 100, 0); + pad.collabClient.sendMessage({"type": "GET_CHAT_MESSAGES", "start": start, "end": chatHead}); + } + else // there are no messages + { + $("#chatloadmessagesbutton").css("display", "none"); + } + + padeditbar.init(); + setTimeout(function() + { + padeditor.ace.focus(); + }, 0); + if(padcookie.getPref("chatAlwaysVisible")){ // if we have a cookie for always showing chat then show it + chat.stickToScreen(true); // stick it to the screen + $('#options-stickychat').prop("checked", true); // set the checkbox to on + } + if(padcookie.getPref("chatAndUsers")){ // if we have a cookie for always showing chat then show it + chat.chatAndUsers(true); // stick it to the screen + $('#options-chatandusers').prop("checked", true); // set the checkbox to on + } + if(padcookie.getPref("showAuthorshipColors") == false){ + pad.changeViewOption('showAuthorColors', false); + } + if(padcookie.getPref("showLineNumbers") == false){ pad.changeViewOption('showLineNumbers', false); } - - // If the noColors value is set to true then we need to hide the background colors on the ace spans - if (settings.noColors == true) - { - pad.changeViewOption('noColors', true); - } - - if (settings.rtlIsTrue == true) - { + if(padcookie.getPref("rtlIsTrue") == true){ pad.changeViewOption('rtlIsTrue', true); } - // If the Monospacefont value is set to true then change it to monospace. - if (settings.useMonospaceFontGlobal == true) + var fonts = ['useMonospaceFont', 'useOpenDyslexicFont', 'useComicSansFont', 'useCourierNewFont', 'useGeorgiaFont', 'useImpactFont', + 'useLucidaFont', 'useLucidaSansFont', 'usePalatinoFont', 'useTahomaFont', 'useTimesNewRomanFont', + 'useTrebuchetFont', 'useVerdanaFont', 'useSymbolFont', 'useWebdingsFont', 'useWingDingsFont', 'useSansSerifFont', + 'useSerifFont']; + + $.each(fonts, function(i, font){ + if(padcookie.getPref(font) == true){ + pad.changeViewOption(font, true); + } + }) + + hooks.aCallAll("postAceInit", {ace: padeditor.ace, pad: pad}); + }, + dispose: function() + { + padeditor.dispose(); + }, + notifyChangeName: function(newName) + { + pad.myUserInfo.name = newName; + pad.collabClient.updateUserInfo(pad.myUserInfo); + }, + notifyChangeColor: function(newColorId) + { + pad.myUserInfo.colorId = newColorId; + pad.collabClient.updateUserInfo(pad.myUserInfo); + }, + changePadOption: function(key, value) + { + var options = {}; + options[key] = value; + pad.handleOptionsChange(options); + pad.collabClient.sendClientMessage( { - pad.changeViewOption('useMonospaceFont', true); - } - // if the globalUserName value is set we need to tell the server and the client about the new authorname - if (settings.globalUserName !== false) + type: 'padoptions', + options: options, + changedBy: pad.myUserInfo.name || "unnamed" + }); + }, + changeViewOption: function(key, value) + { + var options = { + view: {} + }; + options.view[key] = value; + pad.handleOptionsChange(options); + }, + handleOptionsChange: function(opts) + { + // opts object is a full set of options or just + // some options to change + if (opts.view) { - pad.notifyChangeName(settings.globalUserName); // Notifies the server - pad.myUserInfo.name = settings.globalUserName; - $('#myusernameedit').val(settings.globalUserName); // Updates the current users UI - } - if (settings.globalUserColor !== false && colorutils.isCssHex(settings.globalUserColor)) - { - - // Add a 'globalUserColor' property to myUserInfo, so collabClient knows we have a query parameter. - pad.myUserInfo.globalUserColor = settings.globalUserColor; - pad.notifyChangeColor(settings.globalUserColor); // Updates pad.myUserInfo.colorId - paduserlist.setMyUserInfo(pad.myUserInfo); - } - } - //This handles every Message after the clientVars - else - { - //this message advices the client to disconnect - if (obj.disconnect) - { - console.warn("FORCED TO DISCONNECT"); - console.warn(obj); - padconnectionstatus.disconnected(obj.disconnect); - socket.disconnect(); - return; - } - else - { - pad.collabClient.handleMessageFromServer(obj); - } - } - }); - // Bind the colorpicker - var fb = $('#colorpicker').farbtastic({ callback: '#mycolorpickerpreview', width: 220}); - // Bind the read only button - $('#readonlyinput').on('click',function(){ - padeditbar.setEmbedLinks(); - }); -} - -$.extend($.gritter.options, { - position: 'bottom-right', // defaults to 'top-right' but can be 'bottom-left', 'bottom-right', 'top-left', 'top-right' (added in 1.7.1) - fade: false, // dont fade, too jerky on mobile - time: 6000 // hang on the screen for... -}); - -var pad = { - // don't access these directly from outside this file, except - // for debugging - collabClient: null, - myUserInfo: null, - diagnosticInfo: {}, - initTime: 0, - clientTimeOffset: null, - padOptions: {}, - - // these don't require init; clientVars should all go through here - getPadId: function() - { - return clientVars.padId; - }, - getClientIp: function() - { - return clientVars.clientIp; - }, - getColorPalette: function() - { - return clientVars.colorPalette; - }, - getDisplayUserAgent: function() - { - return padutils.uaDisplay(clientVars.userAgent); - }, - getIsDebugEnabled: function() - { - return clientVars.debugEnabled; - }, - getPrivilege: function(name) - { - return clientVars.accountPrivs[name]; - }, - getUserIsGuest: function() - { - return clientVars.userIsGuest; - }, - getUserId: function() - { - return pad.myUserInfo.userId; - }, - getUserName: function() - { - return pad.myUserInfo.name; - }, - userList: function() - { - return paduserlist.users(); - }, - sendClientReady: function(isReconnect, messageType) - { - messageType = typeof messageType !== 'undefined' ? messageType : 'CLIENT_READY'; - sendClientReady(isReconnect, messageType); - }, - switchToPad: function(padId) - { - var options = document.location.href.split('?')[1]; - var newHref = "/p/" + padId; - if (options != null) - newHref = newHref + '?' + options; - - if(window.history && window.history.pushState) - { - $('#chattext p').remove(); //clear the chat messages - window.history.pushState("", "", newHref); - receivedClientVars = false; - sendClientReady(false, 'SWITCH_TO_PAD'); - } - else // fallback - { - window.location.href = newHref; - } - }, - sendClientMessage: function(msg) - { - pad.collabClient.sendClientMessage(msg); - }, - createCookie: createCookie, - - init: function() - { - padutils.setupGlobalExceptionHandler(); - - $(document).ready(function() - { - // start the custom js - if (typeof customStart == "function") customStart(); - getParams(); - - padeditor.init(function () { - handshake(); - }, pad.padOptions.view || {}, pad); - - // To use etherpad you have to allow cookies. - // This will check if the creation of a test-cookie has success. - // Otherwise it shows up a message to the user. - createCookie("test", "test"); - if (!readCookie("test")) - { - $('#loading').hide(); - $('#noCookie').show(); - } - }); - }, - _afterHandshake: function() - { - pad.clientTimeOffset = new Date().getTime() - clientVars.serverTimestamp; - - //initialize the chat - chat.init(this); - padcookie.init(); // initialize the cookies - pad.initTime = +(new Date()); - pad.padOptions = clientVars.initialOptions; - - if ((!browser.msie) && (!(browser.firefox && browser.version.indexOf("1.8.") == 0))) - { - document.domain = document.domain; // for comet - } - - // for IE - if (browser.msie) - { - try - { - document.execCommand("BackgroundImageCache", false, true); - } - catch (e) - {} - } - - // order of inits is important here: - pad.myUserInfo = { - userId: clientVars.userId, - name: clientVars.userName, - ip: pad.getClientIp(), - colorId: clientVars.userColor, - userAgent: pad.getDisplayUserAgent() - }; - - padimpexp.init(this); - padsavedrevs.init(this); - - paduserlist.init(pad.myUserInfo, this); - padconnectionstatus.init(); - padmodals.init(this); - pad.collabClient = getCollabClient(padeditor.ace, clientVars.collab_client_vars, pad.myUserInfo, { - colorPalette: pad.getColorPalette() - }, pad); - pad.collabClient.setOnUserJoin(pad.handleUserJoin); - pad.collabClient.setOnUpdateUserInfo(pad.handleUserUpdate); - pad.collabClient.setOnUserLeave(pad.handleUserLeave); - pad.collabClient.setOnClientMessage(pad.handleClientMessage); - pad.collabClient.setOnServerMessage(pad.handleServerMessage); - pad.collabClient.setOnChannelStateChange(pad.handleChannelStateChange); - pad.collabClient.setOnInternalAction(pad.handleCollabAction); - - // load initial chat-messages - if(clientVars.chatHead != -1) - { - var chatHead = clientVars.chatHead; - var start = Math.max(chatHead - 100, 0); - pad.collabClient.sendMessage({"type": "GET_CHAT_MESSAGES", "start": start, "end": chatHead}); - } - else // there are no messages - { - $("#chatloadmessagesbutton").css("display", "none"); - } - - padeditbar.init(); - setTimeout(function() - { - padeditor.ace.focus(); - }, 0); - if(padcookie.getPref("chatAlwaysVisible")){ // if we have a cookie for always showing chat then show it - chat.stickToScreen(true); // stick it to the screen - $('#options-stickychat').prop("checked", true); // set the checkbox to on - } - if(padcookie.getPref("chatAndUsers")){ // if we have a cookie for always showing chat then show it - chat.chatAndUsers(true); // stick it to the screen - $('#options-chatandusers').prop("checked", true); // set the checkbox to on - } - if(padcookie.getPref("showAuthorshipColors") == false){ - pad.changeViewOption('showAuthorColors', false); - } - if(padcookie.getPref("showLineNumbers") == false){ - pad.changeViewOption('showLineNumbers', false); - } - if(padcookie.getPref("rtlIsTrue") == true){ - pad.changeViewOption('rtlIsTrue', true); - } - - var fonts = ['useMonospaceFont', 'useOpenDyslexicFont', 'useComicSansFont', 'useCourierNewFont', 'useGeorgiaFont', 'useImpactFont', - 'useLucidaFont', 'useLucidaSansFont', 'usePalatinoFont', 'useTahomaFont', 'useTimesNewRomanFont', - 'useTrebuchetFont', 'useVerdanaFont', 'useSymbolFont', 'useWebdingsFont', 'useWingDingsFont', 'useSansSerifFont', - 'useSerifFont']; - - $.each(fonts, function(i, font){ - if(padcookie.getPref(font) == true){ - pad.changeViewOption(font, true); - } - }) - - hooks.aCallAll("postAceInit", {ace: padeditor.ace, pad: pad}); - }, - dispose: function() - { - padeditor.dispose(); - }, - notifyChangeName: function(newName) - { - pad.myUserInfo.name = newName; - pad.collabClient.updateUserInfo(pad.myUserInfo); - }, - notifyChangeColor: function(newColorId) - { - pad.myUserInfo.colorId = newColorId; - pad.collabClient.updateUserInfo(pad.myUserInfo); - }, - changePadOption: function(key, value) - { - var options = {}; - options[key] = value; - pad.handleOptionsChange(options); - pad.collabClient.sendClientMessage( - { - type: 'padoptions', - options: options, - changedBy: pad.myUserInfo.name || "unnamed" - }); - }, - changeViewOption: function(key, value) - { - var options = { - view: {} - }; - options.view[key] = value; - pad.handleOptionsChange(options); - }, - handleOptionsChange: function(opts) - { - // opts object is a full set of options or just - // some options to change - if (opts.view) - { - if (!pad.padOptions.view) - { - pad.padOptions.view = {}; - } - for (var k in opts.view) - { - pad.padOptions.view[k] = opts.view[k]; - padcookie.setPref(k, opts.view[k]); - } - padeditor.setViewOptions(pad.padOptions.view); - } - if (opts.guestPolicy) - { - // order important here - pad.padOptions.guestPolicy = opts.guestPolicy; - } - }, - getPadOptions: function() - { - // caller shouldn't mutate the object - return pad.padOptions; - }, - isPadPublic: function() - { - return pad.getPadOptions().guestPolicy == 'allow'; - }, - suggestUserName: function(userId, name) - { - pad.collabClient.sendClientMessage( - { - type: 'suggestUserName', - unnamedId: userId, - newName: name - }); - }, - handleUserJoin: function(userInfo) - { - paduserlist.userJoinOrUpdate(userInfo); - }, - handleUserUpdate: function(userInfo) - { - paduserlist.userJoinOrUpdate(userInfo); - }, - handleUserLeave: function(userInfo) - { - paduserlist.userLeave(userInfo); - }, - handleClientMessage: function(msg) - { - if (msg.type == 'suggestUserName') - { - if (msg.unnamedId == pad.myUserInfo.userId && msg.newName && !pad.myUserInfo.name) - { - pad.notifyChangeName(msg.newName); - paduserlist.setMyUserInfo(pad.myUserInfo); - } - } - else if (msg.type == 'newRevisionList') - { - padsavedrevs.newRevisionList(msg.revisionList); - } - else if (msg.type == 'revisionLabel') - { - padsavedrevs.newRevisionList(msg.revisionList); - } - else if (msg.type == 'padoptions') - { - var opts = msg.options; - pad.handleOptionsChange(opts); - } - else if (msg.type == 'guestanswer') - { - // someone answered a prompt, remove it - paduserlist.removeGuestPrompt(msg.guestId); - } - }, - dmesg: function(m) - { - if (pad.getIsDebugEnabled()) - { - var djs = $('#djs').get(0); - var wasAtBottom = (djs.scrollTop - (djs.scrollHeight - $(djs).height()) >= -20); - $('#djs').append('' + m + '
'); - if (wasAtBottom) - { - djs.scrollTop = djs.scrollHeight; - } - } - }, - handleServerMessage: function(m) - { - if (m.type == 'NOTICE') - { - if (m.text) - { - alertBar.displayMessage(function(abar) + if (!pad.padOptions.view) { - abar.find("#servermsgdate").text(" (" + padutils.simpleDateTime(new Date) + ")"); - abar.find("#servermsgtext").text(m.text); - }); - } - if (m.js) - { - window['ev' + 'al'](m.js); - } - } - else if (m.type == 'GUEST_PROMPT') - { - paduserlist.showGuestPrompt(m.userId, m.displayName); - } - }, - handleChannelStateChange: function(newState, message) - { - var oldFullyConnected = !! padconnectionstatus.isFullyConnected(); - var wasConnecting = (padconnectionstatus.getStatus().what == 'connecting'); - if (newState == "CONNECTED") - { - padconnectionstatus.connected(); - } - else if (newState == "RECONNECTING") - { - padconnectionstatus.reconnecting(); - } - else if (newState == "DISCONNECTED") - { - pad.diagnosticInfo.disconnectedMessage = message; - pad.diagnosticInfo.padId = pad.getPadId(); - pad.diagnosticInfo.socket = {}; - - //we filter non objects from the socket object and put them in the diagnosticInfo - //this ensures we have no cyclic data - this allows us to stringify the data - for(var i in socket.socket) - { - var value = socket.socket[i]; - var type = typeof value; - - if(type == "string" || type == "number") + pad.padOptions.view = {}; + } + for (var k in opts.view) { - pad.diagnosticInfo.socket[i] = value; + pad.padOptions.view[k] = opts.view[k]; + padcookie.setPref(k, opts.view[k]); + } + padeditor.setViewOptions(pad.padOptions.view); + } + if (opts.guestPolicy) + { + // order important here + pad.padOptions.guestPolicy = opts.guestPolicy; + } + }, + getPadOptions: function() + { + // caller shouldn't mutate the object + return pad.padOptions; + }, + isPadPublic: function() + { + return pad.getPadOptions().guestPolicy == 'allow'; + }, + suggestUserName: function(userId, name) + { + pad.collabClient.sendClientMessage( + { + type: 'suggestUserName', + unnamedId: userId, + newName: name + }); + }, + handleUserJoin: function(userInfo) + { + paduserlist.userJoinOrUpdate(userInfo); + }, + handleUserUpdate: function(userInfo) + { + paduserlist.userJoinOrUpdate(userInfo); + }, + handleUserLeave: function(userInfo) + { + paduserlist.userLeave(userInfo); + }, + handleClientMessage: function(msg) + { + if (msg.type == 'suggestUserName') + { + if (msg.unnamedId == pad.myUserInfo.userId && msg.newName && !pad.myUserInfo.name) + { + pad.notifyChangeName(msg.newName); + paduserlist.setMyUserInfo(pad.myUserInfo); } } - - pad.asyncSendDiagnosticInfo(); - if (typeof window.ajlog == "string") + else if (msg.type == 'newRevisionList') { - window.ajlog += ("Disconnected: " + message + '\n'); + padsavedrevs.newRevisionList(msg.revisionList); } - padeditor.disable(); - padeditbar.disable(); - padimpexp.disable(); - - padconnectionstatus.disconnected(message); - } - var newFullyConnected = !! padconnectionstatus.isFullyConnected(); - if (newFullyConnected != oldFullyConnected) - { - pad.handleIsFullyConnected(newFullyConnected, wasConnecting); - } - }, - handleIsFullyConnected: function(isConnected, isInitialConnect) - { - pad.determineChatVisibility(isConnected && !isInitialConnect); - pad.determineChatAndUsersVisibility(isConnected && !isInitialConnect); - pad.determineAuthorshipColorsVisibility(); - }, - determineChatVisibility: function(asNowConnectedFeedback){ - var chatVisCookie = padcookie.getPref('chatAlwaysVisible'); - if(chatVisCookie){ // if the cookie is set for chat always visible - chat.stickToScreen(true); // stick it to the screen - $('#options-stickychat').prop("checked", true); // set the checkbox to on - } - else{ - $('#options-stickychat').prop("checked", false); // set the checkbox for off - } - }, - determineChatAndUsersVisibility: function(asNowConnectedFeedback){ - var chatAUVisCookie = padcookie.getPref('chatAndUsersVisible'); - if(chatAUVisCookie){ // if the cookie is set for chat always visible - chat.chatAndUsers(true); // stick it to the screen - $('#options-chatandusers').prop("checked", true); // set the checkbox to on - } - else{ - $('#options-chatandusers').prop("checked", false); // set the checkbox for off - } - }, - determineAuthorshipColorsVisibility: function(){ - var authColCookie = padcookie.getPref('showAuthorshipColors'); - if (authColCookie){ - pad.changeViewOption('showAuthorColors', true); - $('#options-colorscheck').prop("checked", true); - } - else { - $('#options-colorscheck').prop("checked", false); - } - }, - handleCollabAction: function(action) - { - if (action == "commitPerformed") - { - padeditbar.setSyncStatus("syncing"); - } - else if (action == "newlyIdle") - { - padeditbar.setSyncStatus("done"); - } - }, - hideServerMessage: function() - { - alertBar.hideMessage(); - }, - asyncSendDiagnosticInfo: function() - { - window.setTimeout(function() - { - $.ajax( + else if (msg.type == 'revisionLabel') { - type: 'post', - url: '/ep/pad/connection-diagnostic-info', - data: { - diagnosticInfo: JSON.stringify(pad.diagnosticInfo) - }, - success: function() - {}, - error: function() - {} - }); - }, 0); - }, - forceReconnect: function() - { - $('form#reconnectform input.padId').val(pad.getPadId()); - pad.diagnosticInfo.collabDiagnosticInfo = pad.collabClient.getDiagnosticInfo(); - $('form#reconnectform input.diagnosticInfo').val(JSON.stringify(pad.diagnosticInfo)); - $('form#reconnectform input.missedChanges').val(JSON.stringify(pad.collabClient.getMissedChanges())); - $('form#reconnectform').submit(); - }, - // this is called from code put into a frame from the server: - handleImportExportFrameCall: function(callName, varargs) - { - padimpexp.handleFrameCall.call(padimpexp, callName, Array.prototype.slice.call(arguments, 1)); - }, - callWhenNotCommitting: function(f) - { - pad.collabClient.callWhenNotCommitting(f); - }, - getCollabRevisionNumber: function() - { - return pad.collabClient.getCurrentRevisionNumber(); - }, - isFullyConnected: function() - { - return padconnectionstatus.isFullyConnected(); - }, - addHistoricalAuthors: function(data) - { - if (!pad.collabClient) + padsavedrevs.newRevisionList(msg.revisionList); + } + else if (msg.type == 'padoptions') + { + var opts = msg.options; + pad.handleOptionsChange(opts); + } + else if (msg.type == 'guestanswer') + { + // someone answered a prompt, remove it + paduserlist.removeGuestPrompt(msg.guestId); + } + }, + dmesg: function(m) + { + if (pad.getIsDebugEnabled()) + { + var djs = $('#djs').get(0); + var wasAtBottom = (djs.scrollTop - (djs.scrollHeight - $(djs).height()) >= -20); + $('#djs').append('' + m + '
'); + if (wasAtBottom) + { + djs.scrollTop = djs.scrollHeight; + } + } + }, + handleServerMessage: function(m) + { + if (m.type == 'NOTICE') + { + if (m.text) + { + alertBar.displayMessage(function(abar) + { + abar.find("#servermsgdate").text(" (" + padutils.simpleDateTime(new Date) + ")"); + abar.find("#servermsgtext").text(m.text); + }); + } + if (m.js) + { + window['ev' + 'al'](m.js); + } + } + else if (m.type == 'GUEST_PROMPT') + { + paduserlist.showGuestPrompt(m.userId, m.displayName); + } + }, + handleChannelStateChange: function(newState, message) + { + var oldFullyConnected = !! padconnectionstatus.isFullyConnected(); + var wasConnecting = (padconnectionstatus.getStatus().what == 'connecting'); + if (newState == "CONNECTED") + { + padconnectionstatus.connected(); + } + else if (newState == "RECONNECTING") + { + padconnectionstatus.reconnecting(); + } + else if (newState == "DISCONNECTED") + { + pad.diagnosticInfo.disconnectedMessage = message; + pad.diagnosticInfo.padId = pad.getPadId(); + pad.diagnosticInfo.socket = {}; + + //we filter non objects from the socket object and put them in the diagnosticInfo + //this ensures we have no cyclic data - this allows us to stringify the data + for(var i in socket.socket) + { + var value = socket.socket[i]; + var type = typeof value; + + if(type == "string" || type == "number") + { + pad.diagnosticInfo.socket[i] = value; + } + } + + pad.asyncSendDiagnosticInfo(); + if (typeof window.ajlog == "string") + { + window.ajlog += ("Disconnected: " + message + '\n'); + } + padeditor.disable(); + padeditbar.disable(); + padimpexp.disable(); + + padconnectionstatus.disconnected(message); + } + var newFullyConnected = !! padconnectionstatus.isFullyConnected(); + if (newFullyConnected != oldFullyConnected) + { + pad.handleIsFullyConnected(newFullyConnected, wasConnecting); + } + }, + handleIsFullyConnected: function(isConnected, isInitialConnect) + { + pad.determineChatVisibility(isConnected && !isInitialConnect); + pad.determineChatAndUsersVisibility(isConnected && !isInitialConnect); + pad.determineAuthorshipColorsVisibility(); + }, + determineChatVisibility: function(asNowConnectedFeedback){ + var chatVisCookie = padcookie.getPref('chatAlwaysVisible'); + if(chatVisCookie){ // if the cookie is set for chat always visible + chat.stickToScreen(true); // stick it to the screen + $('#options-stickychat').prop("checked", true); // set the checkbox to on + } + else{ + $('#options-stickychat').prop("checked", false); // set the checkbox for off + } + }, + determineChatAndUsersVisibility: function(asNowConnectedFeedback){ + var chatAUVisCookie = padcookie.getPref('chatAndUsersVisible'); + if(chatAUVisCookie){ // if the cookie is set for chat always visible + chat.chatAndUsers(true); // stick it to the screen + $('#options-chatandusers').prop("checked", true); // set the checkbox to on + } + else{ + $('#options-chatandusers').prop("checked", false); // set the checkbox for off + } + }, + determineAuthorshipColorsVisibility: function(){ + var authColCookie = padcookie.getPref('showAuthorshipColors'); + if (authColCookie){ + pad.changeViewOption('showAuthorColors', true); + $('#options-colorscheck').prop("checked", true); + } + else { + $('#options-colorscheck').prop("checked", false); + } + }, + handleCollabAction: function(action) + { + if (action == "commitPerformed") + { + padeditbar.setSyncStatus("syncing"); + } + else if (action == "newlyIdle") + { + padeditbar.setSyncStatus("done"); + } + }, + hideServerMessage: function() + { + alertBar.hideMessage(); + }, + asyncSendDiagnosticInfo: function() { window.setTimeout(function() { - pad.addHistoricalAuthors(data); - }, 1000); - } - else - { - pad.collabClient.addHistoricalAuthors(data); - } - } -}; - -var alertBar = (function() -{ - - var animator = padutils.makeShowHideAnimator(arriveAtAnimationState, false, 25, 400); - - function arriveAtAnimationState(state) - { - if (state == -1) - { - $("#alertbar").css('opacity', 0).css('display', 'block'); - } - else if (state == 0) - { - $("#alertbar").css('opacity', 1); - } - else if (state == 1) - { - $("#alertbar").css('opacity', 0).css('display', 'none'); - } - else if (state < 0) - { - $("#alertbar").css('opacity', state + 1); - } - else if (state > 0) - { - $("#alertbar").css('opacity', 1 - state); - } - } - - var self = { - displayMessage: function(setupFunc) - { - animator.show(); - setupFunc($("#alertbar")); + $.ajax( + { + type: 'post', + url: '/ep/pad/connection-diagnostic-info', + data: { + diagnosticInfo: JSON.stringify(pad.diagnosticInfo) + }, + success: function() + {}, + error: function() + {} + }); + }, 0); }, - hideMessage: function() + forceReconnect: function() { - animator.hide(); + $('form#reconnectform input.padId').val(pad.getPadId()); + pad.diagnosticInfo.collabDiagnosticInfo = pad.collabClient.getDiagnosticInfo(); + $('form#reconnectform input.diagnosticInfo').val(JSON.stringify(pad.diagnosticInfo)); + $('form#reconnectform input.missedChanges').val(JSON.stringify(pad.collabClient.getMissedChanges())); + $('form#reconnectform').submit(); + }, + // this is called from code put into a frame from the server: + handleImportExportFrameCall: function(callName, varargs) + { + padimpexp.handleFrameCall.call(padimpexp, callName, Array.prototype.slice.call(arguments, 1)); + }, + callWhenNotCommitting: function(f) + { + pad.collabClient.callWhenNotCommitting(f); + }, + getCollabRevisionNumber: function() + { + return pad.collabClient.getCurrentRevisionNumber(); + }, + isFullyConnected: function() + { + return padconnectionstatus.isFullyConnected(); + }, + addHistoricalAuthors: function(data) + { + if (!pad.collabClient) + { + window.setTimeout(function() + { + pad.addHistoricalAuthors(data); + }, 1000); + } + else + { + pad.collabClient.addHistoricalAuthors(data); + } } }; - return self; -}()); -var hooks = undefined; + var alertBar = (function() + { -function init() { - requirejs(['ep_etherpad-lite/static/js/pluginfw/hooks'], function (h) { - hooks = h; + var animator = padutils.makeShowHideAnimator(arriveAtAnimationState, false, 25, 400); + + function arriveAtAnimationState(state) + { + if (state == -1) + { + $("#alertbar").css('opacity', 0).css('display', 'block'); + } + else if (state == 0) + { + $("#alertbar").css('opacity', 1); + } + else if (state == 1) + { + $("#alertbar").css('opacity', 0).css('display', 'none'); + } + else if (state < 0) + { + $("#alertbar").css('opacity', state + 1); + } + else if (state > 0) + { + $("#alertbar").css('opacity', 1 - state); + } + } + + var self = { + displayMessage: function(setupFunc) + { + animator.show(); + setupFunc($("#alertbar")); + }, + hideMessage: function() + { + animator.hide(); + } + }; + return self; + }()); + + function init() { return pad.init(); - }); -} + } -var settings = { - LineNumbersDisabled: false -, noColors: false -, useMonospaceFontGlobal: false -, globalUserName: false -, globalUserColor: false -, rtlIsTrue: false -}; + var settings = { + LineNumbersDisabled: false + , noColors: false + , useMonospaceFontGlobal: false + , globalUserName: false + , globalUserColor: false + , rtlIsTrue: false + }; -pad.settings = settings; -exports.baseURL = ''; -exports.settings = settings; -exports.createCookie = createCookie; -exports.readCookie = readCookie; -exports.randomString = randomString; -exports.getParams = getParams; -exports.getUrlVars = getUrlVars; -exports.savePassword = savePassword; -exports.handshake = handshake; -exports.pad = pad; -exports.init = init; -exports.alertBar = alertBar; + pad.settings = settings; + exports.baseURL = ''; + exports.settings = settings; + exports.createCookie = createCookie; + exports.readCookie = readCookie; + exports.randomString = randomString; + exports.getParams = getParams; + exports.getUrlVars = getUrlVars; + exports.savePassword = savePassword; + exports.handshake = handshake; + exports.pad = pad; + exports.init = init; + exports.alertBar = alertBar; + + return exports; +}); diff --git a/src/static/js/pad_connectionstatus.js b/src/static/js/pad_connectionstatus.js index 76eedbc4d..2686f6388 100644 --- a/src/static/js/pad_connectionstatus.js +++ b/src/static/js/pad_connectionstatus.js @@ -20,69 +20,77 @@ * limitations under the License. */ -var padmodals = require('./pad_modals').padmodals; +define([ + 'ep_etherpad-lite/static/js/pad_modals' +], function(padModalsModule) { + var exports = {}; -var padconnectionstatus = (function() -{ + var padmodals = padModalsModule.padmodals; - var status = { - what: 'connecting' - }; + var padconnectionstatus = (function() + { - var self = { - init: function() - { - $('button#forcereconnect').click(function() + var status = { + what: 'connecting' + }; + + var self = { + init: function() { - window.location.reload(); - }); - }, - connected: function() - { - status = { - what: 'connected' - }; - padmodals.showModal('connected'); - padmodals.hideOverlay(); - }, - reconnecting: function() - { - status = { - what: 'reconnecting' - }; - - padmodals.showModal('reconnecting'); - padmodals.showOverlay(); - }, - disconnected: function(msg) - { - if(status.what == "disconnected") - return; - - status = { - what: 'disconnected', - why: msg - }; - - var k = String(msg); // known reason why - if (!(k == 'userdup' || k == 'deleted' || k == 'looping' || k == 'slowcommit' || k == 'initsocketfail' || k == 'unauth' || k == 'badChangeset' || k == 'corruptPad')) + $('button#forcereconnect').click(function() + { + window.location.reload(); + }); + }, + connected: function() { - k = 'disconnected'; + status = { + what: 'connected' + }; + padmodals.showModal('connected'); + padmodals.hideOverlay(); + }, + reconnecting: function() + { + status = { + what: 'reconnecting' + }; + + padmodals.showModal('reconnecting'); + padmodals.showOverlay(); + }, + disconnected: function(msg) + { + if(status.what == "disconnected") + return; + + status = { + what: 'disconnected', + why: msg + }; + + var k = String(msg); // known reason why + if (!(k == 'userdup' || k == 'deleted' || k == 'looping' || k == 'slowcommit' || k == 'initsocketfail' || k == 'unauth' || k == 'badChangeset' || k == 'corruptPad')) + { + k = 'disconnected'; + } + + padmodals.showModal(k); + padmodals.showOverlay(); + }, + isFullyConnected: function() + { + return status.what == 'connected'; + }, + getStatus: function() + { + return status; } + }; + return self; + }()); - padmodals.showModal(k); - padmodals.showOverlay(); - }, - isFullyConnected: function() - { - return status.what == 'connected'; - }, - getStatus: function() - { - return status; - } - }; - return self; -}()); + exports.padconnectionstatus = padconnectionstatus; -exports.padconnectionstatus = padconnectionstatus; + return exports; +}); diff --git a/src/static/js/pad_cookie.js b/src/static/js/pad_cookie.js index 9866dbfdd..67503a754 100644 --- a/src/static/js/pad_cookie.js +++ b/src/static/js/pad_cookie.js @@ -21,113 +21,118 @@ */ -var padcookie = (function() -{ - function getRawCookie() +define([], function () { + var exports = {}; + var padcookie = (function() { - // returns null if can't get cookie text - if (!document.cookie) + function getRawCookie() { - return null; - } - // look for (start of string OR semicolon) followed by whitespace followed by prefs=(something); - var regexResult = document.cookie.match(/(?:^|;)\s*prefs=([^;]*)(?:;|$)/); - if ((!regexResult) || (!regexResult[1])) - { - return null; - } - return regexResult[1]; - } - - function setRawCookie(safeText) - { - var expiresDate = new Date(); - expiresDate.setFullYear(3000); - document.cookie = ('prefs=' + safeText + ';expires=' + expiresDate.toGMTString()); - } - - function parseCookie(text) - { - // returns null if can't parse cookie. - try - { - var cookieData = JSON.parse(unescape(text)); - return cookieData; - } - catch (e) - { - return null; - } - } - - function stringifyCookie(data) - { - return escape(JSON.stringify(data)); - } - - function saveCookie() - { - if (!inited) - { - return; - } - setRawCookie(stringifyCookie(cookieData)); - - if ((!getRawCookie()) && (!alreadyWarnedAboutNoCookies)) - { - alert("Warning: it appears that your browser does not have cookies enabled." + " EtherPad uses cookies to keep track of unique users for the purpose" + " of putting a quota on the number of active users. Using EtherPad without " + " cookies may fill up your server's user quota faster than expected."); - alreadyWarnedAboutNoCookies = true; - } - } - - var wasNoCookie = true; - var cookieData = {}; - var alreadyWarnedAboutNoCookies = false; - var inited = false; - - var pad = undefined; - var self = { - init: function(prefsToSet, _pad) - { - pad = _pad; - - var rawCookie = getRawCookie(); - if (rawCookie) + // returns null if can't get cookie text + if (!document.cookie) { - var cookieObj = parseCookie(rawCookie); - if (cookieObj) + return null; + } + // look for (start of string OR semicolon) followed by whitespace followed by prefs=(something); + var regexResult = document.cookie.match(/(?:^|;)\s*prefs=([^;]*)(?:;|$)/); + if ((!regexResult) || (!regexResult[1])) + { + return null; + } + return regexResult[1]; + } + + function setRawCookie(safeText) + { + var expiresDate = new Date(); + expiresDate.setFullYear(3000); + document.cookie = ('prefs=' + safeText + ';expires=' + expiresDate.toGMTString()); + } + + function parseCookie(text) + { + // returns null if can't parse cookie. + try + { + var cookieData = JSON.parse(unescape(text)); + return cookieData; + } + catch (e) + { + return null; + } + } + + function stringifyCookie(data) + { + return escape(JSON.stringify(data)); + } + + function saveCookie() + { + if (!inited) + { + return; + } + setRawCookie(stringifyCookie(cookieData)); + + if ((!getRawCookie()) && (!alreadyWarnedAboutNoCookies)) + { + alert("Warning: it appears that your browser does not have cookies enabled." + " EtherPad uses cookies to keep track of unique users for the purpose" + " of putting a quota on the number of active users. Using EtherPad without " + " cookies may fill up your server's user quota faster than expected."); + alreadyWarnedAboutNoCookies = true; + } + } + + var wasNoCookie = true; + var cookieData = {}; + var alreadyWarnedAboutNoCookies = false; + var inited = false; + + var pad = undefined; + var self = { + init: function(prefsToSet, _pad) + { + pad = _pad; + + var rawCookie = getRawCookie(); + if (rawCookie) { - wasNoCookie = false; // there was a cookie - delete cookieObj.userId; - delete cookieObj.name; - delete cookieObj.colorId; - cookieData = cookieObj; + var cookieObj = parseCookie(rawCookie); + if (cookieObj) + { + wasNoCookie = false; // there was a cookie + delete cookieObj.userId; + delete cookieObj.name; + delete cookieObj.colorId; + cookieData = cookieObj; + } } - } - for (var k in prefsToSet) + for (var k in prefsToSet) + { + cookieData[k] = prefsToSet[k]; + } + + inited = true; + saveCookie(); + }, + wasNoCookie: function() { - cookieData[k] = prefsToSet[k]; + return wasNoCookie; + }, + getPref: function(prefName) + { + return cookieData[prefName]; + }, + setPref: function(prefName, value) + { + cookieData[prefName] = value; + saveCookie(); } + }; + return self; + }()); - inited = true; - saveCookie(); - }, - wasNoCookie: function() - { - return wasNoCookie; - }, - getPref: function(prefName) - { - return cookieData[prefName]; - }, - setPref: function(prefName, value) - { - cookieData[prefName] = value; - saveCookie(); - } - }; - return self; -}()); + exports.padcookie = padcookie; -exports.padcookie = padcookie; + return exports; +}); diff --git a/src/static/js/pad_editbar.js b/src/static/js/pad_editbar.js index d0b020706..ff687e7e8 100644 --- a/src/static/js/pad_editbar.js +++ b/src/static/js/pad_editbar.js @@ -20,462 +20,471 @@ * limitations under the License. */ -var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); -var padutils = require('./pad_utils').padutils; -var padeditor = require('./pad_editor').padeditor; -var padsavedrevs = require('./pad_savedrevs'); +define([ + 'ep_etherpad-lite/static/js/pluginfw/hooks', + 'ep_etherpad-lite/static/js/pad_utils', + 'ep_etherpad-lite/static/js/pad_editor', + 'ep_etherpad-lite/static/js/pad_savedrevs' +], function(hooks, padUtilsModule, padEditorModule, padsavedrevs) { + var exports = {}; -var ToolbarItem = function (element) { - this.$el = element; -}; + var padutils = padUtilsModule.padutils; + var padeditor = padEditorModule.padeditor; -ToolbarItem.prototype.getCommand = function () { - return this.$el.attr("data-key"); -}; + var ToolbarItem = function (element) { + this.$el = element; + }; -ToolbarItem.prototype.getValue = function () { - if (this.isSelect()) { - return this.$el.find("select").val(); - } -}; + ToolbarItem.prototype.getCommand = function () { + return this.$el.attr("data-key"); + }; -ToolbarItem.prototype.setValue = function (val) { - if (this.isSelect()) { - return this.$el.find("select").val(val); - } -}; - - -ToolbarItem.prototype.getType = function () { - return this.$el.attr("data-type"); -}; - -ToolbarItem.prototype.isSelect = function () { - return this.getType() == "select"; -}; - -ToolbarItem.prototype.isButton = function () { - return this.getType() == "button"; -}; - -ToolbarItem.prototype.bind = function (callback) { - var self = this; - - if (self.isButton()) { - self.$el.click(function (event) { - $(':focus').blur(); - callback(self.getCommand(), self); - event.preventDefault(); - }); - } - else if (self.isSelect()) { - self.$el.find("select").change(function () { - callback(self.getCommand(), self); - }); - } -}; - - -var padeditbar = (function() -{ - - var syncAnimation = (function() - { - var SYNCING = -100; - var DONE = 100; - var state = DONE; - var fps = 25; - var step = 1 / fps; - var T_START = -0.5; - var T_FADE = 1.0; - var T_GONE = 1.5; - var animator = padutils.makeAnimationScheduler(function() - { - if (state == SYNCING || state == DONE) - { - return false; - } - else if (state >= T_GONE) - { - state = DONE; - $("#syncstatussyncing").css('display', 'none'); - $("#syncstatusdone").css('display', 'none'); - return false; - } - else if (state < 0) - { - state += step; - if (state >= 0) - { - $("#syncstatussyncing").css('display', 'none'); - $("#syncstatusdone").css('display', 'block').css('opacity', 1); - } - return true; - } - else - { - state += step; - if (state >= T_FADE) - { - $("#syncstatusdone").css('opacity', (T_GONE - state) / (T_GONE - T_FADE)); - } - return true; - } - }, step * 1000); - return { - syncing: function() - { - state = SYNCING; - $("#syncstatussyncing").css('display', 'block'); - $("#syncstatusdone").css('display', 'none'); - }, - done: function() - { - state = T_START; - animator.scheduleAnimation(); - } - }; - }()); - - var self = { - init: function() { - var self = this; - self.dropdowns = []; - // Listen for resize events (sucks but needed as iFrame ace_inner has to be position absolute - // A CSS fix for this would be nice but I'm not sure how we'd do it. - $(window).resize(function(){ - self.redrawHeight(); - }); - - $("#editbar .editbarbutton").attr("unselectable", "on"); // for IE - $("#editbar").removeClass("disabledtoolbar").addClass("enabledtoolbar"); - $("#editbar [data-key]").each(function () { - $(this).unbind("click"); - (new ToolbarItem($(this))).bind(function (command, item) { - self.triggerCommand(command, item); - }); - }); - - $('body:not(#editorcontainerbox)').on("keydown", function(evt){ - bodyKeyEvent(evt); - }); - - $('#editbar').show(); - - this.redrawHeight(); - - registerDefaultCommands(self); - - hooks.callAll("postToolbarInit", { - toolbar: self, - ace: padeditor.ace - }); - }, - isEnabled: function() - { -// return !$("#editbar").hasClass('disabledtoolbar'); - return true; - }, - disable: function() - { - $("#editbar").addClass('disabledtoolbar').removeClass("enabledtoolbar"); - }, - commands: {}, - registerCommand: function (cmd, callback) { - this.commands[cmd] = callback; - return this; - }, - redrawHeight: function(){ - var editbarHeight = $('.menu_left').height() + 1 + "px"; - var containerTop = $('.menu_left').height() + 6 + "px"; - $('#editbar').css("height", editbarHeight); - - $('#editorcontainer').css("top", containerTop); - - // make sure pop ups are in the right place - if($('#editorcontainer').offset()){ - $('.popup').css("top", $('#editorcontainer').offset().top + "px"); - } - - // If sticky chat is enabled.. - if($('#options-stickychat').is(":checked")){ - if($('#editorcontainer').offset()){ - $('#chatbox').css("top", $('#editorcontainer').offset().top + "px"); - } - }; - - // If chat and Users is enabled.. - if($('#options-chatandusers').is(":checked")){ - if($('#editorcontainer').offset()){ - $('#users').css("top", $('#editorcontainer').offset().top + "px"); - } - } - - }, - registerDropdownCommand: function (cmd, dropdown) { - dropdown = dropdown || cmd; - self.dropdowns.push(dropdown) - this.registerCommand(cmd, function () { - self.toggleDropDown(dropdown); - }); - }, - registerAceCommand: function (cmd, callback) { - this.registerCommand(cmd, function (cmd, ace) { - ace.callWithAce(function (ace) { - callback(cmd, ace); - }, cmd, true); - }); - }, - triggerCommand: function (cmd, item) { - if (self.isEnabled() && this.commands[cmd]) { - this.commands[cmd](cmd, padeditor.ace, item); - } - if(padeditor.ace) padeditor.ace.focus(); - }, - toggleDropDown: function(moduleName, cb) - { - // hide all modules and remove highlighting of all buttons - if(moduleName == "none") - { - var returned = false - for(var i=0;i