This commit is contained in:
Marcel Klehr 2012-07-23 05:25:17 -07:00
commit 1f596c6d77
9 changed files with 399 additions and 197 deletions

View file

@ -159,11 +159,7 @@ exports.handleDisconnect = function(client)
*/ */
exports.handleMessage = function(client, message) exports.handleMessage = function(client, message)
{ {
_.map(hooks.callAll( "handleMessage", { client: client, message: message }), function ( newmessage ) {
if ( newmessage || newmessage === null ) {
message = newmessage;
}
});
if(message == null) if(message == null)
{ {
messageLogger.warn("Message is null!"); messageLogger.warn("Message is null!");
@ -175,6 +171,23 @@ exports.handleMessage = function(client, message)
return; 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 () { var finalHandler = function () {
//Check what type of message we get and delegate to the other methodes //Check what type of message we get and delegate to the other methodes
if(message.type == "CLIENT_READY") { if(message.type == "CLIENT_READY") {
@ -203,11 +216,18 @@ exports.handleMessage = function(client, message)
} }
}; };
if (message && message.padId) { if (message) {
async.series([ async.series([
handleMessageHook,
//check permissions //check permissions
function(callback) 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 // Note: message.sessionID is an entirely different kind of
// session from the sessions we use here! Beware! FIXME: Call // session from the sessions we use here! Beware! FIXME: Call
// our "sessions" "connections". // our "sessions" "connections".
@ -231,8 +251,6 @@ exports.handleMessage = function(client, message)
}, },
finalHandler finalHandler
]); ]);
} else {
finalHandler();
} }
} }

View file

@ -24,6 +24,7 @@ var os = require("os");
var path = require('path'); var path = require('path');
var argv = require('./Cli').argv; var argv = require('./Cli').argv;
var npm = require("npm/lib/npm.js"); var npm = require("npm/lib/npm.js");
var vm = require('vm');
/* Root path of the installation */ /* Root path of the installation */
exports.root = path.normalize(path.join(npm.dir, "..")); 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 * This setting is passed with dbType to ueberDB to set up the database
*/ */
exports.dbSettings = { "filename" : path.join(exports.root, "dirty.db") }; exports.dbSettings = { "filename" : path.join(exports.root, "dirty.db") };
/** /**
* The default Text of a new pad * The default Text of a new pad
*/ */
@ -102,32 +104,24 @@ exports.abiwordAvailable = function()
// Discover where the settings file lives // Discover where the settings file lives
var settingsFilename = argv.settings || "settings.json"; var settingsFilename = argv.settings || "settings.json";
if (settingsFilename.charAt(0) != '/') { settingsFilename = path.resolve(path.join(root, settingsFilename));
settingsFilename = path.normalize(path.join(root, settingsFilename));
}
var settingsStr var settingsStr;
try{ try{
//read the settings sync //read the settings sync
settingsStr = fs.readFileSync(settingsFilename).toString(); settingsStr = fs.readFileSync(settingsFilename).toString();
} catch(e){ } catch(e){
console.warn('No settings file found. Using defaults.'); console.warn('No settings file found. Continuing using defaults!');
settingsStr = '{}';
} }
//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; var settings;
try try {
{ if(settingsStr) {
settings = JSON.parse(settingsStr); settings = vm.runInContext('exports = '+settingsStr, vm.createContext(), "settings.json");
} }
catch(e) }catch(e){
{ console.error('There was an error processing your settings.json file: '+e.message);
console.error("There is a syntax error in your settings.json file");
console.error(e.message);
process.exit(1); process.exit(1);
} }
@ -148,8 +142,7 @@ for(var i in settings)
//this setting is unkown, output a warning and throw it away //this setting is unkown, output a warning and throw it away
else else
{ {
console.warn("Unkown Setting: '" + i + "'"); console.warn("Unknown Setting: '" + i + "'. This setting doesn't exist or it was removed");
console.warn("This setting doesn't exist or it was removed");
} }
} }

View file

