diff --git a/.gitignore b/.gitignore index 2fbb3220..4f315224 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ bin/convertSettings.json src/static/js/jquery.js npm-debug.log *.DS_Store +.ep_initialized diff --git a/src/node/hooks/express/specialpages.js b/src/node/hooks/express/specialpages.js index 585a7eab..474f475e 100644 --- a/src/node/hooks/express/specialpages.js +++ b/src/node/hooks/express/specialpages.js @@ -6,27 +6,27 @@ exports.expressCreateServer = function (hook_name, args, cb) { //serve index.html under / args.app.get('/', function(req, res) { - res.send(eejs.require("ep_etherpad-lite/templates/index.html"), { maxAge: exports.maxAge }); + res.send(eejs.require("ep_etherpad-lite/templates/index.html")); }); //serve robots.txt args.app.get('/robots.txt', function(req, res) { var filePath = path.normalize(__dirname + "/../../../static/robots.txt"); - res.sendfile(filePath, { maxAge: exports.maxAge }); + res.sendfile(filePath); }); //serve favicon.ico args.app.get('/favicon.ico', function(req, res) { var filePath = path.normalize(__dirname + "/../../../static/custom/favicon.ico"); - res.sendfile(filePath, { maxAge: exports.maxAge }, function(err) + res.sendfile(filePath, function(err) { //there is no custom favicon, send the default favicon if(err) { filePath = path.normalize(__dirname + "/../../../static/favicon.ico"); - res.sendfile(filePath, { maxAge: exports.maxAge }); + res.sendfile(filePath); } }); }); @@ -34,13 +34,13 @@ exports.expressCreateServer = function (hook_name, args, cb) { //serve pad.html under /p args.app.get('/p/:pad', function(req, res, next) { - res.send(eejs.require("ep_etherpad-lite/templates/pad.html"), { maxAge: exports.maxAge }); + res.send(eejs.require("ep_etherpad-lite/templates/pad.html")); }); //serve timeslider.html under /p/$padname/timeslider args.app.get('/p/:pad/timeslider', function(req, res, next) { - res.send(eejs.require("ep_etherpad-lite/templates/timeslider.html"), { maxAge: exports.maxAge }); + res.send(eejs.require("ep_etherpad-lite/templates/timeslider.html")); }); } \ No newline at end of file diff --git a/src/node/server.js b/src/node/server.js index 8e6d6fa0..6b443edb 100644 --- a/src/node/server.js +++ b/src/node/server.js @@ -51,10 +51,6 @@ console.log("Report bugs at https://github.com/Pita/etherpad-lite/issues") var serverName = "Etherpad-Lite " + version + " (http://j.mp/ep-lite)"; -//cache 6 hours, by default -var hour = 60*60; -exports.maxAge = settings.maxAge || 6 * hour; - //set loglevel log4js.setGlobalLogLevel(settings.loglevel); @@ -92,7 +88,12 @@ async.waterfall([ //let the server listen app.listen(settings.port, settings.ip); console.log("Server is listening at " + settings.ip + ":" + settings.port); - + if(settings.adminHttpAuth){ + console.log("Plugin admin page listening at " + settings.ip + ":" + settings.port + "/admin/plugins"); + } + else{ + console.log("Admin username and password not set in settings.json. To access admin please uncomment and edit adminHttpAuth in settings.json"); + } callback(null); } ]); diff --git a/src/node/utils/Minify.js b/src/node/utils/Minify.js index 1466ceaf..c5996565 100644 --- a/src/node/utils/Minify.js +++ b/src/node/utils/Minify.js @@ -108,7 +108,7 @@ exports.minify = function(req, res, next) date = new Date(date); res.setHeader('last-modified', date.toUTCString()); res.setHeader('date', (new Date()).toUTCString()); - if (settings.maxAge) { + if (settings.maxAge !== undefined) { var expiresDate = new Date((new Date()).getTime()+settings.maxAge*1000); res.setHeader('expires', expiresDate.toUTCString()); res.setHeader('cache-control', 'max-age=' + settings.maxAge); diff --git a/src/static/css/admin.css b/src/static/css/admin.css new file mode 100644 index 00000000..80089c4c --- /dev/null +++ b/src/static/css/admin.css @@ -0,0 +1,49 @@ +table { + border-collapse: collapse; +} +td, th { + border: 1px solid black; + padding-left: 10px; + padding-right: 10px; + padding-top: 2px; + padding-bottom: 2px; +} +.template { + display: none; +} +.dialog { + display: none; + position: absolute; + left: 50%; + top: 50%; + width: 700px; + height: 500px; + margin-left: -350px; + margin-top: -250px; + border: 3px solid #999999; + background: #eeeeee; +} +.dialog .title { + margin: 0; + padding: 2px; + border-bottom: 3px solid #999999; + font-size: 24px; + line-height: 24px; + height: 24px; + overflow: hidden; +} +.dialog .title .close { + float: right; +} +.dialog .history { + background: #222222; + color: #eeeeee; + position: absolute; + top: 41px; + bottom: 10px; + left: 10px; + right: 10px; + padding: 2px; + overflow: auto; +} + diff --git a/src/static/css/pad.css b/src/static/css/pad.css index 19d148a3..21f365e2 100644 --- a/src/static/css/pad.css +++ b/src/static/css/pad.css @@ -42,9 +42,8 @@ a img border-bottom: 1px solid #ccc; overflow: hidden; padding-top: 3px; - position: absolute; - left: 0; - right: 0; + width: 100%; + white-space: nowrap; height: 32px; } @@ -177,7 +176,6 @@ a#backtoprosite { padding-left: 20px; left: 6px; background: url(static/img/protop.gif) no-repeat -5px -6px; } #accountnav { right: 30px; color: #fff; } -.propad a#topbaretherpad { background: url(static/img/protop.gif) no-repeat -397px -3px; } #specialkeyarea { top: 5px; left: 250px; color: yellow; font-weight: bold; font-size: 1.5em; position: absolute; } @@ -606,8 +604,6 @@ table#otheruserstable { display: none; } text-align: left; } -.nonprouser #sharebox-stripe { display: none; } - .sharebox-url { width: 440px; height: 18px; text-align: left; diff --git a/src/static/css/timeslider.css b/src/static/css/timeslider.css index 38e4cfb1..03e97048 100644 --- a/src/static/css/timeslider.css +++ b/src/static/css/timeslider.css @@ -1,4 +1,7 @@ -#editorcontainerbox {overflow:auto; top:40px;} +#editorcontainerbox { + overflow:auto; top:40px; + position: static; +} #padcontent {font-size:12px; padding:10px;} @@ -67,8 +70,9 @@ width:122px; } + .topbarcenter, #docbar {display:none;} -#padmain {top:30px;} +#padmain {top:0px !important;} #editbarright {float:right;} #returnbutton {color:#222; font-size:16px; line-height:29px; margin-top:0; padding-right:6px;} #importexport .popup {width:185px;} @@ -77,6 +81,53 @@ width:185px; } + +.timeslider-bar +{ + background: #f7f7f7; + background: linear-gradient(#f7f7f7, #f1f1f1 80%); + border-bottom: 1px solid #ccc; + overflow: hidden; + padding-top: 3px; + width: 100%; +} + +.timeslider-bar #editbar +{ + border-bottom: none; + float: right; + width: 170px; + width: initial; +} + +.timeslider-bar h1 +{ + margin: 5px; +} +.timeslider-bar p +{ + margin: 5px; +} +#timeslider-top { + width: 100%; + position: fixed; + z-index: 1; +} + +#authorsList .author { + padding-left: 0.4em; + padding-right: 0.4em; +} + +#authorsList .author-anonymous { + padding-left: 0.6em; + padding-right: 0.6em; +} + +#padeditor { + position: static; +} + /* lists */ .list-bullet2, .list-indent2, .list-number2 {margin-left:3em;} .list-bullet3, .list-indent3, .list-number3 {margin-left:4.5em;} diff --git a/src/static/js/AttributeManager.js b/src/static/js/AttributeManager.js index ed6bf1d6..f81c0818 100644 --- a/src/static/js/AttributeManager.js +++ b/src/static/js/AttributeManager.js @@ -2,28 +2,78 @@ var Changeset = require('./Changeset'); var ChangesetUtils = require('./ChangesetUtils'); var _ = require('./underscore'); +var lineMarkerAttribute = 'lmkr'; + +// If one of these attributes are set to the first character of a +// line it is considered as a line attribute marker i.e. attributes +// set on this marker are applied to the whole line. +// The list attribute is only maintained for compatibility reasons +var lineAttributes = [lineMarkerAttribute,'list']; + +/* + The Attribute manager builds changesets based on a document + representation for setting and removing range or line-based attributes. + + @param rep the document representation to be used + @param applyChangesetCallback this callback will be called + once a changeset has been built. +*/ + var AttributeManager = function(rep, applyChangesetCallback) { this.rep = rep; this.applyChangesetCallback = applyChangesetCallback; this.author = ''; + + // If the first char in a line has one of the following attributes + // it will be considered as a line marker }; AttributeManager.prototype = _(AttributeManager.prototype).extend({ applyChangeset: function(changeset){ + if(!this.applyChangesetCallback) return changeset; + var cs = changeset.toString(); if (!Changeset.isIdentity(cs)) { this.applyChangesetCallback(cs); } + + return changeset; }, + /* + Sets attributes on a range + @param start [row, col] tuple pointing to the start of the range + @param end [row, col] tuple pointing to the end of the range + @param attribute: an array of attributes + */ + setAttributesOnRange: function(start, end, attribs) + { + var builder = Changeset.builder(this.rep.lines.totalWidth()); + ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, start); + ChangesetUtils.buildKeepRange(this.rep, builder, start, end, attribs, this.rep.apool); + return this.applyChangeset(builder); + }, + + /* + Returns if the line already has a line marker + @param lineNum: the number of the line + */ lineHasMarker: function(lineNum){ - // get "list" attribute of first char of line - return this.getAttributeOnLine(lineNum, 'list'); + var that = this; + + return _.find(lineAttributes, function(attribute){ + return that.getAttributeOnLine(lineNum, attribute) != ''; + }) !== undefined; }, + /* + Gets a specified attribute on a line + @param lineNum: the number of the line to set the attribute for + @param attributeKey: the name of the attribute to get, e.g. list + */ getAttributeOnLine: function(lineNum, attributeName){ // get `attributeName` attribute of first char of line var aline = this.rep.alines[lineNum]; @@ -61,6 +111,7 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({ builder.insert('*', [ ['author', this.author], ['insertorder', 'first'], + [lineMarkerAttribute, '1'], [attributeName, attributeValue] ], this.rep.apool); } @@ -80,8 +131,6 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({ var builder = Changeset.builder(this.rep.lines.totalWidth()); var hasMarker = this.lineHasMarker(lineNum); - // TODO - if(hasMarker){ ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 0])); ChangesetUtils.buildRemoveRange(this.rep, builder, loc, (loc = [lineNum, 1])); diff --git a/src/static/js/AttributePool.js b/src/static/js/AttributePool.js index a9245daf..f5990c07 100644 --- a/src/static/js/AttributePool.js +++ b/src/static/js/AttributePool.js @@ -22,6 +22,12 @@ * limitations under the License. */ +/* + An AttributePool maintains a mapping from [key,value] Pairs called + Attributes to Numbers (unsigened integers) and vice versa. These numbers are + used to reference Attributes in Changesets. +*/ + var AttributePool = function () { this.numToAttrib = {}; // e.g. {0: ['foo','bar']} this.attribToNum = {}; // e.g. {'foo,bar': 0} diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index 76992259..99bbbb4f 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -35,6 +35,8 @@ var isNodeText = Ace2Common.isNodeText, binarySearchInfinite = Ace2Common.binarySearchInfinite, htmlPrettyEscape = Ace2Common.htmlPrettyEscape, noop = Ace2Common.noop; + var hooks = require('./pluginfw/hooks'); + function Ace2Inner(){ @@ -2203,6 +2205,9 @@ function Ace2Inner(){ } + /* + Converts the position of a char (index in String) into a [row, col] tuple + */ function lineAndColumnFromChar(x) { var lineEntry = rep.lines.atOffset(x); @@ -2269,32 +2274,17 @@ function Ace2Inner(){ function performDocumentApplyAttributesToCharRange(start, end, attribs) { - if (end >= rep.alltext.length) - { - end = rep.alltext.length - 1; - } - performDocumentApplyAttributesToRange(lineAndColumnFromChar(start), lineAndColumnFromChar(end), attribs); + end = Math.min(end, rep.alltext.length - 1); + documentAttributeManager.setAttributesOnRange(lineAndColumnFromChar(start), lineAndColumnFromChar(end), attribs); } editorInfo.ace_performDocumentApplyAttributesToCharRange = performDocumentApplyAttributesToCharRange; - - function performDocumentApplyAttributesToRange(start, end, attribs) - { - var builder = Changeset.builder(rep.lines.totalWidth()); - ChangesetUtils.buildKeepToStartOfRange(rep, builder, start); - ChangesetUtils.buildKeepRange(rep, builder, start, end, attribs, rep.apool); - var cs = builder.toString(); - performDocumentApplyChangeset(cs); - } - editorInfo.ace_performDocumentApplyAttributesToRange = performDocumentApplyAttributesToRange; - - - // TODO move to AttributeManager + function setAttributeOnSelection(attributeName, attributeValue) { if (!(rep.selStart && rep.selEnd)) return; - performDocumentApplyAttributesToRange(rep.selStart, rep.selEnd, [ + documentAttributeManager.setAttributesOnRange(rep.selStart, rep.selEnd, [ [attributeName, attributeValue] ]); } @@ -2355,13 +2345,13 @@ function Ace2Inner(){ if (selectionAllHasIt) { - performDocumentApplyAttributesToRange(rep.selStart, rep.selEnd, [ + documentAttributeManager.setAttributesOnRange(rep.selStart, rep.selEnd, [ [attributeName, ''] ]); } else { - performDocumentApplyAttributesToRange(rep.selStart, rep.selEnd, [ + documentAttributeManager.setAttributesOnRange(rep.selStart, rep.selEnd, [ [attributeName, 'true'] ]); } @@ -2883,6 +2873,10 @@ function Ace2Inner(){ "ul": 1 }; + _.each(hooks.callAll('aceRegisterBlockElements'), function(element){ + _blockElems[element] = 1; + }) + function isBlockElement(n) { return !!_blockElems[(n.tagName || "").toLowerCase()]; @@ -3320,7 +3314,7 @@ function Ace2Inner(){ } _.each(mods, function(mod){ - setLineListType.apply(this, mod); + setLineListType(mod[0], mod[1]); }); return true; } @@ -4845,7 +4839,6 @@ function Ace2Inner(){ function setLineListType(lineNum, listType) { - debugger; if(listType == ''){ documentAttributeManager.removeAttributeOnLine(lineNum, listAttributeName); }else{ @@ -4977,7 +4970,7 @@ function Ace2Inner(){ } _.each(mods, function(mod){ - setLineListType.apply(this, mod); + setLineListType(mod[0], mod[1]); }); } @@ -5401,8 +5394,8 @@ function Ace2Inner(){ // Init documentAttributeManager - documentAttributeManager = new AttributeManager(rep, performDocumentApplyChangeset); + editorInfo.ace_performDocumentApplyAttributesToRange = documentAttributeManager.setAttributesOnRange; $(document).ready(function(){ doc = document; // defined as a var in scope outside @@ -5442,7 +5435,13 @@ function Ace2Inner(){ bindTheEventHandlers(); }); - + + hooks.callAll('aceInitialized', { + editorInfo: editorInfo, + rep: rep, + documentAttributeManager: documentAttributeManager + }); + scheduler.setTimeout(function() { parent.readyFunc(); // defined in code that sets up the inner iframe diff --git a/src/static/js/broadcast_slider.js b/src/static/js/broadcast_slider.js index f88eb5dd..a2a15773 100644 --- a/src/static/js/broadcast_slider.js +++ b/src/static/js/broadcast_slider.js @@ -170,41 +170,67 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) $('#error').show(); } + var fixPadHeight = _.throttle(function(){ + var height = $('#timeslider-top').height(); + $('#editorcontainerbox').css({marginTop: height}); + }, 600); + function setAuthors(authors) { - $("#authorstable").empty(); + var authorsList = $("#authorsList"); + authorsList.empty(); var numAnonymous = 0; var numNamed = 0; + var colorsAnonymous = []; _.each(authors, function(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++; - var tr = $(''); - var swatchtd = $(''); - var swatch = $('
'); - swatch.css('background-color', clientVars.colorPalette[author.colorId]); - swatchtd.append(swatch); - tr.append(swatchtd); - var nametd = $(''); - nametd.text(author.name || "unnamed"); - tr.append(nametd); - $("#authorstable").append(tr); } else { numAnonymous++; + if(authorColor) colorsAnonymous.push(authorColor); } }); if (numAnonymous > 0) { - var html = "" + (numNamed > 0 ? "...and " : "") + numAnonymous + " unnamed author" + (numAnonymous > 1 ? "s" : "") + ""; - $("#authorstable").append($(html)); + var anonymousAuthorString = numAnonymous + " unnamed author" + (numAnonymous > 1 ? "s" : "") + 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) { - $("#authorstable").append($("No Authors")) + authorsList.append("No Authors"); } + + fixPadHeight(); } BroadcastSlider = { @@ -465,7 +491,6 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) { if (clientVars.supportsSlider) { - $("#padmain, #rightbars").css('top', "130px"); $("#timeslider").show(); setSliderLength(clientVars.totalRevs); setSliderPosition(clientVars.revNum); diff --git a/src/static/js/pad_editbar.js b/src/static/js/pad_editbar.js index 7e8cf96e..95d19505 100644 --- a/src/static/js/pad_editbar.js +++ b/src/static/js/pad_editbar.js @@ -200,10 +200,11 @@ var padeditbar = (function() else { var nth_child = indexOf(modules, moduleName) + 1; - if (nth_child > 0 && nth_child <= 3) { + if (nth_child > 0 && nth_child <= (modules.length-1)) { $("#editbar ul#menu_right li:not(:nth-child(" + nth_child + "))").removeClass("selected"); $("#editbar ul#menu_right li:nth-child(" + nth_child + ")").toggleClass("selected"); } + if(modules[modules.length-1] === moduleName) $("#editbar ul#menu_right li").removeClass("selected"); //hide all modules that are not selected and show the selected one for(var i=0;i Plugin manager - + <% } %>