diff --git a/node/Models/Pad.js b/node/Models/Pad.js index 8b9d3f79e..41d91f3d8 100644 --- a/node/Models/Pad.js +++ b/node/Models/Pad.js @@ -7,6 +7,7 @@ var AttributePoolFactory = require("../AttributePoolFactory"); var db = require("../db").db; var async = require("async"); var settings = require('../settings'); +var authorManager = require("../AuthorManager"); /** * Copied from the Etherpad source code. It converts Windows line breaks to Unix line breaks and convert Tabs to spaces @@ -38,6 +39,11 @@ Class('Pad', { getterName : 'getHeadRevisionNumber' }, // head + chatHead : { + is: 'rw', + init: -1 + }, // chatHead + id : { is : 'r' } }, @@ -52,7 +58,6 @@ Class('Pad', { appendRevision : function(aChangeset, author) { - if(!author) author = ''; @@ -77,7 +82,7 @@ Class('Pad', { } db.set("pad:"+this.id+":revs:"+newRev, newRevData); - db.set("pad:"+this.id, {atext: this.atext, pool: this.pool.toJsonable(), head: this.head}); + db.set("pad:"+this.id, {atext: this.atext, pool: this.pool.toJsonable(), head: this.head, chatHead: this.chatHead}); }, //appendRevision getRevisionChangeset : function(revNum, callback) @@ -186,6 +191,99 @@ Class('Pad', { return this.atext.text; }, + appendChatMessage: function(text, userId, time) + { + this.chatHead++; + //save the chat entry in the database + db.set("pad:"+this.id+":chat:"+this.chatHead, {"text": text, "userId": userId, "time": time}); + //save the new chat head + db.setSub("pad:"+this.id, ["chatHead"], this.chatHead); + }, + + getChatMessage: function(entryNum, withName, callback) + { + var _this = this; + var entry; + + async.series([ + //get the chat entry + function(callback) + { + db.get("pad:"+_this.id+":chat:"+entryNum, function(err, _entry) + { + entry = _entry; + callback(err); + }); + }, + //add the authorName + function(callback) + { + //skip if we don't need the authorName + if(!withName) + { + callback(); + return; + } + + //get the authorName + authorManager.getAuthorName(entry.userId, function(err, authorName) + { + entry.userName = authorName; + callback(err); + }); + } + ], function(err) + { + callback(err, entry); + }); + }, + + getLastChatMessages: function(count, callback) + { + //return an empty array if there are no chat messages + if(this.chatHead == -1) + { + callback(null, []); + return; + } + + var _this = this; + + //works only if we decrement the amount, for some reason + count--; + + //set the startpoint + var start = this.chatHead-count; + if(start < 0) + start = 0; + + //set the endpoint + var end = this.chatHead; + + //collect the numbers of chat entries and in which order we need them + var neededEntries = []; + var order = 0; + for(var i=start;i<=end; i++) + { + neededEntries.push({entryNum:i, order: order}); + order++; + } + + //get all entries out of the database + var entries = []; + async.forEach(neededEntries, function(entryObject, callback) + { + _this.getChatMessage(entryObject.entryNum, true, function(err, entry) + { + entries[entryObject.order] = entry; + callback(err); + }); + }, function(err) + { + callback(err, entries); + }); + }, + init : function (callback) { var _this = this; @@ -205,6 +303,11 @@ Class('Pad', { _this.head = value.head; _this.atext = value.atext; _this.pool = _this.pool.fromJsonable(value.pool); + + if(value.chatHead != null) + _this.chatHead = value.chatHead; + else + _this.chatHead = -1; } //this pad doesn't exist, so create it else diff --git a/node/PadMessageHandler.js b/node/PadMessageHandler.js index 694ccf350..00902b1a5 100644 --- a/node/PadMessageHandler.js +++ b/node/PadMessageHandler.js @@ -164,6 +164,11 @@ exports.handleMessage = function(client, message) { handleUserInfoUpdate(client, message); } + else if(message.type == "COLLABROOM" && + message.data.type == "CHAT_MESSAGE") + { + handleChatMessage(client, message); + } else if(message.type == "COLLABROOM" && message.data.type == "CLIENT_MESSAGE" && message.data.payload.type == "suggestUserName") @@ -177,6 +182,71 @@ exports.handleMessage = function(client, message) } } +/** + * Handles a Chat Message + * @param client the client that send this message + * @param message the message from the client + */ +function handleChatMessage(client, message) +{ + var time = new Date().getTime(); + var userId = sessioninfos[client.id].author; + var text = message.data.text; + var padId = session2pad[client.id]; + + var pad; + var userName; + + async.series([ + //get the pad + function(callback) + { + padManager.getPad(padId, function(err, _pad) + { + pad = _pad; + callback(err); + }); + }, + function(callback) + { + authorManager.getAuthorName(userId, function(err, _userName) + { + userName = _userName; + callback(err); + }); + }, + //save the chat message and broadcast it + function(callback) + { + //save the chat message + pad.appendChatMessage(text, userId, time); + + var msg = { + type: "COLLABROOM", + data: { + type: "CHAT_MESSAGE", + userId: userId, + userName: userName, + time: time, + text: text + } + }; + + //broadcast the chat message to everyone on the pad + for(var i in pad2sessions[padId]) + { + socketio.sockets.sockets[pad2sessions[padId][i]].json.send(msg); + } + + callback(); + } + ], function(err) + { + if(err) throw err; + }); +} + + /** * Handles a handleSuggestUserName, that means a user have suggest a userName for a other user * @param client the client that send this message @@ -509,6 +579,7 @@ function handleClientReady(client, message) var pad; var historicalAuthorData = {}; var readOnlyId; + var chatMessages; async.series([ //get all authordata of this new user @@ -557,19 +628,36 @@ function handleClientReady(client, message) ], callback); }); }, + //these db requests all need the pad object function(callback) { var authors = pad.getAllAuthors(); - //get all author data out of the database - async.forEach(authors, function(authorId, callback) - { - authorManager.getAuthor(authorId, function(err, author) + async.parallel([ + //get all author data out of the database + function(callback) { - historicalAuthorData[authorId] = author; - callback(err); - }); - }, callback); + async.forEach(authors, function(authorId, callback) + { + authorManager.getAuthor(authorId, function(err, author) + { + historicalAuthorData[authorId] = author; + callback(err); + }); + }, callback); + }, + //get the latest chat messages + function(callback) + { + pad.getLastChatMessages(20, function(err, _chatMessages) + { + chatMessages = _chatMessages; + callback(err); + }); + } + ], callback); + + }, function(callback) { @@ -629,12 +717,7 @@ function handleClientReady(client, message) "padId": message.padId, "initialTitle": "Pad: " + message.padId, "opts": {}, - "chatHistory": { - "start": 0, - "historicalAuthorData": historicalAuthorData, - "end": 0, - "lines": [] - }, + "chatHistory": chatMessages, "numConnectedUsers": pad2sessions[message.padId].length, "isProPad": false, "readOnlyId": readOnlyId, diff --git a/node/minify.js b/node/minify.js index aee3d3c05..b6f670bdf 100644 --- a/node/minify.js +++ b/node/minify.js @@ -39,7 +39,7 @@ exports.padJS = function(req, res) { res.header("Content-Type","text/javascript"); - var jsFiles = ["jquery.min.js", "plugins.js", "undo-xpopup.js", "json2.js", "pad_utils.js", "pad_cookie.js", "pad_editor.js", "pad_editbar.js", "pad_docbar.js", "pad_modals.js", "ace.js", "collab_client.js", "pad_userlist.js", "pad_impexp.js", "pad_savedrevs.js", "pad_connectionstatus.js", "pad2.js"]; + var jsFiles = ["jquery.min.js", "plugins.js", "undo-xpopup.js", "json2.js", "pad_utils.js", "pad_cookie.js", "pad_editor.js", "pad_editbar.js", "pad_docbar.js", "pad_modals.js", "ace.js", "collab_client.js", "pad_userlist.js", "pad_impexp.js", "pad_savedrevs.js", "pad_connectionstatus.js", "pad2.js", "jquery-ui-slide.js", "chat.js"]; //minifying is enabled if(settings.minify) diff --git a/static/css/pad_lite.css b/static/css/pad_lite.css index 5943905b9..169c91f20 100644 --- a/static/css/pad_lite.css +++ b/static/css/pad_lite.css @@ -1186,3 +1186,120 @@ padding-left:4px; padding-right:4px; padding-top:2px; }*/ + +#chatbox +{ + position:absolute; + bottom:0px; + right: 20px; + width: 180px; + height: 200px; + z-index: 400; + background-color:#f3f7f9; + border-left: 1px solid #999; + border-right: 1px solid #999; + border-top: 1px solid #999; + padding: 3px; + padding-bottom: 10px; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + display:none; +} + +#chattext +{ + background-color: white; + border: 1px solid white; + overflow-y:scroll; + height: 165px; + font-size: 12px; +} + +#chattext p +{ + padding: 3px; +} + +#chatinputbox +{ + padding: 3px 2px; +} + +#chatlabel +{ + font-size:13px; + line-height:16px; + font-weight:bold; + color:#555; + text-decoration: none; + position: relative; + bottom: 3px; +} + +#chatinput +{ + border: 1px solid #BBBBBB; + width: 100%; + float:right; +} + +#chaticon +{ + z-index: 400; + position:absolute; + bottom:0px; + right: 20px; + padding: 5px; + border-left: 1px solid #999; + border-right: 1px solid #999; + border-top: 1px solid #999; + border-top-left-radius: 5px; + border-top-right-radius: 5px; +} + +#chaticon a +{ + text-decoration: none; +} + +#chatcounter +{ + color:#555; + font-size:9px; + position:relative; + bottom: 2px; +} + +#titlebar +{ + line-height:16px; + font-weight:bold; + color:#555; + position: relative; + bottom: 2px; +} + +#titlelabel +{ + font-size:13px; +} + +#titlecross +{ + font-size:16px; + float:right; + text-align: right; + text-decoration: none; + color:#555; +} + +.time +{ + float:right; + color:#333; + font-style:italic; + font-size: 10px; + margin-left: 3px; + margin-right: 3px; + margin-top:2px; +} diff --git a/static/js/ace2_inner.js b/static/js/ace2_inner.js index afafcec72..8be6e7ff5 100644 --- a/static/js/ace2_inner.js +++ b/static/js/ace2_inner.js @@ -138,10 +138,12 @@ function OUTER(gscope) } var dynamicCSS = null; + var dynamicCSSTop = null; function initDynamicCSS() { dynamicCSS = makeCSSManager("dynamicsyntax"); + dynamicCSSTop = makeCSSManager("dynamicsyntax", true); } var changesetTracker = makeChangesetTracker(scheduler, rep.apool, { @@ -184,6 +186,7 @@ function OUTER(gscope) if (dynamicCSS) { dynamicCSS.removeSelectorStyle(getAuthorColorClassSelector(getAuthorClassName(author))); + dynamicCSSTop.removeSelectorStyle(getAuthorColorClassSelector(getAuthorClassName(author))); } } else @@ -201,6 +204,9 @@ function OUTER(gscope) dynamicCSS.selectorStyle(getAuthorColorClassSelector( getAuthorClassName(author))).backgroundColor = bgcolor; + + dynamicCSSTop.selectorStyle(getAuthorColorClassSelector( + getAuthorClassName(author))).backgroundColor = bgcolor; } } } diff --git a/static/js/chat.js b/static/js/chat.js new file mode 100644 index 000000000..4be86d540 --- /dev/null +++ b/static/js/chat.js @@ -0,0 +1,94 @@ +var chat = (function() +{ + var self = { + show: function () + { + $("#chaticon").hide("slide", { direction: "down" }, 500, function() + { + $("#chatbox").show("slide", { direction: "down" }, 750, self.scrollDown); + }); + }, + hide: function () + { + $("#chatcounter").text("0"); + $("#chatbox").hide("slide", { direction: "down" }, 750, function() + { + $("#chaticon").show("slide", { direction: "down" }, 500); + }); + }, + scrollDown: function() + { + console.log($('#chatbox').css("display")); + + if($('#chatbox').css("display") != "none") + $('#chattext').animate({scrollTop: $('#chattext')[0].scrollHeight}, "slow"); + }, + send: function() + { + var text = $("#chatinput").val(); + pad.collabClient.sendMessage({"type": "CHAT_MESSAGE", "text": text}); + $("#chatinput").val(""); + }, + addMessage: function(msg, increment) + { + //correct the time + msg.time += 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 text = padutils.escapeHtmlWithClickableLinks(padutils.escapeHtml(msg.text), "_blank"); + var authorName = msg.userName == null ? "unnamed" : padutils.escapeHtml(msg.userName); + + var html = "

" + authorName + ":" + timeStr + " " + text + "

"; + $("#chattext").append(html); + + //should we increment the counter?? + if(increment) + { + var count = Number($("#chatcounter").text()); + count++; + $("#chatcounter").text(count); + + //animation + $("#chatcounter").css({"font-weight": "bold"}); + setTimeout('$("#chatcounter").css({"font-weight": "normal"})', 500); + } + + self.scrollDown(); + }, + init: function() + { + $("#chatinput").keypress(function(evt) + { + //if the user typed enter, fire the send + if(evt.which == 13) + { + evt.preventDefault(); + self.send(); + } + }); + + for(var i in clientVars.chatHistory) + { + this.addMessage(clientVars.chatHistory[i], false); + } + $("#chatcounter").text(clientVars.chatHistory.length); + } + } + + return self; +}()); diff --git a/static/js/collab_client.js b/static/js/collab_client.js index 1abc8524b..ff3395c7a 100644 --- a/static/js/collab_client.js +++ b/static/js/collab_client.js @@ -441,6 +441,10 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options) { callbacks.onClientMessage(msg.payload); } + else if (msg.type == "CHAT_MESSAGE") + { + chat.addMessage(msg, true); + } else if (msg.type == "SERVER_MESSAGE") { callbacks.onServerMessage(msg.payload); @@ -858,6 +862,7 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options) handleMessageFromServer: handleMessageFromServer, getConnectedUsers: getConnectedUsers, sendClientMessage: sendClientMessage, + sendMessage: sendMessage, getCurrentRevisionNumber: getCurrentRevisionNumber, getDiagnosticInfo: getDiagnosticInfo, getMissedChanges: getMissedChanges, diff --git a/static/js/cssmanager.js b/static/js/cssmanager.js index feaef67a7..85e5552ce 100644 --- a/static/js/cssmanager.js +++ b/static/js/cssmanager.js @@ -14,12 +14,16 @@ * limitations under the License. */ -function makeCSSManager(emptyStylesheetTitle) +function makeCSSManager(emptyStylesheetTitle, top) { - function getSheetByTitle(title) + function getSheetByTitle(title, top) { - var allSheets = document.styleSheets; + if(top) + var allSheets = window.top.document.styleSheets; + else + var allSheets = document.styleSheets; + for (var i = 0; i < allSheets.length; i++) { var s = allSheets[i]; @@ -42,7 +46,7 @@ function makeCSSManager(emptyStylesheetTitle) return null; }*/ - var browserSheet = getSheetByTitle(emptyStylesheetTitle); + var browserSheet = getSheetByTitle(emptyStylesheetTitle, top); //var browserTag = getSheetTagByTitle(emptyStylesheetTitle); diff --git a/static/js/jquery-ui-slide.js b/static/js/jquery-ui-slide.js new file mode 100644 index 000000000..37fcf6394 --- /dev/null +++ b/static/js/jquery-ui-slide.js @@ -0,0 +1,45 @@ +/* + * jQuery UI Effects 1.8.14 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/ + */ +jQuery.effects||function(f,j){function m(c){var a;if(c&&c.constructor==Array&&c.length==3)return c;if(a=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(c))return[parseInt(a[1],10),parseInt(a[2],10),parseInt(a[3],10)];if(a=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(c))return[parseFloat(a[1])*2.55,parseFloat(a[2])*2.55,parseFloat(a[3])*2.55];if(a=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(c))return[parseInt(a[1], +16),parseInt(a[2],16),parseInt(a[3],16)];if(a=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(c))return[parseInt(a[1]+a[1],16),parseInt(a[2]+a[2],16),parseInt(a[3]+a[3],16)];if(/rgba\(0, 0, 0, 0\)/.exec(c))return n.transparent;return n[f.trim(c).toLowerCase()]}function s(c,a){var b;do{b=f.curCSS(c,a);if(b!=""&&b!="transparent"||f.nodeName(c,"body"))break;a="backgroundColor"}while(c=c.parentNode);return m(b)}function o(){var c=document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle, +a={},b,d;if(c&&c.length&&c[0]&&c[c[0]])for(var e=c.length;e--;){b=c[e];if(typeof c[b]=="string"){d=b.replace(/\-(\w)/g,function(g,h){return h.toUpperCase()});a[d]=c[b]}}else for(b in c)if(typeof c[b]==="string")a[b]=c[b];return a}function p(c){var a,b;for(a in c){b=c[a];if(b==null||f.isFunction(b)||a in t||/scrollbar/.test(a)||!/color/i.test(a)&&isNaN(parseFloat(b)))delete c[a]}return c}function u(c,a){var b={_:0},d;for(d in a)if(c[d]!=a[d])b[d]=a[d];return b}function k(c,a,b,d){if(typeof c=="object"){d= +a;b=null;a=c;c=a.effect}if(f.isFunction(a)){d=a;b=null;a={}}if(typeof a=="number"||f.fx.speeds[a]){d=b;b=a;a={}}if(f.isFunction(b)){d=b;b=null}a=a||{};b=b||a.duration;b=f.fx.off?0:typeof b=="number"?b:b in f.fx.speeds?f.fx.speeds[b]:f.fx.speeds._default;d=d||a.complete;return[c,a,b,d]}function l(c){if(!c||typeof c==="number"||f.fx.speeds[c])return true;if(typeof c==="string"&&!f.effects[c])return true;return false}f.effects={};f.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor", +"borderTopColor","borderColor","color","outlineColor"],function(c,a){f.fx.step[a]=function(b){if(!b.colorInit){b.start=s(b.elem,a);b.end=m(b.end);b.colorInit=true}b.elem.style[a]="rgb("+Math.max(Math.min(parseInt(b.pos*(b.end[0]-b.start[0])+b.start[0],10),255),0)+","+Math.max(Math.min(parseInt(b.pos*(b.end[1]-b.start[1])+b.start[1],10),255),0)+","+Math.max(Math.min(parseInt(b.pos*(b.end[2]-b.start[2])+b.start[2],10),255),0)+")"}});var n={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0, +0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211, +211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]},q=["add","remove","toggle"],t={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};f.effects.animateClass=function(c,a,b, +d){if(f.isFunction(b)){d=b;b=null}return this.queue(function(){var e=f(this),g=e.attr("style")||" ",h=p(o.call(this)),r,v=e.attr("class");f.each(q,function(w,i){c[i]&&e[i+"Class"](c[i])});r=p(o.call(this));e.attr("class",v);e.animate(u(h,r),{queue:false,duration:a,easing:b,complete:function(){f.each(q,function(w,i){c[i]&&e[i+"Class"](c[i])});if(typeof e.attr("style")=="object"){e.attr("style").cssText="";e.attr("style").cssText=g}else e.attr("style",g);d&&d.apply(this,arguments);f.dequeue(this)}})})}; +f.fn.extend({_addClass:f.fn.addClass,addClass:function(c,a,b,d){return a?f.effects.animateClass.apply(this,[{add:c},a,b,d]):this._addClass(c)},_removeClass:f.fn.removeClass,removeClass:function(c,a,b,d){return a?f.effects.animateClass.apply(this,[{remove:c},a,b,d]):this._removeClass(c)},_toggleClass:f.fn.toggleClass,toggleClass:function(c,a,b,d,e){return typeof a=="boolean"||a===j?b?f.effects.animateClass.apply(this,[a?{add:c}:{remove:c},b,d,e]):this._toggleClass(c,a):f.effects.animateClass.apply(this, +[{toggle:c},a,b,d])},switchClass:function(c,a,b,d,e){return f.effects.animateClass.apply(this,[{add:a,remove:c},b,d,e])}});f.extend(f.effects,{version:"1.8.14",save:function(c,a){for(var b=0;b").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}); +c.wrap(b);b=c.parent();if(c.css("position")=="static"){b.css({position:"relative"});c.css({position:"relative"})}else{f.extend(a,{position:c.css("position"),zIndex:c.css("z-index")});f.each(["top","left","bottom","right"],function(d,e){a[e]=c.css(e);if(isNaN(parseInt(a[e],10)))a[e]="auto"});c.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})}return b.css(a).show()},removeWrapper:function(c){if(c.parent().is(".ui-effects-wrapper"))return c.parent().replaceWith(c);return c},setTransition:function(c, +a,b,d){d=d||{};f.each(a,function(e,g){unit=c.cssUnit(g);if(unit[0]>0)d[g]=unit[0]*b+unit[1]});return d}});f.fn.extend({effect:function(c){var a=k.apply(this,arguments),b={options:a[1],duration:a[2],callback:a[3]};a=b.options.mode;var d=f.effects[c];if(f.fx.off||!d)return a?this[a](b.duration,b.callback):this.each(function(){b.callback&&b.callback.call(this)});return d.call(this,b)},_show:f.fn.show,show:function(c){if(l(c))return this._show.apply(this,arguments);else{var a=k.apply(this,arguments); +a[1].mode="show";return this.effect.apply(this,a)}},_hide:f.fn.hide,hide:function(c){if(l(c))return this._hide.apply(this,arguments);else{var a=k.apply(this,arguments);a[1].mode="hide";return this.effect.apply(this,a)}},__toggle:f.fn.toggle,toggle:function(c){if(l(c)||typeof c==="boolean"||f.isFunction(c))return this.__toggle.apply(this,arguments);else{var a=k.apply(this,arguments);a[1].mode="toggle";return this.effect.apply(this,a)}},cssUnit:function(c){var a=this.css(c),b=[];f.each(["em","px","%", +"pt"],function(d,e){if(a.indexOf(e)>0)b=[parseFloat(a),e]});return b}});f.easing.jswing=f.easing.swing;f.extend(f.easing,{def:"easeOutQuad",swing:function(c,a,b,d,e){return f.easing[f.easing.def](c,a,b,d,e)},easeInQuad:function(c,a,b,d,e){return d*(a/=e)*a+b},easeOutQuad:function(c,a,b,d,e){return-d*(a/=e)*(a-2)+b},easeInOutQuad:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a+b;return-d/2*(--a*(a-2)-1)+b},easeInCubic:function(c,a,b,d,e){return d*(a/=e)*a*a+b},easeOutCubic:function(c,a,b,d,e){return d* +((a=a/e-1)*a*a+1)+b},easeInOutCubic:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a+b;return d/2*((a-=2)*a*a+2)+b},easeInQuart:function(c,a,b,d,e){return d*(a/=e)*a*a*a+b},easeOutQuart:function(c,a,b,d,e){return-d*((a=a/e-1)*a*a*a-1)+b},easeInOutQuart:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a*a+b;return-d/2*((a-=2)*a*a*a-2)+b},easeInQuint:function(c,a,b,d,e){return d*(a/=e)*a*a*a*a+b},easeOutQuint:function(c,a,b,d,e){return d*((a=a/e-1)*a*a*a*a+1)+b},easeInOutQuint:function(c,a,b,d,e){if((a/= +e/2)<1)return d/2*a*a*a*a*a+b;return d/2*((a-=2)*a*a*a*a+2)+b},easeInSine:function(c,a,b,d,e){return-d*Math.cos(a/e*(Math.PI/2))+d+b},easeOutSine:function(c,a,b,d,e){return d*Math.sin(a/e*(Math.PI/2))+b},easeInOutSine:function(c,a,b,d,e){return-d/2*(Math.cos(Math.PI*a/e)-1)+b},easeInExpo:function(c,a,b,d,e){return a==0?b:d*Math.pow(2,10*(a/e-1))+b},easeOutExpo:function(c,a,b,d,e){return a==e?b+d:d*(-Math.pow(2,-10*a/e)+1)+b},easeInOutExpo:function(c,a,b,d,e){if(a==0)return b;if(a==e)return b+d;if((a/= +e/2)<1)return d/2*Math.pow(2,10*(a-1))+b;return d/2*(-Math.pow(2,-10*--a)+2)+b},easeInCirc:function(c,a,b,d,e){return-d*(Math.sqrt(1-(a/=e)*a)-1)+b},easeOutCirc:function(c,a,b,d,e){return d*Math.sqrt(1-(a=a/e-1)*a)+b},easeInOutCirc:function(c,a,b,d,e){if((a/=e/2)<1)return-d/2*(Math.sqrt(1-a*a)-1)+b;return d/2*(Math.sqrt(1-(a-=2)*a)+1)+b},easeInElastic:function(c,a,b,d,e){c=1.70158;var g=0,h=d;if(a==0)return b;if((a/=e)==1)return b+d;g||(g=e*0.3);if(h + @@ -115,12 +116,12 @@ We removed this feature cause its not worth the space it needs in the editbar -->
  • -
  • +