From 250b8928bd48efe797d73bf975b45beee90e6d8c Mon Sep 17 00:00:00 2001 From: Egil Moeller Date: Sun, 12 Apr 2015 21:03:59 +0200 Subject: [PATCH] The Big Rewrite to AMD format - had to do lots of them at once --- src/node/db/AuthorManager.js | 2 +- src/node/db/GroupManager.js | 2 +- src/static/js/broadcast_slider.js | 953 ++++++------- src/static/js/chat.js | 492 +++---- src/static/js/collab_client.js | 1162 ++++++++-------- src/static/js/pad.js | 1789 ++++++++++++------------- src/static/js/pad_connectionstatus.js | 124 +- src/static/js/pad_cookie.js | 205 +-- src/static/js/pad_editbar.js | 855 ++++++------ src/static/js/pad_editor.js | 369 ++--- src/static/js/pad_modals.js | 62 +- src/static/js/pad_savedrevs.js | 39 +- src/static/js/pad_userlist.js | 1612 +++++++++++----------- src/static/js/pad_utils.js | 917 +++++++------ src/static/js/pluginfw/plugins.js | 1 + src/static/js/random_utils.js | 44 + src/static/js/rjquery.js | 4 +- src/static/js/timeslider.js | 314 ++--- src/templates/pad.html | 24 +- src/templates/timeslider.html | 60 +- 20 files changed, 4582 insertions(+), 4448 deletions(-) create mode 100644 src/static/js/random_utils.js 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 = "

" + authorName + ":" + ctx.timeStr + " " + ctx.text + "

"; - 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 = "

" + authorName + ":" + ctx.timeStr + " " + ctx.text + "

