diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index a0aef6648..d02e65377 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -159,11 +159,7 @@ exports.handleDisconnect = function(client) */ exports.handleMessage = function(client, message) { - _.map(hooks.callAll( "handleMessage", { client: client, message: message }), function ( newmessage ) { - if ( newmessage || newmessage === null ) { - message = newmessage; - } - }); + if(message == null) { messageLogger.warn("Message is null!"); @@ -175,6 +171,23 @@ exports.handleMessage = function(client, message) return; } + var handleMessageHook = function(callback){ + var dropMessage = false; + + // Call handleMessage hook. If a plugin returns null, the message will be dropped. Note that for all messages + // handleMessage will be called, even if the client is not authorized + hooks.aCallAll("handleMessage", { client: client, message: message }, function ( messages ) { + _.each(messages, function(newMessage){ + if ( newmessage === null ) { + dropMessage = true; + } + }); + + // If no plugins explicitly told us to drop the message, its ok to proceed + if(!dropMessage){ callback() }; + }); + } + var finalHandler = function () { //Check what type of message we get and delegate to the other methodes if(message.type == "CLIENT_READY") { @@ -203,11 +216,18 @@ exports.handleMessage = function(client, message) } }; - if (message && message.padId) { + if (message) { async.series([ + handleMessageHook, //check permissions function(callback) { + + if(!message.padId){ + // If the message has a padId we assume the client is already known to the server and needs no re-authorization + callback(); + return; + } // Note: message.sessionID is an entirely different kind of // session from the sessions we use here! Beware! FIXME: Call // our "sessions" "connections". @@ -231,8 +251,6 @@ exports.handleMessage = function(client, message) }, finalHandler ]); - } else { - finalHandler(); } } diff --git a/src/node/utils/Settings.js b/src/node/utils/Settings.js index e60446df4..dd34ac5ee 100644 --- a/src/node/utils/Settings.js +++ b/src/node/utils/Settings.js @@ -24,6 +24,7 @@ var os = require("os"); var path = require('path'); var argv = require('./Cli').argv; var npm = require("npm/lib/npm.js"); +var vm = require('vm'); /* Root path of the installation */ exports.root = path.normalize(path.join(npm.dir, "..")); @@ -45,6 +46,7 @@ exports.dbType = "dirty"; * This setting is passed with dbType to ueberDB to set up the database */ exports.dbSettings = { "filename" : path.join(exports.root, "dirty.db") }; + /** * The default Text of a new pad */ @@ -102,32 +104,24 @@ exports.abiwordAvailable = function() // Discover where the settings file lives var settingsFilename = argv.settings || "settings.json"; -if (settingsFilename.charAt(0) != '/') { - settingsFilename = path.normalize(path.join(root, settingsFilename)); -} +settingsFilename = path.resolve(path.join(root, settingsFilename)); -var settingsStr +var settingsStr; try{ //read the settings sync settingsStr = fs.readFileSync(settingsFilename).toString(); } catch(e){ - console.warn('No settings file found. Using defaults.'); - settingsStr = '{}'; + console.warn('No settings file found. Continuing using defaults!'); } - -//remove all comments -settingsStr = settingsStr.replace(/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/gm,"").replace(/#.*/g,"").replace(/\/\/.*/g,""); -//try to parse the settings +// try to parse the settings var settings; -try -{ - settings = JSON.parse(settingsStr); -} -catch(e) -{ - console.error("There is a syntax error in your settings.json file"); - console.error(e.message); +try { + if(settingsStr) { + settings = vm.runInContext('exports = '+settingsStr, vm.createContext(), "settings.json"); + } +}catch(e){ + console.error('There was an error processing your settings.json file: '+e.message); process.exit(1); } @@ -148,8 +142,7 @@ for(var i in settings) //this setting is unkown, output a warning and throw it away else { - console.warn("Unkown Setting: '" + i + "'"); - console.warn("This setting doesn't exist or it was removed"); + console.warn("Unknown Setting: '" + i + "'. This setting doesn't exist or it was removed"); } } diff --git a/src/static/js/Changeset.js b/src/static/js/Changeset.js index 738ee1bab..cfea43625 100644 --- a/src/static/js/Changeset.js +++ b/src/static/js/Changeset.js @@ -27,8 +27,6 @@ var AttributePool = require("./AttributePool"); -var _opt = null; - /** * ==================== General Util Functions ======================= */ @@ -127,22 +125,13 @@ exports.opIterator = function (opsStr, optStartIndex) { function nextRegexMatch() { prevIndex = curIndex; var result; - if (_opt) { - result = _opt.nextOpInString(opsStr, curIndex); - if (result) { - if (result.opcode() == '?') { - exports.error("Hit error opcode in op stream"); - } - curIndex = result.lastIndex(); - } - } else { - regex.lastIndex = curIndex; - result = regex.exec(opsStr); - curIndex = regex.lastIndex; - if (result[0] == '?') { - exports.error("Hit error opcode in op stream"); - } + regex.lastIndex = curIndex; + result = regex.exec(opsStr); + curIndex = regex.lastIndex; + if (result[0] == '?') { + exports.error("Hit error opcode in op stream"); } + return result; } var regexResult = nextRegexMatch(); @@ -150,13 +139,7 @@ exports.opIterator = function (opsStr, optStartIndex) { function next(optObj) { var op = (optObj || obj); - if (_opt && regexResult) { - op.attribs = regexResult.attribs(); - op.lines = regexResult.lines(); - op.chars = regexResult.chars(); - op.opcode = regexResult.opcode(); - regexResult = nextRegexMatch(); - } else if ((!_opt) && regexResult[0]) { + if (regexResult[0]) { op.attribs = regexResult[1]; op.lines = exports.parseNum(regexResult[2] || 0); op.opcode = regexResult[3]; @@ -169,7 +152,7 @@ exports.opIterator = function (opsStr, optStartIndex) { } function hasNext() { - return !!(_opt ? regexResult : regexResult[0]); + return !!(regexResult[0]); } function lastIndex() { @@ -414,159 +397,109 @@ exports.smartOpAssembler = function () { }; }; -if (_opt) { - exports.mergingOpAssembler = function () { - var assem = _opt.mergingOpAssembler(); - function append(op) { - assem.append(op.opcode, op.chars, op.lines, op.attribs); - } +exports.mergingOpAssembler = function () { + // This assembler can be used in production; it efficiently + // merges consecutive operations that are mergeable, ignores + // no-ops, and drops final pure "keeps". It does not re-order + // operations. + var assem = exports.opAssembler(); + var bufOp = exports.newOp(); - function toString() { - return assem.toString(); - } + // If we get, for example, insertions [xxx\n,yyy], those don't merge, + // but if we get [xxx\n,yyy,zzz\n], that merges to [xxx\nyyyzzz\n]. + // This variable stores the length of yyy and any other newline-less + // ops immediately after it. + var bufOpAdditionalCharsAfterNewline = 0; - function clear() { - assem.clear(); - } - - function endDocument() { - assem.endDocument(); - } - - return { - append: append, - toString: toString, - clear: clear, - endDocument: endDocument - }; - }; -} else { - exports.mergingOpAssembler = function () { - // This assembler can be used in production; it efficiently - // merges consecutive operations that are mergeable, ignores - // no-ops, and drops final pure "keeps". It does not re-order - // operations. - var assem = exports.opAssembler(); - var bufOp = exports.newOp(); - - // If we get, for example, insertions [xxx\n,yyy], those don't merge, - // but if we get [xxx\n,yyy,zzz\n], that merges to [xxx\nyyyzzz\n]. - // This variable stores the length of yyy and any other newline-less - // ops immediately after it. - var bufOpAdditionalCharsAfterNewline = 0; - - function flush(isEndDocument) { - if (bufOp.opcode) { - if (isEndDocument && bufOp.opcode == '=' && !bufOp.attribs) { - // final merged keep, leave it implicit - } else { + function flush(isEndDocument) { + if (bufOp.opcode) { + if (isEndDocument && bufOp.opcode == '=' && !bufOp.attribs) { + // final merged keep, leave it implicit + } else { + assem.append(bufOp); + if (bufOpAdditionalCharsAfterNewline) { + bufOp.chars = bufOpAdditionalCharsAfterNewline; + bufOp.lines = 0; assem.append(bufOp); - if (bufOpAdditionalCharsAfterNewline) { - bufOp.chars = bufOpAdditionalCharsAfterNewline; - bufOp.lines = 0; - assem.append(bufOp); - bufOpAdditionalCharsAfterNewline = 0; - } + bufOpAdditionalCharsAfterNewline = 0; } - bufOp.opcode = ''; } + bufOp.opcode = ''; } + } - function append(op) { - if (op.chars > 0) { - if (bufOp.opcode == op.opcode && bufOp.attribs == op.attribs) { - if (op.lines > 0) { - // bufOp and additional chars are all mergeable into a multi-line op - bufOp.chars += bufOpAdditionalCharsAfterNewline + op.chars; - bufOp.lines += op.lines; - bufOpAdditionalCharsAfterNewline = 0; - } else if (bufOp.lines == 0) { - // both bufOp and op are in-line - bufOp.chars += op.chars; - } else { - // append in-line text to multi-line bufOp - bufOpAdditionalCharsAfterNewline += op.chars; - } + function append(op) { + if (op.chars > 0) { + if (bufOp.opcode == op.opcode && bufOp.attribs == op.attribs) { + if (op.lines > 0) { + // bufOp and additional chars are all mergeable into a multi-line op + bufOp.chars += bufOpAdditionalCharsAfterNewline + op.chars; + bufOp.lines += op.lines; + bufOpAdditionalCharsAfterNewline = 0; + } else if (bufOp.lines == 0) { + // both bufOp and op are in-line + bufOp.chars += op.chars; } else { - flush(); - exports.copyOp(op, bufOp); + // append in-line text to multi-line bufOp + bufOpAdditionalCharsAfterNewline += op.chars; } + } else { + flush(); + exports.copyOp(op, bufOp); } } + } - function endDocument() { - flush(true); - } + function endDocument() { + flush(true); + } - function toString() { - flush(); - return assem.toString(); - } + function toString() { + flush(); + return assem.toString(); + } - function clear() { - assem.clear(); - exports.clearOp(bufOp); - } - return { - append: append, - toString: toString, - clear: clear, - endDocument: endDocument - }; + function clear() { + assem.clear(); + exports.clearOp(bufOp); + } + return { + append: append, + toString: toString, + clear: clear, + endDocument: endDocument }; -} +}; -if (_opt) { - exports.opAssembler = function () { - var assem = _opt.opAssembler(); - // this function allows op to be mutated later (doesn't keep a ref) - function append(op) { - assem.append(op.opcode, op.chars, op.lines, op.attribs); + +exports.opAssembler = function () { + var pieces = []; + // this function allows op to be mutated later (doesn't keep a ref) + + function append(op) { + pieces.push(op.attribs); + if (op.lines) { + pieces.push('|', exports.numToString(op.lines)); } + pieces.push(op.opcode); + pieces.push(exports.numToString(op.chars)); + } - function toString() { - return assem.toString(); - } + function toString() { + return pieces.join(''); + } - function clear() { - assem.clear(); - } - return { - append: append, - toString: toString, - clear: clear - }; + function clear() { + pieces.length = 0; + } + return { + append: append, + toString: toString, + clear: clear }; -} else { - exports.opAssembler = function () { - var pieces = []; - // this function allows op to be mutated later (doesn't keep a ref) - - function append(op) { - pieces.push(op.attribs); - if (op.lines) { - pieces.push('|', exports.numToString(op.lines)); - } - pieces.push(op.opcode); - pieces.push(exports.numToString(op.chars)); - } - - function toString() { - return pieces.join(''); - } - - function clear() { - pieces.length = 0; - } - return { - append: append, - toString: toString, - clear: clear - }; - }; -} +}; /** * A custom made String Iterator diff --git a/src/static/js/chat.js b/src/static/js/chat.js index 47b0ae3ca..9dfc9dc1f 100644 --- a/src/static/js/chat.js +++ b/src/static/js/chat.js @@ -23,11 +23,12 @@ var padutils = require('./pad_utils').padutils; var padcookie = require('./pad_cookie').padcookie; +require('./tinycon'); + var chat = (function() { var isStuck = false; var chatMentions = 0; - var title = document.title; var self = { show: function () { @@ -35,7 +36,7 @@ var chat = (function() $("#chatbox").show(); self.scrollDown(); chatMentions = 0; - document.title = title; + Tinycon.setBubble(0); }, stickToScreen: function(fromInitialCall) // Make chat stick to right hand side of screen { @@ -62,8 +63,12 @@ var chat = (function() }, scrollDown: function() { - if($('#chatbox').css("display") != "none") - $('#chattext').animate({scrollTop: $('#chattext')[0].scrollHeight}, "slow"); + if($('#chatbox').css("display") != "none"){ + if(!self.lastMessage || !self.lastMessage.position() || self.lastMessage.position().top < $('#chattext').height()) { + $('#chattext').animate({scrollTop: $('#chattext')[0].scrollHeight}, "slow"); + self.lastMessage = $('#chattext > p').eq(-1); + } + } }, send: function() { @@ -122,12 +127,9 @@ var chat = (function() // chat throb stuff -- Just make it throw for twice as long if(wasMentioned && !alreadyFocused) { // If the user was mentioned show for twice as long and flash the browser window - if (chatMentions == 0){ - title = document.title; - } $('#chatthrob').html(""+authorName+"" + ": " + text).show().delay(4000).hide(400); chatMentions++; - document.title = "("+chatMentions+") " + title; + Tinycon.setBubble(chatMentions); } else { @@ -137,7 +139,7 @@ var chat = (function() // Clear the chat mentions when the user clicks on the chat input box $('#chatinput').click(function(){ chatMentions = 0; - document.title = title; + Tinycon.setBubble(0); }); self.scrollDown(); diff --git a/src/static/js/pad.js b/src/static/js/pad.js index df6342e2d..a22e181a2 100644 --- a/src/static/js/pad.js +++ b/src/static/js/pad.js @@ -790,8 +790,6 @@ var pad = { }, 1000); } - padsavedrevs.handleIsFullyConnected(isConnected); - // pad.determineSidebarVisibility(isConnected && !isInitialConnect); pad.determineChatVisibility(isConnected && !isInitialConnect); pad.determineAuthorshipColorsVisibility(); diff --git a/src/static/js/pad_userlist.js b/src/static/js/pad_userlist.js index d83825854..474f9b0e1 100644 --- a/src/static/js/pad_userlist.js +++ b/src/static/js/pad_userlist.js @@ -21,6 +21,7 @@ */ var padutils = require('./pad_utils').padutils; +var hooks = require('./pluginfw/hooks'); var myUserInfo = {}; @@ -529,6 +530,10 @@ var paduserlist = (function() return; } + hooks.callAll('userJoinOrUpdate', { + userInfo: info + }); + var userData = {}; userData.color = typeof info.colorId == "number" ? clientVars.colorPalette[info.colorId] : info.colorId; userData.name = info.name; diff --git a/src/static/js/tinycon.js b/src/static/js/tinycon.js new file mode 100644 index 000000000..0e6a10a10 --- /dev/null +++ b/src/static/js/tinycon.js @@ -0,0 +1,237 @@ +/*! + * Tinycon - A small library for manipulating the Favicon + * Tom Moor, http://tommoor.com + * Copyright (c) 2012 Tom Moor + * MIT Licensed + * @version 0.2.6 +*/ + +(function(){ + + var Tinycon = {}; + var currentFavicon = null; + var originalFavicon = null; + var originalTitle = document.title; + var faviconImage = null; + var canvas = null; + var options = {}; + var defaults = { + width: 7, + height: 9, + font: '10px arial', + colour: '#ffffff', + background: '#F03D25', + fallback: true + }; + + var ua = (function () { + var agent = navigator.userAgent.toLowerCase(); + // New function has access to 'agent' via closure + return function (browser) { + return agent.indexOf(browser) !== -1; + }; + }()); + + var browser = { + ie: ua('msie'), + chrome: ua('chrome'), + webkit: ua('chrome') || ua('safari'), + safari: ua('safari') && !ua('chrome'), + mozilla: ua('mozilla') && !ua('chrome') && !ua('safari') + }; + + // private methods + var getFaviconTag = function(){ + + var links = document.getElementsByTagName('link'); + + for(var i=0, len=links.length; i < len; i++) { + if ((links[i].getAttribute('rel') || '').match(/\bicon\b/)) { + return links[i]; + } + } + + return false; + }; + + var removeFaviconTag = function(){ + + var links = document.getElementsByTagName('link'); + var head = document.getElementsByTagName('head')[0]; + + for(var i=0, len=links.length; i < len; i++) { + var exists = (typeof(links[i]) !== 'undefined'); + if (exists && links[i].getAttribute('rel') === 'icon') { + head.removeChild(links[i]); + } + } + }; + + var getCurrentFavicon = function(){ + + if (!originalFavicon || !currentFavicon) { + var tag = getFaviconTag(); + originalFavicon = currentFavicon = tag ? tag.getAttribute('href') : '/favicon.ico'; + } + + return currentFavicon; + }; + + var getCanvas = function (){ + + if (!canvas) { + canvas = document.createElement("canvas"); + canvas.width = 16; + canvas.height = 16; + } + + return canvas; + }; + + var setFaviconTag = function(url){ + removeFaviconTag(); + + var link = document.createElement('link'); + link.type = 'image/x-icon'; + link.rel = 'icon'; + link.href = url; + document.getElementsByTagName('head')[0].appendChild(link); + }; + + var log = function(message){ + if (window.console) window.console.log(message); + }; + + var drawFavicon = function(num, colour) { + + // fallback to updating the browser title if unsupported + if (!getCanvas().getContext || browser.ie || browser.safari || options.fallback === 'force') { + return updateTitle(num); + } + + var context = getCanvas().getContext("2d"); + var colour = colour || '#000000'; + var num = num || 0; + var src = getCurrentFavicon(); + + faviconImage = new Image(); + faviconImage.onload = function() { + + // clear canvas + context.clearRect(0, 0, 16, 16); + + // draw original favicon + context.drawImage(faviconImage, 0, 0, faviconImage.width, faviconImage.height, 0, 0, 16, 16); + + // draw bubble over the top + if (num > 0) drawBubble(context, num, colour); + + // refresh tag in page + refreshFavicon(); + }; + + // allow cross origin resource requests if the image is not a data:uri + // as detailed here: https://github.com/mrdoob/three.js/issues/1305 + if (!src.match(/^data/)) { + faviconImage.crossOrigin = 'anonymous'; + } + + faviconImage.src = src; + }; + + var updateTitle = function(num) { + + if (options.fallback) { + if (num > 0) { + document.title = '('+num+') ' + originalTitle; + } else { + document.title = originalTitle; + } + } + }; + + var drawBubble = function(context, num, colour) { + + // bubble needs to be larger for double digits + var len = (num+"").length-1; + var width = options.width + (6*len); + var w = 16-width; + var h = 16-options.height; + + // webkit seems to render fonts lighter than firefox + context.font = (browser.webkit ? 'bold ' : '') + options.font; + context.fillStyle = options.background; + context.strokeStyle = options.background; + context.lineWidth = 1; + + // bubble + context.fillRect(w,h,width-1,options.height); + + // rounded left + context.beginPath(); + context.moveTo(w-0.5,h+1); + context.lineTo(w-0.5,15); + context.stroke(); + + // rounded right + context.beginPath(); + context.moveTo(15.5,h+1); + context.lineTo(15.5,15); + context.stroke(); + + // bottom shadow + context.beginPath(); + context.strokeStyle = "rgba(0,0,0,0.3)"; + context.moveTo(w,16); + context.lineTo(15,16); + context.stroke(); + + // number + context.fillStyle = options.colour; + context.textAlign = "right"; + context.textBaseline = "top"; + + // unfortunately webkit/mozilla are a pixel different in text positioning + context.fillText(num, 15, browser.mozilla ? 7 : 6); + }; + + var refreshFavicon = function(){ + // check support + if (!getCanvas().getContext) return; + + setFaviconTag(getCanvas().toDataURL()); + }; + + + // public methods + Tinycon.setOptions = function(custom){ + options = {}; + + for(var key in defaults){ + options[key] = custom.hasOwnProperty(key) ? custom[key] : defaults[key]; + } + return this; + }; + + Tinycon.setImage = function(url){ + currentFavicon = url; + refreshFavicon(); + return this; + }; + + Tinycon.setBubble = function(num, colour){ + + // validate + if(isNaN(parseFloat(num)) || !isFinite(num)) return log('Bubble must be a number'); + + drawFavicon(num, colour); + return this; + }; + + Tinycon.reset = function(){ + Tinycon.setImage(originalFavicon); + }; + + Tinycon.setOptions(defaults); + window.Tinycon = Tinycon; +})(); diff --git a/src/templates/admin/plugins.html b/src/templates/admin/plugins.html index 104f6b986..3dad3bd01 100644 --- a/src/templates/admin/plugins.html +++ b/src/templates/admin/plugins.html @@ -21,7 +21,7 @@

Etherpad Lite

- Technical information on installed plugins + Technical information on installed plugins

Installed plugins

diff --git a/src/templates/pad.html b/src/templates/pad.html index 02af9b107..6821b5e6c 100644 --- a/src/templates/pad.html +++ b/src/templates/pad.html @@ -148,7 +148,10 @@
-
Loading...
+
+

Loading...

+ +