@ -27,8 +27,6 @@
var AttributePool = require("./AttributePool"); var AttributePool = require("./AttributePool");
var _opt = null;
/** /**
* ==================== General Util Functions ======================= * ==================== General Util Functions =======================
*/ */
@ -127,22 +125,13 @@ exports.opIterator = function (opsStr, optStartIndex) {
function nextRegexMatch() { function nextRegexMatch() {
prevIndex = curIndex; prevIndex = curIndex;
var result; var result;
if (_opt) { regex.lastIndex = curIndex;
result = _opt.nextOpInString(opsStr, curIndex); result = regex.exec(opsStr);
if (result) { curIndex = regex.lastIndex;
if (result.opcode() == '?') { if (result[0] == '?') {
exports.error("Hit error opcode in op stream"); 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");
}
} }
return result; return result;
} }
var regexResult = nextRegexMatch(); var regexResult = nextRegexMatch();
@ -150,13 +139,7 @@ exports.opIterator = function (opsStr, optStartIndex) {
function next(optObj) { function next(optObj) {
var op = (optObj || obj); var op = (optObj || obj);
if (_opt && regexResult) { if (regexResult[0]) {
op.attribs = regexResult.attribs();
op.lines = regexResult.lines();
op.chars = regexResult.chars();
op.opcode = regexResult.opcode();
regexResult = nextRegexMatch();
} else if ((!_opt) && regexResult[0]) {
op.attribs = regexResult[1]; op.attribs = regexResult[1];
op.lines = exports.parseNum(regexResult[2] || 0); op.lines = exports.parseNum(regexResult[2] || 0);
op.opcode = regexResult[3]; op.opcode = regexResult[3];
@ -169,7 +152,7 @@ exports.opIterator = function (opsStr, optStartIndex) {
} }
function hasNext() { function hasNext() {
return !!(_opt ? regexResult : regexResult[0]); return !!(regexResult[0]);
} }
function lastIndex() { function lastIndex() {
@ -414,159 +397,109 @@ exports.smartOpAssembler = function () {
}; };
}; };
if (_opt) {
exports.mergingOpAssembler = function () {
var assem = _opt.mergingOpAssembler();
function append(op) { exports.mergingOpAssembler = function () {
assem.append(op.opcode, op.chars, op.lines, op.attribs); // 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() { // If we get, for example, insertions [xxx\n,yyy], those don't merge,
return assem.toString(); // 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() { function flush(isEndDocument) {
assem.clear(); if (bufOp.opcode) {
} if (isEndDocument && bufOp.opcode == '=' && !bufOp.attribs) {
// final merged keep, leave it implicit
function endDocument() { } else {
assem.endDocument(); assem.append(bufOp);
} if (bufOpAdditionalCharsAfterNewline) {
bufOp.chars = bufOpAdditionalCharsAfterNewline;
return { bufOp.lines = 0;
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 {
assem.append(bufOp); assem.append(bufOp);
if (bufOpAdditionalCharsAfterNewline) { bufOpAdditionalCharsAfterNewline = 0;
bufOp.chars = bufOpAdditionalCharsAfterNewline;
bufOp.lines = 0;
assem.append(bufOp);
bufOpAdditionalCharsAfterNewline = 0;
}
} }
bufOp.opcode = '';
} }
bufOp.opcode = '';
} }
}
function append(op) { function append(op) {
if (op.chars > 0) { if (op.chars > 0) {
if (bufOp.opcode == op.opcode && bufOp.attribs == op.attribs) { if (bufOp.opcode == op.opcode && bufOp.attribs == op.attribs) {
if (op.lines > 0) { if (op.lines > 0) {
// bufOp and additional chars are all mergeable into a multi-line op // bufOp and additional chars are all mergeable into a multi-line op
bufOp.chars += bufOpAdditionalCharsAfterNewline + op.chars; bufOp.chars += bufOpAdditionalCharsAfterNewline + op.chars;
bufOp.lines += op.lines; bufOp.lines += op.lines;
bufOpAdditionalCharsAfterNewline = 0; bufOpAdditionalCharsAfterNewline = 0;
} else if (bufOp.lines == 0) { } else if (bufOp.lines == 0) {
// both bufOp and op are in-line // both bufOp and op are in-line
bufOp.chars += op.chars; bufOp.chars += op.chars;
} else {
// append in-line text to multi-line bufOp
bufOpAdditionalCharsAfterNewline += op.chars;
}
} else { } else {
flush(); // append in-line text to multi-line bufOp
exports.copyOp(op, bufOp); bufOpAdditionalCharsAfterNewline += op.chars;
} }
} else {
flush();
exports.copyOp(op, bufOp);
} }
} }
}
function endDocument() { function endDocument() {
flush(true); flush(true);
} }
function toString() { function toString() {
flush(); flush();
return assem.toString(); return assem.toString();
} }
function clear() { function clear() {
assem.clear(); assem.clear();
exports.clearOp(bufOp); exports.clearOp(bufOp);
} }
return { return {
append: append, append: append,
toString: toString, toString: toString,
clear: clear, clear: clear,
endDocument: endDocument 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() { function toString() {
return assem.toString(); return pieces.join('');
} }
function clear() { function clear() {
assem.clear(); pieces.length = 0;
} }
return { return {
append: append, append: append,
toString: toString, toString: toString,
clear: clear 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 * A custom made String Iterator

View file

@ -23,11 +23,12 @@
var padutils = require('./pad_utils').padutils; var padutils = require('./pad_utils').padutils;
var padcookie = require('./pad_cookie').padcookie; var padcookie = require('./pad_cookie').padcookie;
require('./tinycon');
var chat = (function() var chat = (function()
{ {
var isStuck = false; var isStuck = false;
var chatMentions = 0; var chatMentions = 0;
var title = document.title;
var self = { var self = {
show: function () show: function ()
{ {
@ -35,7 +36,7 @@ var chat = (function()
$("#chatbox").show(); $("#chatbox").show();
self.scrollDown(); self.scrollDown();
chatMentions = 0; chatMentions = 0;
document.title = title; Tinycon.setBubble(0);
}, },
stickToScreen: function(fromInitialCall) // Make chat stick to right hand side of screen stickToScreen: function(fromInitialCall) // Make chat stick to right hand side of screen
{ {
@ -62,8 +63,12 @@ var chat = (function()
}, },
scrollDown: function() scrollDown: function()
{ {
if($('#chatbox').css("display") != "none") if($('#chatbox').css("display") != "none"){
$('#chattext').animate({scrollTop: $('#chattext')[0].scrollHeight}, "slow"); 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() send: function()
{ {
@ -122,12 +127,9 @@ var chat = (function()
// chat throb stuff -- Just make it throw for twice as long // chat throb stuff -- Just make it throw for twice as long
if(wasMentioned && !alreadyFocused) if(wasMentioned && !alreadyFocused)
{ // If the user was mentioned show for twice as long and flash the browser window { // If the user was mentioned show for twice as long and flash the browser window
if (chatMentions == 0){
title = document.title;
}
$('#chatthrob').html("<b>"+authorName+"</b>" + ": " + text).show().delay(4000).hide(400); $('#chatthrob').html("<b>"+authorName+"</b>" + ": " + text).show().delay(4000).hide(400);
chatMentions++; chatMentions++;
document.title = "("+chatMentions+") " + title; Tinycon.setBubble(chatMentions);
} }
else else
{ {
@ -137,7 +139,7 @@ var chat = (function()
// Clear the chat mentions when the user clicks on the chat input box // Clear the chat mentions when the user clicks on the chat input box
$('#chatinput').click(function(){ $('#chatinput').click(function(){
chatMentions = 0; chatMentions = 0;
document.title = title; Tinycon.setBubble(0);
}); });
self.scrollDown(); self.scrollDown();

View file

@ -790,8 +790,6 @@ var pad = {
}, 1000); }, 1000);
} }
padsavedrevs.handleIsFullyConnected(isConnected);
// pad.determineSidebarVisibility(isConnected && !isInitialConnect); // pad.determineSidebarVisibility(isConnected && !isInitialConnect);
pad.determineChatVisibility(isConnected && !isInitialConnect); pad.determineChatVisibility(isConnected && !isInitialConnect);
pad.determineAuthorshipColorsVisibility(); pad.determineAuthorshipColorsVisibility();

View file

@ -21,6 +21,7 @@
*/ */
var padutils = require('./pad_utils').padutils; var padutils = require('./pad_utils').padutils;
var hooks = require('./pluginfw/hooks');
var myUserInfo = {}; var myUserInfo = {};
@ -529,6 +530,10 @@ var paduserlist = (function()
return; return;
} }
hooks.callAll('userJoinOrUpdate', {
userInfo: info
});
var userData = {}; var userData = {};
userData.color = typeof info.colorId == "number" ? clientVars.colorPalette[info.colorId] : info.colorId; userData.color = typeof info.colorId == "number" ? clientVars.colorPalette[info.colorId] : info.colorId;
userData.name = info.name; userData.name = info.name;

237
src/static/js/tinycon.js Normal file
View file

@ -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;
})();

View file

@ -21,7 +21,7 @@
<h1>Etherpad Lite</h1> <h1>Etherpad Lite</h1>
<a href="/admin/plugins/info">Technical information on installed plugins</a> <a href="plugins/info">Technical information on installed plugins</a>
<div class="separator"></div> <div class="separator"></div>
<h2>Installed plugins</h2> <h2>Installed plugins</h2>

View file

@ -148,7 +148,10 @@
<div id="editorcontainerbox"> <div id="editorcontainerbox">
<div id="editorcontainer"></div> <div id="editorcontainer"></div>
<div id="editorloadingbox">Loading...</div> <div id="editorloadingbox">
<p>Loading...</p>
<noscript><strong>Sorry, you have to enable Javascript in order to use this.</strong></noscript>
</div>
</div> </div>
<div id="settings" class="popup"> <div id="settings" class="popup">
@ -313,6 +316,19 @@
<% e.end_block(); %> <% e.end_block(); %>
<% e.begin_block("scripts"); %> <% e.begin_block("scripts"); %>
<script type="text/javascript">
/* Display errors on page load to the user
(Gets overridden by padutils.setupGlobalExceptionHandler)
*/
window.onerror = function(msg, url, line) {
console.log('error', arguments);
var box = document.getElementById('editorloadingbox');
box.innerHTML = '<p><b>An error occured while loading the pad</b></p>'
+ '<p><b>'+msg+'</b> '
+ '<small>in '+ url +' (line '+ line +')</small></p>'
};
</script>
<script type="text/javascript" src="../static/js/require-kernel.js"></script> <script type="text/javascript" src="../static/js/require-kernel.js"></script>
<script type="text/javascript" src="../socket.io/socket.io.js"></script> <script type="text/javascript" src="../socket.io/socket.io.js"></script>