"; + 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 a").removeClass("selected"); - module.slideUp("fast", cb); - returned = true; - } - } - if(!returned && cb) return cb(); - } - else - { - // hide all modules that are not selected and remove highlighting - // respectively add highlighting to the corresponding button - for(var i=0;i a").removeClass("selected"); - module.slideUp("fast"); - } - else if(self.dropdowns[i]==moduleName) - { - $("li[data-key=" + self.dropdowns[i] + "] > a").addClass("selected"); - module.slideDown("fast", cb); - } - } - } - }, - setSyncStatus: function(status) - { - if (status == "syncing") - { - syncAnimation.syncing(); - } - else if (status == "done") - { - syncAnimation.done(); - } - }, - setEmbedLinks: function() - { - if ($('#readonlyinput').is(':checked')) - { - var basePath = document.location.href.substring(0, document.location.href.indexOf("/p/")); - var readonlyLink = basePath + "/p/" + clientVars.readOnlyId; - $('#embedinput').val(""); - $('#linkinput').val(readonlyLink); - } - else - { - var padurl = window.location.href.split("?")[0]; - $('#embedinput').val(""); - $('#linkinput').val(padurl); - } + ToolbarItem.prototype.getValue = function () { + if (this.isSelect()) { + return this.$el.find("select").val(); } }; - var editbarPosition = 0; + ToolbarItem.prototype.setValue = function (val) { + if (this.isSelect()) { + return this.$el.find("select").val(val); + } + }; - function bodyKeyEvent(evt){ - // If the event is Alt F9 or Escape & we're already in the editbar menu - // Send the users focus back to the pad - if((evt.keyCode === 120 && evt.altKey) || evt.keyCode === 27){ - if($(':focus').parents(".toolbar").length === 1){ - // If we're in the editbar already.. - // Close any dropdowns we have open.. - padeditbar.toggleDropDown("none"); - // Check we're on a pad and not on the timeslider - // Or some other window I haven't thought about! - if(typeof pad === 'undefined'){ - // Timeslider probably.. - // Shift focus away from any drop downs - $(':focus').blur(); // required to do not try to remove! - $('#padmain').focus(); // Focus back onto the pad + 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 a").removeClass("selected"); + module.slideUp("fast", cb); + returned = true; + } + } + if(!returned && cb) return cb(); + } + else + { + // hide all modules that are not selected and remove highlighting + // respectively add highlighting to the corresponding button + for(var i=0;i a").removeClass("selected"); + module.slideUp("fast"); + } + else if(self.dropdowns[i]==moduleName) + { + $("li[data-key=" + self.dropdowns[i] + "] > a").addClass("selected"); + module.slideDown("fast", cb); + } + } + } + }, + setSyncStatus: function(status) + { + if (status == "syncing") + { + syncAnimation.syncing(); + } + else if (status == "done") + { + syncAnimation.done(); + } + }, + setEmbedLinks: function() + { + if ($('#readonlyinput').is(':checked')) + { + var basePath = document.location.href.substring(0, document.location.href.indexOf("/p/")); + var readonlyLink = basePath + "/p/" + clientVars.readOnlyId; + $('#embedinput').val(""); + $('#linkinput').val(readonlyLink); + } + else + { + var padurl = window.location.href.split("?")[0]; + $('#embedinput').val(""); + $('#linkinput').val(padurl); + } + } + }; + + var editbarPosition = 0; + + function bodyKeyEvent(evt){ + + // If the event is Alt F9 or Escape & we're already in the editbar menu + // Send the users focus back to the pad + if((evt.keyCode === 120 && evt.altKey) || evt.keyCode === 27){ + if($(':focus').parents(".toolbar").length === 1){ + // If we're in the editbar already.. + // Close any dropdowns we have open.. + padeditbar.toggleDropDown("none"); + // Check we're on a pad and not on the timeslider + // Or some other window I haven't thought about! + if(typeof pad === 'undefined'){ + // Timeslider probably.. + // Shift focus away from any drop downs + $(':focus').blur(); // required to do not try to remove! + $('#padmain').focus(); // Focus back onto the pad + }else{ + // Shift focus away from any drop downs + $(':focus').blur(); // required to do not try to remove! + padeditor.ace.focus(); // Sends focus back to pad + // The above focus doesn't always work in FF, you have to hit enter afterwards + evt.preventDefault(); + } }else{ - // Shift focus away from any drop downs - $(':focus').blur(); // required to do not try to remove! - padeditor.ace.focus(); // Sends focus back to pad - // The above focus doesn't always work in FF, you have to hit enter afterwards + // Focus on the editbar :) + var firstEditbarElement = parent.parent.$('#editbar').children("ul").first().children().first().children().first().children().first(); + $(this).blur(); + firstEditbarElement.focus(); evt.preventDefault(); } - }else{ - // Focus on the editbar :) - var firstEditbarElement = parent.parent.$('#editbar').children("ul").first().children().first().children().first().children().first(); - $(this).blur(); - firstEditbarElement.focus(); - evt.preventDefault(); } - } - // Are we in the toolbar?? - if($(':focus').parents(".toolbar").length === 1){ - // On arrow keys go to next/previous button item in editbar - if(evt.keyCode !== 39 && evt.keyCode !== 37) return; + // Are we in the toolbar?? + if($(':focus').parents(".toolbar").length === 1){ + // On arrow keys go to next/previous button item in editbar + if(evt.keyCode !== 39 && evt.keyCode !== 37) return; - // Get all the focusable items in the editbar - var focusItems = $('#editbar').find('button, select'); + // Get all the focusable items in the editbar + var focusItems = $('#editbar').find('button, select'); - // On left arrow move to next button in editbar - if(evt.keyCode === 37){ - // If a dropdown is visible or we're in an input don't move to the next button - if($('.popup').is(":visible") || evt.target.localName === "input") return; + // On left arrow move to next button in editbar + if(evt.keyCode === 37){ + // If a dropdown is visible or we're in an input don't move to the next button + if($('.popup').is(":visible") || evt.target.localName === "input") return; - editbarPosition--; - // Allow focus to shift back to end of row and start of row - if(editbarPosition === -1) editbarPosition = focusItems.length -1; - $(focusItems[editbarPosition]).focus() - } - - // On right arrow move to next button in editbar - if(evt.keyCode === 39){ - // If a dropdown is visible or we're in an input don't move to the next button - if($('.popup').is(":visible") || evt.target.localName === "input") return; - - editbarPosition++; - // Allow focus to shift back to end of row and start of row - if(editbarPosition >= focusItems.length) editbarPosition = 0; - $(focusItems[editbarPosition]).focus(); - } - } - - } - - function aceAttributeCommand(cmd, ace) { - ace.ace_toggleAttributeOnSelection(cmd); - } - - function registerDefaultCommands(toolbar) { - toolbar.registerDropdownCommand("showusers", "users"); - toolbar.registerDropdownCommand("settings"); - toolbar.registerDropdownCommand("connectivity"); - toolbar.registerDropdownCommand("import_export"); - toolbar.registerDropdownCommand("embed"); - - toolbar.registerCommand("settings", function () { - toolbar.toggleDropDown("settings", function(){ - $('#options-stickychat').focus(); - }); - }); - - toolbar.registerCommand("import_export", function () { - toolbar.toggleDropDown("import_export", function(){ - // If Import file input exists then focus on it.. - if($('#importfileinput').length !== 0){ - setTimeout(function(){ - $('#importfileinput').focus(); - }, 100); - }else{ - $('.exportlink').first().focus(); + editbarPosition--; + // Allow focus to shift back to end of row and start of row + if(editbarPosition === -1) editbarPosition = focusItems.length -1; + $(focusItems[editbarPosition]).focus() } + + // On right arrow move to next button in editbar + if(evt.keyCode === 39){ + // If a dropdown is visible or we're in an input don't move to the next button + if($('.popup').is(":visible") || evt.target.localName === "input") return; + + editbarPosition++; + // Allow focus to shift back to end of row and start of row + if(editbarPosition >= focusItems.length) editbarPosition = 0; + $(focusItems[editbarPosition]).focus(); + } + } + + } + + function aceAttributeCommand(cmd, ace) { + ace.ace_toggleAttributeOnSelection(cmd); + } + + function registerDefaultCommands(toolbar) { + toolbar.registerDropdownCommand("showusers", "users"); + toolbar.registerDropdownCommand("settings"); + toolbar.registerDropdownCommand("connectivity"); + toolbar.registerDropdownCommand("import_export"); + toolbar.registerDropdownCommand("embed"); + + toolbar.registerCommand("settings", function () { + toolbar.toggleDropDown("settings", function(){ + $('#options-stickychat').focus(); + }); }); - }); - toolbar.registerCommand("showusers", function () { - toolbar.toggleDropDown("users", function(){ - $('#myusernameedit').focus(); + toolbar.registerCommand("import_export", function () { + toolbar.toggleDropDown("import_export", function(){ + // If Import file input exists then focus on it.. + if($('#importfileinput').length !== 0){ + setTimeout(function(){ + $('#importfileinput').focus(); + }, 100); + }else{ + $('.exportlink').first().focus(); + } + }); }); - }); - toolbar.registerCommand("embed", function () { - toolbar.setEmbedLinks(); - toolbar.toggleDropDown("embed", function(){ - $('#linkinput').focus().select(); + toolbar.registerCommand("showusers", function () { + toolbar.toggleDropDown("users", function(){ + $('#myusernameedit').focus(); + }); }); - }); - toolbar.registerCommand("savedRevision", function () { - padsavedrevs.saveNow(); - }); + toolbar.registerCommand("embed", function () { + toolbar.setEmbedLinks(); + toolbar.toggleDropDown("embed", function(){ + $('#linkinput').focus().select(); + }); + }); - toolbar.registerCommand("showTimeSlider", function () { - document.location = document.location.pathname+ '/timeslider'; - }); + toolbar.registerCommand("savedRevision", function () { + padsavedrevs.saveNow(); + }); - toolbar.registerAceCommand("bold", aceAttributeCommand); - toolbar.registerAceCommand("italic", aceAttributeCommand); - toolbar.registerAceCommand("underline", aceAttributeCommand); - toolbar.registerAceCommand("strikethrough", aceAttributeCommand); + toolbar.registerCommand("showTimeSlider", function () { + document.location = document.location.pathname+ '/timeslider'; + }); - toolbar.registerAceCommand("undo", function (cmd, ace) { - ace.ace_doUndoRedo(cmd); - }); + toolbar.registerAceCommand("bold", aceAttributeCommand); + toolbar.registerAceCommand("italic", aceAttributeCommand); + toolbar.registerAceCommand("underline", aceAttributeCommand); + toolbar.registerAceCommand("strikethrough", aceAttributeCommand); - toolbar.registerAceCommand("redo", function (cmd, ace) { - ace.ace_doUndoRedo(cmd); - }); + toolbar.registerAceCommand("undo", function (cmd, ace) { + ace.ace_doUndoRedo(cmd); + }); - toolbar.registerAceCommand("insertunorderedlist", function (cmd, ace) { - ace.ace_doInsertUnorderedList(); - }); + toolbar.registerAceCommand("redo", function (cmd, ace) { + ace.ace_doUndoRedo(cmd); + }); - toolbar.registerAceCommand("insertorderedlist", function (cmd, ace) { - ace.ace_doInsertOrderedList(); - }); - - toolbar.registerAceCommand("indent", function (cmd, ace) { - if (!ace.ace_doIndentOutdent(false)) { + toolbar.registerAceCommand("insertunorderedlist", function (cmd, ace) { ace.ace_doInsertUnorderedList(); - } - }); + }); - toolbar.registerAceCommand("outdent", function (cmd, ace) { - ace.ace_doIndentOutdent(true); - }); + toolbar.registerAceCommand("insertorderedlist", function (cmd, ace) { + ace.ace_doInsertOrderedList(); + }); - toolbar.registerAceCommand("clearauthorship", function (cmd, ace) { - if ((!(ace.ace_getRep().selStart && ace.ace_getRep().selEnd)) || ace.ace_isCaret()) { - if (window.confirm(html10n.get("pad.editbar.clearcolors"))) { - ace.ace_performDocumentApplyAttributesToCharRange(0, ace.ace_getRep().alltext.length, [ - ['author', ''] - ]); + toolbar.registerAceCommand("indent", function (cmd, ace) { + if (!ace.ace_doIndentOutdent(false)) { + ace.ace_doInsertUnorderedList(); } - } - else { - ace.ace_setAttributeOnSelection('author', ''); - } - }); + }); - toolbar.registerCommand('timeslider_returnToPad', function(cmd) { - if( document.referrer.length > 0 && document.referrer.substring(document.referrer.lastIndexOf("/")-1, document.referrer.lastIndexOf("/")) === "p") { - document.location = document.referrer; - } else { - document.location = document.location.href.substring(0,document.location.href.lastIndexOf("/")); - } - }); - } + toolbar.registerAceCommand("outdent", function (cmd, ace) { + ace.ace_doIndentOutdent(true); + }); - return self; -}()); + toolbar.registerAceCommand("clearauthorship", function (cmd, ace) { + if ((!(ace.ace_getRep().selStart && ace.ace_getRep().selEnd)) || ace.ace_isCaret()) { + if (window.confirm(html10n.get("pad.editbar.clearcolors"))) { + ace.ace_performDocumentApplyAttributesToCharRange(0, ace.ace_getRep().alltext.length, [ + ['author', ''] + ]); + } + } + else { + ace.ace_setAttributeOnSelection('author', ''); + } + }); -exports.padeditbar = padeditbar; + toolbar.registerCommand('timeslider_returnToPad', function(cmd) { + if( document.referrer.length > 0 && document.referrer.substring(document.referrer.lastIndexOf("/")-1, document.referrer.lastIndexOf("/")) === "p") { + document.location = document.referrer; + } else { + document.location = document.location.href.substring(0,document.location.href.lastIndexOf("/")); + } + }); + } + + return self; + }()); + + exports.padeditbar = padeditbar; + + return exports; +}); diff --git a/src/static/js/pad_editor.js b/src/static/js/pad_editor.js index dc1435d33..088455d94 100644 --- a/src/static/js/pad_editor.js +++ b/src/static/js/pad_editor.js @@ -20,203 +20,212 @@ * limitations under the License. */ -var padcookie = require('./pad_cookie').padcookie; -var padutils = require('./pad_utils').padutils; +define([ + 'ep_etherpad-lite/static/js/pad_cookie', + 'ep_etherpad-lite/static/js/pad_utils' +], function (padCookieModule, padUtilsModule) { + exports = {}; -var padeditor = (function() -{ - var Ace2Editor = undefined; - var pad = undefined; - var settings = undefined; + var padcookie = padCookieModule.padcookie; + var padutils = padUtilsModule.padutils; - // Array of available fonts - var fonts = ['useMonospaceFont', 'useOpenDyslexicFont', 'useComicSansFont', 'useCourierNewFont', 'useGeorgiaFont', 'useImpactFont', - 'useLucidaFont', 'useLucidaSansFont', 'usePalatinoFont', 'useTahomaFont', 'useTimesNewRomanFont', - 'useTrebuchetFont', 'useVerdanaFont', 'useSymbolFont', 'useWebdingsFont', 'useWingDingsFont', 'useSansSerifFont', - 'useSerifFont']; + var padeditor = (function() + { + var Ace2Editor = undefined; + var pad = undefined; + var settings = undefined; - var self = { - ace: null, - // this is accessed directly from other files - viewZoom: 100, - init: function(readyFunc, initialViewOptions, _pad) { - requirejs(['ep_etherpad-lite/static/js/ace'], function (ace) { + // Array of available fonts + var fonts = ['useMonospaceFont', 'useOpenDyslexicFont', 'useComicSansFont', 'useCourierNewFont', 'useGeorgiaFont', 'useImpactFont', + 'useLucidaFont', 'useLucidaSansFont', 'usePalatinoFont', 'useTahomaFont', 'useTimesNewRomanFont', + 'useTrebuchetFont', 'useVerdanaFont', 'useSymbolFont', 'useWebdingsFont', 'useWingDingsFont', 'useSansSerifFont', + 'useSerifFont']; - Ace2Editor = ace.Ace2Editor; - pad = _pad; - settings = pad.settings; + var self = { + ace: null, + // this is accessed directly from other files + viewZoom: 100, + init: function(readyFunc, initialViewOptions, _pad) { + requirejs(['ep_etherpad-lite/static/js/ace'], function (ace) { - function aceReady() - { - $("#editorloadingbox").hide(); - if (readyFunc) + Ace2Editor = ace.Ace2Editor; + pad = _pad; + settings = pad.settings; + + function aceReady() { - readyFunc(); + $("#editorloadingbox").hide(); + if (readyFunc) + { + readyFunc(); + } } - } - self.ace = new Ace2Editor(); - self.ace.init("editorcontainer", "", aceReady); - self.ace.setProperty("wraps", true); - if (pad.getIsDebugEnabled()) + self.ace = new Ace2Editor(); + self.ace.init("editorcontainer", "", aceReady); + self.ace.setProperty("wraps", true); + if (pad.getIsDebugEnabled()) + { + self.ace.setProperty("dmesg", pad.dmesg); + } + self.initViewOptions(); + self.setViewOptions(initialViewOptions); + + // view bar + $("#viewbarcontents").show(); + }); + }, + initViewOptions: function() + { + // Line numbers + padutils.bindCheckboxChange($("#options-linenoscheck"), function() { - self.ace.setProperty("dmesg", pad.dmesg); - } - self.initViewOptions(); - self.setViewOptions(initialViewOptions); - - // view bar - $("#viewbarcontents").show(); - }); - }, - initViewOptions: function() - { - // Line numbers - padutils.bindCheckboxChange($("#options-linenoscheck"), function() - { - pad.changeViewOption('showLineNumbers', padutils.getCheckbox($("#options-linenoscheck"))); - }); - - // Author colors - padutils.bindCheckboxChange($("#options-colorscheck"), function() - { - padcookie.setPref('showAuthorshipColors', padutils.getCheckbox("#options-colorscheck")); - pad.changeViewOption('showAuthorColors', padutils.getCheckbox("#options-colorscheck")); - }); - - // Right to left - padutils.bindCheckboxChange($("#options-rtlcheck"), function() - { - pad.changeViewOption('rtlIsTrue', padutils.getCheckbox($("#options-rtlcheck"))) - }); - html10n.bind('localized', function() { - pad.changeViewOption('rtlIsTrue', ('rtl' == html10n.getDirection())); - padutils.setCheckbox($("#options-rtlcheck"), ('rtl' == html10n.getDirection())); - }) - - // font family change - $("#viewfontmenu").change(function() - { - $.each(fonts, function(i, font){ - var sfont = font.replace("use",""); - sfont = sfont.replace("Font",""); - sfont = sfont.toLowerCase(); - pad.changeViewOption(font, $("#viewfontmenu").val() == sfont); + pad.changeViewOption('showLineNumbers', padutils.getCheckbox($("#options-linenoscheck"))); }); - }); - - // Language - html10n.bind('localized', function() { + + // Author colors + padutils.bindCheckboxChange($("#options-colorscheck"), function() + { + padcookie.setPref('showAuthorshipColors', padutils.getCheckbox("#options-colorscheck")); + pad.changeViewOption('showAuthorColors', padutils.getCheckbox("#options-colorscheck")); + }); + + // Right to left + padutils.bindCheckboxChange($("#options-rtlcheck"), function() + { + pad.changeViewOption('rtlIsTrue', padutils.getCheckbox($("#options-rtlcheck"))) + }); + html10n.bind('localized', function() { + pad.changeViewOption('rtlIsTrue', ('rtl' == html10n.getDirection())); + padutils.setCheckbox($("#options-rtlcheck"), ('rtl' == html10n.getDirection())); + }) + + // font family change + $("#viewfontmenu").change(function() + { + $.each(fonts, function(i, font){ + var sfont = font.replace("use",""); + sfont = sfont.replace("Font",""); + sfont = sfont.toLowerCase(); + pad.changeViewOption(font, $("#viewfontmenu").val() == sfont); + }); + }); + + // Language + html10n.bind('localized', function() { + $("#languagemenu").val(html10n.getLanguage()); + // translate the value of 'unnamed' and 'Enter your name' textboxes in the userlist + // this does not interfere with html10n's normal value-setting because html10n just ingores s + // also, a value which has been set by the user will be not overwritten since a user-edited + // does *not* have the editempty-class + $('input[data-l10n-id]').each(function(key, input){ + input = $(input); + if(input.hasClass("editempty")){ + input.val(html10n.get(input.attr("data-l10n-id"))); + } + }); + }) $("#languagemenu").val(html10n.getLanguage()); - // translate the value of 'unnamed' and 'Enter your name' textboxes in the userlist - // this does not interfere with html10n's normal value-setting because html10n just ingores s - // also, a value which has been set by the user will be not overwritten since a user-edited - // does *not* have the editempty-class - $('input[data-l10n-id]').each(function(key, input){ - input = $(input); - if(input.hasClass("editempty")){ - input.val(html10n.get(input.attr("data-l10n-id"))); + $("#languagemenu").change(function() { + pad.createCookie("language",$("#languagemenu").val(),null,'/'); + window.html10n.localize([$("#languagemenu").val(), 'en']); + }); + }, + setViewOptions: function(newOptions) + { + function getOption(key, defaultValue) + { + var value = String(newOptions[key]); + if (value == "true") return true; + if (value == "false") return false; + return defaultValue; + } + + var v; + + v = getOption('rtlIsTrue', ('rtl' == html10n.getDirection())); + // Override from parameters if true + if(settings.rtlIsTrue === true) v = true; + self.ace.setProperty("rtlIsTrue", v); + padutils.setCheckbox($("#options-rtlcheck"), v); + + v = getOption('showLineNumbers', true); + self.ace.setProperty("showslinenumbers", v); + padutils.setCheckbox($("#options-linenoscheck"), v); + + v = getOption('showAuthorColors', true); + self.ace.setProperty("showsauthorcolors", v); + padutils.setCheckbox($("#options-colorscheck"), v); + + // Override from parameters if true + if (settings.noColors !== false){ + self.ace.setProperty("showsauthorcolors", !settings.noColors); + } + + var normalFont = true; + // Go through each font and see if the option is set.. + $.each(fonts, function(i, font){ + var isEnabled = getOption(font, false); + if(isEnabled){ + font = font.replace("use",""); + font = font.replace("Font",""); + font = font.toLowerCase(); + if(font === "monospace") self.ace.setProperty("textface", "Courier new"); + if(font === "opendyslexic") self.ace.setProperty("textface", "OpenDyslexic"); + if(font === "comicsans") self.ace.setProperty("textface", "Comic Sans MS"); + if(font === "georgia") self.ace.setProperty("textface", "Georgia"); + if(font === "impact") self.ace.setProperty("textface", "Impact"); + if(font === "lucida") self.ace.setProperty("textface", "Lucida"); + if(font === "lucidasans") self.ace.setProperty("textface", "Lucida Sans Unicode"); + if(font === "palatino") self.ace.setProperty("textface", "Palatino Linotype"); + if(font === "tahoma") self.ace.setProperty("textface", "Tahoma"); + if(font === "timesnewroman") self.ace.setProperty("textface", "Times New Roman"); + if(font === "trebuchet") self.ace.setProperty("textface", "Trebuchet MS"); + if(font === "verdana") self.ace.setProperty("textface", "Verdana"); + if(font === "symbol") self.ace.setProperty("textface", "Symbol"); + if(font === "webdings") self.ace.setProperty("textface", "Webdings"); + if(font === "wingdings") self.ace.setProperty("textface", "Wingdings"); + if(font === "sansserif") self.ace.setProperty("textface", "MS Sans Serif"); + if(font === "serif") self.ace.setProperty("textface", "MS Serif"); + + // $("#viewfontmenu").val(font); + normalFont = false; } }); - }) - $("#languagemenu").val(html10n.getLanguage()); - $("#languagemenu").change(function() { - pad.createCookie("language",$("#languagemenu").val(),null,'/'); - window.html10n.localize([$("#languagemenu").val(), 'en']); - }); - }, - setViewOptions: function(newOptions) - { - function getOption(key, defaultValue) - { - var value = String(newOptions[key]); - if (value == "true") return true; - if (value == "false") return false; - return defaultValue; - } - var v; - - v = getOption('rtlIsTrue', ('rtl' == html10n.getDirection())); - // Override from parameters if true - if(settings.rtlIsTrue === true) v = true; - self.ace.setProperty("rtlIsTrue", v); - padutils.setCheckbox($("#options-rtlcheck"), v); - - v = getOption('showLineNumbers', true); - self.ace.setProperty("showslinenumbers", v); - padutils.setCheckbox($("#options-linenoscheck"), v); - - v = getOption('showAuthorColors', true); - self.ace.setProperty("showsauthorcolors", v); - padutils.setCheckbox($("#options-colorscheck"), v); - - // Override from parameters if true - if (settings.noColors !== false){ - self.ace.setProperty("showsauthorcolors", !settings.noColors); - } - - var normalFont = true; - // Go through each font and see if the option is set.. - $.each(fonts, function(i, font){ - var isEnabled = getOption(font, false); - if(isEnabled){ - font = font.replace("use",""); - font = font.replace("Font",""); - font = font.toLowerCase(); - if(font === "monospace") self.ace.setProperty("textface", "Courier new"); - if(font === "opendyslexic") self.ace.setProperty("textface", "OpenDyslexic"); - if(font === "comicsans") self.ace.setProperty("textface", "Comic Sans MS"); - if(font === "georgia") self.ace.setProperty("textface", "Georgia"); - if(font === "impact") self.ace.setProperty("textface", "Impact"); - if(font === "lucida") self.ace.setProperty("textface", "Lucida"); - if(font === "lucidasans") self.ace.setProperty("textface", "Lucida Sans Unicode"); - if(font === "palatino") self.ace.setProperty("textface", "Palatino Linotype"); - if(font === "tahoma") self.ace.setProperty("textface", "Tahoma"); - if(font === "timesnewroman") self.ace.setProperty("textface", "Times New Roman"); - if(font === "trebuchet") self.ace.setProperty("textface", "Trebuchet MS"); - if(font === "verdana") self.ace.setProperty("textface", "Verdana"); - if(font === "symbol") self.ace.setProperty("textface", "Symbol"); - if(font === "webdings") self.ace.setProperty("textface", "Webdings"); - if(font === "wingdings") self.ace.setProperty("textface", "Wingdings"); - if(font === "sansserif") self.ace.setProperty("textface", "MS Sans Serif"); - if(font === "serif") self.ace.setProperty("textface", "MS Serif"); - - // $("#viewfontmenu").val(font); - normalFont = false; + // No font has been previously selected so use the Normal font + if(normalFont){ + self.ace.setProperty("textface", "Arial, sans-serif"); + // $("#viewfontmenu").val("normal"); } - }); - // No font has been previously selected so use the Normal font - if(normalFont){ - self.ace.setProperty("textface", "Arial, sans-serif"); - // $("#viewfontmenu").val("normal"); - } - - }, - dispose: function() - { - if (self.ace) + }, + dispose: function() { - self.ace.destroy(); - self.ace = null; - } - }, - disable: function() - { - if (self.ace) + if (self.ace) + { + self.ace.destroy(); + self.ace = null; + } + }, + disable: function() { - self.ace.setProperty("grayedOut", true); - self.ace.setEditable(false); + if (self.ace) + { + self.ace.setProperty("grayedOut", true); + self.ace.setEditable(false); + } + }, + restoreRevisionText: function(dataFromServer) + { + pad.addHistoricalAuthors(dataFromServer.historicalAuthorData); + self.ace.importAText(dataFromServer.atext, dataFromServer.apool, true); } - }, - restoreRevisionText: function(dataFromServer) - { - pad.addHistoricalAuthors(dataFromServer.historicalAuthorData); - self.ace.importAText(dataFromServer.atext, dataFromServer.apool, true); - } - }; - return self; -}()); + }; + return self; + }()); -exports.padeditor = padeditor; + exports.padeditor = padeditor; + + return exports; +}); diff --git a/src/static/js/pad_modals.js b/src/static/js/pad_modals.js index 67b03662f..ed78ab766 100644 --- a/src/static/js/pad_modals.js +++ b/src/static/js/pad_modals.js @@ -20,32 +20,40 @@ * limitations under the License. */ -var padeditbar = require('./pad_editbar').padeditbar; +define([ + 'ep_etherpad-lite/static/js/pad_editbar' +], function (padEditbarModule) { + var exports = {}; -var padmodals = (function() -{ - var pad = undefined; - var self = { - init: function(_pad) - { - pad = _pad; - }, - showModal: function(messageId) - { - padeditbar.toggleDropDown("none", function() { - $("#connectivity .visible").removeClass('visible'); - $("#connectivity ."+messageId).addClass('visible'); - padeditbar.toggleDropDown("connectivity"); - }); - }, - showOverlay: function() { - $("#overlay").show(); - }, - hideOverlay: function() { - $("#overlay").hide(); - } - }; - return self; -}()); + var padeditbar = padEditbarModule.padeditbar; -exports.padmodals = padmodals; + var padmodals = (function() + { + var pad = undefined; + var self = { + init: function(_pad) + { + pad = _pad; + }, + showModal: function(messageId) + { + padeditbar.toggleDropDown("none", function() { + $("#connectivity .visible").removeClass('visible'); + $("#connectivity ."+messageId).addClass('visible'); + padeditbar.toggleDropDown("connectivity"); + }); + }, + showOverlay: function() { + $("#overlay").show(); + }, + hideOverlay: function() { + $("#overlay").hide(); + } + }; + return self; + }()); + + exports.padmodals = padmodals; + + return exports; +}); diff --git a/src/static/js/pad_savedrevs.js b/src/static/js/pad_savedrevs.js index 34323b22f..55e3de0be 100644 --- a/src/static/js/pad_savedrevs.js +++ b/src/static/js/pad_savedrevs.js @@ -14,22 +14,27 @@ * limitations under the License. */ -var pad; +define([], function() { + var exports = {}; + var pad; -exports.saveNow = function(){ - pad.collabClient.sendMessage({"type": "SAVE_REVISION"}); - $.gritter.add({ - // (string | mandatory) the heading of the notification - title: _("pad.savedrevs.marked"), - // (string | mandatory) the text inside the notification - text: _("pad.savedrevs.timeslider") || "You can view saved revisions in the timeslider", - // (bool | optional) if you want it to fade out on its own or just sit there - sticky: false, - // (int | optional) the time you want it to be alive for before fading out - time: '2000' - }); -} + exports.saveNow = function(){ + pad.collabClient.sendMessage({"type": "SAVE_REVISION"}); + $.gritter.add({ + // (string | mandatory) the heading of the notification + title: _("pad.savedrevs.marked"), + // (string | mandatory) the text inside the notification + text: _("pad.savedrevs.timeslider") || "You can view saved revisions in the timeslider", + // (bool | optional) if you want it to fade out on its own or just sit there + sticky: false, + // (int | optional) the time you want it to be alive for before fading out + time: '2000' + }); + } -exports.init = function(_pad){ - pad = _pad; -} + exports.init = function(_pad){ + pad = _pad; + } + + return exports; +}); diff --git a/src/static/js/pad_userlist.js b/src/static/js/pad_userlist.js index b5f231998..e46d36798 100644 --- a/src/static/js/pad_userlist.js +++ b/src/static/js/pad_userlist.js @@ -20,827 +20,835 @@ * limitations under the License. */ -var padutils = require('./pad_utils').padutils; -var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); +define([ + 'ep_etherpad-lite/static/js/pad_utils', + 'ep_etherpad-lite/static/js/pluginfw/hooks' +], function(padUtilsModule, hooks) { + var exports = {}; -var myUserInfo = {}; + var padutils = padUtilsModule.padutils; -var colorPickerOpen = false; -var colorPickerSetup = false; -var previousColorId = 0; + var myUserInfo = {}; + + var colorPickerOpen = false; + var colorPickerSetup = false; + var previousColorId = 0; -var paduserlist = (function() -{ - - var rowManager = (function() + var paduserlist = (function() { - // The row manager handles rendering rows of the user list and animating - // their insertion, removal, and reordering. It manipulates TD height - // and TD opacity. - function nextRowId() + var rowManager = (function() { - return "usertr" + (nextRowId.counter++); - } - nextRowId.counter = 1; - // objects are shared; fields are "domId","data","animationStep" - var rowsFadingOut = []; // unordered set - var rowsFadingIn = []; // unordered set - var rowsPresent = []; // in order - var ANIMATION_START = -12; // just starting to fade in - var ANIMATION_END = 12; // just finishing fading out + // The row manager handles rendering rows of the user list and animating + // their insertion, removal, and reordering. It manipulates TD height + // and TD opacity. - - function getAnimationHeight(step, power) - { - var a = Math.abs(step / 12); - if (power == 2) a = a * a; - else if (power == 3) a = a * a * a; - else if (power == 4) a = a * a * a * a; - else if (power >= 5) a = a * a * a * a * a; - return Math.round(26 * (1 - a)); - } - var OPACITY_STEPS = 6; - - var ANIMATION_STEP_TIME = 20; - var LOWER_FRAMERATE_FACTOR = 2; - var scheduleAnimation = padutils.makeAnimationScheduler(animateStep, ANIMATION_STEP_TIME, LOWER_FRAMERATE_FACTOR).scheduleAnimation; - - var NUMCOLS = 4; - - // we do lots of manipulation of table rows and stuff that JQuery makes ok, despite - // IE's poor handling when manipulating the DOM directly. - - function getEmptyRowHtml(height) - { - return ''; - } - - function isNameEditable(data) - { - return (!data.name) && (data.status != 'Disconnected'); - } - - function replaceUserRowContents(tr, height, data) - { - var tds = getUserRowHtml(height, data).match(//gi); - if (isNameEditable(data) && tr.find("td.usertdname input:enabled").length > 0) + function nextRowId() { - // preserve input field node - for (var i = 0; i < tds.length; i++) + return "usertr" + (nextRowId.counter++); + } + nextRowId.counter = 1; + // objects are shared; fields are "domId","data","animationStep" + var rowsFadingOut = []; // unordered set + var rowsFadingIn = []; // unordered set + var rowsPresent = []; // in order + var ANIMATION_START = -12; // just starting to fade in + var ANIMATION_END = 12; // just finishing fading out + + + function getAnimationHeight(step, power) + { + var a = Math.abs(step / 12); + if (power == 2) a = a * a; + else if (power == 3) a = a * a * a; + else if (power == 4) a = a * a * a * a; + else if (power >= 5) a = a * a * a * a * a; + return Math.round(26 * (1 - a)); + } + var OPACITY_STEPS = 6; + + var ANIMATION_STEP_TIME = 20; + var LOWER_FRAMERATE_FACTOR = 2; + var scheduleAnimation = padutils.makeAnimationScheduler(animateStep, ANIMATION_STEP_TIME, LOWER_FRAMERATE_FACTOR).scheduleAnimation; + + var NUMCOLS = 4; + + // we do lots of manipulation of table rows and stuff that JQuery makes ok, despite + // IE's poor handling when manipulating the DOM directly. + + function getEmptyRowHtml(height) + { + return ''; + } + + function isNameEditable(data) + { + return (!data.name) && (data.status != 'Disconnected'); + } + + function replaceUserRowContents(tr, height, data) + { + var tds = getUserRowHtml(height, data).match(//gi); + if (isNameEditable(data) && tr.find("td.usertdname input:enabled").length > 0) { - var oldTd = $(tr.find("td").get(i)); - if (!oldTd.hasClass('usertdname')) + // preserve input field node + for (var i = 0; i < tds.length; i++) { - oldTd.replaceWith(tds[i]); - } - } - } - else - { - tr.html(tds.join('')); - } - return tr; - } - - function getUserRowHtml(height, data) - { - var nameHtml; - if (data.name) - { - nameHtml = padutils.escapeHtml(data.name); - } - else - { - nameHtml = ''; - } - - return ['
 
', '', nameHtml, '', '', padutils.escapeHtml(data.activity), ''].join(''); - } - - function getRowHtml(id, innerHtml, authorId) - { - return '' + innerHtml + ''; - } - - function rowNode(row) - { - return $("#" + row.domId); - } - - function handleRowData(row) - { - if (row.data && row.data.status == 'Disconnected') - { - row.opacity = 0.5; - } - else - { - delete row.opacity; - } - } - - function handleRowNode(tr, data) - { - if (data.titleText) - { - var titleText = data.titleText; - window.setTimeout(function() - { - /* tr.attr('title', titleText)*/ - }, 0); - } - else - { - tr.removeAttr('title'); - } - } - - function handleOtherUserInputs() - { - // handle 'INPUT' elements for naming other unnamed users - $("#otheruserstable input.newinput").each(function() - { - var input = $(this); - var tr = input.closest("tr"); - if (tr.length > 0) - { - var index = tr.parent().children().index(tr); - if (index >= 0) - { - var userId = rowsPresent[index].data.id; - rowManagerMakeNameEditor($(this), userId); - } - } - }).removeClass('newinput'); - } - - // animationPower is 0 to skip animation, 1 for linear, 2 for quadratic, etc. - - - function insertRow(position, data, animationPower) - { - position = Math.max(0, Math.min(rowsPresent.length, position)); - animationPower = (animationPower === undefined ? 4 : animationPower); - - var domId = nextRowId(); - var row = { - data: data, - animationStep: ANIMATION_START, - domId: domId, - animationPower: animationPower - }; - var authorId = data.id; - - handleRowData(row); - rowsPresent.splice(position, 0, row); - var tr; - if (animationPower == 0) - { - tr = $(getRowHtml(domId, getUserRowHtml(getAnimationHeight(0), data), authorId)); - row.animationStep = 0; - } - else - { - rowsFadingIn.push(row); - tr = $(getRowHtml(domId, getEmptyRowHtml(getAnimationHeight(ANIMATION_START)), authorId)); - } - handleRowNode(tr, data); - if (position == 0) - { - $("table#otheruserstable").prepend(tr); - } - else - { - rowNode(rowsPresent[position - 1]).after(tr); - } - - if (animationPower != 0) - { - scheduleAnimation(); - } - - handleOtherUserInputs(); - - return row; - } - - function updateRow(position, data) - { - var row = rowsPresent[position]; - if (row) - { - row.data = data; - handleRowData(row); - if (row.animationStep == 0) - { - // not currently animating - var tr = rowNode(row); - replaceUserRowContents(tr, getAnimationHeight(0), row.data).find("td").css('opacity', (row.opacity === undefined ? 1 : row.opacity)); - handleRowNode(tr, data); - handleOtherUserInputs(); - } - } - } - - function removeRow(position, animationPower) - { - animationPower = (animationPower === undefined ? 4 : animationPower); - var row = rowsPresent[position]; - if (row) - { - rowsPresent.splice(position, 1); // remove - if (animationPower == 0) - { - rowNode(row).remove(); - } - else - { - row.animationStep = -row.animationStep; // use symmetry - row.animationPower = animationPower; - rowsFadingOut.push(row); - scheduleAnimation(); - } - } - } - - // newPosition is position after the row has been removed - - - function moveRow(oldPosition, newPosition, animationPower) - { - animationPower = (animationPower === undefined ? 1 : animationPower); // linear is best - var row = rowsPresent[oldPosition]; - if (row && oldPosition != newPosition) - { - var rowData = row.data; - removeRow(oldPosition, animationPower); - insertRow(newPosition, rowData, animationPower); - } - } - - function animateStep() - { - // animation must be symmetrical - for (var i = rowsFadingIn.length - 1; i >= 0; i--) - { // backwards to allow removal - var row = rowsFadingIn[i]; - var step = ++row.animationStep; - var animHeight = getAnimationHeight(step, row.animationPower); - var node = rowNode(row); - var baseOpacity = (row.opacity === undefined ? 1 : row.opacity); - if (step <= -OPACITY_STEPS) - { - node.find("td").height(animHeight); - } - else if (step == -OPACITY_STEPS + 1) - { - node.html(getUserRowHtml(animHeight, row.data)).find("td").css('opacity', baseOpacity * 1 / OPACITY_STEPS); - handleRowNode(node, row.data); - } - else if (step < 0) - { - node.find("td").css('opacity', baseOpacity * (OPACITY_STEPS - (-step)) / OPACITY_STEPS).height(animHeight); - } - else if (step == 0) - { - // set HTML in case modified during animation - node.html(getUserRowHtml(animHeight, row.data)).find("td").css('opacity', baseOpacity * 1).height(animHeight); - handleRowNode(node, row.data); - rowsFadingIn.splice(i, 1); // remove from set - } - } - for (var i = rowsFadingOut.length - 1; i >= 0; i--) - { // backwards to allow removal - var row = rowsFadingOut[i]; - var step = ++row.animationStep; - var node = rowNode(row); - var animHeight = getAnimationHeight(step, row.animationPower); - var baseOpacity = (row.opacity === undefined ? 1 : row.opacity); - if (step < OPACITY_STEPS) - { - node.find("td").css('opacity', baseOpacity * (OPACITY_STEPS - step) / OPACITY_STEPS).height(animHeight); - } - else if (step == OPACITY_STEPS) - { - node.html(getEmptyRowHtml(animHeight)); - } - else if (step <= ANIMATION_END) - { - node.find("td").height(animHeight); - } - else - { - rowsFadingOut.splice(i, 1); // remove from set - node.remove(); - } - } - - handleOtherUserInputs(); - - return (rowsFadingIn.length > 0) || (rowsFadingOut.length > 0); // is more to do - } - - var self = { - insertRow: insertRow, - removeRow: removeRow, - moveRow: moveRow, - updateRow: updateRow - }; - return self; - }()); ////////// rowManager - var otherUsersInfo = []; - var otherUsersData = []; - - function rowManagerMakeNameEditor(jnode, userId) - { - setUpEditable(jnode, function() - { - var existingIndex = findExistingIndex(userId); - if (existingIndex >= 0) - { - return otherUsersInfo[existingIndex].name || ''; - } - else - { - return ''; - } - }, function(newName) - { - if (!newName) - { - jnode.addClass("editempty"); - jnode.val(_('pad.userlist.unnamed')); - } - else - { - jnode.attr('disabled', 'disabled'); - pad.suggestUserName(userId, newName); - } - }); - } - - function findExistingIndex(userId) - { - var existingIndex = -1; - for (var i = 0; i < otherUsersInfo.length; i++) - { - if (otherUsersInfo[i].userId == userId) - { - existingIndex = i; - break; - } - } - return existingIndex; - } - - function setUpEditable(jqueryNode, valueGetter, valueSetter) - { - jqueryNode.bind('focus', function(evt) - { - var oldValue = valueGetter(); - if (jqueryNode.val() !== oldValue) - { - jqueryNode.val(oldValue); - } - jqueryNode.addClass("editactive").removeClass("editempty"); - }); - jqueryNode.bind('blur', function(evt) - { - var newValue = jqueryNode.removeClass("editactive").val(); - valueSetter(newValue); - }); - padutils.bindEnterAndEscape(jqueryNode, function onEnter() - { - jqueryNode.blur(); - }, function onEscape() - { - jqueryNode.val(valueGetter()).blur(); - }); - jqueryNode.removeAttr('disabled').addClass('editable'); - } - - function updateInviteNotice() - { - if (otherUsersInfo.length == 0) - { - $("#otheruserstable").hide(); - $("#nootherusers").show(); - } - else - { - $("#nootherusers").hide(); - $("#otheruserstable").show(); - } - } - - var knocksToIgnore = {}; - var guestPromptFlashState = 0; - var guestPromptFlash = padutils.makeAnimationScheduler( - - function() - { - var prompts = $("#guestprompts .guestprompt"); - if (prompts.length == 0) - { - return false; // no more to do - } - - guestPromptFlashState = 1 - guestPromptFlashState; - if (guestPromptFlashState) - { - prompts.css('background', '#ffa'); - } - else - { - prompts.css('background', '#ffe'); - } - - return true; - }, 1000); - - var pad = undefined; - var self = { - init: function(myInitialUserInfo, _pad) - { - pad = _pad; - - self.setMyUserInfo(myInitialUserInfo); - - if($('#online_count').length === 0) $('#editbar [data-key=showusers] > a').append('1'); - - $("#otheruserstable tr").remove(); - - if (pad.getUserIsGuest()) - { - $("#myusernameedit").addClass('myusernameedithoverable'); - setUpEditable($("#myusernameedit"), function() - { - return myUserInfo.name || ''; - }, function(newValue) - { - myUserInfo.name = newValue; - pad.notifyChangeName(newValue); - // wrap with setTimeout to do later because we get - // a double "blur" fire in IE... - window.setTimeout(function() - { - self.renderMyUserInfo(); - }, 0); - }); - } - - // color picker - $("#myswatchbox").click(showColorPicker); - $("#mycolorpicker .pickerswatchouter").click(function() - { - $("#mycolorpicker .pickerswatchouter").removeClass('picked'); - $(this).addClass('picked'); - }); - $("#mycolorpickersave").click(function() - { - closeColorPicker(true); - }); - $("#mycolorpickercancel").click(function() - { - closeColorPicker(false); - }); - // - }, - users: function(){ - // Returns an object of users who have been on this pad - // Firstly we have to get live data.. - var userList = otherUsersInfo; - // Now we need to add ourselves.. - userList.push(myUserInfo); - // Now we add historical authors - var historical = clientVars.collab_client_vars.historicalAuthorData; - for (var key in historical){ - var userId = historical[key].userId; - // Check we don't already have this author in our array - var exists = false; - - userList.forEach(function(user){ - if(user.userId === userId) exists = true; - }); - - if(exists === false){ - userList.push(historical[key]); - } - - } - return userList; - }, - setMyUserInfo: function(info) - { - //translate the colorId - if(typeof info.colorId == "number") - { - info.colorId = clientVars.colorPalette[info.colorId]; - } - - myUserInfo = $.extend( - {}, info); - - self.renderMyUserInfo(); - }, - userJoinOrUpdate: function(info) - { - if ((!info.userId) || (info.userId == myUserInfo.userId)) - { - // not sure how this would happen - return; - } - - hooks.callAll('userJoinOrUpdate', { - userInfo: info - }); - - var userData = {}; - userData.color = typeof info.colorId == "number" ? clientVars.colorPalette[info.colorId] : info.colorId; - userData.name = info.name; - userData.status = ''; - userData.activity = ''; - userData.id = info.userId; - // Firefox ignores \n in title text; Safari does a linebreak - userData.titleText = [info.userAgent || '', info.ip || ''].join(' \n'); - - var existingIndex = findExistingIndex(info.userId); - - var numUsersBesides = otherUsersInfo.length; - if (existingIndex >= 0) - { - numUsersBesides--; - } - var newIndex = padutils.binarySearch(numUsersBesides, function(n) - { - if (existingIndex >= 0 && n >= existingIndex) - { - // pretend existingIndex isn't there - n++; - } - var infoN = otherUsersInfo[n]; - var nameN = (infoN.name || '').toLowerCase(); - var nameThis = (info.name || '').toLowerCase(); - var idN = infoN.userId; - var idThis = info.userId; - return (nameN > nameThis) || (nameN == nameThis && idN > idThis); - }); - - if (existingIndex >= 0) - { - // update - if (existingIndex == newIndex) - { - otherUsersInfo[existingIndex] = info; - otherUsersData[existingIndex] = userData; - rowManager.updateRow(existingIndex, userData); - } - else - { - otherUsersInfo.splice(existingIndex, 1); - otherUsersData.splice(existingIndex, 1); - otherUsersInfo.splice(newIndex, 0, info); - otherUsersData.splice(newIndex, 0, userData); - rowManager.updateRow(existingIndex, userData); - rowManager.moveRow(existingIndex, newIndex); - } - } - else - { - otherUsersInfo.splice(newIndex, 0, info); - otherUsersData.splice(newIndex, 0, userData); - rowManager.insertRow(newIndex, userData); - } - - updateInviteNotice(); - - self.updateNumberOfOnlineUsers(); - }, - updateNumberOfOnlineUsers: function() - { - var online = 1; // you are always online! - for (var i = 0; i < otherUsersData.length; i++) - { - if (otherUsersData[i].status == "") - { - online++; - } - } - - $('#online_count').text(online); - - return online; - }, - userLeave: function(info) - { - var existingIndex = findExistingIndex(info.userId); - if (existingIndex >= 0) - { - var userData = otherUsersData[existingIndex]; - userData.status = 'Disconnected'; - rowManager.updateRow(existingIndex, userData); - if (userData.leaveTimer) - { - window.clearTimeout(userData.leaveTimer); - } - // set up a timer that will only fire if no leaves, - // joins, or updates happen for this user in the - // next N seconds, to remove the user from the list. - var thisUserId = info.userId; - var thisLeaveTimer = window.setTimeout(function() - { - var newExistingIndex = findExistingIndex(thisUserId); - if (newExistingIndex >= 0) - { - var newUserData = otherUsersData[newExistingIndex]; - if (newUserData.status == 'Disconnected' && newUserData.leaveTimer == thisLeaveTimer) + var oldTd = $(tr.find("td").get(i)); + if (!oldTd.hasClass('usertdname')) { - otherUsersInfo.splice(newExistingIndex, 1); - otherUsersData.splice(newExistingIndex, 1); - rowManager.removeRow(newExistingIndex); - hooks.callAll('userLeave', { - userInfo: info - }); - updateInviteNotice(); + oldTd.replaceWith(tds[i]); } } - }, 8000); // how long to wait - userData.leaveTimer = thisLeaveTimer; - } - updateInviteNotice(); - - self.updateNumberOfOnlineUsers(); - }, - showGuestPrompt: function(userId, displayName) - { - if (knocksToIgnore[userId]) - { - return; - } - - var encodedUserId = padutils.encodeUserId(userId); - - var actionName = 'hide-guest-prompt-' + encodedUserId; - padutils.cancelActions(actionName); - - var box = $("#guestprompt-" + encodedUserId); - if (box.length == 0) - { - // make guest prompt box - box = $('
'+_('pad.userlist.guest')+': ' + padutils.escapeHtml(displayName) + '
'); - $("#guestprompts").append(box); - } - else - { - // update display name - box.find(".guestname").html(''+_('pad.userlist.guest')+': ' + padutils.escapeHtml(displayName)); - } - var hideLater = padutils.getCancellableAction(actionName, function() - { - self.removeGuestPrompt(userId); - }); - window.setTimeout(hideLater, 15000); // time-out with no knock - guestPromptFlash.scheduleAnimation(); - }, - removeGuestPrompt: function(userId) - { - var box = $("#guestprompt-" + padutils.encodeUserId(userId)); - // remove ID now so a new knock by same user gets new, unfaded box - box.removeAttr('id').fadeOut("fast", function() - { - box.remove(); - }); - - knocksToIgnore[userId] = true; - window.setTimeout(function() - { - delete knocksToIgnore[userId]; - }, 5000); - }, - answerGuestPrompt: function(encodedUserId, approve) - { - var guestId = padutils.decodeUserId(encodedUserId); - - var msg = { - type: 'guestanswer', - authId: pad.getUserId(), - guestId: guestId, - answer: (approve ? "approved" : "denied") - }; - pad.sendClientMessage(msg); - - self.removeGuestPrompt(guestId); - }, - renderMyUserInfo: function() - { - if (myUserInfo.name) - { - $("#myusernameedit").removeClass("editempty").val(myUserInfo.name); - } - else - { - $("#myusernameedit").addClass("editempty").val(_("pad.userlist.entername")); - } - if (colorPickerOpen) - { - $("#myswatchbox").addClass('myswatchboxunhoverable').removeClass('myswatchboxhoverable'); - } - else - { - $("#myswatchbox").addClass('myswatchboxhoverable').removeClass('myswatchboxunhoverable'); - } - - $("#myswatch").css({'background-color': myUserInfo.colorId}); - - if (browser.msie && parseInt(browser.version) <= 8) { - $("li[data-key=showusers] > a").css({'box-shadow': 'inset 0 0 30px ' + myUserInfo.colorId,'background-color': myUserInfo.colorId}); - } - else - { - $("li[data-key=showusers] > a").css({'box-shadow': 'inset 0 0 30px ' + myUserInfo.colorId}); - } - } - }; - return self; -}()); - -function getColorPickerSwatchIndex(jnode) -{ - // return Number(jnode.get(0).className.match(/\bn([0-9]+)\b/)[1])-1; - return $("#colorpickerswatches li").index(jnode); -} - -function closeColorPicker(accept) -{ - if (accept) - { - var newColor = $("#mycolorpickerpreview").css("background-color"); - var parts = newColor.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/); - // parts now should be ["rgb(0, 70, 255", "0", "70", "255"] - if (parts) { - delete (parts[0]); - for (var i = 1; i <= 3; ++i) { - parts[i] = parseInt(parts[i]).toString(16); - if (parts[i].length == 1) parts[i] = '0' + parts[i]; - } - var newColor = "#" +parts.join(''); // "0070ff" - } - myUserInfo.colorId = newColor; - pad.notifyChangeColor(newColor); - paduserlist.renderMyUserInfo(); - } - else - { - //pad.notifyChangeColor(previousColorId); - //paduserlist.renderMyUserInfo(); - } - - colorPickerOpen = false; - $("#mycolorpicker").fadeOut("fast"); -} - -function showColorPicker() -{ - previousColorId = myUserInfo.colorId; - - if (!colorPickerOpen) - { - var palette = pad.getColorPalette(); - - if (!colorPickerSetup) - { - var colorsList = $("#colorpickerswatches") - for (var i = 0; i < palette.length; i++) - { - - var li = $('
  • ', { - style: 'background: ' + palette[i] + ';' - }); - - li.appendTo(colorsList); - - li.bind('click', function(event) + } + else { - $("#colorpickerswatches li").removeClass('picked'); - $(event.target).addClass("picked"); - - var newColorId = getColorPickerSwatchIndex($("#colorpickerswatches .picked")); - pad.notifyChangeColor(newColorId); - }); - + tr.html(tds.join('')); + } + return tr; } - colorPickerSetup = true; + function getUserRowHtml(height, data) + { + var nameHtml; + if (data.name) + { + nameHtml = padutils.escapeHtml(data.name); + } + else + { + nameHtml = ''; + } + + return ['
     
    ', '', nameHtml, '', '', padutils.escapeHtml(data.activity), ''].join(''); + } + + function getRowHtml(id, innerHtml, authorId) + { + return '' + innerHtml + ''; + } + + function rowNode(row) + { + return $("#" + row.domId); + } + + function handleRowData(row) + { + if (row.data && row.data.status == 'Disconnected') + { + row.opacity = 0.5; + } + else + { + delete row.opacity; + } + } + + function handleRowNode(tr, data) + { + if (data.titleText) + { + var titleText = data.titleText; + window.setTimeout(function() + { + /* tr.attr('title', titleText)*/ + }, 0); + } + else + { + tr.removeAttr('title'); + } + } + + function handleOtherUserInputs() + { + // handle 'INPUT' elements for naming other unnamed users + $("#otheruserstable input.newinput").each(function() + { + var input = $(this); + var tr = input.closest("tr"); + if (tr.length > 0) + { + var index = tr.parent().children().index(tr); + if (index >= 0) + { + var userId = rowsPresent[index].data.id; + rowManagerMakeNameEditor($(this), userId); + } + } + }).removeClass('newinput'); + } + + // animationPower is 0 to skip animation, 1 for linear, 2 for quadratic, etc. + + + function insertRow(position, data, animationPower) + { + position = Math.max(0, Math.min(rowsPresent.length, position)); + animationPower = (animationPower === undefined ? 4 : animationPower); + + var domId = nextRowId(); + var row = { + data: data, + animationStep: ANIMATION_START, + domId: domId, + animationPower: animationPower + }; + var authorId = data.id; + + handleRowData(row); + rowsPresent.splice(position, 0, row); + var tr; + if (animationPower == 0) + { + tr = $(getRowHtml(domId, getUserRowHtml(getAnimationHeight(0), data), authorId)); + row.animationStep = 0; + } + else + { + rowsFadingIn.push(row); + tr = $(getRowHtml(domId, getEmptyRowHtml(getAnimationHeight(ANIMATION_START)), authorId)); + } + handleRowNode(tr, data); + if (position == 0) + { + $("table#otheruserstable").prepend(tr); + } + else + { + rowNode(rowsPresent[position - 1]).after(tr); + } + + if (animationPower != 0) + { + scheduleAnimation(); + } + + handleOtherUserInputs(); + + return row; + } + + function updateRow(position, data) + { + var row = rowsPresent[position]; + if (row) + { + row.data = data; + handleRowData(row); + if (row.animationStep == 0) + { + // not currently animating + var tr = rowNode(row); + replaceUserRowContents(tr, getAnimationHeight(0), row.data).find("td").css('opacity', (row.opacity === undefined ? 1 : row.opacity)); + handleRowNode(tr, data); + handleOtherUserInputs(); + } + } + } + + function removeRow(position, animationPower) + { + animationPower = (animationPower === undefined ? 4 : animationPower); + var row = rowsPresent[position]; + if (row) + { + rowsPresent.splice(position, 1); // remove + if (animationPower == 0) + { + rowNode(row).remove(); + } + else + { + row.animationStep = -row.animationStep; // use symmetry + row.animationPower = animationPower; + rowsFadingOut.push(row); + scheduleAnimation(); + } + } + } + + // newPosition is position after the row has been removed + + + function moveRow(oldPosition, newPosition, animationPower) + { + animationPower = (animationPower === undefined ? 1 : animationPower); // linear is best + var row = rowsPresent[oldPosition]; + if (row && oldPosition != newPosition) + { + var rowData = row.data; + removeRow(oldPosition, animationPower); + insertRow(newPosition, rowData, animationPower); + } + } + + function animateStep() + { + // animation must be symmetrical + for (var i = rowsFadingIn.length - 1; i >= 0; i--) + { // backwards to allow removal + var row = rowsFadingIn[i]; + var step = ++row.animationStep; + var animHeight = getAnimationHeight(step, row.animationPower); + var node = rowNode(row); + var baseOpacity = (row.opacity === undefined ? 1 : row.opacity); + if (step <= -OPACITY_STEPS) + { + node.find("td").height(animHeight); + } + else if (step == -OPACITY_STEPS + 1) + { + node.html(getUserRowHtml(animHeight, row.data)).find("td").css('opacity', baseOpacity * 1 / OPACITY_STEPS); + handleRowNode(node, row.data); + } + else if (step < 0) + { + node.find("td").css('opacity', baseOpacity * (OPACITY_STEPS - (-step)) / OPACITY_STEPS).height(animHeight); + } + else if (step == 0) + { + // set HTML in case modified during animation + node.html(getUserRowHtml(animHeight, row.data)).find("td").css('opacity', baseOpacity * 1).height(animHeight); + handleRowNode(node, row.data); + rowsFadingIn.splice(i, 1); // remove from set + } + } + for (var i = rowsFadingOut.length - 1; i >= 0; i--) + { // backwards to allow removal + var row = rowsFadingOut[i]; + var step = ++row.animationStep; + var node = rowNode(row); + var animHeight = getAnimationHeight(step, row.animationPower); + var baseOpacity = (row.opacity === undefined ? 1 : row.opacity); + if (step < OPACITY_STEPS) + { + node.find("td").css('opacity', baseOpacity * (OPACITY_STEPS - step) / OPACITY_STEPS).height(animHeight); + } + else if (step == OPACITY_STEPS) + { + node.html(getEmptyRowHtml(animHeight)); + } + else if (step <= ANIMATION_END) + { + node.find("td").height(animHeight); + } + else + { + rowsFadingOut.splice(i, 1); // remove from set + node.remove(); + } + } + + handleOtherUserInputs(); + + return (rowsFadingIn.length > 0) || (rowsFadingOut.length > 0); // is more to do + } + + var self = { + insertRow: insertRow, + removeRow: removeRow, + moveRow: moveRow, + updateRow: updateRow + }; + return self; + }()); ////////// rowManager + var otherUsersInfo = []; + var otherUsersData = []; + + function rowManagerMakeNameEditor(jnode, userId) + { + setUpEditable(jnode, function() + { + var existingIndex = findExistingIndex(userId); + if (existingIndex >= 0) + { + return otherUsersInfo[existingIndex].name || ''; + } + else + { + return ''; + } + }, function(newName) + { + if (!newName) + { + jnode.addClass("editempty"); + jnode.val(_('pad.userlist.unnamed')); + } + else + { + jnode.attr('disabled', 'disabled'); + pad.suggestUserName(userId, newName); + } + }); } - $("#mycolorpicker").fadeIn(); - colorPickerOpen = true; + function findExistingIndex(userId) + { + var existingIndex = -1; + for (var i = 0; i < otherUsersInfo.length; i++) + { + if (otherUsersInfo[i].userId == userId) + { + existingIndex = i; + break; + } + } + return existingIndex; + } - $("#colorpickerswatches li").removeClass('picked'); - $($("#colorpickerswatches li")[myUserInfo.colorId]).addClass("picked"); //seems weird + function setUpEditable(jqueryNode, valueGetter, valueSetter) + { + jqueryNode.bind('focus', function(evt) + { + var oldValue = valueGetter(); + if (jqueryNode.val() !== oldValue) + { + jqueryNode.val(oldValue); + } + jqueryNode.addClass("editactive").removeClass("editempty"); + }); + jqueryNode.bind('blur', function(evt) + { + var newValue = jqueryNode.removeClass("editactive").val(); + valueSetter(newValue); + }); + padutils.bindEnterAndEscape(jqueryNode, function onEnter() + { + jqueryNode.blur(); + }, function onEscape() + { + jqueryNode.val(valueGetter()).blur(); + }); + jqueryNode.removeAttr('disabled').addClass('editable'); + } + + function updateInviteNotice() + { + if (otherUsersInfo.length == 0) + { + $("#otheruserstable").hide(); + $("#nootherusers").show(); + } + else + { + $("#nootherusers").hide(); + $("#otheruserstable").show(); + } + } + + var knocksToIgnore = {}; + var guestPromptFlashState = 0; + var guestPromptFlash = padutils.makeAnimationScheduler( + + function() + { + var prompts = $("#guestprompts .guestprompt"); + if (prompts.length == 0) + { + return false; // no more to do + } + + guestPromptFlashState = 1 - guestPromptFlashState; + if (guestPromptFlashState) + { + prompts.css('background', '#ffa'); + } + else + { + prompts.css('background', '#ffe'); + } + + return true; + }, 1000); + + var pad = undefined; + var self = { + init: function(myInitialUserInfo, _pad) + { + pad = _pad; + + self.setMyUserInfo(myInitialUserInfo); + + if($('#online_count').length === 0) $('#editbar [data-key=showusers] > a').append('1'); + + $("#otheruserstable tr").remove(); + + if (pad.getUserIsGuest()) + { + $("#myusernameedit").addClass('myusernameedithoverable'); + setUpEditable($("#myusernameedit"), function() + { + return myUserInfo.name || ''; + }, function(newValue) + { + myUserInfo.name = newValue; + pad.notifyChangeName(newValue); + // wrap with setTimeout to do later because we get + // a double "blur" fire in IE... + window.setTimeout(function() + { + self.renderMyUserInfo(); + }, 0); + }); + } + + // color picker + $("#myswatchbox").click(showColorPicker); + $("#mycolorpicker .pickerswatchouter").click(function() + { + $("#mycolorpicker .pickerswatchouter").removeClass('picked'); + $(this).addClass('picked'); + }); + $("#mycolorpickersave").click(function() + { + closeColorPicker(true); + }); + $("#mycolorpickercancel").click(function() + { + closeColorPicker(false); + }); + // + }, + users: function(){ + // Returns an object of users who have been on this pad + // Firstly we have to get live data.. + var userList = otherUsersInfo; + // Now we need to add ourselves.. + userList.push(myUserInfo); + // Now we add historical authors + var historical = clientVars.collab_client_vars.historicalAuthorData; + for (var key in historical){ + var userId = historical[key].userId; + // Check we don't already have this author in our array + var exists = false; + + userList.forEach(function(user){ + if(user.userId === userId) exists = true; + }); + + if(exists === false){ + userList.push(historical[key]); + } + + } + return userList; + }, + setMyUserInfo: function(info) + { + //translate the colorId + if(typeof info.colorId == "number") + { + info.colorId = clientVars.colorPalette[info.colorId]; + } + + myUserInfo = $.extend( + {}, info); + + self.renderMyUserInfo(); + }, + userJoinOrUpdate: function(info) + { + if ((!info.userId) || (info.userId == myUserInfo.userId)) + { + // not sure how this would happen + return; + } + + hooks.callAll('userJoinOrUpdate', { + userInfo: info + }); + + var userData = {}; + userData.color = typeof info.colorId == "number" ? clientVars.colorPalette[info.colorId] : info.colorId; + userData.name = info.name; + userData.status = ''; + userData.activity = ''; + userData.id = info.userId; + // Firefox ignores \n in title text; Safari does a linebreak + userData.titleText = [info.userAgent || '', info.ip || ''].join(' \n'); + + var existingIndex = findExistingIndex(info.userId); + + var numUsersBesides = otherUsersInfo.length; + if (existingIndex >= 0) + { + numUsersBesides--; + } + var newIndex = padutils.binarySearch(numUsersBesides, function(n) + { + if (existingIndex >= 0 && n >= existingIndex) + { + // pretend existingIndex isn't there + n++; + } + var infoN = otherUsersInfo[n]; + var nameN = (infoN.name || '').toLowerCase(); + var nameThis = (info.name || '').toLowerCase(); + var idN = infoN.userId; + var idThis = info.userId; + return (nameN > nameThis) || (nameN == nameThis && idN > idThis); + }); + + if (existingIndex >= 0) + { + // update + if (existingIndex == newIndex) + { + otherUsersInfo[existingIndex] = info; + otherUsersData[existingIndex] = userData; + rowManager.updateRow(existingIndex, userData); + } + else + { + otherUsersInfo.splice(existingIndex, 1); + otherUsersData.splice(existingIndex, 1); + otherUsersInfo.splice(newIndex, 0, info); + otherUsersData.splice(newIndex, 0, userData); + rowManager.updateRow(existingIndex, userData); + rowManager.moveRow(existingIndex, newIndex); + } + } + else + { + otherUsersInfo.splice(newIndex, 0, info); + otherUsersData.splice(newIndex, 0, userData); + rowManager.insertRow(newIndex, userData); + } + + updateInviteNotice(); + + self.updateNumberOfOnlineUsers(); + }, + updateNumberOfOnlineUsers: function() + { + var online = 1; // you are always online! + for (var i = 0; i < otherUsersData.length; i++) + { + if (otherUsersData[i].status == "") + { + online++; + } + } + + $('#online_count').text(online); + + return online; + }, + userLeave: function(info) + { + var existingIndex = findExistingIndex(info.userId); + if (existingIndex >= 0) + { + var userData = otherUsersData[existingIndex]; + userData.status = 'Disconnected'; + rowManager.updateRow(existingIndex, userData); + if (userData.leaveTimer) + { + window.clearTimeout(userData.leaveTimer); + } + // set up a timer that will only fire if no leaves, + // joins, or updates happen for this user in the + // next N seconds, to remove the user from the list. + var thisUserId = info.userId; + var thisLeaveTimer = window.setTimeout(function() + { + var newExistingIndex = findExistingIndex(thisUserId); + if (newExistingIndex >= 0) + { + var newUserData = otherUsersData[newExistingIndex]; + if (newUserData.status == 'Disconnected' && newUserData.leaveTimer == thisLeaveTimer) + { + otherUsersInfo.splice(newExistingIndex, 1); + otherUsersData.splice(newExistingIndex, 1); + rowManager.removeRow(newExistingIndex); + hooks.callAll('userLeave', { + userInfo: info + }); + updateInviteNotice(); + } + } + }, 8000); // how long to wait + userData.leaveTimer = thisLeaveTimer; + } + updateInviteNotice(); + + self.updateNumberOfOnlineUsers(); + }, + showGuestPrompt: function(userId, displayName) + { + if (knocksToIgnore[userId]) + { + return; + } + + var encodedUserId = padutils.encodeUserId(userId); + + var actionName = 'hide-guest-prompt-' + encodedUserId; + padutils.cancelActions(actionName); + + var box = $("#guestprompt-" + encodedUserId); + if (box.length == 0) + { + // make guest prompt box + box = $('
    '+_('pad.userlist.guest')+': ' + padutils.escapeHtml(displayName) + '
    '); + $("#guestprompts").append(box); + } + else + { + // update display name + box.find(".guestname").html(''+_('pad.userlist.guest')+': ' + padutils.escapeHtml(displayName)); + } + var hideLater = padutils.getCancellableAction(actionName, function() + { + self.removeGuestPrompt(userId); + }); + window.setTimeout(hideLater, 15000); // time-out with no knock + guestPromptFlash.scheduleAnimation(); + }, + removeGuestPrompt: function(userId) + { + var box = $("#guestprompt-" + padutils.encodeUserId(userId)); + // remove ID now so a new knock by same user gets new, unfaded box + box.removeAttr('id').fadeOut("fast", function() + { + box.remove(); + }); + + knocksToIgnore[userId] = true; + window.setTimeout(function() + { + delete knocksToIgnore[userId]; + }, 5000); + }, + answerGuestPrompt: function(encodedUserId, approve) + { + var guestId = padutils.decodeUserId(encodedUserId); + + var msg = { + type: 'guestanswer', + authId: pad.getUserId(), + guestId: guestId, + answer: (approve ? "approved" : "denied") + }; + pad.sendClientMessage(msg); + + self.removeGuestPrompt(guestId); + }, + renderMyUserInfo: function() + { + if (myUserInfo.name) + { + $("#myusernameedit").removeClass("editempty").val(myUserInfo.name); + } + else + { + $("#myusernameedit").addClass("editempty").val(_("pad.userlist.entername")); + } + if (colorPickerOpen) + { + $("#myswatchbox").addClass('myswatchboxunhoverable').removeClass('myswatchboxhoverable'); + } + else + { + $("#myswatchbox").addClass('myswatchboxhoverable').removeClass('myswatchboxunhoverable'); + } + + $("#myswatch").css({'background-color': myUserInfo.colorId}); + + if (browser.msie && parseInt(browser.version) <= 8) { + $("li[data-key=showusers] > a").css({'box-shadow': 'inset 0 0 30px ' + myUserInfo.colorId,'background-color': myUserInfo.colorId}); + } + else + { + $("li[data-key=showusers] > a").css({'box-shadow': 'inset 0 0 30px ' + myUserInfo.colorId}); + } + } + }; + return self; + }()); + + function getColorPickerSwatchIndex(jnode) + { + // return Number(jnode.get(0).className.match(/\bn([0-9]+)\b/)[1])-1; + return $("#colorpickerswatches li").index(jnode); } -} -exports.paduserlist = paduserlist; + function closeColorPicker(accept) + { + if (accept) + { + var newColor = $("#mycolorpickerpreview").css("background-color"); + var parts = newColor.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/); + // parts now should be ["rgb(0, 70, 255", "0", "70", "255"] + if (parts) { + delete (parts[0]); + for (var i = 1; i <= 3; ++i) { + parts[i] = parseInt(parts[i]).toString(16); + if (parts[i].length == 1) parts[i] = '0' + parts[i]; + } + var newColor = "#" +parts.join(''); // "0070ff" + } + myUserInfo.colorId = newColor; + pad.notifyChangeColor(newColor); + paduserlist.renderMyUserInfo(); + } + else + { + //pad.notifyChangeColor(previousColorId); + //paduserlist.renderMyUserInfo(); + } + + colorPickerOpen = false; + $("#mycolorpicker").fadeOut("fast"); + } + + function showColorPicker() + { + previousColorId = myUserInfo.colorId; + + if (!colorPickerOpen) + { + var palette = pad.getColorPalette(); + + if (!colorPickerSetup) + { + var colorsList = $("#colorpickerswatches") + for (var i = 0; i < palette.length; i++) + { + + var li = $('
  • ', { + style: 'background: ' + palette[i] + ';' + }); + + li.appendTo(colorsList); + + li.bind('click', function(event) + { + $("#colorpickerswatches li").removeClass('picked'); + $(event.target).addClass("picked"); + + var newColorId = getColorPickerSwatchIndex($("#colorpickerswatches .picked")); + pad.notifyChangeColor(newColorId); + }); + + } + + colorPickerSetup = true; + } + + $("#mycolorpicker").fadeIn(); + colorPickerOpen = true; + + $("#colorpickerswatches li").removeClass('picked'); + $($("#colorpickerswatches li")[myUserInfo.colorId]).addClass("picked"); //seems weird + } + } + + exports.paduserlist = paduserlist; + + return exports; +}); diff --git a/src/static/js/pad_utils.js b/src/static/js/pad_utils.js index ff60ca7c6..a99631477 100644 --- a/src/static/js/pad_utils.js +++ b/src/static/js/pad_utils.js @@ -20,528 +20,517 @@ * limitations under the License. */ -var Security = require('./security'); +define(['ep_etherpad-lite/static/js/pad', 'ep_etherpad-lite/static/js/random_utils'], function(padModule, random_utils) { + var exports = {}; -/** - * Generates a random String with the given length. Is needed to generate the Author, Group, readonly, session Ids - */ + var Security = window.requireKernel('./security'); -function randomString(len) -{ - var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - var randomstring = ''; - len = len || 20 - for (var i = 0; i < len; i++) - { - var rnum = Math.floor(Math.random() * chars.length); - randomstring += chars.substring(rnum, rnum + 1); - } - return randomstring; -} - -function createCookie(name, value, days, path){ /* Used by IE */ - 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 of the cookie isn't set then just create it on root - 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=/"; /* Note this bodge fix for IE is temporary until auth is rewritten */ - } - 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; -} - -var padutils = { - escapeHtml: function(x) - { - return Security.escapeHTML(String(x)); - }, - uniqueId: function() - { - var pad = require('./pad').pad; // Sidestep circular dependency - function encodeNum(n, width) + function createCookie(name, value, days, path){ /* Used by IE */ + if (days) { - // returns string that is exactly 'width' chars, padding with zeros - // and taking rightmost digits - return (Array(width + 1).join('0') + Number(n).toString(35)).slice(-width); + var date = new Date(); + date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + var expires = "; expires=" + date.toGMTString(); + } + else{ + var expires = ""; } - return [pad.getClientIp(), encodeNum(+new Date, 7), encodeNum(Math.floor(Math.random() * 1e9), 4)].join('.'); - }, - uaDisplay: function(ua) - { - var m; - function clean(a) + if(!path){ // IF the Path of the cookie isn't set then just create it on root + 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=/"; /* Note this bodge fix for IE is temporary until auth is rewritten */ + } + 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 maxlen = 16; - a = a.replace(/[^a-zA-Z0-9\.]/g, ''); - if (a.length > maxlen) + 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; + } + + var padutils = { + escapeHtml: function(x) + { + return Security.escapeHTML(String(x)); + }, + uniqueId: function() + { + var pad = padModule.pad; // Sidestep circular dependency + function encodeNum(n, width) { - a = a.substr(0, maxlen); + // returns string that is exactly 'width' chars, padding with zeros + // and taking rightmost digits + return (Array(width + 1).join('0') + Number(n).toString(35)).slice(-width); } - return a; - } - - function checkver(name) + return [pad.getClientIp(), encodeNum(+new Date, 7), encodeNum(Math.floor(Math.random() * 1e9), 4)].join('.'); + }, + uaDisplay: function(ua) { - var m = ua.match(RegExp(name + '\\/([\\d\\.]+)')); + var m; + + function clean(a) + { + var maxlen = 16; + a = a.replace(/[^a-zA-Z0-9\.]/g, ''); + if (a.length > maxlen) + { + a = a.substr(0, maxlen); + } + return a; + } + + function checkver(name) + { + var m = ua.match(RegExp(name + '\\/([\\d\\.]+)')); + if (m && m.length > 1) + { + return clean(name + m[1]); + } + return null; + } + + // firefox + if (checkver('Firefox')) + { + return checkver('Firefox'); + } + + // misc browsers, including IE + m = ua.match(/compatible; ([^;]+);/); if (m && m.length > 1) { - return clean(name + m[1]); - } - return null; - } - - // firefox - if (checkver('Firefox')) - { - return checkver('Firefox'); - } - - // misc browsers, including IE - m = ua.match(/compatible; ([^;]+);/); - if (m && m.length > 1) - { - return clean(m[1]); - } - - // iphone - if (ua.match(/\(iPhone;/)) - { - return 'iPhone'; - } - - // chrome - if (checkver('Chrome')) - { - return checkver('Chrome'); - } - - // safari - m = ua.match(/Safari\/[\d\.]+/); - if (m) - { - var v = '?'; - m = ua.match(/Version\/([\d\.]+)/); - if (m && m.length > 1) - { - v = m[1]; - } - return clean('Safari' + v); - } - - // everything else - var x = ua.split(' ')[0]; - return clean(x); - }, - // e.g. "Thu Jun 18 2009 13:09" - simpleDateTime: function(date) - { - var d = new Date(+date); // accept either number or date - var dayOfWeek = (['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'])[d.getDay()]; - var month = (['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'])[d.getMonth()]; - var dayOfMonth = d.getDate(); - var year = d.getFullYear(); - var hourmin = d.getHours() + ":" + ("0" + d.getMinutes()).slice(-2); - return dayOfWeek + ' ' + month + ' ' + dayOfMonth + ' ' + year + ' ' + hourmin; - }, - findURLs: function(text) - { - // copied from ACE - var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/; - var _REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/?=&#;()$]/.source + '|' + _REGEX_WORDCHAR.source + ')'); - var _REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|nfs):\/\/|mailto:)/.source + _REGEX_URLCHAR.source + '*(?![:.,;])' + _REGEX_URLCHAR.source, 'g'); - - // returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...] - - - function _findURLs(text) - { - _REGEX_URL.lastIndex = 0; - var urls = null; - var execResult; - while ((execResult = _REGEX_URL.exec(text))) - { - urls = (urls || []); - var startIndex = execResult.index; - var url = execResult[0]; - urls.push([startIndex, url]); + return clean(m[1]); } - return urls; - } - - return _findURLs(text); - }, - escapeHtmlWithClickableLinks: function(text, target) - { - var idx = 0; - var pieces = []; - var urls = padutils.findURLs(text); - - function advanceTo(i) - { - if (i > idx) + // iphone + if (ua.match(/\(iPhone;/)) { - pieces.push(Security.escapeHTML(text.substring(idx, i))); - idx = i; + return 'iPhone'; } - } - if (urls) - { - for (var j = 0; j < urls.length; j++) - { - var startIndex = urls[j][0]; - var href = urls[j][1]; - advanceTo(startIndex); - pieces.push(''); - advanceTo(startIndex + href.length); - pieces.push(''); - } - } - advanceTo(text.length); - return pieces.join(''); - }, - bindEnterAndEscape: function(node, onEnter, onEscape) - { - // Use keypress instead of keyup in bindEnterAndEscape - // Keyup event is fired on enter in IME (Input Method Editor), But - // keypress is not. So, I changed to use keypress instead of keyup. - // It is work on Windows (IE8, Chrome 6.0.472), CentOs (Firefox 3.0) and Mac OSX (Firefox 3.6.10, Chrome 6.0.472, Safari 5.0). - if (onEnter) - { - node.keypress(function(evt) + // chrome + if (checkver('Chrome')) { - if (evt.which == 13) + return checkver('Chrome'); + } + + // safari + m = ua.match(/Safari\/[\d\.]+/); + if (m) + { + var v = '?'; + m = ua.match(/Version\/([\d\.]+)/); + if (m && m.length > 1) { - onEnter(evt); + v = m[1]; } - }); - } - - if (onEscape) - { - node.keydown(function(evt) - { - if (evt.which == 27) - { - onEscape(evt); - } - }); - } - }, - timediff: function(d) - { - var pad = require('./pad').pad; // Sidestep circular dependency - function format(n, word) - { - n = Math.round(n); - return ('' + n + ' ' + word + (n != 1 ? 's' : '') + ' ago'); - } - d = Math.max(0, (+(new Date) - (+d) - pad.clientTimeOffset) / 1000); - if (d < 60) - { - return format(d, 'second'); - } - d /= 60; - if (d < 60) - { - return format(d, 'minute'); - } - d /= 60; - if (d < 24) - { - return format(d, 'hour'); - } - d /= 24; - return format(d, 'day'); - }, - makeAnimationScheduler: function(funcToAnimateOneStep, stepTime, stepsAtOnce) - { - if (stepsAtOnce === undefined) - { - stepsAtOnce = 1; - } - - var animationTimer = null; - - function scheduleAnimation() - { - if (!animationTimer) - { - animationTimer = window.setTimeout(function() - { - animationTimer = null; - var n = stepsAtOnce; - var moreToDo = true; - while (moreToDo && n > 0) - { - moreToDo = funcToAnimateOneStep(); - n--; - } - if (moreToDo) - { - // more to do - scheduleAnimation(); - } - }, stepTime * stepsAtOnce); + return clean('Safari' + v); } - } - return { - scheduleAnimation: scheduleAnimation - }; - }, - makeShowHideAnimator: function(funcToArriveAtState, initiallyShown, fps, totalMs) - { - var animationState = (initiallyShown ? 0 : -2); // -2 hidden, -1 to 0 fade in, 0 to 1 fade out - var animationFrameDelay = 1000 / fps; - var animationStep = animationFrameDelay / totalMs; - var scheduleAnimation = padutils.makeAnimationScheduler(animateOneStep, animationFrameDelay).scheduleAnimation; - - function doShow() + // everything else + var x = ua.split(' ')[0]; + return clean(x); + }, + // e.g. "Thu Jun 18 2009 13:09" + simpleDateTime: function(date) { - animationState = -1; - funcToArriveAtState(animationState); - scheduleAnimation(); - } + var d = new Date(+date); // accept either number or date + var dayOfWeek = (['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'])[d.getDay()]; + var month = (['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'])[d.getMonth()]; + var dayOfMonth = d.getDate(); + var year = d.getFullYear(); + var hourmin = d.getHours() + ":" + ("0" + d.getMinutes()).slice(-2); + return dayOfWeek + ' ' + month + ' ' + dayOfMonth + ' ' + year + ' ' + hourmin; + }, + findURLs: function(text) + { + // copied from ACE + var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/; + var _REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/?=&#;()$]/.source + '|' + _REGEX_WORDCHAR.source + ')'); + var _REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|nfs):\/\/|mailto:)/.source + _REGEX_URLCHAR.source + '*(?![:.,;])' + _REGEX_URLCHAR.source, 'g'); - function doQuickShow() - { // start showing without losing any fade-in progress - if (animationState < -1) + // returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...] + + + function _findURLs(text) + { + _REGEX_URL.lastIndex = 0; + var urls = null; + var execResult; + while ((execResult = _REGEX_URL.exec(text))) + { + urls = (urls || []); + var startIndex = execResult.index; + var url = execResult[0]; + urls.push([startIndex, url]); + } + + return urls; + } + + return _findURLs(text); + }, + escapeHtmlWithClickableLinks: function(text, target) + { + var idx = 0; + var pieces = []; + var urls = padutils.findURLs(text); + + function advanceTo(i) + { + if (i > idx) + { + pieces.push(Security.escapeHTML(text.substring(idx, i))); + idx = i; + } + } + if (urls) + { + for (var j = 0; j < urls.length; j++) + { + var startIndex = urls[j][0]; + var href = urls[j][1]; + advanceTo(startIndex); + pieces.push(''); + advanceTo(startIndex + href.length); + pieces.push(''); + } + } + advanceTo(text.length); + return pieces.join(''); + }, + bindEnterAndEscape: function(node, onEnter, onEscape) + { + + // Use keypress instead of keyup in bindEnterAndEscape + // Keyup event is fired on enter in IME (Input Method Editor), But + // keypress is not. So, I changed to use keypress instead of keyup. + // It is work on Windows (IE8, Chrome 6.0.472), CentOs (Firefox 3.0) and Mac OSX (Firefox 3.6.10, Chrome 6.0.472, Safari 5.0). + if (onEnter) + { + node.keypress(function(evt) + { + if (evt.which == 13) + { + onEnter(evt); + } + }); + } + + if (onEscape) + { + node.keydown(function(evt) + { + if (evt.which == 27) + { + onEscape(evt); + } + }); + } + }, + timediff: function(d) + { + var pad = padModule.pad; // Sidestep circular dependency + function format(n, word) + { + n = Math.round(n); + return ('' + n + ' ' + word + (n != 1 ? 's' : '') + ' ago'); + } + d = Math.max(0, (+(new Date) - (+d) - pad.clientTimeOffset) / 1000); + if (d < 60) + { + return format(d, 'second'); + } + d /= 60; + if (d < 60) + { + return format(d, 'minute'); + } + d /= 60; + if (d < 24) + { + return format(d, 'hour'); + } + d /= 24; + return format(d, 'day'); + }, + makeAnimationScheduler: function(funcToAnimateOneStep, stepTime, stepsAtOnce) + { + if (stepsAtOnce === undefined) + { + stepsAtOnce = 1; + } + + var animationTimer = null; + + function scheduleAnimation() + { + if (!animationTimer) + { + animationTimer = window.setTimeout(function() + { + animationTimer = null; + var n = stepsAtOnce; + var moreToDo = true; + while (moreToDo && n > 0) + { + moreToDo = funcToAnimateOneStep(); + n--; + } + if (moreToDo) + { + // more to do + scheduleAnimation(); + } + }, stepTime * stepsAtOnce); + } + } + return { + scheduleAnimation: scheduleAnimation + }; + }, + makeShowHideAnimator: function(funcToArriveAtState, initiallyShown, fps, totalMs) + { + var animationState = (initiallyShown ? 0 : -2); // -2 hidden, -1 to 0 fade in, 0 to 1 fade out + var animationFrameDelay = 1000 / fps; + var animationStep = animationFrameDelay / totalMs; + + var scheduleAnimation = padutils.makeAnimationScheduler(animateOneStep, animationFrameDelay).scheduleAnimation; + + function doShow() { animationState = -1; - } - else if (animationState <= 0) - { - animationState = animationState; - } - else - { - animationState = Math.max(-1, Math.min(0, -animationState)); - } - funcToArriveAtState(animationState); - scheduleAnimation(); - } - - function doHide() - { - if (animationState >= -1 && animationState <= 0) - { - animationState = 1e-6; + funcToArriveAtState(animationState); scheduleAnimation(); } - } - function animateOneStep() - { - if (animationState < -1 || animationState == 0) - { - return false; - } - else if (animationState < 0) - { - // animate show - animationState += animationStep; - if (animationState >= 0) + function doQuickShow() + { // start showing without losing any fade-in progress + if (animationState < -1) { - animationState = 0; - funcToArriveAtState(animationState); - return false; + animationState = -1; + } + else if (animationState <= 0) + { + animationState = animationState; } else { - funcToArriveAtState(animationState); - return true; + animationState = Math.max(-1, Math.min(0, -animationState)); + } + funcToArriveAtState(animationState); + scheduleAnimation(); + } + + function doHide() + { + if (animationState >= -1 && animationState <= 0) + { + animationState = 1e-6; + scheduleAnimation(); } } - else if (animationState > 0) + + function animateOneStep() { - // animate hide - animationState += animationStep; - if (animationState >= 1) + if (animationState < -1 || animationState == 0) { - animationState = 1; - funcToArriveAtState(animationState); - animationState = -2; return false; } - else + else if (animationState < 0) { - funcToArriveAtState(animationState); - return true; + // animate show + animationState += animationStep; + if (animationState >= 0) + { + animationState = 0; + funcToArriveAtState(animationState); + return false; + } + else + { + funcToArriveAtState(animationState); + return true; + } + } + else if (animationState > 0) + { + // animate hide + animationState += animationStep; + if (animationState >= 1) + { + animationState = 1; + funcToArriveAtState(animationState); + animationState = -2; + return false; + } + else + { + funcToArriveAtState(animationState); + return true; + } } } - } - return { - show: doShow, - hide: doHide, - quickShow: doQuickShow - }; - }, - _nextActionId: 1, - uncanceledActions: {}, - getCancellableAction: function(actionType, actionFunc) - { - var o = padutils.uncanceledActions[actionType]; - if (!o) + return { + show: doShow, + hide: doHide, + quickShow: doQuickShow + }; + }, + _nextActionId: 1, + uncanceledActions: {}, + getCancellableAction: function(actionType, actionFunc) { - o = {}; - padutils.uncanceledActions[actionType] = o; - } - var actionId = (padutils._nextActionId++); - o[actionId] = true; - return function() - { - var p = padutils.uncanceledActions[actionType]; - if (p && p[actionId]) + var o = padutils.uncanceledActions[actionType]; + if (!o) { - actionFunc(); + o = {}; + padutils.uncanceledActions[actionType] = o; } - }; - }, - cancelActions: function(actionType) - { - var o = padutils.uncanceledActions[actionType]; - if (o) + var actionId = (padutils._nextActionId++); + o[actionId] = true; + return function() + { + var p = padutils.uncanceledActions[actionType]; + if (p && p[actionId]) + { + actionFunc(); + } + }; + }, + cancelActions: function(actionType) { - // clear it - delete padutils.uncanceledActions[actionType]; - } - }, - makeFieldLabeledWhenEmpty: function(field, labelText) - { - field = $(field); + var o = padutils.uncanceledActions[actionType]; + if (o) + { + // clear it + delete padutils.uncanceledActions[actionType]; + } + }, + makeFieldLabeledWhenEmpty: function(field, labelText) + { + field = $(field); - function clear() - { - field.addClass('editempty'); - field.val(labelText); - } - field.focus(function() - { - if (field.hasClass('editempty')) + function clear() { - field.val(''); + field.addClass('editempty'); + field.val(labelText); } - field.removeClass('editempty'); - }); - field.blur(function() - { - if (!field.val()) + field.focus(function() { - clear(); - } - }); - return { - clear: clear - }; - }, - getCheckbox: function(node) - { - return $(node).is(':checked'); - }, - setCheckbox: function(node, value) - { - if (value) - { - $(node).attr('checked', 'checked'); - } - else - { - $(node).removeAttr('checked'); - } - }, - bindCheckboxChange: function(node, func) - { - $(node).change(func); - }, - encodeUserId: function(userId) - { - return userId.replace(/[^a-y0-9]/g, function(c) - { - if (c == ".") return "-"; - return 'z' + c.charCodeAt(0) + 'z'; - }); - }, - decodeUserId: function(encodedUserId) - { - return encodedUserId.replace(/[a-y0-9]+|-|z.+?z/g, function(cc) - { - if (cc == '-') return '.'; - else if (cc.charAt(0) == 'z') + if (field.hasClass('editempty')) + { + field.val(''); + } + field.removeClass('editempty'); + }); + field.blur(function() { - return String.fromCharCode(Number(cc.slice(1, -1))); + if (!field.val()) + { + clear(); + } + }); + return { + clear: clear + }; + }, + getCheckbox: function(node) + { + return $(node).is(':checked'); + }, + setCheckbox: function(node, value) + { + if (value) + { + $(node).attr('checked', 'checked'); } else { - return cc; + $(node).removeAttr('checked'); } - }); - } -}; - -var globalExceptionHandler = undefined; -function setupGlobalExceptionHandler() { - if (!globalExceptionHandler) { - globalExceptionHandler = function test (msg, url, linenumber) + }, + bindCheckboxChange: function(node, func) { - var errorId = randomString(20); - var userAgent = padutils.escapeHtml(navigator.userAgent); - if ($("#editorloadingbox").attr("display") != "none"){ - //show javascript errors to the user - $("#editorloadingbox").css("padding", "10px"); - $("#editorloadingbox").css("padding-top", "45px"); - $("#editorloadingbox").html("
    An error occured
    The error was reported with the following id: '" + errorId + "'

    Please press and hold Ctrl and press F5 to reload this page, if the problem persists please send this error message to your webmaster:
    '" - + "ErrorId: " + errorId + "
    URL: " + window.location.href + "
    UserAgent: " + userAgent + "
    " + msg + " in " + url + " at line " + linenumber + "'
    "); - } + $(node).change(func); + }, + encodeUserId: function(userId) + { + return userId.replace(/[^a-y0-9]/g, function(c) + { + if (c == ".") return "-"; + return 'z' + c.charCodeAt(0) + 'z'; + }); + }, + decodeUserId: function(encodedUserId) + { + return encodedUserId.replace(/[a-y0-9]+|-|z.+?z/g, function(cc) + { + if (cc == '-') return '.'; + else if (cc.charAt(0) == 'z') + { + return String.fromCharCode(Number(cc.slice(1, -1))); + } + else + { + return cc; + } + }); + } + }; - //send javascript errors to the server - var errObj = {errorInfo: JSON.stringify({errorId: errorId, msg: msg, url: window.location.href, linenumber: linenumber, userAgent: navigator.userAgent})}; - var loc = document.location; - var url = loc.protocol + "//" + loc.hostname + ":" + loc.port + "/" + loc.pathname.substr(1, loc.pathname.indexOf("/p/")) + "jserror"; - - $.post(url, errObj); - - return false; - }; - window.onerror = globalExceptionHandler; + var globalExceptionHandler = undefined; + function setupGlobalExceptionHandler() { + if (!globalExceptionHandler) { + globalExceptionHandler = function test (msg, url, linenumber) + { + var errorId = random_utils.randomString(20); + var userAgent = padutils.escapeHtml(navigator.userAgent); + if ($("#editorloadingbox").attr("display") != "none"){ + //show javascript errors to the user + $("#editorloadingbox").css("padding", "10px"); + $("#editorloadingbox").css("padding-top", "45px"); + $("#editorloadingbox").html("
    An error occured
    The error was reported with the following id: '" + errorId + "'

    Please press and hold Ctrl and press F5 to reload this page, if the problem persists please send this error message to your webmaster:
    '" + + "ErrorId: " + errorId + "
    URL: " + window.location.href + "
    UserAgent: " + userAgent + "
    " + msg + " in " + url + " at line " + linenumber + "'
    "); + } + + //send javascript errors to the server + var errObj = {errorInfo: JSON.stringify({errorId: errorId, msg: msg, url: window.location.href, linenumber: linenumber, userAgent: navigator.userAgent})}; + var loc = document.location; + var url = loc.protocol + "//" + loc.hostname + ":" + loc.port + "/" + loc.pathname.substr(1, loc.pathname.indexOf("/p/")) + "jserror"; + + $.post(url, errObj); + + return false; + }; + window.onerror = globalExceptionHandler; + } } -} -padutils.setupGlobalExceptionHandler = setupGlobalExceptionHandler; + padutils.setupGlobalExceptionHandler = setupGlobalExceptionHandler; -padutils.binarySearch = require('./ace2_common').binarySearch; + padutils.binarySearch = require('./ace2_common').binarySearch; -exports.randomString = randomString; -exports.createCookie = createCookie; -exports.readCookie = readCookie; -exports.padutils = padutils; + exports.randomString = random_utils.randomString; + exports.createCookie = createCookie; + exports.readCookie = readCookie; + exports.padutils = padutils; + + return exports; +}); diff --git a/src/static/js/pluginfw/plugins.js b/src/static/js/pluginfw/plugins.js index 625472121..40df4d60b 100644 --- a/src/static/js/pluginfw/plugins.js +++ b/src/static/js/pluginfw/plugins.js @@ -82,6 +82,7 @@ exports.loadModule = function(path, cb) { cb(require(path)); console.warn("Module uses old CommonJS format: " + path); } catch (e) { + console.warn("Error loading CommonJS module: " + path + "\n" + e.toString()); requirejs([path], cb); } } diff --git a/src/static/js/random_utils.js b/src/static/js/random_utils.js new file mode 100644 index 000000000..7983341ed --- /dev/null +++ b/src/static/js/random_utils.js @@ -0,0 +1,44 @@ +/** + * Copyright 2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +function defineRandomUtils(exports) { + if (exports == undefined) exports = {}; + + /** + * Generates a random String with the given length. Is needed to generate the Author, Group, readonly, session Ids + */ + exports.randomString = function(len) + { + var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + var randomstring = ''; + len = len || 20 + for (var i = 0; i < len; i++) + { + var rnum = Math.floor(Math.random() * chars.length); + randomstring += chars.substring(rnum, rnum + 1); + } + return randomstring; + } + + return exports; +} + + +if (typeof(define) != 'undefined' && define.amd != undefined && typeof(exports) == 'undefined') { + define([], defineRandomUtils); +} else { + defineRandomUtils(exports); +} diff --git a/src/static/js/rjquery.js b/src/static/js/rjquery.js index 2be3d442e..d25a4bc2f 100644 --- a/src/static/js/rjquery.js +++ b/src/static/js/rjquery.js @@ -3,5 +3,7 @@ */ define.amd.jQuery = true; define(["ep_etherpad-lite/static/js/jquery"], function (dummy) { - return window.$.noConflict(true); + return window.$; + // Loading jQuery extensions won't work if you use noConflict :/ + // return window.$.noConflict(true); }); diff --git a/src/static/js/timeslider.js b/src/static/js/timeslider.js index 71ec7b392..d85b71e64 100644 --- a/src/static/js/timeslider.js +++ b/src/static/js/timeslider.js @@ -22,174 +22,184 @@ // These jQuery things should create local references, but for now `require()` // assigns to the global `$` and augments it with plugins. -require('./jquery'); -JSON = require('./json2'); -var createCookie = require('./pad_utils').createCookie; -var readCookie = require('./pad_utils').readCookie; -var randomString = require('./pad_utils').randomString; -var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); +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/broadcast_slider' +], function($, hooks, padUtilsMod, broadcastSliderMod) { + var exports = {}; -var token, padId, export_links; + JSON = window.requireKernel('./json2'); -function init() { - $(document).ready(function () - { - // start the custom js - if (typeof customStart == "function") customStart(); + var createCookie = padUtilsMod.createCookie; + var readCookie = padUtilsMod.readCookie; + var randomString = padUtilsMod.randomString; - //get the padId out of the url - var urlParts= document.location.pathname.split("/"); - padId = decodeURIComponent(urlParts[urlParts.length-2]); + var token, padId, export_links; - //set the title - document.title = padId.replace(/_+/g, ' ') + " | " + document.title; - - //ensure we have a token - token = readCookie("token"); - if(token == null) + function init() { + $(document).ready(function () { - token = "t." + randomString(); - createCookie("token", token, 60); - } + // start the custom js + if (typeof customStart == "function") customStart(); - 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'; - - //build up the socket io connection - socket = io.connect(url, {path: exports.baseURL + 'socket.io', resource: resource}); - - //send the ready message once we're connected - socket.on('connect', function() - { - sendSocketMsg("CLIENT_READY", {}); - }); + //get the padId out of the url + var urlParts= document.location.pathname.split("/"); + padId = decodeURIComponent(urlParts[urlParts.length-2]); - socket.on('disconnect', function() - { - BroadcastSlider.showReconnectUI(); - }); + //set the title + document.title = padId.replace(/_+/g, ' ') + " | " + document.title; - //route the incoming messages - socket.on('message', function(message) - { - if(window.console) console.log(message); - - if(message.type == "CLIENT_VARS") + //ensure we have a token + token = readCookie("token"); + if(token == null) { - handleClientVars(message); + token = "t." + randomString(); + createCookie("token", token, 60); } - else if(message.accessStatus) + + 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'; + + //build up the socket io connection + socket = io.connect(url, {path: exports.baseURL + 'socket.io', resource: resource}); + + //send the ready message once we're connected + socket.on('connect', function() { - $("body").html("

    You have no permission to access this pad

    ") - } else { - changesetLoader.handleMessageFromServer(message); - } + sendSocketMsg("CLIENT_READY", {}); + }); + + socket.on('disconnect', function() + { + BroadcastSlider.showReconnectUI(); + }); + + //route the incoming messages + socket.on('message', function(message) + { + if(window.console) console.log(message); + + if(message.type == "CLIENT_VARS") + { + handleClientVars(message); + } + else if(message.accessStatus) + { + $("body").html("

    You have no permission to access this pad

    ") + } else { + changesetLoader.handleMessageFromServer(message); + } + }); + + //get all the export links + export_links = $('#export > .exportlink') + + $('button#forcereconnect').click(function() + { + window.location.reload(); + }); + + exports.socket = socket; // make the socket available + exports.BroadcastSlider = BroadcastSlider; // Make the slider available + + hooks.aCallAll("postTimesliderInit"); }); - - //get all the export links - export_links = $('#export > .exportlink') - - $('button#forcereconnect').click(function() - { - window.location.reload(); - }); - - exports.socket = socket; // make the socket available - exports.BroadcastSlider = BroadcastSlider; // Make the slider available - - hooks.aCallAll("postTimesliderInit"); - }); -} - -//sends a message over the socket -function sendSocketMsg(type, data) -{ - var sessionID = decodeURIComponent(readCookie("sessionID")); - var password = readCookie("password"); - - var msg = { "component" : "pad", // FIXME: Remove this stupidity! - "type": type, - "data": data, - "padId": padId, - "token": token, - "sessionID": sessionID, - "password": password, - "protocolVersion": 2}; - - socket.json.send(msg); -} - -var fireWhenAllScriptsAreLoaded = []; - -var changesetLoader; -function handleClientVars(message) -{ - //save the client Vars - clientVars = message.data; - - //load all script that doesn't work without the clientVars - BroadcastSlider = require('./broadcast_slider').loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded); - require('./broadcast_revisions').loadBroadcastRevisionsJS(); - changesetLoader = require('./broadcast').loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, BroadcastSlider); - - //initialize export ui - require('./pad_impexp').padimpexp.init(); - - //change export urls when the slider moves - BroadcastSlider.onSlider(function(revno) - { - // export_links is a jQuery Array, so .each is allowed. - export_links.each(function() - { - this.setAttribute('href', this.href.replace( /(.+?)\/\w+\/(\d+\/)?export/ , '$1/' + padId + '/' + revno + '/export')); - }); - }); - - //fire all start functions of these scripts, formerly fired with window.load - for(var i=0;i < fireWhenAllScriptsAreLoaded.length;i++) - { - fireWhenAllScriptsAreLoaded[i](); } - $("#ui-slider-handle").css('left', $("#ui-slider-bar").width() - 2); - // Translate some strings where we only want to set the title not the actual values - $('#playpause_button_icon').attr("title", html10n.get("timeslider.playPause")); - $('#leftstep').attr("title", html10n.get("timeslider.backRevision")); - $('#rightstep').attr("title", html10n.get("timeslider.forwardRevision")); + //sends a message over the socket + function sendSocketMsg(type, data) + { + var sessionID = decodeURIComponent(readCookie("sessionID")); + var password = readCookie("password"); - // font family change - $("#viewfontmenu").change(function(){ - var font = $("#viewfontmenu").val(); - if(font === "monospace") setFont("Courier new"); - if(font === "opendyslexic") setFont("OpenDyslexic"); - if(font === "comicsans") setFont("Comic Sans MS"); - if(font === "georgia") setFont("Georgia"); - if(font === "impact") setFont("Impact"); - if(font === "lucida") setFont("Lucida"); - if(font === "lucidasans") setFont("Lucida Sans Unicode"); - if(font === "palatino") setFont("Palatino Linotype"); - if(font === "tahoma") setFont("Tahoma"); - if(font === "timesnewroman") setFont("Times New Roman"); - if(font === "trebuchet") setFont("Trebuchet MS"); - if(font === "verdana") setFont("Verdana"); - if(font === "symbol") setFont("Symbol"); - if(font === "webdings") setFont("Webdings"); - if(font === "wingdings") setFont("Wingdings"); - if(font === "sansserif") setFont("MS Sans Serif"); - if(font === "serif") setFont("MS Serif"); - }); + var msg = { "component" : "pad", // FIXME: Remove this stupidity! + "type": type, + "data": data, + "padId": padId, + "token": token, + "sessionID": sessionID, + "password": password, + "protocolVersion": 2}; -} + socket.json.send(msg); + } -function setFont(font){ - $('#padcontent').css("font-family", font); -} + var fireWhenAllScriptsAreLoaded = []; -exports.baseURL = ''; -exports.init = init; + var changesetLoader; + function handleClientVars(message) + { + //save the client Vars + clientVars = message.data; + + //load all script that doesn't work without the clientVars + BroadcastSlider = broadcastSliderMod.loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded); + require('./broadcast_revisions').loadBroadcastRevisionsJS(); + changesetLoader = require('./broadcast').loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, BroadcastSlider); + + //initialize export ui + require('./pad_impexp').padimpexp.init(); + + //change export urls when the slider moves + BroadcastSlider.onSlider(function(revno) + { + // export_links is a jQuery Array, so .each is allowed. + export_links.each(function() + { + this.setAttribute('href', this.href.replace( /(.+?)\/\w+\/(\d+\/)?export/ , '$1/' + padId + '/' + revno + '/export')); + }); + }); + + //fire all start functions of these scripts, formerly fired with window.load + for(var i=0;i < fireWhenAllScriptsAreLoaded.length;i++) + { + fireWhenAllScriptsAreLoaded[i](); + } + $("#ui-slider-handle").css('left', $("#ui-slider-bar").width() - 2); + + // Translate some strings where we only want to set the title not the actual values + $('#playpause_button_icon').attr("title", html10n.get("timeslider.playPause")); + $('#leftstep').attr("title", html10n.get("timeslider.backRevision")); + $('#rightstep').attr("title", html10n.get("timeslider.forwardRevision")); + + // font family change + $("#viewfontmenu").change(function(){ + var font = $("#viewfontmenu").val(); + if(font === "monospace") setFont("Courier new"); + if(font === "opendyslexic") setFont("OpenDyslexic"); + if(font === "comicsans") setFont("Comic Sans MS"); + if(font === "georgia") setFont("Georgia"); + if(font === "impact") setFont("Impact"); + if(font === "lucida") setFont("Lucida"); + if(font === "lucidasans") setFont("Lucida Sans Unicode"); + if(font === "palatino") setFont("Palatino Linotype"); + if(font === "tahoma") setFont("Tahoma"); + if(font === "timesnewroman") setFont("Times New Roman"); + if(font === "trebuchet") setFont("Trebuchet MS"); + if(font === "verdana") setFont("Verdana"); + if(font === "symbol") setFont("Symbol"); + if(font === "webdings") setFont("Webdings"); + if(font === "wingdings") setFont("Wingdings"); + if(font === "sansserif") setFont("MS Sans Serif"); + if(font === "serif") setFont("MS Serif"); + }); + + } + + function setFont(font){ + $('#padcontent').css("font-family", font); + } + + exports.baseURL = ''; + exports.init = init; + + return exports; +}); diff --git a/src/templates/pad.html b/src/templates/pad.html index 6eaf75b1a..8766d774d 100644 --- a/src/templates/pad.html +++ b/src/templates/pad.html @@ -370,10 +370,6 @@ - - - <% e.begin_block("customScripts"); %> <% e.end_block(); %> @@ -402,8 +398,11 @@ [ 'ep_etherpad-lite/static/js/rjquery', 'ep_etherpad-lite/static/js/pluginfw/client_plugins', - 'ep_etherpad-lite/static/js/pluginfw/hooks' - ], function ($, plugins, hooks) { + 'ep_etherpad-lite/static/js/pluginfw/hooks', + 'ep_etherpad-lite/static/js/pad', + 'ep_etherpad-lite/static/js/chat', + 'ep_etherpad-lite/static/js/pad_editbar', + ], function ($, plugins, hooks, padMod, chatMod, padEditbarMod) { window.$ = $; // Expose jQuery #HACK window.jQuery = $; @@ -421,16 +420,15 @@ hooks.aCallAll('documentReady'); }); - var pad = require('ep_etherpad-lite/static/js/pad'); - pad.baseURL = baseURL; - pad.init(); + padMod.baseURL = baseURL; + padMod.init(); }); /* TODO: These globals shouldn't exist. */ - pad = require('ep_etherpad-lite/static/js/pad').pad; - chat = require('ep_etherpad-lite/static/js/chat').chat; - padeditbar = require('ep_etherpad-lite/static/js/pad_editbar').padeditbar; - padimpexp = require('ep_etherpad-lite/static/js/pad_impexp').padimpexp; + pad = padMod.pad; + chat = chatMod.chat; + padeditbar = padEditbarMod.padeditbar; + padimpexp = window.requireKernel('ep_etherpad-lite/static/js/pad_impexp').padimpexp; } ); }()); diff --git a/src/templates/timeslider.html b/src/templates/timeslider.html index 6ec27c058..8aa9113c2 100644 --- a/src/templates/timeslider.html +++ b/src/templates/timeslider.html @@ -222,9 +222,7 @@ - - - + @@ -243,31 +241,47 @@ require.setLibraryURI(baseURL + "javascripts/lib"); require.setGlobalKeyPath("require"); - $ = jQuery = require('ep_etherpad-lite/static/js/rjquery').jQuery; // Expose jQuery #HACK - browser = require('ep_etherpad-lite/static/js/browser').browser; + window.requireKernel = require; - if ((!browser.msie) && (!(browser.mozilla && browser.version.indexOf("1.8.") == 0))) { - document.domain = document.domain; // for comet - } + requirejs.config({ + baseUrl: baseURL + "static/plugins", + paths: {'underscore': baseURL + "static/plugins/underscore/underscore"}, + }); - var plugins = require('ep_etherpad-lite/static/js/pluginfw/client_plugins'); - var socket = require('ep_etherpad-lite/static/js/timeslider').socket; - BroadcastSlider = require('ep_etherpad-lite/static/js/timeslider').BroadcastSlider; - plugins.baseURL = baseURL; - - plugins.update(function () { - var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); - hooks.plugins = plugins; + requirejs([ + 'ep_etherpad-lite/static/js/rjquery', + 'ep_etherpad-lite/static/js/pluginfw/client_plugins', + 'ep_etherpad-lite/static/js/pluginfw/hooks' + 'ep_etherpad-lite/static/js/pad_editbar', + 'ep_etherpad-lite/static/js/browser', + 'ep_etherpad-lite/static/js/timeslider' + ], function ($, plugins, hooks, padEditbarMod, browserMod, timesliderMod) { + window.$ = $; // Expose jQuery #HACK + window.jQuery = $; - var timeslider = require('ep_etherpad-lite/static/js/timeslider') - timeslider.baseURL = baseURL; - timeslider.init(); + browser = browserMod.browser; - /* TODO: These globals shouldn't exist. */ - padeditbar = require('ep_etherpad-lite/static/js/pad_editbar').padeditbar; - padimpexp = require('ep_etherpad-lite/static/js/pad_impexp').padimpexp; + if ((!browser.msie) && (!(browser.mozilla && browser.version.indexOf("1.8.") == 0))) { + document.domain = document.domain; // for comet + } - padeditbar.init() + + var socket = timesliderMod.socket; + BroadcastSlider = timesliderMod.BroadcastSlider; + plugins.baseURL = baseURL; + + plugins.update(function () { + hooks.plugins = plugins; + + timesliderMod.baseURL = baseURL; + timesliderMod.init(); + + /* TODO: These globals shouldn't exist. */ + padeditbar = padEditbarMod.padeditbar; + padimpexp = window.requireKernel('ep_etherpad-lite/static/js/pad_impexp').padimpexp; + + padeditbar.init(); + }); }); })();