Removed minified files
authorAlexander Ebert <ebert@woltlab.com>
Wed, 30 Apr 2014 09:56:33 +0000 (11:56 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Wed, 30 Apr 2014 09:56:33 +0000 (11:56 +0200)
wcfsetup/install/files/js/WCF.Combined.js [deleted file]

diff --git a/wcfsetup/install/files/js/WCF.Combined.js b/wcfsetup/install/files/js/WCF.Combined.js
deleted file mode 100755 (executable)
index 6a0da29..0000000
+++ /dev/null
@@ -1,23265 +0,0 @@
-// WCF.Combined.js -- DO NOT EDIT
-
-// included files:
-//  - WCF.js
-//  - WCF.Like.js
-//  - WCF.ACL.js
-//  - WCF.Attachment.js
-//  - WCF.ColorPicker.js
-//  - WCF.Comment.js
-//  - WCF.ImageViewer.js
-//  - WCF.Label.js
-//  - WCF.Location.js
-//  - WCF.Message.js
-//  - WCF.Moderation.js
-//  - WCF.Poll.js
-//  - WCF.Search.Message.js
-//  - WCF.Tagging.js
-//  - WCF.User.js
-
-// WCF.js
-/**
- * Class and function collection for WCF.
- * 
- * Major Contributors: Markus Bartz, Tim Duesterhus, Matthias Schmidt and Marcel Werk
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2014 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- */
-
-(function() {
-       // store original implementation
-       var $jQueryData = jQuery.fn.data;
-       
-       /**
-        * Override jQuery.fn.data() to support custom 'ID' suffix which will
-        * be translated to '-id' at runtime.
-        * 
-        * @see jQuery.fn.data()
-        */
-       jQuery.fn.data = function(key, value) {
-               if (key) {
-                       switch (typeof key) {
-                               case 'object':
-                                       for (var $key in key) {
-                                               if ($key.match(/ID$/)) {
-                                                       var $value = key[$key];
-                                                       delete key[$key];
-                                                       
-                                                       $key = $key.replace(/ID$/, '-id');
-                                                       key[$key] = $value;
-                                               }
-                                       }
-                                       
-                                       arguments[0] = key;
-                               break;
-                               
-                               case 'string':
-                                       if (key.match(/ID$/)) {
-                                               arguments[0] = key.replace(/ID$/, '-id');
-                                       }
-                               break;
-                       } 
-               }
-               
-               // call jQuery's own data method
-               var $data = $jQueryData.apply(this, arguments);
-               
-               // handle .data() call without arguments
-               if (key === undefined) {
-                       for (var $key in $data) {
-                               if ($key.match(/Id$/)) {
-                                       $data[$key.replace(/Id$/, 'ID')] = $data[$key];
-                                       delete $data[$key];
-                               }
-                       }
-               }
-               
-               return $data;
-       };
-       
-       // provide a sane window.console implementation
-       if (!window.console) window.console = { };
-       var consoleProperties = [ "log",/* "debug",*/ "info", "warn", "exception", "assert", "dir", "dirxml", "trace", "group", "groupEnd", "groupCollapsed", "profile", "profileEnd", "count", "clear", "time", "timeEnd", "timeStamp", "table", "error" ];
-       for (var i = 0; i < consoleProperties.length; i++) {
-               if (typeof (console[consoleProperties[i]]) === 'undefined') {
-                       console[consoleProperties[i]] = function () { };
-               }
-       }
-       
-       if (typeof(console.debug) === 'undefined') {
-               // forward console.debug to console.log (IE9)
-               console.debug = function(string) { console.log(string); };
-       }
-})();
-
-/**
- * Simple JavaScript Inheritance
- * By John Resig http://ejohn.org/
- * MIT Licensed.
- */
-// Inspired by base2 and Prototype
-(function(){var a=false,b=/xyz/.test(function(){xyz})?/\b_super\b/:/.*/;this.Class=function(){};Class.extend=function(c){function g(){if(!a&&this.init)this.init.apply(this,arguments);}var d=this.prototype;a=true;var e=new this;a=false;for(var f in c){e[f]=typeof c[f]=="function"&&typeof d[f]=="function"&&b.test(c[f])?function(a,b){return function(){var c=this._super;this._super=d[a];var e=b.apply(this,arguments);this._super=c;return e;};}(f,c[f]):c[f]}g.prototype=e;g.prototype.constructor=g;g.extend=arguments.callee;return g;};})();
-
-/*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas, David Knight. Dual MIT/BSD license */
-window.matchMedia||(window.matchMedia=function(){"use strict";var e=window.styleMedia||window.media;if(!e){var t=document.createElement("style"),n=document.getElementsByTagName("script")[0],r=null;t.type="text/css";t.id="matchmediajs-test";n.parentNode.insertBefore(t,n);r="getComputedStyle"in window&&window.getComputedStyle(t,null)||t.currentStyle;e={matchMedium:function(e){var n="@media "+e+"{ #matchmediajs-test { width: 1px; } }";if(t.styleSheet){t.styleSheet.cssText=n}else{t.textContent=n}return r.width==="1px"}}}return function(t){return{matches:e.matchMedium(t||"all"),media:t||"all"}}}());
-
-/*! matchMedia() polyfill addListener/removeListener extension. Author & copyright (c) 2012: Scott Jehl. Dual MIT/BSD license */
-(function(){if(window.matchMedia&&window.matchMedia("all").addListener){return false}var e=window.matchMedia,t=e("only all").matches,n=false,r=0,i=[],s=function(t){clearTimeout(r);r=setTimeout(function(){for(var t=0,n=i.length;t<n;t++){var r=i[t].mql,s=i[t].listeners||[],o=e(r.media).matches;if(o!==r.matches){r.matches=o;for(var u=0,a=s.length;u<a;u++){s[u].call(window,r)}}}},30)};window.matchMedia=function(r){var o=e(r),u=[],a=0;o.addListener=function(e){if(!t){return}if(!n){n=true;window.addEventListener("resize",s,true)}if(a===0){a=i.push({mql:o,listeners:u})}u.push(e)};o.removeListener=function(e){for(var t=0,n=u.length;t<n;t++){if(u[t]===e){u.splice(t,1)}}};return o}})();
-
-/*!
- * enquire.js v2.1.0 - Awesome Media Queries in JavaScript
- * Copyright (c) 2013 Nick Williams - http://wicky.nillia.ms/enquire.js
- * License: MIT (http://www.opensource.org/licenses/mit-license.php)
- */
-(function(t,i,n){var e=i.matchMedia;"undefined"!=typeof module&&module.exports?module.exports=n(e):"function"==typeof define&&define.amd?define(function(){return i[t]=n(e)}):i[t]=n(e)})("enquire",this,function(t){"use strict";function i(t,i){var n,e=0,s=t.length;for(e;s>e&&(n=i(t[e],e),n!==!1);e++);}function n(t){return"[object Array]"===Object.prototype.toString.apply(t)}function e(t){return"function"==typeof t}function s(t){this.options=t,!t.deferSetup&&this.setup()}function o(i,n){this.query=i,this.isUnconditional=n,this.handlers=[],this.mql=t(i);var e=this;this.listener=function(t){e.mql=t,e.assess()},this.mql.addListener(this.listener)}function r(){if(!t)throw Error("matchMedia not present, legacy browsers require a polyfill");this.queries={},this.browserIsIncapable=!t("only all").matches}return s.prototype={setup:function(){this.options.setup&&this.options.setup(),this.initialised=!0},on:function(){!this.initialised&&this.setup(),this.options.match&&this.options.match()},off:function(){this.options.unmatch&&this.options.unmatch()},destroy:function(){this.options.destroy?this.options.destroy():this.off()},equals:function(t){return this.options===t||this.options.match===t}},o.prototype={addHandler:function(t){var i=new s(t);this.handlers.push(i),this.matches()&&i.on()},removeHandler:function(t){var n=this.handlers;i(n,function(i,e){return i.equals(t)?(i.destroy(),!n.splice(e,1)):void 0})},matches:function(){return this.mql.matches||this.isUnconditional},clear:function(){i(this.handlers,function(t){t.destroy()}),this.mql.removeListener(this.listener),this.handlers.length=0},assess:function(){var t=this.matches()?"on":"off";i(this.handlers,function(i){i[t]()})}},r.prototype={register:function(t,s,r){var h=this.queries,u=r&&this.browserIsIncapable;return h[t]||(h[t]=new o(t,u)),e(s)&&(s={match:s}),n(s)||(s=[s]),i(s,function(i){h[t].addHandler(i)}),this},unregister:function(t,i){var n=this.queries[t];return n&&(i?n.removeHandler(i):(n.clear(),delete this.queries[t])),this}},new r});
-
-/*! head.load - v1.0.3 */
-(function(n,t){"use strict";function w(){}function u(n,t){if(n){typeof n=="object"&&(n=[].slice.call(n));for(var i=0,r=n.length;i<r;i++)t.call(n,n[i],i)}}function it(n,i){var r=Object.prototype.toString.call(i).slice(8,-1);return i!==t&&i!==null&&r===n}function s(n){return it("Function",n)}function a(n){return it("Array",n)}function et(n){var i=n.split("/"),t=i[i.length-1],r=t.indexOf("?");return r!==-1?t.substring(0,r):t}function f(n){(n=n||w,n._done)||(n(),n._done=1)}function ot(n,t,r,u){var f=typeof n=="object"?n:{test:n,success:!t?!1:a(t)?t:[t],failure:!r?!1:a(r)?r:[r],callback:u||w},e=!!f.test;return e&&!!f.success?(f.success.push(f.callback),i.load.apply(null,f.success)):e||!f.failure?u():(f.failure.push(f.callback),i.load.apply(null,f.failure)),i}function v(n){var t={},i,r;if(typeof n=="object")for(i in n)!n[i]||(t={name:i,url:n[i]});else t={name:et(n),url:n};return(r=c[t.name],r&&r.url===t.url)?r:(c[t.name]=t,t)}function y(n){n=n||c;for(var t in n)if(n.hasOwnProperty(t)&&n[t].state!==l)return!1;return!0}function st(n){n.state=ft;u(n.onpreload,function(n){n.call()})}function ht(n){n.state===t&&(n.state=nt,n.onpreload=[],rt({url:n.url,type:"cache"},function(){st(n)}))}function ct(){var n=arguments,t=n[n.length-1],r=[].slice.call(n,1),f=r[0];return(s(t)||(t=null),a(n[0]))?(n[0].push(t),i.load.apply(null,n[0]),i):(f?(u(r,function(n){s(n)||!n||ht(v(n))}),b(v(n[0]),s(f)?f:function(){i.load.apply(null,r)})):b(v(n[0])),i)}function lt(){var n=arguments,t=n[n.length-1],r={};return(s(t)||(t=null),a(n[0]))?(n[0].push(t),i.load.apply(null,n[0]),i):(u(n,function(n){n!==t&&(n=v(n),r[n.name]=n)}),u(n,function(n){n!==t&&(n=v(n),b(n,function(){y(r)&&f(t)}))}),i)}function b(n,t){if(t=t||w,n.state===l){t();return}if(n.state===tt){i.ready(n.name,t);return}if(n.state===nt){n.onpreload.push(function(){b(n,t)});return}n.state=tt;rt(n,function(){n.state=l;t();u(h[n.name],function(n){f(n)});o&&y()&&u(h.ALL,function(n){f(n)})})}function at(n){n=n||"";var t=n.split("?")[0].split(".");return t[t.length-1].toLowerCase()}function rt(t,i){function e(t){t=t||n.event;u.onload=u.onreadystatechange=u.onerror=null;i()}function o(f){f=f||n.event;(f.type==="load"||/loaded|complete/.test(u.readyState)&&(!r.documentMode||r.documentMode<9))&&(n.clearTimeout(t.errorTimeout),n.clearTimeout(t.cssTimeout),u.onload=u.onreadystatechange=u.onerror=null,i())}function s(){if(t.state!==l&&t.cssRetries<=20){for(var i=0,f=r.styleSheets.length;i<f;i++)if(r.styleSheets[i].href===u.href){o({type:"load"});return}t.cssRetries++;t.cssTimeout=n.setTimeout(s,250)}}var u,h,f;i=i||w;h=at(t.url);h==="css"?(u=r.createElement("link"),u.type="text/"+(t.type||"css"),u.rel="stylesheet",u.href=t.url,t.cssRetries=0,t.cssTimeout=n.setTimeout(s,500)):(u=r.createElement("script"),u.type="text/"+(t.type||"javascript"),u.src=t.url);u.onload=u.onreadystatechange=o;u.onerror=e;u.async=!1;u.defer=!1;t.errorTimeout=n.setTimeout(function(){e({type:"timeout"})},7e3);f=r.head||r.getElementsByTagName("head")[0];f.insertBefore(u,f.lastChild)}function vt(){for(var t,u=r.getElementsByTagName("script"),n=0,f=u.length;n<f;n++)if(t=u[n].getAttribute("data-headjs-load"),!!t){i.load(t);return}}function yt(n,t){var v,p,e;return n===r?(o?f(t):d.push(t),i):(s(n)&&(t=n,n="ALL"),a(n))?(v={},u(n,function(n){v[n]=c[n];i.ready(n,function(){y(v)&&f(t)})}),i):typeof n!="string"||!s(t)?i:(p=c[n],p&&p.state===l||n==="ALL"&&y()&&o)?(f(t),i):(e=h[n],e?e.push(t):e=h[n]=[t],i)}function e(){if(!r.body){n.clearTimeout(i.readyTimeout);i.readyTimeout=n.setTimeout(e,50);return}o||(o=!0,vt(),u(d,function(n){f(n)}))}function k(){r.addEventListener?(r.removeEventListener("DOMContentLoaded",k,!1),e()):r.readyState==="complete"&&(r.detachEvent("onreadystatechange",k),e())}var r=n.document,d=[],h={},c={},ut="async"in r.createElement("script")||"MozAppearance"in r.documentElement.style||n.opera,o,g=n.head_conf&&n.head_conf.head||"head",i=n[g]=n[g]||function(){i.ready.apply(null,arguments)},nt=1,ft=2,tt=3,l=4,p;if(r.readyState==="complete")e();else if(r.addEventListener)r.addEventListener("DOMContentLoaded",k,!1),n.addEventListener("load",e,!1);else{r.attachEvent("onreadystatechange",k);n.attachEvent("onload",e);p=!1;try{p=!n.frameElement&&r.documentElement}catch(wt){}p&&p.doScroll&&function pt(){if(!o){try{p.doScroll("left")}catch(t){n.clearTimeout(i.readyTimeout);i.readyTimeout=n.setTimeout(pt,50);return}e()}}()}i.load=i.js=ut?lt:ct;i.test=ot;i.ready=yt;i.ready(r,function(){y()&&u(h.ALL,function(n){f(n)});i.feature&&i.feature("domloaded",!0)})})(window);
-/*
-//# sourceMappingURL=head.load.min.js.map
-*/
-
-/**
- * Provides a hashCode() method for strings, similar to Java's String.hashCode().
- * 
- * @see        http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
- */
-String.prototype.hashCode = function() {
-       var $char;
-       var $hash = 0;
-       
-       if (this.length) {
-               for (var $i = 0, $length = this.length; $i < $length; $i++) {
-                       $char = this.charCodeAt($i);
-                       $hash = (($hash << 5) - $hash) + $char;
-                       $hash = $hash & $hash; // convert to 32bit integer
-               }
-       }
-       
-       return $hash;
-};
-
-/**
- * Adds a Fisher-Yates shuffle algorithm for arrays.
- * 
- * @see        http://stackoverflow.com/a/2450976
- */
-function shuffle(array) {
-       var currentIndex = array.length, temporaryValue, randomIndex;
-       
-       // While there remain elements to shuffle...
-       while (0 !== currentIndex) {
-               // Pick a remaining element...
-               randomIndex = Math.floor(Math.random() * currentIndex);
-               currentIndex -= 1;
-               
-               // And swap it with the current element.
-               temporaryValue = array[currentIndex];
-               array[currentIndex] = array[randomIndex];
-               array[randomIndex] = temporaryValue;
-       }
-       
-       return this;
-};
-
-/**
- * User-Agent based browser detection and touch detection.
- */
-(function() {
-       var ua = navigator.userAgent.toLowerCase();
-       var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
-               /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
-               /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
-               /(msie) ([\w.]+)/.exec( ua ) ||
-               ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
-               [];
-       
-       var matched = {
-               browser: match[ 1 ] || "",
-               version: match[ 2 ] || "0"
-       };
-       browser = {};
-       
-       if ( matched.browser ) {
-               browser[ matched.browser ] = true;
-               browser.version = matched.version;
-       }
-       
-       // Chrome is Webkit, but Webkit is also Safari.
-       if ( browser.chrome ) {
-               browser.webkit = true;
-       } else if ( browser.webkit ) {
-               browser.safari = true;
-       }
-       
-       jQuery.browser = browser;
-       jQuery.browser.touch = (!!('ontouchstart' in window) || (!!('msMaxTouchPoints' in window.navigator) && window.navigator.msMaxTouchPoints > 0));
-       
-       // detect smartphones
-       jQuery.browser.smartphone = ($('html').css('caption-side') == 'bottom');
-       
-       // allow plugins to detect the used editor, value should be the same as the $.browser.<editorName> key
-       jQuery.browser.editor = 'redactor';
-       
-       // CKEditor support (removed in WCF 2.1), do NOT remove this variable for the sake for compatibility
-       jQuery.browser.ckeditor = false;
-       
-       // Redactor support
-       jQuery.browser.redactor = true;
-       
-       // properly detect IE11
-       if (jQuery.browser.mozilla && ua.match(/trident/)) {
-               jQuery.browser.mozilla = false;
-               jQuery.browser.msie = true;
-       }
-})();
-
-/**
- * jQuery.browser.mobile (http://detectmobilebrowser.com/)
- *
- * jQuery.browser.mobile will be true if the browser is a mobile device
- *
- **/
-(function(a){(jQuery.browser=jQuery.browser||{}).mobile=/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od|ad)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))})(navigator.userAgent||navigator.vendor||window.opera);
-
-/**
- * Initialize WCF namespace
- */
-var WCF = {};
-
-/**
- * Extends jQuery with additional methods.
- */
-$.extend(true, {
-       /**
-        * Removes the given value from the given array and returns the array.
-        * 
-        * @param       array           array
-        * @param       mixed           element
-        * @return      array
-        */
-       removeArrayValue: function(array, value) {
-               return $.grep(array, function(element, index) {
-                       return value !== element;
-               });
-       },
-       
-       /**
-        * Escapes an ID to work with jQuery selectors.
-        * 
-        * @see         http://docs.jquery.com/Frequently_Asked_Questions#How_do_I_select_an_element_by_an_ID_that_has_characters_used_in_CSS_notation.3F
-        * @param       string          id
-        * @return      string
-        */
-       wcfEscapeID: function(id) {
-               return id.replace(/(:|\.)/g, '\\$1');
-       },
-       
-       /**
-        * Returns true if given ID exists within DOM.
-        * 
-        * @param       string          id
-        * @return      boolean
-        */
-       wcfIsset: function(id) {
-               return !!$('#' + $.wcfEscapeID(id)).length;
-       },
-       
-       /**
-        * Returns the length of an object.
-        * 
-        * @param       object          targetObject
-        * @return      integer
-        */
-       getLength: function(targetObject) {
-               var $length = 0;
-               
-               for (var $key in targetObject) {
-                       if (targetObject.hasOwnProperty($key)) {
-                               $length++;
-                       }
-               }
-               
-               return $length;
-       }
-});
-
-/**
- * Extends jQuery's chainable methods.
- */
-$.fn.extend({
-       /**
-        * Returns tag name of first jQuery element.
-        * 
-        * @returns     string
-        */
-       getTagName: function() {
-               return (this.length) ? this.get(0).tagName.toLowerCase() : '';
-       },
-       
-       /**
-        * Returns the dimensions for current element.
-        * 
-        * @see         http://api.jquery.com/hidden-selector/
-        * @param       string          type
-        * @return      object
-        */
-       getDimensions: function(type) {
-               var dimensions = css = {};
-               var wasHidden = false;
-               
-               // show element to retrieve dimensions and restore them later
-               if (this.is(':hidden')) {
-                       css = WCF.getInlineCSS(this);
-                       
-                       wasHidden = true;
-                       
-                       this.css({
-                               display: 'block',
-                               visibility: 'hidden'
-                       });
-               }
-               
-               switch (type) {
-                       case 'inner':
-                               dimensions = {
-                                       height: this.innerHeight(),
-                                       width: this.innerWidth()
-                               };
-                       break;
-                       
-                       case 'outer':
-                               dimensions = {
-                                       height: this.outerHeight(),
-                                       width: this.outerWidth()
-                               };
-                       break;
-                       
-                       default:
-                               dimensions = {
-                                       height: this.height(),
-                                       width: this.width()
-                               };
-                       break;
-               }
-               
-               // restore previous settings
-               if (wasHidden) {
-                       WCF.revertInlineCSS(this, css, [ 'display', 'visibility' ]);
-               }
-               
-               return dimensions;
-       },
-       
-       /**
-        * Returns the offsets for current element, defaults to position
-        * relative to document.
-        * 
-        * @see         http://api.jquery.com/hidden-selector/
-        * @param       string          type
-        * @return      object
-        */
-       getOffsets: function(type) {
-               var offsets = css = {};
-               var wasHidden = false;
-               
-               // show element to retrieve dimensions and restore them later
-               if (this.is(':hidden')) {
-                       css = WCF.getInlineCSS(this);
-                       wasHidden = true;
-                       
-                       this.css({
-                               display: 'block',
-                               visibility: 'hidden'
-                       });
-               }
-               
-               switch (type) {
-                       case 'offset':
-                               offsets = this.offset();
-                       break;
-                       
-                       case 'position':
-                       default:
-                               offsets = this.position();
-                       break;
-               }
-               
-               // restore previous settings
-               if (wasHidden) {
-                       WCF.revertInlineCSS(this, css, [ 'display', 'visibility' ]);
-               }
-               
-               return offsets;
-       },
-       
-       /**
-        * Changes element's position to 'absolute' or 'fixed' while maintaining it's
-        * current position relative to viewport. Optionally removes element from
-        * current DOM-node and moving it into body-element (useful for drag & drop)
-        * 
-        * @param       boolean         rebase
-        * @return      object
-        */
-       makePositioned: function(position, rebase) {
-               if (position != 'absolute' && position != 'fixed') {
-                       position = 'absolute';
-               }
-               
-               var $currentPosition = this.getOffsets('position');
-               this.css({
-                       position: position,
-                       left: $currentPosition.left,
-                       margin: 0,
-                       top: $currentPosition.top
-               });
-               
-               if (rebase) {
-                       this.remove().appentTo('body');
-               }
-               
-               return this;
-       },
-       
-       /**
-        * Disables a form element.
-        * 
-        * @return      jQuery
-        */
-       disable: function() {
-               return this.attr('disabled', 'disabled');
-       },
-       
-       /**
-        * Enables a form element.
-        * 
-        * @return      jQuery
-        */
-       enable: function() {
-               return this.removeAttr('disabled');
-       },
-       
-       /**
-        * Returns the element's id. If none is set, a random unique
-        * ID will be assigned.
-        * 
-        * @return      string
-        */
-       wcfIdentify: function() {
-               if (!this.attr('id')) {
-                       this.attr('id', WCF.getRandomID());
-               }
-               
-               return this.attr('id');
-       },
-       
-       /**
-        * Returns the caret position of current element. If the element
-        * does not equal input[type=text], input[type=password] or
-        * textarea, -1 is returned.
-        * 
-        * @return      integer
-        */
-       getCaret: function() {
-               if (this.is('input')) {
-                       if (this.attr('type') != 'text' && this.attr('type') != 'password') {
-                               return -1;
-                       }
-               }
-               else if (!this.is('textarea')) {
-                       return -1;
-               }
-               
-               var $position = 0;
-               var $element = this.get(0);
-               if (document.selection) { // IE 8
-                       // set focus to enable caret on this element
-                       this.focus();
-                       
-                       var $selection = document.selection.createRange();
-                       $selection.moveStart('character', -this.val().length);
-                       $position = $selection.text.length;
-               }
-               else if ($element.selectionStart || $element.selectionStart == '0') { // Opera, Chrome, Firefox, Safari, IE 9+
-                       $position = parseInt($element.selectionStart);
-               }
-               
-               return $position;
-       },
-       
-       /**
-        * Sets the caret position of current element. If the element
-        * does not equal input[type=text], input[type=password] or
-        * textarea, false is returned.
-        * 
-        * @param       integer         position
-        * @return      boolean
-        */
-       setCaret: function (position) {
-               if (this.is('input')) {
-                       if (this.attr('type') != 'text' && this.attr('type') != 'password') {
-                               return false;
-                       }
-               }
-               else if (!this.is('textarea')) {
-                       return false;
-               }
-               
-               var $element = this.get(0);
-               
-               // set focus to enable caret on this element
-               this.focus();
-               if (document.selection) { // IE 8
-                       var $selection = document.selection.createRange();
-                       $selection.moveStart('character', position);
-                       $selection.moveEnd('character', 0);
-                       $selection.select();
-               }
-               else if ($element.selectionStart || $element.selectionStart == '0') { // Opera, Chrome, Firefox, Safari, IE 9+
-                       $element.selectionStart = position;
-                       $element.selectionEnd = position;
-               }
-               
-               return true;
-       },
-       
-       /**
-        * Shows an element by sliding and fading it into viewport.
-        * 
-        * @param       string          direction
-        * @param       object          callback
-        * @param       integer         duration
-        * @returns     jQuery
-        */
-       wcfDropIn: function(direction, callback, duration) {
-               if (!direction) direction = 'up';
-               if (!duration || !parseInt(duration)) duration = 200;
-               
-               return this.show(WCF.getEffect(this, 'drop'), { direction: direction }, duration, callback);
-       },
-       
-       /**
-        * Hides an element by sliding and fading it out the viewport.
-        * 
-        * @param       string          direction
-        * @param       object          callback
-        * @param       integer         duration
-        * @returns     jQuery
-        */
-       wcfDropOut: function(direction, callback, duration) {
-               if (!direction) direction = 'down';
-               if (!duration || !parseInt(duration)) duration = 200;
-               
-               return this.hide(WCF.getEffect(this, 'drop'), { direction: direction }, duration, callback);
-       },
-       
-       /**
-        * Shows an element by blinding it up.
-        * 
-        * @param       string          direction
-        * @param       object          callback
-        * @param       integer         duration
-        * @returns     jQuery
-        */
-       wcfBlindIn: function(direction, callback, duration) {
-               if (!direction) direction = 'vertical';
-               if (!duration || !parseInt(duration)) duration = 200;
-               
-               return this.show(WCF.getEffect(this, 'blind'), { direction: direction }, duration, callback);
-       },
-       
-       /**
-        * Hides an element by blinding it down.
-        * 
-        * @param       string          direction
-        * @param       object          callback
-        * @param       integer         duration
-        * @returns     jQuery
-        */
-       wcfBlindOut: function(direction, callback, duration) {
-               if (!direction) direction = 'vertical';
-               if (!duration || !parseInt(duration)) duration = 200;
-               
-               return this.hide(WCF.getEffect(this, 'blind'), { direction: direction }, duration, callback);
-       },
-       
-       /**
-        * Highlights an element.
-        * 
-        * @param       object          options
-        * @param       object          callback
-        * @returns     jQuery
-        */
-       wcfHighlight: function(options, callback) {
-               return this.effect('highlight', options, 600, callback);
-       },
-       
-       /**
-        * Shows an element by fading it in.
-        * 
-        * @param       object          callback
-        * @param       integer         duration
-        * @returns     jQuery
-        */
-       wcfFadeIn: function(callback, duration) {
-               if (!duration || !parseInt(duration)) duration = 200;
-               
-               return this.show(WCF.getEffect(this, 'fade'), { }, duration, callback);
-       },
-       
-       /**
-        * Hides an element by fading it out.
-        * 
-        * @param       object          callback
-        * @param       integer         duration
-        * @returns     jQuery
-        */
-       wcfFadeOut: function(callback, duration) {
-               if (!duration || !parseInt(duration)) duration = 200;
-               
-               return this.hide(WCF.getEffect(this, 'fade'), { }, duration, callback);
-       }
-});
-
-/**
- * WoltLab Community Framework core methods
- */
-$.extend(WCF, {
-       /**
-        * count of active dialogs
-        * @var integer
-        */
-       activeDialogs: 0,
-       
-       /**
-        * Counter for dynamic element ids
-        * 
-        * @var integer
-        */
-       _idCounter: 0,
-       
-       /**
-        * Returns a dynamically created id.
-        * 
-        * @see         https://github.com/sstephenson/prototype/blob/5e5cfff7c2c253eaf415c279f9083b4650cd4506/src/prototype/dom/dom.js#L1789
-        * @return      string
-        */
-       getRandomID: function() {
-               var $elementID = '';
-               
-               do {
-                       $elementID = 'wcf' + this._idCounter++;
-               }
-               while ($.wcfIsset($elementID));
-               
-               return $elementID;
-       },
-       
-       /**
-        * Wrapper for $.inArray which returns boolean value instead of
-        * index value, similar to PHP's in_array().
-        * 
-        * @param       mixed           needle
-        * @param       array           haystack
-        * @return      boolean
-        */
-       inArray: function(needle, haystack) {
-               return ($.inArray(needle, haystack) != -1);
-       },
-       
-       /**
-        * Adjusts effect for partially supported elements.
-        * 
-        * @param       jQuery          object
-        * @param       string          effect
-        * @return      string
-        */
-       getEffect: function(object, effect) {
-               // most effects are not properly supported on table rows, use highlight instead
-               if (object.is('tr')) {
-                       return 'highlight';
-               }
-               
-               return effect;
-       },
-       
-       /**
-        * Returns inline CSS for given element.
-        * 
-        * @param       jQuery          element
-        * @return      object
-        */
-       getInlineCSS: function(element) {
-               var $inlineStyles = { };
-               var $style = element.attr('style');
-               
-               // no style tag given or empty
-               if (!$style) {
-                       return { };
-               }
-               
-               $style = $style.split(';');
-               for (var $i = 0, $length = $style.length; $i < $length; $i++) {
-                       var $fragment = $.trim($style[$i]);
-                       if ($fragment == '') {
-                               continue;
-                       }
-                       
-                       $fragment = $fragment.split(':');
-                       $inlineStyles[$.trim($fragment[0])] = $.trim($fragment[1]);
-               }
-               
-               return $inlineStyles;
-       },
-       
-       /**
-        * Reverts inline CSS or negates a previously set property.
-        * 
-        * @param       jQuery          element
-        * @param       object          inlineCSS
-        * @param       array<string>   targetProperties
-        */
-       revertInlineCSS: function(element, inlineCSS, targetProperties) {
-               for (var $i = 0, $length = targetProperties.length; $i < $length; $i++) {
-                       var $property = targetProperties[$i];
-                       
-                       // revert inline CSS
-                       if (inlineCSS[$property]) {
-                               element.css($property, inlineCSS[$property]);
-                       }
-                       else {
-                               // negate inline CSS
-                               element.css($property, '');
-                       }
-               }
-       }
-});
-
-/**
- * Browser related functions.
- */
-WCF.Browser = {
-       /**
-        * determines if browser is chrome
-        * @var boolean
-        */
-       _isChrome: null,
-       
-       /**
-        * Returns true, if browser is Chrome, Chromium or using GoogleFrame for Internet Explorer.
-        * 
-        * @return      boolean
-        */
-       isChrome: function() {
-               if (this._isChrome === null) {
-                       this._isChrome = false;
-                       if (/chrom(e|ium)/.test(navigator.userAgent.toLowerCase())) {
-                               this._isChrome = true;
-                       }
-               }
-               
-               return this._isChrome;
-       }
-};
-
-/**
- * Dropdown API
- */
-WCF.Dropdown = {
-       /**
-        * list of callbacks
-        * @var object
-        */
-       _callbacks: { },
-       
-       /**
-        * initialization state
-        * @var boolean
-        */
-       _didInit: false,
-       
-       /**
-        * list of registered dropdowns
-        * @var object
-        */
-       _dropdowns: { },
-       
-       /**
-        * container for dropdown menus
-        * @var object
-        */
-       _menuContainer: null,
-       
-       /**
-        * list of registered dropdown menus
-        * @var object
-        */
-       _menus: { },
-       
-       /**
-        * Initializes dropdowns.
-        */
-       init: function() {
-               if (this._menuContainer === null) {
-                       this._menuContainer = $('<div id="dropdownMenuContainer" />').appendTo(document.body);
-               }
-               
-               var self = this;
-               $('.dropdownToggle:not(.jsDropdownEnabled)').each(function(index, button) {
-                       self.initDropdown($(button), false);
-               });
-               
-               if (!this._didInit) {
-                       this._didInit = true;
-                       
-                       WCF.CloseOverlayHandler.addCallback('WCF.Dropdown', $.proxy(this._closeAll, this));
-                       WCF.DOMNodeInsertedHandler.addCallback('WCF.Dropdown', $.proxy(this.init, this));
-                       $(document).on('scroll', $.proxy(this._scroll, this));
-               }
-       },
-       
-       /**
-        * Handles dropdown positions in overlays when scrolling in the overlay.
-        * 
-        * @param       object          event
-        */
-       _dialogScroll: function(event) {
-               var $dialogContent = $(event.currentTarget);
-               $dialogContent.find('.dropdown.dropdownOpen').each(function(index, element) {
-                       var $dropdown = $(element);
-                       var $dropdownID = $dropdown.wcfIdentify();
-                       var $dropdownOffset = $dropdown.offset();
-                       var $dialogContentOffset = $dialogContent.offset();
-                       
-                       var $verticalScrollTolerance = $(element).height() / 2;
-                       
-                       // check if dropdown toggle is still (partially) visible
-                       if ($dropdownOffset.top + $verticalScrollTolerance <= $dialogContentOffset.top) {
-                               // top check
-                               WCF.Dropdown.toggleDropdown($dropdownID);
-                       }
-                       else if ($dropdownOffset.top >= $dialogContentOffset.top + $dialogContent.height()) {
-                               // bottom check
-                               WCF.Dropdown.toggleDropdown($dropdownID);
-                       }
-                       else if ($dropdownOffset.left <= $dialogContentOffset.left) {
-                               // left check
-                               WCF.Dropdown.toggleDropdown($dropdownID);
-                       }
-                       else if ($dropdownOffset.left >= $dialogContentOffset.left + $dialogContent.width()) {
-                               // right check
-                               WCF.Dropdown.toggleDropdown($dropdownID);
-                       }
-                       else {
-                               WCF.Dropdown.setAlignmentByID($dropdown.wcfIdentify());
-                       }
-               });
-       },
-       
-       /**
-        * Handles dropdown positions in overlays when scrolling in the document.
-        * 
-        * @param       object          event
-        */
-       _scroll: function(event) {
-               for (var $containerID in this._dropdowns) {
-                       var $dropdown = this._dropdowns[$containerID];
-                       if ($dropdown.data('isOverlayDropdownButton') && $dropdown.hasClass('dropdownOpen')) {
-                               this.setAlignmentByID($containerID);
-                       }
-               }
-       },
-       
-       /**
-        * Initializes a dropdown.
-        * 
-        * @param       jQuery          button
-        * @param       boolean         isLazyInitialization
-        */
-       initDropdown: function(button, isLazyInitialization) {
-               if (button.hasClass('jsDropdownEnabled') || button.data('target')) {
-                       return;
-               }
-               
-               var $dropdown = button.parents('.dropdown');
-               if (!$dropdown.length) {
-                       // broken dropdown, ignore
-                       console.debug("[WCF.Dropdown] Invalid dropdown passed, button '" + button.wcfIdentify() + "' does not have a parent with .dropdown, aborting.");
-                       return;
-               }
-               
-               var $dropdownMenu = button.next('.dropdownMenu');
-               if (!$dropdownMenu.length) {
-                       // broken dropdown, ignore
-                       console.debug("[WCF.Dropdown] Invalid dropdown passed, dropdown '" + $dropdown.wcfIdentify() + "' does not have a dropdown menu, aborting.");
-                       return;
-               }
-               
-               $dropdownMenu.detach().appendTo(this._menuContainer);
-               var $containerID = $dropdown.wcfIdentify();
-               if (!this._dropdowns[$containerID]) {
-                       button.addClass('jsDropdownEnabled').click($.proxy(this._toggle, this));
-                       
-                       this._dropdowns[$containerID] = $dropdown;
-                       this._menus[$containerID] = $dropdownMenu;
-               }
-               
-               button.data('target', $containerID);
-               
-               if (isLazyInitialization) {
-                       button.trigger('click');
-               }
-       },
-       
-       /**
-        * Removes the dropdown with the given container id.
-        * 
-        * @param       string          containerID
-        */
-       removeDropdown: function(containerID) {
-               if (this._menus[containerID]) {
-                       $(this._menus[containerID]).remove();
-                       delete this._menus[containerID];
-                       delete this._dropdowns[containerID];
-               }
-       },
-       
-       /**
-        * Initializes a dropdown fragment which behaves like a usual dropdown
-        * but is not controlled by a trigger element.
-        * 
-        * @param       jQuery          dropdown
-        * @param       jQuery          dropdownMenu
-        */
-       initDropdownFragment: function(dropdown, dropdownMenu) {
-               var $containerID = dropdown.wcfIdentify();
-               if (this._dropdowns[$containerID]) {
-                       console.debug("[WCF.Dropdown] Cannot register dropdown identified by '" + $containerID + "' as a fragement.");
-                       return;
-               }
-               
-               this._dropdowns[$containerID] = dropdown;
-               this._menus[$containerID] = dropdownMenu.detach().appendTo(this._menuContainer);
-       },
-       
-       /**
-        * Registers a callback notified upon dropdown state change.
-        * 
-        * @param       string          identifier
-        * @var         object          callback
-        */
-       registerCallback: function(identifier, callback) {
-               if (!$.isFunction(callback)) {
-                       console.debug("[WCF.Dropdown] Callback for '" + identifier + "' is invalid");
-                       return false;
-               }
-               
-               if (!this._callbacks[identifier]) {
-                       this._callbacks[identifier] = [ ];
-               }
-               
-               this._callbacks[identifier].push(callback);
-       },
-       
-       /**
-        * Toggles a dropdown.
-        * 
-        * @param       object          event
-        * @param       string          targetID
-        */
-       _toggle: function(event, targetID) {
-               var $targetID = (event === null) ? targetID : $(event.currentTarget).data('target');
-               
-               // check if 'isOverlayDropdownButton' is set which indicates if
-               // the dropdown toggle is in an overlay
-               var $target = this._dropdowns[$targetID];
-               if ($target && $target.data('isOverlayDropdownButton') === undefined) {
-                       var $dialogContent = $target.parents('.dialogContent');
-                       $target.data('isOverlayDropdownButton', $dialogContent.length > 0);
-                       
-                       if ($dialogContent.length) {
-                               $dialogContent.on('scroll', this._dialogScroll);
-                       }
-               }
-               
-               // close all dropdowns
-               for (var $containerID in this._dropdowns) {
-                       var $dropdown = this._dropdowns[$containerID];
-                       var $dropdownMenu = this._menus[$containerID];
-                       
-                       if ($dropdown.hasClass('dropdownOpen')) {
-                               $dropdown.removeClass('dropdownOpen');
-                               $dropdownMenu.removeClass('dropdownOpen');
-                               
-                               this._notifyCallbacks($containerID, 'close');
-                       }
-                       else if ($containerID === $targetID) {
-                               $dropdown.addClass('dropdownOpen');
-                               $dropdownMenu.addClass('dropdownOpen');
-                               
-                               this._notifyCallbacks($containerID, 'open');
-                               
-                               this.setAlignment($dropdown, $dropdownMenu);
-                       }
-               }
-               
-               if (event !== null) {
-                       event.stopPropagation();
-                       return false;
-               }
-       },
-       
-       /**
-        * Toggles a dropdown.
-        * 
-        * @param       string          containerID
-        */
-       toggleDropdown: function(containerID) {
-               this._toggle(null, containerID);
-       },
-       
-       /**
-        * Returns dropdown by container id.
-        * 
-        * @param       string          containerID
-        * @return      jQuery
-        */
-       getDropdown: function(containerID) {
-               if (this._dropdowns[containerID]) {
-                       return this._dropdowns[containerID];
-               }
-               
-               return null;
-       },
-       
-       /**
-        * Returns dropdown menu by container id.
-        * 
-        * @param       string          containerID
-        * @return      jQuery
-        */
-       getDropdownMenu: function(containerID) {
-               if (this._menus[containerID]) {
-                       return this._menus[containerID];
-               }
-               
-               return null;
-       },
-       
-       /**
-        * Sets alignment for given container id.
-        * 
-        * @param       string          containerID
-        */
-       setAlignmentByID: function(containerID) {
-               var $dropdown = this.getDropdown(containerID);
-               if ($dropdown === null) {
-                       console.debug("[WCF.Dropdown] Unable to find dropdown identified by '" + containerID + "'");
-               }
-               
-               var $dropdownMenu = this.getDropdownMenu(containerID);
-               if ($dropdownMenu === null) {
-                       console.debug("[WCF.Dropdown] Unable to find dropdown menu identified by '" + containerID + "'");
-               }
-               
-               this.setAlignment($dropdown, $dropdownMenu);
-       },
-       
-       /**
-        * Sets alignment for dropdown.
-        * 
-        * @param       jQuery          dropdown
-        * @param       jQuery          dropdownMenu
-        */
-       setAlignment: function(dropdown, dropdownMenu) {
-               // force dropdown menu to be placed in the upper left corner, otherwise
-               // it might cause the calculations to be a bit off if the page exceeds
-               // the window boundaries during getDimensions() making it visible
-               if (!dropdownMenu.data('isInitialized')) {
-                       dropdownMenu.data('isInitialized', true).css({ left: 0, top: 0 });
-               }
-               
-               // get dropdown position
-               var $dropdownDimensions = dropdown.getDimensions('outer');
-               var $dropdownOffsets = dropdown.getOffsets('offset');
-               var $menuDimensions = dropdownMenu.getDimensions('outer');
-               var $windowWidth = $(window).width();
-               
-               // check if button belongs to an i18n textarea
-               var $button = dropdown.find('.dropdownToggle');
-               if ($button.hasClass('dropdownCaptionTextarea')) {
-                       // use button dimensions instead
-                       $dropdownDimensions = $button.getDimensions('outer');
-               }
-               
-               // get alignment
-               var $align = 'left';
-               if (($dropdownOffsets.left + $menuDimensions.width) > $windowWidth) {
-                       $align = 'right';
-               }
-               
-               // calculate offsets
-               var $left = 'auto';
-               var $right = 'auto';
-               if ($align === 'left') {
-                       dropdownMenu.removeClass('dropdownArrowRight');
-                       
-                       $left = $dropdownOffsets.left;
-               }
-               else {
-                       dropdownMenu.addClass('dropdownArrowRight');
-                       
-                       $right = ($windowWidth - ($dropdownOffsets.left + $dropdownDimensions.width));
-               }
-               
-               // rtl works the same with the exception that we need to offset it with the right boundary
-               if (WCF.Language.get('wcf.global.pageDirection') == 'rtl') {
-                       var $oldLeft = $left;
-                       var $oldRight = $right;
-                       
-                       // use reverse positioning
-                       if ($left == 'auto') {
-                               dropdownMenu.removeClass('dropdownArrowRight');
-                       }
-                       else {
-                               $right = $windowWidth - ($dropdownOffsets.left + $dropdownDimensions.width);
-                               $left = 'auto';
-                               
-                               if ($right + $menuDimensions.width > $windowWidth) {
-                                       // exceeded window width, restore ltr values
-                                       $left = $oldLeft;
-                                       $right = $oldRight;
-                                       
-                                       dropdownMenu.addClass('dropdownArrowRight');
-                               }
-                       }
-               }
-               
-               if ($left == 'auto') $right += 'px';
-               else $left += 'px';
-               
-               // calculate vertical offset
-               var $wasHidden = true;
-               if (dropdownMenu.hasClass('dropdownOpen')) {
-                       $wasHidden = false;
-                       dropdownMenu.removeClass('dropdownOpen');
-               }
-               
-               var $bottom = 'auto';
-               var $top = $dropdownOffsets.top + $dropdownDimensions.height + 7;
-               if ($top + $menuDimensions.height > $(window).height() + $(document).scrollTop()) {
-                       $bottom = $(window).height() - $dropdownOffsets.top + 10;
-                       $top = 'auto';
-                       
-                       dropdownMenu.addClass('dropdownArrowBottom');
-               }
-               else {
-                       dropdownMenu.removeClass('dropdownArrowBottom');
-               }
-               
-               if (!$wasHidden) {
-                       dropdownMenu.addClass('dropdownOpen');
-               }
-               
-               dropdownMenu.css({
-                       bottom: $bottom,
-                       left: $left,
-                       right: $right,
-                       top: $top
-               });
-       },
-       
-       /**
-        * Closes all dropdowns.
-        */
-       _closeAll: function() {
-               for (var $containerID in this._dropdowns) {
-                       var $dropdown = this._dropdowns[$containerID];
-                       if ($dropdown.hasClass('dropdownOpen')) {
-                               $dropdown.removeClass('dropdownOpen');
-                               this._menus[$containerID].removeClass('dropdownOpen');
-                               
-                               this._notifyCallbacks($containerID, 'close');
-                       }
-               }
-       },
-       
-       /**
-        * Closes a dropdown without notifying callbacks.
-        * 
-        * @param       string          containerID
-        */
-       close: function(containerID) {
-               if (!this._dropdowns[containerID]) {
-                       return;
-               }
-               
-               this._dropdowns[containerID].removeClass('dropdownMenu');
-               this._menus[containerID].removeClass('dropdownMenu');
-       },
-       
-       /**
-        * Notifies callbacks.
-        * 
-        * @param       string          containerID
-        * @param       string          action
-        */
-       _notifyCallbacks: function(containerID, action) {
-               if (!this._callbacks[containerID]) {
-                       return;
-               }
-               
-               for (var $i = 0, $length = this._callbacks[containerID].length; $i < $length; $i++) {
-                       this._callbacks[containerID][$i](containerID, action);
-               }
-       }
-};
-
-/**
- * Clipboard API
- */
-WCF.Clipboard = {
-       /**
-        * action proxy object
-        * @var WCF.Action.Proxy
-        */
-       _actionProxy: null,
-       
-       /**
-        * action objects
-        * @var object
-        */
-       _actionObjects: {},
-       
-       /**
-        * list of clipboard containers
-        * @var jQuery
-        */
-       _containers: null,
-       
-       /**
-        * container meta data
-        * @var object
-        */
-       _containerData: { },
-       
-       /**
-        * user has marked items
-        * @var boolean
-        */
-       _hasMarkedItems: false,
-       
-       /**
-        * list of ids of marked objects grouped by object type
-        * @var object
-        */
-       _markedObjectIDs: { },
-       
-       /**
-        * current page
-        * @var string
-        */
-       _page: '',
-       
-       /**
-        * current page's object id
-        * @var integer
-        */
-       _pageObjectID: 0,
-       
-       /**
-        * proxy object
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * list of elements already tracked for clipboard actions
-        * @var object
-        */
-       _trackedElements: { },
-       
-       /**
-        * Initializes the clipboard API.
-        * 
-        * @param       string          page
-        * @param       integer         hasMarkedItems
-        * @param       object          actionObjects
-        * @param       integer         pageObjectID
-        */
-       init: function(page, hasMarkedItems, actionObjects, pageObjectID) {
-               this._page = page;
-               this._actionObjects = actionObjects || { };
-               this._hasMarkedItems = (hasMarkedItems > 0);
-               this._pageObjectID = parseInt(pageObjectID) || 0;
-               
-               this._actionProxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._actionSuccess, this),
-                       url: 'index.php/ClipboardProxy/?t=' + SECURITY_TOKEN + SID_ARG_2ND
-               });
-               
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this),
-                       url: 'index.php/Clipboard/?t=' + SECURITY_TOKEN + SID_ARG_2ND
-               });
-               
-               // init containers first
-               this._containers = $('.jsClipboardContainer').each($.proxy(function(index, container) {
-                       this._initContainer(container);
-               }, this));
-               
-               // loads marked items
-               if (this._hasMarkedItems && this._containers.length) {
-                       this._loadMarkedItems();
-               }
-               
-               var self = this;
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.Clipboard', function() {
-                       self._containers = $('.jsClipboardContainer').each($.proxy(function(index, container) {
-                               self._initContainer(container);
-                       }, self));
-               });
-       },
-       
-       /**
-        * Loads marked items on init.
-        */
-       _loadMarkedItems: function() {
-               new WCF.Action.Proxy({
-                       autoSend: true,
-                       data: {
-                               containerData: this._containerData,
-                               pageClassName: this._page,
-                               pageObjectID: this._pageObjectID
-                       },
-                       success: $.proxy(this._loadMarkedItemsSuccess, this),
-                       url: 'index.php/ClipboardLoadMarkedItems/?t=' + SECURITY_TOKEN + SID_ARG_2ND
-               });
-       },
-       
-       /**
-        * Reloads the list of marked items.
-        */
-       reload: function() {
-               if (this._containers === null) {
-                       return;
-               }
-               
-               this._loadMarkedItems();
-       },
-       
-       /**
-        * Marks all returned items as marked
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _loadMarkedItemsSuccess: function(data, textStatus, jqXHR) {
-               this._resetMarkings();
-               
-               for (var $typeName in data.markedItems) {
-                       if (!this._markedObjectIDs[$typeName]) {
-                               this._markedObjectIDs[$typeName] = [ ];
-                       }
-                       
-                       var $objectData = data.markedItems[$typeName];
-                       for (var $i in $objectData) {
-                               this._markedObjectIDs[$typeName].push($objectData[$i]);
-                       }
-                       
-                       // loop through all containers
-                       this._containers.each($.proxy(function(index, container) {
-                               var $container = $(container);
-                               
-                               // typeName does not match, continue
-                               if ($container.data('type') != $typeName) {
-                                       return true;
-                               }
-                               
-                               // mark items as marked
-                               $container.find('input.jsClipboardItem').each($.proxy(function(innerIndex, item) {
-                                       var $item = $(item);
-                                       if (WCF.inArray($item.data('objectID'), this._markedObjectIDs[$typeName])) {
-                                               $item.prop('checked', true);
-                                               
-                                               // add marked class for element container
-                                               $item.parents('.jsClipboardObject').addClass('jsMarked');
-                                       }
-                               }, this));
-                               
-                               // check if there is a markAll-checkbox
-                               $container.find('input.jsClipboardMarkAll').each(function(innerIndex, markAll) {
-                                       var $allItemsMarked = true;
-                                       
-                                       $container.find('input.jsClipboardItem').each(function(itemIndex, item) {
-                                               var $item = $(item);
-                                               if (!$item.prop('checked')) {
-                                                       $allItemsMarked = false;
-                                               }
-                                       });
-                                       
-                                       if ($allItemsMarked) {
-                                               $(markAll).prop('checked', true);
-                                       }
-                               });
-                       }, this));
-               }
-               
-               // call success method to build item list editors
-               this._success(data, textStatus, jqXHR);
-       },
-       
-       /**
-        * Resets all checkboxes.
-        */
-       _resetMarkings: function() {
-               this._containers.each($.proxy(function(index, container) {
-                       var $container = $(container);
-                       
-                       this._markedObjectIDs[$container.data('type')] = [ ];
-                       $container.find('input.jsClipboardItem, input.jsClipboardMarkAll').prop('checked', false);
-                       $container.find('.jsClipboardObject').removeClass('jsMarked');
-               }, this));
-       },
-       
-       /**
-        * Initializes a clipboard container.
-        * 
-        * @param       object          container
-        */
-       _initContainer: function(container) {
-               var $container = $(container);
-               var $containerID = $container.wcfIdentify();
-               
-               if (!this._trackedElements[$containerID]) {
-                       $container.find('.jsClipboardMarkAll').data('hasContainer', $containerID).click($.proxy(this._markAll, this));
-                       
-                       this._markedObjectIDs[$container.data('type')] = [ ];
-                       this._containerData[$container.data('type')] = {};
-                       $.each($container.data(), $.proxy(function(index, element) {
-                               if (index.match(/^type(.+)/)) {
-                                       this._containerData[$container.data('type')][WCF.String.lcfirst(index.replace(/^type/, ''))] = element;
-                               }
-                       }, this));
-                       
-                       this._trackedElements[$containerID] = [ ];
-               }
-               
-               // track individual checkboxes
-               $container.find('input.jsClipboardItem').each($.proxy(function(index, input) {
-                       var $input = $(input);
-                       var $inputID = $input.wcfIdentify();
-                       
-                       if (!WCF.inArray($inputID, this._trackedElements[$containerID])) {
-                               this._trackedElements[$containerID].push($inputID);
-                               
-                               $input.data('hasContainer', $containerID).click($.proxy(this._click, this));
-                       }
-               }, this));
-       },
-       
-       /**
-        * Processes change checkbox state.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               var $item = $(event.target);
-               var $objectID = $item.data('objectID');
-               var $isMarked = ($item.prop('checked')) ? true : false;
-               var $objectIDs = [ $objectID ];
-               
-               if ($item.data('hasContainer')) {
-                       var $container = $('#' + $item.data('hasContainer'));
-                       var $type = $container.data('type');
-               }
-               else {
-                       var $type = $item.data('type');
-               }
-               
-               if ($isMarked) {
-                       this._markedObjectIDs[$type].push($objectID);
-                       $item.parents('.jsClipboardObject').addClass('jsMarked');
-               }
-               else {
-                       this._markedObjectIDs[$type] = $.removeArrayValue(this._markedObjectIDs[$type], $objectID);
-                       $item.parents('.jsClipboardObject').removeClass('jsMarked');
-               }
-               
-               // item is part of a container
-               if ($item.data('hasContainer')) {
-                       // check if all items are marked
-                       var $markedAll = true;
-                       $container.find('input.jsClipboardItem').each(function(index, containerItem) {
-                               var $containerItem = $(containerItem);
-                               if (!$containerItem.prop('checked')) {
-                                       $markedAll = false;
-                               }
-                       });
-                       
-                       // simulate a ticked 'markAll' checkbox
-                       $container.find('.jsClipboardMarkAll').each(function(index, markAll) {
-                               if ($markedAll) {
-                                       $(markAll).prop('checked', true);
-                               }
-                               else {
-                                       $(markAll).prop('checked', false);
-                               }
-                       });
-               }
-               
-               this._saveState($type, $objectIDs, $isMarked);
-       },
-       
-       /**
-        * Marks all associated clipboard items as checked.
-        * 
-        * @param       object          event
-        */
-       _markAll: function(event) {
-               var $item = $(event.target);
-               var $objectIDs = [ ];
-               var $isMarked = true;
-               
-               // if markAll object is a checkbox, allow toggling
-               if ($item.is('input')) {
-                       $isMarked = $item.prop('checked');
-               }
-               
-               if ($item.data('hasContainer')) {
-                       var $container = $('#' + $item.data('hasContainer'));
-                       var $type = $container.data('type');
-               }
-               else {
-                       var $type = $item.data('type');
-               }
-               
-               // handle item containers
-               if ($item.data('hasContainer')) {
-                       // toggle state for all associated items
-                       $container.find('input.jsClipboardItem').each($.proxy(function(index, containerItem) {
-                               var $containerItem = $(containerItem);
-                               var $objectID = $containerItem.data('objectID');
-                               if ($isMarked) {
-                                       if (!$containerItem.prop('checked')) {
-                                               $containerItem.prop('checked', true);
-                                               this._markedObjectIDs[$type].push($objectID);
-                                               $objectIDs.push($objectID);
-                                       }
-                               }
-                               else {
-                                       if ($containerItem.prop('checked')) {
-                                               $containerItem.prop('checked', false);
-                                               this._markedObjectIDs[$type] = $.removeArrayValue(this._markedObjectIDs[$type], $objectID);
-                                               $objectIDs.push($objectID);
-                                       }
-                               }
-                       }, this));
-                       
-                       if ($isMarked) {
-                               $container.find('.jsClipboardObject').addClass('jsMarked');
-                       }
-                       else {
-                               $container.find('.jsClipboardObject').removeClass('jsMarked');
-                       }
-               }
-               
-               // save new status
-               this._saveState($type, $objectIDs, $isMarked);
-       },
-       
-       /**
-        * Saves clipboard item state.
-        * 
-        * @param       string          type
-        * @param       array           objectIDs
-        * @param       boolean         isMarked
-        */
-       _saveState: function(type, objectIDs, isMarked) {
-               this._proxy.setOption('data', {
-                       action: (isMarked) ? 'mark' : 'unmark',
-                       containerData: this._containerData,
-                       objectIDs: objectIDs,
-                       pageClassName: this._page,
-                       pageObjectID: this._pageObjectID,
-                       type: type
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Updates editor options.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               // clear all editors first
-               var $containers = {};
-               $('.jsClipboardEditor').each(function(index, container) {
-                       var $container = $(container);
-                       var $types = eval($container.data('types'));
-                       for (var $i = 0, $length = $types.length; $i < $length; $i++) {
-                               var $typeName = $types[$i];
-                               $containers[$typeName] = $container;
-                       }
-                       
-                       var $containerID = $container.wcfIdentify();
-                       WCF.CloseOverlayHandler.removeCallback($containerID);
-                       
-                       $container.empty();
-               });
-               
-               // do not build new editors
-               if (!data.items) return;
-               
-               // rebuild editors
-               for (var $typeName in data.items) {
-                       if (!$containers[$typeName]) {
-                               continue;
-                       }
-                       
-                       // create container
-                       var $container = $containers[$typeName];
-                       var $list = $container.children('ul');
-                       if ($list.length == 0) {
-                               $list = $('<ul />').appendTo($container);
-                       }
-                       
-                       var $editor = data.items[$typeName];
-                       var $label = $('<li class="dropdown"><span class="dropdownToggle button">' + $editor.label + '</span></li>').appendTo($list);
-                       var $itemList = $('<ol class="dropdownMenu"></ol>').appendTo($label);
-                       
-                       // create editor items
-                       for (var $itemIndex in $editor.items) {
-                               var $item = $editor.items[$itemIndex];
-                               
-                               var $listItem = $('<li><span>' + $item.label + '</span></li>').appendTo($itemList);
-                               $listItem.data('container', $container);
-                               $listItem.data('objectType', $typeName);
-                               $listItem.data('actionName', $item.actionName).data('parameters', $item.parameters);
-                               $listItem.data('internalData', $item.internalData).data('url', $item.url).data('type', $typeName);
-                               
-                               // bind event
-                               $listItem.click($.proxy(this._executeAction, this));
-                       }
-                       
-                       // add 'unmark all'
-                       $('<li class="dropdownDivider" />').appendTo($itemList);
-                       $('<li><span>' + WCF.Language.get('wcf.clipboard.item.unmarkAll') + '</span></li>').appendTo($itemList).click($.proxy(function() {
-                               this._proxy.setOption('data', {
-                                       action: 'unmarkAll',
-                                       type: $typeName
-                               });
-                               this._proxy.setOption('success', $.proxy(function(data, textStatus, jqXHR) {
-                                       this._containers.each($.proxy(function(index, container) {
-                                               var $container = $(container);
-                                               if ($container.data('type') == $typeName) {
-                                                       $container.find('.jsClipboardMarkAll, .jsClipboardItem').prop('checked', false);
-                                                       $container.find('.jsClipboardObject').removeClass('jsMarked');
-                                                       
-                                                       return false;
-                                               }
-                                       }, this));
-                                       
-                                       // call and restore success method
-                                       this._success(data, textStatus, jqXHR);
-                                       this._proxy.setOption('success', $.proxy(this._success, this));
-                               }, this));
-                               this._proxy.sendRequest();
-                       }, this));
-                       
-                       WCF.Dropdown.initDropdown($label.children('.dropdownToggle'), false);
-               }
-       },
-       
-       /**
-        * Closes the clipboard editor item list.
-        */
-       _closeLists: function() {
-               $('.jsClipboardEditor ul').removeClass('dropdownOpen');
-       },
-       
-       /**
-        * Executes a clipboard editor item action.
-        * 
-        * @param       object          event
-        */
-       _executeAction: function(event) {
-               var $listItem = $(event.currentTarget);
-               var $url = $listItem.data('url');
-               if ($url) {
-                       window.location.href = $url;
-               }
-               
-               if ($listItem.data('parameters').className && $listItem.data('parameters').actionName) {
-                       if ($listItem.data('parameters').actionName === 'unmarkAll' || $listItem.data('parameters').objectIDs) {
-                               var $confirmMessage = $listItem.data('internalData')['confirmMessage'];
-                               if ($confirmMessage) {
-                                       var $template = $listItem.data('internalData')['template'];
-                                       if ($template) $template = $($template);
-                                       
-                                       WCF.System.Confirmation.show($confirmMessage, $.proxy(function(action) {
-                                               if (action === 'confirm') {
-                                                       var $data = { };
-                                                       
-                                                       if ($template && $template.length) {
-                                                               $('#wcfSystemConfirmationContent').find('input, select, textarea').each(function(index, item) {
-                                                                       var $item = $(item);
-                                                                       $data[$item.prop('name')] = $item.val();
-                                                               });
-                                                       }
-                                                       
-                                                       this._executeAJAXActions($listItem, $data);
-                                               }
-                                       }, this), '', $template);
-                               }
-                               else {
-                                       this._executeAJAXActions($listItem, { });
-                               }
-                       }
-               }
-               
-               // fire event
-               $listItem.data('container').trigger('clipboardAction', [ $listItem.data('type'), $listItem.data('actionName'), $listItem.data('parameters') ]);
-       },
-       
-       /**
-        * Executes the AJAX actions for the given editor list item.
-        * 
-        * @param       jQuery          listItem
-        * @param       object          data
-        */
-       _executeAJAXActions: function(listItem, data) {
-               data = data || { };
-               var $objectIDs = [];
-               if (listItem.data('parameters').actionName !== 'unmarkAll') {
-                       $.each(listItem.data('parameters').objectIDs, function(index, objectID) {
-                               $objectIDs.push(parseInt(objectID));
-                       });
-               }
-               
-               var $parameters = {
-                       data: data,
-                       containerData: this._containerData[listItem.data('type')]
-               };
-               var $__parameters = listItem.data('internalData')['parameters'];
-               if ($__parameters !== undefined) {
-                       for (var $key in $__parameters) {
-                               $parameters[$key] = $__parameters[$key];
-                       }
-               }
-               
-               new WCF.Action.Proxy({
-                       autoSend: true,
-                       data: {
-                               actionName: listItem.data('parameters').actionName,
-                               className: listItem.data('parameters').className,
-                               objectIDs: $objectIDs,
-                               parameters: $parameters
-                       },
-                       success: $.proxy(function(data) {
-                               if (listItem.data('parameters').actionName !== 'unmarkAll') {
-                                       listItem.data('container').trigger('clipboardActionResponse', [ data, listItem.data('type'), listItem.data('actionName'), listItem.data('parameters') ]);
-                               }
-                               
-                               this._loadMarkedItems();
-                       }, this)
-               });
-               
-               if (this._actionObjects[listItem.data('objectType')] && this._actionObjects[listItem.data('objectType')][listItem.data('parameters').actionName]) {
-                       this._actionObjects[listItem.data('objectType')][listItem.data('parameters').actionName].triggerEffect($objectIDs);
-               }
-       },
-       
-       /**
-        * Sends a clipboard proxy request.
-        * 
-        * @param       object          item
-        */
-       sendRequest: function(item) {
-               var $item = $(item);
-               
-               this._actionProxy.setOption('data', {
-                       parameters: $item.data('parameters'),
-                       typeName: $item.data('type')
-               });
-               this._actionProxy.sendRequest();
-       }
-};
-
-/**
- * Provides a simple call for periodical executed functions. Based upon
- * ideas by Prototype's PeriodicalExecuter.
- * 
- * @see                https://github.com/sstephenson/prototype/blob/master/src/prototype/lang/periodical_executer.js
- * @param      function                callback
- * @param      integer                 delay
- */
-WCF.PeriodicalExecuter = Class.extend({
-       /**
-        * callback for each execution cycle
-        * @var object
-        */
-       _callback: null,
-       
-       /**
-        * interval
-        * @var integer
-        */
-       _delay: 0,
-       
-       /**
-        * interval id
-        * @var integer
-        */
-       _intervalID: null,
-       
-       /**
-        * execution state
-        * @var boolean
-        */
-       _isExecuting: false,
-       
-       /**
-        * Initializes a periodical executer.
-        * 
-        * @param       function                callback
-        * @param       integer                 delay
-        */
-       init: function(callback, delay) {
-               if (!$.isFunction(callback)) {
-                       console.debug('[WCF.PeriodicalExecuter] Given callback is invalid, aborting.');
-                       return;
-               }
-               
-               this._callback = callback;
-               this._interval = delay;
-               this.resume();
-       },
-       
-       /**
-        * Executes callback.
-        */
-       _execute: function() {
-               if (!this._isExecuting) {
-                       try {
-                               this._isExecuting = true;
-                               this._callback(this);
-                               this._isExecuting = false;
-                       }
-                       catch (e) {
-                               this._isExecuting = false;
-                               throw e;
-                       }
-               }
-       },
-       
-       /**
-        * Terminates loop.
-        */
-       stop: function() {
-               if (!this._intervalID) {
-                       return;
-               }
-               
-               clearInterval(this._intervalID);
-       },
-       
-       /**
-        * Resumes the interval-based callback execution.
-        */
-       resume: function() {
-               if (this._intervalID) {
-                       this.stop();
-               }
-               
-               this._intervalID = setInterval($.proxy(this._execute, this), this._interval);
-       }
-});
-
-/**
- * Handler for loading overlays
- */
-WCF.LoadingOverlayHandler = {
-       /**
-        * count of active loading-requests
-        * @var integer
-        */
-       _activeRequests: 0,
-       
-       /**
-        * loading overlay
-        * @var jQuery
-        */
-       _loadingOverlay: null,
-       
-       /**
-        * WCF.PeriodicalExecuter instance
-        * @var WCF.PeriodicalExecuter
-        */
-       _pending: null,
-       
-       /**
-        * Adds one loading-request and shows the loading overlay if nessercery
-        */
-       show: function() {
-               if (this._loadingOverlay === null) { // create loading overlay on first run
-                       this._loadingOverlay = $('<div class="spinner"><span class="icon icon48 icon-spinner" /> <span>' + WCF.Language.get('wcf.global.loading') + '</span></div>').appendTo($('body'));
-                       
-                       // fix position
-                       var $width = this._loadingOverlay.outerWidth();
-                       if ($width < 70) $width = 70;
-                       this._loadingOverlay.css({
-                               marginLeft: Math.ceil(-1 * $width / 2), 
-                               width: $width
-                       }).hide();
-               }
-               
-               this._activeRequests++;
-               if (this._activeRequests == 1) {
-                       if (this._pending === null) {
-                               var self = this;
-                               this._pending = new WCF.PeriodicalExecuter(function(pe) {
-                                       if (self._activeRequests) {
-                                               self._loadingOverlay.stop(true, true).fadeIn(100);
-                                       }
-                                       
-                                       pe.stop();
-                                       self._pending = null;
-                               }, 250); 
-                       }
-                       
-               }
-       },
-       
-       /**
-        * Removes one loading-request and hides loading overlay if there're no more pending requests
-        */
-       hide: function() {
-               this._activeRequests--;
-               if (this._activeRequests == 0) {
-                       if (this._pending !== null) {
-                               this._pending.stop();
-                               this._pending = null;
-                       }
-                       
-                       this._loadingOverlay.stop(true, true).fadeOut(100);
-               }
-       },
-       
-       /**
-        * Updates a icon to/from spinner
-        * 
-        * @param       jQuery  target
-        * @pram        boolean loading
-        */
-       updateIcon: function(target, loading) {
-               var $method = (loading === undefined || loading ? 'addClass' : 'removeClass');
-               
-               target.find('.icon')[$method]('icon-spinner');
-               if (target.hasClass('icon')) {
-                       target[$method]('icon-spinner');
-               }
-       }
-};
-
-/**
- * Namespace for AJAXProxies
- */
-WCF.Action = {};
-
-/**
- * Basic implementation for AJAX-based proxyies
- * 
- * @param      object          options
- */
-WCF.Action.Proxy = Class.extend({
-       /**
-        * shows loading overlay for a single request
-        * @var boolean
-        */
-       _showLoadingOverlayOnce: false,
-       
-       /**
-        * suppresses errors
-        * @var boolean
-        */
-       _suppressErrors: false,
-       
-       /**
-        * last request
-        * @var jqXHR
-        */
-       _lastRequest: null,
-       
-       /**
-        * Initializes AJAXProxy.
-        * 
-        * @param       object          options
-        */
-       init: function(options) {
-               // initialize default values
-               this.options = $.extend(true, {
-                       autoSend: false,
-                       data: { },
-                       dataType: 'json',
-                       after: null,
-                       init: null,
-                       jsonp: 'callback',
-                       async: true,
-                       failure: null,
-                       showLoadingOverlay: true,
-                       success: null,
-                       suppressErrors: false,
-                       type: 'POST',
-                       url: 'index.php/AJAXProxy/?t=' + SECURITY_TOKEN + SID_ARG_2ND,
-                       aborted: null,
-                       autoAbortPrevious: false
-               }, options);
-               
-               this.confirmationDialog = null;
-               this.loading = null;
-               this._showLoadingOverlayOnce = false;
-               this._suppressErrors = (this.options.suppressErrors === true);
-               
-               // send request immediately after initialization
-               if (this.options.autoSend) {
-                       this.sendRequest();
-               }
-               
-               var self = this;
-               $(window).on('beforeunload', function() { self._suppressErrors = true; });
-       },
-       
-       /**
-        * Sends an AJAX request.
-        * 
-        * @param       abortPrevious   boolean
-        * @return      jqXHR
-        */
-       sendRequest: function(abortPrevious) {
-               this._init();
-               
-               if (abortPrevious || this.options.autoAbortPrevious) {
-                       this.abortPrevious();
-               }
-               
-               this._lastRequest = $.ajax({
-                       data: this.options.data,
-                       dataType: this.options.dataType,
-                       jsonp: this.options.jsonp,
-                       async: this.options.async,
-                       type: this.options.type,
-                       url: this.options.url,
-                       success: $.proxy(this._success, this),
-                       error: $.proxy(this._failure, this)
-               });
-               return this._lastRequest;
-       },
-       
-       /**
-        * Aborts the previous request
-        */
-       abortPrevious: function() {
-               if (this._lastRequest !== null) {
-                       this._lastRequest.abort();
-                       this._lastRequest = null;
-               }
-       },
-       
-       /**
-        * Shows loading overlay for a single request.
-        */
-       showLoadingOverlayOnce: function() {
-               this._showLoadingOverlayOnce = true;
-       },
-       
-       /**
-        * Suppressed errors for this action proxy.
-        */
-       suppressErrors: function() {
-               this._suppressErrors = true;
-       },
-       
-       /**
-        * Fires before request is send, displays global loading status.
-        */
-       _init: function() {
-               if ($.isFunction(this.options.init)) {
-                       this.options.init(this);
-               }
-               
-               if (this.options.showLoadingOverlay || this._showLoadingOverlayOnce) {
-                       WCF.LoadingOverlayHandler.show();
-               }
-       },
-       
-       /**
-        * Handles AJAX errors.
-        * 
-        * @param       object          jqXHR
-        * @param       string          textStatus
-        * @param       string          errorThrown
-        */
-       _failure: function(jqXHR, textStatus, errorThrown) {
-               if (textStatus == 'abort') {
-                       // call child method if applicable
-                       if ($.isFunction(this.options.aborted)) {
-                               this.options.aborted(jqXHR);
-                       }
-                       
-                       return;
-               }
-               
-               try {
-                       var $data = $.parseJSON(jqXHR.responseText);
-                       
-                       // call child method if applicable
-                       var $showError = true;
-                       if ($.isFunction(this.options.failure)) {
-                               $showError = this.options.failure($data, jqXHR, textStatus, errorThrown);
-                       }
-                       
-                       if (!this._suppressErrors && $showError !== false) {
-                               var $details = '';
-                               if ($data.stacktrace) $details = '<br /><p>Stacktrace:</p><p>' + $data.stacktrace + '</p>';
-                               else if ($data.exceptionID) $details = '<br /><p>Exception ID: <code>' + $data.exceptionID + '</code></p>';
-                               
-                               $('<div class="ajaxDebugMessage"><p>' + $data.message + '</p>' + $details + '</div>').wcfDialog({ title: WCF.Language.get('wcf.global.error.title') });
-                       }
-               }
-               // failed to parse JSON
-               catch (e) {
-                       // call child method if applicable
-                       var $showError = true;
-                       if ($.isFunction(this.options.failure)) {
-                               $showError = this.options.failure(null, jqXHR, textStatus, errorThrown);
-                       }
-                       
-                       if (!this._suppressErrors && $showError !== false) {
-                               var $message = (textStatus === 'timeout') ? WCF.Language.get('wcf.global.error.timeout') : jqXHR.responseText;
-                               
-                               // validate if $message is neither empty nor 'undefined'
-                               if ($message && $message != 'undefined') {
-                                       $('<div class="ajaxDebugMessage"><p>' + $message + '</p></div>').wcfDialog({ title: WCF.Language.get('wcf.global.error.title') });
-                               }
-                       }
-               }
-               
-               this._after();
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       object          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               // call child method if applicable
-               if ($.isFunction(this.options.success)) {
-                       // trim HTML before processing, see http://jquery.com/upgrade-guide/1.9/#jquery-htmlstring-versus-jquery-selectorstring
-                       if (data && data.returnValues && data.returnValues.template !== undefined) {
-                               data.returnValues.template = $.trim(data.returnValues.template);
-                       }
-                       
-                       this.options.success(data, textStatus, jqXHR);
-               }
-               
-               this._after();
-       },
-       
-       /**
-        * Fires after an AJAX request, hides global loading status.
-        */
-       _after: function() {
-               this._lastRequest = null;
-               if ($.isFunction(this.options.after)) {
-                       this.options.after();
-               }
-               
-               if (this.options.showLoadingOverlay || this._showLoadingOverlayOnce) {
-                       WCF.LoadingOverlayHandler.hide();
-                       
-                       if (this._showLoadingOverlayOnce) {
-                               this._showLoadingOverlayOnce = false;
-                       }
-               }
-               
-               WCF.DOMNodeInsertedHandler.execute();
-               
-               // fix anchor tags generated through WCF::getAnchor()
-               $('a[href*=#]').each(function(index, link) {
-                       var $link = $(link);
-                       if ($link.prop('href').indexOf('AJAXProxy') != -1) {
-                               var $anchor = $link.prop('href').substr($link.prop('href').indexOf('#'));
-                               var $pageLink = document.location.toString().replace(/#.*/, '');
-                               $link.prop('href', $pageLink + $anchor);
-                       }
-               });
-       },
-       
-       /**
-        * Sets options, MUST be used to set parameters before sending request
-        * if calling from child classes.
-        * 
-        * @param       string          optionName
-        * @param       mixed           optionData
-        */
-       setOption: function(optionName, optionData) {
-               this.options[optionName] = optionData;
-       }
-});
-
-/**
- * Basic implementation for simple proxy access using bound elements.
- * 
- * @param      object          options
- * @param      object          callbacks
- */
-WCF.Action.SimpleProxy = Class.extend({
-       /**
-        * Initializes SimpleProxy.
-        * 
-        * @param       object          options
-        * @param       object          callbacks
-        */
-       init: function(options, callbacks) {
-               /**
-                * action-specific options
-                */
-               this.options = $.extend(true, {
-                       action: '',
-                       className: '',
-                       elements: null,
-                       eventName: 'click'
-               }, options);
-               
-               /**
-                * proxy-specific options
-                */
-               this.callbacks = $.extend(true, {
-                       after: null,
-                       failure: null,
-                       init: null,
-                       success: null
-               }, callbacks);
-               
-               if (!this.options.elements) return;
-               
-               // initialize proxy
-               this.proxy = new WCF.Action.Proxy(this.callbacks);
-               
-               // bind event listener
-               this.options.elements.each($.proxy(function(index, element) {
-                       $(element).bind(this.options.eventName, $.proxy(this._handleEvent, this));
-               }, this));
-       },
-       
-       /**
-        * Handles event actions.
-        * 
-        * @param       object          event
-        */
-       _handleEvent: function(event) {
-               this.proxy.setOption('data', {
-                       actionName: this.options.action,
-                       className: this.options.className,
-                       objectIDs: [ $(event.target).data('objectID') ]
-               });
-               
-               this.proxy.sendRequest();
-       }
-});
-
-/**
- * Basic implementation for AJAXProxy-based deletion.
- * 
- * @param      string          className
- * @param      string          containerSelector
- * @param      string          buttonSelector
- */
-WCF.Action.Delete = Class.extend({
-       /**
-        * delete button selector
-        * @var string
-        */
-       _buttonSelector: '',
-       
-       /**
-        * action class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * container selector
-        * @var string
-        */
-       _containerSelector: '',
-       
-       /**
-        * list of known container ids
-        * @var array<string>
-        */
-       _containers: [ ],
-       
-       /**
-        * Initializes 'delete'-Proxy.
-        * 
-        * @param       string          className
-        * @param       string          containerSelector
-        * @param       string          buttonSelector
-        */
-       init: function(className, containerSelector, buttonSelector) {
-               this._containerSelector = containerSelector;
-               this._className = className;
-               this._buttonSelector = (buttonSelector) ? buttonSelector : '.jsDeleteButton';
-               
-               this.proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               this._initElements();
-               
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.Action.Delete' + this._className.hashCode(), $.proxy(this._initElements, this));
-       },
-       
-       /**
-        * Initializes available element containers.
-        */
-       _initElements: function() {
-               var self = this;
-               $(this._containerSelector).each(function(index, container) {
-                       var $container = $(container);
-                       var $containerID = $container.wcfIdentify();
-                       
-                       if (!WCF.inArray($containerID, self._containers)) {
-                               self._containers.push($containerID);
-                               $container.find(self._buttonSelector).click($.proxy(self._click, self));
-                       }
-               });
-       },
-       
-       /**
-        * Sends AJAX request.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               var $target = $(event.currentTarget);
-               event.preventDefault();
-               
-               if ($target.data('confirmMessage')) {
-                       WCF.System.Confirmation.show($target.data('confirmMessage'), $.proxy(this._execute, this), { target: $target });
-               }
-               else {
-                       WCF.LoadingOverlayHandler.updateIcon($target);
-                       this._sendRequest($target);
-               }
-       },
-       
-       /**
-        * Is called if the delete effect has been triggered on the given element.
-        * 
-        * @param       jQuery          element
-        */
-       _didTriggerEffect: function(element) {
-               // does nothing
-       },
-       
-       /**
-        * Executes deletion.
-        * 
-        * @param       string          action
-        * @param       object          parameters
-        */
-       _execute: function(action, parameters) {
-               if (action === 'cancel') {
-                       return;
-               }
-               
-               WCF.LoadingOverlayHandler.updateIcon(parameters.target);
-               this._sendRequest(parameters.target);
-       },
-       
-       /**
-        * Sends the request
-        * 
-        * @param       jQuery  object
-        */
-       _sendRequest: function(object) {
-               this.proxy.setOption('data', {
-                       actionName: 'delete',
-                       className: this._className,
-                       interfaceName: 'wcf\\data\\IDeleteAction',
-                       objectIDs: [ $(object).data('objectID') ]
-               });
-               
-               this.proxy.sendRequest();
-       },
-       
-       /**
-        * Deletes items from containers.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       object          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               this.triggerEffect(data.objectIDs);
-       },
-       
-       /**
-        * Triggers the delete effect for the objects with the given ids.
-        * 
-        * @param       array           objectIDs
-        */
-       triggerEffect: function(objectIDs) {
-               for (var $index in this._containers) {
-                       var $container = $('#' + this._containers[$index]);
-                       if (WCF.inArray($container.find(this._buttonSelector).data('objectID'), objectIDs)) {
-                               var self = this;
-                               $container.wcfBlindOut('up',function() {
-                                       $(this).remove();
-                                       self._containers.splice(self._containers.indexOf($(this).wcfIdentify()), 1);
-                                       self._didTriggerEffect($(this));
-                               });
-                       }
-               }
-       }
-});
-
-/**
- * Basic implementation for deletion of nested elements.
- * 
- * The implementation requires the nested elements to be grouped as numbered lists
- * (ol lists). The child elements of the deleted elements are moved to the parent
- * element of the deleted element.
- * 
- * @see        WCF.Action.Delete
- */
-WCF.Action.NestedDelete = WCF.Action.Delete.extend({
-       /**
-        * @see WCF.Action.Delete.triggerEffect()
-        */
-       triggerEffect: function(objectIDs) {
-               for (var $index in this._containers) {
-                       var $container = $('#' + this._containers[$index]);
-                       if (WCF.inArray($container.find(this._buttonSelector).data('objectID'), objectIDs)) {
-                               // move children up
-                               if ($container.has('ol').has('li').length) {
-                                       if ($container.is(':only-child')) {
-                                               $container.parent().replaceWith($container.find('> ol'));
-                                       }
-                                       else {
-                                               $container.replaceWith($container.find('> ol > li'));
-                                       }
-                                       
-                                       this._containers.splice(this._containers.indexOf($container.wcfIdentify()), 1);
-                                       this._didTriggerEffect($container);
-                               }
-                               else {
-                                       var self = this;
-                                       $container.wcfBlindOut('up', function() {
-                                               $(this).remove();
-                                               self._containers.splice(self._containers.indexOf($(this).wcfIdentify()), 1);
-                                               self._didTriggerEffect($(this));
-                                       });
-                               }
-                       }
-               }
-       }
-});
-
-/**
- * Basic implementation for AJAXProxy-based toggle actions.
- * 
- * @param      string          className
- * @param      jQuery          containerList
- * @param      string          buttonSelector
- */
-WCF.Action.Toggle = Class.extend({
-       /**
-        * toogle button selector
-        * @var string
-        */
-       _buttonSelector: '.jsToggleButton',
-       
-       /**
-        * action class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * container selector
-        * @var string
-        */
-       _containerSelector: '',
-       
-       /**
-        * list of known container ids
-        * @var array<string>
-        */
-       _containers: [ ],
-       
-       /**
-        * Initializes 'toggle'-Proxy
-        * 
-        * @param       string          className
-        * @param       string          containerSelector
-        * @param       string          buttonSelector
-        */
-       init: function(className, containerSelector, buttonSelector) {
-               this._containerSelector = containerSelector;
-               this._className = className;
-               this._buttonSelector = (buttonSelector) ? buttonSelector : '.jsToggleButton';
-               this._containers = [ ];
-               
-               // initialize proxy
-               var options = {
-                       success: $.proxy(this._success, this)
-               };
-               this.proxy = new WCF.Action.Proxy(options);
-               
-               // bind event listener
-               this._initElements();
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.Action.Toggle' + this._className.hashCode(), $.proxy(this._initElements, this));    
-       },
-       
-       /**
-        * Initializes available element containers.
-        */
-       _initElements: function() {
-               $(this._containerSelector).each($.proxy(function(index, container) {
-                       var $container = $(container);
-                       var $containerID = $container.wcfIdentify();
-                       
-                       if (!WCF.inArray($containerID, this._containers)) {
-                               this._containers.push($containerID);
-                               $container.find(this._buttonSelector).click($.proxy(this._click, this));
-                       }
-               }, this));
-       },
-       
-       /**
-        * Sends AJAX request.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               var $target = $(event.currentTarget);
-               event.preventDefault();
-               
-               if ($target.data('confirmMessage')) {
-                       WCF.System.Confirmation.show($target.data('confirmMessage'), $.proxy(this._execute, this), { target: $target });
-               }
-               else {
-                       WCF.LoadingOverlayHandler.updateIcon($target);
-                       this._sendRequest($target);
-               }
-       },
-       
-       /**
-        * Executes toggeling.
-        * 
-        * @param       string          action
-        * @param       object          parameters
-        */
-       _execute: function(action, parameters) {
-               if (action === 'cancel') {
-                       return;
-               }
-               
-               WCF.LoadingOverlayHandler.updateIcon(parameters.target);
-               this._sendRequest(parameters.target);
-       },
-       
-       _sendRequest: function(object) {
-               this.proxy.setOption('data', {
-                       actionName: 'toggle',
-                       className: this._className,
-                       interfaceName: 'wcf\\data\\IToggleAction',
-                       objectIDs: [ $(object).data('objectID') ]
-               });
-               
-               this.proxy.sendRequest();
-       },
-       
-       /**
-        * Toggles status icons.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       object          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               this.triggerEffect(data.objectIDs);
-       },
-       
-       /**
-        * Triggers the toggle effect for the objects with the given ids.
-        * 
-        * @param       array           objectIDs
-        */
-       triggerEffect: function(objectIDs) {
-               for (var $index in this._containers) {
-                       var $container = $('#' + this._containers[$index]);
-                       var $toggleButton = $container.find(this._buttonSelector);
-                       if (WCF.inArray($toggleButton.data('objectID'), objectIDs)) {
-                               $container.wcfHighlight();
-                               this._toggleButton($container, $toggleButton);
-                       }
-               }
-       },
-       
-       /**
-        * Tiggers the toggle effect on a button
-        * 
-        * @param       jQuery  $container
-        * @param       jQuery  $toggleButton
-        */
-       _toggleButton: function($container, $toggleButton) {
-               // toggle icon source
-               WCF.LoadingOverlayHandler.updateIcon($toggleButton, false);
-               if ($toggleButton.hasClass('icon-check-empty')) {
-                       $toggleButton.removeClass('icon-check-empty').addClass('icon-check');
-                       $newTitle = ($toggleButton.data('disableTitle') ? $toggleButton.data('disableTitle') : WCF.Language.get('wcf.global.button.disable'));
-                       $toggleButton.attr('title', $newTitle);
-               }
-               else {
-                       $toggleButton.removeClass('icon-check').addClass('icon-check-empty');
-                       $newTitle = ($toggleButton.data('enableTitle') ? $toggleButton.data('enableTitle') : WCF.Language.get('wcf.global.button.enable'));
-                       $toggleButton.attr('title', $newTitle);
-               }
-               
-               // toggle css class
-               $container.toggleClass('disabled');
-       }
-});
-
-/**
- * Executes provided callback if scroll threshold is reached. Usuable to determine
- * if user reached the bottom of an element to load new elements on the fly.
- * 
- * If you do not provide a value for 'reference' and 'target' it will assume you're
- * monitoring page scrolls, otherwise a valid jQuery selector must be provided for both.
- * 
- * @param      integer         threshold
- * @param      object          callback
- * @param      string          reference
- * @param      string          target
- */
-WCF.Action.Scroll = Class.extend({
-       /**
-        * callback used once threshold is reached
-        * @var object
-        */
-       _callback: null,
-       
-       /**
-        * reference object
-        * @var jQuery
-        */
-       _reference: null,
-       
-       /**
-        * target object
-        * @var jQuery
-        */
-       _target: null,
-       
-       /**
-        * threshold value
-        * @var integer
-        */
-       _threshold: 0,
-       
-       /**
-        * Initializes a new WCF.Action.Scroll object.
-        * 
-        * @param       integer         threshold
-        * @param       object          callback
-        * @param       string          reference
-        * @param       string          target
-        */
-       init: function(threshold, callback, reference, target) {
-               this._threshold = parseInt(threshold);
-               if (this._threshold === 0) {
-                       console.debug("[WCF.Action.Scroll] Given threshold is invalid, aborting.");
-                       return;
-               }
-               
-               if ($.isFunction(callback)) this._callback = callback;
-               if (this._callback === null) {
-                       console.debug("[WCF.Action.Scroll] Given callback is invalid, aborting.");
-                       return;
-               }
-               
-               // bind element references
-               this._reference = $((reference) ? reference : window);
-               this._target = $((target) ? target : document);
-               
-               // watch for scroll event
-               this.start();
-               
-               // check if browser navigated back and jumped to offset before JavaScript was loaded
-               this._scroll();
-       },
-       
-       /**
-        * Calculates if threshold is reached and notifies callback.
-        */
-       _scroll: function() {
-               var $targetHeight = this._target.height();
-               var $topOffset = this._reference.scrollTop();
-               var $referenceHeight = this._reference.height();
-               
-               // calculate if defined threshold is visible
-               if (($targetHeight - ($referenceHeight + $topOffset)) < this._threshold) {
-                       this._callback(this);
-               }
-       },
-       
-       /**
-        * Enables scroll monitoring, may be used to resume.
-        */
-       start: function() {
-               this._reference.on('scroll', $.proxy(this._scroll, this));
-       },
-       
-       /**
-        * Disables scroll monitoring, e.g. no more elements loadable.
-        */
-       stop: function() {
-               this._reference.off('scroll');
-       }
-});
-
-/**
- * Namespace for date-related functions.
- */
-WCF.Date = {};
-
-/**
- * Provides a date picker for date input fields.
- */
-WCF.Date.Picker = {
-       /**
-        * date format
-        * @var string
-        */
-       _dateFormat: 'yy-mm-dd',
-       
-       /**
-        * time format
-        * @var string
-        */
-       _timeFormat: 'g:ia',
-       
-       /**
-        * Initializes the jQuery UI based date picker.
-        */
-       init: function() {
-               // ignore error 'unexpected literal' error; this might be not the best approach
-               // to fix this problem, but since the date is properly processed anyway, we can
-               // simply continue :)   - Alex
-               var $__log = $.timepicker.log;
-               $.timepicker.log = function(error) {
-                       if (error.indexOf('Error parsing the date/time string: Unexpected literal at position') == -1 && error.indexOf('Error parsing the date/time string: Unknown name at position') == -1) {
-                               $__log(error);
-                       }
-               };
-               
-               this._convertDateFormat();
-               this._initDatePicker();
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.Date.Picker', $.proxy(this._initDatePicker, this));
-       },
-       
-       /**
-        * Convert PHPs date() format to jQuery UIs date picker format.
-        */
-       _convertDateFormat: function() {
-               // replacement table
-               // format of PHP date() => format of jQuery UI date picker
-               //
-               // No equivalence in PHP date():
-               // oo   day of the year (three digit)
-               // !    Windows ticks (100ns since 01/01/0001)
-               //
-               // No equivalence in jQuery UI date picker:
-               // N    ISO-8601 numeric representation of the day of the week
-               // w    Numeric representation of the day of the week
-               // W    ISO-8601 week number of year, weeks starting on Monday
-               // t    Number of days in the given month
-               // L    Whether it's a leap year
-               var $replacementTable = {
-                       // time
-                       'a': 'tt',
-                       'A': 'TT',
-                       'g': 'h',
-                       'G': 'H',
-                       'h': 'hh',
-                       'H': 'HH',
-                       'i': 'mm',
-                       's': 'ss',
-                       'u': 'l',
-                       
-                       // day
-                       'd': 'dd',
-                       'D': 'D',
-                       'j': 'd',
-                       'l': 'DD',
-                       'z': 'o',
-                       'S': '', // English ordinal suffix for the day of the month, 2 characters, will be discarded
-
-                       // month
-                       'F': 'MM',
-                       'm': 'mm',
-                       'M': 'M',
-                       'n': 'm',
-
-                       // year
-                       'o': 'yy',
-                       'Y': 'yy',
-                       'y': 'y',
-
-                       // timestamp
-                       'U': '@'
-               };
-               
-               // do the actual replacement
-               // this is not perfect, but a basic implementation and should work in 99% of the cases
-               this._dateFormat = WCF.Language.get('wcf.date.dateFormat').replace(/([^dDjlzSFmMnoYyU\\]*(?:\\.[^dDjlzSFmMnoYyU\\]*)*)([dDjlzSFmMnoYyU])/g, function(match, part1, part2, offset, string) {
-                       for (var $key in $replacementTable) {
-                               if (part2 == $key) {
-                                       part2 = $replacementTable[$key];
-                               }
-                       }
-                       
-                       return part1 + part2;
-               });
-               
-               this._timeFormat = WCF.Language.get('wcf.date.timeFormat').replace(/([^aAgGhHisu\\]*(?:\\.[^aAgGhHisu\\]*)*)([aAgGhHisu])/g, function(match, part1, part2, offset, string) {
-                       for (var $key in $replacementTable) {
-                               if (part2 == $key) {
-                                       part2 = $replacementTable[$key];
-                               }
-                       }
-                       
-                       return part1 + part2;
-               });
-       },
-       
-       /**
-        * Initializes the date picker for valid fields.
-        */
-       _initDatePicker: function() {
-               $('input[type=date]:not(.jsDatePicker), input[type=datetime]:not(.jsDatePicker)').each($.proxy(function(index, input) {
-                       var $input = $(input);
-                       var $inputName = $input.prop('name');
-                       var $inputValue = $input.val(); // should be Y-m-d (H:i:s), must be interpretable by Date
-                       
-                       var $hasTime = $input.attr('type') == 'datetime';
-                       
-                       // update $input
-                       $input.prop('type', 'text').addClass('jsDatePicker');
-                       
-                       // set placeholder
-                       if ($input.data('placeholder')) $input.attr('placeholder', $input.data('placeholder'));
-                       
-                       // insert a hidden element representing the actual date
-                       $input.removeAttr('name');
-                       $input.before('<input type="hidden" id="' + $input.wcfIdentify() + 'DatePicker" name="' + $inputName + '" value="' + $inputValue + '" />');
-                       
-                       // max- and mindate
-                       var $maxDate = $input.attr('max') ? new Date($input.attr('max').replace(' ', 'T')) : null;
-                       var $minDate = $input.attr('min') ? new Date($input.attr('min').replace(' ', 'T')) : null;
-                       
-                       // init date picker
-                       $options = {
-                               altField: '#' + $input.wcfIdentify() + 'DatePicker',
-                               altFormat: 'yy-mm-dd', // PHPs strtotime() understands this best
-                               beforeShow: function(input, instance) {
-                                       // dirty hack to force opening below the input
-                                       setTimeout(function() {
-                                               instance.dpDiv.position({
-                                                       my: 'left top',
-                                                       at: 'left bottom',
-                                                       collision: 'none',
-                                                       of: input
-                                               });
-                                       }, 1);
-                               },
-                               changeMonth: true,
-                               changeYear: true,
-                               dateFormat: this._dateFormat,
-                               dayNames: WCF.Language.get('__days'),
-                               dayNamesMin: WCF.Language.get('__daysShort'),
-                               dayNamesShort: WCF.Language.get('__daysShort'),
-                               firstDay: parseInt(WCF.Language.get('wcf.date.firstDayOfTheWeek')) || 0,
-                               isRTL: WCF.Language.get('wcf.global.pageDirection') == 'rtl',
-                               maxDate: $maxDate,
-                               minDate: $minDate,
-                               monthNames: WCF.Language.get('__months'),
-                               monthNamesShort: WCF.Language.get('__monthsShort'),
-                               showButtonPanel: false,
-                               onClose: function(dateText, datePicker) {
-                                       // clear altField when datepicker is cleared
-                                       if (dateText == '') {
-                                               $(datePicker.settings["altField"]).val(dateText);
-                                       }
-                               },
-                               showOtherMonths: true,
-                               yearRange: ($input.hasClass('birthday') ? '-100:+0' : '1900:2038')
-                       };
-                       
-                       if ($hasTime) {
-                               // drop the seconds
-                               if (/[0-9]{2}:[0-9]{2}:[0-9]{2}$/.test($inputValue)) {
-                                       $inputValue = $inputValue.replace(/:[0-9]{2}$/, '');
-                                       $input.val($inputValue);
-                               }
-                               $inputValue = $inputValue.replace(' ', 'T');
-                               
-                               if ($input.data('ignoreTimezone')) {
-                                       var $timezoneOffset = new Date().getTimezoneOffset();
-                                       var $timezone = ($timezoneOffset > 0) ? '-' : '+'; // -120 equals GMT+0200
-                                       $timezoneOffset = Math.abs($timezoneOffset);
-                                       var $hours = (Math.floor($timezoneOffset / 60)).toString();
-                                       var $minutes = ($timezoneOffset % 60).toString();
-                                       $timezone += ($hours.length == 2) ? $hours : '0' + $hours;
-                                       $timezone += ':';
-                                       $timezone += ($minutes.length == 2) ? $minutes : '0' + $minutes;
-                                       
-                                       $inputValue = $inputValue.replace(/[+-][0-9]{2}:[0-9]{2}$/, $timezone);
-                               }
-                               
-                               $options = $.extend($options, {
-                                       altFieldTimeOnly: false,
-                                       altTimeFormat: 'HH:mm',
-                                       controlType: 'select',
-                                       hourText: WCF.Language.get('wcf.date.hour'),
-                                       minuteText: WCF.Language.get('wcf.date.minute'),
-                                       showTime: false,
-                                       timeFormat: this._timeFormat,
-                                       yearRange: ($input.hasClass('birthday') ? '-100:+0' : '1900:2038')
-                               });
-                       }
-                       
-                       if ($hasTime) {
-                               $input.datetimepicker($options);
-                       }
-                       else {
-                               $input.datepicker($options);
-                       }
-                       
-                       // format default date
-                       if ($inputValue) {
-                               if (!$hasTime) {
-                                       // drop timezone for date-only input
-                                       $inputValue = new Date($inputValue);
-                                       $inputValue.setMinutes($inputValue.getMinutes() + $inputValue.getTimezoneOffset());
-                               }
-                               
-                               $input.datepicker('setDate', $inputValue);
-                       }
-                       
-                       // bug workaround: setDate creates the widget but unfortunately doesn't hide it...
-                       $input.datepicker('widget').hide();
-               }, this));
-       }
-};
-
-/**
- * Provides utility functions for date operations.
- */
-WCF.Date.Util = {
-       /**
-        * Returns UTC timestamp, if date is not given, current time will be used.
-        * 
-        * @param       Date            date
-        * @return      integer
-        */
-       gmdate: function(date) {
-               var $date = (date) ? date : new Date();
-               
-               return Math.round(Date.UTC(
-                       $date.getUTCFullYear(),
-                       $date.getUTCMonth(),
-                       $date.getUTCDay(),
-                       $date.getUTCHours(),
-                       $date.getUTCMinutes(),
-                       $date.getUTCSeconds()
-               ) / 1000);
-       },
-       
-       /**
-        * Returns a Date object with precise offset (including timezone and local timezone).
-        * Parameters timestamp and offset must be in miliseconds!
-        * 
-        * @param       integer         timestamp
-        * @param       integer         offset
-        * @return      Date
-        */
-       getTimezoneDate: function(timestamp, offset) {
-               var $date = new Date(timestamp);
-               var $localOffset = $date.getTimezoneOffset() * 60000;
-               
-               return new Date((timestamp + $localOffset + offset));
-       }
-};
-
-/**
- * Handles relative time designations.
- */
-WCF.Date.Time = Class.extend({
-       /**
-        * Date of current timestamp
-        * @var Date
-        */
-       _date: 0,
-       
-       /**
-        * list of time elements
-        * @var jQuery
-        */
-       _elements: null,
-       
-       /**
-        * difference between server and local time
-        * @var integer
-        */
-       _offset: null,
-       
-       /**
-        * current timestamp
-        * @var integer
-        */
-       _timestamp: 0,
-       
-       /**
-        * Initializes relative datetimes.
-        */
-       init: function() {
-               this._elements = $('time.datetime');
-               this._offset = null;
-               this._timestamp = 0;
-               
-               // calculate relative datetime on init
-               this._refresh();
-               
-               // re-calculate relative datetime every minute
-               new WCF.PeriodicalExecuter($.proxy(this._refresh, this), 60000);
-               
-               // bind dom node inserted listener
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.Date.Time', $.proxy(this._domNodeInserted, this));
-       },
-       
-       /**
-        * Updates element collection once a DOM node was inserted.
-        */
-       _domNodeInserted: function() {
-               this._elements = $('time.datetime');
-               this._refresh();
-       },
-       
-       /**
-        * Refreshes relative datetime for each element.
-        */
-       _refresh: function() {
-               this._date = new Date();
-               this._timestamp = (this._date.getTime() - this._date.getMilliseconds()) / 1000;
-               if (this._offset === null) {
-                       this._offset = this._timestamp - TIME_NOW;
-               }
-               
-               this._elements.each($.proxy(this._refreshElement, this));
-       },
-       
-       /**
-        * Refreshes relative datetime for current element.
-        * 
-        * @param       integer         index
-        * @param       object          element
-        */
-       _refreshElement: function(index, element) {
-               var $element = $(element);
-               
-               if (!$element.attr('title')) {
-                       $element.attr('title', $element.text());
-               }
-               
-               var $timestamp = $element.data('timestamp') + this._offset;
-               var $date = $element.data('date');
-               var $time = $element.data('time');
-               var $offset = $element.data('offset');
-               
-               // skip for future dates
-               if ($element.data('isFutureDate')) return;
-               
-               // timestamp is less than 60 seconds ago
-               if ($timestamp >= this._timestamp || this._timestamp < ($timestamp + 60)) {
-                       $element.text(WCF.Language.get('wcf.date.relative.now'));
-               }
-               // timestamp is less than 60 minutes ago (display 1 hour ago rather than 60 minutes ago)
-               else if (this._timestamp < ($timestamp + 3540)) {
-                       var $minutes = Math.max(Math.round((this._timestamp - $timestamp) / 60), 1);
-                       $element.text(WCF.Language.get('wcf.date.relative.minutes', { minutes: $minutes }));
-               }
-               // timestamp is less than 24 hours ago
-               else if (this._timestamp < ($timestamp + 86400)) {
-                       var $hours = Math.round((this._timestamp - $timestamp) / 3600);
-                       $element.text(WCF.Language.get('wcf.date.relative.hours', { hours: $hours }));
-               }
-               // timestamp is less than 6 days ago
-               else if (this._timestamp < ($timestamp + 518400)) {
-                       var $midnight = new Date(this._date.getFullYear(), this._date.getMonth(), this._date.getDate());
-                       var $days = Math.ceil(($midnight / 1000 - $timestamp) / 86400);
-                       
-                       // get day of week
-                       var $dateObj = WCF.Date.Util.getTimezoneDate(($timestamp * 1000), $offset * 1000);
-                       var $dow = $dateObj.getDay();
-                       var $day = WCF.Language.get('__days')[$dow];
-                       
-                       $element.text(WCF.Language.get('wcf.date.relative.pastDays', { days: $days, day: $day, time: $time }));
-               }
-               // timestamp is between ~700 million years BC and last week
-               else {
-                       var $string = WCF.Language.get('wcf.date.shortDateTimeFormat');
-                       $element.text($string.replace(/\%date\%/, $date).replace(/\%time\%/, $time));
-               }
-       }
-});
-
-/**
- * Hash-like dictionary. Based upon idead from Prototype's hash
- * 
- * @see        https://github.com/sstephenson/prototype/blob/master/src/prototype/lang/hash.js
- */
-WCF.Dictionary = Class.extend({
-       /**
-        * list of variables
-        * @var object
-        */
-       _variables: { },
-       
-       /**
-        * Initializes a new dictionary.
-        */
-       init: function() {
-               this._variables = { };
-       },
-       
-       /**
-        * Adds an entry.
-        * 
-        * @param       string          key
-        * @param       mixed           value
-        */
-       add: function(key, value) {
-               this._variables[key] = value;
-       },
-       
-       /**
-        * Adds a traditional object to current dataset.
-        * 
-        * @param       object          object
-        */
-       addObject: function(object) {
-               for (var $key in object) {
-                       this.add($key, object[$key]);
-               }
-       },
-       
-       /**
-        * Adds a dictionary to current dataset.
-        * 
-        * @param       object          dictionary
-        */
-       addDictionary: function(dictionary) {
-               dictionary.each($.proxy(function(pair) {
-                       this.add(pair.key, pair.value);
-               }, this));
-       },
-       
-       /**
-        * Retrieves the value of an entry or returns null if key is not found.
-        * 
-        * @param       string          key
-        * @returns     mixed
-        */
-       get: function(key) {
-               if (this.isset(key)) {
-                       return this._variables[key];
-               }
-               
-               return null;
-       },
-       
-       /**
-        * Returns true if given key is a valid entry.
-        * 
-        * @param       string          key
-        */
-       isset: function(key) {
-               return this._variables.hasOwnProperty(key);
-       },
-       
-       /**
-        * Removes an entry.
-        * 
-        * @param       string          key
-        */
-       remove: function(key) {
-               delete this._variables[key];
-       },
-       
-       /**
-        * Iterates through dictionary.
-        * 
-        * Usage:
-        *      var $hash = new WCF.Dictionary();
-        *      $hash.add('foo', 'bar');
-        *      $hash.each(function(pair) {
-        *              // alerts:      foo = bar
-        *              alert(pair.key + ' = ' + pair.value);
-        *      });
-        * 
-        * @param       function        callback
-        */
-       each: function(callback) {
-               if (!$.isFunction(callback)) {
-                       return;
-               }
-               
-               for (var $key in this._variables) {
-                       var $value = this._variables[$key];
-                       var $pair = {
-                               key: $key,
-                               value: $value
-                       };
-                       
-                       callback($pair);
-               }
-       },
-       
-       /**
-        * Returns the amount of items.
-        * 
-        * @return      integer
-        */
-       count: function() {
-               return $.getLength(this._variables);
-       },
-       
-       /**
-        * Returns true if dictionary is empty.
-        * 
-        * @return      integer
-        */
-       isEmpty: function() {
-               return !this.count();
-       }
-});
-
-/**
- * Global language storage.
- * 
- * @see        WCF.Dictionary
- */
-WCF.Language = {
-       _variables: new WCF.Dictionary(),
-       
-       /**
-        * @see WCF.Dictionary.add()
-        */
-       add: function(key, value) {
-               this._variables.add(key, value);
-       },
-       
-       /**
-        * @see WCF.Dictionary.addObject()
-        */
-       addObject: function(object) {
-               this._variables.addObject(object);
-       },
-       
-       /**
-        * Retrieves a variable.
-        * 
-        * @param       string          key
-        * @return      mixed
-        */
-       get: function(key, parameters) {
-               // initialize parameters with an empty object
-               if (parameters == null) var parameters = { };
-               
-               var value = this._variables.get(key);
-               
-               if (value === null) {
-                       // return key again
-                       return key;
-               }
-               else if (typeof value === 'string') {
-                       // transform strings into template and try to refetch
-                       this.add(key, new WCF.Template(value));
-                       return this.get(key, parameters);
-               }
-               else if (typeof value.fetch === 'function') {
-                       // evaluate templates
-                       value = value.fetch(parameters);
-               }
-               
-               return value;
-       }
-};
-
-/**
- * Handles multiple language input fields.
- * 
- * @param      string          elementID
- * @param      boolean         forceSelection
- * @param      object          values
- * @param      object          availableLanguages
- */
-WCF.MultipleLanguageInput = Class.extend({
-       /**
-        * list of available languages
-        * @var object
-        */
-       _availableLanguages: {},
-       
-       /**
-        * button element
-        * @var jQuery
-        */
-       _button: null,
-       
-       /**
-        * initialization state
-        * @var boolean
-        */
-       _didInit: false,
-       
-       /**
-        * target input element
-        * @var jQuery
-        */
-       _element: null,
-       
-       /**
-        * true, if data was entered after initialization
-        * @var boolean
-        */
-       _insertedDataAfterInit: false,
-       
-       /**
-        * enables multiple language ability
-        * @var boolean
-        */
-       _isEnabled: false,
-       
-       /**
-        * enforce multiple language ability
-        * @var boolean
-        */
-       _forceSelection: false,
-       
-       /**
-        * currently active language id
-        * @var integer
-        */
-       _languageID: 0,
-       
-       /**
-        * language selection list
-        * @var jQuery
-        */
-       _list: null,
-       
-       /**
-        * list of language values on init
-        * @var object
-        */
-       _values: null,
-       
-       /**
-        * Initializes multiple language ability for given element id.
-        * 
-        * @param       integer         elementID
-        * @param       boolean         forceSelection
-        * @param       boolean         isEnabled
-        * @param       object          values
-        * @param       object          availableLanguages
-        */
-       init: function(elementID, forceSelection, values, availableLanguages) {
-               this._button = null;
-               this._element = $('#' + $.wcfEscapeID(elementID));
-               this._forceSelection = forceSelection;
-               this._values = values;
-               this._availableLanguages = availableLanguages;
-               
-               // unescape values
-               if ($.getLength(this._values)) {
-                       for (var $key in this._values) {
-                               this._values[$key] = WCF.String.unescapeHTML(this._values[$key]);
-                       }
-               }
-               
-               // default to current user language
-               this._languageID = LANGUAGE_ID;
-               if (this._element.length == 0) {
-                       console.debug("[WCF.MultipleLanguageInput] element id '" + elementID + "' is unknown");
-                       return;
-               }
-               
-               // build selection handler
-               var $enableOnInit = ($.getLength(this._values) > 0) ? true : false;
-               this._insertedDataAfterInit = $enableOnInit;
-               this._prepareElement($enableOnInit);
-               
-               // listen for submit event
-               this._element.parents('form').submit($.proxy(this._submit, this));
-               
-               this._didInit = true;
-       },
-       
-       /**
-        * Builds language handler.
-        * 
-        * @param       boolean         enableOnInit
-        */
-       _prepareElement: function(enableOnInit) {
-               this._element.wrap('<div class="dropdown preInput" />');
-               var $wrapper = this._element.parent();
-               this._button = $('<p class="button dropdownToggle"><span>' + WCF.Language.get('wcf.global.button.disabledI18n') + '</span></p>').prependTo($wrapper);
-               
-               // insert list
-               this._list = $('<ul class="dropdownMenu"></ul>').insertAfter(this._button);
-               
-               // add a special class if next item is a textarea
-               if (this._button.nextAll('textarea').length) {
-                       this._button.addClass('dropdownCaptionTextarea');
-               }
-               else {
-                       this._button.addClass('dropdownCaption');
-               }
-               
-               // insert available languages
-               for (var $languageID in this._availableLanguages) {
-                       $('<li><span>' + this._availableLanguages[$languageID] + '</span></li>').data('languageID', $languageID).click($.proxy(this._changeLanguage, this)).appendTo(this._list);
-               }
-               
-               // disable language input
-               if (!this._forceSelection) {
-                       $('<li class="dropdownDivider" />').appendTo(this._list);
-                       $('<li><span>' + WCF.Language.get('wcf.global.button.disabledI18n') + '</span></li>').click($.proxy(this._disable, this)).appendTo(this._list);
-               }
-               
-               WCF.Dropdown.initDropdown(this._button, enableOnInit);
-               
-               if (enableOnInit || this._forceSelection) {
-                       this._isEnabled = true;
-                       
-                       // pre-select current language
-                       this._list.children('li').each($.proxy(function(index, listItem) {
-                               var $listItem = $(listItem);
-                               if ($listItem.data('languageID') == this._languageID) {
-                                       $listItem.trigger('click');
-                               }
-                       }, this));
-               }
-               
-               WCF.Dropdown.registerCallback($wrapper.wcfIdentify(), $.proxy(this._handleAction, this));
-       },
-       
-       /**
-        * Handles dropdown actions.
-        * 
-        * @param       string          containerID
-        * @param       string          action
-        */
-       _handleAction: function(containerID, action) {
-               if (action === 'open') {
-                       this._enable();
-               }
-               else {
-                       this._closeSelection();
-               }
-       },
-       
-       /**
-        * Enables the language selection or shows the selection if already enabled.
-        * 
-        * @param       object          event
-        */
-       _enable: function(event) {
-               if (!this._isEnabled) {
-                       var $button = (this._button.is('p')) ? this._button.children('span:eq(0)') : this._button;
-                       $button.addClass('active');
-                       
-                       this._isEnabled = true;
-               }
-               
-               // toggle list
-               if (this._list.is(':visible')) {
-                       this._showSelection();
-               }
-       },
-       
-       /**
-        * Shows the language selection.
-        */
-       _showSelection: function() {
-               if (this._isEnabled) {
-                       // display status for each language
-                       this._list.children('li').each($.proxy(function(index, listItem) {
-                               var $listItem = $(listItem);
-                               var $languageID = $listItem.data('languageID');
-                               
-                               if ($languageID) {
-                                       if (this._values[$languageID] && this._values[$languageID] != '') {
-                                               $listItem.removeClass('missingValue');
-                                       }
-                                       else {
-                                               $listItem.addClass('missingValue');
-                                       }
-                               }
-                       }, this));
-               }
-       },
-       
-       /**
-        * Closes the language selection.
-        */
-       _closeSelection: function() {
-               this._disable();
-       },
-       
-       /**
-        * Changes the currently active language.
-        * 
-        * @param       object          event
-        */
-       _changeLanguage: function(event) {
-               var $button = $(event.currentTarget);
-               this._insertedDataAfterInit = true;
-               
-               // save current value
-               if (this._didInit) {
-                       this._values[this._languageID] = this._element.val();
-               }
-               
-               // set new language
-               this._languageID = $button.data('languageID');
-               if (this._values[this._languageID]) {
-                       this._element.val(this._values[this._languageID]);
-               }
-               else {
-                       this._element.val('');
-               }
-               
-               // update marking
-               this._list.children('li').removeClass('active');
-               $button.addClass('active');
-               
-               // update label
-               this._button.children('span').addClass('active').text(this._availableLanguages[this._languageID]);
-               
-               // close selection and set focus on input element
-               if (this._didInit) {
-                       this._element.blur().focus();
-               }
-       },
-       
-       /**
-        * Disables language selection for current element.
-        * 
-        * @param       object          event
-        */
-       _disable: function(event) {
-               if (event === undefined && this._insertedDataAfterInit) {
-                       event = null;
-               }
-               
-               if (this._forceSelection || !this._list || event === null) {
-                       return;
-               }
-               
-               // remove active marking
-               this._button.children('span').removeClass('active').text(WCF.Language.get('wcf.global.button.disabledI18n'));
-               
-               // update element value
-               if (this._values[LANGUAGE_ID]) {
-                       this._element.val(this._values[LANGUAGE_ID]);
-               }
-               else {
-                       // no value for current language found, proceed with empty input
-                       this._element.val();
-               }
-               
-               if (event) {
-                       this._list.children('li').removeClass('active');
-                       $(event.currentTarget).addClass('active');
-               }
-               
-               this._element.blur().focus();
-               this._insertedDataAfterInit = false;
-               this._isEnabled = false;
-               this._values = { };
-       },
-       
-       /**
-        * Prepares language variables on before submit.
-        */
-       _submit: function() {
-               // insert hidden form elements on before submit
-               if (!this._isEnabled) {
-                       return 0xDEADBEEF;
-               }
-               
-               // fetch active value
-               if (this._languageID) {
-                       this._values[this._languageID] = this._element.val();
-               }
-               
-               var $form = $(this._element.parents('form')[0]);
-               var $elementID = this._element.wcfIdentify();
-               
-               for (var $languageID in this._availableLanguages) {
-                       if (this._values[$languageID] === undefined) {
-                               this._values[$languageID] = '';
-                       }
-                       
-                       $('<input type="hidden" name="' + $elementID + '_i18n[' + $languageID + ']" value="' + WCF.String.escapeHTML(this._values[$languageID]) + '" />').appendTo($form);
-               }
-               
-               // remove name attribute to prevent conflict with i18n values
-               this._element.removeAttr('name');
-       }
-});
-
-/**
- * Number utilities.
- */
-WCF.Number = {
-       /**
-        * Rounds a number to a given number of decimal places. Defaults to 0.
-        * 
-        * @param       number          number
-        * @param       decimalPlaces   number of decimal places
-        * @return      number
-        */
-       round: function (number, decimalPlaces) {
-               decimalPlaces = Math.pow(10, (decimalPlaces || 0));
-               
-               return Math.round(number * decimalPlaces) / decimalPlaces;
-       }
-};
-
-/**
- * String utilities.
- */
-WCF.String = {
-       /**
-        * Adds thousands separators to a given number.
-        * 
-        * @see         http://stackoverflow.com/a/6502556/782822
-        * @param       mixed           number
-        * @return      string
-        */
-       addThousandsSeparator: function(number) {
-               return String(number).replace(/(^-?\d{1,3}|\d{3})(?=(?:\d{3})+(?:$|\.))/g, '$1' + WCF.Language.get('wcf.global.thousandsSeparator'));
-       },
-       
-       /**
-        * Escapes special HTML-characters within a string
-        * 
-        * @param       string  string
-        * @return      string
-        */
-       escapeHTML: function (string) {
-               return String(string).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
-       },
-       
-       /**
-        * Escapes a String to work with RegExp.
-        * 
-        * @see         https://github.com/sstephenson/prototype/blob/master/src/prototype/lang/regexp.js#L25
-        * @param       string  string
-        * @return      string
-        */
-       escapeRegExp: function(string) {
-               return String(string).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
-       },
-       
-       /**
-        * Rounds number to given count of floating point digits, localizes decimal-point and inserts thousands-separators
-        * 
-        * @param       mixed   number
-        * @return      string
-        */
-       formatNumeric: function(number, decimalPlaces) {
-               number = String(WCF.Number.round(number, decimalPlaces || 2));
-               numberParts = number.split('.');
-               
-               number = this.addThousandsSeparator(numberParts[0]);
-               if (numberParts.length > 1) number += WCF.Language.get('wcf.global.decimalPoint') + numberParts[1];
-               
-               number = number.replace('-', '\u2212');
-               
-               return number;
-       },
-       
-       /**
-        * Makes a string's first character lowercase
-        * 
-        * @param       string          string
-        * @return      string
-        */
-       lcfirst: function(string) {
-               return String(string).substring(0, 1).toLowerCase() + string.substring(1);
-       },
-       
-       /**
-        * Makes a string's first character uppercase
-        * 
-        * @param       string          string
-        * @return      string
-        */
-       ucfirst: function(string) {
-               return String(string).substring(0, 1).toUpperCase() + string.substring(1);
-       },
-       
-       /**
-        * Unescapes special HTML-characters within a string
-        * 
-        * @param       string          string
-        * @return      string
-        */
-       unescapeHTML: function (string) {
-               return String(string).replace(/&amp;/g, '&').replace(/&quot;/g, '"').replace(/&lt;/g, '<').replace(/&gt;/g, '>');
-       }
-};
-
-/**
- * Basic implementation for WCF TabMenus. Use the data attributes 'active' to specify the
- * tab which should be shown on init. Furthermore you may specify a 'store' data-attribute
- * which will be filled with the currently selected tab.
- */
-WCF.TabMenu = {
-       /**
-        * list of tabmenu containers
-        * @var object
-        */
-       _containers: { },
-       
-       /**
-        * initialization state
-        * @var boolean
-        */
-       _didInit: false,
-       
-       /**
-        * Initializes all TabMenus
-        */
-       init: function() {
-               var $containers = $('.tabMenuContainer:not(.staticTabMenuContainer)');
-               var self = this;
-               $containers.each(function(index, tabMenu) {
-                       var $tabMenu = $(tabMenu);
-                       var $containerID = $tabMenu.wcfIdentify();
-                       if (self._containers[$containerID]) {
-                               // continue with next container
-                               return true;
-                       }
-                       
-                       if ($tabMenu.data('store') && !$('#' + $tabMenu.data('store')).length) {
-                               $('<input type="hidden" name="' + $tabMenu.data('store') + '" value="" id="' + $tabMenu.data('store') + '" />').appendTo($tabMenu.parents('form').find('.formSubmit'));
-                       }
-                       
-                       // init jQuery UI TabMenu
-                       self._containers[$containerID] = $tabMenu;
-                       $tabMenu.wcfTabs({
-                               active: false,
-                               activate: function(event, eventData) {
-                                       var $panel = $(eventData.newPanel);
-                                       var $container = $panel.closest('.tabMenuContainer');
-                                       
-                                       // store currently selected item
-                                       var $tabMenu = $container;
-                                       while (true) {
-                                               // do not trigger on init
-                                               if ($tabMenu.data('isParent') === undefined) {
-                                                       break;
-                                               }
-                                               
-                                               if ($tabMenu.data('isParent')) {
-                                                       if ($tabMenu.data('store')) {
-                                                               $('#' + $tabMenu.data('store')).val($panel.attr('id'));
-                                                       }
-                                                       
-                                                       break;
-                                               }
-                                               else {
-                                                       $tabMenu = $tabMenu.data('parent');
-                                               }
-                                       }
-                                       
-                                       // set panel id as location hash
-                                       if (WCF.TabMenu._didInit) {
-                                               // do not update history if within an overlay
-                                               if ($panel.data('inTabMenu') == undefined) {
-                                                       $panel.data('inTabMenu', ($panel.parents('.dialogContainer').length));
-                                               }
-                                               
-                                               if (!$panel.data('inTabMenu')) {
-                                                       if (window.history) {
-                                                               window.history.pushState(null, document.title, window.location.toString().replace(/#.+$/, '') + '#' + $panel.attr('id'));
-                                                       }
-                                                       else {
-                                                               location.hash = '#' + $panel.attr('id');
-                                                       }
-                                               }
-                                       }
-                               }
-                       });
-                       
-                       $tabMenu.data('isParent', ($tabMenu.children('.tabMenuContainer, .tabMenuContent').length > 0)).data('parent', false);
-                       if (!$tabMenu.data('isParent')) {
-                               // check if we're a child element
-                               if ($tabMenu.parent().hasClass('tabMenuContainer')) {
-                                       $tabMenu.data('parent', $tabMenu.parent());
-                               }
-                       }
-               });
-               
-               // try to resolve location hash
-               if (!this._didInit) {
-                       this._selectActiveTab();
-                       $(window).bind('hashchange', $.proxy(this.selectTabs, this));
-                       
-                       if (!this._selectErroneousTab()) {
-                               this.selectTabs();
-                       }
-                       
-                       if ($.browser.mozilla && location.hash) {
-                               var $target = $(location.hash);
-                               if ($target.length && $target.hasClass('tabMenuContent')) {
-                                       var $offset = $target.offset();
-                                       window.scrollTo($offset.left, $offset.top);
-                               }
-                       }
-               }
-               
-               this._didInit = true;
-       },
-       
-       /**
-        * Reloads the tab menus.
-        */
-       reload: function() {
-               this._containers = { };
-               this.init();
-       },
-       
-       /**
-        * Force display of first erroneous tab and returns true if at least one
-        * tab contains an error.
-        * 
-        * @return      boolean
-        */
-       _selectErroneousTab: function() {
-               var $foundErrors = false;
-               for (var $containerID in this._containers) {
-                       var $tabMenu = this._containers[$containerID];
-                       
-                       if ($tabMenu.find('.formError').length) {
-                               $foundErrors = true;
-                               
-                               if (!$tabMenu.data('isParent')) {
-                                       while (true) {
-                                               if ($tabMenu.data('parent') === false) {
-                                                       break;
-                                               }
-                                               
-                                               $tabMenu = $tabMenu.data('parent').wcfTabs('selectTab', $tabMenu.wcfIdentify());
-                                       }
-                                       
-                                       return true;
-                               }
-                       }
-               }
-               
-               // found an error in a non-nested tab menu
-               if ($foundErrors) {
-                       for (var $containerID in this._containers) {
-                               var $tabMenu = this._containers[$containerID];
-                               var $formError = $tabMenu.find('.formError:eq(0)');
-                               
-                               if ($formError.length) {
-                                       // find the tab container
-                                       $tabMenu.wcfTabs('selectTab', $formError.parents('.tabMenuContent').wcfIdentify());
-                                       
-                                       while (true) {
-                                               if ($tabMenu.data('parent') === false) {
-                                                       break;
-                                               }
-                                               
-                                               $tabMenu = $tabMenu.data('parent').wcfTabs('selectTab', $tabMenu.wcfIdentify());
-                                       }
-                                       
-                                       return true;
-                               }
-                       }
-               }
-               
-               return false;
-       },
-       
-       /**
-        * Selects the active tab menu item.
-        */
-       _selectActiveTab: function() {
-               for (var $containerID in this._containers) {
-                       var $tabMenu = this._containers[$containerID];
-                       if ($tabMenu.data('active')) {
-                               var $index = $tabMenu.data('active');
-                               var $subIndex = null;
-                               if (/-/.test($index)) {
-                                       var $tmp = $index.split('-');
-                                       $index = $tmp[0];
-                                       $subIndex = $tmp[1];
-                               }
-                               
-                               $tabMenu.find('.tabMenuContent').each(function(innerIndex, tabMenuItem) {
-                                       var $tabMenuItem = $(tabMenuItem);
-                                       if ($tabMenuItem.wcfIdentify() == $index) {
-                                               $tabMenu.wcfTabs('select', innerIndex);
-                                               if ($subIndex !== null) {
-                                                       if ($tabMenuItem.hasClass('tabMenuContainer')) {
-                                                               $tabMenuItem.wcfTabs('selectTab', $tabMenu.data('active'));
-                                                       }
-                                                       else {
-                                                               $tabMenu.wcfTabs('selectTab', $tabMenu.data('active'));
-                                                       }
-                                               }
-                                               
-                                               return false;
-                                       }
-                               });
-                       }
-               }
-       },
-       
-       /**
-        * Resolves location hash to display tab menus.
-        * 
-        * @return      boolean
-        */
-       selectTabs: function() {
-               if (location.hash) {
-                       var $hash = location.hash.substr(1);
-                       
-                       // try to find matching tab menu container
-                       var $tabMenu = $('#' + $.wcfEscapeID($hash));
-                       if ($tabMenu.length === 1 && $tabMenu.hasClass('ui-tabs-panel')) {
-                               $tabMenu = $tabMenu.parent('.ui-tabs');
-                               if ($tabMenu.length) {
-                                       $tabMenu.wcfTabs('selectTab', $hash);
-                                       
-                                       // check if this is a nested tab menu
-                                       if ($tabMenu.hasClass('ui-tabs-panel')) {
-                                               $hash = $tabMenu.wcfIdentify();
-                                               $tabMenu = $tabMenu.parent('.ui-tabs');
-                                               if ($tabMenu.length) {
-                                                       $tabMenu.wcfTabs('selectTab', $hash);
-                                               }
-                                       }
-                                       
-                                       return true;
-                               }
-                       }
-               }
-               
-               return false;
-       }
-};
-
-/**
- * Templates that may be fetched more than once with different variables.
- * Based upon ideas from Prototype's template.
- * 
- * Usage:
- *     var myTemplate = new WCF.Template('{$hello} World');
- *     myTemplate.fetch({ hello: 'Hi' }); // Hi World
- *     myTemplate.fetch({ hello: 'Hello' }); // Hello World
- *     
- *     my2ndTemplate = new WCF.Template('{@$html}{$html}');
- *     my2ndTemplate.fetch({ html: '<b>Test</b>' }); // <b>Test</b>&lt;b&gt;Test&lt;/b&gt;
- *     
- *     var my3rdTemplate = new WCF.Template('You can use {literal}{$variable}{/literal}-Tags here');
- *     my3rdTemplate.fetch({ variable: 'Not shown' }); // You can use {$variable}-Tags here
- * 
- * @param      template                template-content
- * @see                https://github.com/sstephenson/prototype/blob/master/src/prototype/lang/template.js
- */
-WCF.Template = Class.extend({
-       /**
-        * Prepares template
-        * 
-        * @param       $template               template-content
-        */
-       init: function(template) {
-               var $literals = new WCF.Dictionary();
-               var $tagID = 0;
-               
-               // escape \ and ' and newlines
-               template = template.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/(\r\n|\n|\r)/g, '\\n');
-               
-               // save literal-tags
-               template = template.replace(/\{literal\}(.*?)\{\/literal\}/g, $.proxy(function(match) {
-                       // hopefully no one uses this string in one of his templates
-                       var id = '@@@@@@@@@@@'+Math.random()+'@@@@@@@@@@@';
-                       $literals.add(id, match.replace(/\{\/?literal\}/g, ''));
-                       
-                       return id;
-               }, this));
-               
-               // remove comments
-               template = template.replace(/\{\*.*?\*\}/g, '');
-               
-               var parseParameterList = function(parameterString) {
-                       var $chars = parameterString.split('');
-                       var $parameters = { };
-                       var $inName = true;
-                       var $name = '';
-                       var $value = '';
-                       var $doubleQuoted = false;
-                       var $singleQuoted = false;
-                       var $escaped = false;
-                       
-                       for (var $i = 0, $max = $chars.length; $i < $max; $i++) {
-                               var $char = $chars[$i];
-                               if ($inName && $char != '=' && $char != ' ') $name += $char;
-                               else if ($inName && $char == '=') {
-                                       $inName = false;
-                                       $singleQuoted = false;
-                                       $doubleQuoted = false;
-                                       $escaped = false;
-                               }
-                               else if (!$inName && !$singleQuoted && !$doubleQuoted && $char == ' ') {
-                                       $inName = true;
-                                       $parameters[$name] = $value;
-                                       $value = $name = '';
-                               }
-                               else if (!$inName && $singleQuoted && !$escaped && $char == "'") {
-                                       $singleQuoted = false;
-                                       $value += $char;
-                               }
-                               else if (!$inName && !$singleQuoted && !$doubleQuoted && $char == "'") {
-                                       $singleQuoted = true;
-                                       $value += $char;
-                               }
-                               else if (!$inName && $doubleQuoted && !$escaped && $char == '"') {
-                                       $doubleQuoted = false;
-                                       $value += $char;
-                               }
-                               else if (!$inName && !$singleQuoted && !$doubleQuoted && $char == '"') {
-                                       $doubleQuoted = true;
-                                       $value += $char;
-                               }
-                               else if (!$inName && ($doubleQuoted || $singleQuoted) && !$escaped && $char == '\\') {
-                                       $escaped = true;
-                                       $value += $char;
-                               }
-                               else if (!$inName) {
-                                       $escaped = false;
-                                       $value += $char;
-                               }
-                       }
-                       $parameters[$name] = $value;
-                       
-                       if ($doubleQuoted || $singleQuoted || $escaped) throw new Error('Syntax error in parameterList: "' + parameterString + '"');
-                       
-                       return $parameters;
-               };
-               
-               var unescape = function(string) {
-                       return string.replace(/\\n/g, "\n").replace(/\\\\/g, '\\').replace(/\\'/g, "'");
-               };
-               
-               template = template.replace(/\{(\$[^\}]+?)\}/g, function(_, content) {
-                       content = unescape(content.replace(/\$([^.\[\s]+)/g, "(v['$1'])"));
-                       
-                       return "' + WCF.String.escapeHTML(" + content + ") + '";
-               })
-               // Numeric Variable
-               .replace(/\{#(\$[^\}]+?)\}/g, function(_, content) {
-                       content = unescape(content.replace(/\$([^.\[\s]+)/g, "(v['$1'])"));
-                       
-                       return "' + WCF.String.formatNumeric(" + content + ") + '";
-               })
-               // Variable without escaping
-               .replace(/\{@(\$[^\}]+?)\}/g, function(_, content) {
-                       content = unescape(content.replace(/\$([^.\[\s]+)/g, "(v['$1'])"));
-                       
-                       return "' + " + content + " + '";
-               })
-               // {lang}foo{/lang}
-               .replace(/{lang}(.+?){\/lang}/g, function(_, content) {
-                       return "' + WCF.Language.get('" + unescape(content) + "') + '";
-               })
-               // {if}
-               .replace(/\{if (.+?)\}/g, function(_, content) {
-                       content = unescape(content.replace(/\$([^.\[\s]+)/g, "(v['$1'])"));
-                       
-                       return  "';\n" +
-                               "if (" + content + ") {\n" +
-                               "       $output += '";
-               })
-               // {elseif}
-               .replace(/\{else ?if (.+?)\}/g, function(_, content) {
-                       content = unescape(content.replace(/\$([^.\[\s]+)/g, "(v['$1'])"));
-                       
-                       return  "';\n" +
-                               "}\n" +
-                               "else if (" + content + ") {\n" +
-                               "       $output += '";
-               })
-               // {implode}
-               .replace(/\{implode (.+?)\}/g, function(_, content) {
-                       $tagID++;
-                       
-                       content = content.replace(/\\\\/g, '\\').replace(/\\'/g, "'");
-                       var $parameters = parseParameterList(content);
-                       
-                       if (typeof $parameters['from'] === 'undefined') throw new Error('Missing from attribute in implode-tag');
-                       if (typeof $parameters['item'] === 'undefined') throw new Error('Missing item attribute in implode-tag');
-                       if (typeof $parameters['glue'] === 'undefined') $parameters['glue'] = "', '";
-                       
-                       $parameters['from'] = $parameters['from'].replace(/\$([^.\[\s]+)/g, "(v.$1)");
-                       
-                       return  "';\n"+
-                               "var $implode_" + $tagID + " = false;\n" +
-                               "for ($implodeKey_" + $tagID + " in " + $parameters['from'] + ") {\n" +
-                               "       v[" + $parameters['item'] + "] = " + $parameters['from'] + "[$implodeKey_" + $tagID + "];\n" +
-                               (typeof $parameters['key'] !== 'undefined' ? "          v[" + $parameters['key'] + "] = $implodeKey_" + $tagID + ";\n" : "") +
-                               "       if ($implode_" + $tagID + ") $output += " + $parameters['glue'] + ";\n" +
-                               "       $implode_" + $tagID + " = true;\n" +
-                               "       $output += '";
-               })
-               // {foreach}
-               .replace(/\{foreach (.+?)\}/g, function(_, content) {
-                       $tagID++;
-                       
-                       content = content.replace(/\\\\/g, '\\').replace(/\\'/g, "'");
-                       var $parameters = parseParameterList(content);
-                       
-                       if (typeof $parameters['from'] === 'undefined') throw new Error('Missing from attribute in foreach-tag');
-                       if (typeof $parameters['item'] === 'undefined') throw new Error('Missing item attribute in foreach-tag');
-                       $parameters['from'] = $parameters['from'].replace(/\$([^.\[\s]+)/g, "(v.$1)");
-                       
-                       return  "';\n" +
-                               "$foreach_"+$tagID+" = false;\n" +
-                               "for ($foreachKey_" + $tagID + " in " + $parameters['from'] + ") {\n" +
-                               "       $foreach_"+$tagID+" = true;\n" +
-                               "       break;\n" +
-                               "}\n" +
-                               "if ($foreach_"+$tagID+") {\n" +
-                               "       for ($foreachKey_" + $tagID + " in " + $parameters['from'] + ") {\n" +
-                               "               v[" + $parameters['item'] + "] = " + $parameters['from'] + "[$foreachKey_" + $tagID + "];\n" +
-                               (typeof $parameters['key'] !== 'undefined' ? "          v[" + $parameters['key'] + "] = $foreachKey_" + $tagID + ";\n" : "") +
-                               "               $output += '";
-               })
-               // {foreachelse}
-               .replace(/\{foreachelse\}/g, 
-                       "';\n" +
-                       "       }\n" +
-                       "}\n" +
-                       "else {\n" +
-                       "       {\n" +
-                       "               $output += '"
-               )
-               // {/foreach}
-               .replace(/\{\/foreach\}/g, 
-                       "';\n" +
-                       "       }\n" +
-                       "}\n" +
-                       "$output += '"
-               )
-               // {else}
-               .replace(/\{else\}/g, 
-                       "';\n" +
-                       "}\n" +
-                       "else {\n" +
-                       "       $output += '"
-               )
-               // {/if} and {/implode}
-               .replace(/\{\/(if|implode)\}/g, 
-                       "';\n" +
-                       "}\n" +
-                       "$output += '"
-               );
-               
-               // call callback
-               for (var key in WCF.Template.callbacks) {
-                       template = WCF.Template.callbacks[key](template);
-               }
-               
-               // insert delimiter tags
-               template = template.replace('{ldelim}', '{').replace('{rdelim}', '}');
-               
-               $literals.each(function(pair) {
-                       template = template.replace(pair.key, pair.value);
-               });
-               
-               template = "$output += '" + template + "';";
-               
-               try {
-                       this.fetch = new Function("v", "if (typeof v != 'object') { v = {}; } v.__window = window; v.__wcf = window.WCF; var $output = ''; " + template + ' return $output;');
-               }
-               catch (e) {
-                       console.debug("var $output = ''; " + template + ' return $output;');
-                       throw e;
-               }
-       },
-       
-       /**
-        * Fetches the template with the given variables.
-        * 
-        * @param       v       variables to insert
-        * @return              parsed template
-        */
-       fetch: function(v) {
-               // this will be replaced in the init function
-       }
-});
-
-/**
- * Array of callbacks that will be called after parsing the included tags. Only applies to Templates compiled after the callback was added.
- * 
- * @var        array<Function>
- */
-WCF.Template.callbacks = [ ];
-
-/**
- * Toggles options.
- * 
- * @param      string          element
- * @param      array           showItems
- * @param      array           hideItems
- * @param      function        callback
- */
-WCF.ToggleOptions = Class.extend({
-       /**
-        * target item
-        * 
-        * @var jQuery
-        */
-       _element: null,
-       
-       /**
-        * list of items to be shown
-        * 
-        * @var array
-        */
-       _showItems: [],
-       
-       /**
-        * list of items to be hidden
-        * 
-        * @var array
-        */
-       _hideItems: [],
-               
-       /**
-        * callback after options were toggled
-        * 
-        * @var function
-        */
-        _callback: null,
-       
-       /**
-        * Initializes option toggle.
-        * 
-        * @param       string          element
-        * @param       array           showItems
-        * @param       array           hideItems
-        * @param       function        callback
-        */
-       init: function(element, showItems, hideItems, callback) {
-               this._element = $('#' + element);
-               this._showItems = showItems;
-               this._hideItems = hideItems;
-               if (callback !== undefined) {
-                       this._callback = callback;
-               }
-               
-               // bind event
-               this._element.click($.proxy(this._toggle, this));
-               
-               // execute toggle on init
-               this._toggle();
-       },
-       
-       /**
-        * Toggles items.
-        */
-       _toggle: function() {
-               if (!this._element.prop('checked')) return;
-               
-               for (var $i = 0, $length = this._showItems.length; $i < $length; $i++) {
-                       var $item = this._showItems[$i];
-                       
-                       $('#' + $item).show();
-               }
-               
-               for (var $i = 0, $length = this._hideItems.length; $i < $length; $i++) {
-                       var $item = this._hideItems[$i];
-                       
-                       $('#' + $item).hide();
-               }
-               
-               if (this._callback !== null) {
-                       this._callback();
-               }
-       }
-});
-
-/**
- * Namespace for all kind of collapsible containers.
- */
-WCF.Collapsible = {};
-
-/**
- * Simple implementation for collapsible content, neither does it
- * store its state nor does it allow AJAX callbacks to fetch content.
- */
-WCF.Collapsible.Simple = {
-       /**
-        * Initializes collapsibles.
-        */
-       init: function() {
-               $('.jsCollapsible').each($.proxy(function(index, button) {
-                       this._initButton(button);
-               }, this));
-       },
-       
-       /**
-        * Binds an event listener on all buttons triggering the collapsible.
-        * 
-        * @param       object          button
-        */
-       _initButton: function(button) {
-               var $button = $(button);
-               var $isOpen = $button.data('isOpen');
-               
-               if (!$isOpen) {
-                       // hide container on init
-                       $('#' + $button.data('collapsibleContainer')).hide();
-               }
-               
-               $button.click($.proxy(this._toggle, this));
-       },
-       
-       /**
-        * Toggles collapsible containers on click.
-        * 
-        * @param       object          event
-        */
-       _toggle: function(event) {
-               var $button = $(event.currentTarget);
-               var $isOpen = $button.data('isOpen');
-               var $target = $('#' + $.wcfEscapeID($button.data('collapsibleContainer')));
-               
-               if ($isOpen) {
-                       $target.stop().wcfBlindOut('vertical', $.proxy(function() {
-                               this._toggleImage($button);
-                       }, this));
-                       $isOpen = false;
-               }
-               else {
-                       $target.stop().wcfBlindIn('vertical', $.proxy(function() {
-                               this._toggleImage($button);
-                       }, this));
-                       $isOpen = true;
-               }
-               
-               $button.data('isOpen', $isOpen);
-               
-               // suppress event
-               event.stopPropagation();
-               return false;
-       },
-       
-       /**
-        * Toggles image of target button.
-        * 
-        * @param       jQuery          button
-        */
-       _toggleImage: function(button) {
-               var $icon = button.find('span.icon');
-               if (button.data('isOpen')) {
-                       $icon.removeClass('icon-chevron-right').addClass('icon-chevron-down');
-               }
-               else {
-                       $icon.removeClass('icon-chevron-down').addClass('icon-chevron-right');
-               }
-       }
-};
-
-/**
- * Basic implementation for collapsible containers with AJAX support. Results for open
- * and closed state will be cached.
- * 
- * @param      string          className
- */
-WCF.Collapsible.Remote = Class.extend({
-       /**
-        * class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * list of active containers
-        * @var object
-        */
-       _containers: {},
-       
-       /**
-        * container meta data
-        * @var object
-        */
-       _containerData: {},
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * Initializes the controller for collapsible containers with AJAX support.
-        * 
-        * @param       string  className
-        */
-       init: function(className) {
-               this._className = className;
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               // initialize each container
-               this._init();
-               
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.Collapsible.Remote', $.proxy(this._init, this));
-       },
-       
-       /**
-        * Initializes a collapsible container.
-        * 
-        * @param       string          containerID
-        */
-       _init: function(containerID) {
-               this._getContainers().each($.proxy(function(index, container) {
-                       var $container = $(container);
-                       var $containerID = $container.wcfIdentify();
-                       
-                       if (this._containers[$containerID] === undefined) {
-                               this._containers[$containerID] = $container;
-                               
-                               this._initContainer($containerID);
-                       }
-               }, this));
-       },
-       
-       /**
-        * Initializes a collapsible container.
-        * 
-        * @param       string          containerID
-        */
-       _initContainer: function(containerID) {
-               var $target = this._getTarget(containerID);
-               var $buttonContainer = this._getButtonContainer(containerID);
-               var $button = this._createButton(containerID, $buttonContainer);
-               
-               // store container meta data
-               this._containerData[containerID] = {
-                       button: $button,
-                       buttonContainer: $buttonContainer,
-                       isOpen: this._containers[containerID].data('isOpen'),
-                       target: $target
-               };
-               
-               // add 'jsCollapsed' CSS class
-               if (!this._containers[containerID].data('isOpen')) {
-                       $('#' + containerID).addClass('jsCollapsed');
-               }
-       },
-       
-       /**
-        * Returns a collection of collapsible containers.
-        * 
-        * @return      jQuery
-        */
-       _getContainers: function() { },
-       
-       /**
-        * Returns the target element for current collapsible container.
-        * 
-        * @param       integer         containerID
-        * @return      jQuery
-        */
-       _getTarget: function(containerID) { },
-       
-       /**
-        * Returns the button container for current collapsible container.
-        * 
-        * @param       integer         containerID
-        * @return      jQuery
-        */
-       _getButtonContainer: function(containerID) { },
-       
-       /**
-        * Creates the toggle button.
-        * 
-        * @param       integer         containerID
-        * @param       jQuery          buttonContainer
-        */
-       _createButton: function(containerID, buttonContainer) {
-               var $isOpen = this._containers[containerID].data('isOpen');
-               var $button = $('<span class="collapsibleButton jsTooltip pointer icon icon16 icon-' + ($isOpen ? 'chevron-down' : 'chevron-right') + '" title="'+WCF.Language.get('wcf.global.button.collapsible')+'">').prependTo(buttonContainer);
-               $button.data('containerID', containerID).click($.proxy(this._toggleContainer, this));
-               
-               return $button;
-       },
-       
-       /**
-        * Toggles a container.
-        * 
-        * @param       object          event
-        */
-       _toggleContainer: function(event) {
-               var $button = $(event.currentTarget);
-               var $containerID = $button.data('containerID');
-               var $isOpen = this._containerData[$containerID].isOpen;
-               var $state = ($isOpen) ? 'open' : 'close';
-               var $newState = ($isOpen) ? 'close' : 'open';
-               
-               // fetch content state via AJAX
-               this._proxy.setOption('data', {
-                       actionName: 'loadContainer',
-                       className: this._className,
-                       interfaceName: 'wcf\\data\\ILoadableContainerAction',
-                       objectIDs: [ this._getObjectID($containerID) ],
-                       parameters: $.extend(true, {
-                               containerID: $containerID,
-                               currentState: $state,
-                               newState: $newState
-                       }, this._getAdditionalParameters($containerID))
-               });
-               this._proxy.sendRequest();
-               
-               // toogle 'jsCollapsed' CSS class
-               $('#' + $containerID).toggleClass('jsCollapsed');
-               
-               // set spinner for current button
-               // this._exchangeIcon($button);
-       },
-       
-       /**
-        * Exchanges button icon.
-        * 
-        * @param       jQuery          button
-        * @param       string          newIcon
-        */
-       _exchangeIcon: function(button, newIcon) {
-               newIcon = newIcon || 'spinner';
-               button.removeClass('icon-chevron-down icon-chevron-right icon-spinner').addClass('icon-' + newIcon);
-       },
-       
-       /**
-        * Returns the object id for current container.
-        * 
-        * @param       integer         containerID
-        * @return      integer
-        */
-       _getObjectID: function(containerID) {
-               return $('#' + containerID).data('objectID');
-       },
-       
-       /**
-        * Returns additional parameters.
-        * 
-        * @param       integer         containerID
-        * @return      object
-        */
-       _getAdditionalParameters: function(containerID) {
-               return {};
-       },
-       
-       /**
-        * Updates container content.
-        * 
-        * @param       integer         containerID
-        * @param       string          newContent
-        * @param       string          newState
-        */
-       _updateContent: function(containerID, newContent, newState) {
-               this._containerData[containerID].target.html(newContent);
-       },
-       
-       /**
-        * Sets content upon successfull AJAX request.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               // validate container id
-               if (!data.returnValues.containerID) return;
-               var $containerID = data.returnValues.containerID;
-               
-               // check if container id is known
-               if (!this._containers[$containerID]) return;
-               
-               // update content storage
-               this._containerData[$containerID].isOpen = (data.returnValues.isOpen) ? true : false;
-               var $newState = (data.returnValues.isOpen) ? 'open' : 'close';
-               
-               // update container content
-               this._updateContent($containerID, $.trim(data.returnValues.content), $newState);
-               
-               // update icon
-               this._exchangeIcon(this._containerData[$containerID].button, (data.returnValues.isOpen ? 'chevron-down' : 'chevron-right'));
-       }
-});
-
-/**
- * Basic implementation for collapsible containers with AJAX support. Requires collapsible
- * content to be available in DOM already, if you want to load content on the fly use
- * WCF.Collapsible.Remote instead.
- */
-WCF.Collapsible.SimpleRemote = WCF.Collapsible.Remote.extend({
-       /**
-        * Initializes an AJAX-based collapsible handler.
-        * 
-        * @param       string          className
-        */
-       init: function(className) {
-               this._super(className);
-               
-               // override settings for action proxy
-               this._proxy = new WCF.Action.Proxy({
-                       showLoadingOverlay: false
-               });
-       },
-       
-       /**
-        * @see WCF.Collapsible.Remote._initContainer()
-        */
-       _initContainer: function(containerID) {
-               this._super(containerID);
-               
-               // hide container on init if applicable
-               if (!this._containerData[containerID].isOpen) {
-                       this._containerData[containerID].target.hide();
-                       this._exchangeIcon(this._containerData[containerID].button, 'chevron-right');
-               }
-       },
-       
-       /**
-        * Toggles container visibility.
-        * 
-        * @param       object          event
-        */
-       _toggleContainer: function(event) {
-               var $button = $(event.currentTarget);
-               var $containerID = $button.data('containerID');
-               var $isOpen = this._containerData[$containerID].isOpen;
-               var $currentState = ($isOpen) ? 'open' : 'close';
-               var $newState = ($isOpen) ? 'close' : 'open';
-               
-               this._proxy.setOption('data', {
-                       actionName: 'toggleContainer',
-                       className: this._className,
-                       interfaceName: 'wcf\\data\\IToggleContainerAction',
-                       objectIDs: [ this._getObjectID($containerID) ],
-                       parameters: $.extend(true, {
-                               containerID: $containerID,
-                               currentState: $currentState,
-                               newState: $newState
-                       }, this._getAdditionalParameters($containerID))
-               });
-               this._proxy.sendRequest();
-               
-               // exchange icon
-               this._exchangeIcon(this._containerData[$containerID].button, ($newState === 'open' ? 'chevron-down' : 'chevron-right'));
-               
-               // toggle container
-               if ($newState === 'open') {
-                       this._containerData[$containerID].target.show();
-               }
-               else {
-                       this._containerData[$containerID].target.hide();
-               }
-               
-               // toogle 'jsCollapsed' CSS class
-               $('#' + $containerID).toggleClass('jsCollapsed');
-               
-               // update container data
-               this._containerData[$containerID].isOpen = ($newState === 'open' ? true : false);
-       }
-});
-
-/**
- * Provides collapsible sidebars with persistency support.
- */
-WCF.Collapsible.Sidebar = Class.extend({
-       /**
-        * trigger button object
-        * @var jQuery
-        */
-       _button: null,
-       
-       /**
-        * trigger button height
-        * @var integer
-        */
-       _buttonHeight: 0,
-       
-       /**
-        * sidebar state
-        * @var boolean
-        */
-       _isOpen: false,
-       
-       /**
-        * main container object
-        * @var jQuery
-        */
-       _mainContainer: null,
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * sidebar object
-        * @var jQuery
-        */
-       _sidebar: null,
-       
-       /**
-        * sidebar height
-        * @var integer
-        */
-       _sidebarHeight: 0,
-       
-       /**
-        * sidebar identifier
-        * @var string
-        */
-       _sidebarName: '',
-       
-       /**
-        * sidebar offset from document top
-        * @var integer
-        */
-       _sidebarOffset: 0,
-       
-       /**
-        * user panel height
-        * @var integer
-        */
-       _userPanelHeight: 0,
-       
-       /**
-        * Creates a new WCF.Collapsible.Sidebar object.
-        */
-       init: function() {
-               this._sidebar = $('.sidebar:eq(0)');
-               if (!this._sidebar.length) {
-                       console.debug("[WCF.Collapsible.Sidebar] Could not find sidebar, aborting.");
-                       return;
-               }
-               
-               this._isOpen = (this._sidebar.data('isOpen')) ? true : false;
-               this._sidebarName = this._sidebar.data('sidebarName');
-               this._mainContainer = $('#main');
-               this._sidebarHeight = this._sidebar.height();
-               this._sidebarOffset = this._sidebar.getOffsets('offset').top;
-               this._userPanelHeight = $('#topMenu').outerHeight();
-               
-               // add toggle button
-               this._button = $('<a class="collapsibleButton jsTooltip" title="' + WCF.Language.get('wcf.global.button.collapsible') + '" />').prependTo(this._sidebar);
-               this._button.wrap('<span />');
-               this._button.click($.proxy(this._click, this));
-               this._buttonHeight = this._button.outerHeight();
-               
-               WCF.DOMNodeInsertedHandler.execute();
-               
-               this._proxy = new WCF.Action.Proxy({
-                       showLoadingOverlay: false,
-                       url: 'index.php/AJAXInvoke/?t=' + SECURITY_TOKEN + SID_ARG_2ND
-               });
-               
-               $(document).scroll($.proxy(this._scroll, this)).resize($.proxy(this._scroll, this));
-               
-               this._renderSidebar();
-               this._scroll();
-               
-               // fake resize event once transition has completed
-               var $window = $(window);
-               this._sidebar.on('webkitTransitionEnd transitionend msTransitionEnd oTransitionEnd', function() { $window.trigger('resize'); });
-       },
-       
-       /**
-        * Handles clicks on the trigger button.
-        */
-       _click: function() {
-               this._isOpen = (this._isOpen) ? false : true;
-               
-               this._proxy.setOption('data', {
-                       actionName: 'toggle',
-                       className: 'wcf\\system\\user\\collapsible\\content\\UserCollapsibleSidebarHandler',
-                       isOpen: (this._isOpen ? 1 : 0),
-                       sidebarName: this._sidebarName
-               });
-               this._proxy.sendRequest();
-               
-               this._renderSidebar();
-       },
-       
-       /**
-        * Aligns the toggle button upon scroll or resize.
-        */
-       _scroll: function() {
-               var $window = $(window);
-               var $scrollOffset = $window.scrollTop();
-               
-               // calculate top and bottom coordinates of visible sidebar
-               var $topOffset = Math.max($scrollOffset - this._sidebarOffset, 0);
-               var $bottomOffset = Math.min(this._mainContainer.height(), ($window.height() + $scrollOffset) - this._sidebarOffset);
-               
-               var $buttonTop = 0;
-               if ($bottomOffset === $topOffset) {
-                       // sidebar not within visible area
-                       $buttonTop = this._sidebarOffset + this._sidebarHeight;
-               }
-               else {
-                       $buttonTop = $topOffset + (($bottomOffset - $topOffset) / 2);
-                       
-                       // if the user panel is above the sidebar, substract it's height
-                       var $overlap = Math.max(Math.min($topOffset - this._userPanelHeight, this._userPanelHeight), 0);
-                       if ($overlap > 0) {
-                               $buttonTop += ($overlap / 2);
-                       }
-               }
-               
-               // ensure the button does not exceed bottom boundaries
-               if (($bottomOffset - $topOffset - this._userPanelHeight) < this._buttonHeight) {
-                       $buttonTop = $buttonTop - this._buttonHeight;
-               }
-               else {
-                       // exclude half button height
-                       $buttonTop = Math.max($buttonTop - (this._buttonHeight / 2), 0);
-               }
-               
-               this._button.css({ top: $buttonTop + 'px' });
-               
-       },
-       
-       /**
-        * Renders the sidebar state.
-        */
-       _renderSidebar: function() {
-               if (this._isOpen) {
-                       $('.sidebarOrientationLeft, .sidebarOrientationRight').removeClass('sidebarCollapsed');
-               }
-               else {
-                       $('.sidebarOrientationLeft, .sidebarOrientationRight').addClass('sidebarCollapsed');
-               }
-               
-               // update button position
-               this._scroll();
-               
-               // IE9 does not support transitions, fire resize event manually
-               if ($.browser.msie && $.browser.version.indexOf('9') === 0) {
-                       $(window).trigger('resize');
-               }
-       }
-});
-
-/**
- * Holds userdata of the current user
- */
-WCF.User = {
-       /**
-        * id of the active user
-        * @var integer
-        */
-       userID: 0,
-       
-       /**
-        * name of the active user
-        * @var string
-        */
-       username: '',
-       
-       /**
-        * Initializes userdata
-        * 
-        * @param       integer userID
-        * @param       string  username
-        */
-       init: function(userID, username) {
-               this.userID = userID;
-               this.username = username;
-       }
-};
-
-/**
- * Namespace for effect-related functions.
- */
-WCF.Effect = {};
-
-/**
- * Scrolls to a specific element offset, optionally handling menu height.
- */
-WCF.Effect.Scroll = Class.extend({
-       /**
-        * Scrolls to a specific element offset.
-        * 
-        * @param       jQuery          element
-        * @param       boolean         excludeMenuHeight
-        * @param       boolean         disableAnimation
-        * @return      boolean
-        */
-       scrollTo: function(element, excludeMenuHeight, disableAnimation) {
-               if (!element.length) {
-                       return true;
-               }
-               
-               var $elementOffset = element.getOffsets('offset').top;
-               var $documentHeight = $(document).height();
-               var $windowHeight = $(window).height();
-               
-               // handles menu height
-               /*if (excludeMenuHeight) {
-                       $elementOffset = Math.max($elementOffset - $('#topMenu').outerHeight(), 0);
-               }*/
-               
-               if ($elementOffset > $documentHeight - $windowHeight) {
-                       $elementOffset = $documentHeight - $windowHeight;
-                       if ($elementOffset < 0) {
-                               $elementOffset = 0;
-                       }
-               }
-               
-               if (disableAnimation === true) {
-                       $('html,body').scrollTop($elementOffset);
-               }
-               else {
-                       $('html,body').animate({ scrollTop: $elementOffset }, 400, function (x, t, b, c, d) {
-                               return -c * ( ( t = t / d - 1 ) * t * t * t - 1) + b;
-                       });
-               }
-               
-               return false;
-       }
-});
-
-/**
- * Creates a smooth scroll effect.
- */
-WCF.Effect.SmoothScroll = WCF.Effect.Scroll.extend({
-       /**
-        * Initializes effect.
-        */
-       init: function() {
-               var self = this;
-               $(document).on('click', 'a[href$=#top],a[href$=#bottom]', function() {
-                       var $target = $(this.hash);
-                       self.scrollTo($target, true);
-                       
-                       return false;
-               });
-       }
-});
-
-/**
- * Creates the balloon tool-tip.
- */
-WCF.Effect.BalloonTooltip = Class.extend({
-       /**
-        * initialization state
-        * @var boolean
-        */
-       _didInit: false,
-       
-       /**
-        * tooltip element
-        * @var jQuery
-        */
-       _tooltip: null,
-       
-       /**
-        * cache viewport dimensions
-        * @var object
-        */
-       _viewportDimensions: { },
-       
-       /**
-        * Initializes tooltips.
-        */
-       init: function() {
-               if (jQuery.browser.mobile) return;
-               
-               if (!this._didInit) {
-                       // create empty div
-                       this._tooltip = $('<div id="balloonTooltip" class="balloonTooltip"><span id="balloonTooltipText"></span><span class="pointer"><span></span></span></div>').appendTo($('body')).hide();
-                       
-                       // get viewport dimensions
-                       this._updateViewportDimensions();
-                       
-                       // update viewport dimensions on resize
-                       $(window).resize($.proxy(this._updateViewportDimensions, this));
-                       
-                       // observe DOM changes
-                       WCF.DOMNodeInsertedHandler.addCallback('WCF.Effect.BalloonTooltip', $.proxy(this.init, this));
-                       
-                       this._didInit = true;
-               }
-               
-               // init elements
-               $('.jsTooltip').each($.proxy(this._initTooltip, this));
-       },
-       
-       /**
-        * Updates cached viewport dimensions.
-        */
-       _updateViewportDimensions: function() {
-               this._viewportDimensions = $(document).getDimensions();
-       },
-       
-       /**
-        * Initializes a tooltip element.
-        * 
-        * @param       integer         index
-        * @param       object          element
-        */
-       _initTooltip: function(index, element) {
-               var $element = $(element);
-               
-               if ($element.hasClass('jsTooltip')) {
-                       $element.removeClass('jsTooltip');
-                       var $title = $element.attr('title');
-                       
-                       // ignore empty elements
-                       if ($title !== '') {
-                               $element.data('tooltip', $title);
-                               $element.removeAttr('title');
-                               
-                               $element.hover(
-                                       $.proxy(this._mouseEnterHandler, this),
-                                       $.proxy(this._mouseLeaveHandler, this)
-                               );
-                               $element.click($.proxy(this._mouseLeaveHandler, this));
-                       }
-               }
-       },
-       
-       /**
-        * Shows tooltip on hover.
-        * 
-        * @param       object          event
-        */
-       _mouseEnterHandler: function(event) {
-               var $element = $(event.currentTarget);
-               
-               var $title = $element.attr('title');
-               if ($title && $title !== '') {
-                       $element.data('tooltip', $title);
-                       $element.removeAttr('title');
-               }
-               
-               // reset tooltip position
-               this._tooltip.css({
-                       top: "0px",
-                       left: "0px"
-               });
-               
-               // empty tooltip, skip
-               if (!$element.data('tooltip')) {
-                       this._tooltip.hide();
-                       return;
-               }
-               
-               // update text
-               this._tooltip.children('span:eq(0)').text($element.data('tooltip'));
-               
-               // get arrow
-               var $arrow = this._tooltip.find('.pointer');
-               
-               // get arrow width
-               this._tooltip.show();
-               var $arrowWidth = $arrow.outerWidth();
-               this._tooltip.hide();
-               
-               // calculate position
-               var $elementOffsets = $element.getOffsets('offset');
-               var $elementDimensions = $element.getDimensions('outer');
-               var $tooltipDimensions = this._tooltip.getDimensions('outer');
-               var $tooltipDimensionsInner = this._tooltip.getDimensions('inner');
-               
-               var $elementCenter = $elementOffsets.left + Math.ceil($elementDimensions.width / 2);
-               var $tooltipHalfWidth = Math.ceil($tooltipDimensions.width / 2);
-               
-               // determine alignment
-               var $alignment = 'center';
-               if (($elementCenter - $tooltipHalfWidth) < 5) {
-                       $alignment = 'left';
-               }
-               else if ((this._viewportDimensions.width - 5) < ($elementCenter + $tooltipHalfWidth)) {
-                       $alignment = 'right';
-               }
-               
-               // calculate top offset
-               if ($elementOffsets.top + $elementDimensions.height + $tooltipDimensions.height - $(document).scrollTop() < $(window).height()) {
-                       var $top = $elementOffsets.top + $elementDimensions.height + 7;
-                       this._tooltip.removeClass('inverse');
-                       $arrow.css('top', -5);
-               }
-               else {
-                       var $top = $elementOffsets.top - $tooltipDimensions.height - 7;
-                       this._tooltip.addClass('inverse');
-                       $arrow.css('top', $tooltipDimensions.height);
-               }
-               
-               // calculate left offset
-               switch ($alignment) {
-                       case 'center':
-                               var $left = Math.round($elementOffsets.left - $tooltipHalfWidth + ($elementDimensions.width / 2));
-                               
-                               $arrow.css({
-                                       left: ($tooltipDimensionsInner.width / 2 - $arrowWidth / 2) + "px"
-                               });
-                       break;
-                       
-                       case 'left':
-                               var $left = $elementOffsets.left;
-                               
-                               $arrow.css({
-                                       left: "5px"
-                               });
-                       break;
-                       
-                       case 'right':
-                               var $left = $elementOffsets.left + $elementDimensions.width - $tooltipDimensions.width;
-                               
-                               $arrow.css({
-                                       left: ($tooltipDimensionsInner.width - $arrowWidth - 5) + "px"
-                               });
-                       break;
-               }
-               
-               // move tooltip
-               this._tooltip.css({
-                       top: $top + "px",
-                       left: $left + "px"
-               });
-               
-               // show tooltip
-               this._tooltip.wcfFadeIn();
-       },
-       
-       /**
-        * Hides tooltip once cursor left the element.
-        * 
-        * @param       object          event
-        */
-       _mouseLeaveHandler: function(event) {
-               this._tooltip.stop().hide().css({
-                       opacity: 1
-               });
-       }
-});
-
-/**
- * Handles clicks outside an overlay, hitting body-tag through bubbling.
- * 
- * You should always remove callbacks before disposing the attached element,
- * preventing errors from blocking the iteration. Furthermore you should
- * always handle clicks on your overlay's container and return 'false' to
- * prevent bubbling.
- */
-WCF.CloseOverlayHandler = {
-       /**
-        * list of callbacks
-        * @var WCF.Dictionary
-        */
-       _callbacks: new WCF.Dictionary(),
-       
-       /**
-        * indicates that overlay handler is listening to click events on body-tag
-        * @var boolean
-        */
-       _isListening: false,
-       
-       /**
-        * Adds a new callback.
-        * 
-        * @param       string          identifier
-        * @param       object          callback
-        */
-       addCallback: function(identifier, callback) {
-               this._bindListener();
-               
-               if (this._callbacks.isset(identifier)) {
-                       console.debug("[WCF.CloseOverlayHandler] identifier '" + identifier + "' is already bound to a callback");
-                       return false;
-               }
-               
-               this._callbacks.add(identifier, callback);
-       },
-       
-       /**
-        * Removes a callback from list.
-        * 
-        * @param       string          identifier
-        */
-       removeCallback: function(identifier) {
-               if (this._callbacks.isset(identifier)) {
-                       this._callbacks.remove(identifier);
-               }
-       },
-       
-       /**
-        * Binds click event handler.
-        */
-       _bindListener: function() {
-               if (this._isListening) return;
-               
-               $('body').click($.proxy(this._executeCallbacks, this));
-               
-               this._isListening = true;
-       },
-       
-       /**
-        * Executes callbacks on click.
-        */
-       _executeCallbacks: function(event) {
-               this._callbacks.each(function(pair) {
-                       // execute callback
-                       pair.value();
-               });
-       }
-};
-
-/**
- * Notifies objects once a DOM node was inserted.
- */
-WCF.DOMNodeInsertedHandler = {
-       /**
-        * list of callbacks
-        * @var array<object>
-        */
-       _callbacks: [ ],
-       
-       /**
-        * prevent infinite loop if a callback manipulates DOM
-        * @var boolean
-        */
-       _isExecuting: false,
-       
-       /**
-        * Adds a new callback.
-        * 
-        * @param       string          identifier
-        * @param       object          callback
-        */
-       addCallback: function(identifier, callback) {
-               this._callbacks.push(callback);
-       },
-       
-       /**
-        * Executes callbacks on click.
-        */
-       _executeCallbacks: function() {
-               if (this._isExecuting) return;
-               
-               // do not track events while executing callbacks
-               this._isExecuting = true;
-               
-               for (var $i = 0, $length = this._callbacks.length; $i < $length; $i++) {
-                       this._callbacks[$i]();
-               }
-               
-               // enable listener again
-               this._isExecuting = false;
-       },
-       
-       /**
-        * Executes all callbacks.
-        */
-       execute: function() {
-               this._executeCallbacks();
-       }
-};
-
-/**
- * Notifies objects once a DOM node was removed.
- */
-WCF.DOMNodeRemovedHandler = {
-       /**
-        * list of callbacks
-        * @var WCF.Dictionary
-        */
-       _callbacks: new WCF.Dictionary(),
-       
-       /**
-        * prevent infinite loop if a callback manipulates DOM
-        * @var boolean
-        */
-       _isExecuting: false,
-       
-       /**
-        * indicates that overlay handler is listening to DOMNodeRemoved events on body-tag
-        * @var boolean
-        */
-       _isListening: false,
-       
-       /**
-        * Adds a new callback.
-        * 
-        * @param       string          identifier
-        * @param       object          callback
-        */
-       addCallback: function(identifier, callback) {
-               this._bindListener();
-               
-               if (this._callbacks.isset(identifier)) {
-                       console.debug("[WCF.DOMNodeRemovedHandler] identifier '" + identifier + "' is already bound to a callback");
-                       return false;
-               }
-               
-               this._callbacks.add(identifier, callback);
-       },
-       
-       /**
-        * Removes a callback from list.
-        * 
-        * @param       string          identifier
-        */
-       removeCallback: function(identifier) {
-               if (this._callbacks.isset(identifier)) {
-                       this._callbacks.remove(identifier);
-               }
-       },
-       
-       /**
-        * Binds click event handler.
-        */
-       _bindListener: function() {
-               if (this._isListening) return;
-               
-               $(document).bind('DOMNodeRemoved', $.proxy(this._executeCallbacks, this));
-               
-               this._isListening = true;
-       },
-       
-       /**
-        * Executes callbacks if a DOM node is removed.
-        */
-       _executeCallbacks: function(event) {
-               if (this._isExecuting) return;
-               
-               // do not track events while executing callbacks
-               this._isExecuting = true;
-               
-               this._callbacks.each(function(pair) {
-                       // execute callback
-                       pair.value(event);
-               });
-               
-               // enable listener again
-               this._isExecuting = false;
-       }
-};
-
-WCF.PageVisibilityHandler = {
-       /**
-        * list of callbacks
-        * @var WCF.Dictionary
-        */
-       _callbacks: new WCF.Dictionary(),
-       
-       /**
-        * indicates that event listeners are bound
-        * @var boolean
-        */
-       _isListening: false,
-       
-       /**
-        * name of window's hidden property
-        * @var string
-        */
-       _hiddenFieldName: '',
-       
-       /**
-        * Adds a new callback.
-        * 
-        * @param       string          identifier
-        * @param       object          callback
-        */
-       addCallback: function(identifier, callback) {
-               this._bindListener();
-               
-               if (this._callbacks.isset(identifier)) {
-                       console.debug("[WCF.PageVisibilityHandler] identifier '" + identifier + "' is already bound to a callback");
-                       return false;
-               }
-               
-               this._callbacks.add(identifier, callback);
-       },
-       
-       /**
-        * Removes a callback from list.
-        * 
-        * @param       string          identifier
-        */
-       removeCallback: function(identifier) {
-               if (this._callbacks.isset(identifier)) {
-                       this._callbacks.remove(identifier);
-               }
-       },
-       
-       /**
-        * Binds click event handler.
-        */
-       _bindListener: function() {
-               if (this._isListening) return;
-               
-               var $eventName = null;
-               if (typeof document.hidden !== "undefined") {
-                       this._hiddenFieldName = "hidden";
-                       $eventName = "visibilitychange";
-               }
-               else if (typeof document.mozHidden !== "undefined") {
-                       this._hiddenFieldName = "mozHidden";
-                       $eventName = "mozvisibilitychange";
-               }
-               else if (typeof document.msHidden !== "undefined") {
-                       this._hiddenFieldName = "msHidden";
-                       $eventName = "msvisibilitychange";
-               }
-               else if (typeof document.webkitHidden !== "undefined") {
-                       this._hiddenFieldName = "webkitHidden";
-                       $eventName = "webkitvisibilitychange";
-               }
-               
-               if ($eventName === null) {
-                       console.debug("[WCF.PageVisibilityHandler] This browser does not support the page visibility API.");
-               }
-               else {
-                       $(document).on($eventName, $.proxy(this._executeCallbacks, this));
-               }
-               
-               this._isListening = true;
-       },
-       
-       /**
-        * Executes callbacks if page is hidden/visible again.
-        */
-       _executeCallbacks: function(event) {
-               if (this._isExecuting) return;
-               
-               // do not track events while executing callbacks
-               this._isExecuting = true;
-               
-               var $state = document[this._hiddenFieldName];
-               this._callbacks.each(function(pair) {
-                       // execute callback
-                       pair.value($state);
-               });
-               
-               // enable listener again
-               this._isExecuting = false;
-       }
-};
-
-/**
- * Namespace for table related classes.
- */
-WCF.Table = {};
-
-/**
- * Handles empty tables which can be used in combination with WCF.Action.Proxy.
- */
-WCF.Table.EmptyTableHandler = Class.extend({
-       /**
-        * handler options
-        * @var object
-        */
-       _options: {},
-       
-       /**
-        * class name of the relevant rows
-        * @var string
-        */
-       _rowClassName: '',
-       
-       /**
-        * Initalizes a new WCF.Table.EmptyTableHandler object.
-        * 
-        * @param       jQuery          tableContainer
-        * @param       string          rowClassName
-        * @param       object          options
-        */
-       init: function(tableContainer, rowClassName, options) {
-               this._rowClassName = rowClassName;
-               this._tableContainer = tableContainer;
-               
-               this._options = $.extend(true, {
-                       emptyMessage: null,
-                       messageType: 'info',
-                       refreshPage: false,
-                       updatePageNumber: false
-               }, options || { });
-               
-               WCF.DOMNodeRemovedHandler.addCallback('WCF.Table.EmptyTableHandler.' + rowClassName, $.proxy(this._remove, this));
-       },
-       
-       /**
-        * Handles the removal of a DOM node.
-        */
-       _remove: function(event) {
-               var element = $(event.target);
-               
-               // check if DOM element is relevant
-               if (element.hasClass(this._rowClassName)) {
-                       var tbody = element.parents('tbody:eq(0)');
-                       
-                       // check if table will be empty if DOM node is removed
-                       if (tbody.children('tr').length == 1) {
-                               if (this._options.emptyMessage) {
-                                       // insert message
-                                       this._tableContainer.replaceWith($('<p />').addClass(this._options.messageType).text(this._options.emptyMessage));
-                               }
-                               else if (this._options.refreshPage) {
-                                       // refresh page
-                                       if (this._options.updatePageNumber) {
-                                               // calculate the new page number
-                                               var pageNumberURLComponents = window.location.href.match(/(\?|&)pageNo=(\d+)/g);
-                                               if (pageNumberURLComponents) {
-                                                       var currentPageNumber = pageNumberURLComponents[pageNumberURLComponents.length - 1].match(/\d+/g);
-                                                       if (this._options.updatePageNumber > 0) {
-                                                               currentPageNumber++;
-                                                       }
-                                                       else {
-                                                               currentPageNumber--;
-                                                       }
-                                                       
-                                                       window.location = window.location.href.replace(pageNumberURLComponents[pageNumberURLComponents.length - 1], pageNumberURLComponents[pageNumberURLComponents.length - 1][0] + 'pageNo=' + currentPageNumber);
-                                               }
-                                       }
-                                       else {
-                                               window.location.reload();
-                                       }
-                               }
-                               else {
-                                       // simply remove the table container
-                                       this._tableContainer.remove();
-                               }
-                       }
-               }
-       }
-});
-
-/**
- * Namespace for search related classes.
- */
-WCF.Search = {};
-
-/**
- * Performs a quick search.
- */
-WCF.Search.Base = Class.extend({
-       /**
-        * notification callback
-        * @var object
-        */
-       _callback: null,
-       
-       /**
-        * class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * comma seperated list
-        * @var boolean
-        */
-       _commaSeperated: false,
-       
-       /**
-        * delay in miliseconds before a request is send to the server
-        * @var integer
-        */
-       _delay: 0,
-       
-       /**
-        * list with values that are excluded from seaching
-        * @var array
-        */
-       _excludedSearchValues: [],
-       
-       /**
-        * count of available results
-        * @var integer
-        */
-       _itemCount: 0,
-       
-       /**
-        * item index, -1 if none is selected
-        * @var integer
-        */
-       _itemIndex: -1,
-       
-       /**
-        * result list
-        * @var jQuery
-        */
-       _list: null,
-       
-       /**
-        * old search string, used for comparison
-        * @var array<string>
-        */
-       _oldSearchString: [ ],
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * search input field
-        * @var jQuery
-        */
-       _searchInput: null,
-       
-       /**
-        * minimum search input length, MUST be 1 or higher
-        * @var integer
-        */
-       _triggerLength: 3,
-       
-       /**
-        * delay timer
-        * @var WCF.PeriodicalExecuter
-        */
-       _timer: null,
-       
-       /**
-        * Initializes a new search.
-        * 
-        * @param       jQuery          searchInput
-        * @param       object          callback
-        * @param       array           excludedSearchValues
-        * @param       boolean         commaSeperated
-        * @param       boolean         showLoadingOverlay
-        */
-       init: function(searchInput, callback, excludedSearchValues, commaSeperated, showLoadingOverlay) {
-               if (callback !== null && callback !== undefined && !$.isFunction(callback)) {
-                       console.debug("[WCF.Search.Base] The given callback is invalid, aborting.");
-                       return;
-               }
-               
-               this._callback = (callback) ? callback : null;
-               this._delay = 0;
-               this._excludedSearchValues = [];
-               if (excludedSearchValues) {
-                       this._excludedSearchValues = excludedSearchValues;
-               }
-               
-               this._searchInput = $(searchInput);
-               if (!this._searchInput.length) {
-                       console.debug("[WCF.Search.Base] Selector '" + searchInput + "' for search input is invalid, aborting.");
-                       return;
-               }
-               
-               this._searchInput.keydown($.proxy(this._keyDown, this)).keyup($.proxy(this._keyUp, this)).wrap('<span class="dropdown" />');
-               
-               if ($.browser.mozilla && $.browser.touch) {
-                       this._searchInput.on('input', $.proxy(this._keyUp, this));
-               }
-               
-               this._list = $('<ul class="dropdownMenu" />').insertAfter(this._searchInput);
-               this._commaSeperated = (commaSeperated) ? true : false;
-               this._oldSearchString = [ ];
-               
-               this._itemCount = 0;
-               this._itemIndex = -1;
-               
-               this._proxy = new WCF.Action.Proxy({
-                       showLoadingOverlay: (showLoadingOverlay !== true ? false : true),
-                       success: $.proxy(this._success, this),
-                       autoAbortPrevious: true
-               });
-               
-               if (this._searchInput.is('input')) {
-                       this._searchInput.attr('autocomplete', 'off');
-               }
-               
-               this._searchInput.blur($.proxy(this._blur, this));
-               
-               WCF.Dropdown.initDropdownFragment(this._searchInput.parent(), this._list);
-       },
-       
-       /**
-        * Closes the dropdown after a short delay.
-        */
-       _blur: function() {
-               var self = this;
-               new WCF.PeriodicalExecuter(function(pe) {
-                       if (self._list.is(':visible')) {
-                               self._clearList(false);
-                       }
-                       
-                       pe.stop();
-               }, 250);
-       },
-       
-       /**
-        * Blocks execution of 'Enter' event.
-        * 
-        * @param       object          event
-        */
-       _keyDown: function(event) {
-               if (event.which === $.ui.keyCode.ENTER) {
-                       var $dropdown = this._searchInput.parents('.dropdown');
-                       
-                       if ($dropdown.data('disableAutoFocus')) {
-                               if (this._itemIndex !== -1) {
-                                       event.preventDefault();
-                               }
-                       }
-                       else if ($dropdown.data('preventSubmit') || this._itemIndex !== -1) {
-                               event.preventDefault();
-                       }
-               }
-       },
-       
-       /**
-        * Performs a search upon key up.
-        * 
-        * @param       object          event
-        */
-       _keyUp: function(event) {
-               // handle arrow keys and return key
-               switch (event.which) {
-                       case 37: // arrow-left
-                       case 39: // arrow-right
-                               return;
-                       break;
-                       
-                       case 38: // arrow up
-                               this._selectPreviousItem();
-                               return;
-                       break;
-                       
-                       case 40: // arrow down
-                               this._selectNextItem();
-                               return;
-                       break;
-                       
-                       case 13: // return key
-                               return this._selectElement(event);
-                       break;
-               }
-               
-               var $content = this._getSearchString(event);
-               if ($content === '') {
-                       this._clearList(true);
-               }
-               else if ($content.length >= this._triggerLength) {
-                       var $parameters = {
-                               data: {
-                                       excludedSearchValues: this._excludedSearchValues,
-                                       searchString: $content
-                               }
-                       };
-                       
-                       if (this._delay) {
-                               if (this._timer !== null) {
-                                       this._timer.stop();
-                               }
-                               
-                               var self = this;
-                               this._timer = new WCF.PeriodicalExecuter(function() {
-                                       self._queryServer($parameters);
-                                       
-                                       self._timer.stop();
-                                       self._timer = null;
-                               }, this._delay);
-                       }
-                       else {
-                               this._queryServer($parameters);
-                       }
-               }
-               else {
-                       // input below trigger length
-                       this._clearList(false);
-               }
-       },
-       
-       /**
-        * Queries the server.
-        * 
-        * @param       object          parameters
-        */
-       _queryServer: function(parameters) {
-               this._searchInput.parents('.searchBar').addClass('loading');
-               this._proxy.setOption('data', {
-                       actionName: 'getSearchResultList',
-                       className: this._className,
-                       interfaceName: 'wcf\\data\\ISearchAction',
-                       parameters: this._getParameters(parameters)
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Sets query delay in miliseconds.
-        * 
-        * @param       integer         delay
-        */
-       setDelay: function(delay) {
-               this._delay = delay;
-       },
-       
-       /**
-        * Selects the next item in list.
-        */
-       _selectNextItem: function() {
-               if (this._itemCount === 0) {
-                       return;
-               }
-               
-               // remove previous marking
-               this._itemIndex++;
-               if (this._itemIndex === this._itemCount) {
-                       this._itemIndex = 0;
-               }
-               
-               this._highlightSelectedElement();
-       },
-       
-       /**
-        * Selects the previous item in list.
-        */
-       _selectPreviousItem: function() {
-               if (this._itemCount === 0) {
-                       return;
-               }
-               
-               this._itemIndex--;
-               if (this._itemIndex === -1) {
-                       this._itemIndex = this._itemCount - 1;
-               }
-               
-               this._highlightSelectedElement();
-       },
-       
-       /**
-        * Highlights the active item.
-        */
-       _highlightSelectedElement: function() {
-               this._list.find('li').removeClass('dropdownNavigationItem');
-               this._list.find('li:eq(' + this._itemIndex + ')').addClass('dropdownNavigationItem');
-       },
-       
-       /**
-        * Selects the active item by pressing the return key.
-        * 
-        * @param       object          event
-        * @return      boolean
-        */
-       _selectElement: function(event) {
-               if (this._itemCount === 0) {
-                       return true;
-               }
-               
-               this._list.find('li.dropdownNavigationItem').trigger('click');
-               
-               return false;
-       },
-       
-       /**
-        * Returns search string.
-        * 
-        * @return      string
-        */
-       _getSearchString: function(event) {
-               var $searchString = $.trim(this._searchInput.val());
-               if (this._commaSeperated) {
-                       var $keyCode = event.keyCode || event.which;
-                       if ($keyCode == $.ui.keyCode.COMMA) {
-                               // ignore event if char is ','
-                               return '';
-                       }
-                       
-                       var $current = $searchString.split(',');
-                       var $length = $current.length;
-                       for (var $i = 0; $i < $length; $i++) {
-                               // remove whitespaces at the beginning or end
-                               $current[$i] = $.trim($current[$i]);
-                       }
-                       
-                       for (var $i = 0; $i < $length; $i++) {
-                               var $part = $current[$i];
-                               
-                               if (this._oldSearchString[$i]) {
-                                       // compare part
-                                       if ($part != this._oldSearchString[$i]) {
-                                               // current part was changed
-                                               $searchString = $part;
-                                               break;
-                                       }
-                               }
-                               else {
-                                       // new part was added
-                                       $searchString = $part;
-                                       break;
-                               }
-                       }
-                       
-                       this._oldSearchString = $current;
-               }
-               
-               return $searchString;
-       },
-       
-       /**
-        * Returns parameters for quick search.
-        * 
-        * @param       object          parameters
-        * @return      object
-        */
-       _getParameters: function(parameters) {
-               return parameters;
-       },
-       
-       /**
-        * Evalutes search results.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               this._clearList(false);
-               this._searchInput.parents('.searchBar').removeClass('loading');
-               
-               if ($.getLength(data.returnValues)) {
-                       for (var $i in data.returnValues) {
-                               var $item = data.returnValues[$i];
-                               
-                               this._createListItem($item);
-                       }
-               }
-               else if (!this._handleEmptyResult()) {
-                       return;
-               }
-               
-               WCF.CloseOverlayHandler.addCallback('WCF.Search.Base', $.proxy(function() { this._clearList(); }, this));
-               
-               var $containerID = this._searchInput.parents('.dropdown').wcfIdentify();
-               if (!WCF.Dropdown.getDropdownMenu($containerID).hasClass('dropdownOpen')) {
-                       WCF.Dropdown.toggleDropdown($containerID);
-               }
-               
-               // pre-select first item
-               this._itemIndex = -1;
-               if (!WCF.Dropdown.getDropdown($containerID).data('disableAutoFocus')) {
-                       this._selectNextItem();
-               }
-       },
-       
-       /**
-        * Handles empty result lists, should return false if dropdown should be hidden.
-        * 
-        * @return      boolean
-        */
-       _handleEmptyResult: function() {
-               return false;
-       },
-       
-       /**
-        * Creates a new list item.
-        * 
-        * @param       object          item
-        * @return      jQuery
-        */
-       _createListItem: function(item) {
-               var $listItem = $('<li><span>' + WCF.String.escapeHTML(item.label) + '</span></li>').appendTo(this._list);
-               $listItem.data('objectID', item.objectID).data('label', item.label).click($.proxy(this._executeCallback, this));
-               
-               this._itemCount++;
-               
-               return $listItem;
-       },
-       
-       /**
-        * Executes callback upon result click.
-        * 
-        * @param       object          event
-        */
-       _executeCallback: function(event) {
-               var $clearSearchInput = false;
-               var $listItem = $(event.currentTarget);
-               // notify callback
-               if (this._commaSeperated) {
-                       // auto-complete current part
-                       var $result = $listItem.data('label');
-                       for (var $i = 0, $length = this._oldSearchString.length; $i < $length; $i++) {
-                               var $part = this._oldSearchString[$i];
-                               if ($result.toLowerCase().indexOf($part.toLowerCase()) === 0) {
-                                       this._oldSearchString[$i] = $result;
-                                       this._searchInput.val(this._oldSearchString.join(', '));
-                                       
-                                       if ($.browser.webkit) {
-                                               // chrome won't display the new value until the textarea is rendered again
-                                               // this quick fix forces chrome to render it again, even though it changes nothing
-                                               this._searchInput.css({ display: 'block' });
-                                       }
-                                       
-                                       // set focus on input field again
-                                       var $position = this._searchInput.val().toLowerCase().indexOf($result.toLowerCase()) + $result.length;
-                                       this._searchInput.focus().setCaret($position);
-                                       
-                                       break;
-                               }
-                       }
-               }
-               else {
-                       if (this._callback === null) {
-                               this._searchInput.val($listItem.data('label'));
-                       }
-                       else {
-                               $clearSearchInput = (this._callback($listItem.data()) === true) ? true : false;
-                       }
-               }
-               
-               // close list and revert input
-               this._clearList($clearSearchInput);
-       },
-       
-       /**
-        * Closes the suggestion list and clears search input on demand.
-        * 
-        * @param       boolean         clearSearchInput
-        */
-       _clearList: function(clearSearchInput) {
-               if (clearSearchInput && !this._commaSeperated) {
-                       this._searchInput.val('');
-               }
-               
-               // close dropdown
-               WCF.Dropdown.getDropdown(this._searchInput.parents('.dropdown').wcfIdentify()).removeClass('dropdownOpen');
-               WCF.Dropdown.getDropdownMenu(this._searchInput.parents('.dropdown').wcfIdentify()).removeClass('dropdownOpen');
-               
-               this._list.end().empty();
-               
-               WCF.CloseOverlayHandler.removeCallback('WCF.Search.Base');
-               
-               // reset item navigation
-               this._itemCount = 0;
-               this._itemIndex = -1;
-       },
-       
-       /**
-        * Adds an excluded search value.
-        * 
-        * @param       string          value
-        */
-       addExcludedSearchValue: function(value) {
-               if (!WCF.inArray(value, this._excludedSearchValues)) {
-                       this._excludedSearchValues.push(value);
-               }
-       },
-       
-       /**
-        * Removes an excluded search value.
-        * 
-        * @param       string          value
-        */
-       removeExcludedSearchValue: function(value) {
-               var index = $.inArray(value, this._excludedSearchValues);
-               if (index != -1) {
-                       this._excludedSearchValues.splice(index, 1);
-               }
-       }
-});
-
-/**
- * Provides quick search for users and user groups.
- * 
- * @see        WCF.Search.Base
- */
-WCF.Search.User = WCF.Search.Base.extend({
-       /**
-        * @see WCF.Search.Base._className
-        */
-       _className: 'wcf\\data\\user\\UserAction',
-       
-       /**
-        * include user groups in search
-        * @var boolean
-        */
-       _includeUserGroups: false,
-       
-       /**
-        * @see WCF.Search.Base.init()
-        */
-       init: function(searchInput, callback, includeUserGroups, excludedSearchValues, commaSeperated) {
-               this._includeUserGroups = includeUserGroups;
-               
-               this._super(searchInput, callback, excludedSearchValues, commaSeperated);
-       },
-       
-       /**
-        * @see WCF.Search.Base._getParameters()
-        */
-       _getParameters: function(parameters) {
-               parameters.data.includeUserGroups = this._includeUserGroups ? 1 : 0;
-               
-               return parameters;
-       },
-       
-       /**
-        * @see WCF.Search.Base._createListItem()
-        */
-       _createListItem: function(item) {
-               var $listItem = this._super(item);
-               
-               var $icon = null;
-               if (item.icon) {
-                       $icon = $(item.icon);
-               }
-               else if (this._includeUserGroups && item.type === 'group') {
-                       $icon = $('<span class="icon icon16 icon-group" />');
-               }
-               
-               if ($icon) {
-                       var $label = $listItem.find('span').detach();
-                       
-                       var $box16 = $('<div />').addClass('box16').appendTo($listItem);
-                       
-                       $box16.append($icon);
-                       $box16.append($('<div />').append($label));
-               }
-               
-               // insert item type
-               $listItem.data('type', item.type);
-               
-               return $listItem;
-       }
-});
-
-/**
- * Namespace for system-related classes.
- */
-WCF.System = { };
-
-/**
- * Namespace for dependency-related classes.
- */
-WCF.System.Dependency = { };
-
-/**
- * JavaScript Dependency Manager.
- */
-WCF.System.Dependency.Manager = {
-       /**
-        * list of callbacks grouped by identifier
-        * @var object
-        */
-       _callbacks: { },
-       
-       /**
-        * list of loaded identifiers
-        * @var array<string>
-        */
-       _loaded: [ ],
-       
-       /**
-        * list of setup callbacks grouped by identifier
-        * @var object
-        */
-       _setupCallbacks: { },
-       
-       /**
-        * Registers a callback for given identifier, will be executed after all setup
-        * callbacks have been invoked.
-        * 
-        * @param       string          identifier
-        * @param       object          callback
-        */
-       register: function(identifier, callback) {
-               if (!$.isFunction(callback)) {
-                       console.debug("[WCF.System.Dependency.Manager] Callback for identifier '" + identifier + "' is invalid, aborting.");
-                       return;
-               }
-               
-               // already loaded, invoke now
-               if (WCF.inArray(identifier, this._loaded)) {
-                       setTimeout(function() {
-                               callback();
-                       }, 1);
-               }
-               else {
-                       if (!this._callbacks[identifier]) {
-                               this._callbacks[identifier] = [ ];
-                       }
-                       
-                       this._callbacks[identifier].push(callback);
-               }
-       },
-       
-       /**
-        * Registers a setup callback for given identifier, will be invoked
-        * prior to all other callbacks.
-        * 
-        * @param       string          identifier
-        * @param       object          callback
-        */
-       setup: function(identifier, callback) {
-               if (!$.isFunction(callback)) {
-                       console.debug("[WCF.System.Dependency.Manager] Setup callback for identifier '" + identifier + "' is invalid, aborting.");
-                       return;
-               }
-               
-               if (!this._setupCallbacks[identifier]) {
-                       this._setupCallbacks[identifier] = [ ];
-               }
-               
-               this._setupCallbacks[identifier].push(callback);
-       },
-       
-       /**
-        * Invokes all callbacks for given identifier and marks it as loaded.
-        * 
-        * @param       string          identifier
-        */
-       invoke: function(identifier) {
-               if (this._setupCallbacks[identifier]) {
-                       for (var $i = 0, $length = this._setupCallbacks[identifier].length; $i < $length; $i++) {
-                               this._setupCallbacks[identifier][$i]();
-                       }
-                       
-                       delete this._setupCallbacks[identifier];
-               }
-               
-               this._loaded.push(identifier);
-               
-               if (this._callbacks[identifier]) {
-                       for (var $i = 0, $length = this._callbacks[identifier].length; $i < $length; $i++) {
-                               this._callbacks[identifier][$i]();
-                       }
-                       
-                       delete this._callbacks[identifier];
-               }
-       }
-};
-
-/**
- * Provides flexible dropdowns for tab-based menus.
- */
-WCF.System.FlexibleMenu = {
-       /**
-        * list of containers
-        * @var object<jQuery>
-        */
-       _containers: { },
-       
-       /**
-        * list of registered container ids
-        * @var array<string>
-        */
-       _containerIDs: [ ],
-       
-       /**
-        * list of dropdowns
-        * @var object<jQuery>
-        */
-       _dropdowns: { },
-       
-       /**
-        * list of dropdown menus
-        * @var object<jQuery>
-        */
-       _dropdownMenus: { },
-       
-       /**
-        * list of hidden status for containers
-        * @var object<boolean>
-        */
-       _hasHiddenItems: { },
-       
-       /**
-        * true if menus are currently rebuilt
-        * @var boolean
-        */
-       _isWorking: false,
-       
-       /**
-        * list of tab menu items per container
-        * @var object<jQuery>
-        */
-       _menuItems: { },
-       
-       /**
-        * Initializes the WCF.System.FlexibleMenu class.
-        */
-       init: function() {
-               // register .mainMenu and .navigationHeader by default
-               this.registerMenu('mainMenu');
-               this.registerMenu($('.navigationHeader:eq(0)').wcfIdentify());
-               
-               this._registerTabMenus();
-               
-               $(window).resize($.proxy(this.rebuildAll, this));
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.System.FlexibleMenu', $.proxy(this._registerTabMenus, this));
-       },
-       
-       /**
-        * Registers tab menus.
-        */
-       _registerTabMenus: function() {
-               // register tab menus
-               $('.tabMenuContainer:not(.jsFlexibleMenuEnabled)').each(function(index, tabMenuContainer) {
-                       var $navigation = $(tabMenuContainer).children('nav');
-                       if ($navigation.length && $navigation.find('> ul:eq(0) > li').length) {
-                               WCF.System.FlexibleMenu.registerMenu($navigation.wcfIdentify());
-                       }
-               });
-       },
-       
-       /**
-        * Registers a tab-based menu by id.
-        * 
-        * Required DOM:
-        * <container>
-        *      <ul style="white-space: nowrap">
-        *              <li>tab 1</li>
-        *              <li>tab 2</li>
-        *              ...
-        *              <li>tab n</li>
-        *      </ul>
-        * </container>
-        * 
-        * @param       string          containerID
-        */
-       registerMenu: function(containerID) {
-               var $container = $('#' + containerID);
-               if (!$container.length) {
-                       console.debug("[WCF.System.FlexibleMenu] Unable to find container identified by '" + containerID + "', aborting.");
-                       return;
-               }
-               
-               this._containerIDs.push(containerID);
-               this._containers[containerID] = $container;
-               this._menuItems[containerID] = $container.find('> ul:eq(0) > li');
-               this._dropdowns[containerID] = $('<li class="dropdown"><a class="icon icon16 icon-list" /></li>').data('containerID', containerID).click($.proxy(this._click, this));
-               this._dropdownMenus[containerID] = $('<ul class="dropdownMenu" />').appendTo(this._dropdowns[containerID]);
-               this._hasHiddenItems[containerID] = false;
-               
-               this.rebuild(containerID);
-               
-               WCF.Dropdown.initDropdown(this._dropdowns[containerID].children('a'));
-       },
-       
-       /**
-        * Rebuilds all registered containers.
-        */
-       rebuildAll: function() {
-               if (this._isWorking) {
-                       return;
-               }
-               
-               this._isWorking = true;
-               
-               for (var $i = 0, $length = this._containerIDs.length; $i < $length; $i++) {
-                       this.rebuild(this._containerIDs[$i]);
-               }
-               
-               this._isWorking = false;
-       },
-       
-       /**
-        * Rebuilds a container, will be automatically invoked on window resize and registering.
-        * 
-        * @param       string          containerID
-        */
-       rebuild: function(containerID) {
-               if (!this._containers[containerID]) {
-                       console.debug("[WCF.System.FlexibleMenu] Cannot rebuild unknown container identified by '" + containerID + "'");
-                       return;
-               }
-               
-               var $changedItems = false;
-               var $container = this._containers[containerID];
-               var $currentWidth = 0;
-               
-               // the current width is based upon all items without the dropdown
-               var $menuItems = this._menuItems[containerID].filter(':visible');
-               for (var $i = 0, $length = $menuItems.length; $i < $length; $i++) {
-                       $currentWidth += $($menuItems[$i]).outerWidth(true);
-               }
-               
-               // insert dropdown for calculation purposes
-               if (!this._hasHiddenItems[containerID]) {
-                       this._dropdowns[containerID].appendTo($container.children('ul:eq(0)'));
-               }
-               
-               var $dropdownWidth = this._dropdowns[containerID].outerWidth(true);
-               
-               // remove dropdown previously inserted
-               if (!this._hasHiddenItems[containerID]) {
-                       this._dropdowns[containerID].detach();
-               }
-               
-               var $maximumWidth = $container.parent().innerWidth();
-               
-               // substract padding from the parent element
-               $maximumWidth -= parseInt($container.parent().css('padding-left').replace(/px$/, '')) + parseInt($container.parent().css('padding-right').replace(/px$/, ''));
-               
-               // substract margins and paddings from the container itself
-               $maximumWidth -= parseInt($container.css('margin-left').replace(/px$/, '')) + parseInt($container.css('margin-right').replace(/px$/, ''));
-               $maximumWidth -= parseInt($container.css('padding-left').replace(/px$/, '')) + parseInt($container.css('padding-right').replace(/px$/, ''));
-               
-               // substract paddings from the actual list
-               $maximumWidth -= parseInt($container.children('ul:eq(0)').css('padding-left').replace(/px$/, '')) + parseInt($container.children('ul:eq(0)').css('padding-right').replace(/px$/, '')); 
-               if ($currentWidth > $maximumWidth || (this._hasHiddenItems[containerID] && ($currentWidth > $maximumWidth - $dropdownWidth))) {
-                       var $menuItems = $menuItems.filter(':not(.active):not(.ui-state-active):visible');
-                       
-                       // substract dropdown width from maximum width
-                       $maximumWidth -= $dropdownWidth;
-                       
-                       // hide items starting with the last in list (ignores active item)
-                       for (var $i = ($menuItems.length - 1); $i >= 0; $i--) {
-                               if ($currentWidth > $maximumWidth) {
-                                       var $item = $($menuItems[$i]);
-                                       $currentWidth -= $item.outerWidth(true);
-                                       $item.hide();
-                                       
-                                       $changedItems = true;
-                                       this._hasHiddenItems[containerID] = true;
-                               }
-                               else {
-                                       break;
-                               }
-                       }
-                       
-                       if (this._hasHiddenItems[containerID]) {
-                               this._dropdowns[containerID].appendTo($container.children('ul:eq(0)'));
-                       }
-               }
-               else if (this._hasHiddenItems[containerID] && $currentWidth < $maximumWidth) {
-                       var $hiddenItems = this._menuItems[containerID].filter(':not(:visible)');
-                       
-                       // substract dropdown width from maximum width unless it is the last item
-                       $maximumWidth -= $dropdownWidth;
-                       
-                       // reverts items starting with the first hidden one
-                       for (var $i = 0, $length = $hiddenItems.length; $i < $length; $i++) {
-                               var $item = $($hiddenItems[$i]);
-                               $currentWidth += $item.outerWidth();
-                               
-                               if ($i + 1 == $length) {
-                                       $maximumWidth += $dropdownWidth;
-                               }
-                               
-                               if ($currentWidth < $maximumWidth) {
-                                       // enough space, show item
-                                       $item.css('display', '');
-                                       $changedItems = true;
-                               }
-                               else {
-                                       break;
-                               }
-                       }
-                       
-                       if ($changedItems) {
-                               this._hasHiddenItems[containerID] = (this._menuItems[containerID].filter(':not(:visible)').length > 0);
-                               if (!this._hasHiddenItems[containerID]) {
-                                       this._dropdowns[containerID].detach();
-                               }
-                       }
-               }
-               
-               // build dropdown menu for hidden items
-               if ($changedItems) {
-                       this._dropdownMenus[containerID].empty();
-                       this._menuItems[containerID].filter(':not(:visible)').each($.proxy(function(index, item) {
-                               $('<li>' + $(item).html() + '</li>').appendTo(this._dropdownMenus[containerID]);
-                       }, this));
-               }
-       }
-};
-
-/**
- * Namespace for mobile device-related classes.
- */
-WCF.System.Mobile = { };
-
-/**
- * Handles general navigation and UX on mobile devices.
- */
-WCF.System.Mobile.UX = {
-       /**
-        * true if mobile optimizations are enabled
-        * @var boolean
-        */
-       _enabled: false,
-       
-       /**
-        * main container
-        * @var jQuery
-        */
-       _main: null,
-       
-       /**
-        * sidebar container
-        * @var jQuery
-        */
-       _sidebar: null,
-       
-       /**
-        * Initializes the WCF.System.Mobile.UX class.
-        */
-       init: function() {
-               this._enabled = false;
-               this._main = $('#main');
-               this._sidebar = this._main.find('> div > div > .sidebar');
-               
-               if ($.browser.touch) {
-                       $('html').addClass('touch');
-               }
-               
-               enquire.register('screen and (max-width: 800px)', {
-                       match: $.proxy(this._enable, this),
-                       unmatch: $.proxy(this._disable, this),
-                       setup: $.proxy(this._setup, this),
-                       deferSetup: true
-               });
-               
-               if ($.browser.msie && this._sidebar.width() > 305) {
-                       // sidebar is rarely broken on IE9/IE10
-                       this._sidebar.css('display', 'none').css('display', '');
-               }
-       },
-       
-       /**
-        * Initializes the mobile optimization once the media query matches.
-        */
-       _setup: function() {
-               this._initSidebarToggleButtons();
-               this._initSearchBar();
-               this._initButtonGroupNavigation();
-               
-               WCF.CloseOverlayHandler.addCallback('WCF.System.Mobile.UX', $.proxy(this._closeMenus, this));
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.System.Mobile.UX', $.proxy(this._initButtonGroupNavigation, this));
-       },
-       
-       /**
-        * Enables the mobile optimization.
-        */
-       _enable: function() {
-               this._enabled = true;
-               
-               if ($.browser.msie) {
-                       this._sidebar.css('display', 'none').css('display', '');
-               }
-       },
-       
-       /**
-        * Disables the mobile optimization.
-        */
-       _disable: function() {
-               this._enabled = false;
-               
-               if ($.browser.msie) {
-                       this._sidebar.css('display', 'none').css('display', '');
-               }
-       },
-       
-       /**
-        * Initializes the sidebar toggle buttons.
-        */
-       _initSidebarToggleButtons: function() {
-               var $sidebarLeft = this._main.hasClass('sidebarOrientationLeft');
-               var $sidebarRight = this._main.hasClass('sidebarOrientationRight');
-               if ($sidebarLeft || $sidebarRight) {
-                       // use icons if language item is empty/non-existant
-                       var $languageShowSidebar = 'wcf.global.sidebar.show' + ($sidebarLeft ? 'Left' : 'Right') + 'Sidebar';
-                       if ($languageShowSidebar === WCF.Language.get($languageShowSidebar) || WCF.Language.get($languageShowSidebar) === '') {
-                               $languageShowSidebar = '<span class="icon icon16 icon-double-angle-' + ($sidebarLeft ? 'left' : 'right') + '" />';
-                       }
-                       
-                       var $languageHideSidebar = 'wcf.global.sidebar.hide' + ($sidebarLeft ? 'Left' : 'Right') + 'Sidebar';
-                       if ($languageHideSidebar === WCF.Language.get($languageHideSidebar) || WCF.Language.get($languageHideSidebar) === '') {
-                               $languageHideSidebar = '<span class="icon icon16 icon-double-angle-' + ($sidebarLeft ? 'right' : 'left') + '" />';
-                       }
-                       
-                       // add toggle buttons
-                       var self = this;
-                       $('<span class="button small mobileSidebarToggleButton">' + $languageShowSidebar + '</span>').appendTo($('.content')).click(function() { self._main.addClass('mobileShowSidebar'); });
-                       $('<span class="button small mobileSidebarToggleButton">' + $languageHideSidebar + '</span>').appendTo($('.sidebar')).click(function() { self._main.removeClass('mobileShowSidebar'); });
-               }
-       },
-       
-       /**
-        * Initializes the search bar.
-        */
-       _initSearchBar: function() {
-               var $searchBar = $('.searchBar:eq(0)');
-               
-               var self = this;
-               $searchBar.click(function() {
-                       if (self._enabled) {
-                               $searchBar.addClass('searchBarOpen');
-                       }
-               });
-               
-               this._main.click(function() { $searchBar.removeClass('searchBarOpen'); });
-       },
-       
-       /**
-        * Initializes the button group lists, converting them into native dropdowns.
-        */
-       _initButtonGroupNavigation: function() {
-               $('.buttonGroupNavigation:not(.jsMobileButtonGroupNavigation)').each(function(index, navigation) {
-                       var $navigation = $(navigation).addClass('jsMobileButtonGroupNavigation');
-                       var $button = $('<a class="dropdownLabel"><span class="icon icon24 icon-list" /></a>').prependTo($navigation);
-                       
-                       $button.click(function() { $button.next().toggleClass('open'); return false; });
-               });
-       },
-       
-       /**
-        * Closes menus.
-        */
-       _closeMenus: function() {
-               $('.jsMobileButtonGroupNavigation > ul.open').removeClass('open');
-       }
-};
-
-WCF.System.Page = { };
-
-WCF.System.Page.Multiple = Class.extend({
-       _cache: { },
-       _options: { },
-       _pageNo: 1,
-       _pages: 0,
-       
-       init: function(options) {
-               this._options = $.extend({
-                       // elements
-                       container: null,
-                       pagination: null,
-                       
-                       // callbacks
-                       loadItems: null
-               }, options);
-       },
-       
-       /**
-        * Callback after page has changed.
-        * 
-        * @param       object          event
-        * @param       object          data
-        */
-       _showPage: function(event, data) {
-               if (data && data.activePage) {
-                       if (!data.template) {
-                               this._previousPageNo = this._pageNo;
-                       }
-                       
-                       this._pageNo = data.activePage;
-               }
-               
-               if (this._cache[this._pageNo] || (data && data.template)) {
-                       this._cache[this._previousPageNo] = this._list.children().detach();
-                       
-                       if (data && data.template) {
-                               this._list.html(data.template);
-                       }
-                       else {
-                               this._list.append(this._cache[this._pageNo]);
-                       }
-               }
-               else {
-                       this._loadItems();
-               }
-       },
-       
-       showPage: function(pageNo, template) {
-               this._showPage(null, {
-                       activePage: pageNo,
-                       template: template
-               });
-       }
-});
-
-/**
- * System notification overlays.
- * 
- * @param      string          message
- * @param      string          cssClassNames
- */
-WCF.System.Notification = Class.extend({
-       /**
-        * callback on notification close
-        * @var object
-        */
-       _callback: null,
-       
-       /**
-        * CSS class names
-        * @var string
-        */
-       _cssClassNames: '',
-       
-       /**
-        * notification message
-        * @var string
-        */
-       _message: '',
-       
-       /**
-        * notification overlay
-        * @var jQuery
-        */
-       _overlay: null,
-       
-       /**
-        * Creates a new system notification overlay.
-        * 
-        * @param       string          message
-        * @param       string          cssClassNames
-        */
-       init: function(message, cssClassNames) {
-               this._cssClassNames = cssClassNames || 'success';
-               this._message = message || WCF.Language.get('wcf.global.success');
-               this._overlay = $('#systemNotification');
-               
-               if (!this._overlay.length) {
-                       this._overlay = $('<div id="systemNotification"><p></p></div>').hide().appendTo(document.body);
-               }
-       },
-       
-       /**
-        * Shows the notification overlay.
-        * 
-        * @param       object          callback
-        * @param       integer         duration
-        * @param       string          message
-        * @param       string          cssClassName
-        */
-       show: function(callback, duration, message, cssClassNames) {
-               duration = parseInt(duration);
-               if (!duration) duration = 2000;
-               
-               if (callback && $.isFunction(callback)) {
-                       this._callback = callback;
-               }
-               
-               this._overlay.children('p').html((message || this._message));
-               this._overlay.children('p').removeClass().addClass((cssClassNames || this._cssClassNames));
-               
-               // hide overlay after specified duration
-               new WCF.PeriodicalExecuter($.proxy(this._hide, this), duration);
-               
-               this._overlay.wcfFadeIn(undefined, 300);
-       },
-       
-       /**
-        * Hides the notification overlay after executing the callback.
-        * 
-        * @param       WCF.PeriodicalExecuter          pe
-        */
-       _hide: function(pe) {
-               if (this._callback !== null) {
-                       this._callback();
-               }
-               
-               this._overlay.wcfFadeOut(undefined, 300);
-               
-               pe.stop();
-       }
-});
-
-/**
- * Provides dialog-based confirmations.
- */
-WCF.System.Confirmation = {
-       /**
-        * notification callback
-        * @var object
-        */
-       _callback: null,
-       
-       /**
-        * confirmation dialog
-        * @var jQuery
-        */
-       _dialog: null,
-       
-       /**
-        * callback parameters
-        * @var object
-        */
-       _parameters: null,
-       
-       /**
-        * dialog visibility
-        * @var boolean
-        */
-       _visible: false,
-       
-       /**
-        * confirmation button
-        * @var jQuery
-        */
-       _confirmationButton: null,
-       
-       /**
-        * Displays a confirmation dialog.
-        * 
-        * @param       string          message
-        * @param       object          callback
-        * @param       object          parameters
-        * @param       jQuery          template
-        */
-       show: function(message, callback, parameters, template) {
-               if (this._visible) {
-                       console.debug('[WCF.System.Confirmation] Confirmation dialog is already open, refusing action.');
-                       return;
-               }
-               
-               if (!$.isFunction(callback)) {
-                       console.debug('[WCF.System.Confirmation] Given callback is invalid, aborting.');
-                       return;
-               }
-               
-               this._callback = callback;
-               this._parameters = parameters;
-               
-               var $render = true;
-               if (this._dialog === null) {
-                       this._createDialog();
-                       $render = false;
-               }
-               
-               this._dialog.find('#wcfSystemConfirmationContent').empty().hide();
-               if (template && template.length) {
-                       template.appendTo(this._dialog.find('#wcfSystemConfirmationContent').show());
-               }
-               
-               this._dialog.find('p').text(message);
-               this._dialog.wcfDialog({
-                       onClose: $.proxy(this._close, this),
-                       onShow: $.proxy(this._show, this),
-                       title: WCF.Language.get('wcf.global.confirmation.title')
-               });
-               if ($render) {
-                       this._dialog.wcfDialog('render');
-               }
-               
-               this._confirmationButton.focus();
-               this._visible = true;
-       },
-       
-       /**
-        * Creates the confirmation dialog on first use.
-        */
-       _createDialog: function() {
-               this._dialog = $('<div id="wcfSystemConfirmation" class="systemConfirmation"><p /><div id="wcfSystemConfirmationContent" /></div>').hide().appendTo(document.body);
-               var $formButtons = $('<div class="formSubmit" />').appendTo(this._dialog);
-               
-               this._confirmationButton = $('<button class="buttonPrimary">' + WCF.Language.get('wcf.global.confirmation.confirm') + '</button>').data('action', 'confirm').click($.proxy(this._click, this)).appendTo($formButtons);
-               $('<button>' + WCF.Language.get('wcf.global.confirmation.cancel') + '</button>').data('action', 'cancel').click($.proxy(this._click, this)).appendTo($formButtons);
-       },
-       
-       /**
-        * Handles button clicks.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               this._notify($(event.currentTarget).data('action'));
-       },
-       
-       /**
-        * Handles dialog being closed.
-        */
-       _close: function() {
-               if (this._visible) {
-                       this._notify('cancel');
-               }
-       },
-       
-       /**
-        * Notifies callback upon user's decision.
-        * 
-        * @param       string          action
-        */
-       _notify: function(action) {
-               this._visible = false;
-               this._dialog.wcfDialog('close');
-               
-               this._callback(action, this._parameters);
-       },
-       
-       /**
-        * Tries to set focus on confirm button.
-        */
-       _show: function() {
-               this._dialog.find('button.buttonPrimary').blur().focus();
-       }
-};
-
-/**
- * Disables the ability to scroll the page.
- */
-WCF.System.DisableScrolling = {
-       /**
-        * number of times scrolling was disabled (nested calls)
-        * @var integer
-        */
-       _depth: 0,
-       
-       /**
-        * old overflow-value of the body element
-        * @var string
-        */
-       _oldOverflow: null,
-       
-       /**
-        * Disables scrolling.
-        */
-       disable: function () {
-               // do not block scrolling on touch devices
-               if ($.browser.touch) {
-                       return;
-               }
-               
-               if (this._depth === 0) {
-                       this._oldOverflow = $(document.body).css('overflow');
-                       $(document.body).css('overflow', 'hidden');
-               }
-               
-               this._depth++;
-       },
-       
-       /**
-        * Enables scrolling again.
-        * Must be called the same number of times disable() was called to enable scrolling.
-        */
-       enable: function () {
-               if (this._depth === 0) return;
-               
-               this._depth--;
-               
-               if (this._depth === 0) {
-                       $(document.body).css('overflow', this._oldOverflow);
-               }
-       }
-};
-
-/**
- * Provides the 'jump to page' overlay.
- */
-WCF.System.PageNavigation = {
-       /**
-        * submit button
-        * @var jQuery
-        */
-       _button: null,
-       
-       /**
-        * page No description
-        * @var jQuery
-        */
-       _description: null,
-       
-       /**
-        * dialog overlay
-        * @var jQuery
-        */
-       _dialog: null,
-       
-       /**
-        * active element id
-        * @var string
-        */
-       _elementID: '',
-       
-       /**
-        * list of tracked navigation bars
-        * @var object
-        */
-       _elements: { },
-       
-       /**
-        * page No input
-        * @var jQuery
-        */
-       _pageNo: null,
-       
-       /**
-        * Initializes the 'jump to page' overlay for given selector.
-        * 
-        * @param       string          selector
-        * @param       object          callback
-        */
-       init: function(selector, callback) {
-               var $elements = $(selector);
-               if (!$elements.length) {
-                       return;
-               }
-               
-               callback = callback || null;
-               if (callback !== null && !$.isFunction(callback)) {
-                       console.debug("[WCF.System.PageNavigation] Callback for selector '" + selector + "' is invalid, aborting.");
-                       return;
-               }
-               
-               this._initElements($elements, callback);
-       },
-       
-       /**
-        * Initializes the 'jump to page' overlay for given elements.
-        * 
-        * @param       jQuery          elements
-        * @param       object          callback
-        */
-       _initElements: function(elements, callback) {
-               var self = this;
-               elements.each(function(index, element) {
-                       var $element = $(element);
-                       var $elementID = $element.wcfIdentify();
-                       
-                       if (self._elements[$elementID] === undefined) {
-                               self._elements[$elementID] = $element;
-                               $element.find('li.jumpTo').data('elementID', $elementID).click($.proxy(self._click, self));
-                       }
-               }).data('callback', callback);
-       },
-       
-       /**
-        * Shows the 'jump to page' overlay.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               this._elementID = $(event.currentTarget).data('elementID');
-               
-               if (this._dialog === null) {
-                       this._dialog = $('<div id="pageNavigationOverlay" />').hide().appendTo(document.body);
-                       
-                       var $fieldset = $('<fieldset><legend>' + WCF.Language.get('wcf.global.page.jumpTo') + '</legend></fieldset>').appendTo(this._dialog);
-                       $('<dl><dt><label for="jsPageNavigationPageNo">' + WCF.Language.get('wcf.global.page.jumpTo') + '</label></dt><dd></dd></dl>').appendTo($fieldset);
-                       this._pageNo = $('<input type="number" id="jsPageNavigationPageNo" value="1" min="1" max="1" class="tiny" />').keyup($.proxy(this._keyUp, this)).appendTo($fieldset.find('dd'));
-                       this._description = $('<small></small>').insertAfter(this._pageNo);
-                       var $formSubmit = $('<div class="formSubmit" />').appendTo(this._dialog);
-                       this._button = $('<button class="buttonPrimary">' + WCF.Language.get('wcf.global.button.submit') + '</button>').click($.proxy(this._submit, this)).appendTo($formSubmit);
-               }
-               
-               this._button.enable();
-               this._description.html(WCF.Language.get('wcf.global.page.jumpTo.description').replace(/#pages#/, this._elements[this._elementID].data('pages')));
-               this._pageNo.val(this._elements[this._elementID].data('pages')).attr('max', this._elements[this._elementID].data('pages'));
-               
-               this._dialog.wcfDialog({
-                       'title': WCF.Language.get('wcf.global.page.pageNavigation')
-               });
-       },
-       
-       /**
-        * Validates the page No input.
-        *
-        * @param       Event           event
-        */
-       _keyUp: function(event) {
-               if (event.which == $.ui.keyCode.ENTER && !this._button.prop('disabled')) {
-                       this._submit();
-                       return;
-               }
-               
-               var $pageNo = parseInt(this._pageNo.val()) || 0;
-               if ($pageNo < 1 || $pageNo > this._pageNo.attr('max')) {
-                       this._button.disable();
-               }
-               else {
-                       this._button.enable();
-               }
-       },
-       
-       /**
-        * Redirects to given page No.
-        */
-       _submit: function() {
-               var $pageNavigation = this._elements[this._elementID];
-               if ($pageNavigation.data('callback') === null) {
-                       var $redirectURL = $pageNavigation.data('link').replace(/pageNo=%d/, 'pageNo=' + this._pageNo.val());
-                       window.location = $redirectURL;
-               }
-               else {
-                       $pageNavigation.data('callback')(this._pageNo.val());
-                       this._dialog.wcfDialog('close');
-               }
-       }
-};
-
-/**
- * Sends periodical requests to protect the session from expiring. By default
- * it will send a request 1 minute before it would expire.
- * 
- * @param      integer         seconds
- */
-WCF.System.KeepAlive = Class.extend({
-       /**
-        * Initializes the WCF.System.KeepAlive class.
-        * 
-        * @param       integer         seconds
-        */
-       init: function(seconds) {
-               new WCF.PeriodicalExecuter(function(pe) {
-                       new WCF.Action.Proxy({
-                               autoSend: true,
-                               data: {
-                                       actionName: 'keepAlive',
-                                       className: 'wcf\\data\\session\\SessionAction'
-                               },
-                               failure: function() { pe.stop(); },
-                               showLoadingOverlay: false,
-                               suppressErrors: true
-                       });
-               }, (seconds * 1000));
-       }
-});
-
-/**
- * Default implementation for inline editors.
- * 
- * @param      string          elementSelector
- */
-WCF.InlineEditor = Class.extend({
-       /**
-        * list of registered callbacks
-        * @var array<object>
-        */
-       _callbacks: [ ],
-       
-       /**
-        * list of dropdown selections
-        * @var object
-        */
-       _dropdowns: { },
-       
-       /**
-        * list of container elements
-        * @var object
-        */
-       _elements: { },
-       
-       /**
-        * notification object
-        * @var WCF.System.Notification
-        */
-       _notification: null,
-       
-       /**
-        * list of known options
-        * @var array<object>
-        */
-       _options: [ ],
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * list of data to update upon success
-        * @var array<object>
-        */
-       _updateData: [ ],
-       
-       /**
-        * Initializes a new inline editor.
-        */
-       init: function(elementSelector) {
-               var $elements = $(elementSelector);
-               if (!$elements.length) {
-                       return;
-               }
-               
-               this._setOptions();
-               var $quickOption = '';
-               for (var $i = 0, $length = this._options.length; $i < $length; $i++) {
-                       if (this._options[$i].isQuickOption) {
-                               $quickOption = this._options[$i].optionName;
-                               break;
-                       }
-               }
-               
-               var self = this;
-               $elements.each(function(index, element) {
-                       var $element = $(element);
-                       var $elementID = $element.wcfIdentify();
-                       
-                       // find trigger element
-                       var $trigger = self._getTriggerElement($element);
-                       if ($trigger === null || $trigger.length !== 1) {
-                               return;
-                       }
-                       
-                       $trigger.click($.proxy(self._show, self)).data('elementID', $elementID);
-                       if ($quickOption) {
-                               // simulate click on target action
-                               $trigger.disableSelection().data('optionName', $quickOption).dblclick($.proxy(self._click, self));
-                       }
-                       
-                       // store reference
-                       self._elements[$elementID] = $element;
-               });
-               
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               WCF.CloseOverlayHandler.addCallback('WCF.InlineEditor', $.proxy(this._closeAll, this));
-               
-               this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success'), 'success');
-       },
-       
-       /**
-        * Closes all inline editors.
-        */
-       _closeAll: function() {
-               for (var $elementID in this._elements) {
-                       this._hide($elementID);
-               }
-       },
-       
-       /**
-        * Sets options for this inline editor.
-        */
-       _setOptions: function() {
-               this._options = [ ];
-       },
-       
-       /**
-        * Register an option callback for validation and execution.
-        * 
-        * @param       object          callback
-        */
-       registerCallback: function(callback) {
-               if ($.isFunction(callback)) {
-                       this._callbacks.push(callback);
-               }
-       },
-       
-       /**
-        * Returns the triggering element.
-        * 
-        * @param       jQuery          element
-        * @return      jQuery
-        */
-       _getTriggerElement: function(element) {
-               return null;
-       },
-       
-       /**
-        * Shows a dropdown menu if options are available.
-        * 
-        * @param       object          event
-        */
-       _show: function(event) {
-               var $elementID = $(event.currentTarget).data('elementID');
-               
-               // build dropdown
-               var $trigger = null;
-               if (!this._dropdowns[$elementID]) {
-                       $trigger = this._getTriggerElement(this._elements[$elementID]).addClass('dropdownToggle').wrap('<span class="dropdown" />');
-                       this._dropdowns[$elementID] = $('<ul class="dropdownMenu" />').insertAfter($trigger);
-               }
-               this._dropdowns[$elementID].empty();
-               
-               // validate options
-               var $hasOptions = false;
-               var $lastElementType = '';
-               for (var $i = 0, $length = this._options.length; $i < $length; $i++) {
-                       var $option = this._options[$i];
-                       
-                       if ($option.optionName === 'divider') {
-                               if ($lastElementType !== '' && $lastElementType !== 'divider') {
-                                       $('<li class="dropdownDivider" />').appendTo(this._dropdowns[$elementID]);
-                                       $lastElementType = $option.optionName;
-                               }
-                       }
-                       else if (this._validate($elementID, $option.optionName) || this._validateCallbacks($elementID, $option.optionName)) {
-                               var $listItem = $('<li><span>' + $option.label + '</span></li>').appendTo(this._dropdowns[$elementID]);
-                               $listItem.data('elementID', $elementID).data('optionName', $option.optionName).data('isQuickOption', ($option.isQuickOption ? true : false)).click($.proxy(this._click, this));
-                               
-                               $hasOptions = true;
-                               $lastElementType = $option.optionName;
-                       }
-               }
-               
-               if ($hasOptions) {
-                       // if last child is divider, remove it
-                       var $lastChild = this._dropdowns[$elementID].children().last();
-                       if ($lastChild.hasClass('dropdownDivider')) {
-                               $lastChild.remove();
-                       }
-                       
-                       // check if only element is a quick option
-                       var $quickOption = null;
-                       var $count = 0;
-                       this._dropdowns[$elementID].children().each(function(index, child) {
-                               var $child = $(child);
-                               if (!$child.hasClass('dropdownDivider')) {
-                                       if ($child.data('isQuickOption')) {
-                                               $quickOption = $child;
-                                       }
-                                       else {
-                                               $count++;
-                                       }
-                               }
-                       });
-                       
-                       if (!$count) {
-                               $quickOption.trigger('click');
-                               
-                               if ($trigger !== null) {
-                                       WCF.Dropdown.close($trigger.parents('.dropdown').wcfIdentify());
-                               }
-                               
-                               return false;
-                       }
-               }
-               
-               if ($trigger !== null) {
-                       WCF.Dropdown.initDropdown($trigger, true);
-               }
-               
-               return false;
-       },
-       
-       /**
-        * Validates an option.
-        * 
-        * @param       string          elementID
-        * @param       string          optionName
-        * @returns     boolean
-        */
-       _validate: function(elementID, optionName) {
-               return false;
-       },
-       
-       /**
-        * Validates an option provided by callbacks.
-        * 
-        * @param       string          elementID
-        * @param       string          optionName
-        * @return      boolean
-        */
-       _validateCallbacks: function(elementID, optionName) {
-               var $length = this._callbacks.length;
-               if ($length) {
-                       for (var $i = 0; $i < $length; $i++) {
-                               if (this._callbacks[$i].validate(this._elements[elementID], optionName)) {
-                                       return true;
-                               }
-                       }
-               }
-               
-               return false;
-       },
-       
-       /**
-        * Handles AJAX responses.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               var $length = this._updateData.length;
-               if (!$length) {
-                       return;
-               }
-               
-               this._updateState(data);
-               
-               this._updateData = [ ];
-       },
-       
-       /**
-        * Update element states based upon update data.
-        * 
-        * @param       object          data
-        */
-       _updateState: function(data) { },
-       
-       /**
-        * Handles clicks within dropdown.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               var $listItem = $(event.currentTarget);
-               var $elementID = $listItem.data('elementID');
-               var $optionName = $listItem.data('optionName');
-               
-               if (!this._execute($elementID, $optionName)) {
-                       this._executeCallback($elementID, $optionName);
-               }
-               
-               this._hide($elementID);
-       },
-       
-       /**
-        * Executes actions associated with an option.
-        * 
-        * @param       string          elementID
-        * @param       string          optionName
-        * @return      boolean
-        */
-       _execute: function(elementID, optionName) {
-               return false;
-       },
-       
-       /**
-        * Executes actions associated with an option provided by callbacks.
-        * 
-        * @param       string          elementID
-        * @param       string          optionName
-        * @return      boolean
-        */
-       _executeCallback: function(elementID, optionName) {
-               var $length = this._callbacks.length;
-               if ($length) {
-                       for (var $i = 0; $i < $length; $i++) {
-                               if (this._callbacks[$i].execute(this._elements[elementID], optionName)) {
-                                       return true;
-                               }
-                       }
-               }
-               
-               return false;
-       },
-       
-       /**
-        * Hides a dropdown menu.
-        * 
-        * @param       string          elementID
-        */
-       _hide: function(elementID) {
-               if (this._dropdowns[elementID]) {
-                       this._dropdowns[elementID].empty().removeClass('dropdownOpen');
-               }
-       }
-});
-
-/**
- * Default implementation for ajax file uploads
- * 
- * @param      jquery          buttonSelector
- * @param      jquery          fileListSelector
- * @param      string          className
- * @param      jquery          options
- */
-WCF.Upload = Class.extend({
-       /**
-        * name of the upload field
-        * @var string
-        */
-       _name: '__files[]',
-       
-       /**
-        * button selector
-        * @var jQuery
-        */
-       _buttonSelector: null,
-       
-       /**
-        * file list selector
-        * @var jQuery
-        */
-       _fileListSelector: null,
-       
-       /**
-        * upload file
-        * @var jQuery
-        */
-       _fileUpload: null,
-       
-       /**
-        * class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * iframe for IE<10 fallback
-        * @var jQuery
-        */
-       _iframe: null,
-       
-       /**
-        * internal file id
-        * @var integer
-        */
-       _internalFileID: 0,
-       
-       /**
-        * additional options
-        * @var jQuery
-        */
-       _options: {},
-       
-       /**
-        * upload matrix
-        * @var array
-        */
-       _uploadMatrix: [],
-       
-       /**
-        * true, if the active user's browser supports ajax file uploads
-        * @var boolean
-        */
-       _supportsAJAXUpload: true,
-       
-       /**
-        * fallback overlay for stupid browsers
-        * @var jquery
-        */
-       _overlay: null,
-       
-       /**
-        * Initializes a new upload handler.
-        * 
-        * @param       string          buttonSelector
-        * @param       string          fileListSelector
-        * @param       string          className
-        * @param       object          options
-        */
-       init: function(buttonSelector, fileListSelector, className, options) {
-               this._buttonSelector = buttonSelector;
-               this._fileListSelector = fileListSelector;
-               this._className = className;
-               this._internalFileID = 0;
-               this._options = $.extend(true, {
-                       action: 'upload',
-                       multiple: false,
-                       url: 'index.php/AJAXUpload/?t=' + SECURITY_TOKEN + SID_ARG_2ND
-               }, options || { });
-               
-               // check for ajax upload support
-               var $xhr = new XMLHttpRequest();
-               this._supportsAJAXUpload = ($xhr && ('upload' in $xhr) && ('onprogress' in $xhr.upload));
-               
-               // create upload button
-               this._createButton();
-       },
-       
-       /**
-        * Creates the upload button.
-        */
-       _createButton: function() {
-               if (this._supportsAJAXUpload) {
-                       this._fileUpload = $('<input type="file" name="' + this._name + '" ' + (this._options.multiple ? 'multiple="true" ' : '') + '/>');
-                       this._fileUpload.change($.proxy(this._upload, this));
-                       var $button = $('<p class="button uploadButton"><span>' + WCF.Language.get('wcf.global.button.upload') + '</span></p>');
-                       $button.prepend(this._fileUpload);
-               }
-               else {
-                       var $button = $('<p class="button uploadFallbackButton"><span>' + WCF.Language.get('wcf.global.button.upload') + '</span></p>');
-                       $button.click($.proxy(this._showOverlay, this));
-               }
-               
-               this._insertButton($button);
-       },
-       
-       /**
-        * Inserts the upload button.
-        * 
-        * @param       jQuery          button
-        */
-       _insertButton: function(button) {
-               this._buttonSelector.append(button);
-       },
-       
-       /**
-        * Removes the upload button.
-        */
-       _removeButton: function() {
-               var $selector = '.uploadButton';
-               if (!this._supportsAJAXUpload) {
-                       $selector = '.uploadFallbackButton';
-               }
-               
-               this._buttonSelector.find($selector).remove();
-       },
-       
-       /**
-        * Callback for file uploads.
-        */
-       _upload: function() {
-               var $files = this._fileUpload.prop('files');
-               if ($files.length) {
-                       var $fd = new FormData();
-                       var $uploadID = this._createUploadMatrix($files);
-                       
-                       // no more files left, abort
-                       if (!this._uploadMatrix[$uploadID].length) {
-                               return;
-                       }
-                       
-                       for (var $i = 0, $length = $files.length; $i < $length; $i++) {
-                               if (this._uploadMatrix[$uploadID][$i]) {
-                                       var $internalFileID = this._uploadMatrix[$uploadID][$i].data('internalFileID');
-                                       $fd.append('__files[' + $internalFileID + ']', $files[$i]);
-                               }
-                       }
-                       
-                       $fd.append('actionName', this._options.action);
-                       $fd.append('className', this._className);
-                       var $additionalParameters = this._getParameters();
-                       for (var $name in $additionalParameters) {
-                               $fd.append('parameters[' + $name + ']', $additionalParameters[$name]);
-                       }
-                       
-                       var self = this;
-                       $.ajax({ 
-                               type: 'POST',
-                               url: this._options.url,
-                               enctype: 'multipart/form-data',
-                               data: $fd,
-                               contentType: false,
-                               processData: false,
-                               success: function(data, textStatus, jqXHR) {
-                                       self._success($uploadID, data);
-                               },
-                               error: $.proxy(this._error, this),
-                               xhr: function() {
-                                       var $xhr = $.ajaxSettings.xhr();
-                                       if ($xhr) {
-                                               $xhr.upload.addEventListener('progress', function(event) {
-                                                       self._progress($uploadID, event);
-                                               }, false);
-                                       }
-                                       return $xhr;
-                               }
-                       });
-               }
-       },
-       
-       /**
-        * Creates upload matrix for provided files.
-        * 
-        * @param       array<object>           files
-        * @return      integer
-        */
-       _createUploadMatrix: function(files) {
-               if (files.length) {
-                       var $uploadID = this._uploadMatrix.length;
-                       this._uploadMatrix[$uploadID] = [ ];
-                       
-                       for (var $i = 0, $length = files.length; $i < $length; $i++) {
-                               var $file = files[$i];
-                               var $li = this._initFile($file);
-                               
-                               if (!$li.hasClass('uploadFailed')) {
-                                       $li.data('filename', $file.name).data('internalFileID', this._internalFileID++);
-                                       this._uploadMatrix[$uploadID][$i] = $li;
-                               }
-                       }
-                       
-                       return $uploadID;
-               }
-               
-               return null;
-       },
-       
-       /**
-        * Callback for success event.
-        * 
-        * @param       integer         uploadID
-        * @param       object          data
-        */
-       _success: function(uploadID, data) { },
-       
-       /**
-        * Callback for error event.
-        * 
-        * @param       jQuery          jqXHR
-        * @param       string          textStatus
-        * @param       string          errorThrown
-        */
-       _error: function(jqXHR, textStatus, errorThrown) { },
-       
-       /**
-        * Callback for progress event.
-        * 
-        * @param       integer         uploadID
-        * @param       object          event
-        */
-       _progress: function(uploadID, event) {
-               var $percentComplete = Math.round(event.loaded * 100 / event.total);
-               
-               for (var $i in this._uploadMatrix[uploadID]) {
-                       this._uploadMatrix[uploadID][$i].find('progress').attr('value', $percentComplete);
-               }
-       },
-       
-       /**
-        * Returns additional parameters.
-        * 
-        * @return      object
-        */
-       _getParameters: function() {
-               return {};
-       },
-       
-       /**
-        * Initializes list item for uploaded file.
-        * 
-        * @return      jQuery
-        */
-       _initFile: function(file) {
-               return $('<li>' + file.name + ' (' + file.size + ')<progress max="100" /></li>').appendTo(this._fileListSelector);
-       },
-       
-       /**
-        * Shows the fallback overlay (work in progress)
-        */
-       _showOverlay: function() {
-               // create iframe
-               if (this._iframe === null) {
-                       this._iframe = $('<iframe name="__fileUploadIFrame" />').hide().appendTo(document.body);
-               }
-               
-               // create overlay
-               if (!this._overlay) {
-                       this._overlay = $('<div><form enctype="multipart/form-data" method="post" action="' + this._options.url + '" target="__fileUploadIFrame" /></div>').hide().appendTo(document.body);
-                       
-                       var $form = this._overlay.find('form');
-                       $('<dl class="wide"><dd><input type="file" id="__fileUpload" name="' + this._name + '" ' + (this._options.multiple ? 'multiple="true" ' : '') + '/></dd></dl>').appendTo($form);
-                       $('<div class="formSubmit"><input type="submit" value="Upload" accesskey="s" /></div></form>').appendTo($form);
-                       
-                       $('<input type="hidden" name="isFallback" value="1" />').appendTo($form);
-                       $('<input type="hidden" name="actionName" value="' + this._options.action + '" />').appendTo($form);
-                       $('<input type="hidden" name="className" value="' + this._className + '" />').appendTo($form);
-                       var $additionalParameters = this._getParameters();
-                       for (var $name in $additionalParameters) {
-                               $('<input type="hidden" name="' + $name + '" value="' + $additionalParameters[$name] + '" />').appendTo($form);
-                       }
-                       
-                       $form.submit($.proxy(function() {
-                               var $file = {
-                                       name: this._getFilename(),
-                                       size: ''
-                               };
-                               
-                               var $uploadID = this._createUploadMatrix([ $file ]);
-                               var self = this;
-                               this._iframe.data('loading', true).off('load').load(function() { self._evaluateResponse($uploadID); });
-                               this._overlay.wcfDialog('close');
-                       }, this));
-               }
-               
-               this._overlay.wcfDialog({
-                       title: WCF.Language.get('wcf.global.button.upload')
-               });
-       },
-       
-       /**
-        * Evaluates iframe response.
-        * 
-        * @param       integer         uploadID
-        */
-       _evaluateResponse: function(uploadID) {
-               var $returnValues = $.parseJSON(this._iframe.contents().find('pre').html());
-               this._success(uploadID, $returnValues);
-       },
-       
-       /**
-        * Returns name of selected file.
-        * 
-        * @return      string
-        */
-       _getFilename: function() {
-               return $('#__fileUpload').val().split('\\').pop();
-       }
-});
-
-/**
- * Default implementation for parallel AJAX file uploads.
- */
-WCF.Upload.Parallel = WCF.Upload.extend({
-       /**
-        * @see WCF.Upload.init()
-        */
-       init: function(buttonSelector, fileListSelector, className, options) {
-               // force multiple uploads
-               options = $.extend(true, options || { }, {
-                       multiple: true
-               });
-               
-               this._super(buttonSelector, fileListSelector, className, options);
-       },
-       
-       /**
-        * @see WCF.Upload._upload()
-        */
-       _upload: function() {
-               var $files = this._fileUpload.prop('files');
-               for (var $i = 0, $length = $files.length; $i < $length; $i++) {
-                       var $file = $files[$i];
-                       var $formData = new FormData();
-                       var $internalFileID = this._createUploadMatrix($file);
-                       
-                       if (!this._uploadMatrix[$internalFileID].length) {
-                               continue;
-                       }
-                       
-                       $formData.append('__files[' + $internalFileID + ']', $file);
-                       $formData.append('actionName', this._options.action);
-                       $formData.append('className', this._className);
-                       var $additionalParameters = this._getParameters();
-                       for (var $name in $additionalParameters) {
-                               $formData.append('parameters[' + $name + ']', $additionalParameters[$name]);
-                       }
-                       
-                       this._sendRequest($internalFileID, $formData);
-               }
-       },
-       
-       /**
-        * Sends an AJAX request to upload a file.
-        * 
-        * @param       integer         internalFileID
-        * @param       FormData        formData
-        */
-       _sendRequest: function(internalFileID, formData) {
-               var self = this;
-               $.ajax({ 
-                       type: 'POST',
-                       url: this._options.url,
-                       enctype: 'multipart/form-data',
-                       data: formData,
-                       contentType: false,
-                       processData: false,
-                       success: function(data, textStatus, jqXHR) {
-                               self._success(internalFileID, data);
-                       },
-                       error: $.proxy(this._error, this),
-                       xhr: function() {
-                               var $xhr = $.ajaxSettings.xhr();
-                               if ($xhr) {
-                                       $xhr.upload.addEventListener('progress', function(event) {
-                                               self._progress(internalFileID, event);
-                                       }, false);
-                               }
-                               return $xhr;
-                       }
-               });
-       },
-       
-       /**
-        * Creates upload matrix for provided file and returns its internal file id.
-        * 
-        * @param       object          file
-        * @return      integer
-        */
-       _createUploadMatrix: function(file) {
-               var $li = this._initFile(file);
-               if (!$li.hasClass('uploadFailed')) {
-                       $li.data('filename', file.name).data('internalFileID', this._internalFileID);
-                       this._uploadMatrix[this._internalFileID++] = $li;
-                       
-                       return this._internalFileID - 1;
-               }
-               
-               return null;
-       },
-       
-       /**
-        * Callback for success event.
-        * 
-        * @param       integer         internalFileID
-        * @param       object          data
-        */
-       _success: function(internalFileID, data) { },
-       
-       /**
-        * Callback for progress event.
-        * 
-        * @param       integer         internalFileID
-        * @param       object          event
-        */
-       _progress: function(internalFileID, event) {
-               var $percentComplete = Math.round(event.loaded * 100 / event.total);
-               
-               this._uploadMatrix[internalFileID].find('progress').attr('value', $percentComplete);
-       },
-       
-       /**
-        * @see WCF.Upload._showOverlay()
-        */
-       _showOverlay: function() {
-               // create iframe
-               if (this._iframe === null) {
-                       this._iframe = $('<iframe name="__fileUploadIFrame" />').hide().appendTo(document.body);
-               }
-               
-               // create overlay
-               if (!this._overlay) {
-                       this._overlay = $('<div><form enctype="multipart/form-data" method="post" action="' + this._options.url + '" target="__fileUploadIFrame" /></div>').hide().appendTo(document.body);
-                       
-                       var $form = this._overlay.find('form');
-                       $('<dl class="wide"><dd><input type="file" id="__fileUpload" name="' + this._name + '" ' + (this._options.multiple ? 'multiple="true" ' : '') + '/></dd></dl>').appendTo($form);
-                       $('<div class="formSubmit"><input type="submit" value="Upload" accesskey="s" /></div></form>').appendTo($form);
-                       
-                       $('<input type="hidden" name="isFallback" value="1" />').appendTo($form);
-                       $('<input type="hidden" name="actionName" value="' + this._options.action + '" />').appendTo($form);
-                       $('<input type="hidden" name="className" value="' + this._className + '" />').appendTo($form);
-                       var $additionalParameters = this._getParameters();
-                       for (var $name in $additionalParameters) {
-                               $('<input type="hidden" name="' + $name + '" value="' + $additionalParameters[$name] + '" />').appendTo($form);
-                       }
-                       
-                       $form.submit($.proxy(function() {
-                               var $file = {
-                                       name: this._getFilename(),
-                                       size: ''
-                               };
-                               
-                               var $internalFileID = this._createUploadMatrix($file);
-                               var self = this;
-                               this._iframe.data('loading', true).off('load').load(function() { self._evaluateResponse($internalFileID); });
-                               this._overlay.wcfDialog('close');
-                       }, this));
-               }
-               
-               this._overlay.wcfDialog({
-                       title: WCF.Language.get('wcf.global.button.upload')
-               });
-       },
-       
-       /**
-        * Evaluates iframe response.
-        * 
-        * @param       integer         internalFileID
-        */
-       _evaluateResponse: function(internalFileID) {
-               var $returnValues = $.parseJSON(this._iframe.contents().find('pre').html());
-               this._success(internalFileID, $returnValues);
-       }
-});
-
-/**
- * Namespace for sortables.
- */
-WCF.Sortable = { };
-
-/**
- * Sortable implementation for lists.
- * 
- * @param      string          containerID
- * @param      string          className
- * @param      integer         offset
- * @param      object          options
- */
-WCF.Sortable.List = Class.extend({
-       /**
-        * additional parameters for AJAX request
-        * @var object
-        */
-       _additionalParameters: { },
-       
-       /**
-        * action class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * container id
-        * @var string
-        */
-       _containerID: '',
-       
-       /**
-        * container object
-        * @var jQuery
-        */
-       _container: null,
-       
-       /**
-        * notification object
-        * @var WCF.System.Notification
-        */
-       _notification: null,
-       
-       /**
-        * show order offset
-        * @var integer
-        */
-       _offset: 0,
-       
-       /**
-        * list of options
-        * @var object
-        */
-       _options: { },
-       
-       /**
-        * proxy object
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * object structure
-        * @var object
-        */
-       _structure: { },
-       
-       /**
-        * Creates a new sortable list.
-        * 
-        * @param       string          containerID
-        * @param       string          className
-        * @param       integer         offset
-        * @param       object          options
-        * @param       boolean         isSimpleSorting
-        * @param       object          additionalParameters
-        */
-       init: function(containerID, className, offset, options, isSimpleSorting, additionalParameters) {
-               this._additionalParameters = additionalParameters || { };
-               this._containerID = $.wcfEscapeID(containerID);
-               this._container = $('#' + this._containerID);
-               this._className = className;
-               this._offset = (offset) ? offset : 0;
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               this._structure = { };
-               
-               // init sortable
-               this._options = $.extend(true, {
-                       axis: 'y',
-                       connectWith: '#' + this._containerID + ' .sortableList',
-                       disableNesting: 'sortableNoNesting',
-                       doNotClear: true,
-                       errorClass: 'sortableInvalidTarget',
-                       forcePlaceholderSize: true,
-                       helper: 'clone',
-                       items: 'li:not(.sortableNoSorting)',
-                       opacity: .6,
-                       placeholder: 'sortablePlaceholder',
-                       tolerance: 'pointer',
-                       toleranceElement: '> span'
-               }, options || { });
-               
-               if (isSimpleSorting) {
-                       $('#' + this._containerID + ' .sortableList').sortable(this._options);
-               }
-               else {
-                       $('#' + this._containerID + ' > .sortableList').nestedSortable(this._options);
-               }
-               
-               if (this._className) {
-                       var $formSubmit = this._container.find('.formSubmit');
-                       if (!$formSubmit.length) {
-                               $formSubmit = this._container.next('.formSubmit');
-                               if (!$formSubmit.length) {
-                                       console.debug("[WCF.Sortable.Simple] Unable to find form submit for saving, aborting.");
-                                       return;
-                               }
-                       }
-                       
-                       $formSubmit.children('button[data-type="submit"]').click($.proxy(this._submit, this));
-               }
-       },
-       
-       /**
-        * Saves object structure.
-        */
-       _submit: function() {
-               // reset structure
-               this._structure = { };
-               
-               // build structure
-               this._container.find('.sortableList').each($.proxy(function(index, list) {
-                       var $list = $(list);
-                       var $parentID = $list.data('objectID');
-                       
-                       if ($parentID !== undefined) {
-                               $list.children(this._options.items).each($.proxy(function(index, listItem) {
-                                       var $objectID = $(listItem).data('objectID');
-                                       
-                                       if (!this._structure[$parentID]) {
-                                               this._structure[$parentID] = [ ];
-                                       }
-                                       
-                                       this._structure[$parentID].push($objectID);
-                               }, this));
-                       }
-               }, this));
-               
-               // send request
-               var $parameters = $.extend(true, {
-                       data: {
-                               offset: this._offset,
-                               structure: this._structure
-                       }
-               }, this._additionalParameters);
-               
-               this._proxy.setOption('data', {
-                       actionName: 'updatePosition',
-                       className: this._className,
-                       interfaceName: 'wcf\\data\\ISortableAction',
-                       parameters: $parameters
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Shows notification upon success.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               if (this._notification === null) {
-                       this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success.edit'));
-               }
-               
-               this._notification.show();
-       }
-});
-
-WCF.Popover = Class.extend({
-       /**
-        * currently active element id
-        * @var string
-        */
-       _activeElementID: '',
-       
-       /**
-        * cancels popover
-        * @var boolean
-        */
-       _cancelPopover: false,
-       
-       /**
-        * element data
-        * @var object
-        */
-       _data: { },
-       
-       /**
-        * default dimensions, should reflect the estimated size
-        * @var object
-        */
-       _defaultDimensions: {
-               height: 150,
-               width: 450
-       },
-       
-       /**
-        * default orientation, may be a combintion of left/right and bottom/top
-        * @var object
-        */
-       _defaultOrientation: {
-               x: 'right',
-               y: 'top'
-       },
-       
-       /**
-        * delay to show or hide popover, values in miliseconds
-        * @var object
-        */
-       _delay: {
-               show: 800,
-               hide: 500
-       },
-       
-       /**
-        * true, if an element is being hovered
-        * @var boolean
-        */
-       _hoverElement: false,
-       
-       /**
-        * element id of element being hovered
-        * @var string
-        */
-       _hoverElementID: '',
-       
-       /**
-        * true, if popover is being hovered
-        * @var boolean
-        */
-       _hoverPopover: false,
-       
-       /**
-        * minimum margin (all directions) for popover
-        * @var integer
-        */
-       _margin: 20,
-       
-       /**
-        * periodical executer once element or popover is no longer being hovered
-        * @var WCF.PeriodicalExecuter
-        */
-       _peOut: null,
-       
-       /**
-        * periodical executer once an element is being hovered
-        * @var WCF.PeriodicalExecuter
-        */
-       _peOverElement: null,
-       
-       /**
-        * popover object
-        * @var jQuery
-        */
-       _popover: null,
-       
-       /**
-        * popover content
-        * @var jQuery
-        */
-       _popoverContent: null,
-       
-       /**
-        * popover horizontal offset
-        * @var integer
-        */
-       _popoverOffset: 10,
-       
-       /**
-        * element selector
-        * @var string
-        */
-       _selector: '',
-       
-       /**
-        * Initializes a new WCF.Popover object.
-        * 
-        * @param       string          selector
-        */
-       init: function(selector) {
-               if ($.browser.mobile) return;
-               
-               // assign default values
-               this._activeElementID = '';
-               this._cancelPopover = false;
-               this._data = { };
-               this._defaultDimensions = {
-                       height: 150,
-                       width: 450
-               };
-               this._defaultOrientation = {
-                       x: 'right',
-                       y: 'top'
-               };
-               this._delay = {
-                       show: 800,
-                       hide: 500
-               };
-               this._hoverElement = false;
-               this._hoverElementID = '';
-               this._hoverPopover = false;
-               this._margin = 20;
-               this._peOut = null;
-               this._peOverElement = null;
-               this._popoverOffset = 10;
-               this._selector = selector;
-               
-               this._popover = $('<div class="popover"><span class="icon icon48 icon-spinner"></span><div class="popoverContent"></div></div>').hide().appendTo(document.body);
-               this._popoverContent = this._popover.children('.popoverContent:eq(0)');
-               this._popover.hover($.proxy(this._overPopover, this), $.proxy(this._out, this));
-               
-               this._initContainers();
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.Popover.'+selector, $.proxy(this._initContainers, this));
-       },
-       
-       /**
-        * Initializes all element triggers.
-        */
-       _initContainers: function() {
-               if ($.browser.mobile) return;
-               
-               var $elements = $(this._selector);
-               if (!$elements.length) {
-                       return;
-               }
-               
-               $elements.each($.proxy(function(index, element) {
-                       var $element = $(element);
-                       var $elementID = $element.wcfIdentify();
-                       
-                       if (!this._data[$elementID]) {
-                               this._data[$elementID] = {
-                                       'content': null,
-                                       'isLoading': false
-                               };
-                               
-                               $element.hover($.proxy(this._overElement, this), $.proxy(this._out, this));
-                               
-                               if ($element.is('a') && $element.attr('href')) {
-                                       $element.click($.proxy(this._cancel, this));
-                               }
-                       }
-               }, this));
-       },
-       
-       /**
-        * Cancels popovers if link is being clicked
-        */
-       _cancel: function(event) {
-               this._cancelPopover = true;
-               this._hide(true);
-       },
-       
-       /**
-        * Triggered once an element is being hovered.
-        * 
-        * @param       object          event
-        */
-       _overElement: function(event) {
-               if (this._cancelPopover) {
-                       return;
-               }
-               
-               if (this._peOverElement !== null) {
-                       this._peOverElement.stop();
-               }
-               
-               var $elementID = $(event.currentTarget).wcfIdentify();
-               this._hoverElementID = $elementID;
-               this._peOverElement = new WCF.PeriodicalExecuter($.proxy(function(pe) {
-                       pe.stop();
-                       
-                       // still above the same element
-                       if (this._hoverElementID === $elementID) {
-                               this._activeElementID = $elementID;
-                               this._prepare();
-                       }
-               }, this), this._delay.show);
-               
-               this._hoverElement = true;
-               this._hoverPopover = false;
-       },
-       
-       /**
-        * Prepares popover to be displayed.
-        */
-       _prepare: function() {
-               if (this._cancelPopover) {
-                       return;
-               }
-               
-               if (this._peOut !== null) {
-                       this._peOut.stop();
-               }
-               
-               // hide and reset
-               if (this._popover.is(':visible')) {
-                       this._hide(true);
-               }
-               
-               // insert html
-               if (!this._data[this._activeElementID].loading && this._data[this._activeElementID].content) {
-                       this._popoverContent.html(this._data[this._activeElementID].content);
-                       
-                       WCF.DOMNodeInsertedHandler.execute();
-               }
-               else {
-                       this._data[this._activeElementID].loading = true;
-               }
-               
-               // get dimensions
-               var $dimensions = this._popover.show().getDimensions();
-               if (this._data[this._activeElementID].loading) {
-                       $dimensions = {
-                               height: Math.max($dimensions.height, this._defaultDimensions.height),
-                               width: Math.max($dimensions.width, this._defaultDimensions.width)
-                       };
-               }
-               else {
-                       $dimensions = this._fixElementDimensions(this._popover, $dimensions);
-               }
-               this._popover.hide();
-               
-               // get orientation
-               var $orientation = this._getOrientation($dimensions.height, $dimensions.width);
-               this._popover.css(this._getCSS($orientation.x, $orientation.y));
-               
-               // apply orientation to popover
-               this._popover.removeClass('bottom left right top').addClass($orientation.x).addClass($orientation.y);
-               
-               this._show();
-       },
-       
-       /**
-        * Displays the popover.
-        */
-       _show: function() {
-               if (this._cancelPopover) {
-                       return;
-               }
-               
-               this._popover.stop().show().css({ opacity: 1 }).wcfFadeIn();
-               
-               if (this._data[this._activeElementID].loading) {
-                       this._popover.children('span').show();
-                       this._loadContent();
-               }
-               else {
-                       this._popover.children('span').hide();
-                       this._popoverContent.css({ opacity: 1 });
-               }
-       },
-       
-       /**
-        * Loads content, should be overwritten by child classes.
-        */
-       _loadContent: function() { },
-       
-       /**
-        * Inserts content and animating transition.
-        * 
-        * @param       string          elementID
-        * @param       boolean         animate
-        */
-       _insertContent: function(elementID, content, animate) {
-               this._data[elementID] = {
-                       content: content,
-                       loading: false
-               };
-               
-               // only update content if element id is active
-               if (this._activeElementID === elementID) {
-                       if (animate) {
-                               // get current dimensions
-                               var $dimensions = this._popoverContent.getDimensions();
-                               
-                               // insert new content
-                               this._popoverContent.css({
-                                       height: 'auto',
-                                       width: 'auto'
-                               });
-                               this._popoverContent.html(this._data[elementID].content);
-                               var $newDimensions = this._popoverContent.getDimensions();
-                               
-                               // enforce current dimensions and remove HTML
-                               this._popoverContent.html('').css({
-                                       height: $dimensions.height + 'px',
-                                       width: $dimensions.width + 'px'
-                               });
-                               
-                               // animate to new dimensons
-                               var self = this;
-                               this._popoverContent.animate({
-                                       height: $newDimensions.height + 'px',
-                                       width: $newDimensions.width + 'px'
-                               }, 300, function() {
-                                       self._popover.children('span').hide();
-                                       self._popoverContent.html(self._data[elementID].content).css({ opacity: 0 }).animate({ opacity: 1 }, 200);
-                                       
-                                       WCF.DOMNodeInsertedHandler.execute();
-                               });
-                       }
-                       else {
-                               // insert new content
-                               this._popover.children('span').hide();
-                               this._popoverContent.html(this._data[elementID].content);
-                               
-                               WCF.DOMNodeInsertedHandler.execute();
-                       }
-               }
-       },
-       
-       /**
-        * Hides the popover.
-        */
-       _hide: function(disableAnimation) {
-               var self = this;
-               this._popoverContent.stop();
-               this._popover.stop();
-               
-               if (disableAnimation) {
-                       self._popover.css({ opacity: 0 }).hide();
-                       self._popoverContent.empty().css({ height: 'auto', opacity: 0, width: 'auto' });
-               }
-               else {
-                       this._popover.wcfFadeOut(function() {
-                               self._popoverContent.empty().css({ height: 'auto', opacity: 0, width: 'auto' });
-                               self._popover.hide();
-                       });
-               }
-       },
-       
-       /**
-        * Triggered once popover is being hovered.
-        */
-       _overPopover: function() {
-               if (this._peOut !== null) {
-                       this._peOut.stop();
-               }
-               
-               this._hoverElement = false;
-               this._hoverPopover = true;
-       },
-       
-       /**
-        * Triggered once element *or* popover is now longer hovered.
-        */
-       _out: function(event) {
-               if (this._cancelPopover) {
-                       return;
-               }
-               
-               this._hoverElementID = '';
-               this._hoverElement = false;
-               this._hoverPopover = false;
-               
-               this._peOut = new WCF.PeriodicalExecuter($.proxy(function(pe) {
-                       pe.stop();
-                       
-                       // hide popover is neither element nor popover was hovered given time
-                       if (!this._hoverElement && !this._hoverPopover) {
-                               this._hide(false);
-                       }
-               }, this), this._delay.hide);
-       },
-       
-       /**
-        * Resolves popover orientation, tries to use default orientation first.
-        * 
-        * @param       integer         height
-        * @param       integer         width
-        * @return      object
-        */
-       _getOrientation: function(height, width) {
-               // get offsets and dimensions
-               var $element = $('#' + this._activeElementID);
-               var $offsets = $element.getOffsets('offset');
-               var $elementDimensions = $element.getDimensions();
-               var $documentDimensions = $(document).getDimensions();
-               
-               // try default orientation first
-               var $orientationX = (this._defaultOrientation.x === 'left') ? 'left' : 'right';
-               var $orientationY = (this._defaultOrientation.y === 'bottom') ? 'bottom' : 'top';
-               var $result = this._evaluateOrientation($orientationX, $orientationY, $offsets, $elementDimensions, $documentDimensions, height, width);
-               
-               if ($result.flawed) {
-                       // try flipping orientationX
-                       $orientationX = ($orientationX === 'left') ? 'right' : 'left';
-                       $result = this._evaluateOrientation($orientationX, $orientationY, $offsets, $elementDimensions, $documentDimensions, height, width);
-                       
-                       if ($result.flawed) {
-                               // try flipping orientationY while maintaing original orientationX
-                               $orientationX = ($orientationX === 'right') ? 'left' : 'right';
-                               $orientationY = ($orientationY === 'bottom') ? 'top' : 'bottom';
-                               $result = this._evaluateOrientation($orientationX, $orientationY, $offsets, $elementDimensions, $documentDimensions, height, width);
-                               
-                               if ($result.flawed) {
-                                       // try flipping both orientationX and orientationY compared to default values
-                                       $orientationX = ($orientationX === 'left') ? 'right' : 'left';
-                                       $result = this._evaluateOrientation($orientationX, $orientationY, $offsets, $elementDimensions, $documentDimensions, height, width);
-                                       
-                                       if ($result.flawed) {
-                                               // fuck this shit, we will use the default orientation
-                                               $orientationX = (this._defaultOrientationX === 'left') ? 'left' : 'right';
-                                               $orientationY = (this._defaultOrientationY === 'bottom') ? 'bottom' : 'top';
-                                       }
-                               }
-                       }
-               }
-               
-               return {
-                       x: $orientationX,
-                       y: $orientationY
-               };
-       },
-       
-       /**
-        * Evaluates if popover fits into given orientation.
-        * 
-        * @param       string          orientationX
-        * @param       string          orientationY
-        * @param       object          offsets
-        * @param       object          elementDimensions
-        * @param       object          documentDimensions
-        * @param       integer         height
-        * @param       integer         width
-        * @return      object
-        */
-       _evaluateOrientation: function(orientationX, orientationY, offsets, elementDimensions, documentDimensions, height, width) {
-               var $heightDifference = 0, $widthDifference = 0;
-               switch (orientationX) {
-                       case 'left':
-                               $widthDifference = offsets.left - width;
-                       break;
-                       
-                       case 'right':
-                               $widthDifference = documentDimensions.width - (offsets.left + width);
-                       break;
-               }
-               
-               switch (orientationY) {
-                       case 'bottom':
-                               $heightDifference = documentDimensions.height - (offsets.top + elementDimensions.height + this._popoverOffset + height);
-                       break;
-                       
-                       case 'top':
-                               $heightDifference = offsets.top - (height - this._popoverOffset);
-                       break;
-               }
-               
-               // check if both difference are above margin
-               var $flawed = false;
-               if ($heightDifference < this._margin || $widthDifference < this._margin) {
-                       $flawed = true;
-               }
-               
-               return {
-                       flawed: $flawed,
-                       x: $widthDifference,
-                       y: $heightDifference
-               };
-       },
-       
-       /**
-        * Computes CSS for popover.
-        * 
-        * @param       string          orientationX
-        * @param       string          orientationY
-        * @return      object
-        */
-       _getCSS: function(orientationX, orientationY) {
-               var $css = {
-                       bottom: 'auto',
-                       left: 'auto',
-                       right: 'auto',
-                       top: 'auto'
-               };
-               
-               var $element = $('#' + this._activeElementID);
-               var $offsets = $element.getOffsets('offset');
-               var $elementDimensions = this._fixElementDimensions($element, $element.getDimensions());
-               var $windowDimensions = $(window).getDimensions();
-               
-               switch (orientationX) {
-                       case 'left':
-                               $css.right = $windowDimensions.width - ($offsets.left + $elementDimensions.width);
-                       break;
-                       
-                       case 'right':
-                               $css.left = $offsets.left;
-                       break;
-               }
-               
-               switch (orientationY) {
-                       case 'bottom':
-                               $css.top = $offsets.top + ($elementDimensions.height + this._popoverOffset);
-                       break;
-                       
-                       case 'top':
-                               $css.bottom = $windowDimensions.height - ($offsets.top - this._popoverOffset);
-                       break;
-               }
-               
-               return $css;
-       },
-       
-       /**
-        * Tries to fix dimensions if element is partially hidden (overflow: hidden).
-        * 
-        * @param       jQuery          element
-        * @param       object          dimensions
-        * @return      dimensions
-        */
-       _fixElementDimensions: function(element, dimensions) {
-               var $parentDimensions = element.parent().getDimensions();
-               
-               if ($parentDimensions.height < dimensions.height) {
-                       dimensions.height = $parentDimensions.height;
-               }
-               
-               if ($parentDimensions.width < dimensions.width) {
-                       dimensions.width = $parentDimensions.width;
-               }
-               
-               return dimensions;
-       }
-});
-
-/**
- * Provides an extensible item list with built-in search.
- * 
- * @param      string          itemListSelector
- * @param      string          searchInputSelector
- */
-WCF.EditableItemList = Class.extend({
-       /**
-        * allows custom input not recognized by search to be added
-        * @var boolean
-        */
-       _allowCustomInput: false,
-       
-       /**
-        * action class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * internal data storage
-        * @var mixed
-        */
-       _data: { },
-       
-       /**
-        * form container
-        * @var jQuery
-        */
-       _form: null,
-       
-       /**
-        * item list container
-        * @var jQuery
-        */
-       _itemList: null,
-       
-       /**
-        * current object id
-        * @var integer
-        */
-       _objectID: 0,
-       
-       /**
-        * object type id
-        * @var integer
-        */
-       _objectTypeID: 0,
-       
-       /**
-        * search controller
-        * @var WCF.Search.Base
-        */
-       _search: null,
-       
-       /**
-        * search input element
-        * @var jQuery
-        */
-       _searchInput: null,
-       
-       /**
-        * Creates a new WCF.EditableItemList object.
-        * 
-        * @param       string          itemListSelector
-        * @param       string          searchInputSelector
-        */
-       init: function(itemListSelector, searchInputSelector) {
-               this._itemList = $(itemListSelector);
-               this._searchInput = $(searchInputSelector);
-               this._data = { };
-               
-               if (!this._itemList.length || !this._searchInput.length) {
-                       console.debug("[WCF.EditableItemList] Item list and/or search input do not exist, aborting.");
-                       return;
-               }
-               
-               this._objectID = this._getObjectID();
-               this._objectTypeID = this._getObjectTypeID();
-               
-               // bind item listener
-               this._itemList.find('.jsEditableItem').click($.proxy(this._click, this));
-               
-               // create item list
-               if (!this._itemList.children('ul').length) {
-                       $('<ul />').appendTo(this._itemList);
-               }
-               this._itemList = this._itemList.children('ul');
-               
-               // bind form submit
-               this._form = this._itemList.parents('form').submit($.proxy(this._submit, this));
-               
-               if (this._allowCustomInput) {
-                       var self = this;
-                       this._searchInput.keydown($.proxy(this._keyDown, this)).on('paste', function() {
-                               setTimeout(function() { self._onPaste(); }, 100);
-                       });
-               }
-               
-               // block form submit through [ENTER]
-               this._searchInput.parents('.dropdown').data('preventSubmit', true);
-       },
-       
-       /**
-        * Handles the key down event.
-        * 
-        * @param       object          event
-        */
-       _keyDown: function(event) {
-               // 188 = [,]
-               if (event === null || event.which === 188 || event.which === $.ui.keyCode.ENTER) {
-                       if (event !== null && event.which === $.ui.keyCode.ENTER && this._search) {
-                               if (this._search._itemIndex !== -1) {
-                                       return false;
-                               }
-                       }
-                       
-                       var $value = $.trim(this._searchInput.val());
-                       
-                       // read everything left from caret position
-                       if (event && event.which === 188) {
-                               $value = $value.substring(0, this._searchInput.getCaret());
-                       }
-                       
-                       if ($value === '') {
-                               return true;
-                       }
-                       
-                       this.addItem({
-                               objectID: 0,
-                               label: $value
-                       });
-                       
-                       // reset input
-                       if (event && event.which === 188) {
-                               this._searchInput.val($.trim(this._searchInput.val().substr(this._searchInput.getCaret())));
-                       }
-                       else {
-                               this._searchInput.val('');
-                       }
-                       
-                       if (event !== null) {
-                               event.stopPropagation();
-                       }
-                       
-                       return false;
-               }
-               
-               return true;
-       },
-       
-       /**
-        * Handle paste event.
-        */
-       _onPaste: function() {
-               // split content by comma
-               var $value = $.trim(this._searchInput.val());
-               $value = $value.split(',');
-               
-               for (var $i = 0, $length = $value.length; $i < $length; $i++) {
-                       var $label = $.trim($value[$i]);
-                       if ($label === '') {
-                               continue;
-                       }
-                       
-                       this.addItem({
-                               objectID: 0,
-                               label: $label
-                       });
-               }
-               
-               this._searchInput.val('');
-       },
-       
-       /**
-        * Loads raw data and converts it into internal structure. Override this methods
-        * in your derived classes.
-        * 
-        * @param       object          data
-        */
-       load: function(data) { },
-       
-       /**
-        * Removes an item on click.
-        * 
-        * @param       object          event
-        * @return      boolean
-        */
-       _click: function(event) {
-               var $element = $(event.currentTarget);
-               var $objectID = $element.data('objectID');
-               var $label = $element.data('label');
-               
-               if (this._search) {
-                       this._search.removeExcludedSearchValue($label);
-               }
-               this._removeItem($objectID, $label);
-               
-               $element.remove();
-               
-               event.stopPropagation();
-               return false;
-       },
-       
-       /**
-        * Returns current object id.
-        * 
-        * @return      integer
-        */
-       _getObjectID: function() {
-               return 0;
-       },
-       
-       /**
-        * Returns current object type id.
-        * 
-        * @return      integer
-        */
-       _getObjectTypeID: function() {
-               return 0;
-       },
-       
-       /**
-        * Adds a new item to the list.
-        * 
-        * @param       object          data
-        * @return      boolean
-        */
-       addItem: function(data) {
-               if (this._data[data.objectID]) {
-                       if (!(data.objectID === 0 && this._allowCustomInput)) {
-                               return true;
-                       }
-               }
-               
-               var $listItem = $('<li class="badge">' + WCF.String.escapeHTML(data.label) + '</li>').data('objectID', data.objectID).data('label', data.label).appendTo(this._itemList);
-               $listItem.click($.proxy(this._click, this));
-               
-               if (this._search) {
-                       this._search.addExcludedSearchValue(data.label);
-               }
-               this._addItem(data.objectID, data.label);
-               
-               return true;
-       },
-       
-       /**
-        * Clears the list of items.
-        */
-       clearList: function() {
-               this._itemList.children('li').each($.proxy(function(index, element) {
-                       var $element = $(element);
-                       
-                       if (this._search) {
-                               this._search.removeExcludedSearchValue($element.data('label'));
-                       }
-                       
-                       $element.remove();
-                       this._removeItem($element.data('objectID'), $element.data('label'));
-               }, this));
-       },
-       
-       /**
-        * Handles form submit, override in your class.
-        */
-       _submit: function() {
-               this._keyDown(null);
-       },
-       
-       /**
-        * Adds an item to internal storage.
-        * 
-        * @param       integer         objectID
-        * @param       string          label
-        */
-       _addItem: function(objectID, label) {
-               this._data[objectID] = label;
-       },
-       
-       /**
-        * Removes an item from internal storage.
-        * 
-        * @param       integer         objectID
-        * @param       string          label
-        */
-       _removeItem: function(objectID, label) {
-               delete this._data[objectID];
-       },
-       
-       /**
-        * Returns the search input field.
-        * 
-        * @return      jQuery
-        */
-       getSearchInput: function() {
-               return this._searchInput;
-       }
-});
-
-/**
- * Provides a generic sitemap.
- */
-WCF.Sitemap = Class.extend({
-       /**
-        * sitemap name cache
-        * @var array
-        */
-       _cache: [ ],
-       
-       /**
-        * dialog overlay
-        * @var jQuery
-        */
-       _dialog: null,
-       
-       /**
-        * initialization state
-        * @var boolean
-        */
-       _didInit: false,
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * Initializes the generic sitemap.
-        */
-       init: function() {
-               $('#sitemap').click($.proxy(this._click, this));
-               
-               this._cache = [ ];
-               this._dialog = null;
-               this._didInit = false;
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-       },
-       
-       /**
-        * Handles clicks on the sitemap icon.
-        */
-       _click: function() {
-               if (this._dialog === null) {
-                       this._dialog = $('<div id="sitemapDialog" />').appendTo(document.body);
-                       
-                       this._proxy.setOption('data', {
-                               actionName: 'getSitemap',
-                               className: 'wcf\\data\\sitemap\\SitemapAction'
-                       });
-                       this._proxy.sendRequest();
-               }
-               else {
-                       this._dialog.wcfDialog('open');
-               }
-       },
-       
-       /**
-        * Handles successful AJAX responses.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               if (this._didInit) {
-                       this._cache.push(data.returnValues.sitemapName);
-                       
-                       this._dialog.find('#sitemap_' + data.returnValues.sitemapName).html(data.returnValues.template);
-                       
-                       // redraw dialog
-                       this._dialog.wcfDialog('render');
-               }
-               else {
-                       // mark sitemap name as loaded
-                       this._cache.push(data.returnValues.sitemapName);
-                       
-                       // insert sitemap template
-                       this._dialog.html(data.returnValues.template);
-                       
-                       // bind event listener
-                       this._dialog.find('.sitemapNavigation').click($.proxy(this._navigate, this));
-                       
-                       // select active item
-                       this._dialog.find('.tabMenuContainer').wcfTabs('select', 'sitemap_' + data.returnValues.sitemapName);
-                       
-                       // show dialog
-                       this._dialog.wcfDialog({
-                               title: WCF.Language.get('wcf.page.sitemap')
-                       });
-                       
-                       this._didInit = true;
-               }
-       },
-       
-       /**
-        * Navigates between different sitemaps.
-        * 
-        * @param       object          event
-        */
-       _navigate: function(event) {
-               var $sitemapName = $(event.currentTarget).data('sitemapName');
-               if (WCF.inArray($sitemapName, this._cache)) {
-                       this._dialog.find('.tabMenuContainer').wcfTabs('select', 'sitemap_' + $sitemapName);
-                       
-                       // redraw dialog
-                       this._dialog.wcfDialog('render');
-               }
-               else {
-                       this._proxy.setOption('data', {
-                               actionName: 'getSitemap',
-                               className: 'wcf\\data\\sitemap\\SitemapAction',
-                               parameters: {
-                                       sitemapName: $sitemapName
-                               }
-                       });
-                       this._proxy.sendRequest();
-               }
-       }
-});
-
-/**
- * Provides a language chooser.
- * 
- * @param      string          containerID
- * @param      string          inputFieldID
- * @param      integer         languageID
- * @param      object          languages
- * @param      object          callback
- */
-WCF.Language.Chooser = Class.extend({
-       /**
-        * callback object
-        * @var object
-        */
-       _callback: null,
-       
-       /**
-        * dropdown object
-        * @var jQuery
-        */
-       _dropdown: null,
-       
-       /**
-        * input field
-        * @var jQuery
-        */
-       _input: null,
-       
-       /**
-        * Initializes the language chooser.
-        * 
-        * @param       string          containerID
-        * @param       string          inputFieldID
-        * @param       integer         languageID
-        * @param       object          languages
-        * @param       object          callback
-        * @param       boolean         allowEmptyValue
-        */
-       init: function(containerID, inputFieldID, languageID, languages, callback, allowEmptyValue) {
-               var $container = $('#' + containerID);
-               if ($container.length != 1) {
-                       console.debug("[WCF.Language.Chooser] Invalid container id '" + containerID + "' given");
-                       return;
-               }
-               
-               // bind language id input
-               this._input = $('#' + inputFieldID);
-               if (!this._input.length) {
-                       this._input = $('<input type="hidden" name="' + inputFieldID + '" value="' + languageID + '" />').appendTo($container);
-               }
-               
-               // handle callback
-               if (callback !== undefined) {
-                       if (!$.isFunction(callback)) {
-                               console.debug("[WCF.Language.Chooser] Given callback is invalid");
-                               return;
-                       }
-                       
-                       this._callback = callback;
-               }
-               
-               // create language dropdown
-               this._dropdown = $('<div class="dropdown" id="' + containerID + '-languageChooser" />').appendTo($container);
-               $('<div class="dropdownToggle boxFlag box24" data-toggle="' + containerID + '-languageChooser"></div>').appendTo(this._dropdown);
-               var $dropdownMenu = $('<ul class="dropdownMenu" />').appendTo(this._dropdown);
-               
-               for (var $languageID in languages) {
-                       var $language = languages[$languageID];
-                       var $item = $('<li class="boxFlag"><a class="box24"><div class="framed"><img src="' + $language.iconPath + '" alt="" class="iconFlag" /></div> <div><h3>' + $language.languageName + '</h3></div></a></li>').appendTo($dropdownMenu);
-                       $item.data('languageID', $languageID).click($.proxy(this._click, this));
-                       
-                       // update dropdown label
-                       if ($languageID == languageID) {
-                               var $html = $('' + $item.html());
-                               var $innerContent = $html.children().detach();
-                               this._dropdown.children('.dropdownToggle').empty().append($innerContent);
-                       }
-               }
-               
-               // allow an empty selection (e.g. using as language filter)
-               if (allowEmptyValue) {
-                       $('<li class="dropdownDivider" />').appendTo($dropdownMenu);
-                       var $item = $('<li><a>' + WCF.Language.get('wcf.global.language.noSelection') + '</a></li>').data('languageID', 0).click($.proxy(this._click, this)).appendTo($dropdownMenu);
-                       
-                       if (languageID === 0) {
-                               this._dropdown.children('.dropdownToggle').empty().append($item.html());
-                       }
-               }
-               
-               WCF.Dropdown.init();
-       },
-       
-       /**
-        * Handles click events.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               var $item = $(event.currentTarget);
-               var $languageID = $item.data('languageID');
-               
-               // update input field
-               this._input.val($languageID);
-               
-               // update dropdown label
-               var $html = $('' + $item.html());
-               var $innerContent = ($languageID === 0) ? $html : $html.children().detach();
-               this._dropdown.children('.dropdownToggle').empty().append($innerContent);
-               
-               // execute callback
-               if (this._callback !== null) {
-                       this._callback($item);
-               }
-       }
-});
-
-/**
- * Namespace for style related classes.
- */
-WCF.Style = { };
-
-/**
- * Provides a visual style chooser.
- */
-WCF.Style.Chooser = Class.extend({
-       /**
-        * dialog overlay
-        * @var jQuery
-        */
-       _dialog: null,
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * Initializes the style chooser class.
-        */
-       init: function() {
-               $('<li class="styleChooser"><a>' + WCF.Language.get('wcf.style.changeStyle') + '</a></li>').appendTo($('#footerNavigation > ul.navigationItems')).click($.proxy(this._showDialog, this));
-               
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-       },
-       
-       /**
-        * Displays the style chooser dialog.
-        */
-       _showDialog: function() {
-               if (this._dialog === null) {
-                       this._dialog = $('<div id="styleChooser" />').hide().appendTo(document.body);
-                       this._loadDialog();
-               }
-               else {
-                       this._dialog.wcfDialog({
-                               title: WCF.Language.get('wcf.style.changeStyle')
-                       });
-               }
-       },
-       
-       /**
-        * Loads the style chooser dialog.
-        */
-       _loadDialog: function() {
-               this._proxy.setOption('data', {
-                       actionName: 'getStyleChooser',
-                       className: 'wcf\\data\\style\\StyleAction'
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               if (data.actionName === 'changeStyle') {
-                       window.location.reload();
-                       return;
-               }
-               
-               this._dialog.html(data.returnValues.template);
-               this._dialog.find('li').addClass('pointer').click($.proxy(this._click, this));
-               
-               this._showDialog();
-       },
-       
-       /**
-        * Changes user style.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               this._proxy.setOption('data', {
-                       actionName: 'changeStyle',
-                       className: 'wcf\\data\\style\\StyleAction',
-                       objectIDs: [ $(event.currentTarget).data('styleID') ]
-               });
-               this._proxy.sendRequest();
-       }
-});
-
-/**
- * Converts static user panel items into interactive dropdowns.
- * 
- * @param      string          containerID
- */
-WCF.UserPanel = Class.extend({
-       /**
-        * target container
-        * @var jQuery
-        */
-       _container: null,
-       
-       /**
-        * initialization state
-        * @var boolean
-        */
-       _didLoad: false,
-       
-       /**
-        * original link element
-        * @var jQuery
-        */
-       _link: null,
-       
-       /**
-        * language variable name for 'no items'
-        * @var string
-        */
-       _noItems: '',
-       
-       /**
-        * reverts to original link if return values are empty
-        * @var boolean
-        */
-       _revertOnEmpty: true,
-       
-       /**
-        * Initialites the WCF.UserPanel class.
-        * 
-        * @param       string          containerID
-        */
-       init: function(containerID) {
-               this._container = $('#' + containerID);
-               this._didLoad = false;
-               this._revertOnEmpty = true;
-               
-               if (this._container.length != 1) {
-                       console.debug("[WCF.UserPanel] Unable to find container identfied by '" + containerID + "', aborting.");
-                       return;
-               }
-               
-               this._convert();
-       },
-       
-       /**
-        * Converts link into an interactive dropdown menu.
-        */
-       _convert: function() {
-               this._container.addClass('dropdown');
-               this._link = this._container.children('a').remove();
-               
-               var $button = $('<a class="dropdownToggle">' + this._link.html() + '</a>').appendTo(this._container).click($.proxy(this._click, this));
-               var $dropdownMenu = $('<ul class="dropdownMenu" />').appendTo(this._container);
-               $('<li class="jsDropdownPlaceholder"><span>' + WCF.Language.get('wcf.global.loading') + '</span></li>').appendTo($dropdownMenu);
-               
-               this._addDefaultItems($dropdownMenu);
-               
-               this._container.dblclick($.proxy(function() {
-                       window.location = this._link.attr('href');
-                       return false;
-               }, this));
-               
-               WCF.Dropdown.initDropdown($button, false);
-       },
-       
-       /**
-        * Adds default items to dropdown menu.
-        * 
-        * @param       jQuery          dropdownMenu
-        */
-       _addDefaultItems: function(dropdownMenu) { },
-       
-       /**
-        * Adds a dropdown divider.
-        * 
-        * @param       jQuery          dropdownMenu
-        */
-       _addDivider: function(dropdownMenu) {
-               $('<li class="dropdownDivider" />').appendTo(dropdownMenu);
-       },
-       
-       /**
-        * Handles clicks on the dropdown item.
-        */
-       _click: function() {
-               if (this._didLoad) {
-                       return;
-               }
-               
-               new WCF.Action.Proxy({
-                       autoSend: true,
-                       data: this._getParameters(),
-                       success: $.proxy(this._success, this)
-               });
-               
-               this._didLoad = true;
-       },
-       
-       /**
-        * Returns a list of parameters for AJAX request.
-        * 
-        * @return      object
-        */
-       _getParameters: function() {
-               return { };
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               var $dropdownMenu = WCF.Dropdown.getDropdownMenu(this._container.wcfIdentify());
-               $dropdownMenu.children('.jsDropdownPlaceholder').remove();
-               
-               if (data.returnValues && data.returnValues.template) {
-                       $('' + data.returnValues.template).prependTo($dropdownMenu);
-                       
-                       // update badge
-                       var $badge = this._container.find('.badge');
-                       if (!$badge.length) {
-                               $badge = $('<span class="badge badgeInverse" />').appendTo(this._container.children('.dropdownToggle'));
-                               $badge.before(' ');
-                       }
-                       $badge.html(data.returnValues.totalCount);
-                       
-                       this._after($dropdownMenu);
-               }
-               else {
-                       $('<li><span>' + WCF.Language.get(this._noItems) + '</span></li>').prependTo($dropdownMenu);
-                       
-                       // remove badge
-                       this._container.find('.badge').remove();
-               }
-       },
-       
-       /**
-        * Execute actions after the dropdown menu has been populated.
-        * 
-        * @param       object          dropdownMenu
-        */
-       _after: function(dropdownMenu) { }
-});
-
-/**
- * WCF implementation for dialogs, based upon ideas by jQuery UI.
- */
-$.widget('ui.wcfDialog', {
-       /**
-        * close button
-        * @var jQuery
-        */
-       _closeButton: null,
-       
-       /**
-        * dialog container
-        * @var jQuery
-        */
-       _container: null,
-       
-       /**
-        * dialog content
-        * @var jQuery
-        */
-       _content: null,
-       
-       /**
-        * modal overlay
-        * @var jQuery
-        */
-       _overlay: null,
-       
-       /**
-        * plain html for title
-        * @var string
-        */
-       _title: null,
-       
-       /**
-        * title bar
-        * @var jQuery
-        */
-       _titlebar: null,
-       
-       /**
-        * dialog visibility state
-        * @var boolean
-        */
-       _isOpen: false,
-       
-       /**
-        * option list
-        * @var object
-        */
-       options: {
-               // dialog
-               autoOpen: true,
-               closable: true,
-               closeButtonLabel: null,
-               closeConfirmMessage: null,
-               closeViaModal: true,
-               hideTitle: false,
-               modal: true,
-               title: '',
-               zIndex: 400,
-               
-               // event callbacks
-               onClose: null,
-               onShow: null
-       },
-       
-       /**
-        * @see $.widget._createWidget()
-        */
-       _createWidget: function(options, element) {
-               // ignore script tags
-               if ($(element).getTagName() === 'script') {
-                       console.debug("[ui.wcfDialog] Ignored script tag");
-                       this.element = false;
-                       return null;
-               }
-               
-               $.Widget.prototype._createWidget.apply(this, arguments);
-       },
-       
-       /**
-        * Initializes a new dialog.
-        */
-       _init: function() {
-               if (this.options.autoOpen) {
-                       this.open();
-               }
-               
-               // act on resize
-               $(window).resize($.proxy(this._resize, this));
-       },
-       
-       /**
-        * Creates a new dialog instance.
-        */
-       _create: function() {
-               if (this.options.closeButtonLabel === null) {
-                       this.options.closeButtonLabel = WCF.Language.get('wcf.global.button.close');
-               }
-               
-               // create dialog container
-               this._container = $('<div class="dialogContainer" />').hide().css({ zIndex: this.options.zIndex }).appendTo(document.body);
-               this._titlebar = $('<header class="dialogTitlebar" />').hide().appendTo(this._container);
-               this._title = $('<span class="dialogTitle" />').hide().appendTo(this._titlebar);
-               this._closeButton = $('<a class="dialogCloseButton jsTooltip" title="' + this.options.closeButtonLabel + '"><span /></a>').click($.proxy(this.close, this)).hide().appendTo(this._titlebar);
-               this._content = $('<div class="dialogContent" />').appendTo(this._container);
-               
-               this._setOption('title', this.options.title);
-               this._setOption('closable', this.options.closable);
-               
-               // move target element into content
-               var $content = this.element.detach();
-               this._content.html($content);
-               
-               // create modal view
-               if (this.options.modal) {
-                       this._overlay = $('#jsWcfDialogOverlay');
-                       if (!this._overlay.length) {
-                               this._overlay = $('<div id="jsWcfDialogOverlay" class="dialogOverlay" />').css({ height: '100%', zIndex: 399 }).hide().appendTo(document.body);
-                       }
-                       
-                       if (this.options.closable && this.options.closeViaModal) {
-                               this._overlay.click($.proxy(this.close, this));
-                               
-                               $(document).keyup($.proxy(function(event) {
-                                       if (event.keyCode && event.keyCode === $.ui.keyCode.ESCAPE) {
-                                               this.close();
-                                               event.preventDefault();
-                                       }
-                               }, this));
-                       }
-               }
-               
-               WCF.DOMNodeInsertedHandler.execute();
-       },
-       
-       /**
-        * Sets the given option to the given value.
-        * See the jQuery UI widget documentation for more.
-        */
-       _setOption: function(key, value) {
-               this.options[key] = value;
-               
-               if (key == 'hideTitle' || key == 'title') {
-                       if (!this.options.hideTitle && this.options.title != '') {
-                               this._title.html(this.options.title).show();
-                       } else {
-                               this._title.html('');
-                       }
-               } else if (key == 'closable' || key == 'closeButtonLabel') {
-                       if (this.options.closable) {
-                               this._closeButton.attr('title', this.options.closeButtonLabel).show().find('span').html(this.options.closeButtonLabel);
-                               
-                               WCF.DOMNodeInsertedHandler.execute();
-                       } else {
-                               this._closeButton.hide();
-                       }
-               }
-               
-               if ((!this.options.hideTitle && this.options.title != '') || this.options.closable) {
-                       this._titlebar.show();
-               } else {
-                       this._titlebar.hide();
-               }
-               
-               return this;
-       },
-       
-       /**
-        * Opens this dialog.
-        */
-       open: function() {
-               // ignore script tags
-               if (this.element === false) {
-                       return;
-               }
-               
-               if (this.isOpen()) {
-                       return;
-               }
-               
-               if (this._overlay !== null) {
-                       WCF.activeDialogs++;
-                       
-                       if (WCF.activeDialogs === 1) {
-                               this._overlay.show();
-                       }
-               }
-               
-               this.render();
-               this._isOpen = true;
-       },
-       
-       /**
-        * Returns true if dialog is visible.
-        * 
-        * @return      boolean
-        */
-       isOpen: function() {
-               return this._isOpen;
-       },
-       
-       /**
-        * Closes this dialog.
-        * 
-        * This function can be manually called, even if the dialog is set as not
-        * closable by the user.
-        * 
-        * @param       object          event
-        */
-       close: function(event) {
-               if (!this.isOpen()) {
-                       return;
-               }
-               
-               if (this.options.closeConfirmMessage) {
-                       WCF.System.Confirmation.show(this.options.closeConfirmMessage, $.proxy(function(action) {
-                               if (action === 'confirm') {
-                                       this._close();
-                               }
-                       }, this));
-               }
-               else {
-                       this._close();
-               }
-               
-               if (event !== undefined) {
-                       event.preventDefault();
-               }
-       },
-       
-       /**
-        * Handles dialog closing, should never be called directly.
-        * 
-        * @see $.ui.wcfDialog.close()
-        */
-       _close: function() {
-               this._isOpen = false;
-               this._container.wcfFadeOut();
-               
-               if (this._overlay !== null) {
-                       WCF.activeDialogs--;
-                       
-                       if (WCF.activeDialogs === 0) {
-                               this._overlay.hide();
-                       }
-               }
-               
-               if (this.options.onClose !== null) {
-                       this.options.onClose();
-               }
-       },
-       
-       /**
-        * Renders dialog on resize if visible.
-        */
-       _resize: function() {
-               if (this.isOpen()) {
-                       this.render();
-               }
-       },
-       
-       /**
-        * Renders this dialog, should be called whenever content is updated.
-        */
-       render: function() {
-               // check if this if dialog was previously hidden and container is fixed
-               // at 0px (mobile optimization), in this case scroll to top
-               if (!this._container.is(':visible') && this._container.css('top') === '0px') {
-                       window.scrollTo(0, 0);
-               }
-               
-               // force dialog and it's contents to be visible
-               this._container.show();
-               this._content.children().show();
-               
-               // remove fixed content dimensions for calculation
-               this._content.css({
-                       height: 'auto',
-                       width: 'auto'
-               });
-               
-               // terminate concurrent rendering processes
-               this._container.stop();
-               this._content.stop();
-               
-               // set dialog to be fully opaque, prevents weird bugs in WebKit
-               this._container.show().css('opacity', 1.0);
-               
-               // handle positioning of form submit controls
-               var $heightDifference = 0;
-               if (this._content.find('.formSubmit').length) {
-                       $heightDifference = this._content.find('.formSubmit').outerHeight();
-                       
-                       this._content.addClass('dialogForm').css({ marginBottom: $heightDifference + 'px' });
-               }
-               else {
-                       this._content.removeClass('dialogForm').css({ marginBottom: '0px' });
-               }
-               
-               // force 800px or 90% width
-               var $windowDimensions = $(window).getDimensions();
-               if ($windowDimensions.width * 0.9 > 800) {
-                       this._container.css('maxWidth', '800px');
-               }
-               
-               // calculate dimensions
-               var $containerDimensions = this._container.getDimensions('outer');
-               var $contentDimensions = this._content.getDimensions();
-               
-               // calculate maximum content height
-               var $heightDifference = $containerDimensions.height - $contentDimensions.height;
-               var $maximumHeight = $windowDimensions.height - $heightDifference - 120;
-               this._content.css({ maxHeight: $maximumHeight + 'px' });
-               
-               this._determineOverflow();
-               
-               // calculate new dimensions
-               $containerDimensions = this._container.getDimensions('outer');
-               
-               // move container
-               var $leftOffset = Math.round(($windowDimensions.width - $containerDimensions.width) / 2);
-               var $topOffset = Math.round(($windowDimensions.height - $containerDimensions.height) / 2);
-               
-               // place container at 20% height if possible
-               var $desiredTopOffset = Math.round(($windowDimensions.height / 100) * 20);
-               if ($desiredTopOffset < $topOffset) {
-                       $topOffset = $desiredTopOffset;
-               }
-               
-               // apply offset
-               this._container.css({
-                       left: $leftOffset + 'px',
-                       top: $topOffset + 'px'
-               });
-               
-               // remove static dimensions
-               this._content.css({
-                       height: 'auto',
-                       width: 'auto'
-               });
-               
-               if (!this.isOpen()) {
-                       // hide container again
-                       this._container.hide();
-                       
-                       // fade in container
-                       this._container.wcfFadeIn($.proxy(function() {
-                               if (this.options.onShow !== null) {
-                                       this.options.onShow();
-                               }
-                       }, this));
-               }
-       },
-       
-       /**
-        * Determines content overflow based upon static dimensions.
-        */
-       _determineOverflow: function() {
-               var $max = $(window).getDimensions();
-               var $maxHeight = this._content.css('maxHeight');
-               this._content.css('maxHeight', 'none');
-               var $dialog = this._container.getDimensions('outer');
-               
-               var $overflow = 'visible';
-               if (($max.height * 0.8 < $dialog.height) || ($max.width * 0.8 < $dialog.width)) {
-                       $overflow = 'auto';
-               }
-               
-               this._content.css('overflow', $overflow);
-               this._content.css('maxHeight', $maxHeight);
-               
-               if ($overflow === 'visible') {
-                       // content may already overflow, even though the overall height is still below the threshold
-                       var $contentHeight = 0;
-                       this._content.children().each(function(index, child) {
-                               $contentHeight += $(child).outerHeight();
-                       });
-                       
-                       if (this._content.height() < $contentHeight) {
-                               this._content.css('overflow', 'auto');
-                       }
-               }
-       },
-       
-       /**
-        * Returns calculated content dimensions.
-        * 
-        * @param       integer         maximumHeight
-        * @return      object
-        */
-       _getContentDimensions: function(maximumHeight) {
-               var $contentDimensions = this._content.getDimensions();
-               
-               // set height to maximum height if exceeded
-               if (maximumHeight && $contentDimensions.height > maximumHeight) {
-                       $contentDimensions.height = maximumHeight;
-               }
-               
-               return $contentDimensions;
-       }
-});
-
-/**
- * Provides a slideshow for lists.
- */
-$.widget('ui.wcfSlideshow', {
-       /**
-        * button list object
-        * @var jQuery
-        */
-       _buttonList: null,
-       
-       /**
-        * number of items
-        * @var integer
-        */
-       _count: 0,
-       
-       /**
-        * item index
-        * @var integer
-        */
-       _index: 0,
-       
-       /**
-        * item list object
-        * @var jQuery
-        */
-       _itemList: null,
-       
-       /**
-        * list of items
-        * @var jQuery
-        */
-       _items: null,
-       
-       /**
-        * timer object
-        * @var WCF.PeriodicalExecuter
-        */
-       _timer: null,
-       
-       /**
-        * list item width
-        * @var integer
-        */
-       _width: 0,
-       
-       /**
-        * list of options
-        * @var object
-        */
-       options: {
-               /* enables automatic cycling of items */
-               cycle: true,
-               /* cycle interval in seconds */
-               cycleInterval: 5,
-               /* gap between items in pixels */
-               itemGap: 50,
-       },
-       
-       /**
-        * Creates a new instance of ui.wcfSlideshow.
-        */
-       _create: function() {
-               this._itemList = this.element.children('ul');
-               this._items = this._itemList.children('li');
-               this._count = this._items.length;
-               this._index = 0;
-               
-               if (this._count > 1) {
-                       this._initSlideshow();
-               }
-       },
-       
-       /**
-        * Initializes the slideshow.
-        */
-       _initSlideshow: function() {
-               // calculate item dimensions
-               var $itemHeight = $(this._items.get(0)).outerHeight();
-               this._items.addClass('slideshowItem');
-               this._width = this.element.css('height', $itemHeight).innerWidth();
-               this._itemList.addClass('slideshowItemList').css('left', 0);
-               
-               this._items.each($.proxy(function(index, item) {
-                       $(item).show().css({
-                               height: $itemHeight,
-                               left: ((this._width + this.options.itemGap) * index),
-                               width: this._width
-                       });
-               }, this));
-               
-               this.element.css({
-                       height: $itemHeight,
-                       width: this._width
-               }).hover($.proxy(this._hoverIn, this), $.proxy(this._hoverOut, this));
-               
-               // create toggle buttons
-               this._buttonList = $('<ul class="slideshowButtonList" />').appendTo(this.element);
-               for (var $i = 0; $i < this._count; $i++) {
-                       var $link = $('<li><a><span class="icon icon16 icon-circle" /></a></li>').data('index', $i).click($.proxy(this._click, this)).appendTo(this._buttonList);
-                       if ($i == 0) {
-                               $link.find('.icon').addClass('active');
-                       }
-               }
-               
-               this._resetTimer();
-               
-               $(window).resize($.proxy(this._resize, this));
-       },
-       
-       /**
-        * Handles browser resizing
-        */
-       _resize: function() {
-               this._width = this.element.css('width', 'auto').innerWidth();
-               this._items.each($.proxy(function(index, item) {
-                       $(item).css({
-                               left: ((this._width + this.options.itemGap) * index),
-                               width: this._width
-                       });
-               }, this));
-               
-               this._index--;
-               this.moveTo(null);
-       },
-       
-       /**
-        * Disables cycling while hovering.
-        */
-       _hoverIn: function() {
-               if (this._timer !== null) {
-                       this._timer.stop();
-               }
-       },
-       
-       /**
-        * Enables cycling after mouse out.
-        */
-       _hoverOut: function() {
-               this._resetTimer();
-       },
-       
-       /**
-        * Resets cycle timer.
-        */
-       _resetTimer: function() {
-               if (!this.options.cycle) {
-                       return;
-               }
-               
-               if (this._timer !== null) {
-                       this._timer.stop();
-               }
-               
-               var self = this;
-               this._timer = new WCF.PeriodicalExecuter(function() {
-                       self.moveTo(null);
-               }, this.options.cycleInterval * 1000);
-       },
-       
-       /**
-        * Handles clicks on the select buttons.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               this.moveTo($(event.currentTarget).data('index'));
-               
-               this._resetTimer();
-       },
-       
-       /**
-        * Moves to a specified item index, NULL will move to the next item in list.
-        * 
-        * @param       integer         index
-        */
-       moveTo: function(index) {
-               this._index = (index === null) ? this._index + 1 : index;
-               if (this._index == this._count) {
-                       this._index = 0;
-               }
-               
-               $(this._buttonList.find('.icon').removeClass('active').get(this._index)).addClass('active');
-               this._itemList.css('left', this._index * (this._width + this.options.itemGap) * -1);
-               
-               this._trigger('moveTo', null, { index: this._index });
-       },
-       
-       /**
-        * Returns item by index or null if index is invalid.
-        * 
-        * @return      jQuery
-        */
-       getItem: function(index) {
-               if (this._items[index]) {
-                       return this._items[index];
-               }
-               
-               return null;
-       }
-});
-
-/**
- * Custom tab menu implementation for WCF.
- */
-$.widget('ui.wcfTabs', $.ui.tabs, {
-       /**
-        * Workaround for ids containing a dot ".", until jQuery UI devs learn
-        * to properly escape ids ... (it took 18 months until they finally
-        * fixed it!)
-        * 
-        * @see http://bugs.jqueryui.com/ticket/4681
-        * @see $.ui.tabs.prototype._sanitizeSelector()
-        */
-       _sanitizeSelector: function(hash) {
-               return hash.replace(/([:\.])/g, '\\$1');
-       },
-       
-       /**
-        * @see $.ui.tabs.prototype.select()
-        */
-       select: function(index) {
-               if (!$.isNumeric(index)) {
-                       // panel identifier given
-                       this.panels.each(function(i, panel) {
-                               if ($(panel).wcfIdentify() === index) {
-                                       index = i;
-                                       return false;
-                               }
-                       });
-                       
-                       // unable to identify panel
-                       if (!$.isNumeric(index)) {
-                               console.debug("[ui.wcfTabs] Unable to find panel identified by '" + index + "', aborting.");
-                               return;
-                       }
-               }
-               
-               this._setOption('active', index);
-       },
-       
-       /**
-        * Selects a specific tab by triggering the 'click' event.
-        * 
-        * @param       string          tabIdentifier
-        */
-       selectTab: function(tabIdentifier) {
-               tabIdentifier = '#' + tabIdentifier;
-               
-               this.anchors.each(function(index, anchor) {
-                       var $anchor = $(anchor);
-                       if ($anchor.prop('hash') === tabIdentifier) {
-                               $anchor.trigger('click');
-                               return false;
-                       }
-               });
-       },
-       
-       /**
-        * Returns the currently selected tab index.
-        * 
-        * @return      integer
-        */
-       getCurrentIndex: function() {
-               return this.lis.index(this.lis.filter('.ui-tabs-selected'));
-       },
-       
-       /**
-        * Returns true if identifier is used by an anchor.
-        * 
-        * @param       string          identifier
-        * @param       boolean         isChildren
-        * @return      boolean
-        */
-       hasAnchor: function(identifier, isChildren) {
-               var $matches = false;
-               
-               this.anchors.each(function(index, anchor) {
-                       var $href = $(anchor).attr('href');
-                       if (/#.+/.test($href)) {
-                               // split by anchor
-                               var $parts = $href.split('#', 2);
-                               if (isChildren) {
-                                       $parts = $parts[1].split('-', 2);
-                               }
-                               
-                               if ($parts[1] === identifier) {
-                                       $matches = true;
-                                       
-                                       // terminate loop
-                                       return false;
-                               }
-                       }
-               });
-               
-               return $matches;
-       },
-       
-       /**
-        * Shows default tab.
-        */
-       revertToDefault: function() {
-               var $active = this.element.data('active');
-               if (!$active || $active === '') $active = 0;
-               
-               this.select($active);
-       },
-       
-       /**
-        * @see $.ui.tabs.prototype._processTabs()
-        */
-       _processTabs: function() {
-               var that = this;
-               
-               this.tablist = this._getList()
-                       .addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
-                       .attr( "role", "tablist" );
-               
-               this.tabs = this.tablist.find( "> li:has(a[href])" )
-                       .addClass( "ui-state-default ui-corner-top" )
-                       .attr({
-                               role: "tab",
-                               tabIndex: -1
-                       });
-               
-               this.anchors = this.tabs.map(function() {
-                               return $( "a", this )[ 0 ];
-                       })
-                       .addClass( "ui-tabs-anchor" )
-                       .attr({
-                               role: "presentation",
-                               tabIndex: -1
-                       });
-               
-               this.panels = $();
-               
-               this.anchors.each(function( i, anchor ) {
-                       var selector, panel,
-                               anchorId = $( anchor ).uniqueId().attr( "id" ),
-                               tab = $( anchor ).closest( "li" ),
-                               originalAriaControls = tab.attr( "aria-controls" );
-                       
-                       // inline tab
-                       selector = anchor.hash;
-                       panel = that.element.find( that._sanitizeSelector( selector ) );
-                       
-                       if ( panel.length) {
-                               that.panels = that.panels.add( panel );
-                       }
-                       if ( originalAriaControls ) {
-                               tab.data( "ui-tabs-aria-controls", originalAriaControls );
-                       }
-                       tab.attr({
-                               "aria-controls": selector.substring( 1 ),
-                               "aria-labelledby": anchorId
-                       });
-                       panel.attr( "aria-labelledby", anchorId );
-               });
-               
-               this.panels
-                       .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
-                       .attr( "role", "tabpanel" );
-       },
-       
-       /**
-        * @see $.ui.tabs.prototype.load()
-        */
-       load: function( index, event ) {
-               return;
-       }
-});
-
-/**
- * jQuery widget implementation of the wcf pagination.
- */
-$.widget('ui.wcfPages', {
-       SHOW_LINKS: 11,
-       SHOW_SUB_LINKS: 20,
-       
-       options: {
-               // vars
-               activePage: 1,
-               maxPage: 1,
-               
-               // language
-               // we use options here instead of language variables, because the paginator is not only usable with pages
-               nextPage: null,
-               previousPage: null
-       },
-       
-       /**
-        * Creates the pages widget.
-        */
-       _create: function() {
-               if (this.options.nextPage === null) this.options.nextPage = WCF.Language.get('wcf.global.page.next');
-               if (this.options.previousPage === null) this.options.previousPage = WCF.Language.get('wcf.global.page.previous');
-               
-               this.element.addClass('pageNavigation');
-               
-               this._render();
-       },
-       
-       /**
-        * Destroys the pages widget.
-        */
-       destroy: function() {
-               $.Widget.prototype.destroy.apply(this, arguments);
-               
-               this.element.children().remove();
-       },
-       
-       /**
-        * Renders the pages widget.
-        */
-       _render: function() {
-               // only render if we have more than 1 page
-               if (!this.options.disabled && this.options.maxPage > 1) {
-                       var $hasHiddenPages = false;
-                       
-                       // make sure pagination is visible
-                       if (this.element.hasClass('hidden')) {
-                               this.element.removeClass('hidden');
-                       }
-                       this.element.show();
-                       
-                       this.element.children().remove();
-                       
-                       var $pageList = $('<ul />');
-                       this.element.append($pageList);
-                       
-                       var $previousElement = $('<li class="button skip" />');
-                       $pageList.append($previousElement);
-                       
-                       if (this.options.activePage > 1) {
-                               var $previousLink = $('<a' + ((this.options.previousPage != null) ? (' title="' + this.options.previousPage + '"') : ('')) + '></a>');
-                               $previousElement.append($previousLink);
-                               this._bindSwitchPage($previousLink, this.options.activePage - 1);
-                               
-                               var $previousImage = $('<span class="icon icon16 icon-double-angle-left" />');
-                               $previousLink.append($previousImage);
-                       }
-                       else {
-                               var $previousImage = $('<span class="icon icon16 icon-double-angle-left" />');
-                               $previousElement.append($previousImage);
-                               $previousElement.addClass('disabled').removeClass('button');
-                               $previousImage.addClass('disabled');
-                       }
-                       
-                       // add first page
-                       $pageList.append(this._renderLink(1));
-                       
-                       // calculate page links
-                       var $maxLinks = this.SHOW_LINKS - 4;
-                       var $linksBefore = this.options.activePage - 2;
-                       if ($linksBefore < 0) $linksBefore = 0;
-                       var $linksAfter = this.options.maxPage - (this.options.activePage + 1);
-                       if ($linksAfter < 0) $linksAfter = 0;
-                       if (this.options.activePage > 1 && this.options.activePage < this.options.maxPage) $maxLinks--;
-                       
-                       var $half = $maxLinks / 2;
-                       var $left = this.options.activePage;
-                       var $right = this.options.activePage;
-                       if ($left < 1) $left = 1;
-                       if ($right < 1) $right = 1;
-                       if ($right > this.options.maxPage - 1) $right = this.options.maxPage - 1;
-                       
-                       if ($linksBefore >= $half) {
-                               $left -= $half;
-                       }
-                       else {
-                               $left -= $linksBefore;
-                               $right += $half - $linksBefore;
-                       }
-                       
-                       if ($linksAfter >= $half) {
-                               $right += $half;
-                       }
-                       else {
-                               $right += $linksAfter;
-                               $left -= $half - $linksAfter;
-                       }
-                       
-                       $right = Math.ceil($right);
-                       $left = Math.ceil($left);
-                       if ($left < 1) $left = 1;
-                       if ($right > this.options.maxPage) $right = this.options.maxPage;
-                       
-                       // left ... links
-                       if ($left > 1) {
-                               if ($left - 1 < 2) {
-                                       $pageList.append(this._renderLink(2));
-                               }
-                               else {
-                                       $('<li class="button jumpTo"><a title="' + WCF.Language.get('wcf.global.page.jumpTo') + '" class="jsTooltip">...</a></li>').appendTo($pageList);
-                                       $hasHiddenPages = true;
-                               }
-                       }
-                       
-                       // visible links
-                       for (var $i = $left + 1; $i < $right; $i++) {
-                               $pageList.append(this._renderLink($i));
-                       }
-                       
-                       // right ... links
-                       if ($right < this.options.maxPage) {
-                               if (this.options.maxPage - $right < 2) {
-                                       $pageList.append(this._renderLink(this.options.maxPage - 1));
-                               }
-                               else {
-                                       $('<li class="button jumpTo"><a title="' + WCF.Language.get('wcf.global.page.jumpTo') + '" class="jsTooltip">...</a></li>').appendTo($pageList);
-                                       $hasHiddenPages = true;
-                               }
-                       }
-                       
-                       // add last page
-                       $pageList.append(this._renderLink(this.options.maxPage));
-                       
-                       // add next button
-                       var $nextElement = $('<li class="button skip" />');
-                       $pageList.append($nextElement);
-                       
-                       if (this.options.activePage < this.options.maxPage) {
-                               var $nextLink = $('<a' + ((this.options.nextPage != null) ? (' title="' + this.options.nextPage + '"') : ('')) + '></a>');
-                               $nextElement.append($nextLink);
-                               this._bindSwitchPage($nextLink, this.options.activePage + 1);
-                               
-                               var $nextImage = $('<span class="icon icon16 icon-double-angle-right" />');
-                               $nextLink.append($nextImage);
-                       }
-                       else {
-                               var $nextImage = $('<span class="icon icon16 icon-double-angle-right" />');
-                               $nextElement.append($nextImage);
-                               $nextElement.addClass('disabled').removeClass('button');
-                               $nextImage.addClass('disabled');
-                       }
-                       
-                       if ($hasHiddenPages) {
-                               $pageList.data('pages', this.options.maxPage);
-                               WCF.System.PageNavigation.init('#' + $pageList.wcfIdentify(), $.proxy(function(pageNo) {
-                                       this.switchPage(pageNo);
-                               }, this));
-                       }
-               }
-               else {
-                       // otherwise hide the paginator if not already hidden
-                       this.element.hide();
-               }
-       },
-       
-       /**
-        * Renders a page link.
-        * 
-        * @parameter   integer         page
-        * @return      jQuery
-        */
-       _renderLink: function(page, lineBreak) {
-               var $pageElement = $('<li class="button"></li>');
-               if (lineBreak != undefined && lineBreak) {
-                       $pageElement.addClass('break');
-               }
-               if (page != this.options.activePage) {
-                       var $pageLink = $('<a>' + WCF.String.addThousandsSeparator(page) + '</a>'); 
-                       $pageElement.append($pageLink);
-                       this._bindSwitchPage($pageLink, page);
-               }
-               else {
-                       $pageElement.addClass('active');
-                       var $pageSubElement = $('<span>' + WCF.String.addThousandsSeparator(page) + '</span>');
-                       $pageElement.append($pageSubElement);
-               }
-               
-               return $pageElement;
-       },
-       
-       /**
-        * Binds the 'click'-event for the page switching to the given element.
-        * 
-        * @parameter   $(element)      element
-        * @paremeter   integer         page
-        */
-       _bindSwitchPage: function(element, page) {
-               var $self = this;
-               element.click(function() {
-                       $self.switchPage(page);
-               });
-       },
-       
-       /**
-        * Switches to the given page
-        * 
-        * @parameter   Event           event
-        * @parameter   integer         page
-        */
-       switchPage: function(page) {
-               this._setOption('activePage', page);
-       },
-       
-       /**
-        * Sets the given option to the given value.
-        * See the jQuery UI widget documentation for more.
-        */
-       _setOption: function(key, value) {
-               if (key == 'activePage') {
-                       if (value != this.options[key] && value > 0 && value <= this.options.maxPage) {
-                               // you can prevent the page switching by returning false or by event.preventDefault()
-                               // in a shouldSwitch-callback. e.g. if an AJAX request is already running.
-                               var $result = this._trigger('shouldSwitch', undefined, {
-                                       nextPage: value
-                               });
-                               
-                               if ($result || $result !== undefined) {
-                                       this.options[key] = value;
-                                       this._render();
-                                       this._trigger('switched', undefined, {
-                                               activePage: value
-                                       });
-                               }
-                               else {
-                                       this._trigger('notSwitched', undefined, {
-                                               activePage: value
-                                       });
-                               }
-                       }
-               }
-               else {
-                       this.options[key] = value;
-                       
-                       if (key == 'disabled') {
-                               if (value) {
-                                       this.element.children().remove();
-                               }
-                               else {
-                                       this._render();
-                               }
-                       }
-                       else if (key == 'maxPage') {
-                               this._render();
-                       }
-               }
-               
-               return this;
-       },
-       
-       /**
-        * Start input of pagenumber
-        * 
-        * @parameter   Event           event
-        */
-       _startInput: function(event) {
-               // hide a-tag
-               var $childLink = $(event.currentTarget);
-               if (!$childLink.is('a')) $childLink = $childLink.parent('a');
-               
-               $childLink.hide();
-               
-               // show input-tag
-               var $childInput = $childLink.parent('li').children('input')
-                       .css('display', 'block')
-                       .val('');
-               
-               $childInput.focus();
-       },
-       
-       /**
-        * Stops input of pagenumber
-        * 
-        * @parameter   Event           event
-        */
-       _stopInput: function(event) {
-               // hide input-tag
-               var $childInput = $(event.currentTarget);
-               $childInput.css('display', 'none');
-               
-               // show a-tag
-               var $childContainer = $childInput.parent('li');
-               if ($childContainer != undefined && $childContainer != null) {
-                       $childContainer.children('a').show();
-               }
-       },
-       
-       /**
-        * Handles input of pagenumber
-        * 
-        * @parameter   Event           event
-        */
-       _handleInput: function(event) {
-               var $ie7 = ($.browser.msie && $.browser.version == '7.0');
-               if (event.type != 'keyup' || $ie7) {
-                       if (!$ie7 || ((event.which == 13 || event.which == 27) && event.type == 'keyup')) {
-                               if (event.which == 13) {
-                                       this.switchPage(parseInt($(event.currentTarget).val()));
-                               }
-                               
-                               if (event.which == 13 || event.which == 27) {
-                                       this._stopInput(event);
-                                       event.stopPropagation();
-                               }
-                       }
-               }
-       }
-});
-
-/**
- * Namespace for category related classes.
- */
-WCF.Category = { };
-
-/**
- * Handles selection of categories.
- */
-WCF.Category.NestedList = Class.extend({
-       /**
-        * list of categories
-        * @var object
-        */
-       _categories: { },
-       
-       /**
-        * Initializes the WCF.Category.NestedList object.
-        */
-       init: function() {
-               var self = this;
-               $('.jsCategory').each(function(index, category) {
-                       var $category = $(category).data('parentCategoryID', null).change($.proxy(self._updateSelection, self));
-                       self._categories[$category.val()] = $category;
-                       
-                       // find child categories
-                       var $childCategoryIDs = [ ];
-                       $category.parents('li').find('.jsChildCategory').each(function(innerIndex, childCategory) {
-                               var $childCategory = $(childCategory).data('parentCategoryID', $category.val()).change($.proxy(self._updateSelection, self));
-                               self._categories[$childCategory.val()] = $childCategory;
-                               $childCategoryIDs.push($childCategory.val());
-                               
-                               if ($childCategory.is(':checked')) {
-                                       $category.prop('checked', 'checked');
-                               }
-                       });
-                       
-                       $category.data('childCategoryIDs', $childCategoryIDs);
-               });
-       },
-       
-       /**
-        * Updates selection of categories.
-        * 
-        * @param       object          event
-        */
-       _updateSelection: function(event) {
-               var $category = $(event.currentTarget);
-               var $parentCategoryID = $category.data('parentCategoryID');
-               
-               if ($category.is(':checked')) {
-                       // child category
-                       if ($parentCategoryID !== null) {
-                               // mark parent category as checked
-                               this._categories[$parentCategoryID].prop('checked', 'checked');
-                       }
-               }
-               else {
-                       // top-level category
-                       if ($parentCategoryID === null) {
-                               // unmark all child categories
-                               var $childCategoryIDs = $category.data('childCategoryIDs');
-                               for (var $i = 0, $length = $childCategoryIDs.length; $i < $length; $i++) {
-                                       this._categories[$childCategoryIDs[$i]].prop('checked', false);
-                               }
-                       }
-               }
-       }
-});
-
-/**
- * Encapsulate eval() within an own function to prevent problems
- * with optimizing and minifiny JS.
- * 
- * @param      mixed           expression
- * @returns    mixed
- */
-function wcfEval(expression) {
-       return eval(expression);
-}
-
-
-// WCF.Like.js
-/**
- * Like support for WCF
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2014 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- */
-WCF.Like = Class.extend({
-       /**
-        * true, if users can like their own content
-        * @var boolean
-        */
-       _allowForOwnContent: false,
-       
-       /**
-        * user can like
-        * @var boolean
-        */
-       _canLike: false,
-       
-       /**
-        * list of containers
-        * @var object
-        */
-       _containers: { },
-       
-       /**
-        * container meta data
-        * @var object
-        */
-       _containerData: { },
-       
-       /**
-        * enables the dislike option
-        */
-       _enableDislikes: true,
-       
-       /**
-        * prevents like/dislike until the server responded
-        * @var boolean
-        */
-       _isBusy: false,
-       
-       /**
-        * cached grouped user lists for like details
-        * @var object
-        */
-       _likeDetails: { },
-       
-       /**
-        * proxy object
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * shows the detailed summary of users who liked the object
-        * @var boolean
-        */
-       _showSummary: true,
-       
-       /**
-        * Initializes like support.
-        * 
-        * @param       boolean         canLike
-        * @param       boolean         enableDislikes
-        * @param       boolean         showSummary
-        * @param       boolean         allowForOwnContent
-        */
-       init: function(canLike, enableDislikes, showSummary, allowForOwnContent) {
-               this._canLike = canLike;
-               this._enableDislikes = enableDislikes;
-               this._isBusy = false;
-               this._likeDetails = { };
-               this._showSummary = showSummary;
-               this._allowForOwnContent = allowForOwnContent;
-               
-               var $containers = this._getContainers();
-               this._initContainers($containers);
-               
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               // bind dom node inserted listener
-               var $date = new Date();
-               var $identifier = $date.toString().hashCode + $date.getUTCMilliseconds();
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.Like' + $identifier, $.proxy(this._domNodeInserted, this));
-       },
-       
-       /**
-        * Initialize containers once new nodes are inserted.
-        */
-       _domNodeInserted: function() {
-               var $containers = this._getContainers();
-               this._initContainers($containers);
-               
-       },
-       
-       /**
-        * Initializes like containers.
-        * 
-        * @param       object          containers
-        */
-       _initContainers: function(containers) {
-               var $createdWidgets = false;
-               containers.each($.proxy(function(index, container) {
-                       // set container
-                       var $container = $(container);
-                       var $containerID = $container.wcfIdentify();
-                       
-                       if (!this._containers[$containerID]) {
-                               this._containers[$containerID] = $container;
-                               
-                               // set container data
-                               this._containerData[$containerID] = {
-                                       'likeButton': null,
-                                       'badge': null,
-                                       'dislikeButton': null,
-                                       'likes': $container.data('like-likes'),
-                                       'dislikes': $container.data('like-dislikes'),
-                                       'objectType': $container.data('objectType'),
-                                       'objectID': this._getObjectID($containerID),
-                                       'users': eval($container.data('like-users')),
-                                       'liked': $container.data('like-liked')
-                               };
-                               
-                               // create UI
-                               this._createWidget($containerID);
-                               
-                               $createdWidgets = true;
-                       }
-               }, this));
-               
-               if ($createdWidgets) {
-                       new WCF.PeriodicalExecuter(function(pe) {
-                               pe.stop();
-                               
-                               WCF.DOMNodeInsertedHandler.execute();
-                       }, 250);
-               }
-       },
-       
-       /**
-        * Returns a list of available object containers.
-        * 
-        * @return      jQuery
-        */
-       _getContainers: function() { },
-       
-       /**
-        * Returns widget container for target object container.
-        * 
-        * @param       string          containerID
-        * @return      jQuery
-        */
-       _getWidgetContainer: function(containerID) { },
-       
-       /**
-        * Returns object id for targer object container.
-        * 
-        * @param       string          containerID
-        * @return      integer
-        */
-       _getObjectID: function(containerID) { },
-       
-       /**
-        * Adds the like widget.
-        * 
-        * @param       integer         containerID
-        * @param       jQuery          widget
-        */
-       _addWidget: function(containerID, widget) {
-               var $widgetContainer = this._getWidgetContainer(containerID);
-               
-               widget.appendTo($widgetContainer);
-       },
-       
-       /**
-        * Builds the like widget.
-        * 
-        * @param       integer         containerID
-        * @param       jQuery          likeButton
-        * @param       jQuery          dislikeButton
-        * @param       jQuery          badge
-        * @param       jQuery          summary
-        */
-       _buildWidget: function(containerID, likeButton, dislikeButton, badge, summary) {
-               var $widget = $('<aside class="likesWidget"><ul></ul></aside>');
-               if (this._canLike) {
-                       likeButton.appendTo($widget.find('ul'));
-                       dislikeButton.appendTo($widget.find('ul'));
-               }
-               badge.appendTo($widget);
-               
-               this._addWidget(containerID, $widget); 
-       },
-       
-       /**
-        * Creates the like widget.
-        * 
-        * @param       integer         containerID
-        */
-       _createWidget: function(containerID) {
-               var $likeButton = $('<li class="likeButton"><a title="'+WCF.Language.get('wcf.like.button.like')+'" class="jsTooltip"><span class="icon icon16 icon-thumbs-up-alt" /> <span class="invisible">'+WCF.Language.get('wcf.like.button.like')+'</span></a></li>');
-               var $dislikeButton = $('<li class="dislikeButton"><a title="'+WCF.Language.get('wcf.like.button.dislike')+'" class="jsTooltip"><span class="icon icon16 icon-thumbs-down-alt" /> <span class="invisible">'+WCF.Language.get('wcf.like.button.dislike')+'</span></a></li>');
-               if (!this._enableDislikes) $dislikeButton.hide();
-               
-               if (!this._allowForOwnContent && (WCF.User.userID == this._containers[containerID].data('userID'))) {
-                       $likeButton = $('');
-                       $dislikeButton = $('');
-               }
-               
-               var $badge = $('<a class="badge jsTooltip likesBadge" />').data('containerID', containerID).click($.proxy(this._showLikeDetails, this));
-               
-               var $summary = null;
-               if (this._showSummary) {
-                       $summary = $('<p class="likesSummary"><span class="pointer" /></p>');
-                       $summary.children('span').data('containerID', containerID).click($.proxy(this._showLikeDetails, this));
-               }
-               this._buildWidget(containerID, $likeButton, $dislikeButton, $badge, $summary);
-               
-               this._containerData[containerID].likeButton = $likeButton;
-               this._containerData[containerID].dislikeButton = $dislikeButton;
-               this._containerData[containerID].badge = $badge;
-               this._containerData[containerID].summary = $summary;
-               
-               $likeButton.data('containerID', containerID).data('type', 'like').click($.proxy(this._click, this));
-               $dislikeButton.data('containerID', containerID).data('type', 'dislike').click($.proxy(this._click, this));
-               this._setActiveState($likeButton, $dislikeButton, this._containerData[containerID].liked);
-               this._updateBadge(containerID);
-               if (this._showSummary) this._updateSummary(containerID);
-       },
-       
-       /**
-        * Displays like details for an object.
-        * 
-        * @param       object          event
-        * @param       string          containerID
-        */
-       _showLikeDetails: function(event, containerID) {
-               var $containerID = (event === null) ? containerID : $(event.currentTarget).data('containerID');
-               
-               if (this._likeDetails[$containerID] === undefined) {
-                       this._likeDetails[$containerID] = new WCF.User.List('wcf\\data\\like\\LikeAction', WCF.Language.get('wcf.like.details'), {
-                               data: {
-                                       containerID: $containerID,
-                                       objectID: this._containerData[$containerID].objectID,
-                                       objectType: this._containerData[$containerID].objectType
-                               }
-                       });
-               }
-               
-               this._likeDetails[$containerID].open();
-       },
-       
-       /**
-        * Handles likes and dislikes.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               var $button = $(event.currentTarget);
-               if ($button === null) {
-                       console.debug("[WCF.Like] Unable to find target button, aborting.");
-                       return;
-               }
-               
-               this._sendRequest($button.data('containerID'), $button.data('type'));
-       },
-       
-       /**
-        * Sends request through proxy.
-        * 
-        * @param       integer         containerID
-        * @param       string          type
-        */
-       _sendRequest: function(containerID, type) {
-               // ignore retards spamming clicks on the buttons
-               if (this._isBusy) {
-                       return;
-               }
-               
-               this._isBusy = true;
-               
-               this._proxy.setOption('data', {
-                       actionName: type,
-                       className: 'wcf\\data\\like\\LikeAction',
-                       parameters: {
-                               data: {
-                                       containerID: containerID,
-                                       objectID: this._containerData[containerID].objectID,
-                                       objectType: this._containerData[containerID].objectType
-                               }
-                       }
-               });
-               
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Updates likeable object.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       object          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               var $containerID = data.returnValues.containerID;
-               
-               if (!this._containers[$containerID]) {
-                       return;
-               }
-               
-               switch (data.actionName) {
-                       case 'dislike':
-                       case 'like':
-                               // update container data
-                               this._containerData[$containerID].likes = parseInt(data.returnValues.likes);
-                               this._containerData[$containerID].dislikes = parseInt(data.returnValues.dislikes);
-                               this._containerData[$containerID].users = data.returnValues.users;
-                               
-                               // update label
-                               this._updateBadge($containerID);
-                               // update summary
-                               if (this._showSummary) this._updateSummary($containerID);
-                               
-                               // mark button as active
-                               var $likeButton = this._containerData[$containerID].likeButton;
-                               var $dislikeButton = this._containerData[$containerID].dislikeButton;
-                               var $likeStatus = 0;
-                               if (data.returnValues.isLiked) $likeStatus = 1;
-                               else if (data.returnValues.isDisliked) $likeStatus = -1;
-                               this._setActiveState($likeButton, $dislikeButton, $likeStatus);
-                               
-                               // invalidate cache for like details
-                               if (this._likeDetails[$containerID] !== undefined) {
-                                       delete this._likeDetails[$containerID];
-                               }
-                               
-                               this._isBusy = false;
-                       break;
-               }
-       },
-       
-       _updateBadge: function(containerID) {
-               if (!this._containerData[containerID].likes && !this._containerData[containerID].dislikes) {
-                       this._containerData[containerID].badge.hide();
-               }
-               else {
-                       this._containerData[containerID].badge.show();
-                       
-                       // update like counter
-                       var $cumulativeLikes = this._containerData[containerID].likes - this._containerData[containerID].dislikes;
-                       var $badge = this._containerData[containerID].badge;
-                       $badge.removeClass('green red');
-                       if ($cumulativeLikes > 0) {
-                               $badge.text('+' + WCF.String.formatNumeric($cumulativeLikes));
-                               $badge.addClass('green');
-                       }
-                       else if ($cumulativeLikes < 0) {
-                               $badge.text(WCF.String.formatNumeric($cumulativeLikes));
-                               $badge.addClass('red');
-                       }
-                       else {
-                               $badge.text('\u00B10');
-                       }
-                       
-                       // update tooltip
-                       var $likes = this._containerData[containerID].likes;
-                       var $dislikes = this._containerData[containerID].dislikes;
-                       $badge.data('tooltip', WCF.Language.get('wcf.like.tooltip', { likes: $likes, dislikes: $dislikes }));
-               }
-       },
-       
-       _updateSummary: function(containerID) {
-               if (!this._containerData[containerID].likes) {
-                       this._containerData[containerID].summary.hide();
-               }
-               else {
-                       this._containerData[containerID].summary.show();
-                       
-                       var $users = this._containerData[containerID].users;
-                       var $userArray = [];
-                       for (var $userID in $users) $userArray.push($users[$userID].username);
-                       var $others = this._containerData[containerID].likes - $userArray.length;
-                       
-                       this._containerData[containerID].summary.children('span').html(WCF.Language.get('wcf.like.summary', { users: $userArray, others: $others }));
-               }
-       },
-       
-       /**
-        * Sets button active state.
-        * 
-        * @param       jquery          likeButton
-        * @param       jquery          dislikeButton
-        * @param       integer         likeStatus
-        */
-       _setActiveState: function(likeButton, dislikeButton, likeStatus) {
-               likeButton.removeClass('active');
-               dislikeButton.removeClass('active');
-               
-               if (likeStatus == 1) {
-                       likeButton.addClass('active');
-               }
-               else if (likeStatus == -1) {
-                       dislikeButton.addClass('active');
-               }
-       }
-});
-
-
-// WCF.ACL.js
-/**
- * Namespace for ACL
- */
-WCF.ACL = { };
-
-/**
- * ACL support for WCF
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2014 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- */
-WCF.ACL.List = Class.extend({
-       /**
-        * name of the category the acl options belong to
-        * @var string
-        */
-       _categoryName: '',
-       
-       /**
-        * ACL container
-        * @var jQuery
-        */
-       _container: null,
-       
-       /**
-        * list of ACL container elements
-        * @var object
-        */
-       _containerElements: { },
-       
-       /**
-        * object id
-        * @var integer
-        */
-       _objectID: 0,
-       
-       /**
-        * object type id
-        * @var integer
-        */
-       _objectTypeID: null,
-       
-       /**
-        * list of available ACL options
-        * @var object
-        */
-       _options: { },
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * user search handler
-        * @var WCF.Search.User
-        */
-       _search: null,
-       
-       /**
-        * list of ACL settings
-        * @var object
-        */
-       _values: {
-               group: { },
-               user: { }
-       },
-       
-       /**
-        * Initializes the ACL configuration.
-        * 
-        * @param       string          containerSelector
-        * @param       integer         objectTypeID
-        * @param       string          categoryName
-        * @param       integer         objectID
-        * @param       boolean         includeUserGroups
-        */
-       init: function(containerSelector, objectTypeID, categoryName, objectID, includeUserGroups, initialPermissions) {
-               this._objectID = objectID || 0;
-               this._objectTypeID = objectTypeID;
-               this._categoryName = categoryName;
-               if (includeUserGroups === undefined) {
-                       includeUserGroups = true;
-               }
-               this._values = {
-                       group: { },
-                       user: { }
-               };
-               
-               this._proxy = new WCF.Action.Proxy({
-                       showLoadingOverlay: false,
-                       success: $.proxy(this._success, this)
-               });
-               
-               // bind hidden container
-               this._container = $(containerSelector).hide().addClass('aclContainer');
-               
-               // insert container elements
-               var $elementContainer = this._container.children('dd');
-               var $aclList = $('<ul class="aclList container" />').appendTo($elementContainer);
-               var $searchInput = $('<input type="text" class="long" placeholder="' + WCF.Language.get('wcf.acl.search.' + (!includeUserGroups ? 'user.' : '') + 'description') + '" />').appendTo($elementContainer);
-               var $permissionList = $('<ul class="aclPermissionList container" />').hide().appendTo($elementContainer);
-               
-               // set elements
-               this._containerElements = {
-                       aclList: $aclList,
-                       denyAll: null,
-                       grantAll: null,
-                       permissionList: $permissionList,
-                       searchInput: $searchInput
-               };
-               
-               // prepare search input
-               this._search = new WCF.Search.User($searchInput, $.proxy(this.addObject, this), includeUserGroups);
-               
-               // bind event listener for submit
-               var $form = this._container.parents('form:eq(0)');
-               $form.submit($.proxy(this.submit, this));
-               
-               // reset ACL on click
-               var $resetButton = $form.find('input[type=reset]:eq(0)');
-               if ($resetButton.length) {
-                       $resetButton.click($.proxy(this._reset, this));
-               }
-               
-               if (initialPermissions) {
-                       this._success(initialPermissions);
-               }
-               else {
-                       this._loadACL();
-               }
-       },
-       
-       /**
-        * Restores the original ACL state.
-        */
-       _reset: function() {
-               // reset stored values
-               this._values = {
-                       group: { },
-                       user: { }
-               };
-               
-               // remove entries
-               this._containerElements.aclList.empty();
-               this._containerElements.searchInput.val('');
-               
-               // deselect all input elements
-               this._containerElements.permissionList.hide().find('input[type=checkbox]').prop('checked', false);
-       },
-       
-       /**
-        * Loads current ACL configuration.
-        */
-       _loadACL: function() {
-               this._proxy.setOption('data', {
-                       actionName: 'loadAll',
-                       className: 'wcf\\data\\acl\\option\\ACLOptionAction',
-                       parameters: {
-                               categoryName: this._categoryName,
-                               objectID: this._objectID,
-                               objectTypeID: this._objectTypeID
-                       }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Adds a new object to acl list.
-        * 
-        * @param       object          data
-        */
-       addObject: function(data) {
-               var $listItem = this._createListItem(data.objectID, data.label, data.type);
-               
-               // toggle element
-               this._savePermissions();
-               this._containerElements.aclList.children('li').removeClass('active');
-               $listItem.addClass('active');
-               
-               this._search.addExcludedSearchValue(data.label);
-               
-               // uncheck all option values
-               this._containerElements.permissionList.find('input[type=checkbox]').prop('checked', false);
-               
-               // clear search input
-               this._containerElements.searchInput.val('');
-               
-               // show permissions
-               this._containerElements.permissionList.show();
-               
-               WCF.DOMNodeInsertedHandler.execute();
-       },
-       
-       /**
-        * Creates a list item with the given data and returns it.
-        * 
-        * @param       integer         objectID
-        * @param       string          label
-        * @param       string          type
-        * @return      jQuery
-        */
-       _createListItem: function(objectID, label, type) {
-               var $listItem = $('<li><span class="icon icon16 icon-' + (type === 'group' ? 'group' : 'user') + '" /> <span>' + label + '</span></li>').appendTo(this._containerElements.aclList);
-               $listItem.data('objectID', objectID).data('type', type).data('label', label).click($.proxy(this._click, this));
-               $('<span class="icon icon16 icon-remove jsTooltip pointer" title="' + WCF.Language.get('wcf.global.button.delete') + '" />').click($.proxy(this._removeItem, this)).appendTo($listItem);
-               
-               return $listItem;
-       },
-       
-       /**
-        * Removes an item from list.
-        * 
-        * @param       object          event
-        */
-       _removeItem: function(event) {
-               var $listItem = $(event.currentTarget).parent();
-               var $type = $listItem.data('type');
-               var $objectID = $listItem.data('objectID');
-               
-               this._search.removeExcludedSearchValue($listItem.data('label'));
-               $listItem.remove();
-               
-               // remove stored data
-               if (this._values[$type][$objectID]) {
-                       delete this._values[$type][$objectID];
-               }
-               
-               // try to select something else
-               this._selectFirstEntry();
-       },
-       
-       /**
-        * Selects the first available entry.
-        */
-       _selectFirstEntry: function() {
-               var $listItem = this._containerElements.aclList.children('li:eq(0)');
-               if ($listItem.length) {
-                       this._select($listItem, false);
-               }
-               else {
-                       this._reset();
-               }
-       },
-       
-       /**
-        * Parses current ACL configuration.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               if (!$.getLength(data.returnValues.options)) {
-                       return;
-               }
-               
-               // prepare options
-               var $count = 0;
-               var $structure = { };
-               for (var $optionID in data.returnValues.options) {
-                       var $option = data.returnValues.options[$optionID];
-                       
-                       var $listItem = $('<li><span>' + $option.label + '</span></li>').data('optionID', $optionID).data('optionName', $option.optionName);
-                       var $grantPermission = $('<input type="checkbox" id="grant' + $optionID + '" />').appendTo($listItem).wrap('<label for="grant' + $optionID + '" class="jsTooltip" title="' + WCF.Language.get('wcf.acl.option.grant') + '" />');
-                       var $denyPermission = $('<input type="checkbox" id="deny' + $optionID + '" />').appendTo($listItem).wrap('<label for="deny' + $optionID + '" class="jsTooltip" title="' + WCF.Language.get('wcf.acl.option.deny') + '" />');
-                       
-                       $grantPermission.data('type', 'grant').data('optionID', $optionID).change($.proxy(this._change, this));
-                       $denyPermission.data('type', 'deny').data('optionID', $optionID).change($.proxy(this._change, this));
-                       
-                       if (!$structure[$option.categoryName]) {
-                               $structure[$option.categoryName] = [ ];
-                       }
-                       
-                       if ($option.categoryName === '') {
-                               $listItem.appendTo(this._containerElements.permissionList);
-                       }
-                       else {
-                               $structure[$option.categoryName].push($listItem);
-                       }
-                       
-                       $count++;
-               }
-               
-               // add a "full access" permission if there are more than one option
-               if ($count > 1) {
-                       var $listItem = $('<li class="aclFullAccess"><span>' + WCF.Language.get('wcf.acl.option.fullAccess') + '</span></li>').prependTo(this._containerElements.permissionList);
-                       this._containerElements.grantAll = $('<input type="checkbox" id="grantAll" />').appendTo($listItem).wrap('<label for="grantAll" class="jsTooltip" title="' + WCF.Language.get('wcf.acl.option.grant') + '" />');
-                       this._containerElements.denyAll = $('<input type="checkbox" id="denyAll" />').appendTo($listItem).wrap('<label for="denyAll" class="jsTooltip" title="' + WCF.Language.get('wcf.acl.option.deny') + '" />');
-                       
-                       // bind events
-                       this._containerElements.grantAll.data('type', 'grant').change($.proxy(this._changeAll, this));
-                       this._containerElements.denyAll.data('type', 'deny').change($.proxy(this._changeAll, this));
-               }
-               
-               if ($.getLength($structure)) {
-                       for (var $categoryName in $structure) {
-                               var $listItems = $structure[$categoryName];
-                               
-                               if (data.returnValues.categories[$categoryName]) {
-                                       $('<li class="aclCategory">' + data.returnValues.categories[$categoryName] + '</li>').appendTo(this._containerElements.permissionList);
-                               }
-                               
-                               for (var $i = 0, $length = $listItems.length; $i < $length; $i++) {
-                                       $listItems[$i].appendTo(this._containerElements.permissionList);
-                               }
-                       }
-               }
-               
-               // set data
-               this._parseData(data, 'group');
-               this._parseData(data, 'user');
-               
-               // show container
-               this._container.show();
-               
-               // pre-select an entry
-               this._selectFirstEntry();
-       },
-       
-       /**
-        * Parses user and group data.
-        * 
-        * @param       object          data
-        * @param       string          type
-        */
-       _parseData: function(data, type) {
-               if (!$.getLength(data.returnValues[type].option)) {
-                       return;
-               }
-               
-               // add list items
-               for (var $typeID in data.returnValues[type].label) {
-                       this._createListItem($typeID, data.returnValues[type].label[$typeID], type);
-                       
-                       this._search.addExcludedSearchValue(data.returnValues[type].label[$typeID]);
-               }
-               
-               // add options
-               this._values[type] = data.returnValues[type].option;
-               
-               WCF.DOMNodeInsertedHandler.execute();
-       },
-       
-       /**
-        * Prepares permission list for a specific object.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               var $listItem = $(event.currentTarget);
-               if ($listItem.hasClass('active')) {
-                       return;
-               }
-               
-               this._select($listItem, true);
-       },
-       
-       /**
-        * Selects the given item and marks it as active.
-        * 
-        * @param       jQuery          listItem
-        * @param       boolean         savePermissions
-        */
-       _select: function(listItem, savePermissions) {
-               // save previous permissions
-               if (savePermissions) {
-                       this._savePermissions();
-               }
-               
-               // switch active item
-               this._containerElements.aclList.children('li').removeClass('active');
-               listItem.addClass('active');
-               
-               // apply permissions for current item
-               this._setupPermissions(listItem.data('type'), listItem.data('objectID'));
-       },
-       
-       /**
-        * Toggles between deny and grant.
-        * 
-        * @param       object          event
-        */
-       _change: function(event) {
-               var $checkbox = $(event.currentTarget);
-               var $optionID = $checkbox.data('optionID');
-               var $type = $checkbox.data('type');
-               
-               if ($checkbox.is(':checked')) {
-                       if ($type === 'deny') {
-                               $('#grant' + $optionID).prop('checked', false);
-                               
-                               if (this._containerElements.grantAll !== null) {
-                                       this._containerElements.grantAll.prop('checked', false);
-                               }
-                       }
-                       else {
-                               $('#deny' + $optionID).prop('checked', false);
-                               
-                               if (this._containerElements.denyAll !== null) {
-                                       this._containerElements.denyAll.prop('checked', false);
-                               }
-                       }
-               }
-               else {
-                       if ($type === 'deny' && this._containerElements.denyAll !== null) {
-                               this._containerElements.denyAll.prop('checked', false);
-                       }
-                       else if ($type === 'grant' && this._containerElements.grantAll !== null) {
-                               this._containerElements.grantAll.prop('checked', false);
-                       }
-               }
-               
-               var $allChecked = true;
-               this._containerElements.permissionList.find('input[type=checkbox]').each(function(index, item) {
-                       var $item = $(item);
-                       
-                       if ($item.data('type') === $type && $item.attr('id') !== $type + 'All') {
-                               if (!$item.is(':checked')) {
-                                       $allChecked = false;
-                                       return false;
-                               }
-                       }
-               });
-               if ($type == 'deny') {
-                       if (this._containerElements.denyAll !== null) {
-                               if ($allChecked) this._containerElements.denyAll.prop('checked', true);
-                               else this._containerElements.denyAll.prop('checked', false);
-                       }
-               }
-               else {
-                       if (this._containerElements.grantAll !== null) {
-                               if ($allChecked) this._containerElements.grantAll.prop('checked', true);
-                               else this._containerElements.grantAll.prop('checked', false);
-                       }
-               }
-       },
-       
-       /**
-        * Toggles all options between deny and grant.
-        * 
-        * @param       object          event
-        */
-       _changeAll: function(event) {
-               var $checkbox = $(event.currentTarget);
-               var $type = $checkbox.data('type');
-               
-               if ($checkbox.is(':checked')) {
-                       if ($type === 'deny') {
-                               this._containerElements.grantAll.prop('checked', false);
-                               
-                               this._containerElements.permissionList.find('input[type=checkbox]').each(function(index, item) {
-                                       var $item = $(item);
-                                       
-                                       if ($item.data('type') === 'deny' && $item.attr('id') !== 'denyAll') {
-                                               $item.prop('checked', true).trigger('change');
-                                       }
-                               });
-                       }
-                       else {
-                               this._containerElements.denyAll.prop('checked', false);
-                               
-                               this._containerElements.permissionList.find('input[type=checkbox]').each(function(index, item) {
-                                       var $item = $(item);
-                                       
-                                       if ($item.data('type') === 'grant' && $item.attr('id') !== 'grantAll') {
-                                               $item.prop('checked', true).trigger('change');
-                                       }
-                               });
-                       }
-               }
-               else {
-                       if ($type === 'deny') {
-                               this._containerElements.grantAll.prop('checked', false);
-                               
-                               this._containerElements.permissionList.find('input[type=checkbox]').each(function(index, item) {
-                                       var $item = $(item);
-                                       
-                                       if ($item.data('type') === 'deny' && $item.attr('id') !== 'denyAll') {
-                                               $item.prop('checked', false).trigger('change');
-                                       }
-                               });
-                       }
-                       else {
-                               this._containerElements.denyAll.prop('checked', false);
-                               
-                               this._containerElements.permissionList.find('input[type=checkbox]').each(function(index, item) {
-                                       var $item = $(item);
-                                       
-                                       if ($item.data('type') === 'grant' && $item.attr('id') !== 'grantAll') {
-                                               $item.prop('checked', false).trigger('change');
-                                       }
-                               });
-                       }
-               }
-       },
-       
-       /**
-        * Setups permission input for given object.
-        * 
-        * @param       string          type
-        * @param       integer         objectID
-        */
-       _setupPermissions: function(type, objectID) {
-               // reset all checkboxes to unchecked
-               this._containerElements.permissionList.find("input[type='checkbox']").prop('checked', false);
-               
-               // use stored permissions if applicable
-               if (this._values[type] && this._values[type][objectID]) {
-                       for (var $optionID in this._values[type][objectID]) {
-                               if (this._values[type][objectID][$optionID] == 1) {
-                                       $('#grant' + $optionID).prop('checked', true).trigger('change');
-                               }
-                               else {
-                                       $('#deny' + $optionID).prop('checked', true).trigger('change');
-                               }
-                       }
-               }
-               
-               // show permissions
-               this._containerElements.permissionList.show();
-       },
-       
-       /**
-        * Saves currently set permissions.
-        */
-       _savePermissions: function() {
-               // get active object
-               var $activeObject = this._containerElements.aclList.find('li.active');
-               if (!$activeObject.length) {
-                       return;
-               }
-               
-               var $objectID = $activeObject.data('objectID');
-               var $type = $activeObject.data('type');
-               
-               // clear old values
-               this._values[$type][$objectID] = { };
-               
-               var self = this;
-               this._containerElements.permissionList.find("input[type='checkbox']").each(function(index, checkbox) {
-                       var $checkbox = $(checkbox);
-                       if ($checkbox.attr('id') != 'grantAll' && $checkbox.attr('id') != 'denyAll') {
-                               var $optionValue = ($checkbox.data('type') === 'deny') ? 0 : 1;
-                               var $optionID = $checkbox.data('optionID');
-                               
-                               if ($checkbox.is(':checked')) {
-                                       // store value
-                                       self._values[$type][$objectID][$optionID] = $optionValue;
-                                       
-                                       // reset value afterwards
-                                       $checkbox.prop('checked', false);
-                               }
-                               else if (self._values[$type] && self._values[$type][$objectID] && self._values[$type][$objectID][$optionID] && self._values[$type][$objectID][$optionID] == $optionValue) {
-                                       delete self._values[$type][$objectID][$optionID];
-                               }
-                       }
-               });
-       },
-       
-       /**
-        * Prepares ACL values on submit.
-        * 
-        * @param       object          event
-        */
-       submit: function(event) {
-               this._savePermissions();
-               
-               this._save('group');
-               this._save('user');
-       },
-       
-       /**
-        * Inserts hidden form elements for each value.
-        * 
-        * @param       string          $type
-        */
-       _save: function($type) {
-               if ($.getLength(this._values[$type])) {
-                       var $form = this._container.parents('form:eq(0)');
-                       
-                       for (var $objectID in this._values[$type]) {
-                               var $object = this._values[$type][$objectID];
-                               
-                               for (var $optionID in $object) {
-                                       $('<input type="hidden" name="aclValues[' + $type + '][' + $objectID + '][' + $optionID + ']" value="' + $object[$optionID] + '" />').appendTo($form);
-                               }
-                       }
-               }
-       }
-});
-
-
-// WCF.Attachment.js
-/**
- * Namespace for attachments
- */
-WCF.Attachment = {};
-
-/**
- * Attachment upload function
- * 
- * @see        WCF.Upload
- */
-WCF.Attachment.Upload = WCF.Upload.extend({
-       /**
-        * object type of the object the uploaded attachments belong to
-        * @var string
-        */
-       _objectType: '',
-       
-       /**
-        * id of the object the uploaded attachments belong to
-        * @var string
-        */
-       _objectID: 0,
-       
-       /**
-        * temporary hash to identify uploaded attachments
-        * @var string
-        */
-       _tmpHash: '',
-       
-       /**
-        * id of the parent object of the object the uploaded attachments belongs to
-        * @var string
-        */
-       _parentObjectID: 0,
-       
-       /**
-        * container if of WYSIWYG editor
-        * @var string
-        */
-       _wysiwygContainerID: '',
-       
-       /**
-        * @see WCF.Upload.init()
-        */
-       init: function(buttonSelector, fileListSelector, objectType, objectID, tmpHash, parentObjectID, maxUploads, wysiwygContainerID) {
-               this._super(buttonSelector, fileListSelector, 'wcf\\data\\attachment\\AttachmentAction', { multiple: true, maxUploads: maxUploads });
-               
-               this._objectType = objectType;
-               this._objectID = objectID;
-               this._tmpHash = tmpHash;
-               this._parentObjectID = parentObjectID;
-               this._wysiwygContainerID = wysiwygContainerID;
-               
-               this._buttonSelector.children('p.button').click($.proxy(this._validateLimit, this));
-               this._fileListSelector.find('.jsButtonInsertAttachment').click($.proxy(this._insert, this));
-               
-               WCF.DOMNodeRemovedHandler.addCallback('WCF.Attachment.Upload', $.proxy(this._removeLimitError, this));
-       },
-       
-       /**
-        * Validates upload limits.
-        * 
-        * @return      boolean
-        */
-       _validateLimit: function() {
-               var $innerError = this._buttonSelector.next('small.innerError');
-               
-               // check maximum uploads
-               var $max = this._options.maxUploads - this._fileListSelector.children('li:not(.uploadFailed)').length;
-               var $filesLength = (this._fileUpload) ? this._fileUpload.prop('files').length : 0;
-               if ($max <= 0 || $max < $filesLength) {
-                       // reached limit
-                       var $errorMessage = ($max <= 0) ? WCF.Language.get('wcf.attachment.upload.error.reachedLimit') : WCF.Language.get('wcf.attachment.upload.error.reachedRemainingLimit').replace(/#remaining#/, $max);
-                       if (!$innerError.length) {
-                               $innerError = $('<small class="innerError" />').insertAfter(this._buttonSelector);
-                       }
-                       
-                       $innerError.html($errorMessage);
-                       
-                       return false;
-               }
-               
-               // remove previous errors
-               $innerError.remove();
-               
-               return true;
-       },
-       
-       /**
-        * Removes the limit error message.
-        * 
-        * @param       object          event
-        */
-       _removeLimitError: function(event) {
-               var $target = $(event.target);
-               if ($target.is('li.box48') && $target.parent().wcfIdentify() === this._fileListSelector.wcfIdentify()) {
-                       this._buttonSelector.next('small.innerError').remove();
-               }
-       },
-       
-       /**
-        * @see WCF.Upload._upload()
-        */
-       _upload: function() {
-               if (this._validateLimit()) {
-                       this._super();
-               }
-               
-               if (this._fileUpload) {
-                       // remove and re-create the upload button since the 'files' property
-                       // of the input field is readonly thus it can't be reset
-                       this._removeButton();
-                       this._createButton();
-               }
-       },
-       
-       /**
-        * @see WCF.Upload._createUploadMatrix()
-        */
-       _createUploadMatrix: function(files) {
-               // remove failed uploads
-               this._fileListSelector.children('li.uploadFailed').remove();
-               
-               return this._super(files);
-       },
-       
-       /**
-        * @see WCF.Upload._getParameters()
-        */
-       _getParameters: function() {
-               return {
-                       objectType: this._objectType,
-                       objectID: this._objectID,
-                       tmpHash: this._tmpHash,
-                       parentObjectID: this._parentObjectID
-               };
-       },
-       
-       /**
-        * @see WCF.Upload._initFile()
-        */
-       _initFile: function(file) {
-               var $li = $('<li class="box48"><span class="icon icon48 icon-spinner" /><div><div><p>'+file.name+'</p><small><progress max="100"></progress></small></div><ul></ul></div></li>').data('filename', file.name);
-               this._fileListSelector.append($li);
-               this._fileListSelector.show();
-               
-               // validate file size
-               if (this._buttonSelector.data('maxSize') < file.size) {
-                       // remove progress bar
-                       $li.find('progress').remove();
-                       
-                       // upload icon
-                       $li.children('.icon-spinner').removeClass('icon-spinner').addClass('icon-ban-circle');
-                       
-                       // error message
-                       $li.find('div > div').append($('<small class="innerError">' + WCF.Language.get('wcf.attachment.upload.error.tooLarge') + '</small>'));
-                       $li.addClass('uploadFailed');
-               }
-               
-               return $li;
-       },
-       
-       /**
-        * @see WCF.Upload._success()
-        */
-       _success: function(uploadID, data) {
-               for (var $i in this._uploadMatrix[uploadID]) {
-                       // get li
-                       var $li = this._uploadMatrix[uploadID][$i];
-                       
-                       // remove progress bar
-                       $li.find('progress').remove();
-                       
-                       // get filename and check result
-                       var $filename = $li.data('filename');
-                       var $internalFileID = $li.data('internalFileID');
-                       if (data.returnValues && data.returnValues.attachments[$internalFileID]) {
-                               // show thumbnail
-                               if (data.returnValues.attachments[$internalFileID]['tinyURL']) {
-                                       $li.children('.icon-spinner').replaceWith($('<img src="' + data.returnValues.attachments[$internalFileID]['tinyURL'] + '" alt="" class="attachmentTinyThumbnail" />'));
-                               }
-                               // show file icon
-                               else {
-                                       $li.children('.icon-spinner').removeClass('icon-spinner').addClass('icon-paper-clip');
-                               }
-                               
-                               // update attachment link
-                               var $link = $('<a href=""></a>');
-                               $link.text($filename).attr('href', data.returnValues.attachments[$internalFileID]['url']);
-                               
-                               if (data.returnValues.attachments[$internalFileID]['isImage'] != 0) {
-                                       $link.addClass('jsImageViewer').attr('title', $filename);
-                               }
-                               $li.find('p').empty().append($link);
-                               
-                               // update file size
-                               $li.find('small').append(data.returnValues.attachments[$internalFileID]['formattedFilesize']);
-                               
-                               // init buttons
-                               var $deleteButton = $('<li><span class="icon icon16 icon-remove pointer jsTooltip jsDeleteButton" title="'+WCF.Language.get('wcf.global.button.delete')+'" data-object-id="'+data.returnValues.attachments[$internalFileID]['attachmentID']+'" data-confirm-message="'+WCF.Language.get('wcf.attachment.delete.sure')+'" /></li>');
-                               $li.find('ul').append($deleteButton);
-                               
-                               if (this._wysiwygContainerID) {
-                                       var $insertButton = $('<li><span class="icon icon16 icon-paste pointer jsTooltip jsButtonInsertAttachment" title="' + WCF.Language.get('wcf.attachment.insert') + '" data-object-id="' + data.returnValues.attachments[$internalFileID]['attachmentID'] + '" /></li>');
-                                       $insertButton.children('.jsButtonInsertAttachment').click($.proxy(this._insert, this));
-                                       $li.find('ul').append($insertButton);
-                               }
-                       }
-                       else {
-                               // upload icon
-                               $li.children('.icon-spinner').removeClass('icon-spinner').addClass('icon-ban-circle');
-                               var $errorMessage = '';
-                               
-                               // error handling
-                               if (data.returnValues && data.returnValues.errors[$internalFileID]) {
-                                       $errorMessage = data.returnValues.errors[$internalFileID]['errorType'];
-                               }
-                               else {
-                                       // unknown error
-                                       $errorMessage = 'uploadFailed';
-                               }
-                               
-                               $li.find('div > div').append($('<small class="innerError">'+WCF.Language.get('wcf.attachment.upload.error.'+$errorMessage)+'</small>'));
-                               $li.addClass('uploadFailed');
-                       }
-                       
-                       // fix webkit rendering bug
-                       $li.css('display', 'block');
-               }
-               
-               WCF.DOMNodeInsertedHandler.execute();
-       },
-       
-       /**
-        * Inserts an attachment into WYSIWYG editor contents.
-        * 
-        * @param       object          event
-        */
-       _insert: function(event) {
-               var $attachmentID = $(event.currentTarget).data('objectID');
-               var $bbcode = '[attach=' + $attachmentID + '][/attach]';
-               
-               var $ckEditor = ($.browser.mobile) ? null : $('#' + this._wysiwygContainerID).ckeditorGet();
-               if ($ckEditor !== null && $ckEditor.mode === 'wysiwyg') {
-                       // in design mode
-                       $ckEditor.insertText($bbcode);
-               }
-               else {
-                       // in source mode
-                       var $textarea = ($.browser.mobile) ? $('#' + this._wysiwygContainerID) : $('#' + this._wysiwygContainerID).next('.cke_editor_text').find('textarea');
-                       var $value = $textarea.val();
-                       if ($value.length == 0) {
-                               $textarea.val($bbcode);
-                       }
-                       else {
-                               var $position = $textarea.getCaret();
-                               $textarea.val( $value.substr(0, $position) + $bbcode + $value.substr($position) );
-                       }
-               }
-       },
-       
-       /**
-        * @see WCF.Upload._error()
-        */
-       _error: function(data) {
-               // mark uploads as failed
-               this._fileListSelector.find('li').each(function(index, listItem) {
-                       var $listItem = $(listItem);
-                       if ($listItem.children('.icon-spinner').length) {
-                               // upload icon
-                               $listItem.addClass('uploadFailed').children('.icon-spinner').removeClass('icon-spinner').addClass('icon-ban-circle');
-                               $listItem.find('div > div').append($('<small class="innerError">' + (data.responseJSON && data.responseJSON.message ? data.responseJSON.message : WCF.Language.get('wcf.attachment.upload.error.uploadFailed')) + '</small>'));
-                       }
-               });
-       }
-});
-
-
-// WCF.ColorPicker.js
-/**
- * Color picker for WCF
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2014 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- */
-WCF.ColorPicker = Class.extend({
-       /**
-        * hue bar element
-        * @var jQuery
-        */
-       _bar: null,
-       
-       /**
-        * bar selector is being moved
-        * @var boolean
-        */
-       _barActive: false,
-       
-       /**
-        * bar selector element
-        * @var jQuery
-        */
-       _barSelector: null,
-       
-       /**
-        * dialog overlay
-        * @var jQuery
-        */
-       _dialog: null,
-       
-       /**
-        * initialization state
-        * @var boolean
-        */
-       _didInit: false,
-       
-       /**
-        * active element id
-        * @var string
-        */
-       _elementID: '',
-       
-       /**
-        * saturation and value gradient element
-        * @var jQuery
-        */
-       _gradient: null,
-       
-       /**
-        * gradient selector is being moved
-        * @var boolean
-        */
-       _gradientActive: false,
-       
-       /**
-        * gradient selector element
-        * @var jQuery
-        */
-       _gradientSelector: null,
-       
-       /**
-        * HEX input element
-        * @var jQuery
-        */
-       _hex: null,
-       
-       /**
-        * HSV representation
-        * @var object
-        */
-       _hsv: { },
-       
-       /**
-        * visual new color element
-        * @var jQuery
-        */
-       _newColor: null,
-       
-       /**
-        * visual previous color element
-        * @var jQuery
-        */
-       _oldColor: null,
-       
-       /**
-        * list of RGBa input elements
-        * @var object
-        */
-       _rgba: { },
-       
-       /**
-        * RegExp to parse rgba()
-        * @var RegExp
-        */
-       _rgbaRegExp: null,
-       
-       /**
-        * Initializes the WCF.ColorPicker class.
-        * 
-        * @param       string          selector
-        */
-       init: function(selector) {
-               this._elementID = '';
-               this._hsv = { h: 0, s: 100, v: 100 };
-               this._position = { };
-               
-               var $elements = $(selector);
-               if (!$elements.length) {
-                       console.debug("[WCF.ColorPicker] Selector does not match any element, aborting.");
-                       return;
-               }
-               
-               $elements.click($.proxy(this._open, this));
-       },
-       
-       /**
-        * Opens the color picker overlay.
-        * 
-        * @param       object          event
-        */
-       _open: function(event) {
-               if (!this._didInit) {
-                       // init color picker on first usage
-                       this._initColorPicker();
-                       this._didInit = true;
-               }
-               
-               // load values from element
-               var $element = $(event.currentTarget);
-               this._elementID = $element.wcfIdentify();
-               this._parseColor($element);
-               
-               // set 'current' color
-               var $rgb = this.hsvToRgb(this._hsv.h, this._hsv.s, this._hsv.v);
-               this._oldColor.css({ backgroundColor: 'rgb(' + $rgb.r + ', ' + $rgb.g + ', ' + $rgb.b + ')' });
-               
-               this._dialog.wcfDialog({
-                       'title': WCF.Language.get('wcf.style.colorPicker')
-               });
-       },
-       
-       /**
-        * Parses the color of an element.
-        * 
-        * @param       jQuery          element
-        */
-       _parseColor: function(element) {
-               if (element.data('hsv') && element.data('rgb')) {
-                       // create an explicit copy here, otherwise it would be only a reference
-                       var $hsv = element.data('hsv');
-                       for (var $type in $hsv) {
-                               this._hsv[$type] = $hsv[$type];
-                       }
-                       this._updateValues(element.data('rgb'), true, true);
-                       this._rgba.a.val(parseInt(element.data('alpha')));
-               }
-               else {
-                       if (this._rgbaRegExp === null) {
-                               this._rgbaRegExp = new RegExp("^rgba\\((\\d{1,3}), ?(\\d{1,3}), ?(\\d{1,3}), ?(1|1\\.00?|0|0?\\.[0-9]{1,2})\\)$");
-                       }
-                       
-                       // parse value
-                       this._rgbaRegExp.exec(element.data('color'));
-                       var $alpha = RegExp.$4;
-                       // convert into x.yz
-                       if ($alpha.indexOf('.') === 0) {
-                               $alpha = "0" + $alpha;
-                       }
-                       $alpha *= 100;
-                       
-                       this._updateValues({
-                               r: RegExp.$1,
-                               g: RegExp.$2,
-                               b: RegExp.$3,
-                               a: Math.round($alpha)
-                       }, true, true);
-               }
-       },
-       
-       /**
-        * Initializes the color picker upon first usage.
-        */
-       _initColorPicker: function() {
-               this._dialog = $('<div id="colorPickerContainer" />').hide().appendTo(document.body);
-               
-               // create gradient
-               this._gradient = $('<div id="colorPickerGradient" />').appendTo(this._dialog);
-               this._gradientSelector = $('<span id="colorPickerGradientSelector"><span></span></span>').appendTo(this._gradient);
-               
-               // create bar
-               this._bar = $('<div id="colorPickerBar" />').appendTo(this._dialog);
-               this._barSelector = $('<span id="colorPickerBarSelector" />').appendTo(this._bar);
-               
-               // bind event listener
-               this._gradient.mousedown($.proxy(this._mouseDownGradient, this));
-               this._bar.mousedown($.proxy(this._mouseDownBar, this));
-               
-               var self = this;
-               $(document).mouseup(function(event) {
-                       if (self._barActive) {
-                               self._barActive = false;
-                               self._mouseBar(event);
-                       }
-                       else if (self._gradientActive) {
-                               self._gradientActive = false;
-                               self._mouseGradient(event);
-                       }
-               }).mousemove(function(event) {
-                       if (self._barActive) {
-                               self._mouseBar(event);
-                       }
-                       else if (self._gradientActive) {
-                               self._mouseGradient(event);
-                       }
-               });
-               
-               this._initColorPickerForm();
-       },
-       
-       /**
-        * Initializes the color picker input elements upon first usage.
-        */
-       _initColorPickerForm: function() {
-               var $form = $('<div id="colorPickerForm" />').appendTo(this._dialog);
-               
-               // new and current color
-               $('<small>' + WCF.Language.get('wcf.style.colorPicker.new') + '</small>').appendTo($form);
-               var $colors = $('<ul class="colors" />').appendTo($form);
-               this._newColor = $('<li class="new" />').appendTo($colors);
-               this._oldColor = $('<li class="old" />').appendTo($colors);
-               $('<small>' + WCF.Language.get('wcf.style.colorPicker.current') + '</small>').appendTo($form);
-               
-               // RGBa input
-               var $rgba = $('<ul class="rgba" />').appendTo($form);
-               this._createInputElement('r', 'R', 0, 255).appendTo($rgba);
-               this._createInputElement('g', 'G', 0, 255).appendTo($rgba);
-               this._createInputElement('b', 'B', 0, 255).appendTo($rgba);
-               this._createInputElement('a', 'a', 0, 100).appendTo($rgba);
-               
-               // HEX input
-               var $hex = $('<ul class="hex"><li><label><span>#</span></label></li></ul>').appendTo($form);
-               this._hex = $('<input type="text" maxlength="6" />').appendTo($hex.find('label'));
-               
-               // bind event listener
-               this._rgba.r.blur($.proxy(this._blurRgba, this)).keyup($.proxy(this._keyUpRGBA, this));
-               this._rgba.g.blur($.proxy(this._blurRgba, this)).keyup($.proxy(this._keyUpRGBA, this));
-               this._rgba.b.blur($.proxy(this._blurRgba, this)).keyup($.proxy(this._keyUpRGBA, this));
-               this._rgba.a.blur($.proxy(this._blurRgba, this)).keyup($.proxy(this._keyUpRGBA, this));
-               this._hex.blur($.proxy(this._blurHex, this)).keyup($.proxy(this._keyUpHex, this));
-               
-               // submit button
-               var $submitForm = $('<div class="formSubmit" />').appendTo(this._dialog);
-               $('<button class="buttonPrimary">' + WCF.Language.get('wcf.style.colorPicker.button.apply') + '</button>').appendTo($submitForm).click($.proxy(this._submit, this));
-               
-               // allow pasting of colors like '#888888'
-               var self = this;
-               this._hex.on('paste', function() {
-                       self._hex.attr('maxlength', '7');
-                       
-                       setTimeout(function() {
-                               var $value = self._hex.val();
-                               if ($value.substring(0, 1) == '#') {
-                                       $value = $value.substr(1);
-                               }
-                               
-                               if ($value.length > 6) {
-                                       $value = $value.substring(0, 6);
-                               }
-                               
-                               self._hex.attr('maxlength', '6').val($value);
-                       }, 50);
-               });
-       },
-       
-       /**
-        * Submits form on enter.
-        */
-       _keyUpRGBA: function(event) {
-               if (event.which == 13) {
-                       this._blurRgba();
-                       this._submit();
-               }
-       },
-       
-       /**
-        * Submits form on enter.
-        */
-       _keyUpHex: function(event) {
-               if (event.which == 13) {
-                       this._blurHex();
-                       this._submit();
-               }
-       },
-       
-       /**
-        * Assigns the new color for active element.
-        */
-       _submit: function() {
-               var $rgb = this.hsvToRgb(this._hsv.h, this._hsv.s, this._hsv.v);
-               
-               // create an explicit copy here, otherwise it would be only a reference
-               var $hsv = { };
-               for (var $type in this._hsv) {
-                       $hsv[$type] = this._hsv[$type];
-               }
-               
-               var $element = $('#' + this._elementID);
-               $element.data('hsv', $hsv).css({ backgroundColor: 'rgb(' + $rgb.r + ', ' + $rgb.g + ', ' + $rgb.b + ')' }).data('alpha', parseInt(this._rgba.a.val()));
-               $element.data('rgb', {
-                       r: this._rgba.r.val(),
-                       g: this._rgba.g.val(),
-                       b: this._rgba.b.val()
-               });
-               $('#' + $element.data('store')).val('rgba(' + this._rgba.r.val() + ', ' + this._rgba.g.val() + ', ' + this._rgba.b.val() + ', ' + (this._rgba.a.val() / 100) + ')').trigger('change');
-               
-               this._dialog.wcfDialog('close');
-       },
-       
-       /**
-        * Creates an input element.
-        * 
-        * @param       string          type
-        * @param       string          label
-        * @param       integer         min
-        * @param       integer         max
-        * @return      jQuery
-        */
-       _createInputElement: function(type, label, min, max) {
-               // create elements
-               var $listItem = $('<li class="' + type + '" />');
-               var $label = $('<label />').appendTo($listItem);
-               $('<span>' + label + '</span>').appendTo($label);
-               this._rgba[type] = $('<input type="number" value="0" min="' + min + '" max="' + max + '" step="1" />').appendTo($label);
-               
-               return $listItem;
-       },
-       
-       /**
-        * Handles the mouse down event on the gradient.
-        * 
-        * @param       object          event
-        */
-       _mouseDownGradient: function(event) {
-               this._gradientActive = true;
-               this._mouseGradient(event);
-       },
-       
-       /**
-        * Handles updates of gradient selector position.
-        * 
-        * @param       object          event
-        */
-       _mouseGradient: function(event) {
-               var $position = this._gradient.getOffsets('offset');
-               
-               var $left = Math.max(Math.min(event.pageX - $position.left, 255), 0);
-               var $top = Math.max(Math.min(event.pageY - $position.top, 255), 0);
-               
-               // calculate saturation and value
-               this._hsv.s = Math.max(0, Math.min(1, $left / 255)) * 100;
-               this._hsv.v = Math.max(0, Math.min(1, (255 - $top) / 255)) * 100;
-               
-               // update color
-               this._updateValues(null);
-       },
-       
-       /**
-        * Handles the mouse down event on the bar.
-        * 
-        * @param       object          event
-        */
-       _mouseDownBar: function(event) {
-               this._barActive = true;
-               this._mouseBar(event);
-       },
-       
-       /**
-        * Handles updates of the bar selector position.
-        * 
-        * @param       object          event
-        */
-       _mouseBar: function(event) {
-               var $position = this._bar.getOffsets('offset');
-               var $top = Math.max(Math.min(event.pageY - $position.top, 255), 0);
-               this._barSelector.css({ top: $top + 'px' });
-               
-               // calculate hue
-               this._hsv.h = Math.max(0, Math.min(359, Math.round((255 - $top) / 255 * 360)));
-               
-               // update color
-               this._updateValues(null);
-       },
-       
-       /**
-        * Handles changes of RGBa input fields.
-        */
-       _blurRgba: function() {
-               for (var $type in this._rgba) {
-                       var $value = parseInt(this._rgba[$type].val()) || 0;
-                       
-                       // alpha
-                       if ($type === 'a') {
-                               this._rgba[$type].val(Math.max(0, Math.min(100, $value)));
-                       }
-                       else {
-                               // rgb
-                               this._rgba[$type].val(Math.max(0, Math.min(255, $value)));
-                       }
-               }
-               
-               this._updateValues({
-                       r: this._rgba.r.val(),
-                       g: this._rgba.g.val(),
-                       b: this._rgba.b.val()
-               }, true, true);
-       },
-       
-       /**
-        * Handles change of HEX value.
-        */
-       _blurHex: function() {
-               var $value = this.hexToRgb(this._hex.val());
-               if ($value !== Number.NaN) {
-                       this._updateValues($value, true, true);
-               }
-       },
-       
-       /**
-        * Updates the values of all elements, including color picker and
-        * input elements. Argument 'rgb' may be null.
-        * 
-        * @param       object          rgb
-        * @param       boolean         changeH
-        * @param       boolean         changeSV
-        */
-       _updateValues: function(rgb, changeH, changeSV) {
-               changeH = (changeH === true) ? true : false;
-               changeSV = (changeSV === true) ? true : false;
-               
-               // calculate RGB values from HSV
-               if (rgb === null) {
-                       rgb = this.hsvToRgb(this._hsv.h, this._hsv.s, this._hsv.v);
-               }
-               
-               // add alpha channel
-               if (rgb.a === undefined) {
-                       rgb.a = this._rgba.a.val();
-               }
-               
-               // adjust RGBa input
-               for (var $type in rgb) {
-                       this._rgba[$type].val(rgb[$type]);
-               }
-               
-               // set hex input
-               this._hex.val(this.rgbToHex(rgb.r, rgb.g, rgb.b));
-               
-               // calculate HSV to adjust selectors
-               if (changeH || changeSV) {
-                       var $hsv = this.rgbToHsv(rgb.r, rgb.g, rgb.b);
-                       
-                       // adjust hue
-                       if (changeH) {
-                               this._hsv.h = $hsv.h;
-                       }
-                       
-                       // adjust saturation and value
-                       if (changeSV) {
-                               this._hsv.s = $hsv.s;
-                               this._hsv.v = $hsv.v;
-                       }
-               }
-               
-               // adjust bar selector
-               var $top = Math.max(0, Math.min(255, 255 - (this._hsv.h / 360) * 255));
-               this._barSelector.css({ top: $top + 'px' });
-               
-               // adjust gradient selector
-               var $left = Math.max(0, Math.min(255, (this._hsv.s / 100) * 255));
-               var $top = Math.max(0, Math.min(255, 255 - ((this._hsv.v / 100) * 255)));
-               this._gradientSelector.css({
-                       left: ($left - 6) + 'px',
-                       top: ($top - 6) + 'px'
-               });
-                               
-               // update 'new' color
-               this._newColor.css({ backgroundColor: 'rgb(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ')' });
-               
-               // adjust gradient color
-               var $rgb = this.hsvToRgb(this._hsv.h, 100, 100);
-               this._gradient.css({ backgroundColor: 'rgb(' + $rgb.r + ', ' + $rgb.g + ', ' + $rgb.b + ')' });
-       },
-       
-       /**
-        * Converts a HSV color into RGB.
-        * 
-        * @see https://secure.wikimedia.org/wikipedia/de/wiki/HSV-Farbraum#Transformation_von_RGB_und_HSV
-        * 
-        * @param       integer         h
-        * @param       integer         s
-        * @param       integer         v
-        * @return      object
-        */
-       hsvToRgb: function(h, s, v) {
-               var $rgb = { r: 0, g: 0, b: 0 };
-               var $h, $f, $p, $q, $t;
-               
-               $h = Math.floor(h / 60);
-               $f = h / 60 - $h;
-               
-               s /= 100;
-               v /= 100;
-               
-               $p = v * (1 - s);
-               $q = v * (1 - s * $f);
-               $t = v * (1 - s * (1 - $f));
-               
-               if (s == 0) {
-                       $rgb.r = $rgb.g = $rgb.b = v;
-               }
-               else {
-                       switch ($h) {
-                               case 1:
-                                       $rgb.r = $q;
-                                       $rgb.g = v;
-                                       $rgb.b = $p;
-                               break;
-                               
-                               case 2:
-                                       $rgb.r = $p;
-                                       $rgb.g = v;
-                                       $rgb.b = $t;
-                               break;
-                               
-                               case 3:
-                                       $rgb.r = $p;
-                                       $rgb.g = $q;
-                                       $rgb.b = v;
-                               break;
-                               
-                               case 4:
-                                       $rgb.r = $t;
-                                       $rgb.g = $p;
-                                       $rgb.b = v;
-                               break;
-                               
-                               case 5:
-                                       $rgb.r = v;
-                                       $rgb.g = $p;
-                                       $rgb.b = $q;
-                               break;
-                               
-                               case 0:
-                               case 6:
-                                       $rgb.r = v;
-                                       $rgb.g = $t;
-                                       $rgb.b = $p;
-                               break;
-                       }
-               }
-               
-               return {
-                       r: Math.round($rgb.r * 255),
-                       g: Math.round($rgb.g * 255),
-                       b: Math.round($rgb.b * 255)
-               };
-       },
-       
-       /**
-        * Converts a RGB color into HSV.
-        * 
-        * @see https://secure.wikimedia.org/wikipedia/de/wiki/HSV-Farbraum#Transformation_von_RGB_und_HSV
-        * 
-        * @param       integer         r
-        * @param       integer         g
-        * @param       integer         b
-        * @return      object
-        */
-       rgbToHsv: function(r, g, b) {
-               var $h, $s, $v;
-               var $max, $min, $diff;
-               
-               r /= 255;
-               g /= 255;
-               b /= 255;
-               
-               $max = Math.max(Math.max(r, g), b);
-               $min = Math.min(Math.min(r, g), b);
-               $diff = $max - $min;
-               
-               $h = 0;
-               if ($max !== $min) {
-                       switch ($max) {
-                               case r:
-                                       $h = 60 * (0 + (g - b) / $diff);
-                               break;
-                               
-                               case g:
-                                       $h = 60 * (2 + (b - r) / $diff);
-                               break;
-                               
-                               case b:
-                                       $h = 60 * (4 + (r - g) / $diff);
-                               break;
-                       }
-                       
-                       if ($h < 0) {
-                               $h += 360;
-                       }
-               }
-               
-               if ($max === 0) {
-                       $s = 0;
-               }
-               else {
-                       $s = $diff / $max;
-               }
-               
-               $v = $max;
-               
-               return {
-                       h: Math.round($h),
-                       s: Math.round($s * 100),
-                       v: Math.round($v * 100)
-               };
-       },
-       
-       /**
-        * Converts HEX into RGB.
-        * 
-        * @param       string          hex
-        * @return      object
-        */
-       hexToRgb: function(hex) {
-               if (/^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(hex)) {
-                       // only convert #abc and #abcdef
-                       hex = hex.split('');
-                       
-                       // drop the hashtag
-                       if (hex[0] === '#') {
-                               hex.shift();
-                       }
-                       
-                       // parse shorthand #xyz
-                       if (hex.length === 3) {
-                               return {
-                                       r: parseInt(hex[0] + '' + hex[0], 16),
-                                       g: parseInt(hex[1] + '' + hex[1], 16),
-                                       b: parseInt(hex[2] + '' + hex[2], 16)
-                               };
-                       }
-                       else {
-                               return {
-                                       r: parseInt(hex[0] + '' + hex[1], 16),
-                                       g: parseInt(hex[2] + '' + hex[3], 16),
-                                       b: parseInt(hex[4] + '' + hex[5], 16)
-                               };
-                       }
-               }
-               
-               return Number.NaN;
-       },
-       
-       /**
-        * Converts a RGB into HEX.
-        * 
-        * @see http://www.linuxtopia.org/online_books/javascript_guides/javascript_faq/rgbtohex.htm
-        * 
-        * @param       integer         r
-        * @param       integer         g
-        * @param       integer         b
-        * @return      string
-        */
-       rgbToHex: function(r, g, b) {
-               return ("0123456789ABCDEF".charAt((r - r % 16) / 16) + '' + "0123456789ABCDEF".charAt(r % 16)) + '' + ("0123456789ABCDEF".charAt((g - g % 16) / 16) + '' + "0123456789ABCDEF".charAt(g % 16)) + '' + ("0123456789ABCDEF".charAt((b - b % 16) / 16) + '' + "0123456789ABCDEF".charAt(b % 16));
-       }
-});
-
-// WCF.Comment.js
-/**
- * Namespace for comments
- */
-WCF.Comment = { };
-
-/**
- * Comment support for WCF
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2014 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- */
-WCF.Comment.Handler = Class.extend({
-       /**
-        * input element to add a comment
-        * @var jQuery
-        */
-       _commentAdd: null,
-       
-       /**
-        * list of comment buttons per comment
-        * @var object
-        */
-       _commentButtonList: { },
-       
-       /**
-        * list of comment objects
-        * @var object
-        */
-       _comments: { },
-       
-       /**
-        * comment container object
-        * @var jQuery
-        */
-       _container: null,
-       
-       /**
-        * container id
-        * @var string
-        */
-       _containerID: '',
-       
-       /**
-        * number of currently displayed comments
-        * @var integer
-        */
-       _displayedComments: 0,
-       
-       /**
-        * button to load next comments
-        * @var jQuery
-        */
-       _loadNextComments: null,
-       
-       /**
-        * buttons to load next responses per comment
-        * @var object
-        */
-       _loadNextResponses: { },
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * list of response objects
-        * @var object
-        */
-       _responses: { },
-       
-       /**
-        * user's avatar
-        * @var string
-        */
-       _userAvatar: '',
-       
-       /**
-        * data of the comment the active guest user is about to create
-        * @var object
-        */
-       _commentData: { },
-       
-       /**
-        * guest dialog with username input field and recaptcha
-        * @var jQuery
-        */
-       _guestDialog: null,
-
-       /**
-        * true if the guest has to solve a recaptcha challenge to save the comment
-        * @var boolean
-        */
-       _useRecaptcha: true,
-       
-       /**
-        * Initializes the WCF.Comment.Handler class.
-        * 
-        * @param       string          containerID
-        * @param       string          userAvatar
-        */
-       init: function(containerID, userAvatar) {
-               this._commentAdd = null;
-               this._commentButtonList = { };
-               this._comments = { };
-               this._containerID = containerID;
-               this._displayedComments = 0;
-               this._loadNextComments = null;
-               this._loadNextResponses = { };
-               this._responses = { };
-               this._userAvatar = userAvatar;
-               
-               this._container = $('#' + $.wcfEscapeID(this._containerID));
-               if (!this._container.length) {
-                       console.debug("[WCF.Comment.Handler] Unable to find container identified by '" + this._containerID + "'");
-               }
-               
-               this._proxy = new WCF.Action.Proxy({
-                       failure: $.proxy(this._failure, this),
-                       success: $.proxy(this._success, this)
-               });
-               
-               this._initComments();
-               this._initResponses();
-               
-               // add new comment
-               if (this._container.data('canAdd')) {
-                       this._initAddComment();
-               }
-               
-               WCF.DOMNodeInsertedHandler.execute();
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.Comment.Handler', $.proxy(this._domNodeInserted, this));
-       },
-       
-       /**
-        * Shows a button to load next comments.
-        */
-       _handleLoadNextComments: function() {
-               if (this._displayedComments < this._container.data('comments')) {
-                       if (this._loadNextComments === null) {
-                               this._loadNextComments = $('<li class="commentLoadNext"><button class="small">' + WCF.Language.get('wcf.comment.more') + '</button></li>').appendTo(this._container);
-                               this._loadNextComments.children('button').click($.proxy(this._loadComments, this));
-                       }
-                       
-                       this._loadNextComments.children('button').enable();
-               }
-               else if (this._loadNextComments !== null) {
-                       this._loadNextComments.hide();
-               }
-       },
-       
-       /**
-        * Shows a button to load next responses per comment.
-        * 
-        * @param       integer         commentID
-        */
-       _handleLoadNextResponses: function(commentID) {
-               var $comment = this._comments[commentID];
-               $comment.data('displayedResponses', $comment.find('ul.commentResponseList > li').length);
-               
-               if ($comment.data('displayedResponses') < $comment.data('responses')) {
-                       if (this._loadNextResponses[commentID] === undefined) {
-                               var $difference = $comment.data('responses') - $comment.data('displayedResponses');
-                               this._loadNextResponses[commentID] = $('<li class="jsCommentLoadNextResponses"><a>' + WCF.Language.get('wcf.comment.response.more', { count: $difference }) + '</a></li>').appendTo(this._commentButtonList[commentID]);
-                               this._loadNextResponses[commentID].children('a').data('commentID', commentID).click($.proxy(this._loadResponses, this));
-                               this._commentButtonList[commentID].parent().show();
-                       }
-               }
-               else if (this._loadNextResponses[commentID] !== undefined) {
-                       var $showAddResponse = this._loadNextResponses[commentID].next();
-                       this._loadNextResponses[commentID].remove();
-                       if ($showAddResponse.length) {
-                               $showAddResponse.trigger('click');
-                       }
-               }
-       },
-       
-       /**
-        * Loads next comments.
-        */
-       _loadComments: function() {
-               this._loadNextComments.children('button').disable();
-               
-               this._proxy.setOption('data', {
-                       actionName: 'loadComments',
-                       className: 'wcf\\data\\comment\\CommentAction',
-                       parameters: {
-                               data: {
-                                       objectID: this._container.data('objectID'),
-                                       objectTypeID: this._container.data('objectTypeID'),
-                                       lastCommentTime: this._container.data('lastCommentTime')
-                               }
-                       }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Loads next responses for given comment.
-        * 
-        * @param       object          event
-        */
-       _loadResponses: function(event) {
-               this._loadResponsesExecute($(event.currentTarget).disable().data('commentID'), false);
-               
-       },
-       
-       /**
-        * Executes loading of comments, optionally fetching all at once.
-        * 
-        * @param       integer         commentID
-        * @param       boolean         loadAllResponses
-        */
-       _loadResponsesExecute: function(commentID, loadAllResponses) {
-               this._proxy.setOption('data', {
-                       actionName: 'loadResponses',
-                       className: 'wcf\\data\\comment\\response\\CommentResponseAction',
-                       parameters: {
-                               data: {
-                                       commentID: commentID,
-                                       lastResponseTime: this._comments[commentID].data('lastResponseTime'),
-                                       loadAllResponses: (loadAllResponses ? 1 : 0)
-                               }
-                       }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Handles DOMNodeInserted events.
-        */
-       _domNodeInserted: function() {
-               this._initComments();
-               this._initResponses();
-       },
-       
-       /**
-        * Initializes available comments.
-        */
-       _initComments: function() {
-               var self = this;
-               var $loadedComments = false;
-               this._container.find('.jsComment').each(function(index, comment) {
-                       var $comment = $(comment).removeClass('jsComment');
-                       var $commentID = $comment.data('commentID');
-                       self._comments[$commentID] = $comment;
-                       
-                       var $insertAfter = $comment.find('ul.commentResponseList');
-                       if (!$insertAfter.length) $insertAfter = $comment.find('.commentContent');
-                       
-                       $container = $('<div class="commentOptionContainer" />').hide().insertAfter($insertAfter);
-                       self._commentButtonList[$commentID] = $('<ul />').appendTo($container);
-                       
-                       self._handleLoadNextResponses($commentID);
-                       self._initComment($commentID, $comment);
-                       self._displayedComments++;
-                       
-                       $loadedComments = true;
-               });
-               
-               if ($loadedComments) {
-                       this._handleLoadNextComments();
-               }
-       },
-       
-       /**
-        * Initializes a specific comment.
-        * 
-        * @param       integer         commentID
-        * @param       jQuery          comment
-        */
-       _initComment: function(commentID, comment) {
-               if (this._container.data('canAdd')) {
-                       this._initAddResponse(commentID, comment);
-               }
-               
-               if (comment.data('canEdit')) {
-                       var $editButton = $('<li><a class="jsTooltip" title="' + WCF.Language.get('wcf.global.button.edit') + '"><span class="icon icon16 icon-pencil" /> <span class="invisible">' + WCF.Language.get('wcf.global.button.edit') + '</span></a></li>');
-                       $editButton.data('commentID', commentID).appendTo(comment.find('ul.commentOptions:eq(0)')).click($.proxy(this._prepareEdit, this));
-               }
-               
-               if (comment.data('canDelete')) {
-                       var $deleteButton = $('<li><a class="jsTooltip" title="' + WCF.Language.get('wcf.global.button.delete') + '"><span class="icon icon16 icon-remove" /> <span class="invisible">' + WCF.Language.get('wcf.global.button.delete') + '</span></a></li>');
-                       $deleteButton.data('commentID', commentID).appendTo(comment.find('ul.commentOptions:eq(0)')).click($.proxy(this._delete, this));
-               }
-       },
-       
-       /**
-        * Initializes available responses.
-        */
-       _initResponses: function() {
-               var self = this;
-               this._container.find('.jsCommentResponse').each(function(index, response) {
-                       var $response = $(response).removeClass('jsCommentResponse');
-                       var $responseID = $response.data('responseID');
-                       self._responses[$responseID] = $response;
-                       
-                       self._initResponse($responseID, $response);
-               });
-       },
-       
-       /**
-        * Initializes a specific response.
-        * 
-        * @param       integer         responseID
-        * @param       jQuery          response
-        */
-       _initResponse: function(responseID, response) {
-               if (response.data('canEdit')) {
-                       var $editButton = $('<li><a class="jsTooltip" title="' + WCF.Language.get('wcf.global.button.edit') + '"><span class="icon icon16 icon-pencil" /> <span class="invisible">' + WCF.Language.get('wcf.global.button.edit') + '</span></a></li>');
-                       
-                       var self = this;
-                       $editButton.data('responseID', responseID).appendTo(response.find('ul.commentOptions:eq(0)')).click(function(event) { self._prepareEdit(event, true); });
-               }
-               
-               if (response.data('canDelete')) {
-                       var $deleteButton = $('<li><a class="jsTooltip" title="' + WCF.Language.get('wcf.global.button.delete') + '"><span class="icon icon16 icon-remove" /> <span class="invisible">' + WCF.Language.get('wcf.global.button.delete') + '</span></a></li>');
-                       
-                       var self = this;
-                       $deleteButton.data('responseID', responseID).appendTo(response.find('ul.commentOptions:eq(0)')).click(function(event) { self._delete(event, true); });
-               }
-       },
-       
-       /**
-        * Initializes the UI components to add a comment.
-        */
-       _initAddComment: function() {
-               // create UI
-               this._commentAdd = $('<li class="box32 jsCommentAdd"><span class="framed">' + this._userAvatar + '</span><div /></li>').prependTo(this._container);
-               var $inputContainer = this._commentAdd.children('div');
-               var $input = $('<input type="text" placeholder="' + WCF.Language.get('wcf.comment.add') + '" maxlength="65535" class="long" />').appendTo($inputContainer);
-               $('<small>' + WCF.Language.get('wcf.comment.description') + '</small>').appendTo($inputContainer);
-               
-               $input.keyup($.proxy(this._keyUp, this));
-       },
-       
-       /**
-        * Initializes the UI elements to add a response.
-        * 
-        * @param       integer         commentID
-        * @param       jQuery          comment
-        */
-       _initAddResponse: function(commentID, comment) {
-               var $placeholder = null;
-               if (!comment.data('responses') || this._loadNextResponses[commentID]) {
-                       $placeholder = $('<li class="jsCommentShowAddResponse"><a>' + WCF.Language.get('wcf.comment.button.response.add') + '</a></li>').data('commentID', commentID).click($.proxy(this._showAddResponse, this)).appendTo(this._commentButtonList[commentID]);
-               }
-               
-               var $listItem = $('<div class="box32 commentResponseAdd jsCommentResponseAdd"><span class="framed">' + this._userAvatar + '</span><div /></div>');
-               if ($placeholder !== null) {
-                       $listItem.hide();
-               }
-               else {
-                       this._commentButtonList[commentID].parent().addClass('jsAddResponseActive');
-               }
-               $listItem.appendTo(this._commentButtonList[commentID].parent().show());
-               
-               var $inputContainer = $listItem.children('div');
-               var $input = $('<input type="text" placeholder="' + WCF.Language.get('wcf.comment.response.add') + '" maxlength="65535" class="long" />').data('commentID', commentID).appendTo($inputContainer);
-               $('<small>' + WCF.Language.get('wcf.comment.description') + '</small>').appendTo($inputContainer);
-               
-               var self = this;
-               $input.keyup(function(event) { self._keyUp(event, true); });
-               
-               comment.data('responsePlaceholder', $placeholder).data('responseInput', $listItem);
-       },
-       
-       /**
-        * Prepares editing of a comment or response.
-        * 
-        * @param       object          event
-        * @param       boolean         isResponse
-        */
-       _prepareEdit: function(event, isResponse) {
-               var $button = $(event.currentTarget);
-               var $data = {
-                       objectID: this._container.data('objectID'),
-                       objectTypeID: this._container.data('objectTypeID')
-               };
-               
-               if (isResponse === true) {
-                       $data.responseID = $button.data('responseID');
-               }
-               else {
-                       $data.commentID = $button.data('commentID');
-               }
-               
-               this._proxy.setOption('data', {
-                       actionName: 'prepareEdit',
-                       className: 'wcf\\data\\comment\\CommentAction',
-                       parameters: {
-                               data: $data
-                       }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Displays the UI elements to create a response.
-        * 
-        * @param       object          event
-        */
-       _showAddResponse: function(event) {
-               var $placeholder = $(event.currentTarget);
-               var $commentID = $placeholder.data('commentID');
-               if ($placeholder.prev().hasClass('jsCommentLoadNextResponses')) {
-                       this._loadResponsesExecute($commentID, true);
-                       $placeholder.parent().children('.button').disable();
-               }
-               
-               $placeholder.remove();
-               
-               var $responseInput = this._comments[$commentID].data('responseInput').show();
-               $responseInput.find('input').focus();
-               
-               $responseInput.parents('.commentOptionContainer').addClass('jsAddResponseActive');
-       },
-       
-       /**
-        * Handles the keyup event for comments and responses.
-        * 
-        * @param       object          event
-        * @param       boolean         isResponse
-        */
-       _keyUp: function(event, isResponse) {
-               // ignore every key except for [Enter] and [Esc]
-               if (event.which !== 13 && event.which !== 27) {
-                       return;
-               }
-               
-               var $input = $(event.currentTarget);
-               
-               // cancel input
-               if (event.which === 27) {
-                       $input.val('').trigger('blur', event);
-                       return;
-               }
-               
-               var $value = $.trim($input.val());
-               
-               // ignore empty comments
-               if ($value == '') {
-                       return;
-               }
-               
-               var $actionName = 'addComment';
-               var $data = {
-                       message: $value,
-                       objectID: this._container.data('objectID'),
-                       objectTypeID: this._container.data('objectTypeID')
-               };
-               if (isResponse === true) {
-                       $actionName = 'addResponse';
-                       $data.commentID = $input.data('commentID');
-               }
-               
-               if (!WCF.User.userID) {
-                       this._commentData = $data;
-                       
-                       // check if guest dialog has already been loaded
-                       if (this._guestDialog === null) {
-                               this._proxy.setOption('data', {
-                                       actionName: 'getGuestDialog',
-                                       className: 'wcf\\data\\comment\\CommentAction',
-                                       parameters: {
-                                               data: {
-                                                       message: $value,
-                                                       objectID: this._container.data('objectID'),
-                                                       objectTypeID: this._container.data('objectTypeID')
-                                               }
-                                       }
-                               });
-                               this._proxy.sendRequest();
-                       }
-                       else {
-                               // request a new recaptcha
-                               if (this._useRecaptcha) {
-                                       Recaptcha.reload();
-                               }
-                               
-                               this._guestDialog.find('input[type="submit"]').enable();
-                               
-                               this._guestDialog.wcfDialog('open');
-                       }
-               }
-               else {
-                       this._proxy.setOption('data', {
-                               actionName: $actionName,
-                               className: 'wcf\\data\\comment\\CommentAction',
-                               parameters: {
-                                       data: $data
-                               }
-                       });
-                       this._proxy.sendRequest();
-               }
-       },
-       
-       /**
-        * Shows a confirmation message prior to comment or response deletion.
-        * 
-        * @param       object          event
-        * @param       boolean         isResponse
-        */
-       _delete: function(event, isResponse) {
-               WCF.System.Confirmation.show(WCF.Language.get('wcf.comment.delete.confirmMessage'), $.proxy(function(action) {
-                       if (action === 'confirm') {
-                               var $data = {
-                                       objectID: this._container.data('objectID'),
-                                       objectTypeID: this._container.data('objectTypeID')
-                               };
-                               if (isResponse !== true) {
-                                       $data.commentID = $(event.currentTarget).data('commentID');
-                               }
-                               else {
-                                       $data.responseID = $(event.currentTarget).data('responseID');
-                               }
-                               
-                               this._proxy.setOption('data', {
-                                       actionName: 'remove',
-                                       className: 'wcf\\data\\comment\\CommentAction',
-                                       parameters: {
-                                               data: $data
-                                       }
-                               });
-                               this._proxy.sendRequest();
-                       }
-               }, this));
-       },
-       
-       /**
-        * Handles a failed AJAX request.
-        * 
-        * @param       object          data
-        * @param       object          jqXHR
-        * @param       string          textStatus
-        * @param       string          errorThrown
-        * @return      boolean
-        */
-       _failure: function(data, jqXHR, textStatus, errorThrown) {
-               if (!WCF.User.userID && this._guestDialog) {
-                       // enable submit button again
-                       this._guestDialog.find('input[type="submit"]').enable();
-               }
-               
-               return true;
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               switch (data.actionName) {
-                       case 'addComment':
-                               if (data.returnValues.errors) {
-                                       this._handleGuestDialogErrors(data.returnValues.errors);
-                               }
-                               else {
-                                       this._commentAdd.find('input').val('').blur();
-                                       $(data.returnValues.template).insertAfter(this._commentAdd).wcfFadeIn();
-                                       
-                                       if (!WCF.User.userID) {
-                                               this._guestDialog.wcfDialog('close');
-                                       }
-                               }
-                       break;
-                       
-                       case 'addResponse':
-                               if (data.returnValues.errors) {
-                                       this._handleGuestDialogErrors(data.returnValues.errors);
-                               }
-                               else {
-                                       var $comment = this._comments[data.returnValues.commentID];
-                                       $comment.find('.jsCommentResponseAdd input').val('').blur();
-                                       
-                                       var $responseList = $comment.find('ul.commentResponseList');
-                                       if (!$responseList.length) $responseList = $('<ul class="commentResponseList" />').insertBefore($comment.find('.commentOptionContainer'));
-                                       $(data.returnValues.template).appendTo($responseList).wcfFadeIn();
-                               }
-                               
-                               if (!WCF.User.userID) {
-                                       this._guestDialog.wcfDialog('close');
-                               }
-                       break;
-                       
-                       case 'edit':
-                               this._update(data);
-                       break;
-                       
-                       case 'loadComments':
-                               this._insertComments(data);
-                       break;
-                       
-                       case 'loadResponses':
-                               this._insertResponses(data);
-                       break;
-                       
-                       case 'prepareEdit':
-                               this._edit(data);
-                       break;
-                       
-                       case 'remove':
-                               this._remove(data);
-                       break;
-                       
-                       case 'getGuestDialog':
-                               this._createGuestDialog(data);
-                       break;
-               }
-               
-               WCF.DOMNodeInsertedHandler.execute();
-       },
-       
-       /**
-        * Inserts previously loaded comments.
-        * 
-        * @param       object          data
-        */
-       _insertComments: function(data) {
-               // insert comments
-               $(data.returnValues.template).insertBefore(this._loadNextComments);
-               
-               // update time of last comment
-               this._container.data('lastCommentTime', data.returnValues.lastCommentTime);
-       },
-       
-       /**
-        * Inserts previously loaded responses.
-        * 
-        * @param       object          data
-        */
-       _insertResponses: function(data) {
-               var $comment = this._comments[data.returnValues.commentID];
-               
-               // insert responses
-               $(data.returnValues.template).appendTo($comment.find('ul.commentResponseList'));
-               
-               // update time of last response
-               $comment.data('lastResponseTime', data.returnValues.lastResponseTime);
-               
-               // update button state to load next responses
-               this._handleLoadNextResponses(data.returnValues.commentID);
-       },
-       
-       /**
-        * Removes a comment or response from list.
-        * 
-        * @param       object          data
-        */
-       _remove: function(data) {
-               if (data.returnValues.commentID) {
-                       this._comments[data.returnValues.commentID].remove();
-                       delete this._comments[data.returnValues.commentID];
-               }
-               else {
-                       this._responses[data.returnValues.responseID].remove();
-                       delete this._responses[data.returnValues.responseID];
-               }
-       },
-       
-       /**
-        * Prepares editing of a comment or response.
-        * 
-        * @param       object          data
-        */
-       _edit: function(data) {
-               if (data.returnValues.commentID) {
-                       var $content = this._comments[data.returnValues.commentID].find('.commentContent:eq(0) .userMessage:eq(0)');
-               }
-               else {
-                       var $content = this._responses[data.returnValues.responseID].find('.commentContent:eq(0) .userMessage:eq(0)');
-               }
-               
-               // replace content with input field
-               $content.html($.proxy(function(index, oldHTML) {
-                       var $input = $('<input type="text" class="long" maxlength="65535" /><small>' + WCF.Language.get('wcf.comment.description') + '</small>').val(data.returnValues.message);
-                       $input.data('__html', oldHTML).keyup($.proxy(this._saveEdit, this));
-                       
-                       if (data.returnValues.commentID) {
-                               $input.data('commentID', data.returnValues.commentID);
-                       }
-                       else {
-                               $input.data('responseID', data.returnValues.responseID);
-                       }
-                       
-                       return $input;
-               }, this));
-               $content.children('input').focus();
-               
-               // hide elements
-               $content.parent().find('.containerHeadline:eq(0)').hide();
-               $content.parent().find('.buttonGroupNavigation:eq(0)').hide();
-       },
-       
-       /**
-        * Updates a comment or response.
-        * 
-        * @param       object          data
-        */
-       _update: function(data) {
-               if (data.returnValues.commentID) {
-                       var $input = this._comments[data.returnValues.commentID].find('.commentContent:eq(0) .userMessage:eq(0) > input');
-               }
-               else {
-                       var $input = this._responses[data.returnValues.responseID].find('.commentContent:eq(0) .userMessage:eq(0) > input');
-               }
-               
-               $input.data('__html', data.returnValues.message);
-               
-               this._cancelEdit($input);
-       },
-       
-       /**
-        * Creates the guest dialog based on the given return data from the AJAX
-        * request.
-        * 
-        * @param       object          data
-        */
-       _createGuestDialog: function(data) {
-               this._guestDialog = $('<div id="commentAddGuestDialog" />').append(data.returnValues.template).hide().appendTo(document.body);
-               
-               // bind submit event listeners
-               this._guestDialog.find('input[type="submit"]').click($.proxy(this._submit, this));
-
-               this._guestDialog.find('input[type="text"]').keydown($.proxy(this._keyDown, this));
-
-               // check if recaptcha is used
-               this._useRecaptcha = this._guestDialog.find('dl.reCaptcha').length > 0;
-               
-               this._guestDialog.wcfDialog({
-                       'title': WCF.Language.get('wcf.comment.guestDialog.title')
-               });
-       },
-
-       /**
-        * Handles clicking enter in the input fields of the guest dialog by
-        * submitting it.
-        * 
-        * @param       Event           event
-        */
-       _keyDown: function(event) {
-               if (event.which === $.ui.keyCode.ENTER) {
-                       this._submit();
-               }
-       },
-
-       /**
-        * Handles errors during creation of a comment or response due to the input
-        * in the guest dialog.
-        * 
-        * @param       object          errors
-        */
-       _handleGuestDialogErrors: function(errors) {
-               if (errors.username) {
-                       var $usernameInput = this._guestDialog.find('input[name="username"]');
-                       var $errorMessage = $usernameInput.next('.innerError');
-                       if (!$errorMessage.length) {
-                               $errorMessage = $('<small class="innerError" />').text(errors.username).insertAfter($usernameInput);
-                       }
-                       else {
-                               $errorMessage.text(errors.username).show();
-                       }
-               }
-               
-               if (errors.recaptcha) {
-                       Recaptcha.reload();
-
-                       var $recaptchaInput = this._guestDialog.find('input[name="recaptcha_response_field"]');
-                       var $errorMessage = $recaptchaInput.next('.innerError');
-                       if (!$errorMessage.length) {
-                               $errorMessage = $('<small class="innerError" />').text(errors.recaptcha).insertAfter($recaptchaInput);
-                       }
-                       else {
-                               $errorMessage.text(errors.recaptcha).show();
-                       }
-               }
-
-               this._guestDialog.find('input[type="submit"]').enable();
-       },
-       
-       /**
-        * Handles submitting the guest dialog.
-        * 
-        * @param       Event           event
-        */
-       _submit: function(event) {
-               var $submit = true;
-
-               this._guestDialog.find('input[type="submit"]').enable();
-
-               // validate username
-               var $usernameInput = this._guestDialog.find('input[name="username"]');
-               var $username = $usernameInput.val();
-               var $usernameErrorMessage = $usernameInput.next('.innerError');
-               if (!$username) {
-                       $submit = false;
-                       if (!$usernameErrorMessage.length) {
-                               $usernameErrorMessage = $('<small class="innerError" />').text(WCF.Language.get('wcf.global.form.error.empty')).insertAfter($usernameInput);
-                       }
-                       else {
-                               $usernameErrorMessage.text(WCF.Language.get('wcf.global.form.error.empty')).show();
-                       }
-               }
-
-               // validate recaptcha
-               if (this._useRecaptcha) {
-                       var $recaptchaInput = this._guestDialog.find('input[name="recaptcha_response_field"]');
-                       var $recaptchaResponse = $recaptchaInput.val();
-                       var $recaptchaErrorMessage = $recaptchaInput.next('.innerError');
-                       if (!$recaptchaResponse) {
-                               $submit = false;
-                               if (!$recaptchaErrorMessage.length) {
-                                       $recaptchaErrorMessage = $('<small class="innerError" />').text(WCF.Language.get('wcf.global.form.error.empty')).insertAfter($recaptchaInput);
-                               }
-                               else {
-                                       $recaptchaErrorMessage.text(WCF.Language.get('wcf.global.form.error.empty')).show();
-                               }
-                       }
-               }
-
-               if ($submit) {
-                       if ($usernameErrorMessage.length) {
-                               $usernameErrorMessage.hide();
-                       }
-
-                       if (this._useRecaptcha && $recaptchaErrorMessage.length) {
-                               $recaptchaErrorMessage.hide();
-                       }
-
-                       var $data = this._commentData;
-                       $data.username = $username;
-
-                       var $parameters = {
-                               data: $data
-                       };
-
-                       if (this._useRecaptcha) {
-                               $parameters.recaptchaChallenge = Recaptcha.get_challenge();
-                               $parameters.recaptchaResponse = Recaptcha.get_response();
-                       }
-                       
-                       this._proxy.setOption('data', {
-                               actionName: this._commentData.commentID ? 'addResponse' : 'addComment',
-                               className: 'wcf\\data\\comment\\CommentAction',
-                               parameters: $parameters
-                       });
-                       this._proxy.sendRequest();
-
-                       this._guestDialog.find('input[type="submit"]').disable();
-               }
-       },
-       
-       /**
-        * Saves editing of a comment or response.
-        * 
-        * @param       object          event
-        */
-       _saveEdit: function(event) {
-               var $input = $(event.currentTarget);
-               
-               // abort with [Esc]
-               if (event.which === 27) {
-                       this._cancelEdit($input);
-                       return;
-               }
-               else if (event.which !== 13) {
-                       // ignore everything except for [Enter]
-                       return;
-               }
-               
-               var $message = $.trim($input.val());
-               
-               // ignore empty message
-               if ($message === '') {
-                       return;
-               }
-               
-               var $data = {
-                       message: $message,
-                       objectID: this._container.data('objectID'),
-                       objectTypeID: this._container.data('objectTypeID')
-               };
-               if ($input.data('commentID')) {
-                       $data.commentID = $input.data('commentID');
-               }
-               else {
-                       $data.responseID = $input.data('responseID');
-               }
-               
-               this._proxy.setOption('data', {
-                       actionName: 'edit',
-                       className: 'wcf\\data\\comment\\CommentAction',
-                       parameters: {
-                               data: $data
-                       }
-               });
-               this._proxy.sendRequest()
-       },
-       
-       /**
-        * Cancels editing of a comment or response.
-        * 
-        * @param       jQuery          input
-        */
-       _cancelEdit: function(input) {
-               // restore elements
-               input.parent().prev('.containerHeadline:eq(0)').show();
-               input.parent().next('.buttonGroupNavigation:eq(0)').show();
-               
-               // restore HTML
-               input.parent().html(input.data('__html'));
-       }
-});
-
-/**
- * Like support for comments
- * 
- * @see        WCF.Like
- */
-WCF.Comment.Like = WCF.Like.extend({
-       /**
-        * @see WCF.Like._getContainers()
-        */
-       _getContainers: function() {
-               return $('.commentList > li.comment');
-       },
-       
-       /**
-        * @see WCF.Like._getObjectID()
-        */
-       _getObjectID: function(containerID) {
-               return this._containers[containerID].data('commentID');
-       },
-       
-       /**
-        * @see WCF.Like._buildWidget()
-        */
-       _buildWidget: function(containerID, likeButton, dislikeButton, badge, summary) {
-               this._containers[containerID].find('.containerHeadline:eq(0) > h3').append(badge);
-               
-               if (this._canLike) {
-                       likeButton.appendTo(this._containers[containerID].find('.commentOptions:eq(0)'));
-                       dislikeButton.appendTo(this._containers[containerID].find('.commentOptions:eq(0)'));
-               }
-       },
-       
-       /**
-        * @see WCF.Like._getWidgetContainer()
-        */
-       _getWidgetContainer: function(containerID) {},
-       
-       /**
-        * @see WCF.Like._addWidget()
-        */
-       _addWidget: function(containerID, widget) {}
-});
-
-/**
- * Namespace for comment responses
- */
-WCF.Comment.Response = { };
-
-/**
- * Like support for comments responses.
- * 
- * @see        WCF.Like
- */
-WCF.Comment.Response.Like = WCF.Like.extend({
-       /**
-        * @see WCF.Like._addWidget()
-        */
-       _addWidget: function(containerID, widget) { },
-       
-       /**
-        * @see WCF.Like._buildWidget()
-        */
-       _buildWidget: function(containerID, likeButton, dislikeButton, badge, summary) {
-               this._containers[containerID].find('.containerHeadline:eq(0) > h3').append(badge);
-               
-               if (this._canLike) {
-                       likeButton.appendTo(this._containers[containerID].find('.commentOptions:eq(0)'));
-                       dislikeButton.appendTo(this._containers[containerID].find('.commentOptions:eq(0)'));
-               }
-       },
-       
-       /**
-        * @see WCF.Like._getContainers()
-        */
-       _getContainers: function() {
-               return $('.commentResponseList > li.commentResponse');
-       },
-       
-       /**
-        * @see WCF.Like._getObjectID()
-        */
-       _getObjectID: function(containerID) {
-               return this._containers[containerID].data('responseID');
-       },
-       
-       /**
-        * @see WCF.Like._getWidgetContainer()
-        */
-       _getWidgetContainer: function(containerID) { }
-});
-
-
-// WCF.ImageViewer.js
-/**
- * ImageViewer for WCF.
- * Based upon "Slimbox 2" by Christophe Beyls 2007-2012, http://www.digitalia.be/software/slimbox2, MIT-style license.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2014 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- */
-WCF.ImageViewer = Class.extend({
-       /**
-        * Initializes the ImageViewer for every a-tag with the attribute rel = imageviewer.
-        */
-       init: function() {
-               // navigation buttons
-               $('<span class="icon icon16 icon-chevron-left jsTooltip" title="' + WCF.Language.get('wcf.imageViewer.previous') + '" />').appendTo($('#lbPrevLink'));
-               $('<span class="icon icon16 icon-chevron-right jsTooltip" title="' + WCF.Language.get('wcf.imageViewer.next') + '" />').appendTo($('#lbNextLink'));
-               
-               // close and enlarge icons
-               $('<span class="icon icon32 icon-remove jsTooltip" title="' + WCF.Language.get('wcf.imageViewer.close') + '" />').appendTo($('#lbCloseLink'));
-               var $buttonEnlarge = $('<span class="icon icon32 icon-resize-full jsTooltip" title="' + WCF.Language.get('wcf.imageViewer.enlarge') + '" id="lbEnlarge" />').insertAfter($('#lbCloseLink'));
-               
-               // handle enlarge button
-               $buttonEnlarge.click($.proxy(this._enlarge, this));
-               
-               this._initImageViewer();
-               
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.ImageViewer', $.proxy(this._domNodeInserted, this));
-               WCF.DOMNodeInsertedHandler.execute();
-       },
-       
-       /**
-        * Executes actions upon DOMNodeInserted events.
-        */
-       _domNodeInserted: function() {
-               this._initImageSizeCheck();
-               this._initImageViewer();
-       },
-       
-       /**
-        * Initializes the image viewer for all links with class ".jsImageViewer"
-        */
-       _initImageViewer: function() {
-               // disable ImageViewer on touch devices identifying themselves as 'mobile'
-               if ($.browser.touch && /[Mm]obile/.test(navigator.userAgent)) {
-                       // Apple always appends mobile regardless if it is an iPad or iP(hone|od)
-                       if (!/iPad/.test(navigator.userAgent)) {
-                               return;
-                       }
-               }
-               
-               var $links = $('a.jsImageViewer');
-               if ($links.length) {
-                       $links.removeClass('jsImageViewer').slimbox({
-                               counterText: WCF.Language.get('wcf.imageViewer.counter'),
-                               loop: true
-                       });
-               }
-       },
-       
-       /**
-        * Redirects to image for full view.
-        */
-       _enlarge: function() {
-               var $url = $('#lbImage').css('backgroundImage');
-               if ($url) {
-                       $url = $url.replace(/^url\((["']?)(.*)\1\)$/, '$2');
-                       window.location = $url;
-               }
-       },
-       
-       /**
-        * Initializes the image size check.
-        */
-       _initImageSizeCheck: function() {
-               $('.jsResizeImage').each($.proxy(function(index, image) {
-                       if (image.complete) this._checkImageSize({ currentTarget: image });
-               }, this));
-               
-               $('.jsResizeImage').on('load', $.proxy(this._checkImageSize, this));
-       },
-       
-       /**
-        * Checks the image size.
-        */
-       _checkImageSize: function(event) {
-               var $image = $(event.currentTarget);
-               if (!$image.is(':visible')) {
-                       $image.off('load');
-                       
-                       return;
-               }
-               
-               $image.removeClass('jsResizeImage');
-               var $dimensions = $image.getDimensions();
-               var $maxWidth = $image.parents('div').innerWidth();
-               
-               if ($dimensions.width > $maxWidth) {
-                       $image.css({
-                               height: Math.round($dimensions.height * ($maxWidth / $dimensions.width)) + 'px',
-                               width: $maxWidth + 'px'
-                       });
-                       
-                       if (!$image.parents('a').length) {
-                               $image.wrap('<a href="' + $image.attr('src') + '" />');
-                               $image.parent().slimbox();
-                       }
-               }
-       }
-});
-
-/**
- * Provides a focused image viewer for WCF.
- * 
- * Usage:
- * $('.triggerElement').wcfImageViewer({
- *     shiftBy: 5,
- *     
- *     enableSlideshow: 1,
- *     speed: 5,
- *     
- *     className: 'wcf\\data\\foo\\FooAction'
- * });
- */
-$.widget('ui.wcfImageViewer', {
-       /**
-        * active image index
-        * @var integer
-        */
-       _active: -1,
-       
-       /**
-        * active image object id
-        * @var integer
-        */
-       _activeImage: null,
-       
-       /**
-        * image viewer container object
-        * @var jQuery
-        */
-       _container: null,
-       
-       /**
-        * initialization state
-        * @var boolean
-        */
-       _didInit: false,
-       
-       /**
-        * overrides slideshow settings unless explicitly enabled by user
-        * @var boolean
-        */
-       _disableSlideshow: false,
-       
-       /**
-        * event namespace used to distinguish event handlers using $.proxy
-        * @var string
-        */
-       _eventNamespace: '',
-       
-       /**
-        * list of available images
-        * @var array<object>
-        */
-       _images: [ ],
-       
-       /**
-        * true if image viewer is open
-        * @var boolean
-        */
-       _isOpen: false,
-       
-       /**
-        * number of total images
-        * @var integer
-        */
-       _items: -1,
-       
-       /**
-        * maximum dimensions for enlarged view
-        * @var object<integer>
-        */
-       _maxDimensions: {
-               height: 0,
-               width: 0
-       },
-       
-       /**
-        * action proxy object
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * true if slideshow is currently running
-        * @var boolean
-        */
-       _slideshowEnabled: false,
-       
-       /**
-        * visible width of thumbnail container
-        * @var integer
-        */
-       _thumbnailContainerWidth: 0,
-       
-       /**
-        * right margin of a thumbnail
-        * @var integer
-        */
-       _thumbnailMarginRight: 0,
-       
-       /**
-        * left offset of thumbnail list
-        * @var integer
-        */
-       _thumbnailOffset: 0,
-       
-       /**
-        * outer width of a thumbnail (includes margin)
-        * @var integer
-        */
-       _thumbnailWidth: 0,
-       
-       /**
-        * slideshow timer object
-        * @var WCF.PeriodicalExecuter
-        */
-       _timer: null,
-       
-       /**
-        * list of interface elements
-        * @var object<jQuery>
-        */
-       _ui: {
-               buttonNext: null,
-               buttonPrevious: null,
-               header: null,
-               image: null,
-               imageContainer: null,
-               imageList: null,
-               slideshow: {
-                       container: null,
-                       enlarge: null,
-                       next: null,
-                       previous: null,
-                       toggle: null
-               }
-       },
-       
-       /**
-        * list of options parsed during init
-        * @var object<mixed>
-        */
-       options: {
-               // navigation
-               shiftBy: 5, // thumbnail slider control
-               
-               // slideshow
-               enableSlideshow: 1,
-               speed: 5, // time in seconds
-               
-               // ajax
-               className: '' // must be an instance of \wcf\data\IImageViewerAction
-       },
-       
-       /**
-        * Creates a new wcfImageViewer instance.
-        */
-       _create: function() {
-               this._active = -1;
-               this._activeImage = null;
-               this._container = null;
-               this._didInit = false;
-               this._disableSlideshow = (this.element.data('disableSlideshow'));
-               this._eventNamespace = this.element.wcfIdentify();
-               this._images = [ ];
-               this._isOpen = false;
-               this._items = -1;
-               this._maxDimensions = {
-                       height: document.documentElement.clientHeight,
-                       width: document.documentElement.clientWidth
-               };
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               this._slideshowEnabled = false;
-               this._thumbnailContainerWidth = 0;
-               this._thumbnailMarginRight = 0;
-               this._thumbnailOffset = 0;
-               this._thumbnaiLWidth = 0;
-               this._timer = null;
-               this._ui = { };
-               
-               this.element.click($.proxy(this.open, this));
-       },
-       
-       /**
-        * Opens the image viewer.
-        * 
-        * @param       object          event
-        * @return      boolean
-        */
-       open: function(event) {
-               if (event) event.preventDefault();
-               
-               if (this._isOpen) {
-                       return false;
-               }
-               
-               if (this._images.length === 0) {
-                       this._loadNextImages(true);
-               }
-               else {
-                       this._render(false, this.element.data('targetImageID'));
-                       
-                       if (this._items > 1 && this._slideshowEnabled) {
-                               this.startSlideshow();
-                       }
-               }
-               
-               this._bindListener();
-               
-               this._isOpen = true;
-               
-               WCF.System.DisableScrolling.disable();
-               
-               return true;
-       },
-       
-       /**
-        * Closes the image viewer.
-        * 
-        * @return      boolean
-        */
-       close: function(event) {
-               if (event) event.preventDefault();
-               
-               if (!this._isOpen) {
-                       return false;
-               }
-               
-               this._container.removeClass('open');
-               if (this._timer !== null) {
-                       this._timer.stop();
-               }
-               
-               this._unbindListener();
-               
-               this._isOpen = false;
-               
-               WCF.System.DisableScrolling.enable();
-               
-               return true;
-       },
-       
-       /**
-        * Enables the slideshow.
-        * 
-        * @return      boolean
-        */
-       startSlideshow: function() {
-               if (this._disableSlideshow || this._slideshowEnabled) {
-                       return false;
-               }
-               
-               if (this._timer === null) {
-                       this._timer = new WCF.PeriodicalExecuter($.proxy(function() {
-                               var $index = this._active + 1;
-                               if ($index == this._items) {
-                                       $index = 0;
-                               }
-                               
-                               this.showImage($index);
-                       }, this), this.options.speed * 1000);
-               }
-               else {
-                       this._timer.resume();
-               }
-               
-               this._slideshowEnabled = true;
-               
-               this._ui.slideshow.toggle.children('span').removeClass('icon-play').addClass('icon-pause');
-               
-               return true;
-       },
-       
-       /**
-        * Disables the slideshow.
-        * 
-        * @param       boolean         disableSlideshow
-        * @return      boolean
-        */
-       stopSlideshow: function(disableSlideshow) {
-               if (!this._slideshowEnabled) {
-                       return false;
-               }
-               
-               this._timer.stop();
-               if (disableSlideshow) {
-                       this._ui.slideshow.toggle.children('span').removeClass('icon-pause').addClass('icon-play');
-               }
-               
-               this._slideshowEnabled = false;
-               
-               return true;
-       },
-       
-       /**
-        * Binds event listeners.
-        */
-       _bindListener: function() {
-               $(document).on('keydown.' + this._eventNamespace, $.proxy(this._keyDown, this));
-               $(window).on('resize.' + this._eventNamespace, $.proxy(this._renderImage, this));
-       },
-       
-       /**
-        * Unbinds event listeners.
-        */
-       _unbindListener: function() {
-               $(document).off('keydown.' + this._eventNamespace);
-               $(window).off('resize.' + this._eventNamespace);
-       },
-       
-       /**
-        * Closes the slideshow on escape.
-        * 
-        * @param       object          event
-        * @return      boolean
-        */
-       _keyDown: function(event) {
-               switch (event.which) {
-                       // close slideshow
-                       case $.ui.keyCode.ESCAPE:
-                               this.close();
-                       break;
-                       
-                       // show previous image
-                       case $.ui.keyCode.LEFT:
-                               this._previousImage();
-                       break;
-                       
-                       // show next image
-                       case $.ui.keyCode.RIGHT:
-                               this._nextImage();
-                       break;
-                       
-                       // enable fullscreen mode
-                       case $.ui.keyCode.UP:
-                               if (!this._container.hasClass('maximized')) {
-                                       this._toggleView();
-                               }
-                       break;
-                       
-                       // disable fullscreen mode
-                       case $.ui.keyCode.DOWN:
-                               if (this._container.hasClass('maximized')) {
-                                       this._toggleView();
-                               }
-                       break;
-                       
-                       // jump to image page or full version
-                       case $.ui.keyCode.ENTER:
-                               var $link = this._ui.header.find('> div > h1 > a');
-                               if ($link.length == 1) {
-                                       // forward to image page
-                                       window.location = $link.prop('href');
-                               }
-                               else {
-                                       // forward to full version
-                                       this._ui.slideshow.full.trigger('click');
-                               }
-                       break;
-                       
-                       // toggle play/pause (80 = [p])
-                       case 80:
-                               this._ui.slideshow.toggle.trigger('click');
-                       break;
-                       
-                       default:
-                               return true;
-                       break;
-               }
-               
-               return false;
-       },
-       
-       /**
-        * Renders the image viewer UI.
-        * 
-        * @param       boolean         initialized
-        * @param       integer         targetImageID
-        */
-       _render: function(initialized, targetImageID) {
-               this._container.addClass('open');
-               
-               var $thumbnail = null;
-               if (initialized) {
-                       $thumbnail = this._ui.imageList.children('li:eq(0)');
-                       this._thumbnailMarginRight = parseInt($thumbnail.css('marginRight').replace(/px$/, '')) || 0;
-                       this._thumbnailWidth = $thumbnail.outerWidth(true);
-                       this._thumbnailContainerWidth = this._ui.imageList.parent().innerWidth();
-                       
-                       if (this._items > 1 && this.options.enableSlideshow && !targetImageID) {
-                               this.startSlideshow();
-                       }
-               }
-               
-               if (targetImageID) {
-                       this._ui.imageList.children('li').each($.proxy(function(index, item) {
-                               var $item = $(item);
-                               if ($item.data('objectID') == targetImageID) {
-                                       $item.trigger('click');
-                                       this.moveToImage($item.data('index'));
-                                       
-                                       return false;
-                               }
-                       }, this));
-               }
-               else if ($thumbnail !== null) {
-                       $thumbnail.trigger('click');
-               }
-                       
-               this._toggleButtons();
-               
-               // check if there is enough space to load more thumbnails
-               this._preload();
-       },
-       
-       /**
-        * Attempts to load the next images.
-        */
-       _preload: function() {
-               if (this._images.length < this._items) {
-                       var $thumbnailsWidth = this._images.length * this._thumbnailWidth;
-                       if ($thumbnailsWidth - this._thumbnailOffset < this._thumbnailContainerWidth) {
-                               this._loadNextImages(false);
-                       }
-               }
-       },
-       
-       /**
-        * Displays image on thumbnail click.
-        * 
-        * @param       object          event
-        */
-       _showImage: function(event) {
-               this.showImage($(event.currentTarget).data('index'), true);
-       },
-       
-       /**
-        * Displays an image by index.
-        * 
-        * @param       integer         index
-        * @param       boolean         disableSlideshow
-        * @return      boolean
-        */
-       showImage: function(index, disableSlideshow) {
-               if (this._active == index) {
-                       return false;
-               }
-               
-               this.stopSlideshow(disableSlideshow || false);
-               
-               // reset active marking
-               if (this._active != -1) {
-                       this._images[this._active].listItem.removeClass('active');
-               }
-               
-               this._active = index;
-               var $image = this._images[index];
-               
-               this._ui.imageList.children('li').removeClass('active');
-               $image.listItem.addClass('active');
-               
-               var $dimensions = this._ui.imageContainer.getDimensions('inner');
-               var $newImageIndex = (this._activeImage ? 0 : 1);
-               
-               if (this._activeImage !== null) {
-                       this._ui.images[this._activeImage].removeClass('active');
-               }
-               
-               this._activeImage = $newImageIndex;
-               
-               var $currentActiveImage = this._active;
-               this._ui.imageContainer.addClass('loading');
-               this._ui.images[$newImageIndex].off('load').prop('src', false).on('load', $.proxy(function() {
-                       this._imageOnLoad($currentActiveImage, $newImageIndex);
-               }, this));
-               
-               this._renderImage($newImageIndex, $image, $dimensions);
-               
-               // user
-               var $link = this._ui.header.find('> div > a').prop('href', $image.user.link).prop('title', $image.user.username);
-               $link.children('img').prop('src', $image.user.avatarURL);
-               
-               // meta data
-               var $title = WCF.String.escapeHTML($image.image.title);
-               if ($image.image.link) $title = '<a href="' + $image.image.link + '">' + $image.image.title + '</a>';
-               this._ui.header.find('> div > h1').html($title);
-               
-               var $seriesTitle = ($image.series && $image.series.title ? WCF.String.escapeHTML($image.series.title) : '');
-               if ($image.series.link) $seriesTitle = '<a href="' + $image.series.link + '">' + $seriesTitle + '</a>';
-               this._ui.header.find('> div > h2').html($seriesTitle);
-               
-               this._ui.header.find('> div > h3').text(WCF.Language.get('wcf.imageViewer.seriesIndex').replace(/{x}/, $image.listItem.data('index') + 1).replace(/{y}/, this._items));
-               
-               this._ui.slideshow.full.data('link', ($image.image.fullURL ? $image.image.fullURL : $image.image.url));
-               
-               this.moveToImage($image.listItem.data('index'));
-               
-               this._toggleButtons();
-               
-               return true;
-       },
-       
-       /**
-        * Callback function for the image 'load' event.
-        * 
-        * @param       integer         currentActiveImage
-        * @param       integer         activeImageIndex
-        */
-       _imageOnLoad: function(currentActiveImage, activeImageIndex) {
-               // image did not load in time, ignore
-               if (currentActiveImage != this._active) {
-                       return;
-               }
-               
-               this._ui.imageContainer.removeClass('loading');
-               this._ui.images[activeImageIndex].addClass('active');
-               
-               this.startSlideshow();
-       },
-       
-       /**
-        * Renders target image, leaving 'imageData' undefined will invoke the rendering process for the currently active image.
-        * 
-        * @param       integer         targetIndex
-        * @param       object          imageData
-        * @param       object          containerDimensions
-        */
-       _renderImage: function(targetIndex, imageData, containerDimensions) {
-               if (!imageData) {
-                       targetIndex = this._activeImage;
-                       imageData = this._images[this._active];
-                       
-                       containerDimensions = {
-                               height: $(window).height() - (this._container.hasClass('maximized') ? 0 : 200),
-                               width: this._ui.imageContainer.innerWidth()
-                       };
-               }
-               
-               // simulate padding
-               containerDimensions.height -= 22;
-               containerDimensions.width -= 20;
-               
-               this._ui.images[targetIndex].prop('src', imageData.image.url);
-               
-               var $height = imageData.image.height;
-               var $width = imageData.image.width;
-               var $ratio = 0.0;
-               
-               // check if image exceeds dimensions on the Y axis
-               if ($height > containerDimensions.height) {
-                       $ratio = containerDimensions.height / $height;
-                       $height = containerDimensions.height;
-                       $width = Math.floor($width * $ratio);
-               }
-               
-               // check if image exceeds dimensions on the X axis
-               if ($width > containerDimensions.width) {
-                       $ratio = containerDimensions.width / $width;
-                       $width = containerDimensions.width;
-                       $height = Math.floor($height * $ratio);
-               }
-               
-               var $left = Math.floor((containerDimensions.width - $width) / 2);
-               
-               this._ui.images[targetIndex].css({
-                       height: $height + 'px',
-                       left: ($left + 10) + 'px',
-                       marginTop: (Math.round($height / 2) * -1) + 'px',
-                       width: $width + 'px'
-               });
-       },
-       
-       /**
-        * Initialites the user interface.
-        * 
-        * @return      boolean
-        */
-       _initUI: function() {
-               if (this._didInit) {
-                       return false;
-               }
-               
-               this._didInit = true;
-               
-               this._container = $('<div class="wcfImageViewer" />').appendTo(document.body);
-               var $imageContainer = $('<div><img class="active" /><img /></div>').appendTo(this._container);
-               var $imageList = $('<footer><span class="wcfImageViewerButtonPrevious icon icon-double-angle-left" /><div><ul /></div><span class="wcfImageViewerButtonNext icon icon-double-angle-right" /></footer>').appendTo(this._container);
-               var $slideshowContainer = $('<ul />').appendTo($imageContainer);
-               var $slideshowButtonPrevious = $('<li class="wcfImageViewerSlideshowButtonPrevious"><span class="icon icon48 icon-angle-left" /></li>').appendTo($slideshowContainer);
-               var $slideshowButtonToggle = $('<li class="wcfImageViewerSlideshowButtonToggle pointer"><span class="icon icon48 icon-play" /></li>').appendTo($slideshowContainer);
-               var $slideshowButtonNext = $('<li class="wcfImageViewerSlideshowButtonNext"><span class="icon icon48 icon-angle-right" /></li>').appendTo($slideshowContainer);
-               var $slideshowButtonEnlarge = $('<li class="wcfImageViewerSlideshowButtonEnlarge pointer jsTooltip" title="' + WCF.Language.get('wcf.imageViewer.button.enlarge') + '"><span class="icon icon48 icon-resize-full" /></li>').appendTo($slideshowContainer);
-               var $slideshowButtonFull = $('<li class="wcfImageViewerSlideshowButtonFull pointer jsTooltip" title="' + WCF.Language.get('wcf.imageViewer.button.full') + '"><span class="icon icon48 icon-external-link" /></li>').appendTo($slideshowContainer);
-               
-               this._ui = {
-                       buttonNext: $imageList.children('span.wcfImageViewerButtonNext'),
-                       buttonPrevious: $imageList.children('span.wcfImageViewerButtonPrevious'),
-                       header: $('<header><div class="box64"><a class="framed jsTooltip"><img /></a><h1 /><h2 /><h3 /></div></header>').appendTo(this._container),
-                       imageContainer: $imageContainer,
-                       images: [
-                               $imageContainer.children('img:eq(0)').on('webkitTransitionEnd transitionend msTransitionEnd oTransitionEnd', function() { $(this).removeClass('animateTransformation'); }),
-                               $imageContainer.children('img:eq(1)').on('webkitTransitionEnd transitionend msTransitionEnd oTransitionEnd', function() { $(this).removeClass('animateTransformation'); })
-                       ],
-                       imageList: $imageList.find('> div > ul'),
-                       slideshow: {
-                               container: $slideshowContainer,
-                               enlarge: $slideshowButtonEnlarge,
-                               full: $slideshowButtonFull,
-                               next: $slideshowButtonNext,
-                               previous: $slideshowButtonPrevious,
-                               toggle: $slideshowButtonToggle
-                       }
-               };
-               
-               this._ui.buttonNext.click($.proxy(this._next, this));
-               this._ui.buttonPrevious.click($.proxy(this._previous, this));
-               
-               $slideshowButtonNext.click($.proxy(this._nextImage, this));
-               $slideshowButtonPrevious.click($.proxy(this._previousImage, this));
-               $slideshowButtonEnlarge.click($.proxy(this._toggleView, this));
-               $slideshowButtonToggle.click($.proxy(function() {
-                       if (this._slideshowEnabled) {
-                               this.stopSlideshow(true);
-                       }
-                       else {
-                               this._disableSlideshow = false;
-                               this.startSlideshow();
-                       }
-               }, this));
-               $slideshowButtonFull.click(function(event) { window.location = $(event.currentTarget).data('link'); });
-               
-               // close button
-               $('<span class="wcfImageViewerButtonClose icon icon48 icon-remove pointer jsTooltip" title="' + WCF.Language.get('wcf.global.button.close') + '" />').appendTo(this._ui.header).click($.proxy(this.close, this));
-               
-               return true;
-       },
-       
-       /**
-        * Toggles between normal and fullscreen view.
-        */
-       _toggleView: function() {
-               this._ui.images[this._activeImage].addClass('animateTransformation');
-               this._container.toggleClass('maximized');
-               this._ui.slideshow.enlarge.toggleClass('active').children('span').toggleClass('icon-resize-full').toggleClass('icon-resize-small');
-               
-               this._renderImage(null, undefined, null);
-       },
-       
-       /**
-        * Shifts the thumbnail list.
-        * 
-        * @param       object          event
-        * @param       integer         shiftBy
-        */
-       _next: function(event, shiftBy) {
-               if (this._ui.buttonNext.hasClass('pointer')) {
-                       if (shiftBy == undefined) {
-                               this.stopSlideshow(true);
-                       }
-                       
-                       var $maximumOffset = Math.max((this._items * this._thumbnailWidth) - this._thumbnailContainerWidth - this._thumbnailMarginRight, 0);
-                       this._thumbnailOffset = Math.min(this._thumbnailOffset + (this._thumbnailWidth * (shiftBy ? shiftBy : this.options.shiftBy)), $maximumOffset);
-                       this._ui.imageList.css('marginLeft', (this._thumbnailOffset * -1));
-               }
-               
-               this._preload();
-               
-               this._toggleButtons();
-       },
-       
-       /**
-        * Unshifts the thumbnail list.
-        * 
-        * @param       object          event
-        * @param       integer         shiftBy
-        */
-       _previous: function(event, unshiftBy) {
-               if (this._ui.buttonPrevious.hasClass('pointer')) {
-                       if (unshiftBy == undefined) {
-                               this.stopSlideshow(true);
-                       }
-                       
-                       this._thumbnailOffset = Math.max(this._thumbnailOffset - (this._thumbnailWidth * (unshiftBy ? unshiftBy : this.options.shiftBy)), 0);
-                       this._ui.imageList.css('marginLeft', (this._thumbnailOffset * -1));
-               }
-               
-               this._toggleButtons();
-       },
-       
-       /**
-        * Displays the next image.
-        * 
-        * @param       object          event
-        */
-       _nextImage: function(event) {
-               if (this._ui.slideshow.next.hasClass('pointer')) {
-                       this._disableSlideshow = true;
-                       
-                       this.stopSlideshow(true);
-                       this.showImage(this._active + 1);
-               }
-       },
-       
-       /**
-        * Displays the previous image.
-        * 
-        * @param       object          event
-        */
-       _previousImage: function(event) {
-               if (this._ui.slideshow.previous.hasClass('pointer')) {
-                       this._disableSlideshow = true;
-                       
-                       this.stopSlideshow(true);
-                       this.showImage(this._active - 1);
-               }
-       },
-       
-       /**
-        * Moves thumbnail list to target thumbnail.
-        * 
-        * @param       integer         seriesIndex
-        */
-       moveToImage: function(seriesIndex) {
-               // calculate start and end of thumbnail
-               var $start = (seriesIndex - 3) * this._thumbnailWidth;
-               var $end = $start + (this._thumbnailWidth * 5);
-               
-               // calculate visible offsets
-               var $left = this._thumbnailOffset;
-               var $right = this._thumbnailOffset + this._thumbnailContainerWidth;
-               
-               // check if thumbnail is within boundaries
-               var $shouldMove = false;
-               if ($start < $left || $end > $right) {
-                       $shouldMove = true;
-               }
-               
-               // try to shift until the thumbnail itself and the next/previous 2 thumbnails are visible
-               if ($shouldMove) {
-                       var $shiftBy = 0;
-                       
-                       // unshift
-                       if ($start < $left) {
-                               while ($start < $left) {
-                                       $shiftBy++;
-                                       $left -= this._thumbnailWidth;
-                               }
-                               
-                               this._previous(null, $shiftBy);
-                       }
-                       else {
-                               // shift
-                               while ($end > $right) {
-                                       $shiftBy++;
-                                       $right += this._thumbnailWidth;
-                               }
-                               
-                               this._next(null, $shiftBy);
-                       }
-               }
-       },
-       
-       /**
-        * Toggles control buttons.
-        */
-       _toggleButtons: function() {
-               // button 'previous'
-               if (this._thumbnailOffset > 0) {
-                       this._ui.buttonPrevious.addClass('pointer');
-               }
-               else {
-                       this._ui.buttonPrevious.removeClass('pointer');
-               }
-               
-               // button 'next'
-               var $maximumOffset = (this._images.length * this._thumbnailWidth) - this._thumbnailContainerWidth - this._thumbnailMarginRight;
-               if (this._thumbnailOffset >= $maximumOffset) {
-                       this._ui.buttonNext.removeClass('pointer');
-               }
-               else {
-                       this._ui.buttonNext.addClass('pointer');
-               }
-               
-               // slideshow controls
-               if (this._active > 0) {
-                       this._ui.slideshow.previous.addClass('pointer');
-               }
-               else {
-                       this._ui.slideshow.previous.removeClass('pointer');
-               }
-               
-               if (this._active + 1 < this._images.length) {
-                       this._ui.slideshow.next.addClass('pointer');
-               }
-               else {
-                       this._ui.slideshow.next.removeClass('pointer');
-               }
-       },
-       
-       /**
-        * Inserts thumbnails.
-        * 
-        * @param       array<object>   images
-        */
-       _createThumbnails: function(images) {
-               for (var $i = 0, $length = images.length; $i < $length; $i++) {
-                       var $image = images[$i];
-                       
-                       var $listItem = $('<li class="loading pointer"><img src="' + $image.thumbnail.url + '" /></li>').appendTo(this._ui.imageList);
-                       $listItem.data('index', this._images.length).data('objectID', $image.objectID).click($.proxy(this._showImage, this));
-                       var $img = $listItem.children('img');
-                       if ($img.get(0).complete) {
-                               // thumbnail is read from cache
-                               $listItem.removeClass('loading');
-                       }
-                       else {
-                               $img.on('load', function() { $(this).parent().removeClass('loading'); });
-                       }
-                       
-                       $image.listItem = $listItem;
-                       this._images.push($image);
-               }
-       },
-       
-       /**
-        * Loads the next images via AJAX.
-        * 
-        * @param       boolean         init
-        */
-       _loadNextImages: function(init) {
-               this._proxy.setOption('data', {
-                       actionName: 'loadNextImages',
-                       className: this.options.className,
-                       interfaceName: 'wcf\\data\\IImageViewerAction',
-                       objectIDs: [ this.element.data('objectID') ],
-                       parameters: {
-                               maximumHeight: this._maxDimensions.height,
-                               maximumWidth: this._maxDimensions.width,
-                               offset: this._images.length,
-                               targetImageID: (init && this.element.data('targetImageID') ? this.element.data('targetImageID') : 0)
-                       }
-               });
-               this._proxy.setOption('showLoadingOverlay', false);
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               if (data.returnValues.items) {
-                       this._items = data.returnValues.items;
-               }
-               
-               var $initialized = this._initUI();
-               
-               this._createThumbnails(data.returnValues.images);
-               
-               var $targetImageID = (data.returnValues.targetImageID ? data.returnValues.targetImageID : 0);
-               this._render($initialized, $targetImageID);
-       }
-});
-
-
-// WCF.Label.js
-/**
- * Namespace for labels.
- */
-WCF.Label = {};
-
-/**
- * Provides enhancements for ACP label management.
- */
-WCF.Label.ACPList = Class.extend({
-       /**
-        * input element
-        * @var jQuery
-        */
-       _labelInput: null,
-       
-       /**
-        * list of pre-defined label items
-        * @var array<jQuery>
-        */
-       _labelList: [ ],
-       
-       /**
-        * Initializes the ACP label list.
-        */
-       init: function() {
-               this._labelInput = $('#label').keydown($.proxy(this._keyPressed, this)).keyup($.proxy(this._keyPressed, this)).blur($.proxy(this._keyPressed, this));
-               
-               if ($.browser.mozilla && $.browser.touch) {
-                       this._labelInput.on('input', $.proxy(this._keyPressed, this));
-               }
-               
-               $('#labelList').find('input[type="radio"]').each($.proxy(function(index, input) {
-                       var $input = $(input);
-                       
-                       // ignore custom values
-                       if ($input.prop('value') !== 'custom') {
-                               this._labelList.push($($input.next('span')));
-                       }
-               }, this));
-       },
-       
-       /**
-        * Renders label name as label or falls back to a default value if label is empty.
-        */
-       _keyPressed: function() {
-               var $text = this._labelInput.prop('value');
-               if ($text === '') $text = WCF.Language.get('wcf.acp.label.defaultValue');
-               
-               for (var $i = 0, $length = this._labelList.length; $i < $length; $i++) {
-                       this._labelList[$i].text($text);
-               }
-       }
-});
-
-/**
- * Provides simple logic to inherit associations within structured lists.
- */
-WCF.Label.ACPList.Connect = Class.extend({
-       /**
-        * Initializes inheritation for structured lists.
-        */
-       init: function() {
-               var $listItems = $('#connect .structuredList li');
-               if (!$listItems.length) return;
-               
-               $listItems.each($.proxy(function(index, item) {
-                       $(item).find('input[type="checkbox"]').click($.proxy(this._click, this));
-               }, this));
-       },
-       
-       /**
-        * Marks items as checked if they're logically below current item.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               var $listItem = $(event.currentTarget);
-               if ($listItem.is(':checked')) {
-                       $listItem = $listItem.parents('li');
-                       var $depth = $listItem.data('depth');
-                       
-                       while (true) {
-                               $listItem = $listItem.next();
-                               if (!$listItem.length) {
-                                       // no more siblings
-                                       return true;
-                               }
-                               
-                               // element is on the same or higher level (= lower depth)
-                               if ($listItem.data('depth') <= $depth) {
-                                       return true;
-                               }
-                               
-                               $listItem.find('input[type="checkbox"]').prop('checked', 'checked');
-                       }
-               }
-       }
-});
-
-/**
- * Provides a flexible label chooser.
- * 
- * @param      object          selectedLabelIDs
- * @param      string          containerSelector
- * @param      string          submitButtonSelector
- * @param      boolean         showWithoutSelection
- */
-WCF.Label.Chooser = Class.extend({
-       /**
-        * label container
-        * @var jQuery
-        */
-       _container: null,
-       
-       /**
-        * list of label groups
-        * @var object
-        */
-       _groups: { },
-       
-       /**
-        * show the 'without selection' option
-        * @var boolean
-        */
-       _showWithoutSelection: false,
-       
-       /**
-        * Initializes a new label chooser.
-        * 
-        * @param       object          selectedLabelIDs
-        * @param       string          containerSelector
-        * @param       string          submitButtonSelector
-        * @param       boolean         showWithoutSelection
-        */
-       init: function(selectedLabelIDs, containerSelector, submitButtonSelector, showWithoutSelection) {
-               this._container = null;
-               this._groups = { };
-               this._showWithoutSelection = (showWithoutSelection === true);
-               
-               // init containers
-               this._initContainers(containerSelector);
-               
-               // pre-select labels
-               if ($.getLength(selectedLabelIDs)) {
-                       for (var $groupID in selectedLabelIDs) {
-                               var $group = this._groups[$groupID];
-                               if ($group) {
-                                       WCF.Dropdown.getDropdownMenu($group.wcfIdentify()).find('> ul > li:not(.dropdownDivider)').each($.proxy(function(index, label) {
-                                               var $label = $(label);
-                                               var $labelID = $label.data('labelID') || 0;
-                                               if ($labelID && selectedLabelIDs[$groupID] == $labelID) {
-                                                       this._selectLabel($label, true);
-                                               }
-                                       }, this));
-                               }
-                       }
-               }
-               
-               // mark all containers as initialized
-               for (var $containerID in this._containers) {
-                       var $dropdown = this._containers[$containerID];
-                       if ($dropdown.data('labelID') === undefined) {
-                               $dropdown.data('labelID', 0);
-                       }
-               }
-               
-               this._container = $(containerSelector);
-               if (submitButtonSelector) {
-                       $(submitButtonSelector).click($.proxy(this._submit, this));
-               }
-               else if (this._container.is('form')) {
-                       this._container.submit($.proxy(this._submit, this));
-               }
-       },
-       
-       /**
-        * Initializes label groups.
-        * 
-        * @param       string          containerSelector
-        */
-       _initContainers: function(containerSelector) {
-               $(containerSelector).find('.labelChooser').each($.proxy(function(index, group) {
-                       var $group = $(group);
-                       var $groupID = $group.data('groupID');
-                       
-                       if (!this._groups[$groupID]) {
-                               var $containerID = $group.wcfIdentify();
-                               var $dropdownMenu = WCF.Dropdown.getDropdownMenu($containerID);
-                               if ($dropdownMenu === null) {
-                                       WCF.Dropdown.initDropdown($group.find('.dropdownToggle'));
-                                       $dropdownMenu = WCF.Dropdown.getDropdownMenu($containerID);
-                               }
-                               
-                               var $additionalList = $dropdownMenu;
-                               if ($dropdownMenu.getTagName() == 'div' && $dropdownMenu.children('.scrollableDropdownMenu').length) {
-                                       $additionalList = $('<ul />').appendTo($dropdownMenu);
-                                       $dropdownMenu = $dropdownMenu.children('.scrollableDropdownMenu');
-                               }
-                               
-                               this._groups[$groupID] = $group;
-                               
-                               $dropdownMenu.children('li').data('groupID', $groupID).click($.proxy(this._click, this));
-                               
-                               if (!$group.data('forceSelection') || this._showWithoutSelection) {
-                                       $('<li class="dropdownDivider" />').appendTo($additionalList);
-                               }
-                               
-                               if (this._showWithoutSelection) {
-                                       $('<li data-label-id="-1"><span><span class="badge label">' + WCF.Language.get('wcf.label.withoutSelection') + '</span></span></li>').data('groupID', $groupID).appendTo($additionalList).click($.proxy(this._click, this));
-                               }
-                               
-                               if (!$group.data('forceSelection')) {
-                                       var $buttonEmpty = $('<li data-label-id="0"><span><span class="badge label">' + WCF.Language.get('wcf.label.none') + '</span></span></li>').data('groupID', $groupID).appendTo($additionalList);
-                                       $buttonEmpty.click($.proxy(this._click, this));
-                               }
-                       }
-               }, this));
-       },
-       
-       /**
-        * Handles label selections.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               this._selectLabel($(event.currentTarget), false);
-       },
-       
-       /**
-        * Selects a label.
-        * 
-        * @param       jQuery          label
-        * @param       boolean         onInit
-        */
-       _selectLabel: function(label, onInit) {
-               var $group = this._groups[label.data('groupID')];
-               
-               // already initialized, ignore
-               if (onInit && $group.data('labelID') !== undefined) {
-                       return;
-               }
-               
-               // save label id
-               if (label.data('labelID')) {
-                       $group.data('labelID', label.data('labelID'));
-               }
-               else {
-                       $group.data('labelID', 0);
-               }
-               
-               // replace button
-               label = label.find('span > span');
-               $group.find('.dropdownToggle > span').removeClass().addClass(label.attr('class')).text(label.text());
-       },
-       
-       /**
-        * Creates hidden input elements on submit.
-        */
-       _submit: function() {
-               // get form submit area
-               var $formSubmit = this._container.find('.formSubmit');
-               
-               // remove old, hidden values
-               $formSubmit.find('input[type="hidden"]').each(function(index, input) {
-                       var $input = $(input);
-                       if ($input.attr('name').indexOf('labelIDs[') === 0) {
-                               $input.remove();
-                       }
-               });
-               
-               // insert label ids
-               for (var $groupID in this._groups) {
-                       var $group = this._groups[$groupID];
-                       if ($group.data('labelID')) {
-                               $('<input type="hidden" name="labelIDs[' + $groupID + ']" value="' + $group.data('labelID') + '" />').appendTo($formSubmit);
-                       }
-               }
-       }
-});
-
-
-// WCF.Location.js
-/**
- * Location-related classes for WCF
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2014 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- */
-WCF.Location = { };
-
-/**
- * Provides location-related utility functions.
- */
-WCF.Location.Util = {
-       /**
-        * Passes the user's current latitude and longitude to the given function
-        * as parameters. If the user's current position cannot be determined,
-        * undefined will be passed as both parameters.
-        * 
-        * @param       function        callback
-        * @param       integer         timeout
-        */
-       getLocation: function(callback, timeout) {
-               if (navigator.geolocation) {
-                       navigator.geolocation.getCurrentPosition(function(position) {
-                               callback(position.coords.latitude, position.coords.longitude);
-                       }, function() {
-                               callback(undefined, undefined);
-                       }, {
-                               timeout: timeout || 5000
-                       });
-               }
-               else {
-                       callback(undefined, undefined);
-               }
-       }
-};
-
-/**
- * Namespace for Google Maps-related classes.
- */
-WCF.Location.GoogleMaps = { };
-
-/**
- * Handles the global Google Maps settings.
- */
-WCF.Location.GoogleMaps.Settings = {
-       /**
-        * Google Maps settings
-        * @var object
-        */
-       _settings: { },
-       
-       /**
-        * Returns the value of a certain setting or null if it doesn't exist.
-        * 
-        * If no parameter is given, all settings are returned.
-        * 
-        * @param       string          setting
-        * @return      mixed
-        */
-       get: function(setting) {
-               if (setting === undefined) {
-                       return this._settings;
-               }
-               
-               if (this._settings[setting] !== undefined) {
-                       return this._settings[setting];
-               }
-               
-               return null;
-       },
-       
-       /**
-        * Sets the value of a certain setting.
-        * 
-        * @param       mixed           setting
-        * @param       mixed           value
-        */
-       set: function(setting, value) {
-               if ($.isPlainObject(setting)) {
-                       for (var index in setting) {
-                               this._settings[index] = setting[index];
-                       }
-               }
-               else {
-                       this._settings[setting] = value;
-               }
-       }
-};
-
-/**
- * Handles a Google Maps map.
- */
-WCF.Location.GoogleMaps.Map = Class.extend({
-       /**
-        * map object for the displayed map
-        * @var google.maps.Map
-        */
-       _map: null,
-       
-       /**
-        * list of markers on the map
-        * @var array<google.maps.Marker>
-        */
-       _markers: [ ],
-       
-       /**
-        * Initalizes a new WCF.Location.Map object.
-        * 
-        * @param       string          mapContainerID
-        * @param       object          mapOptions
-        */
-       init: function(mapContainerID, mapOptions) {
-               this._mapContainer = $('#' + mapContainerID);
-               this._mapOptions = $.extend(true, this._getDefaultMapOptions(), mapOptions);
-               
-               this._map = new google.maps.Map(this._mapContainer[0], this._mapOptions);
-               this._markers = [ ];
-               
-               // fix maps in mobile sidebars by refreshing the map when displaying
-               // the map
-               if (this._mapContainer.parents('.sidebar').length) {
-                       enquire.register('screen and (max-width: 800px)', {
-                               setup: $.proxy(this._addSidebarMapListener, this),
-                               deferSetup: true
-                       });
-               }
-               
-               this.refresh();
-       },
-       
-       /**
-        * Adds click listener to mobile sidebar toggle button to refresh map.
-        */
-       _addSidebarMapListener: function() {
-               $('.content > .mobileSidebarToggleButton').click($.proxy(this.refresh, this));
-       },
-       
-       /**
-        * Returns the default map options.
-        * 
-        * @return      object
-        */
-       _getDefaultMapOptions: function() {
-               var $defaultMapOptions = { };
-               
-               // dummy center value
-               $defaultMapOptions.center = new google.maps.LatLng(WCF.Location.GoogleMaps.Settings.get('defaultLatitude'), WCF.Location.GoogleMaps.Settings.get('defaultLongitude'));
-               
-               // double click to zoom
-               $defaultMapOptions.disableDoubleClickZoom = WCF.Location.GoogleMaps.Settings.get('disableDoubleClickZoom');
-               
-               // draggable
-               $defaultMapOptions.draggable = WCF.Location.GoogleMaps.Settings.get('draggable');
-               
-               // map type
-               switch (WCF.Location.GoogleMaps.Settings.get('mapType')) {
-                       case 'map':
-                               $defaultMapOptions.mapTypeId = google.maps.MapTypeId.ROADMAP;
-                       break;
-                       
-                       case 'satellite':
-                               $defaultMapOptions.mapTypeId = google.maps.MapTypeId.SATELLITE;
-                       break;
-                       
-                       case 'physical':
-                               $defaultMapOptions.mapTypeId = google.maps.MapTypeId.TERRAIN;
-                       break;
-                       
-                       case 'hybrid':
-                       default:
-                               $defaultMapOptions.mapTypeId = google.maps.MapTypeId.HYBRID;
-                       break;
-               }
-               
-               /// map type controls
-               $defaultMapOptions.mapTypeControl = WCF.Location.GoogleMaps.Settings.get('mapTypeControl') != 'off';
-               if ($defaultMapOptions.mapTypeControl) {
-                       switch (WCF.Location.GoogleMaps.Settings.get('mapTypeControl')) {
-                               case 'dropdown':
-                                       $defaultMapOptions.mapTypeControlOptions = {
-                                               style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
-                                       };
-                               break;
-                               
-                               case 'horizontalBar':
-                                       $defaultMapOptions.mapTypeControlOptions = {
-                                               style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR
-                                       };
-                               break;
-                               
-                               default:
-                                       $defaultMapOptions.mapTypeControlOptions = {
-                                               style: google.maps.MapTypeControlStyle.DEFAULT
-                                       };
-                               break;
-                       }
-               }
-               
-               // scale control
-               $defaultMapOptions.scaleControl = WCF.Location.GoogleMaps.Settings.get('scaleControl');
-               $defaultMapOptions.scrollwheel = WCF.Location.GoogleMaps.Settings.get('scrollwheel');
-               
-               // zoom
-               $defaultMapOptions.zoom = WCF.Location.GoogleMaps.Settings.get('zoom');
-               
-               return $defaultMapOptions;
-       },
-       
-       /**
-        * Adds a draggable marker at the given position to the map and returns
-        * the created marker object.
-        * 
-        * @param       float           latitude
-        * @param       float           longitude
-        * @return      google.maps.Marker
-        */
-       addDraggableMarker: function(latitude, longitude) {
-               var $marker = new google.maps.Marker({
-                       clickable: false,
-                       draggable: true,
-                       map: this._map,
-                       position: new google.maps.LatLng(latitude, longitude),
-                       zIndex: 1
-               });
-               
-               this._markers.push($marker);
-               
-               return $marker;
-       },
-       
-       /**
-        * Adds a marker with the given data to the map and returns the created
-        * marker object.
-        * 
-        * @param       float           latitude
-        * @param       float           longitude
-        * @param       string          title
-        * @param       mixed           icon
-        * @param       string          information
-        * @return      google.maps.Marker
-        */
-       addMarker: function(latitude, longitude, title, icon, information) {
-               var $marker = new google.maps.Marker({
-                       map: this._map,
-                       position: new google.maps.LatLng(latitude, longitude),
-                       title: title
-               });
-               
-               // add icon
-               if (icon) {
-                       $marker.setIcon(icon);
-               }
-               
-               // add info window for marker information
-               if (information) {
-                       var $infoWindow = new google.maps.InfoWindow({
-                               content: information
-                       });
-                       google.maps.event.addListener($marker, 'click', $.proxy(function() {
-                               $infoWindow.open(this._map, $marker);
-                       }, this));
-                       
-                       // add info window object to marker object
-                       $marker.infoWindow = $infoWindow;
-               }
-               
-               this._markers.push($marker);
-               
-               return $marker;
-       },
-       
-       /**
-        * Returns all markers on the map.
-        * 
-        * @return      array<google.maps.Marker>
-        */
-       getMarkers: function() {
-               return this._markers;
-       },
-       
-       /**
-        * Returns the Google Maps map object.
-        * 
-        * @return      google.maps.Map
-        */
-       getMap: function() {
-               return this._map;
-       },
-       
-       /**
-        * Refreshes the map.
-        */
-       refresh: function() {
-               // save current center since resize does not preserve it
-               var $center = this._map.getCenter();
-               
-               google.maps.event.trigger(this._map, 'resize');
-               
-               // set center to old value again
-               this._map.setCenter($center);
-       },
-       
-       /**
-        * Refreshes the boundaries of the map to show all markers.
-        */
-       refreshBounds: function() {
-               var $minLatitude = null;
-               var $maxLatitude = null;
-               var $minLongitude = null;
-               var $maxLongitude = null;
-               
-               for (var $index in this._markers) {
-                       var $marker = this._markers[$index];
-                       var $latitude = $marker.getPosition().lat();
-                       var $longitude = $marker.getPosition().lng();
-                       
-                       if ($minLatitude === null) {
-                               $minLatitude = $maxLatitude = $latitude;
-                               $minLongitude = $maxLongitude = $longitude;
-                       }
-                       else {
-                               if ($minLatitude > $latitude) {
-                                       $minLatitude = $latitude;
-                               }
-                               else if ($maxLatitude < $latitude) {
-                                       $maxLatitude = $latitude;
-                               }
-                               
-                               if ($minLongitude > $latitude) {
-                                       $minLongitude = $latitude;
-                               }
-                               else if ($maxLongitude < $longitude) {
-                                       $maxLongitude = $longitude;
-                               }
-                       }
-               }
-               
-               this._map.fitBounds(new google.maps.LatLngBounds(
-                       new google.maps.LatLng($minLatitude, $minLongitude),
-                       new google.maps.LatLng($maxLatitude, $maxLongitude)
-               ));
-       },
-       
-       /**
-        * Removes all markers from the map.
-        */
-       removeMarkers: function() {
-               for (var $index in this._markers) {
-                       this._markers[$index].setMap(null);
-               }
-               
-               this._markers = [ ];
-       },
-       
-       /**
-        * Sets the center of the map to the given position.
-        * 
-        * @param       float           latitude
-        * @param       float           longitude
-        */
-       setCenter: function(latitude, longitude) {
-               this._map.setCenter(new google.maps.LatLng(latitude, longitude));
-       }
-});
-
-/**
- * Handles a large map with many markers where (new) markers are loaded via AJAX.
- */
-WCF.Location.GoogleMaps.LargeMap = WCF.Location.GoogleMaps.Map.extend({
-       /**
-        * name of the PHP class executing the 'getMapMarkers' action
-        * @var string
-        */
-       _actionClassName: null,
-       
-       /**
-        * indicates if the maps center can be set by location search
-        * @var WCF.Location.GoogleMaps.LocationSearch
-        */
-       _locationSearch: null,
-       
-       /**
-        * selector for the location search input
-        * @var string
-        */
-       _locationSearchInputSelector: null,
-       
-       /**
-        * cluster handling the markers on the map
-        * @var MarkerClusterer
-        */
-       _markerClusterer: null,
-       
-       /**
-        * ids of the objects which are already displayed
-        * @var array<integer>
-        */
-       _objectIDs: [ ],
-       
-       /**
-        * previous coordinates of the north east map boundary
-        * @var google.maps.LatLng
-        */
-       _previousNorthEast: null,
-       
-       /**
-        * previous coordinates of the south west map boundary
-        * @var google.maps.LatLng
-        */
-       _previousSouthWest: null,
-       
-       /**
-        * @see WCF.Location.GoogleMaps.Map.init()
-        */
-       init: function(mapContainerID, mapOptions, actionClassName, locationSearchInputSelector) {
-               this._super(mapContainerID, mapOptions);
-               
-               this._actionClassName = actionClassName;
-               this._locationSearchInputSelector = locationSearchInputSelector || '';
-               this._objectIDs = [ ];
-               
-               if (this._locationSearchInputSelector) {
-                       this._locationSearch = new WCF.Location.GoogleMaps.LocationSearch(locationSearchInputSelector, $.proxy(this._centerMap, this));
-               }
-               
-               this._markerClusterer = new MarkerClusterer(this._map, this._markers, {
-                       maxZoom: 17
-               });
-               
-               this._proxy = new WCF.Action.Proxy({
-                       showLoadingOverlay: false,
-                       success: $.proxy(this._success, this)
-               });
-               
-               this._previousNorthEast = null;
-               this._previousSouthWest = null;
-               google.maps.event.addListener(this._map, 'idle', $.proxy(this._loadMarkers, this));
-       },
-       
-       /**
-        * Centers the map based on a location search result.
-        * 
-        * @param       object          data
-        */
-       _centerMap: function(data) {
-               this.setCenter(data.location.lat(), data.location.lng());
-               
-               $(this._locationSearchInputSelector).val(data.label);
-       },
-       
-       /**
-        * Loads markers if the map is reloaded.
-        */
-       _loadMarkers: function() {
-               var $northEast = this._map.getBounds().getNorthEast();
-               var $southWest = this._map.getBounds().getSouthWest();
-               
-               // check if the user has zoomed in, then all markers are already
-               // displayed
-               if (this._previousNorthEast && this._previousNorthEast.lat() >= $northEast.lat() && this._previousNorthEast.lng() >= $northEast.lng() && this._previousSouthWest.lat() <= $southWest.lat() && this._previousSouthWest.lng() <= $southWest.lng()) {
-                       return;
-               }
-               
-               this._previousNorthEast = $northEast;
-               this._previousSouthWest = $southWest;
-               
-               this._proxy.setOption('data', {
-                       actionName: 'getMapMarkers',
-                       className: this._actionClassName,
-                       parameters: {
-                               excludedObjectIDs: this._objectIDs,
-                               eastLongitude: $northEast.lng(),
-                               northLatitude: $northEast.lat(),
-                               southLatitude: $southWest.lat(),
-                               westLongitude: $southWest.lng()
-                       }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Handles a successful AJAX request.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               if (data.returnValues && data.returnValues.markers) {
-                       for (var $index in data.returnValues.markers) {
-                               var $markerInfo = data.returnValues.markers[$index];
-                               
-                               this.addMarker($markerInfo.latitude, $markerInfo.longitude, $markerInfo.title, null, $markerInfo.infoWindow);
-                               
-                               if ($markerInfo.objectID) {
-                                       this._objectIDs.push($markerInfo.objectID);
-                               }
-                               else if ($markerInfo.objectIDs) {
-                                       this._objectIDs = this._objectIDs.concat($markerInfo.objectIDs);
-                               }
-                       }
-               }
-       },
-       
-       /**
-        * @see WCF.Location.GoogleMaps.Map.addMarker()
-        */
-       addMarker: function(latitude, longitude, title, icon, information) {
-               var $marker = this._super(latitude, longitude, title, icon, information);
-               this._markerClusterer.addMarker($marker);
-               
-               return $marker;
-       }
-});
-
-/**
- * Provides location searches based on google.maps.Geocoder.
- */
-WCF.Location.GoogleMaps.LocationSearch = WCF.Search.Base.extend({
-       /**
-        * Google Maps geocoder object
-        * @var google.maps.Geocoder
-        */
-       _geocoder: null,
-       
-       /**
-        * @see WCF.Search.Base.init()
-        */
-       init: function(searchInput, callback, excludedSearchValues, commaSeperated, showLoadingOverlay) {
-               this._super(searchInput, callback, excludedSearchValues, commaSeperated, showLoadingOverlay);
-               
-               this._geocoder = new google.maps.Geocoder();
-       },
-       
-       /**
-        * @see WCF.Search.Base._createListItem()
-        */
-       _createListItem: function(geocoderResult) {
-               var $listItem = $('<li><span>' + WCF.String.escapeHTML(geocoderResult.formatted_address) + '</span></li>').appendTo(this._list);
-               $listItem.data('location', geocoderResult.geometry.location).data('label', geocoderResult.formatted_address).click($.proxy(this._executeCallback, this));
-               
-               this._itemCount++;
-               
-               return $listItem;
-       },
-       
-       /**
-        * @see WCF.Search.Base._keyUp()
-        */
-       _keyUp: function(event) {
-               // handle arrow keys and return key
-               switch (event.which) {
-                       case $.ui.keyCode.LEFT:
-                       case $.ui.keyCode.RIGHT:
-                               return;
-                       break;
-                       
-                       case $.ui.keyCode.UP:
-                               this._selectPreviousItem();
-                               return;
-                       break;
-                       
-                       case $.ui.keyCode.DOWN:
-                               this._selectNextItem();
-                               return;
-                       break;
-                       
-                       case $.ui.keyCode.ENTER:
-                               return this._selectElement(event);
-                       break;
-               }
-               
-               var $content = this._getSearchString(event);
-               if ($content === '') {
-                       this._clearList(true);
-               }
-               else if ($content.length >= this._triggerLength) {
-                       this._clearList(false);
-                       
-                       this._geocoder.geocode({
-                               address: $content
-                       }, $.proxy(this._success, this));
-               }
-               else {
-                       // input below trigger length
-                       this._clearList(false);
-               }
-       },
-       
-       /**
-        * Handles a successfull geocoder request.
-        * 
-        * @param       array           results
-        * @param       integer         status
-        */
-       _success: function(results, status) {
-               if (status != google.maps.GeocoderStatus.OK) {
-                       return;
-               }
-               
-               if ($.getLength(results)) {
-                       var $count = 0;
-                       for (var $index in results) {
-                               this._createListItem(results[$index]);
-                               
-                               if (++$count == 10) {
-                                       break;
-                               }
-                       }
-               }
-               else if (!this._handleEmptyResult()) {
-                       return;
-               }
-               
-               WCF.CloseOverlayHandler.addCallback('WCF.Search.Base', $.proxy(function() { this._clearList(); }, this));
-               
-               var $containerID = this._searchInput.parents('.dropdown').wcfIdentify();
-               if (!WCF.Dropdown.getDropdownMenu($containerID).hasClass('dropdownOpen')) {
-                       WCF.Dropdown.toggleDropdown($containerID);
-               }
-               
-               // pre-select first item
-               this._itemIndex = -1;
-               if (!WCF.Dropdown.getDropdown($containerID).data('disableAutoFocus')) {
-                       this._selectNextItem();
-               }
-       }
-});
-
-/**
- * Handles setting a single location on a Google Map.
- */
-WCF.Location.GoogleMaps.LocationInput = Class.extend({
-       /**
-        * location search object
-        * @var WCF.Location.GoogleMaps.LocationSearch
-        */
-       _locationSearch: null,
-       
-       /**
-        * related map object
-        * @var WCF.Location.GoogleMaps.Map
-        */
-       _map: null,
-       
-       /**
-        * draggable marker to set the location
-        * @var google.maps.Marker
-        */
-       _marker: null,
-       
-       /**
-        * Initializes a new WCF.Location.GoogleMaps.LocationInput object.
-        * 
-        * @param       string          mapContainerID
-        * @param       object          mapOptions
-        * @param       string          searchInput
-        * @param       function        callback
-        */
-       init: function(mapContainerID, mapOptions, searchInput, latitude, longitude) {
-               this._searchInput = searchInput;
-               this._map = new WCF.Location.GoogleMaps.Map(mapContainerID, mapOptions);
-               this._locationSearch = new WCF.Location.GoogleMaps.LocationSearch(searchInput, $.proxy(this._setMarkerByLocation, this));
-               
-               if (latitude && longitude) {
-                       this._marker = this._map.addDraggableMarker(latitude, longitude);
-               }
-               else {
-                       this._marker = this._map.addDraggableMarker(0, 0);
-                       
-                       WCF.Location.Util.getLocation($.proxy(function(latitude, longitude) {
-                               if (latitude !== undefined && longitude !== undefined) {
-                                       WCF.Location.GoogleMaps.Util.moveMarker(this._marker, latitude, longitude);
-                                       WCF.Location.GoogleMaps.Util.focusMarker(this._marker);
-                               }
-                       }, this));
-               }
-               
-               this._marker.addListener('dragend', $.proxy(this._updateLocation, this));
-       },
-       
-       /**
-        * Returns the related map.
-        * 
-        * @return      WCF.Location.GoogleMaps.Map
-        */
-       getMap: function() {
-               return this._map;
-       },
-       
-       /**
-        * Returns the draggable marker used to set the location.
-        * 
-        * @return      google.maps.Marker
-        */
-       getMarker: function() {
-               return this._marker;
-       },
-       
-       /**
-        * Updates location on marker position change.
-        */
-       _updateLocation: function() {
-               WCF.Location.GoogleMaps.Util.reverseGeocoding($.proxy(function(result) {
-                       if (result !== null) {
-                               $(this._searchInput).val(result);
-                       }
-               }, this), this._marker);
-       },
-       
-       /**
-        * Sets the marker based on an entered location.
-        * 
-        * @param       object          data
-        */
-       _setMarkerByLocation: function(data) {
-               this._marker.setPosition(data.location);
-               WCF.Location.GoogleMaps.Util.focusMarker(this._marker);
-               
-               $(this._searchInput).val(data.label);
-       }
-});
-
-/**
- * Provides utility functions for Google Maps maps.
- */
-WCF.Location.GoogleMaps.Util = {
-       /**
-        * geocoder instance
-        * @var google.maps.Geocoder
-        */
-       _geocoder: null,
-       
-       /**
-        * Focuses the given marker's map on the marker.
-        * 
-        * @param       google.maps.Marker      marker
-        */
-       focusMarker: function(marker) {
-               marker.getMap().setCenter(marker.getPosition());
-       },
-       
-       /**
-        * Returns the latitude and longitude of the given marker.
-        * 
-        * @return      object
-        */
-       getMarkerPosition: function(marker) {
-               return {
-                       latitude: marker.getPosition().lat(),
-                       longitude: marker.getPosition().lng()
-               };
-       },
-       
-       /**
-        * Moves the given marker to the given position.
-        * 
-        * @param       google.maps.Marker              marker
-        * @param       float                           latitude
-        * @param       float                           longitude
-        * @param       boolean                         dragend         indicates if "dragend" event is fired
-        */
-       moveMarker: function(marker, latitude, longitude, triggerDragend) {
-               marker.setPosition(new google.maps.LatLng(latitude, longitude));
-               
-               if (triggerDragend) {
-                       google.maps.event.trigger(marker, 'dragend');
-               }
-       },
-       
-       /**
-        * Performs a reverse geocoding request.
-        * 
-        * @param       object                  callback
-        * @param       google.maps.Marker      marker
-        * @param       string                  latitude
-        * @param       string                  longitude
-        * @param       boolean                 fullResult
-        */
-       reverseGeocoding: function(callback, marker, latitude, longitude, fullResult) {
-               if (marker) {
-                       latitude = marker.getPosition().lat();
-                       longitude = marker.getPosition().lng();
-               }
-               
-               if (this._geocoder === null) {
-                       this._geocoder = new google.maps.Geocoder();
-               }
-               
-               var $latLng = new google.maps.LatLng(latitude, longitude);
-               this._geocoder.geocode({ latLng: $latLng }, function(results, status) {
-                       if (status == google.maps.GeocoderStatus.OK) {
-                               callback((fullResult ? results : results[0].formatted_address));
-                       }
-                       else {
-                               callback(null);
-                       }
-               });
-       }
-};
-
-
-// WCF.Message.js
-/**
- * Message related classes for WCF
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2014 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- */
-WCF.Message = { };
-
-/**
- * Namespace for BBCode related classes.
- */
-WCF.Message.BBCode = { };
-
-/**
- * BBCode Viewer for WCF.
- */
-WCF.Message.BBCode.CodeViewer = Class.extend({
-       /**
-        * dialog overlay
-        * @var jQuery
-        */
-       _dialog: null,
-       
-       /**
-        * Initializes the WCF.Message.BBCode.CodeViewer class.
-        */
-       init: function() {
-               this._dialog = null;
-               
-               this._initCodeBoxes();
-               
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.Message.BBCode.CodeViewer', $.proxy(this._initCodeBoxes, this));
-               WCF.DOMNodeInsertedHandler.execute();
-       },
-       
-       /**
-        * Initializes available code boxes.
-        */
-       _initCodeBoxes: function() {
-               $('.codeBox:not(.jsCodeViewer)').each($.proxy(function(index, codeBox) {
-                       var $codeBox = $(codeBox).addClass('jsCodeViewer');
-                       
-                       $('<span class="icon icon16 icon-copy pointer jsTooltip" title="' + WCF.Language.get('wcf.message.bbcode.code.copy') + '" />').appendTo($codeBox.find('div > h3')).click($.proxy(this._click, this));
-               }, this));
-       },
-       
-       /**
-        * Shows a code viewer for a specific code box.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               var $content = '';
-               $(event.currentTarget).parents('div').next('ol').children('li').each(function(index, listItem) {
-                       if ($content) {
-                               $content += "\n";
-                       }
-                       
-                       // do *not* use $.trim here, as we want to preserve whitespaces
-                       $content += $(listItem).text().replace(/\n+$/, '');
-               });
-               
-               if (this._dialog === null) {
-                       this._dialog = $('<div><textarea cols="60" rows="12" readonly="readonly" /></div>').hide().appendTo(document.body);
-                       this._dialog.children('textarea').val($content);
-                       this._dialog.wcfDialog({
-                               title: WCF.Language.get('wcf.message.bbcode.code.copy')
-                       });
-               }
-               else {
-                       this._dialog.children('textarea').val($content);
-                       this._dialog.wcfDialog('open');
-               }
-               
-               this._dialog.children('textarea').select();
-       }
-});
-
-/**
- * Prevents multiple submits of the same form by disabling the submit button.
- */
-WCF.Message.FormGuard = Class.extend({
-       /**
-        * Initializes the WCF.Message.FormGuard class.
-        */
-       init: function() {
-               var $forms = $('form.jsFormGuard').removeClass('jsFormGuard').submit(function() {
-                       $(this).find('.formSubmit input[type=submit]').disable();
-               });
-               
-               // restore buttons, prevents disabled buttons on back navigation in Opera
-               $(window).unload(function() {
-                       $forms.find('.formSubmit input[type=submit]').enable();
-               });
-       }
-});
-
-/**
- * Provides previews for Redactor message fields.
- * 
- * @param      string          className
- * @param      string          messageFieldID
- * @param      string          previewButtonID
- */
-WCF.Message.Preview = Class.extend({
-       /**
-        * class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * message field id
-        * @var string
-        */
-       _messageFieldID: '',
-       
-       /**
-        * message field
-        * @var jQuery
-        */
-       _messageField: null,
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * preview button
-        * @var jQuery
-        */
-       _previewButton: null,
-       
-       /**
-        * previous button label
-        * @var string
-        */
-       _previewButtonLabel: '',
-       
-       /**
-        * Initializes a new WCF.Message.Preview object.
-        * 
-        * @param       string          className
-        * @param       string          messageFieldID
-        * @param       string          previewButtonID
-        */
-       init: function(className, messageFieldID, previewButtonID) {
-               this._className = className;
-               
-               // validate message field
-               this._messageFieldID = $.wcfEscapeID(messageFieldID);
-               this._messageField = $('#' + this._messageFieldID);
-               if (!this._messageField.length) {
-                       console.debug("[WCF.Message.Preview] Unable to find message field identified by '" + this._messageFieldID + "'");
-                       return;
-               }
-               
-               // validate preview button
-               previewButtonID = $.wcfEscapeID(previewButtonID);
-               this._previewButton = $('#' + previewButtonID);
-               if (!this._previewButton.length) {
-                       console.debug("[WCF.Message.Preview] Unable to find preview button identified by '" + previewButtonID + "'");
-                       return;
-               }
-               
-               this._previewButton.click($.proxy(this._click, this));
-               this._proxy = new WCF.Action.Proxy({
-                       failure: $.proxy(this._failure, this),
-                       success: $.proxy(this._success, this)
-               });
-       },
-       
-       /**
-        * Reads message field input and triggers an AJAX request.
-        */
-       _click: function(event) {
-               var $message = this._getMessage();
-               if ($message === null) {
-                       console.debug("[WCF.Message.Preview] Unable to access Redactor instance of '" + this._messageFieldID + "'");
-                       return;
-               }
-               
-               this._proxy.setOption('data', {
-                       actionName: 'getMessagePreview',
-                       className: this._className,
-                       parameters: this._getParameters($message)
-               });
-               this._proxy.sendRequest();
-               
-               // update button label
-               this._previewButtonLabel = this._previewButton.html();
-               this._previewButton.html(WCF.Language.get('wcf.global.loading')).disable();
-               
-               // poke event
-               event.stopPropagation();
-               return false;
-       },
-       
-       /**
-        * Returns request parameters.
-        * 
-        * @param       string          message
-        * @return      object
-        */
-       _getParameters: function(message) {
-               // collect message form options
-               var $options = { };
-               $('#settings').find('input[type=checkbox]').each(function(index, checkbox) {
-                       var $checkbox = $(checkbox);
-                       if ($checkbox.is(':checked')) {
-                               $options[$checkbox.prop('name')] = $checkbox.prop('value');
-                       }
-               });
-               
-               // build parameters
-               return {
-                       data: {
-                               message: message
-                       },
-                       options: $options
-               };
-       },
-       
-       /**
-        * Returns parsed message from Redactor or null if editor was not accessible.
-        * 
-        * @return      string
-        */
-       _getMessage: function() {
-               if (!$.browser.redactor) {
-                       return this._messageField.val();
-               }
-               else if (this._messageField.data('redactor')) {
-                       return this._messageField.redactor('getText');
-               }
-               
-               return null;
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               // restore preview button
-               this._previewButton.html(this._previewButtonLabel).enable();
-               
-               // remove error message
-               this._messageField.parent().children('small.innerError').remove();
-               
-               // evaluate message
-               this._handleResponse(data);
-       },
-       
-       /**
-        * Evaluates response data.
-        * 
-        * @param       object          data
-        */
-       _handleResponse: function(data) { },
-       
-       /**
-        * Handles errors during preview requests.
-        * 
-        * The return values indicates if the default error overlay is shown.
-        * 
-        * @param       object          data
-        * @return      boolean
-        */
-       _failure: function(data) {
-               if (data === null || data.returnValues === undefined || data.returnValues.errorType === undefined) {
-                       return true;
-               }
-               
-               // restore preview button
-               this._previewButton.html(this._previewButtonLabel).enable();
-               
-               var $innerError = this._messageField.next('small.innerError').empty();
-               if (!$innerError.length) {
-                       $innerError = $('<small class="innerError" />').appendTo(this._messageField.parent());
-               }
-               
-               $innerError.html(data.returnValues.errorType);
-               
-               return false;
-       }
-});
-
-/**
- * Default implementation for message previews.
- * 
- * @see        WCF.Message.Preview
- */
-WCF.Message.DefaultPreview = WCF.Message.Preview.extend({
-       _attachmentObjectType: null,
-       _attachmentObjectID: null,
-       _tmpHash: null,
-       
-       /**
-        * @see WCF.Message.Preview.init()
-        */
-       init: function(attachmentObjectType, attachmentObjectID, tmpHash) {
-               this._super('wcf\\data\\bbcode\\MessagePreviewAction', 'text', 'previewButton');
-               
-               this._attachmentObjectType = attachmentObjectType || null;
-               this._attachmentObjectID = attachmentObjectID || null;
-               this._tmpHash = tmpHash || null;
-       },
-       
-       /**
-        * @see WCF.Message.Preview._handleResponse()
-        */
-       _handleResponse: function(data) {
-               var $preview = $('#previewContainer');
-               if (!$preview.length) {
-                       $preview = $('<div class="container containerPadding marginTop" id="previewContainer"><fieldset><legend>' + WCF.Language.get('wcf.global.preview') + '</legend><div></div></fieldset>').prependTo($('#messageContainer')).wcfFadeIn();
-               }
-               
-               $preview.find('div:eq(0)').html(data.returnValues.message);
-               
-               new WCF.Effect.Scroll().scrollTo($preview);
-       },
-       
-       /**
-        * @see WCF.Message.Preview._getParameters()
-        */
-       _getParameters: function(message) {
-               var $parameters = this._super(message);
-               
-               if (this._attachmentObjectType != null) {
-                       $parameters.attachmentObjectType = this._attachmentObjectType;
-                       $parameters.attachmentObjectID = this._attachmentObjectID;
-                       $parameters.tmpHash = this._tmpHash;
-               }
-               
-               return $parameters;
-       }
-});
-
-/**
- * Handles multilingualism for messages.
- * 
- * @param      integer         languageID
- * @param      object          availableLanguages
- * @param      boolean         forceSelection
- */
-WCF.Message.Multilingualism = Class.extend({
-       /**
-        * list of available languages
-        * @var object
-        */
-       _availableLanguages: { },
-       
-       /**
-        * language id
-        * @var integer
-        */
-       _languageID: 0,
-       
-       /**
-        * language input element
-        * @var jQuery
-        */
-       _languageInput: null,
-       
-       /**
-        * Initializes WCF.Message.Multilingualism
-        * 
-        * @param       integer         languageID
-        * @param       object          availableLanguages
-        * @param       boolean         forceSelection
-        */
-       init: function(languageID, availableLanguages, forceSelection) {
-               this._availableLanguages = availableLanguages;
-               this._languageID = languageID || 0;
-               
-               this._languageInput = $('#languageID');
-               
-               // preselect current language id
-               this._updateLabel();
-               
-               // register event listener
-               this._languageInput.find('.dropdownMenu > li').click($.proxy(this._click, this));
-               
-               // add element to disable multilingualism
-               if (!forceSelection) {
-                       var $dropdownMenu = this._languageInput.find('.dropdownMenu');
-                       $('<li class="dropdownDivider" />').appendTo($dropdownMenu);
-                       $('<li><span><span class="badge">' + this._availableLanguages[0] + '</span></span></li>').click($.proxy(this._disable, this)).appendTo($dropdownMenu);
-               }
-               
-               // bind submit event
-               this._languageInput.parents('form').submit($.proxy(this._submit, this));
-       },
-       
-       /**
-        * Handles language selections.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               this._languageID = $(event.currentTarget).data('languageID');
-               this._updateLabel();
-       },
-       
-       /**
-        * Disables language selection.
-        */
-       _disable: function() {
-               this._languageID = 0;
-               this._updateLabel();
-       },
-       
-       /**
-        * Updates selected language.
-        */
-       _updateLabel: function() {
-               this._languageInput.find('.dropdownToggle > span').text(this._availableLanguages[this._languageID]);
-       },
-       
-       /**
-        * Sets language id upon submit.
-        */
-       _submit: function() {
-               this._languageInput.next('input[name=languageID]').prop('value', this._languageID);
-       }
-});
-
-/**
- * Loads smiley categories upon user request.
- */
-WCF.Message.SmileyCategories = Class.extend({
-       /**
-        * list of already loaded category ids
-        * @var array<integer>
-        */
-       _cache: [ ],
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * Initializes the smiley loader.
-        */
-       init: function() {
-               this._cache = [ ];
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               $('#smilies').on('wcftabsbeforeactivate', $.proxy(this._click, this));
-               
-               // handle onload
-               var self = this;
-               new WCF.PeriodicalExecuter(function(pe) {
-                       pe.stop();
-                       
-                       self._click({ }, { newTab: $('#smilies > .menu li.ui-state-active') });
-               }, 100);
-       },
-       
-       /**
-        * Handles tab menu clicks.
-        * 
-        * @param       object          event
-        * @param       object          ui
-        */
-       _click: function(event, ui) {
-               var $categoryID = parseInt($(ui.newTab).children('a').data('smileyCategoryID'));
-               
-               if ($categoryID && !WCF.inArray($categoryID, this._cache)) {
-                       this._proxy.setOption('data', {
-                               actionName: 'getSmilies',
-                               className: 'wcf\\data\\smiley\\category\\SmileyCategoryAction',
-                               objectIDs: [ $categoryID ]
-                       });
-                       this._proxy.sendRequest();
-               }
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               var $categoryID = parseInt(data.returnValues.smileyCategoryID);
-               this._cache.push($categoryID);
-               
-               $('#smilies-' + $categoryID).html(data.returnValues.template);
-       }
-});
-
-/**
- * Handles smiley clicks.
- */
-WCF.Message.Smilies = Class.extend({
-       /**
-        * redactor element
-        * @var $.Redactor
-        */
-       _redactor: null,
-       
-       _wysiwygSelector: '',
-       
-       /**
-        * Initializes the smiley handler.
-        * 
-        * @param       string          wysiwygSelector
-        */
-       init: function(wysiwygSelector) {
-               this._wysiwygSelector = wysiwygSelector;
-               
-               WCF.System.Dependency.Manager.register('Redactor_' + this._wysiwygSelector, $.proxy(function() {
-                       this._redactor = $('#' + this._wysiwygSelector).redactor('getObject');
-                       
-                       // add smiley click handler
-                       $(document).on('click', '.jsSmiley', $.proxy(this._smileyClick, this));
-               }, this));
-       },
-       
-       /**
-        * Handles tab smiley clicks.
-        * 
-        * @param       object          event
-        */
-       _smileyClick: function(event) {
-               var $target = $(event.currentTarget);
-               var $smileyCode = $target.data('smileyCode');
-               var $smileyPath = $target.data('smileyPath');
-               
-               // register smiley
-               this._redactor.insertSmiley($smileyCode, $smileyPath, true);
-       }
-});
-
-/**
- * Provides an AJAX-based quick reply for messages.
- */
-WCF.Message.QuickReply = Class.extend({
-       /**
-        * quick reply container
-        * @var jQuery
-        */
-       _container: null,
-       
-       /**
-        * message field
-        * @var jQuery
-        */
-       _messageField: null,
-       
-       /**
-        * notification object
-        * @var WCF.System.Notification
-        */
-       _notification: null,
-       
-       /**
-        * true, if a request to save the message is pending
-        * @var boolean
-        */
-       _pendingSave: false,
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * quote manager object
-        * @var WCF.Message.Quote.Manager
-        */
-       _quoteManager: null,
-       
-       /**
-        * scroll handler
-        * @var WCF.Effect.Scroll
-        */
-       _scrollHandler: null,
-       
-       /**
-        * success message for created but invisible messages
-        * @var string
-        */
-       _successMessageNonVisible: '',
-       
-       /**
-        * Initializes a new WCF.Message.QuickReply object.
-        * 
-        * @param       boolean                         supportExtendedForm
-        * @param       WCF.Message.Quote.Manager       quoteManager
-        */
-       init: function(supportExtendedForm, quoteManager) {
-               this._container = $('#messageQuickReply');
-               this._container.children('.message').addClass('jsInvalidQuoteTarget');
-               this._messageField = $('#text');
-               this._pendingSave = false;
-               if (!this._container || !this._messageField) {
-                       return;
-               }
-               
-               // button actions
-               var $formSubmit = this._container.find('.formSubmit');
-               $formSubmit.find('button[data-type=save]').click($.proxy(this._save, this));
-               if (supportExtendedForm) $formSubmit.find('button[data-type=extended]').click($.proxy(this._prepareExtended, this));
-               $formSubmit.find('button[data-type=cancel]').click($.proxy(this._cancel, this));
-               
-               if (quoteManager) this._quoteManager = quoteManager;
-               
-               $('.jsQuickReply').data('__api', this).click($.proxy(this.click, this));
-               
-               this._proxy = new WCF.Action.Proxy({
-                       failure: $.proxy(this._failure, this),
-                       showLoadingOverlay: false,
-                       success: $.proxy(this._success, this)
-               });
-               this._scroll = new WCF.Effect.Scroll();
-               this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success.add'));
-               this._successMessageNonVisible = '';
-       },
-       
-       /**
-        * Handles clicks on reply button.
-        * 
-        * @param       object          event
-        */
-       click: function(event) {
-               this._container.toggle();
-               
-               if (this._container.is(':visible')) {
-                       // TODO: Scrolling is anything but smooth, better use the init callback
-                       this._scroll.scrollTo(this._container, true);
-                       
-                       WCF.Message.Submit.registerButton('text', this._container.find('.formSubmit button[data-type=save]'));
-                       
-                       if (this._quoteManager) {
-                               // check if message field is empty
-                               var $empty = true;
-                               if ($.browser.redactor) {
-                                       if (this._messageField.data('redactor')) {
-                                               $empty = (!$.trim(this._messageField.redactor('getText')));
-                                               this._editorCallback($empty);
-                                       }
-                               }
-                               else {
-                                       $empty = (!this._messageField.val().length);
-                                       this._editorCallback($empty);
-                               }
-                       }
-               }
-               
-               // discard event
-               if (event !== null) {
-                       event.stopPropagation();
-                       return false;
-               }
-       },
-       
-       /**
-        * Inserts quotes and focuses the editor.
-        */
-       _editorCallback: function(isEmpty) {
-               if (isEmpty) {
-                       this._quoteManager.insertQuotes(this._getClassName(), this._getObjectID(), $.proxy(this._insertQuotes, this));
-               }
-               
-               if ($.browser.redactor) {
-                       this._messageField.redactor('focus');
-               }
-               else {
-                       this._messageField.focus();
-               }
-       },
-       
-       /**
-        * Returns container element.
-        * 
-        * @return      jQuery
-        */
-       getContainer: function() {
-               return this._container;
-       },
-       
-       /**
-        * Insertes quotes into the quick reply editor.
-        * 
-        * @param       object          data
-        */
-       _insertQuotes: function(data) {
-               if (!data.returnValues.template) {
-                       return;
-               }
-               
-               if ($.browser.redactor) {
-                       this._messageField.redactor('insertDynamic', data.returnValues.template);
-               }
-               else {
-                       this._messageField.val(data.returnValues.template);
-               }
-       },
-       
-       /**
-        * Saves message.
-        */
-       _save: function() {
-               if (this._pendingSave) {
-                       return;
-               }
-               
-               var $message = '';
-               if ($.browser.redactor) {
-                       $message = this._messageField.redactor('getText');
-               }
-               else {
-                       $message = $.trim(this._messageField.val());
-               }
-               
-               // check if message is empty
-               var $innerError = this._messageField.parent().find('small.innerError');
-               if ($message === '' || $message === '0') {
-                       if (!$innerError.length) {
-                               $innerError = $('<small class="innerError" />').appendTo(this._messageField.parent());
-                       }
-                       
-                       $innerError.html(WCF.Language.get('wcf.global.form.error.empty'));
-                       return;
-               }
-               else {
-                       $innerError.remove();
-               }
-               
-               this._pendingSave = true;
-               
-               this._proxy.setOption('data', {
-                       actionName: 'quickReply',
-                       className: this._getClassName(),
-                       interfaceName: 'wcf\\data\\IMessageQuickReplyAction',
-                       parameters: this._getParameters($message)
-               });
-               this._proxy.sendRequest();
-               
-               // show spinner and hide Redactor
-               var $messageBody = this._container.find('.messageQuickReplyContent .messageBody');
-               $('<span class="icon icon48 icon-spinner" />').appendTo($messageBody);
-               $messageBody.children('.redactor_box').hide().end().next().hide();
-       },
-       
-       /**
-        * Returns the parameters for the save request.
-        * 
-        * @param       string          message
-        * @return      object
-        */
-       _getParameters: function(message) {
-               var $parameters = {
-                       objectID: this._getObjectID(),
-                       data: {
-                               message: message
-                       },
-                       lastPostTime: this._container.data('lastPostTime'),
-                       pageNo: this._container.data('pageNo'),
-                       removeQuoteIDs: (this._quoteManager === null ? [ ] : this._quoteManager.getQuotesMarkedForRemoval()),
-                       tmpHash: this._container.data('tmpHash') || ''
-               };
-               if (this._container.data('anchor')) {
-                       $parameters.anchor = this._container.data('anchor');
-               }
-               
-               return $parameters;
-       },
-       
-       /**
-        * Cancels quick reply.
-        */
-       _cancel: function() {
-               this._revertQuickReply(true);
-               
-               if ($.browser.redactor) {
-                       this._messageField.redactor('reset');
-               }
-               else {
-                       this._messageField.val('');
-               }
-       },
-       
-       /**
-        * Reverts quick reply to original state and optionally hiding it.
-        * 
-        * @param       boolean         hide
-        */
-       _revertQuickReply: function(hide) {
-               var $messageBody = this._container.find('.messageQuickReplyContent .messageBody');
-               
-               if (hide) {
-                       this._container.hide();
-                       
-                       // remove previous error messages
-                       $messageBody.children('small.innerError').remove();
-               }
-               
-               // display Redactor
-               $messageBody.children('.icon-spinner').remove();
-               $messageBody.children('.redactor_box').show();
-               
-               // display form submit
-               $messageBody.next().show();
-       },
-       
-       /**
-        * Prepares jump to extended message add form.
-        */
-       _prepareExtended: function() {
-               this._pendingSave = true;
-               
-               // mark quotes for removal
-               if (this._quoteManager !== null) {
-                       this._quoteManager.markQuotesForRemoval();
-               }
-               
-               var $message = '';
-               if ($.browser.redactor) {
-                       $message = this._messageField.redactor('getText');
-               }
-               else {
-                       $message = this._messageField.val();
-               }
-               
-               new WCF.Action.Proxy({
-                       autoSend: true,
-                       data: {
-                               actionName: 'jumpToExtended',
-                               className: this._getClassName(),
-                               interfaceName: 'wcf\\data\\IExtendedMessageQuickReplyAction',
-                               parameters: {
-                                       containerID: this._getObjectID(),
-                                       message: $message
-                               }
-                       },
-                       success: function(data, textStatus, jqXHR) {
-                               window.location = data.returnValues.url;
-                       }
-               });
-       },
-       
-       /**
-        * Handles successful AJAX calls.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               if ($.browser.redactor) {
-                       this._messageField.redactor('autosavePurge');
-               }
-               
-               // redirect to new page
-               if (data.returnValues.url) {
-                       window.location = data.returnValues.url;
-               }
-               else {
-                       if (data.returnValues.template) {
-                               // insert HTML
-                               var $message = $('' + data.returnValues.template);
-                               if (this._container.data('sortOrder') == 'DESC') {
-                                       $message.insertAfter(this._container);
-                               }
-                               else {
-                                       $message.insertBefore(this._container);
-                               }
-                               
-                               // update last post time
-                               this._container.data('lastPostTime', data.returnValues.lastPostTime);
-                               
-                               // show notification
-                               this._notification.show(undefined, undefined, WCF.Language.get('wcf.global.success.add'));
-                               
-                               this._updateHistory($message.wcfIdentify());
-                       }
-                       else {
-                               // show notification
-                               var $message = (this._successMessageNonVisible) ? this._successMessageNonVisible : 'wcf.global.success.add';
-                               this._notification.show(undefined, 5000, WCF.Language.get($message));
-                       }
-                       
-                       if ($.browser.redactor) {
-                               this._messageField.redactor('reset');
-                       }
-                       else {
-                               this._messageField.val('');
-                       }
-                       
-                       // hide quick reply and revert it
-                       this._revertQuickReply(true);
-                       
-                       // count stored quotes
-                       if (this._quoteManager !== null) {
-                               this._quoteManager.countQuotes();
-                       }
-                       
-                       this._pendingSave = false;
-               }
-       },
-       
-       /**
-        * Reverts quick reply on failure to preserve entered message.
-        */
-       _failure: function(data) {
-               this._pendingSave = false;
-               this._revertQuickReply(false);
-               
-               if (data === null || data.returnValues === undefined || data.returnValues.errorType === undefined) {
-                       return true;
-               }
-               
-               var $messageBody = this._container.find('.messageQuickReplyContent .messageBody');
-               var $innerError = $messageBody.children('small.innerError').empty();
-               if (!$innerError.length) {
-                       $innerError = $('<small class="innerError" />').appendTo($messageBody);
-               }
-               
-               $innerError.html(data.returnValues.errorType);
-               
-               return false;
-       },
-       
-       /**
-        * Returns action class name.
-        * 
-        * @return      string
-        */
-       _getClassName: function() {
-               return '';
-       },
-       
-       /**
-        * Returns object id.
-        * 
-        * @return      integer
-        */
-       _getObjectID: function() {
-               return 0;
-       },
-       
-       /**
-        * Updates the history to avoid old content when going back in the browser
-        * history.
-        * 
-        * @param       hash
-        */
-       _updateHistory: function(hash) {
-               window.location.hash = hash;
-       }
-});
-
-/**
- * Provides an inline message editor.
- * 
- * @param      integer         containerID
- */
-WCF.Message.InlineEditor = Class.extend({
-       /**
-        * currently active message
-        * @var string
-        */
-       _activeElementID: '',
-       
-       /**
-        * message cache
-        * @var string
-        */
-       _cache: '',
-       
-       /**
-        * list of messages
-        * @var object
-        */
-       _container: { },
-       
-       /**
-        * container id
-        * @var integer
-        */
-       _containerID: 0,
-       
-       /**
-        * list of dropdowns
-        * @var object
-        */
-       _dropdowns: { },
-       
-       /**
-        * CSS selector for the message container
-        * @var string
-        */
-       _messageContainerSelector: '.jsMessage',
-       
-       /**
-        * prefix of the message editor CSS id
-        * @var string
-        */
-       _messageEditorIDPrefix: 'messageEditor',
-       
-       /**
-        * notification object
-        * @var WCF.System.Notification
-        */
-       _notification: null,
-       
-       /**
-        * proxy object
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * quote manager object
-        * @var WCF.Message.Quote.Manager
-        */
-       _quoteManager: null,
-       
-       /**
-        * support for extended editing form
-        * @var boolean
-        */
-       _supportExtendedForm: false,
-       
-       /**
-        * Initializes a new WCF.Message.InlineEditor object.
-        * 
-        * @param       integer                         containerID
-        * @param       boolean                         supportExtendedForm
-        * @param       WCF.Message.Quote.Manager       quoteManager
-        */
-       init: function(containerID, supportExtendedForm, quoteManager) {
-               this._activeElementID = '';
-               this._cache = '';
-               this._container = { };
-               this._containerID = parseInt(containerID);
-               this._dropdowns = { };
-               this._quoteManager = quoteManager || null;
-               this._supportExtendedForm = (supportExtendedForm) ? true : false;
-               this._proxy = new WCF.Action.Proxy({
-                       failure: $.proxy(this._failure, this),
-                       showLoadingOverlay: false,
-                       success: $.proxy(this._success, this)
-               });
-               this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success.edit'));
-               
-               this.initContainers();
-               
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.Message.InlineEditor', $.proxy(this.initContainers, this));
-       },
-       
-       /**
-        * Initializes editing capability for all messages.
-        */
-       initContainers: function() {
-               $(this._messageContainerSelector).each($.proxy(function(index, container) {
-                       var $container = $(container);
-                       var $containerID = $container.wcfIdentify();
-                       
-                       if (!this._container[$containerID]) {
-                               this._container[$containerID] = $container;
-                               
-                               if ($container.data('canEditInline')) {
-                                       var $button = $container.find('.jsMessageEditButton:eq(0)').data('containerID', $containerID).click($.proxy(this._clickInline, this));
-                                       if ($container.data('canEdit')) $button.dblclick($.proxy(this._click, this));
-                               }
-                               else if ($container.data('canEdit')) {
-                                       $container.find('.jsMessageEditButton:eq(0)').data('containerID', $containerID).click($.proxy(this._click, this));
-                               }
-                       }
-               }, this));
-       },
-       
-       /**
-        * Loads WYSIWYG editor for selected message.
-        * 
-        * @param       object          event
-        * @param       integer         containerID
-        * @return      boolean
-        */
-       _click: function(event, containerID) {
-               var $containerID = (event === null) ? containerID : $(event.currentTarget).data('containerID');
-               if (this._activeElementID === '') {
-                       this._activeElementID = $containerID;
-                       this._prepare();
-                       
-                       this._proxy.setOption('data', {
-                               actionName: 'beginEdit',
-                               className: this._getClassName(),
-                               interfaceName: 'wcf\\data\\IMessageInlineEditorAction',
-                               parameters: {
-                                       containerID: this._containerID,
-                                       objectID: this._container[$containerID].data('objectID')
-                               }
-                       });
-                       this._proxy.setOption('failure', $.proxy(function() { this._cancel(); }, this));
-                       this._proxy.sendRequest();
-               }
-               else {
-                       var $notification = new WCF.System.Notification(WCF.Language.get('wcf.message.error.editorAlreadyInUse'), 'warning');
-                       $notification.show();
-               }
-               
-               // force closing dropdown to avoid displaying the dropdown after
-               // triple clicks
-               if (this._dropdowns[this._container[$containerID].data('objectID')]) {
-                       this._dropdowns[this._container[$containerID].data('objectID')].removeClass('dropdownOpen');
-               }
-               
-               if (event !== null) {
-                       event.stopPropagation();
-                       return false;
-               }
-       },
-       
-       /**
-        * Provides an inline dropdown menu instead of directly loading the WYSIWYG editor.
-        * 
-        * @param       object          event
-        * @return      boolean
-        */
-       _clickInline: function(event) {
-               var $button = $(event.currentTarget);
-               
-               if (!$button.hasClass('dropdownToggle')) {
-                       var $containerID = $button.data('containerID');
-                       
-                       $button.addClass('dropdownToggle').parent().addClass('dropdown');
-                       
-                       var $dropdownMenu = $('<ul class="dropdownMenu" />').insertAfter($button);
-                       this._initDropdownMenu($containerID, $dropdownMenu);
-                       
-                       WCF.DOMNodeInsertedHandler.execute();
-                       
-                       this._dropdowns[this._container[$containerID].data('objectID')] = $dropdownMenu;
-                       
-                       WCF.Dropdown.registerCallback($button.parent().wcfIdentify(), $.proxy(this._toggleDropdown, this));
-                       
-                       // trigger click event
-                       $button.trigger('click');
-               }
-               
-               event.stopPropagation();
-               return false;
-       },
-       
-       /**
-        * Handles errorneus editing requests.
-        * 
-        * @param       object          data
-        */
-       _failure: function(data) {
-               this._revertEditor();
-               
-               if (data === null || data.returnValues === undefined || data.returnValues.errorType === undefined) {
-                       return true;
-               }
-               
-               var $messageBody = this._container[this._activeElementID].find('.messageBody .messageInlineEditor');
-               var $innerError = $messageBody.children('small.innerError').empty();
-               if (!$innerError.length) {
-                       $innerError = $('<small class="innerError" />').insertBefore($messageBody.children('.formSubmit'));
-               }
-               
-               $innerError.html(data.returnValues.errorType);
-               
-               return false;
-       },
-       
-       /**
-        * Forces message options to stay visible if toggling dropdown menu.
-        * 
-        * @param       string          containerID
-        * @param       string          action
-        */
-       _toggleDropdown: function(containerID, action) {
-               WCF.Dropdown.getDropdown(containerID).parents('.messageOptions').toggleClass('forceOpen');
-       },
-       
-       /**
-        * Initializes the inline edit dropdown menu.
-        * 
-        * @param       integer         containerID
-        * @param       jQuery          dropdownMenu
-        */
-       _initDropdownMenu: function(containerID, dropdownMenu) { },
-       
-       /**
-        * Prepares message for WYSIWYG display.
-        */
-       _prepare: function() {
-               var $messageBody = this._container[this._activeElementID].find('.messageBody');
-               $('<span class="icon icon48 icon-spinner" />').appendTo($messageBody);
-               
-               var $content = $messageBody.find('.messageText');
-               
-               // hide unrelated content
-               $content.parent().children('.jsInlineEditorHideContent').hide();
-               $messageBody.children('.attachmentThumbnailList, .attachmentFileList').hide();
-               
-               this._cache = $content.detach();
-       },
-       
-       /**
-        * Cancels editing and reverts to original message.
-        */
-       _cancel: function() {
-               var $container = this._container[this._activeElementID].removeClass('jsInvalidQuoteTarget');
-               
-               // remove editor
-               var $target = $('#' + this._messageEditorIDPrefix + $container.data('objectID'));
-               $target.redactor('autosavePurge');
-               $target.redactor('destroy');
-               
-               // restore message
-               var $messageBody = $container.find('.messageBody');
-               $messageBody.children('.icon-spinner').remove();
-               $messageBody.children('div:eq(0)').html(this._cache);
-               $messageBody.children('.attachmentThumbnailList, .attachmentFileList').show();
-               
-               // show unrelated content
-               $messageBody.find('.jsInlineEditorHideContent').show();
-               
-               // revert message options
-               this._container[this._activeElementID].find('.messageOptions').removeClass('forceHidden');
-               
-               this._activeElementID = '';
-               
-               if (this._quoteManager) {
-                       this._quoteManager.clearAlternativeEditor();
-               }
-       },
-       
-       /**
-        * Handles successful AJAX calls.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               switch (data.returnValues.actionName) {
-                       case 'beginEdit':
-                               this._showEditor(data);
-                       break;
-                       
-                       case 'save':
-                               this._showMessage(data);
-                       break;
-               }
-       },
-       
-       /**
-        * Shows WYSIWYG editor for active message.
-        * 
-        * @param       object          data
-        */
-       _showEditor: function(data) {
-               // revert failure function
-               this._proxy.setOption('failure', $.proxy(this._failure, this));
-               
-               var $messageBody = this._container[this._activeElementID].addClass('jsInvalidQuoteTarget').find('.messageBody');
-               $messageBody.children('.icon-spinner').remove();
-               var $content = $messageBody.children('div:eq(0)');
-               
-               // insert wysiwyg
-               $('' + data.returnValues.template).appendTo($content);
-               
-               // bind buttons
-               var $formSubmit = $content.find('.formSubmit');
-               var $saveButton = $formSubmit.find('button[data-type=save]').click($.proxy(this._save, this));
-               if (this._supportExtendedForm) $formSubmit.find('button[data-type=extended]').click($.proxy(this._prepareExtended, this));
-               $formSubmit.find('button[data-type=cancel]').click($.proxy(this._cancel, this));
-               
-               WCF.Message.Submit.registerButton(
-                       this._messageEditorIDPrefix + this._container[this._activeElementID].data('objectID'),
-                       $saveButton
-               );
-               
-               // hide message options
-               this._container[this._activeElementID].find('.messageOptions').addClass('forceHidden');
-               
-               var $element = $('#' + this._messageEditorIDPrefix + this._container[this._activeElementID].data('objectID'));
-               if ($.browser.redactor) {
-                       new WCF.PeriodicalExecuter($.proxy(function(pe) {
-                               pe.stop();
-                               
-                               if (this._quoteManager) {
-                                       this._quoteManager.setAlternativeEditor($element);
-                               }
-                       }, this), 250);
-               }
-               else {
-                       $element.focus();
-               }
-       },
-       
-       /**
-        * Reverts editor.
-        */
-       _revertEditor: function() {
-               var $messageBody = this._container[this._activeElementID].removeClass('jsInvalidQuoteTarget').find('.messageBody');
-               $messageBody.children('span.icon-spinner').remove();
-               $messageBody.children('div:eq(0)').children().show();
-               $messageBody.children('.attachmentThumbnailList, .attachmentFileList').show();
-               
-               // show unrelated content
-               $messageBody.find('.jsInlineEditorHideContent').show();
-               
-               if (this._quoteManager) {
-                       this._quoteManager.clearAlternativeEditor();
-               }
-       },
-       
-       /**
-        * Saves editor contents.
-        */
-       _save: function() {
-               var $container = this._container[this._activeElementID];
-               var $objectID = $container.data('objectID');
-               var $message = '';
-               
-               if ($.browser.redactor) {
-                       $message = $('#' + this._messageEditorIDPrefix + $objectID).redactor('getText');
-               }
-               else {
-                       $message = $('#' + this._messageEditorIDPrefix + $objectID).val();
-               }
-               
-               this._proxy.setOption('data', {
-                       actionName: 'save',
-                       className: this._getClassName(),
-                       interfaceName: 'wcf\\data\\IMessageInlineEditorAction',
-                       parameters: {
-                               containerID: this._containerID,
-                               data: {
-                                       message: $message
-                               },
-                               objectID: $objectID
-                       }
-               });
-               this._proxy.sendRequest();
-               
-               this._hideEditor();
-       },
-       
-       /**
-        * Prepares jumping to extended editing mode.
-        */
-       _prepareExtended: function() {
-               var $container = this._container[this._activeElementID];
-               var $objectID = $container.data('objectID');
-               var $message = '';
-               
-               if ($.browser.redactor) {
-                       $message = $('#' + this._messageEditorIDPrefix + $objectID).redactor('getText');
-               }
-               else {
-                       $message = $('#' + this._messageEditorIDPrefix + $objectID).val();
-               }
-               
-               new WCF.Action.Proxy({
-                       autoSend: true,
-                       data: {
-                               actionName: 'jumpToExtended',
-                               className: this._getClassName(),
-                               parameters: {
-                                       containerID: this._containerID,
-                                       message: $message,
-                                       messageID: $objectID
-                               }
-                       },
-                       success: function(data, textStatus, jqXHR) {
-                               window.location = data.returnValues.url;
-                       }
-               });
-       },
-       
-       /**
-        * Hides WYSIWYG editor.
-        */
-       _hideEditor: function() {
-               var $messageBody = this._container[this._activeElementID].removeClass('jsInvalidQuoteTarget').find('.messageBody');
-               $('<span class="icon icon48 icon-spinner" />').appendTo($messageBody);
-               $messageBody.children('div:eq(0)').children().hide();
-               $messageBody.children('.attachmentThumbnailList, .attachmentFileList').show();
-               
-               // show unrelated content
-               $messageBody.find('.jsInlineEditorHideContent').show();
-               
-               if (this._quoteManager) {
-                       this._quoteManager.clearAlternativeEditor();
-               }
-       },
-       
-       /**
-        * Shows rendered message.
-        * 
-        * @param       object          data
-        */
-       _showMessage: function(data) {
-               var $container = this._container[this._activeElementID].removeClass('jsInvalidQuoteTarget');
-               var $messageBody = $container.find('.messageBody');
-               $messageBody.children('.icon-spinner').remove();
-               var $content = $messageBody.children('div:eq(0)');
-               
-               // show unrelated content
-               $content.parent().children('.jsInlineEditorHideContent').show();
-               
-               // revert message options
-               this._container[this._activeElementID].find('.messageOptions').removeClass('forceHidden');
-               
-               // remove editor
-               if ($.browser.redactor) {
-                       $('#' + this._messageEditorIDPrefix + $container.data('objectID')).redactor('destroy');
-               }
-               
-               $content.empty();
-               
-               // insert new message
-               $content.html('<div class="messageText">' + data.returnValues.message + '</div>');
-               
-               if (data.returnValues.attachmentList == undefined) {
-                       $messageBody.children('.attachmentThumbnailList, .attachmentFileList').show();
-               }
-               else {
-                       $messageBody.children('.attachmentThumbnailList, .attachmentFileList').remove();
-                       
-                       if (data.returnValues.attachmentList) {
-                               $(data.returnValues.attachmentList).insertAfter($messageBody.children('div:eq(0)'));
-                       }
-               }
-               
-               this._activeElementID = '';
-               
-               this._updateHistory(this._getHash($container.data('objectID')));
-               
-               this._notification.show();
-               
-               if (this._quoteManager) {
-                       this._quoteManager.clearAlternativeEditor();
-               }
-       },
-       
-       /**
-        * Returns message action class name.
-        * 
-        * @return      string
-        */
-       _getClassName: function() {
-               return '';
-       },
-       
-       /**
-        * Returns the hash added to the url after successfully editing a message.
-        * 
-        * @return      string
-        */
-       _getHash: function(objectID) {
-               return '#message' + objectID;
-       },
-       
-       /**
-        * Updates the history to avoid old content when going back in the browser
-        * history.
-        * 
-        * @param       hash
-        */
-       _updateHistory: function(hash) {
-               window.location.hash = hash;
-       }
-});
-
-/**
- * Handles submit buttons for forms with an embedded WYSIWYG editor.
- */
-WCF.Message.Submit = {
-       /**
-        * list of registered buttons
-        * @var object
-        */
-       _buttons: { },
-       
-       /**
-        * Registers submit button for specified wysiwyg container id.
-        * 
-        * @param       string          wysiwygContainerID
-        * @param       string          selector
-        */
-       registerButton: function(wysiwygContainerID, selector) {
-               if (!WCF.Browser.isChrome()) {
-                       return;
-               }
-               
-               this._buttons[wysiwygContainerID] = $(selector);
-       },
-       
-       /**
-        * Triggers 'click' event for registered buttons.
-        */
-       execute: function(wysiwygContainerID) {
-               if (!this._buttons[wysiwygContainerID]) {
-                       return;
-               }
-               
-               this._buttons[wysiwygContainerID].trigger('click');
-       }
-};
-
-/**
- * Namespace for message quotes.
- */
-WCF.Message.Quote = { };
-
-/**
- * Handles message quotes.
- * 
- * @param      string          className
- * @param      string          objectType
- * @param      string          containerSelector
- * @param      string          messageBodySelector
- */
-WCF.Message.Quote.Handler = Class.extend({
-       /**
-        * active container id
-        * @var string
-        */
-       _activeContainerID: '',
-       
-       /**
-        * action class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * list of message containers
-        * @var object
-        */
-       _containers: { },
-       
-       /**
-        * container selector
-        * @var string
-        */
-       _containerSelector: '',
-       
-       /**
-        * 'copy quote' overlay
-        * @var jQuery
-        */
-       _copyQuote: null,
-       
-       /**
-        * marked message
-        * @var string
-        */
-       _message: '',
-       
-       /**
-        * message body selector
-        * @var string
-        */
-       _messageBodySelector: '',
-       
-       /**
-        * object id
-        * @var integer
-        */
-       _objectID: 0,
-       
-       /**
-        * object type name
-        * @var string
-        */
-       _objectType: '',
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * quote manager
-        * @var WCF.Message.Quote.Manager
-        */
-       _quoteManager: null,
-       
-       /**
-        * Initializes the quote handler for given object type.
-        * 
-        * @param       WCF.Message.Quote.Manager       quoteManager
-        * @param       string                          className
-        * @param       string                          objectType
-        * @param       string                          containerSelector
-        * @param       string                          messageBodySelector
-        * @param       string                          messageContentSelector
-        */
-       init: function(quoteManager, className, objectType, containerSelector, messageBodySelector, messageContentSelector) {
-               this._className = className;
-               if (this._className == '') {
-                       console.debug("[WCF.Message.QuoteManager] Empty class name given, aborting.");
-                       return;
-               }
-               
-               this._objectType = objectType;
-               if (this._objectType == '') {
-                       console.debug("[WCF.Message.QuoteManager] Empty object type name given, aborting.");
-                       return;
-               }
-               
-               this._containerSelector = containerSelector;
-               this._message = '';
-               this._messageBodySelector = messageBodySelector;
-               this._messageContentSelector = messageContentSelector;
-               this._objectID = 0;
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               this._initContainers();
-               this._initCopyQuote();
-               
-               $(document).mouseup($.proxy(this._mouseUp, this));
-               
-               // register with quote manager
-               this._quoteManager = quoteManager;
-               this._quoteManager.register(this._objectType, this);
-               
-               // register with DOMNodeInsertedHandler
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.Message.Quote.Handler' + objectType.hashCode(), $.proxy(this._initContainers, this));
-       },
-       
-       /**
-        * Initializes message containers.
-        */
-       _initContainers: function() {
-               var self = this;
-               $(this._containerSelector).each(function(index, container) {
-                       var $container = $(container);
-                       var $containerID = $container.wcfIdentify();
-                       
-                       if (!self._containers[$containerID]) {
-                               self._containers[$containerID] = $container;
-                               if ($container.hasClass('jsInvalidQuoteTarget')) {
-                                       return true;
-                               }
-                               
-                               if (self._messageBodySelector !== null) {
-                                       $container = $container.find(self._messageBodySelector).data('containerID', $containerID);
-                               }
-                               
-                               $container.mousedown($.proxy(self._mouseDown, self));
-                               
-                               // bind event to quote whole message
-                               self._containers[$containerID].find('.jsQuoteMessage').click($.proxy(self._saveFullQuote, self));
-                       }
-               });
-       },
-       
-       /**
-        * Handles mouse down event.
-        * 
-        * @param       object          event
-        */
-       _mouseDown: function(event) {
-               // hide copy quote
-               this._copyQuote.hide();
-               
-               // store container ID
-               var $container = $(event.currentTarget);
-               
-               if (this._messageBodySelector) {
-                       $container = this._containers[$container.data('containerID')];
-               }
-               
-               if ($container.hasClass('jsInvalidQuoteTarget')) {
-                       this._activeContainerID = '';
-                       
-                       return;
-               }
-               
-               this._activeContainerID = $container.wcfIdentify();
-               
-               // remove alt-tag from all images, fixes quoting in Firefox
-               if ($.browser.mozilla) {
-                       $container.find('img').each(function() {
-                               var $image = $(this);
-                               $image.data('__alt', $image.attr('alt')).removeAttr('alt');
-                       });
-               }
-       },
-       
-       /**
-        * Returns the text of a node and its children.
-        * 
-        * @param       object          node
-        * @return      string
-        */
-       _getNodeText: function(node) {
-               var nodeText = '';
-               
-               for (var i = 0; i < node.childNodes.length; i++) {
-                       if (node.childNodes[i].nodeType == 3) {
-                               // text node
-                               nodeText += node.childNodes[i].nodeValue;
-                       }
-                       else {
-                               if (!node.childNodes[i].tagName) {
-                                       continue;
-                               }
-                               
-                               var $tagName = node.childNodes[i].tagName.toLowerCase();
-                               if ($tagName === 'li') {
-                                       nodeText += "\r\n";
-                               }
-                               else if ($tagName === 'td' && !$.browser.msie) {
-                                       nodeText += "\r\n";
-                               }
-                               
-                               nodeText += this._getNodeText(node.childNodes[i]);
-                               
-                               if ($tagName === 'ul') {
-                                       nodeText += "\n";
-                               }
-                       }
-               }
-               
-               return nodeText;
-       },
-       
-       /**
-        * Handles the mouse up event.
-        * 
-        * @param       object          event
-        */
-       _mouseUp: function(event) {
-               // ignore event
-               if (this._activeContainerID == '') {
-                       this._copyQuote.hide();
-                       
-                       return;
-               }
-               
-               var $container = this._containers[this._activeContainerID];
-               var $selection = this._getSelectedText();
-               var $text = $.trim($selection);
-               if ($text == '') {
-                       this._copyQuote.hide();
-                       
-                       return;
-               }
-               
-               // compare selection with message text of given container
-               var $messageText = null;
-               if (this._messageBodySelector) {
-                       $messageText = this._getNodeText($container.find(this._messageContentSelector).get(0));
-               }
-               else {
-                       $messageText = this._getNodeText($container.get(0));
-               }
-               
-               // selected text is not part of $messageText or contains text from unrelated nodes
-               if (this._normalize($messageText).indexOf(this._normalize($text)) === -1) {
-                       return;
-               }
-               this._copyQuote.show();
-               
-               var $coordinates = this._getBoundingRectangle($container, $selection);
-               var $dimensions = this._copyQuote.getDimensions('outer');
-               var $left = ($coordinates.right - $coordinates.left) / 2 - ($dimensions.width / 2) + $coordinates.left;
-               
-               this._copyQuote.css({
-                       top: $coordinates.top - $dimensions.height - 7 + 'px',
-                       left: $left + 'px'
-               });
-               this._copyQuote.hide();
-               
-               // reset containerID
-               this._activeContainerID = '';
-               
-               // show element after a delay, to prevent display if text was unmarked again (clicking into marked text)
-               var self = this;
-               new WCF.PeriodicalExecuter(function(pe) {
-                       pe.stop();
-                       
-                       var $text = $.trim(self._getSelectedText());
-                       if ($text != '') {
-                               self._copyQuote.show();
-                               self._message = $text;
-                               self._objectID = $container.data('objectID');
-                               
-                               // revert alt tags, fixes quoting in Firefox
-                               if ($.browser.mozilla) {
-                                       $container.find('img').each(function() {
-                                               var $image = $(this);
-                                               $image.attr('alt', $image.data('__alt'));
-                                       });
-                               }
-                       }
-               }, 10);
-       },
-       
-       /**
-        * Normalizes a text for comparison.
-        * 
-        * @param       string          text
-        * @return      string
-        */
-       _normalize: function(text) {
-               return text.replace(/\r?\n|\r/g, "\n").replace(/\s/g, ' ').replace(/\s{2,}/g, ' ');
-       },
-       
-       /**
-        * Returns the left or right offset of the current text selection.
-        * 
-        * @param       object          range
-        * @param       boolean         before
-        * @return      object
-        */
-       _getOffset: function(range, before) {
-               range.collapse(before);
-               
-               var $elementID = WCF.getRandomID();
-               var $element = document.createElement('span');
-               $element.innerHTML = '<span id="' + $elementID + '"></span>';
-               var $fragment = document.createDocumentFragment(), $node;
-               while ($node = $element.firstChild) {
-                       $fragment.appendChild($node);
-               }
-               range.insertNode($fragment);
-               
-               $element = $('#' + $elementID);
-               var $position = $element.offset();
-               $position.top = $position.top - $(window).scrollTop();
-               $element.remove();
-               
-               return $position;
-       },
-       
-       /**
-        * Returns the offsets of the selection's bounding rectangle.
-        * 
-        * @return      object
-        */
-       _getBoundingRectangle: function(container, selection) {
-               var $coordinates = null;
-               
-               if (document.createRange && typeof document.createRange().getBoundingClientRect != "undefined") { // Opera, Firefox, Safari, Chrome
-                       if (selection.rangeCount > 0) {
-                               // the coordinates returned by getBoundingClientRect() is relative to the window, not the document!
-                               //var $rect = selection.getRangeAt(0).getBoundingClientRect();
-                               var $rects = selection.getRangeAt(0).getClientRects();
-                               var $rect = selection.getRangeAt(0).getBoundingClientRect();
-                               
-                               /*
-                               var $rect = { };
-                               if (!$.browser.mozilla && $rects.length > 1) {
-                                       // save current selection to restore it later
-                                       var $range = selection.getRangeAt(0);
-                                       var $bckp = this._saveSelection(container.get(0));
-                                       var $position1 = this._getOffset($range, true);
-                                       
-                                       var $range = selection.getRangeAt(0);
-                                       var $position2 = this._getOffset($range, false);
-                                       
-                                       $rect = {
-                                               left: Math.min($position1.left, $position2.left),
-                                               right: Math.max($position1.left, $position2.left),
-                                               top: Math.max($position1.top, $position2.top)
-                                       };
-                                       
-                                       // restore selection
-                                       this._restoreSelection(container.get(0), $bckp);
-                               }
-                               else {
-                                       $rect = selection.getRangeAt(0).getBoundingClientRect();
-                               }
-                               */
-                               
-                               var $document = $(document);
-                               var $offsetTop = $document.scrollTop();
-                               
-                               $coordinates = {
-                                       left: $rect.left,
-                                       right: $rect.right,
-                                       top: $rect.top + $offsetTop
-                               };
-                       }
-               }
-               else if (document.selection && document.selection.type != "Control") { // IE
-                       var $range = document.selection.createRange();
-                       
-                       $coordinates = {
-                               left: $range.boundingLeft,
-                               right: $range.boundingRight,
-                               top: $range.boundingTop
-                       };
-               }
-               
-               return $coordinates;
-       },
-       
-       /**
-        * Saves current selection.
-        * 
-        * @see         http://stackoverflow.com/a/13950376
-        * 
-        * @param       object          containerEl
-        * @return      object
-        */
-       _saveSelection: function(containerEl) {
-               if (window.getSelection && document.createRange) {
-                       var range = window.getSelection().getRangeAt(0);
-                       var preSelectionRange = range.cloneRange();
-                       preSelectionRange.selectNodeContents(containerEl);
-                       preSelectionRange.setEnd(range.startContainer, range.startOffset);
-                       var start = preSelectionRange.toString().length;
-                       
-                       return {
-                               start: start,
-                               end: start + range.toString().length
-                       };
-               }
-               else {
-                       var selectedTextRange = document.selection.createRange();
-                       var preSelectionTextRange = document.body.createTextRange();
-                       preSelectionTextRange.moveToElementText(containerEl);
-                       preSelectionTextRange.setEndPoint("EndToStart", selectedTextRange);
-                       var start = preSelectionTextRange.text.length;
-                       
-                       return {
-                               start: start,
-                               end: start + selectedTextRange.text.length
-                       };
-               }
-       },
-       
-       /**
-        * Restores a selection.
-        * 
-        * @see         http://stackoverflow.com/a/13950376
-        * 
-        * @param       object          containerEl
-        * @param       object          savedSel
-        */
-       _restoreSelection: function(containerEl, savedSel) {
-               if (window.getSelection && document.createRange) {
-                       var charIndex = 0, range = document.createRange();
-                       range.setStart(containerEl, 0);
-                       range.collapse(true);
-                       var nodeStack = [containerEl], node, foundStart = false, stop = false;
-                       
-                       while (!stop && (node = nodeStack.pop())) {
-                               if (node.nodeType == 3) {
-                                       var nextCharIndex = charIndex + node.length;
-                                       if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
-                                               range.setStart(node, savedSel.start - charIndex);
-                                               foundStart = true;
-                                       }
-                                       if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
-                                               range.setEnd(node, savedSel.end - charIndex);
-                                               stop = true;
-                                       }
-                                       charIndex = nextCharIndex;
-                               } else {
-                                       var i = node.childNodes.length;
-                                       while (i--) {
-                                               nodeStack.push(node.childNodes[i]);
-                                       };
-                               };
-                       }
-                       
-                       var sel = window.getSelection();
-                       sel.removeAllRanges();
-                       sel.addRange(range);
-               }
-               else {
-                       var textRange = document.body.createTextRange();
-                       textRange.moveToElementText(containerEl);
-                       textRange.collapse(true);
-                       textRange.moveEnd("character", savedSel.end);
-                       textRange.moveStart("character", savedSel.start);
-                       textRange.select();
-               }
-       },
-       
-       /**
-        * Initializes the 'copy quote' element.
-        */
-       _initCopyQuote: function() {
-               this._copyQuote = $('#quoteManagerCopy');
-               if (!this._copyQuote.length) {
-                       this._copyQuote = $('<div id="quoteManagerCopy" class="balloonTooltip"><span>' + WCF.Language.get('wcf.message.quote.quoteSelected') + '</span><span class="pointer"><span></span></span></div>').hide().appendTo(document.body);
-                       this._copyQuote.click($.proxy(this._saveQuote, this));
-               }
-       },
-       
-       /**
-        * Returns the text selection.
-        * 
-        * @return      object
-        */
-       _getSelectedText: function() {
-               if (window.getSelection) { // Opera, Firefox, Safari, Chrome, IE 9+
-                       return window.getSelection();
-               }
-               else if (document.getSelection) { // Opera, Firefox, Safari, Chrome, IE 9+
-                       return document.getSelection();
-               }
-               else if (document.selection) { // IE 8
-                       return document.selection.createRange().text;
-               }
-               
-               return '';
-       },
-       
-       /**
-        * Saves a full quote.
-        * 
-        * @param       object          event
-        */
-       _saveFullQuote: function(event) {
-               var $listItem = $(event.currentTarget);
-               
-               this._proxy.setOption('data', {
-                       actionName: 'saveFullQuote',
-                       className: this._className,
-                       interfaceName: 'wcf\\data\\IMessageQuoteAction',
-                       objectIDs: [ $listItem.data('objectID') ]
-               });
-               this._proxy.sendRequest();
-               
-               // mark element as quoted
-               if ($listItem.data('isQuoted')) {
-                       $listItem.data('isQuoted', false).children('a').removeClass('active');
-               }
-               else {
-                       $listItem.data('isQuoted', true).children('a').addClass('active');
-               }
-               
-               // discard event
-               event.stopPropagation();
-               return false;
-       },
-       
-       /**
-        * Saves a quote.
-        */
-       _saveQuote: function() {
-               this._proxy.setOption('data', {
-                       actionName: 'saveQuote',
-                       className: this._className,
-                       interfaceName: 'wcf\\data\\IMessageQuoteAction',
-                       objectIDs: [ this._objectID ],
-                       parameters: {
-                               message: this._message
-                       }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               if (data.returnValues.count !== undefined) {
-                       var $fullQuoteObjectIDs = (data.fullQuoteObjectIDs !== undefined) ? data.fullQuoteObjectIDs : { };
-                       this._quoteManager.updateCount(data.returnValues.count, $fullQuoteObjectIDs);
-               }
-       },
-       
-       /**
-        * Updates the full quote data for all matching objects.
-        * 
-        * @param       array<integer>          $objectIDs
-        */
-       updateFullQuoteObjectIDs: function(objectIDs) {
-               for (var $containerID in this._containers) {
-                       this._containers[$containerID].find('.jsQuoteMessage').each(function(index, button) {
-                               // reset all markings
-                               var $button = $(button).data('isQuoted', 0);
-                               $button.children('a').removeClass('active');
-                               
-                               // mark as active
-                               if (WCF.inArray($button.data('objectID'), objectIDs)) {
-                                       $button.data('isQuoted', 1).children('a').addClass('active');
-                               }
-                       });
-               }
-       }
-});
-
-/**
- * Manages stored quotes.
- * 
- * @param      integer         count
- */
-WCF.Message.Quote.Manager = Class.extend({
-       /**
-        * list of form buttons
-        * @var object
-        */
-       _buttons: { },
-       
-       /**
-        * number of stored quotes
-        * @var integer
-        */
-       _count: 0,
-       
-       /**
-        * dialog overlay
-        * @var jQuery
-        */
-       _dialog: null,
-       
-       /**
-        * Redactor element
-        * @var jQuery
-        */
-       _editorElement: null,
-       
-       /**
-        * alternative Redactor element
-        * @var jQuery
-        */
-       _editorElementAlternative: null,
-       
-       /**
-        * form element
-        * @var jQuery
-        */
-       _form: null,
-       
-       /**
-        * list of quote handlers
-        * @var object
-        */
-       _handlers: { },
-       
-       /**
-        * true, if an up-to-date template exists
-        * @var boolean
-        */
-       _hasTemplate: false,
-       
-       /**
-        * true, if related quotes should be inserted
-        * @var boolean
-        */
-       _insertQuotes: true,
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * list of quotes to remove upon submit
-        * @var array<string>
-        */
-       _removeOnSubmit: [ ],
-       
-       /**
-        * show quotes element
-        * @var jQuery
-        */
-       _showQuotes: null,
-       
-       /**
-        * allow pasting
-        * @var boolean
-        */
-       _supportPaste: false,
-       
-       /**
-        * Initializes the quote manager.
-        * 
-        * @param       integer         count
-        * @param       string          elementID
-        * @param       boolean         supportPaste
-        * @param       array<string>   removeOnSubmit
-        */
-       init: function(count, elementID, supportPaste, removeOnSubmit) {
-               this._buttons = {
-                       insert: null,
-                       remove: null
-               };
-               this._count = parseInt(count) || 0;
-               this._dialog = null;
-               this._editorElement = null;
-               this._editorElementAlternative = null;
-               this._form = null;
-               this._handlers = { };
-               this._hasTemplate = false;
-               this._insertQuotes = true;
-               this._removeOnSubmit = [ ];
-               this._showQuotes = null;
-               this._supportPaste = false;
-               
-               if (elementID) {
-                       this._editorElement = $('#' + elementID);
-                       if (this._editorElement.length) {
-                               this._supportPaste = true;
-                               
-                               // get surrounding form-tag
-                               this._form = this._editorElement.parents('form:eq(0)');
-                               if (this._form.length) {
-                                       this._form.submit($.proxy(this._submit, this));
-                                       this._removeOnSubmit = removeOnSubmit || [ ];
-                               }
-                               else {
-                                       this._form = null;
-                                       
-                                       // allow override
-                                       this._supportPaste = (supportPaste === true) ? true : false;
-                               }
-                       }
-               }
-               
-               this._proxy = new WCF.Action.Proxy({
-                       showLoadingOverlay: false,
-                       success: $.proxy(this._success, this),
-                       url: 'index.php/MessageQuote/?t=' + SECURITY_TOKEN + SID_ARG_2ND
-               });
-               
-               this._toggleShowQuotes();
-       },
-       
-       /**
-        * Sets an alternative editor element on runtime.
-        * 
-        * @param       jQuery          element
-        */
-       setAlternativeEditor: function(element) {
-               this._editorElementAlternative = element;
-       },
-       
-       /**
-        * Clears alternative editor element.
-        */
-       clearAlternativeEditor: function() {
-               this._editorElementAlternative = null;
-       },
-       
-       /**
-        * Registers a quote handler.
-        * 
-        * @param       string                          objectType
-        * @param       WCF.Message.Quote.Handler       handler
-        */
-       register: function(objectType, handler) {
-               this._handlers[objectType] = handler;
-       },
-       
-       /**
-        * Updates number of stored quotes.
-        * 
-        * @param       integer         count
-        * @param       object          fullQuoteObjectIDs
-        */
-       updateCount: function(count, fullQuoteObjectIDs) {
-               this._count = parseInt(count) || 0;
-               
-               this._toggleShowQuotes();
-               
-               // update full quote ids of handlers
-               for (var $objectType in this._handlers) {
-                       if (fullQuoteObjectIDs[$objectType]) {
-                               this._handlers[$objectType].updateFullQuoteObjectIDs(fullQuoteObjectIDs[$objectType]);
-                       }
-               }
-       },
-       
-       /**
-        * Inserts all associated quotes upon first time using quick reply.
-        * 
-        * @param       string          className
-        * @param       integer         parentObjectID
-        * @param       object          callback
-        */
-       insertQuotes: function(className, parentObjectID, callback) {
-               if (!this._insertQuotes) {
-                       this._insertQuotes = true;
-                       
-                       return;
-               }
-               
-               new WCF.Action.Proxy({
-                       autoSend: true,
-                       data: {
-                               actionName: 'getRenderedQuotes',
-                               className: className,
-                               interfaceName: 'wcf\\data\\IMessageQuoteAction',
-                               parameters: {
-                                       parentObjectID: parentObjectID
-                               }
-                       },
-                       success: callback
-               });
-       },
-       
-       /**
-        * Toggles the display of the 'Show quotes' button
-        */
-       _toggleShowQuotes: function() {
-               if (!this._count) {
-                       if (this._showQuotes !== null) {
-                               this._showQuotes.hide();
-                       }
-               }
-               else {
-                       if (this._showQuotes === null) {
-                               this._showQuotes = $('#showQuotes');
-                               if (!this._showQuotes.length) {
-                                       this._showQuotes = $('<div id="showQuotes" class="balloonTooltip" />').click($.proxy(this._click, this)).appendTo(document.body);
-                               }
-                       }
-                       
-                       var $text = WCF.Language.get('wcf.message.quote.showQuotes').replace(/#count#/, this._count);
-                       this._showQuotes.text($text).show();
-               }
-               
-               this._hasTemplate = false;
-       },
-       
-       /**
-        * Handles clicks on 'Show quotes'.
-        */
-       _click: function() {
-               if (this._hasTemplate) {
-                       this._dialog.wcfDialog('open');
-               }
-               else {
-                       this._proxy.showLoadingOverlayOnce();
-                       
-                       this._proxy.setOption('data', {
-                               actionName: 'getQuotes',
-                               supportPaste: this._supportPaste
-                       });
-                       this._proxy.sendRequest();
-               }
-       },
-       
-       /**
-        * Renders the dialog.
-        * 
-        * @param       string          template
-        */
-       renderDialog: function(template) {
-               // create dialog if not exists
-               if (this._dialog === null) {
-                       this._dialog = $('#messageQuoteList');
-                       if (!this._dialog.length) {
-                               this._dialog = $('<div id="messageQuoteList" />').hide().appendTo(document.body);
-                       }
-               }
-               
-               // add template
-               this._dialog.html(template);
-               
-               // add 'insert' and 'delete' buttons
-               var $formSubmit = $('<div class="formSubmit" />').appendTo(this._dialog);
-               if (this._supportPaste) this._buttons.insert = $('<button class="buttonPrimary">' + WCF.Language.get('wcf.message.quote.insertAllQuotes') + '</button>').click($.proxy(this._insertSelected, this)).appendTo($formSubmit);
-               this._buttons.remove = $('<button>' + WCF.Language.get('wcf.message.quote.removeAllQuotes') + '</button>').click($.proxy(this._removeSelected, this)).appendTo($formSubmit);
-               
-               // show dialog
-               this._dialog.wcfDialog({
-                       title: WCF.Language.get('wcf.message.quote.manageQuotes')
-               });
-               this._dialog.wcfDialog('render');
-               this._hasTemplate = true;
-               
-               // bind event listener
-               var $insertQuoteButtons = this._dialog.find('.jsInsertQuote');
-               if (this._supportPaste) {
-                       $insertQuoteButtons.click($.proxy(this._insertQuote, this));
-               }
-               else {
-                       $insertQuoteButtons.hide();
-               }
-               
-               this._dialog.find('input.jsCheckbox').change($.proxy(this._changeButtons, this));
-               
-               // mark quotes for removal
-               if (this._removeOnSubmit.length) {
-                       var self = this;
-                       this._dialog.find('input.jsRemoveQuote').each(function(index, input) {
-                               var $input = $(input).change($.proxy(this._change, this));
-                               
-                               // mark for deletion
-                               if (WCF.inArray($input.parent('li').attr('data-quote-id'), self._removeOnSubmit)) {
-                                       $input.attr('checked', 'checked');
-                               }
-                       });
-               }
-       },
-       
-       /**
-        * Updates button labels if a checkbox is checked or unchecked.
-        */
-       _changeButtons: function() {
-               // selection
-               if (this._dialog.find('input.jsCheckbox:checked').length) {
-                       if (this._supportPaste) this._buttons.insert.html(WCF.Language.get('wcf.message.quote.insertSelectedQuotes'));
-                       this._buttons.remove.html(WCF.Language.get('wcf.message.quote.removeSelectedQuotes'));
-               }
-               else {
-                       // no selection, pick all
-                       if (this._supportPaste) this._buttons.insert.html(WCF.Language.get('wcf.message.quote.insertAllQuotes'));
-                       this._buttons.remove.html(WCF.Language.get('wcf.message.quote.removeAllQuotes'));
-               }
-       },
-       
-       /**
-        * Checks for change event on delete-checkboxes.
-        * 
-        * @param       object          event
-        */
-       _change: function(event) {
-               var $input = $(event.currentTarget);
-               var $quoteID = $input.parent('li').attr('data-quote-id');
-               
-               if ($input.prop('checked')) {
-                       this._removeOnSubmit.push($quoteID);
-               }
-               else {
-                       for (var $index in this._removeOnSubmit) {
-                               if (this._removeOnSubmit[$index] == $quoteID) {
-                                       delete this._removeOnSubmit[$index];
-                                       break;
-                               }
-                       }
-               }
-       },
-       
-       /**
-        * Inserts the selected quotes.
-        */
-       _insertSelected: function() {
-               if (this._editorElementAlternative === null) {
-                       var $api = $('.jsQuickReply:eq(0)').data('__api');
-                       if ($api && !$api.getContainer().is(':visible')) {
-                               this._insertQuotes = false;
-                               $api.click(null);
-                       }
-               }
-               
-               if (!this._dialog.find('input.jsCheckbox:checked').length) {
-                       this._dialog.find('input.jsCheckbox').prop('checked', 'checked');
-               }
-               
-               // insert all quotes
-               this._dialog.find('input.jsCheckbox:checked').each($.proxy(function(index, input) {
-                       this._insertQuote(null, input);
-               }, this));
-               
-               // close dialog
-               this._dialog.wcfDialog('close');
-       },
-       
-       /**
-        * Inserts a quote.
-        * 
-        * @param       object          event
-        * @param       object          inputElement
-        */
-       _insertQuote: function(event, inputElement) {
-               if (event !== null && this._editorElementAlternative === null) {
-                       var $api = $('.jsQuickReply:eq(0)').data('__api');
-                       if ($api && !$api.getContainer().is(':visible')) {
-                               this._insertQuotes = false;
-                               $api.click(null);
-                       }
-               }
-               
-               var $listItem = (event === null) ? $(inputElement).parents('li') : $(event.currentTarget).parents('li');
-               var $quote = $.trim($listItem.children('div.jsFullQuote').text());
-               var $message = $listItem.parents('article.message');
-               
-               // build quote tag
-               $quote = "[quote='" + $message.attr('data-username') + "','" + $message.data('link') + "']" + $quote + "[/quote]";
-               
-               // insert into editor
-               if ($.browser.redactor) {
-                       if (this._editorElementAlternative === null) {
-                               this._editorElement.redactor('insertDynamic', $quote);
-                       }
-                       else {
-                               this._editorElementAlternative.redactor('insertDynamic', $quote);
-                       }
-               }
-               else {
-                       // plain textarea
-                       var $textarea = (this._editorElementAlternative === null) ? this._editorElement : this._editorElementAlternative;
-                       var $value = $textarea.val();
-                       $quote += "\n\n";
-                       if ($value.length == 0) {
-                               $textarea.val($quote);
-                       }
-                       else {
-                               var $position = $textarea.getCaret();
-                               $textarea.val( $value.substr(0, $position) + $quote + $value.substr($position) );
-                       }
-               }
-               
-               // remove quote upon submit or upon request
-               this._removeOnSubmit.push($listItem.attr('data-quote-id'));
-               
-               // close dialog
-               if (event !== null) {
-                       this._dialog.wcfDialog('close');
-               }
-       },
-       
-       /**
-        * Removes selected quotes.
-        */
-       _removeSelected: function() {
-               if (!this._dialog.find('input.jsCheckbox:checked').length) {
-                       this._dialog.find('input.jsCheckbox').prop('checked', 'checked');
-               }
-               
-               var $quoteIDs = [ ];
-               this._dialog.find('input.jsCheckbox:checked').each(function(index, input) {
-                       $quoteIDs.push($(input).parents('li').attr('data-quote-id'));
-               });
-               
-               if ($quoteIDs.length) {
-                       // get object types
-                       var $objectTypes = [ ];
-                       for (var $objectType in this._handlers) {
-                               $objectTypes.push($objectType);
-                       }
-                       
-                       this._proxy.setOption('data', {
-                               actionName: 'remove',
-                               getFullQuoteObjectIDs: this._handlers.length > 0,
-                               objectTypes: $objectTypes,
-                               quoteIDs: $quoteIDs
-                       });
-                       this._proxy.sendRequest();
-                       
-                       this._dialog.wcfDialog('close');
-               }
-       },
-       
-       /**
-        * Appends list of quote ids to remove after successful submit.
-        */
-       _submit: function() {
-               if (this._supportPaste && this._removeOnSubmit.length > 0) {
-                       var $formSubmit = this._form.find('.formSubmit');
-                       for (var $i in this._removeOnSubmit) {
-                               $('<input type="hidden" name="__removeQuoteIDs[]" value="' + this._removeOnSubmit[$i] + '" />').appendTo($formSubmit);
-                       }
-               }
-       },
-       
-       /**
-        * Returns a list of quote ids marked for removal.
-        * 
-        * @return      array<integer>
-        */
-       getQuotesMarkedForRemoval: function() {
-               return this._removeOnSubmit;
-       },
-       
-       /**
-        * Marks quote ids for removal.
-        */
-       markQuotesForRemoval: function() {
-               if (this._removeOnSubmit.length) {
-                       this._proxy.setOption('data', {
-                               actionName: 'markForRemoval',
-                               quoteIDs: this._removeOnSubmit
-                       });
-                       this._proxy.suppressErrors();
-                       this._proxy.sendRequest();
-               }
-       },
-       
-       /**
-        * Removes all marked quote ids.
-        */
-       removeMarkedQuotes: function() {
-               if (this._removeOnSubmit.length) {
-                       this._proxy.setOption('data', {
-                               actionName: 'removeMarkedQuotes',
-                               getFullQuoteObjectIDs: this._handlers.length > 0
-                       });
-                       this._proxy.sendRequest();
-               }
-       },
-       
-       /**
-        * Counts stored quotes.
-        */
-       countQuotes: function() {
-               var $objectTypes = [ ];
-               for (var $objectType in this._handlers) {
-                       $objectTypes.push($objectType);
-               }
-               
-               this._proxy.setOption('data', {
-                       actionName: 'count',
-                       getFullQuoteObjectIDs: this._handlers.length > 0,
-                       objectTypes: $objectTypes
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               if (data === null) {
-                       return;
-               }
-               
-               if (data.count !== undefined) {
-                       var $fullQuoteObjectIDs = (data.fullQuoteObjectIDs !== undefined) ? data.fullQuoteObjectIDs : { };
-                       this.updateCount(data.count, $fullQuoteObjectIDs);
-               }
-               
-               if (data.template !== undefined) {
-                       if ($.trim(data.template) == '') {
-                               this.updateCount(0, { });
-                       }
-                       else {
-                               this.renderDialog(data.template);
-                       }
-               }
-       }
-});
-
-/**
- * Namespace for message sharing related classes.
- */
-WCF.Message.Share = { };
-
-/**
- * Displays a dialog overlay for permalinks.
- */
-WCF.Message.Share.Content = Class.extend({
-       /**
-        * list of cached templates
-        * @var object
-        */
-       _cache: { },
-       
-       /**
-        * dialog overlay
-        * @var jQuery
-        */
-       _dialog: null,
-       
-       /**
-        * Initializes the WCF.Message.Share.Content class.
-        */
-       init: function() {
-               this._cache = { };
-               this._dialog = null;
-               
-               this._initLinks();
-               
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.Message.Share.Content', $.proxy(this._initLinks, this));
-       },
-       
-       /**
-        * Initializes share links.
-        */
-       _initLinks: function() {
-               $('a.jsButtonShare').removeClass('jsButtonShare').click($.proxy(this._click, this));
-       },
-       
-       /**
-        * Displays links to share this content.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               event.preventDefault();
-               
-               var $target = $(event.currentTarget);
-               var $link = $target.prop('href');
-               var $title = ($target.data('linkTitle') ? $target.data('linkTitle') : $link);
-               var $key = $link.hashCode();
-               if (this._cache[$key] === undefined) {
-                       // remove dialog contents
-                       var $dialogInitialized = false;
-                       if (this._dialog === null) {
-                               this._dialog = $('<div />').hide().appendTo(document.body);
-                               $dialogInitialized = true;
-                       }
-                       else {
-                               this._dialog.empty();
-                       }
-                       
-                       // permalink (plain text)
-                       var $fieldset = $('<fieldset><legend><label for="__sharePermalink">' + WCF.Language.get('wcf.message.share.permalink') + '</label></legend></fieldset>').appendTo(this._dialog);
-                       $('<input type="text" id="__sharePermalink" class="long" readonly="readonly" />').attr('value', $link).appendTo($fieldset);
-                       
-                       // permalink (BBCode)
-                       var $fieldset = $('<fieldset><legend><label for="__sharePermalinkBBCode">' + WCF.Language.get('wcf.message.share.permalink.bbcode') + '</label></legend></fieldset>').appendTo(this._dialog);
-                       $('<input type="text" id="__sharePermalinkBBCode" class="long" readonly="readonly" />').attr('value', '[url=\'' + $link + '\']' + $title + '[/url]').appendTo($fieldset);
-                       
-                       // permalink (HTML)
-                       var $fieldset = $('<fieldset><legend><label for="__sharePermalinkHTML">' + WCF.Language.get('wcf.message.share.permalink.html') + '</label></legend></fieldset>').appendTo(this._dialog);
-                       $('<input type="text" id="__sharePermalinkHTML" class="long" readonly="readonly" />').attr('value', '<a href="' + $link + '">' + WCF.String.escapeHTML($title) + '</a>').appendTo($fieldset);
-                       
-                       this._cache[$key] = this._dialog.html();
-                       
-                       if ($dialogInitialized) {
-                               this._dialog.wcfDialog({
-                                       title: WCF.Language.get('wcf.message.share')
-                               });
-                       }
-                       else {
-                               this._dialog.wcfDialog('open');
-                       }
-               }
-               else {
-                       this._dialog.html(this._cache[$key]).wcfDialog('open');
-               }
-               
-               this._enableSelection();
-       },
-       
-       /**
-        * Enables text selection.
-        */
-       _enableSelection: function() {
-               var $inputElements = this._dialog.find('input').click(function() { $(this).select(); });
-               
-               // Safari on iOS can only select the text if it is not readonly and setSelectionRange() is used
-               if (navigator.userAgent.match(/iP(ad|hone|od)/)) {
-                       $inputElements.keydown(function() { return false; }).removeAttr('readonly').click(function() { this.setSelectionRange(0, 9999); });
-               }
-       }
-});
-
-/**
- * Provides buttons to share a page through multiple social community sites.
- * 
- * @param      boolean         fetchObjectCount
- */
-WCF.Message.Share.Page = Class.extend({
-       /**
-        * list of share buttons
-        * @var object
-        */
-       _ui: { },
-       
-       /**
-        * page description
-        * @var string
-        */
-       _pageDescription: '',
-       
-       /**
-        * canonical page URL
-        * @var string
-        */
-       _pageURL: '',
-       
-       /**
-        * Initializes the WCF.Message.Share.Page class.
-        * 
-        * @param       boolean         fetchObjectCount
-        */
-       init: function(fetchObjectCount) {
-               this._pageDescription = encodeURIComponent($('meta[property="og:title"]').prop('content'));
-               this._pageURL = encodeURIComponent($('meta[property="og:url"]').prop('content'));
-               
-               var $container = $('.messageShareButtons');
-               this._ui = {
-                       facebook: $container.find('.jsShareFacebook'),
-                       google: $container.find('.jsShareGoogle'),
-                       reddit: $container.find('.jsShareReddit'),
-                       twitter: $container.find('.jsShareTwitter')
-               };
-               
-               this._ui.facebook.children('a').click($.proxy(this._shareFacebook, this));
-               this._ui.google.children('a').click($.proxy(this._shareGoogle, this));
-               this._ui.reddit.children('a').click($.proxy(this._shareReddit, this));
-               this._ui.twitter.children('a').click($.proxy(this._shareTwitter, this));
-               
-               if (fetchObjectCount === true) {
-                       this._fetchFacebook();
-                       this._fetchTwitter();
-                       this._fetchReddit();
-               }
-       },
-       
-       /**
-        * Shares current page to selected social community site.
-        * 
-        * @param       string          objectName
-        * @param       string          url
-        * @param       boolean         appendURL
-        */
-       _share: function(objectName, url, appendURL) {
-               window.open(url.replace(/{pageURL}/, this._pageURL).replace(/{text}/, this._pageDescription + (appendURL ? " " + this._pageURL : "")), 'height=600,width=600');
-       },
-       
-       /**
-        * Shares current page with Facebook.
-        */
-       _shareFacebook: function() {
-               this._share('facebook', 'https://www.facebook.com/sharer.php?u={pageURL}&t={text}', true);
-       },
-       
-       /**
-        * Shares current page with Google Plus.
-        */
-       _shareGoogle: function() {
-               this._share('google', 'https://plus.google.com/share?url={pageURL}', true);
-       },
-       
-       /**
-        * Shares current page with Reddit.
-        */
-       _shareReddit: function() {
-               this._share('reddit', 'https://ssl.reddit.com/submit?url={pageURL}', true);
-       },
-       
-       /**
-        * Shares current page with Twitter.
-        */
-       _shareTwitter: function() {
-               this._share('twitter', 'https://twitter.com/share?url={pageURL}&text={text}', false);
-       },
-       
-       /**
-        * Fetches share count from a social community site.
-        * 
-        * @param       string          url
-        * @param       object          callback
-        * @param       string          callbackName
-        */
-       _fetchCount: function(url, callback, callbackName) {
-               var $options = {
-                       autoSend: true,
-                       dataType: 'jsonp',
-                       showLoadingOverlay: false,
-                       success: callback,
-                       suppressErrors: true,
-                       type: 'GET',
-                       url: url.replace(/{pageURL}/, this._pageURL)
-               };
-               if (callbackName) {
-                       $options.jsonp = callbackName;
-               }
-               
-               new WCF.Action.Proxy($options);
-       },
-       
-       /**
-        * Fetches number of Facebook likes.
-        */
-       _fetchFacebook: function() {
-               this._fetchCount('https://graph.facebook.com/?id={pageURL}', $.proxy(function(data) {
-                       if (data.shares) {
-                               this._ui.facebook.children('span.badge').show().text(data.shares);
-                       }
-               }, this));
-       },
-       
-       /**
-        * Fetches tweet count from Twitter.
-        */
-       _fetchTwitter: function() {
-               if (window.location.protocol.match(/^https/)) return;
-               
-               this._fetchCount('http://urls.api.twitter.com/1/urls/count.json?url={pageURL}', $.proxy(function(data) {
-                       if (data.count) {
-                               this._ui.twitter.children('span.badge').show().text(data.count);
-                       }
-               }, this));
-       },
-       
-       /**
-        * Fetches cumulative vote sum from Reddit.
-        */
-       _fetchReddit: function() {
-               if (window.location.protocol.match(/^https/)) return;
-               
-               this._fetchCount('http://www.reddit.com/api/info.json?url={pageURL}', $.proxy(function(data) {
-                       if (data.data.children.length) {
-                               this._ui.reddit.children('span.badge').show().text(data.data.children[0].data.score);
-                       }
-               }, this), 'jsonp');
-       }
-});
-
-/**
- * Handles user mention suggestions in Redactor instances.
- * 
- * Important: Objects of this class have to be created before the CKEditor
- * is initialized!
- */
-WCF.Message.UserMention = Class.extend({
-       /**
-        * current caret position
-        * @var DOMRange
-        */
-       _caretPosition: null,
-       
-       /**
-        * name of the class used to get the user suggestions
-        * @var string
-        */
-       _className: 'wcf\\data\\user\\UserAction',
-       
-       /**
-        * dropdown object
-        * @var jQuery
-        */
-       _dropdown: null,
-       
-       /**
-        * dropdown menu object
-        * @var jQuery
-        */
-       _dropdownMenu: null,
-       
-       /**
-        * suggestion item index, -1 if none is selected
-        * @var integer
-        */
-       _itemIndex: -1,
-       
-       /**
-        * line height
-        * @var integer
-        */
-       _lineHeight: null,
-       
-       /**
-        * current beginning of the mentioning
-        * @var string
-        */
-       _mentionStart: '',
-       
-       /**
-        * redactor instance object
-        * @var $.Redactor
-        */
-       _redactor: null,
-       
-       /**
-        * Initalizes user suggestions for the CKEditor with the given textarea id.
-        * 
-        * @param       string          wysiwygSelector
-        */
-       init: function(wysiwygSelector) {
-               this._textarea = $('#' + wysiwygSelector);
-               this._redactor = this._textarea.redactor('getObject');
-               
-               this._redactor.setOption('keyupCallback', $.proxy(this._keyup, this));
-               this._redactor.setOption('wkeydownCallback', $.proxy(this._keydown, this));
-               
-               this._dropdown = this._textarea.redactor('getEditor');
-               this._dropdownMenu = $('<ul class="dropdownMenu userSuggestionList" />').appendTo(this._textarea.parent());
-               WCF.Dropdown.initDropdownFragment(this._dropdown, this._dropdownMenu);
-               
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-       },
-       
-       /**
-        * Clears the suggestion list.
-        */
-       _clearList: function() {
-               this._hideList();
-               
-               this._dropdownMenu.empty();
-       },
-       
-       /**
-        * Handles a click on a list item suggesting a username.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               // restore caret position
-               this._redactor.replaceRangesWith(this._caretPosition);
-               
-               this._setUsername($(event.currentTarget).data('username'));
-       },
-       
-       /**
-        * Creates an item in the suggestion list with the given data.
-        * 
-        * @return      object
-        */
-       _createListItem: function(listItemData) {
-               var $listItem = $('<li />').data('username', listItemData.label).click($.proxy(this._click, this)).appendTo(this._dropdownMenu);
-               
-               var $box16 = $('<div />').addClass('box16').appendTo($listItem);
-               $box16.append($(listItemData.icon).addClass('framed'));
-               $box16.append($('<div />').append($('<span />').text(listItemData.label)));
-       },
-       
-       /**
-        * Returns the offsets used to set the position of the user suggestion
-        * dropdown.
-        * 
-        * @return      object
-        */
-       _getDropdownMenuPosition: function() {
-               var $orgRange = getSelection().getRangeAt(0).cloneRange();
-               
-               // mark the entire text, starting from the '@' to the current cursor position
-               var $newRange = document.createRange();
-               $newRange.setStart($orgRange.startContainer, $orgRange.startOffset - (this._mentionStart.length + 1));
-               $newRange.setEnd($orgRange.startContainer, $orgRange.startOffset);
-               
-               this._redactor.replaceRangesWith($newRange);
-               
-               // get the offsets of the bounding box of current text selection
-               var $range = getSelection().getRangeAt(0);
-               var $rect = $range.getBoundingClientRect();
-               var $window = $(window);
-               var $offsets = {
-                       top: Math.round($rect.bottom) + $window.scrollTop(),
-                       left: Math.round($rect.left) + $window.scrollLeft()
-               };
-               
-               if (this._lineHeight === null) {
-                       this._lineHeight = Math.round($rect.bottom - $rect.top);
-               }
-               
-               // restore caret position
-               this._redactor.replaceRangesWith($orgRange);
-               this._caretPosition = $orgRange;
-               
-               return $offsets;
-       },
-       
-       /**
-        * Replaces the started mentioning with a chosen username.
-        */
-       _setUsername: function(username) {
-               var $orgRange = getSelection().getRangeAt(0).cloneRange();
-               
-               // allow redactor to undo this
-               this._redactor.bufferSet();
-               
-               var $newRange = document.createRange();
-               $newRange.setStart($orgRange.startContainer, $orgRange.startOffset - (this._mentionStart.length + 1));
-               $newRange.setEnd($orgRange.startContainer, $orgRange.startOffset);
-               
-               this._redactor.replaceRangesWith($newRange);
-               
-               var $range = getSelection().getRangeAt(0);
-               $range.deleteContents();
-               $range.collapse(true);
-               
-               // insert username
-               if (username.indexOf("'") !== -1) {
-                       username = username.replace(/'/g, "''");
-                       username = "'" + username + "'";
-               }
-               else if (username.indexOf(' ') !== -1) {
-                       username = "'" + username + "'";
-               }
-               
-               // use native API to prevent issues in Internet Explorer
-               var $text = document.createTextNode('@' + username);
-               $range.insertNode($text);
-               
-               var $newRange = document.createRange();
-               $newRange.setStart($text, username.length + 1);
-               $newRange.setEnd($text, username.length + 1);
-               
-               this._redactor.replaceRangesWith($newRange);
-               
-               this._hideList();
-       },
-       
-       /**
-        * Returns the parameters for the AJAX request.
-        * 
-        * @return      object
-        */
-       _getParameters: function() {
-               return {
-                       data: {
-                               includeUserGroups: false,
-                               searchString: this._mentionStart
-                       }
-               };
-       },
-       
-       /**
-        * Returns the relevant text in front of the caret in the current line.
-        * 
-        * @return      string
-        */
-       _getTextLineInFrontOfCaret: function() {
-               // if text is marked, user suggestions are disabled
-               if (this._redactor.getSelectionHtml().length) {
-                       return '';
-               }
-               
-               var $range = this._redactor.getSelection().getRangeAt(0);
-               var $text = $range.startContainer.textContent.substr(0, $range.startOffset);
-               
-               // remove unicode zero width space and non-breaking space
-               var $textBackup = $text;
-               $text = '';
-               for (var $i = 0; $i < $textBackup.length; $i++) {
-                       var $byte = $textBackup.charCodeAt($i).toString(16);
-                       if ($byte != '200b' && !/\s/.test($textBackup[$i])) {
-                               if ($textBackup[$i] === '@' && $i && /\s/.test($textBackup[$i - 1])) {
-                                       $text = '';
-                               }
-                               
-                               $text += $textBackup[$i];
-                       }
-                       else {
-                               $text = '';
-                       }
-               }
-               
-               return $text;
-       },
-       
-       /**
-        * Hides the suggestion list.
-        */
-       _hideList: function() {
-               this._dropdown.removeClass('dropdownOpen');
-               this._dropdownMenu.removeClass('dropdownOpen');
-               
-               this._itemIndex = -1;
-       },
-       
-       /**
-        * Handles the keydown event to check if the user starts mentioning someone.
-        * 
-        * @param       object          event
-        */
-       _keydown: function(event) {
-               if (this._redactor.inPlainMode()) {
-                       return true;
-               }
-               
-               if (this._dropdownMenu.is(':visible')) {
-                       switch (event.which) {
-                               case $.ui.keyCode.ENTER:
-                                       event.preventDefault();
-                                       
-                                       this._dropdownMenu.children('li').eq(this._itemIndex).trigger('click');
-                                       
-                                       return false;
-                               break;
-                               
-                               case $.ui.keyCode.UP:
-                                       event.preventDefault();
-                                       
-                                       this._selectItem(this._itemIndex - 1);
-                                       
-                                       return false;
-                               break;
-                               
-                               case $.ui.keyCode.DOWN:
-                                       event.preventDefault();
-                                       
-                                       this._selectItem(this._itemIndex + 1);
-                                       
-                                       return false;
-                               break;
-                       }
-               }
-               
-               return true;
-       },
-       
-       /**
-        * Handles the keyup event to check if the user starts mentioning someone.
-        * 
-        * @param       object          event
-        */
-       _keyup: function(event) {
-               if (this._redactor.inPlainMode()) {
-                       return true;
-               }
-               
-               // ignore enter key up event
-               if (event.which === $.ui.keyCode.ENTER) {
-                       return;
-               }
-               
-               // ignore event if suggestion list and user pressed enter, arrow up or arrow down
-               if (this._dropdownMenu.is(':visible') && event.which in { 13:1, 38:1, 40:1 }) {
-                       return;
-               }
-               
-               var $currentText = this._getTextLineInFrontOfCaret();
-               if ($currentText) {
-                       var $match = $currentText.match(/@([^,]{3,})$/);
-                       if ($match) {
-                               // if mentioning is at text begin or there's a whitespace character
-                               // before the '@', everything is fine
-                               if (!$match.index || $currentText[$match.index - 1].match(/\s/)) {
-                                       this._mentionStart = $match[1];
-                                       
-                                       this._proxy.setOption('data', {
-                                               actionName: 'getSearchResultList',
-                                               className: this._className,
-                                               interfaceName: 'wcf\\data\\ISearchAction',
-                                               parameters: this._getParameters()
-                                       });
-                                       this._proxy.sendRequest();
-                               }
-                       }
-                       else {
-                               this._hideList();
-                       }
-               }
-               else {
-                       this._hideList();
-               }
-       },
-       
-       /**
-        * Selects the suggestion with the given item index.
-        * 
-        * @param       integer         itemIndex
-        */
-       _selectItem: function(itemIndex) {
-               var $li = this._dropdownMenu.children('li');
-               
-               if (itemIndex < 0) {
-                       itemIndex = $li.length - 1;
-               }
-               else if (itemIndex + 1 > $li.length) {
-                       itemIndex = 0;
-               }
-               
-               $li.removeClass('dropdownNavigationItem');
-               $li.eq(itemIndex).addClass('dropdownNavigationItem');
-               
-               this._itemIndex = itemIndex;
-       },
-       
-       /**
-        * Shows the suggestion list.
-        */
-       _showList: function() {
-               this._dropdown.addClass('dropdownOpen');
-               this._dropdownMenu.addClass('dropdownOpen');
-       },
-       
-       /**
-        * Evalutes user suggestion-AJAX request results.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               this._clearList(false);
-               
-               if ($.getLength(data.returnValues)) {
-                       for (var $i in data.returnValues) {
-                               var $item = data.returnValues[$i];
-                               this._createListItem($item);
-                       }
-                       
-                       this._updateSuggestionListPosition();
-                       this._showList();
-               }
-       },
-       
-       /**
-        * Updates the position of the suggestion list.
-        */
-       _updateSuggestionListPosition: function() {
-               try {
-                       var $dropdownMenuPosition = this._getDropdownMenuPosition();
-                       $dropdownMenuPosition.top += 5; // add a little vertical gap
-                       
-                       this._dropdownMenu.css($dropdownMenuPosition);
-                       this._selectItem(0);
-                       
-                       if ($dropdownMenuPosition.top + this._dropdownMenu.outerHeight() + 10 > $(window).height() + $(document).scrollTop()) {
-                               this._dropdownMenu.addClass('dropdownArrowBottom');
-                               
-                               this._dropdownMenu.css({
-                                       top: $dropdownMenuPosition.top - this._dropdownMenu.outerHeight() - 2 * this._lineHeight + 5
-                               });
-                       }
-                       else {
-                               this._dropdownMenu.removeClass('dropdownArrowBottom');
-                       }
-               }
-               catch (e) {
-                       // ignore errors that are caused by pressing enter to
-                       // often in a short period of time
-               }
-       }
-});
-
-
-// WCF.Moderation.js
-/**
- * Namespace for moderation related classes.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2014 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- */
-WCF.Moderation = { };
-
-/**
- * Moderation queue management.
- * 
- * @param      integer         queueID
- * @param      string          redirectURL
- */
-WCF.Moderation.Management = Class.extend({
-       /**
-        * button selector
-        * @var string
-        */
-       _buttonSelector: '',
-       
-       /**
-        * action class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * list of templates for confirmation message by action name
-        * @var object
-        */
-       _confirmationTemplate: { },
-       
-       /**
-        * language item pattern
-        * @var string
-        */
-       _languageItem: '',
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * queue id
-        * @var integer
-        */
-       _queueID: 0,
-       
-       /**
-        * redirect URL
-        * @var string
-        */
-       _redirectURL: '',
-       
-       /**
-        * Initializes the moderation report management.
-        * 
-        * @param       integer         queueID
-        * @param       string          redirectURL
-        * @param       string          languageItem
-        */
-       init: function(queueID, redirectURL, languageItem) {
-               if (!this._buttonSelector) {
-                       console.debug("[WCF.Moderation.Management] Missing button selector, aborting.");
-                       return;
-               }
-               else if (!this._className) {
-                       console.debug("[WCF.Moderation.Management] Missing class name, aborting.");
-                       return;
-               }
-               
-               this._queueID = queueID;
-               this._redirectURL = redirectURL;
-               this._languageItem = languageItem;
-               
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               $(this._buttonSelector).click($.proxy(this._click, this));
-       },
-       
-       /**
-        * Handles clicks on the action buttons.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               var $actionName = $(event.currentTarget).wcfIdentify();
-               var $innerTemplate = '';
-               if (this._confirmationTemplate[$actionName]) {
-                       $innerTemplate = this._confirmationTemplate[$actionName];
-               }
-               
-               WCF.System.Confirmation.show(WCF.Language.get(this._languageItem.replace(/{actionName}/, $actionName)), $.proxy(function(action) {
-                       if (action === 'confirm') {
-                               var $parameters = {
-                                       actionName: $actionName,
-                                       className: this._className,
-                                       objectIDs: [ this._queueID ]
-                               };
-                               if (this._confirmationTemplate[$actionName]) {
-                                       $parameters.parameters = { };
-                                       $innerTemplate.find('input, textarea').each(function(index, element) {
-                                               var $element = $(element);
-                                               var $value = $element.val();
-                                               if ($element.getTagName() === 'input' && $element.attr('type') === 'checkbox') {
-                                                       if (!$element.is(':checked')) {
-                                                               $value = null;
-                                                       }
-                                               }
-                                               
-                                               if ($value !== null) {
-                                                       $parameters.parameters[$element.attr('name')] = $value;
-                                               }
-                                       });
-                               }
-                               
-                               this._proxy.setOption('data', $parameters);
-                               this._proxy.sendRequest();
-                               
-                               $(this._buttonSelector).disable();
-                       }
-               }, this), { }, $innerTemplate);
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               var $notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success'));
-               var self = this;
-               $notification.show(function() {
-                       window.location = self._redirectURL;
-               });
-       }
-});
-
-/**
- * Namespace for activation related classes.
- */
-WCF.Moderation.Activation = { };
-
-/**
- * Manages disabled content within moderation.
- * 
- * @see        WCF.Moderation.Management
- */
-WCF.Moderation.Activation.Management = WCF.Moderation.Management.extend({
-       /**
-        * @see WCF.Moderation.Management.init()
-        */
-       init: function(queueID, redirectURL) {
-               this._buttonSelector = '#enableContent, #removeContent';
-               this._className = 'wcf\\data\\moderation\\queue\\ModerationQueueActivationAction';
-               
-               this._super(queueID, redirectURL, 'wcf.moderation.activation.{actionName}.confirmMessage');
-       }
-});
-
-/**
- * Namespace for report related classes.
- */
-WCF.Moderation.Report = { };
-
-/**
- * Handles content report.
- * 
- * @param      string          objectType
- * @param      string          buttonSelector
- */
-WCF.Moderation.Report.Content = Class.extend({
-       /**
-        * list of buttons
-        * @var object
-        */
-       _buttons: { },
-       
-       /**
-        * button selector
-        * @var string
-        */
-       _buttonSelector: '',
-       
-       /**
-        * dialog overlay
-        * @var jQuery
-        */
-       _dialog: null,
-       
-       /**
-        * notification object
-        * @var WCF.System.Notification
-        */
-       _notification: null,
-       
-       /**
-        * object id
-        * @var integer
-        */
-       _objectID: 0,
-       
-       /**
-        * object type name
-        * @var string
-        */
-       _objectType: '',
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * Creates a new WCF.Moderation.Report object.
-        * 
-        * @param       string          objectType
-        * @param       string          buttonSelector
-        */
-       init: function(objectType, buttonSelector) {
-               this._objectType = objectType;
-               this._buttonSelector = buttonSelector;
-               
-               this._buttons = { };
-               this._notification = null;
-               this._objectID = 0;
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               this._initButtons();
-               
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.Moderation.Report' + this._objectType.hashCode(), $.proxy(this._initButtons, this));
-       },
-       
-       /**
-        * Initializes the report feature for all matching buttons.
-        */
-       _initButtons: function() {
-               var self = this;
-               $(this._buttonSelector).each(function(index, button) {
-                       var $button = $(button);
-                       var $buttonID = $button.wcfIdentify();
-                       
-                       if (!self._buttons[$buttonID]) {
-                               self._buttons[$buttonID] = $button;
-                               $button.click($.proxy(self._click, self));
-                       }
-               });
-       },
-       
-       /**
-        * Handles clicks on a report button.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               this._objectID = $(event.currentTarget).data('objectID');
-               
-               this._proxy.setOption('data', {
-                       actionName: 'prepareReport',
-                       className: 'wcf\\data\\moderation\\queue\\ModerationQueueReportAction',
-                       parameters: {
-                               objectID: this._objectID,
-                               objectType: this._objectType
-                       }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               // object has been successfully reported
-               if (data.returnValues.reported) {
-                       if (this._notification === null) {
-                               this._notification = new WCF.System.Notification(WCF.Language.get('wcf.moderation.report.success'));
-                       }
-                       
-                       // show success and close dialog
-                       this._dialog.wcfDialog('close');
-                       this._notification.show();
-               }
-               else if (data.returnValues.template) {
-                       // display template
-                       this._showDialog(data.returnValues.template);
-                       
-                       if (!data.returnValues.alreadyReported) {
-                               // bind event listener for buttons
-                               this._dialog.find('.jsSubmitReport').click($.proxy(this._submit, this));
-                       }
-               }
-       },
-       
-       /**
-        * Displays the dialog overlay.
-        * 
-        * @param       string          template
-        */
-       _showDialog: function(template) {
-               if (this._dialog === null) {
-                       this._dialog = $('#moderationReport');
-                       if (!this._dialog.length) {
-                               this._dialog = $('<div id="moderationReport" />').hide().appendTo(document.body);
-                       }
-               }
-               
-               this._dialog.html(template).wcfDialog({
-                       title: WCF.Language.get('wcf.moderation.report.reportContent')
-               }).wcfDialog('render');
-       },
-       
-       /**
-        * Submits a report unless the textarea is empty.
-        */
-       _submit: function() {
-               var $text = this._dialog.find('.jsReportMessage').val();
-               if ($text == '') {
-                       this._dialog.find('fieldset > dl').addClass('formError');
-                       
-                       if (!this._dialog.find('.innerError').length) {
-                               this._dialog.find('.jsReportMessage').after($('<small class="innerError">' + WCF.Language.get('wcf.global.form.error.empty') + "</small>"));;
-                       }
-                       
-                       return;
-               }
-               
-               this._proxy.setOption('data', {
-                       actionName: 'report',
-                       className: 'wcf\\data\\moderation\\queue\\ModerationQueueReportAction',
-                       parameters: {
-                               message: $text,
-                               objectID: this._objectID,
-                               objectType: this._objectType
-                       }
-               });
-               this._proxy.sendRequest();
-       }
-});
-
-/**
- * Manages reported content within moderation.
- * 
- * @see        WCF.Moderation.Management
- */
-WCF.Moderation.Report.Management = WCF.Moderation.Management.extend({
-       /**
-        * @see WCF.Moderation.Management.init()
-        */
-       init: function(queueID, redirectURL) {
-               this._buttonSelector = '#removeContent, #removeReport';
-               this._className = 'wcf\\data\\moderation\\queue\\ModerationQueueReportAction';
-               
-               this._super(queueID, redirectURL, 'wcf.moderation.report.{actionName}.confirmMessage');
-               
-               this._confirmationTemplate.removeContent = $('<fieldset><dl><dt><label for="message">' + WCF.Language.get('wcf.moderation.report.removeContent.reason') + '</label></dt><dd><textarea name="message" id="message" cols="40" rows="3" /></dd></dl></fieldset>');
-       }
-});
-
-/**
- * Provides a dropdown for user panel.
- * 
- * @see        WCF.UserPanel
- */
-WCF.Moderation.UserPanel = WCF.UserPanel.extend({
-       /**
-        * link to show all outstanding queues
-        * @var string
-        */
-       _showAllLink: '',
-       
-       /**
-        * link to deleted content list
-        * @var string
-        */
-       _deletedContentLink: '',
-       
-       /**
-        * @see WCF.UserPanel.init()
-        */
-       init: function(showAllLink, deletedContentLink) {
-               this._noItems = 'wcf.moderation.noMoreItems';
-               this._showAllLink = showAllLink;
-               this._deletedContentLink = deletedContentLink;
-               
-               this._super('outstandingModeration');
-       },
-       
-       /**
-        * @see WCF.UserPanel._addDefaultItems()
-        */
-       _addDefaultItems: function(dropdownMenu) {
-               this._addDivider(dropdownMenu);
-               $('<li><a href="' + this._showAllLink + '">' + WCF.Language.get('wcf.moderation.showAll') + '</a></li>').appendTo(dropdownMenu);
-               this._addDivider(dropdownMenu);
-               $('<li><a href="' + this._deletedContentLink + '">' + WCF.Language.get('wcf.moderation.showDeletedContent') + '</a></li>').appendTo(dropdownMenu);
-       },
-       
-       /**
-        * @see WCF.UserPanel._getParameters()
-        */
-       _getParameters: function() {
-               return {
-                       actionName: 'getOutstandingQueues',
-                       className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction'
-               };
-       }
-});
-
-
-// WCF.Poll.js
-/**
- * Namespace for poll-related classes.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2014 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- */
-WCF.Poll = { };
-
-/**
- * Handles poll option management.
- * 
- * @param      string          containerID
- * @param      array<object>   optionList
- */
-WCF.Poll.Management = Class.extend({
-       /**
-        * container object
-        * @var jQuery
-        */
-       _container: null,
-       
-       /**
-        * number of options
-        * @var integer
-        */
-       _count: 0,
-       
-       /**
-        * width for input-elements
-        * @var integer
-        */
-       _inputSize: 0,
-       
-       /**
-        * maximum allowed number of options
-        * @var integer
-        */
-       _maxOptions: 0,
-       
-       /**
-        * Initializes the WCF.Poll.Management class.
-        * 
-        * @param       string          containerID
-        * @param       array<object>   optionList
-        * @param       integer         maxOptions
-        */
-       init: function(containerID, optionList, maxOptions) {
-               this._count = 0;
-               this._maxOptions = maxOptions || -1;
-               this._container = $('#' + containerID).children('ol:eq(0)');
-               if (!this._container.length) {
-                       console.debug("[WCF.Poll.Management] Invalid container id given, aborting.");
-                       return;
-               }
-               
-               optionList = optionList || [ ];
-               this._createOptionList(optionList);
-               
-               // bind event listener
-               $(window).resize($.proxy(this._resize, this));
-               this._container.parents('form').submit($.proxy(this._submit, this));
-               
-               // init sorting
-               new WCF.Sortable.List(containerID, '', undefined, undefined, true);
-               
-               // trigger resize event for field length calculation
-               this._resize();
-               
-               // update size on tab select
-               var $tabMenuContent = this._container.parents('.tabMenuContent:eq(0)');
-               var $tabMenuContentID = $tabMenuContent.wcfIdentify();
-               var self = this;
-               $tabMenuContent.parents('.tabMenuContainer:eq(0)').on('wcftabsactivate', function(event, ui) {
-                       if (ui.newPanel.wcfIdentify() == $tabMenuContentID) {
-                               self._resize();
-                       }
-               });
-       },
-       
-       /**
-        * Creates the option list on init.
-        * 
-        * @param       array<object>           optionList
-        */
-       _createOptionList: function(optionList) {
-               for (var $i = 0, $length = optionList.length; $i < $length; $i++) {
-                       var $option = optionList[$i];
-                       this._createOption($option.optionValue, $option.optionID);
-               }
-               
-               // add empty option
-               this._createOption();
-       },
-       
-       /**
-        * Creates a new option element.
-        * 
-        * @param       string          optionValue
-        * @param       integer         optionID
-        * @param       jQuery          insertAfter
-        */
-       _createOption: function(optionValue, optionID, insertAfter) {
-               optionValue = optionValue || '';
-               optionID = parseInt(optionID) || 0;
-               insertAfter = insertAfter || null;
-               
-               var $listItem = $('<li class="sortableNode" />').data('optionID', optionID);
-               if (insertAfter === null) {
-                       $listItem.appendTo(this._container);
-               }
-               else {
-                       $listItem.insertAfter(insertAfter);
-               }
-               
-               // insert buttons
-               var $buttonContainer = $('<span class="sortableButtonContainer" />').appendTo($listItem);
-               $('<span class="icon icon16 icon-plus jsTooltip jsAddOption pointer" title="' + WCF.Language.get('wcf.poll.button.addOption') + '" />').click($.proxy(this._addOption, this)).appendTo($buttonContainer);
-               $('<span class="icon icon16 icon-remove jsTooltip jsDeleteOption pointer" title="' + WCF.Language.get('wcf.poll.button.removeOption') + '" />').click($.proxy(this._removeOption, this)).appendTo($buttonContainer);
-               
-               // insert input field
-               var $input = $('<input type="text" value="' + optionValue + '" maxlength="255" />').css({ width: this._inputSize + "px" }).keydown($.proxy(this._keyDown, this)).appendTo($listItem);
-               
-               if (insertAfter !== null) {
-                       $input.focus();
-               }
-               
-               WCF.DOMNodeInsertedHandler.execute();
-               
-               this._count++;
-               if (this._count === this._maxOptions) {
-                       this._container.find('span.jsAddOption').removeClass('pointer').addClass('disabled');
-               }
-       },
-       
-       /**
-        * Handles key down events for option input field.
-        * 
-        * @param       object          event
-        * @return      boolean
-        */
-       _keyDown: function(event) {
-               // ignore every key except for [Enter]
-               if (event.which !== 13) {
-                       return true;
-               }
-               
-               $(event.currentTarget).prev('.sortableButtonContainer').children('.jsAddOption').trigger('click');
-               
-               event.preventDefault();
-               event.stopPropagation();
-               return false;
-       },
-       
-       /**
-        * Adds a new option after current one.
-        * 
-        * @param       object          event
-        */
-       _addOption: function(event) {
-               if (this._count === this._maxOptions) {
-                       return false;
-               }
-               
-               var $listItem = $(event.currentTarget).parents('li');
-               
-               this._createOption(undefined, undefined, $listItem);
-       },
-       
-       /**
-        * Removes an option.
-        * 
-        * @param       object          event
-        */
-       _removeOption: function(event) {
-               $(event.currentTarget).parents('li').remove();
-               
-               this._count--;
-               this._container.find('span.jsAddOption').addClass('pointer').removeClass('disabled');
-               
-               if (this._container.children('li').length == 0) {
-                       this._createOption();
-               }
-       },
-       
-       /**
-        * Handles the 'resize'-event to adjust input-width.
-        */
-       _resize: function() {
-               var $containerWidth = this._container.innerWidth();
-               
-               // select first option to determine dimensions
-               var $listItem = this._container.children('li:eq(0)');
-               var $buttonWidth = $listItem.children('.sortableButtonContainer').outerWidth();
-               var $inputSize = $containerWidth - $buttonWidth;
-               
-               if ($inputSize != this._inputSize) {
-                       this._inputSize = $inputSize;
-                       
-                       // update width of <input /> elements
-                       this._container.find('li > input').css({ width: this._inputSize + 'px' });
-               }
-       },
-       
-       /**
-        * Inserts hidden input elements storing the option values.
-        */
-       _submit: function() {
-               var $options = [ ];
-               this._container.children('li').each(function(index, listItem) {
-                       var $listItem = $(listItem);
-                       var $optionValue = $.trim($listItem.children('input').val());
-                       
-                       // ignore empty values
-                       if ($optionValue != '') {
-                               $options.push({
-                                       optionID: $listItem.data('optionID'),
-                                       optionValue: $optionValue
-                               });
-                       }
-               });
-               
-               // create hidden input fields
-               if ($options.length) {
-                       var $formSubmit = this._container.parents('form').find('.formSubmit');
-                       
-                       for (var $i = 0, $length = $options.length; $i < $length; $i++) {
-                               var $option = $options[$i];
-                               $('<input type="hidden" name="pollOptions[' + $i + ']" />').val($option.optionID + '_' + $option.optionValue).appendTo($formSubmit);
-                       }
-               }
-       }
-});
-
-/**
- * Manages poll voting and result display.
- * 
- * @param      string          containerSelector
- */
-WCF.Poll.Manager = Class.extend({
-       /**
-        * template cache
-        * @var object
-        */
-       _cache: { },
-       
-       /**
-        * list of permissions to view participants
-        * @var object
-        */
-       _canViewParticipants: { },
-       
-       /**
-        * list of permissions to view result
-        * @var object
-        */
-       _canViewResult: { },
-       
-       /**
-        * list of permissions
-        * @var object
-        */
-       _canVote: { },
-       
-       /**
-        * list of input elements per poll
-        * @var object
-        */
-       _inputElements: { },
-       
-       /**
-        * list of participant lists
-        * @var object
-        */
-       _participants: { },
-       
-       /**
-        * list of poll objects
-        * @var object
-        */
-       _polls: { },
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * Intiailizes the poll manager.
-        * 
-        * @param       string          containerSelector
-        */
-       init: function(containerSelector) {
-               var $polls = $(containerSelector);
-               if (!$polls.length) {
-                       console.debug("[WCF.Poll.Manager] Given selector '" + containerSelector + "' does not match, aborting.");
-                       return;
-               }
-               
-               this._cache = { };
-               this._canViewParticipants = { };
-               this._canViewResult = { };
-               this._inputElements = { };
-               this._participants = { };
-               this._polls = { };
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this),
-                       url: 'index.php/Poll/?t=' + SECURITY_TOKEN + SID_ARG_2ND
-               });
-               
-               // init polls
-               var self = this;
-               $polls.each(function(index, poll) {
-                       var $poll = $(poll);
-                       var $pollID = $poll.data('pollID');
-                       
-                       if (self._polls[$pollID] === undefined) {
-                               self._cache[$pollID] = {
-                                       result: '',
-                                       vote: ''
-                               };
-                               self._polls[$pollID] = $poll;
-                               
-                               self._canViewParticipants[$pollID] = ($poll.data('canViewParticipants')) ? true : false;
-                               self._canViewResult[$pollID] = ($poll.data('canViewResult')) ? true : false;
-                               self._canVote[$pollID] = ($poll.data('canVote')) ? true : false;
-                               
-                               self._bindListeners($pollID);
-                               
-                               if ($poll.data('inVote')) {
-                                       self._prepareVote($pollID);
-                               }
-                               
-                               self._toggleButtons($pollID);
-                       }
-               });
-       },
-       
-       /**
-        * Bind event listeners for current poll id.
-        * 
-        * @param       integer         pollID
-        */
-       _bindListeners: function(pollID) {
-               this._polls[pollID].find('.jsButtonPollShowParticipants').data('pollID', pollID).click($.proxy(this._showParticipants, this));
-               this._polls[pollID].find('.jsButtonPollShowResult').data('pollID', pollID).click($.proxy(this._showResult, this));
-               this._polls[pollID].find('.jsButtonPollShowVote').data('pollID', pollID).click($.proxy(this._showVote, this));
-               this._polls[pollID].find('.jsButtonPollVote').data('pollID', pollID).click($.proxy(this._vote, this));
-       },
-       
-       /**
-        * Displays poll result template.
-        * 
-        * @param       object          event
-        * @param       integer         pollID
-        */
-       _showResult: function(event, pollID) {
-               var $pollID = (event === null) ? pollID : $(event.currentTarget).data('pollID');
-               
-               // user cannot see the results yet
-               if (!this._canViewResult[$pollID]) {
-                       return;
-               }
-               
-               // ignore request, we're within results already
-               if (!this._polls[$pollID].data('inVote')) {
-                       return;
-               }
-               
-               if (!this._cache[$pollID].result) {
-                       this._proxy.setOption('data', {
-                               actionName: 'getResult',
-                               pollID: $pollID
-                       });
-                       this._proxy.sendRequest();
-               }
-               else {
-                       // show results from cache
-                       this._polls[$pollID].find('.pollInnerContainer').html(this._cache[$pollID].result);
-                       
-                       // set vote state
-                       this._polls[$pollID].data('inVote', false);
-                       
-                       // toggle buttons
-                       this._toggleButtons($pollID);
-               }
-       },
-       
-       /**
-        * Displays a list of participants.
-        * 
-        * @param       object          event
-        */
-       _showParticipants: function(event) {
-               var $pollID = $(event.currentTarget).data('pollID');
-               if (!this._participants[$pollID]) {
-                       this._participants[$pollID] = new WCF.User.List('wcf\\data\\poll\\PollAction', this._polls[$pollID].data('question'), { pollID: $pollID });
-               }
-               
-               this._participants[$pollID].open();
-       },
-       
-       /**
-        * Displays the vote template.
-        * 
-        * @param       object          event
-        * @param       integer         pollID
-        */
-       _showVote: function(event, pollID) {
-               var $pollID = (event === null) ? pollID : $(event.currentTarget).data('pollID');
-               
-               // user cannot vote (e.g. already voted or guest)
-               if (!this._canVote[$pollID]) {
-                       return;
-               }
-               
-               // ignore request, we're within vote already
-               if (this._polls[$pollID].data('inVote')) {
-                       return;
-               }
-               
-               if (!this._cache[$pollID].vote) {
-                       this._proxy.setOption('data', {
-                               actionName: 'getVote',
-                               pollID: $pollID
-                       });
-                       this._proxy.sendRequest();
-               }
-               else {
-                       // show vote from cache
-                       this._polls[$pollID].find('.pollInnerContainer').html(this._cache[$pollID].vote);
-                       
-                       // set vote state
-                       this._polls[$pollID].data('inVote', true);
-                       
-                       // bind event listener and toggle buttons
-                       this._prepareVote($pollID);
-                       this._toggleButtons($pollID);
-               }
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               if (!data || !data.actionName) {
-                       return;
-               }
-               
-               var $pollID = data.pollID;
-               
-               // updating result template
-               if (data.resultTemplate) {
-                       this._cache[$pollID].result = data.resultTemplate;
-               }
-               
-               // updating vote template
-               if (data.voteTemplate) {
-                       this._cache[$pollID].vote = data.voteTemplate;
-               }
-               
-               switch (data.actionName) {
-                       case 'getResult':
-                               this._showResult(null, $pollID);
-                       break;
-                       
-                       case 'getVote':
-                               this._showVote(null, $pollID);
-                       break;
-                       
-                       case 'vote':
-                               // display results
-                               this._canViewResult[$pollID] = true;
-                               this._canVote[$pollID] = (data.canVote) ? true : false;
-                               this._showResult(null, $pollID);
-                       break;
-               }
-       },
-       
-       /**
-        * Binds event listener for vote template.
-        * 
-        * @param       integer         pollID
-        */
-       _prepareVote: function(pollID) {
-               this._polls[pollID].find('.jsButtonPollVote').disable();
-               
-               var $voteContainer = this._polls[pollID].find('.pollInnerContainer > .jsPollVote');
-               var self = this;
-               this._inputElements[pollID] = $voteContainer.find('input').change(function() { self._handleVoteButton(pollID); });
-               this._handleVoteButton(pollID);
-               
-               var $maxVotes = $voteContainer.data('maxVotes');
-               if (this._inputElements[pollID].filter('[type=checkbox]').length) {
-                       this._inputElements[pollID].change(function() { self._enforceMaxVotes(pollID, $maxVotes); });
-                       this._enforceMaxVotes(pollID, $maxVotes);
-               }
-       },
-       
-       /**
-        * Enforces max votes for input fields.
-        * 
-        * @param       integer         pollID
-        * @param       integer         maxVotes
-        */
-       _enforceMaxVotes: function(pollID, maxVotes) {
-               var $elements = this._inputElements[pollID];
-               
-               if ($elements.filter(':checked').length == maxVotes) {
-                       $elements.filter(':not(:checked)').disable();
-               }
-               else {
-                       $elements.enable();
-               }
-       },
-       
-       /**
-        * Enables or disable vote button.
-        * 
-        * @param       integer         pollID
-        */
-       _handleVoteButton: function(pollID) {
-               var $elements = this._inputElements[pollID];
-               var $voteButton = this._polls[pollID].find('.jsButtonPollVote');
-               
-               if ($elements.filter(':checked').length) {
-                       $voteButton.enable();
-               }
-               else {
-                       $voteButton.disable();
-               }
-       },
-       
-       /**
-        * Toggles buttons for given poll id.
-        * 
-        * @param       integer         pollID
-        */
-       _toggleButtons: function(pollID) {
-               var $formSubmit = this._polls[pollID].children('.formSubmit');
-               $formSubmit.find('.jsButtonPollShowParticipants, .jsButtonPollShowResult, .jsButtonPollShowVote, .jsButtonPollVote').hide();
-               
-               var $hideFormSubmit = true;
-               if (this._polls[pollID].data('inVote')) {
-                       $hideFormSubmit = false;
-                       $formSubmit.find('.jsButtonPollVote').show();
-                       
-                       if (this._canViewResult[pollID]) {
-                               $formSubmit.find('.jsButtonPollShowResult').show();
-                       }
-               }
-               else {
-                       if (this._canVote[pollID]) {
-                               $hideFormSubmit = false;
-                               $formSubmit.find('.jsButtonPollShowVote').show();
-                       }
-                       
-                       if (this._canViewParticipants[pollID]) {
-                               $hideFormSubmit = false;
-                               $formSubmit.find('.jsButtonPollShowParticipants').show();
-                       }
-               }
-               
-               if ($hideFormSubmit) {
-                       $formSubmit.hide();
-               }
-       },
-       
-       /**
-        * Executes a user's vote.
-        * 
-        * @param       object          event
-        */
-       _vote: function(event) {
-               var $pollID = $(event.currentTarget).data('pollID');
-               
-               // user cannot vote
-               if (!this._canVote[$pollID]) {
-                       return;
-               }
-               
-               // collect values
-               var $optionIDs = [ ];
-               this._inputElements[$pollID].each(function(index, input) {
-                       var $input = $(input);
-                       if ($input.is(':checked')) {
-                               $optionIDs.push($input.data('optionID'));
-                       }
-               });
-               
-               if ($optionIDs.length) {
-                       this._proxy.setOption('data', {
-                               actionName: 'vote',
-                               optionIDs: $optionIDs,
-                               pollID: $pollID
-                       });
-                       this._proxy.sendRequest();
-               }
-       }
-});
-
-
-// WCF.Search.Message.js
-/**
- * Namespace
- */
-WCF.Search.Message = {};
-
-/**
- * Provides quick search for search keywords.
- * 
- * @see        WCF.Search.Base
- */
-WCF.Search.Message.KeywordList = WCF.Search.Base.extend({
-       /**
-        * @see WCF.Search.Base._className
-        */
-       _className: 'wcf\\data\\search\\keyword\\SearchKeywordAction',
-       
-       /**
-        * dropdown divider
-        * @var jQuery
-        */
-       _divider: null,
-       
-       /**
-        * true, if submit should be forced
-        * @var boolean
-        */
-       _forceSubmit: false,
-       
-       /**
-        * @see WCF.Search.Base.init()
-        */
-       init: function(searchInput, callback, excludedSearchValues) {
-               if (!$.isFunction(callback)) {
-                       console.debug("[WCF.Search.Message.KeywordList] The given callback is invalid, aborting.");
-                       return;
-               }
-               
-               this._callback = callback;
-               this._excludedSearchValues = [];
-               if (excludedSearchValues) {
-                       this._excludedSearchValues = excludedSearchValues;
-               }
-               this._searchInput = $(searchInput).keyup($.proxy(this._keyUp, this)).keydown($.proxy(function(event) {
-                       // block form submit
-                       if (event.which === 13) {
-                               // ... unless there are no suggestions or suggestions are optional and none is selected
-                               if (this._itemCount && this._itemIndex !== -1) {
-                                       event.preventDefault();
-                               }
-                       }
-               }, this));
-               
-               var $dropdownMenu = WCF.Dropdown.getDropdownMenu(this._searchInput.parents('.dropdown').wcfIdentify());
-               var $lastDivider = $dropdownMenu.find('li.dropdownDivider').last();
-               this._divider = $('<li class="dropdownDivider" />').hide().insertBefore($lastDivider);
-               this._list = $('<li class="dropdownList"><ul /></li>').hide().insertBefore($lastDivider).children('ul');
-               
-               // supress clicks on checkboxes
-               $dropdownMenu.find('input, label').on('click', function(event) { event.stopPropagation(); });
-               
-               this._proxy = new WCF.Action.Proxy({
-                       showLoadingOverlay: false,
-                       success: $.proxy(this._success, this)
-               });
-       },
-       
-       /**
-        * @see WCF.Search.Base._createListItem()
-        */
-       _createListItem: function(item) {
-               this._divider.show();
-               this._list.parent().show();
-               
-               this._super(item);
-       },
-       
-       /**
-        * @see WCF.Search.Base._clearList()
-        */
-       _clearList: function(clearSearchInput) {
-               if (clearSearchInput) {
-                       this._searchInput.val('');
-               }
-               
-               this._divider.hide();
-               this._list.empty().parent().hide();
-               
-               WCF.CloseOverlayHandler.removeCallback('WCF.Search.Base');
-               
-               // reset item navigation
-               this._itemCount = 0;
-               this._itemIndex = -1;
-       }
-});
-
-/**
- * Handles the search area box.
- * 
- * @param      jQuery          searchArea
- */
-WCF.Search.Message.SearchArea = Class.extend({
-       /**
-        * search area object
-        * @var jQuery
-        */
-       _searchArea: null,
-       
-       /**
-        * Initializes the WCF.Search.Message.SearchArea class.
-        * 
-        * @param       jQuery          searchArea
-        */
-       init: function(searchArea) {
-               this._searchArea = searchArea;
-               
-               var $keywordList = new WCF.Search.Message.KeywordList(this._searchArea.find('input[type=search]'), $.proxy(this._callback, this));
-               $keywordList.setDelay(500);
-               
-               // forward clicks on the search icon to input field
-               var self = this;
-               var $input = this._searchArea.find('input[type=search]');
-               this._searchArea.click(function(event) {
-                       // only forward clicks if the search element itself is the target
-                       if (event.target == self._searchArea[0]) {
-                               $input.focus().trigger('click');
-                               return false;
-                       }
-               });
-               
-               if (this._searchArea.hasClass('dropdown')) {
-                       var $containerID = this._searchArea.wcfIdentify();
-                       var $form = this._searchArea.find('form');
-                       $form.submit(function() {
-                               // copy checkboxes and hidden fields into form
-                               var $dropdownMenu = WCF.Dropdown.getDropdownMenu($containerID);
-                               
-                               $dropdownMenu.find('input[type=hidden]').appendTo($form);
-                               $dropdownMenu.find('input[type=checkbox]:checked').each(function(index, input) {
-                                       var $input = $(input);
-                                       
-                                       $('<input type="hidden" name="' + $input.attr('name') + '" value="' + $input.attr('value') + '" />').appendTo($form);
-                               });
-                       });
-               }
-       },
-       
-       /**
-        * Callback for WCF.Search.Message.KeywordList.
-        * 
-        * @param       object          data
-        * @return      boolean
-        */
-       _callback: function(data) {
-               this._searchArea.find('input[type=search]').val(data.label);
-               this._searchArea.find('input[type=search]').focus();
-               return false;
-       }
-});
-
-// WCF.Tagging.js
-/**
- * Tagging System for WCF
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2014 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- */
-
-/**
- * Namespace for tagging related functions.
- */
-WCF.Tagging = {};
-
-/**
- * Editable tag list.
- * 
- * @see        WCF.EditableItemList
- */
-WCF.Tagging.TagList = WCF.EditableItemList.extend({
-       /**
-        * @see WCF.EditableItemList._className
-        */
-       _className: 'wcf\\data\\tag\\TagAction',
-       
-       /**
-        * maximum tag length
-        * @var integer
-        */
-       _maxLength: 0,
-       
-       /**
-        * @see WCF.EditableItemList.init()
-        */
-       init: function(itemListSelector, searchInputSelector, maxLength) {
-               this._allowCustomInput = true;
-               this._maxLength = maxLength;
-               
-               this._super(itemListSelector, searchInputSelector);
-               
-               this._data = [ ];
-               this._search = new WCF.Tagging.TagSearch(this._searchInput, $.proxy(this.addItem, this));
-               this._itemList.addClass('tagList');
-               $(itemListSelector).data('__api', this);
-       },
-       
-       /**
-        * @see WCF.EditableItemList._keyDown()
-        */
-       _keyDown: function(event) {
-               if (this._super(event)) {
-                       // ignore submit event
-                       if (event === null) {
-                               return true;
-                       }
-                       
-                       var $keyCode = event.which;
-                       // allow [backspace], [escape], [enter] and [delete]
-                       if ($keyCode === 8 || $keyCode === 27 || $keyCode === 13 || $keyCode === 46) {
-                               return true;
-                       }
-                       else if ($keyCode > 36 && $keyCode < 41) {
-                               // allow arrow keys (37-40)
-                               return true;
-                       }
-                       
-                       if (this._searchInput.val().length >= this._maxLength) {
-                               return false;
-                       }
-                       
-                       return true;
-               }
-               
-               return false;
-       },
-       
-       /**
-        * @see WCF.EditableItemList._submit()
-        */
-       _submit: function() {
-               this._super();
-               
-               for (var $i = 0, $length = this._data.length; $i < $length; $i++) {
-                       // deleting items leaves crappy indices
-                       if (this._data[$i]) {
-                               $('<input type="hidden" name="tags[]" />').val(this._data[$i]).appendTo(this._form);
-                       }
-               };
-       },
-       
-       /**
-        * @see WCF.EditableItemList.addItem()
-        */
-       addItem: function(data) {
-               // enforce max length by trimming values
-               if (!data.objectID && data.label.length > this._maxLength) {
-                       data.label = data.label.substr(0, this._maxLength);
-               }
-               
-               var result = this._super(data);
-               $(this._itemList).find('.badge:not(tag)').addClass('tag');
-               
-               return result;
-       },
-       
-       /**
-        * @see WCF.EditableItemList._addItem()
-        */
-       _addItem: function(objectID, label) {
-               this._data.push(label);
-       },
-       
-       /**
-        * @see WCF.EditableItemList.clearList()
-        */
-       clearList: function() {
-               this._super();
-               
-               this._data = [ ];
-       },
-       
-       /**
-        * Returns the current tags.
-        * 
-        * @return      array<string>
-        */
-       getTags: function() {
-               return this._data;
-       },
-       
-       /**
-        * @see WCF.EditableItemList._removeItem()
-        */
-       _removeItem: function(objectID, label) {
-               for (var $i = 0, $length = this._data.length; $i < $length; $i++) {
-                       if (this._data[$i] === label) {
-                               // don't use "delete" here since it doesn't reindex
-                               // the array
-                               this._data.splice($i, 1);
-                               return;
-                       }
-               }
-       },
-       
-       /**
-        * @see WCF.EditableItemList.load()
-        */
-       load: function(data) {
-               if (data && data.length) {
-                       for (var $i = 0, $length = data.length; $i < $length; $i++) {
-                               this.addItem({ objectID: 0, label: WCF.String.unescapeHTML(data[$i]) });
-                       }
-               }
-       }
-});
-
-/**
- * Search handler for tags.
- * 
- * @see        WCF.Search.Base
- */
-WCF.Tagging.TagSearch = WCF.Search.Base.extend({
-       /**
-        * @see WCF.Search.Base._className
-        */
-       _className: 'wcf\\data\\tag\\TagAction',
-       
-       /**
-        * @see WCF.Search.Base.init()
-        */
-       init: function(searchInput, callback, excludedSearchValues, commaSeperated) {
-               this._super(searchInput, callback, excludedSearchValues, commaSeperated, false);
-       }
-});
-
-
-// WCF.User.js
-/**
- * User-related classes.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2014 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- */
-
-/**
- * User login
- * 
- * @param      boolean         isQuickLogin
- */
-WCF.User.Login = Class.extend({
-       /**
-        * login button
-        * @var jQuery
-        */
-       _loginSubmitButton: null,
-       
-       /**
-        * password input
-        * @var jQuery
-        */
-       _password: null,
-       
-       /**
-        * password input container
-        * @var jQuery
-        */
-       _passwordContainer: null,
-       
-       /**
-        * cookie input
-        * @var jQuery
-        */
-       _useCookies: null,
-       
-       /**
-        * cookie input container
-        * @var jQuery
-        */
-       _useCookiesContainer: null,
-       
-       /**
-        * Initializes the user login
-        * 
-        * @param       boolean         isQuickLogin
-        */
-       init: function(isQuickLogin) {
-               this._loginSubmitButton = $('#loginSubmitButton');
-               this._password = $('#password'),
-               this._passwordContainer = this._password.parents('dl');
-               this._useCookies = $('#useCookies');
-               this._useCookiesContainer = this._useCookies.parents('dl');
-               
-               var $loginForm = $('#loginForm');
-               $loginForm.find('input[name=action]').change($.proxy(this._change, this));
-               
-               if (isQuickLogin) {
-                       WCF.User.QuickLogin.init();
-               }
-       },
-       
-       /**
-        * Handle toggle between login and register.
-        * 
-        * @param       object          event
-        */
-       _change: function(event) {
-               if ($(event.currentTarget).val() === 'register') {
-                       this._setState(false, WCF.Language.get('wcf.user.button.register'));
-               }
-               else {
-                       this._setState(true, WCF.Language.get('wcf.user.button.login'));
-               }
-       },
-       
-       /**
-        * Sets form states.
-        * 
-        * @param       boolean         enable
-        * @param       string          buttonTitle
-        */
-       _setState: function(enable, buttonTitle) {
-               if (enable) {
-                       this._password.enable();
-                       this._passwordContainer.removeClass('disabled');
-                       this._useCookies.enable();
-                       this._useCookiesContainer.removeClass('disabled');
-               }
-               else {
-                       this._password.disable();
-                       this._passwordContainer.addClass('disabled');
-                       this._useCookies.disable();
-                       this._useCookiesContainer.addClass('disabled');
-               }
-               
-               this._loginSubmitButton.val(buttonTitle);
-       }
-});
-
-/**
- * Quick login box
- */
-WCF.User.QuickLogin = {
-       /**
-        * dialog overlay
-        * @var jQuery
-        */
-       _dialog: null,
-       
-       /**
-        * login message container
-        * @var jQuery
-        */
-       _loginMessage: null,
-       
-       /**
-        * Initializes the quick login box
-        */
-       init: function() {
-               $('.loginLink').click($.proxy(this._render, this));
-               
-               // prepend protocol and hostname
-               $('#loginForm input[name=url]').val(function(index, value) {
-                       return window.location.protocol + '//' + window.location.host + value;
-               });
-       },
-       
-       /**
-        * Displays the quick login box with a info message
-        * 
-        * @param       string  message
-        */
-       show: function(message) {
-               if (message) {
-                       if (this._loginMessage === null) {
-                               this._loginMessage = $('<p class="info" />').hide().prependTo($('#loginForm > form'));
-                       }
-                       
-                       this._loginMessage.show().text(message);
-               }
-               else if (this._loginMessage !== null) {
-                       this._loginMessage.hide();
-               }
-               
-               this._render();
-       },
-       
-       /**
-        * Renders the dialog
-        * 
-        * @param       jQuery.Event    event
-        */
-       _render: function(event) {
-               if (event !== undefined) {
-                       event.preventDefault();
-               }
-               
-               if (this._dialog === null) {
-                       this._dialog = $('#loginForm').wcfDialog({
-                               title: WCF.Language.get('wcf.user.login')
-                       });
-                       this._dialog.find('#username').focus();
-               }
-               else {
-                       this._dialog.wcfDialog('open');
-               }
-       }
-};
-
-/**
- * UserProfile namespace
- */
-WCF.User.Profile = {};
-
-/**
- * Shows the activity point list for users.
- */
-WCF.User.Profile.ActivityPointList = {
-       /**
-        * list of cached templates
-        * @var object
-        */
-       _cache: { },
-       
-       /**
-        * dialog overlay
-        * @var jQuery
-        */
-       _dialog: null,
-       
-       /**
-        * initialization state
-        * @var boolean
-        */
-       _didInit: false,
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * Initializes the WCF.User.Profile.ActivityPointList class.
-        */
-       init: function() {
-               if (this._didInit) {
-                       return;
-               }
-               
-               this._cache = { };
-               this._dialog = null;
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               this._init();
-               
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.User.Profile.ActivityPointList', $.proxy(this._init, this));
-               
-               this._didInit = true;
-       },
-       
-       /**
-        * Initializes display for activity points.
-        */
-       _init: function() {
-               $('.activityPointsDisplay').removeClass('activityPointsDisplay').click($.proxy(this._click, this));
-       },
-       
-       /**
-        * Shows or loads the activity point for selected user.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               var $userID = $(event.currentTarget).data('userID');
-               
-               if (this._cache[$userID] === undefined) {
-                       this._proxy.setOption('data', {
-                               actionName: 'getDetailedActivityPointList',
-                               className: 'wcf\\data\\user\\UserProfileAction',
-                               objectIDs: [ $userID ]
-                       });
-                       this._proxy.sendRequest();
-               }
-               else {
-                       this._show($userID);
-               }
-       },
-       
-       /**
-        * Displays activity points for given user.
-        * 
-        * @param       integer         userID
-        */
-       _show: function(userID) {
-               if (this._dialog === null) {
-                       this._dialog = $('<div>' + this._cache[userID] + '</div>').hide().appendTo(document.body);
-                       this._dialog.wcfDialog({
-                               title: WCF.Language.get('wcf.user.activityPoint')
-                       });
-               }
-               else {
-                       this._dialog.html(this._cache[userID]);
-                       this._dialog.wcfDialog('open');
-               }
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               this._cache[data.returnValues.userID] = data.returnValues.template;
-               this._show(data.returnValues.userID);
-       }
-};
-
-/**
- * Provides methods to follow an user.
- * 
- * @param      integer         userID
- * @param      boolean         following
- */
-WCF.User.Profile.Follow = Class.extend({
-       /**
-        * follow button
-        * @var jQuery
-        */
-       _button: null,
-       
-       /**
-        * true if following current user
-        * @var boolean
-        */
-       _following: false,
-       
-       /**
-        * action proxy object
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * user id
-        * @var integer
-        */
-       _userID: 0,
-       
-       /**
-        * Creates a new follow object.
-        * 
-        * @param       integer         userID
-        * @param       boolean         following
-        */
-       init: function (userID, following) {
-               this._following = following;
-               this._userID = userID;
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               this._createButton();
-               this._showButton();
-       },
-       
-       /**
-        * Creates the (un-)follow button
-        */
-       _createButton: function () {
-               this._button = $('<li id="followUser"><a class="button jsTooltip" title="'+WCF.Language.get('wcf.user.button.'+(this._following ? 'un' : '')+'follow')+'"><span class="icon icon16 icon-plus"></span> <span class="invisible">'+WCF.Language.get('wcf.user.button.'+(this._following ? 'un' : '')+'follow')+'</span></a></li>').prependTo($('#profileButtonContainer'));
-               this._button.click($.proxy(this._execute, this));
-       },
-       
-       /**
-        * Follows or unfollows an user.
-        */
-       _execute: function () {
-               var $actionName = (this._following) ? 'unfollow' : 'follow';
-               this._proxy.setOption('data', {
-                       'actionName': $actionName,
-                       'className': 'wcf\\data\\user\\follow\\UserFollowAction',
-                       'parameters': {
-                               data: {
-                                       userID: this._userID
-                               }
-                       }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Displays current follow state.
-        */
-       _showButton: function () {
-               if (this._following) {
-                       this._button.find('.button').data('tooltip', WCF.Language.get('wcf.user.button.unfollow')).addClass('active').children('.icon').removeClass('icon-plus').addClass('icon-minus');
-               }
-               else {
-                       this._button.find('.button').data('tooltip', WCF.Language.get('wcf.user.button.follow')).removeClass('active').children('.icon').removeClass('icon-minus').addClass('icon-plus');
-               }
-       },
-       
-       /**
-        * Update object state on success.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function (data, textStatus, jqXHR) {
-               this._following = data.returnValues.following;
-               this._showButton();
-               
-               var $notification = new WCF.System.Notification();
-               $notification.show();
-       }
-});
-
-/**
- * Provides methods to manage ignored users.
- * 
- * @param      integer         userID
- * @param      boolean         isIgnoredUser
- */
-WCF.User.Profile.IgnoreUser = Class.extend({
-       /**
-        * ignore button
-        * @var jQuery
-        */
-       _button: null,
-       
-       /**
-        * ignore state
-        * @var boolean
-        */
-       _isIgnoredUser: false,
-       
-       /**
-        * ajax proxy object
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * target user id
-        * @var integer
-        */
-       _userID: 0,
-       
-       /**
-        * Initializes methods to manage an ignored user.
-        * 
-        * @param       integer         userID
-        * @param       boolean         isIgnoredUser
-        */
-       init: function(userID, isIgnoredUser) {
-               this._userID = userID;
-               this._isIgnoredUser = isIgnoredUser;
-               
-               // initialize proxy
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               // handle button
-               this._updateButton();
-               this._button.click($.proxy(this._click, this));
-       },
-       
-       /**
-        * Handle clicks, might cause 'ignore' or 'unignore' to be triggered.
-        */
-       _click: function() {
-               var $action = (this._isIgnoredUser) ? 'unignore' : 'ignore';
-               
-               this._proxy.setOption('data', {
-                       actionName: $action,
-                       className: 'wcf\\data\\user\\ignore\\UserIgnoreAction',
-                       parameters: {
-                               data: {
-                                       ignoreUserID: this._userID
-                               }
-                       }
-               });
-               
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Updates button label and function upon successful request.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               this._isIgnoredUser = data.returnValues.isIgnoredUser;
-               this._updateButton();
-               
-               var $notification = new WCF.System.Notification();
-               $notification.show();
-       },
-       
-       /**
-        * Updates button label and inserts it if not exists.
-        */
-       _updateButton: function() {
-               if (this._button === null) {
-                       this._button = $('<li id="ignoreUser"><a class="button jsTooltip" title="'+WCF.Language.get('wcf.user.button.'+(this._isIgnoredUser ? 'un' : '')+'ignore')+'"><span class="icon icon16 icon-ban-circle"></span> <span class="invisible">'+WCF.Language.get('wcf.user.button.'+(this._isIgnoredUser ? 'un' : '')+'ignore')+'</span></a></li>').prependTo($('#profileButtonContainer'));
-               }
-               
-               this._button.find('.button').data('tooltip', WCF.Language.get('wcf.user.button.' + (this._isIgnoredUser ? 'un' : '') + 'ignore'));
-               if (this._isIgnoredUser) this._button.find('.button').addClass('active').children('.icon').removeClass('icon-ban-circle').addClass('icon-circle-blank');
-               else this._button.find('.button').removeClass('active').children('.icon').removeClass('icon-circle-blank').addClass('icon-ban-circle');
-       }
-});
-
-/**
- * Provides methods to load tab menu content upon request.
- */
-WCF.User.Profile.TabMenu = Class.extend({
-       /**
-        * list of containers
-        * @var object
-        */
-       _hasContent: { },
-       
-       /**
-        * profile content
-        * @var jQuery
-        */
-       _profileContent: null,
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * target user id
-        * @var integer
-        */
-       _userID: 0,
-       
-       /**
-        * Initializes the tab menu loader.
-        * 
-        * @param       integer         userID
-        */
-       init: function(userID) {
-               this._profileContent = $('#profileContent');
-               this._userID = userID;
-               
-               var $activeMenuItem = this._profileContent.data('active');
-               var $enableProxy = false;
-               
-               // fetch content state
-               this._profileContent.find('div.tabMenuContent').each($.proxy(function(index, container) {
-                       var $containerID = $(container).wcfIdentify();
-                       
-                       if ($activeMenuItem === $containerID) {
-                               this._hasContent[$containerID] = true;
-                       }
-                       else {
-                               this._hasContent[$containerID] = false;
-                               $enableProxy = true;
-                       }
-               }, this));
-               
-               // enable loader if at least one container is empty
-               if ($enableProxy) {
-                       this._proxy = new WCF.Action.Proxy({
-                               success: $.proxy(this._success, this)
-                       });
-                       
-                       this._profileContent.bind('wcftabsbeforeactivate', $.proxy(this._loadContent, this));
-               }
-       },
-       
-       /**
-        * Prepares to load content once tabs are being switched.
-        * 
-        * @param       object          event
-        * @param       object          ui
-        */
-       _loadContent: function(event, ui) {
-               var $panel = $(ui.newPanel);
-               var $containerID = $panel.attr('id');
-               
-               if (!this._hasContent[$containerID]) {
-                       this._proxy.setOption('data', {
-                               actionName: 'getContent',
-                               className: 'wcf\\data\\user\\profile\\menu\\item\\UserProfileMenuItemAction',
-                               parameters: {
-                                       data: {
-                                               containerID: $containerID,
-                                               menuItem: $panel.data('menuItem'),
-                                               userID: this._userID
-                                       }
-                               }
-                       });
-                       this._proxy.sendRequest();
-               }
-       },
-       
-       /**
-        * Shows previously requested content.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               var $containerID = data.returnValues.containerID;
-               this._hasContent[$containerID] = true;
-               
-               // insert content
-               var $content = this._profileContent.find('#' + $containerID);
-               $('<div>' + data.returnValues.template + '</div>').hide().appendTo($content);
-               
-               // slide in content
-               $content.children('div').wcfBlindIn();
-       }
-});
-
-/**
- * User profile inline editor.
- * 
- * @param      integer         userID
- * @param      boolean         editOnInit
- */
-WCF.User.Profile.Editor = Class.extend({
-       /**
-        * current action
-        * @var string
-        */
-       _actionName: '',
-       
-       /**
-        * list of interface buttons
-        * @var object
-        */
-       _buttons: { },
-       
-       /**
-        * cached tab content
-        * @var string
-        */
-       _cachedTemplate: '',
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * tab object
-        * @var jQuery
-        */
-       _tab: null,
-       
-       /**
-        * target user id
-        * @var integer
-        */
-       _userID: 0,
-       
-       /**
-        * Initializes the WCF.User.Profile.Editor object.
-        * 
-        * @param       integer         userID
-        * @param       boolean         editOnInit
-        */
-       init: function(userID, editOnInit) {
-               this._actionName = '';
-               this._cachedTemplate = '';
-               this._tab = $('#about');
-               this._userID = userID;
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               this._initButtons();
-               
-               // begin editing on page load
-               if (editOnInit) {
-                       this._beginEdit();
-               }
-       },
-       
-       /**
-        * Initializes interface buttons.
-        */
-       _initButtons: function() {
-               var $buttonContainer = $('#profileButtonContainer');
-               
-               // create buttons
-               this._buttons = {
-                       beginEdit: $('<li><a class="button"><span class="icon icon16 icon-pencil" /> <span>' + WCF.Language.get('wcf.user.editProfile') + '</span></a></li>').click($.proxy(this._beginEdit, this)).appendTo($buttonContainer)
-               };
-       },
-       
-       /**
-        * Begins editing.
-        */
-       _beginEdit: function() {
-               this._actionName = 'beginEdit';
-               this._buttons.beginEdit.hide();
-               $('#profileContent').wcfTabs('select', 'about');
-               
-               // load form
-               this._proxy.setOption('data', {
-                       actionName: 'beginEdit',
-                       className: 'wcf\\data\\user\\UserProfileAction',
-                       objectIDs: [ this._userID ]
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Saves input values.
-        */
-       _save: function() {
-               this._actionName = 'save';
-               
-               // collect values
-               var $regExp = /values\[([a-zA-Z0-9._-]+)\]/;
-               var $values = { };
-               this._tab.find('input, textarea, select').each(function(index, element) {
-                       var $element = $(element);
-                       
-                       if ($element.getTagName() === 'input') {
-                               var $type = $element.attr('type');
-                               
-                               if (($type === 'radio' || $type === 'checkbox') && !$element.prop('checked')) {
-                                       return;
-                               }
-                       }
-                       
-                       var $name = $element.attr('name');
-                       if ($regExp.test($name)) {
-                               $values[RegExp.$1] = $element.val();
-                       }
-               });
-               
-               this._proxy.setOption('data', {
-                       actionName: 'save',
-                       className: 'wcf\\data\\user\\UserProfileAction',
-                       objectIDs: [ this._userID ],
-                       parameters: {
-                               values: $values
-                       }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Restores back to default view.
-        */
-       _restore: function() {
-               this._actionName = 'restore';
-               this._buttons.beginEdit.show();
-               
-               this._destroyCKEditor();
-               
-               this._tab.html(this._cachedTemplate).children().css({ height: 'auto' });
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               switch (this._actionName) {
-                       case 'beginEdit':
-                               this._prepareEdit(data);
-                       break;
-                       
-                       case 'save':
-                               // save was successful, show parsed template
-                               if (data.returnValues.success) {
-                                       this._cachedTemplate = data.returnValues.template;
-                                       this._restore();
-                               }
-                               else {
-                                       this._prepareEdit(data, true);
-                               }
-                       break;
-               }
-       },
-       
-       /**
-        * Prepares editing mode.
-        * 
-        * @param       object          data
-        * @param       boolean         disableCache
-        */
-       _prepareEdit: function(data, disableCache) {
-               this._destroyCKEditor();
-               
-               // update template
-               var self = this;
-               this._tab.html(function(index, oldHTML) {
-                       if (disableCache !== true) {
-                               self._cachedTemplate = oldHTML;
-                       }
-                       
-                       return data.returnValues.template;
-               });
-               
-               // block autocomplete
-               this._tab.find('input[type=text]').attr('autocomplete', 'off');
-               
-               // bind event listener
-               this._tab.find('.formSubmit > button[data-type=save]').click($.proxy(this._save, this));
-               this._tab.find('.formSubmit > button[data-type=restore]').click($.proxy(this._restore, this));
-               this._tab.find('input').keyup(function(event) {
-                       if (event.which === 13) { // Enter
-                               self._save();
-                               
-                               event.preventDefault();
-                               return false;
-                       }
-               });
-       },
-       
-       /**
-        * Destroys all CKEditor instances within current tab.
-        */
-       _destroyCKEditor: function() {
-               // destroy all CKEditor instances
-               this._tab.find('textarea + .cke').each(function(index, container) {
-                       var $instanceName = $(container).attr('id').replace(/cke_/, '');
-                       if (CKEDITOR.instances[$instanceName]) {
-                               CKEDITOR.instances[$instanceName].destroy();
-                       }
-               });
-       }
-});
-
-/**
- * Namespace for registration functions.
- */
-WCF.User.Registration = {};
-
-/**
- * Validates the password.
- * 
- * @param      jQuery          element
- * @param      jQuery          confirmElement
- * @param      object          options
- */
-WCF.User.Registration.Validation = Class.extend({
-       /**
-        * action name
-        * @var string
-        */
-       _actionName: '',
-       
-       /**
-        * class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * confirmation input element
-        * @var jQuery
-        */
-       _confirmElement: null,
-       
-       /**
-        * input element
-        * @var jQuery
-        */
-       _element: null,
-       
-       /**
-        * list of error messages
-        * @var object
-        */
-       _errorMessages: { },
-       
-       /**
-        * list of additional options
-        * @var object
-        */
-       _options: { },
-       
-       /**
-        * AJAX proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * Initializes the validation.
-        * 
-        * @param       jQuery          element
-        * @param       jQuery          confirmElement
-        * @param       object          options
-        */
-       init: function(element, confirmElement, options) {
-               this._element = element;
-               this._element.blur($.proxy(this._blur, this));
-               this._confirmElement = confirmElement || null;
-               
-               if (this._confirmElement !== null) {
-                       this._confirmElement.blur($.proxy(this._blurConfirm, this));
-               }
-               
-               options = options || { };
-               this._setOptions(options);
-               
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this),
-                       showLoadingOverlay: false
-               });
-               
-               this._setErrorMessages();
-       },
-       
-       /**
-        * Sets additional options
-        */
-       _setOptions: function(options) { },
-       
-       /**
-        * Sets error messages.
-        */
-       _setErrorMessages: function() {
-               this._errorMessages = {
-                       ajaxError: '',
-                       notEqual: ''
-               };
-       },
-       
-       /**
-        * Validates once focus on input is lost.
-        * 
-        * @param       object          event
-        */
-       _blur: function(event) {
-               var $value = this._element.val();
-               if (!$value) {
-                       return this._showError(this._element, WCF.Language.get('wcf.global.form.error.empty'));
-               }
-               
-               if (this._confirmElement !== null) {
-                       var $confirmValue = this._confirmElement.val();
-                       if ($confirmValue != '' && $value != $confirmValue) {
-                               return this._showError(this._confirmElement, this._errorMessages.notEqual);
-                       }
-               }
-               
-               if (!this._validateOptions()) {
-                       return;
-               }
-               
-               this._proxy.setOption('data', {
-                       actionName: this._actionName,
-                       className: this._className,
-                       parameters: this._getParameters()
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Returns a list of parameters.
-        * 
-        * @return      object
-        */
-       _getParameters: function() {
-               return { };
-       },
-       
-       /**
-        * Validates input by options.
-        * 
-        * @return      boolean
-        */
-       _validateOptions: function() {
-               return true;
-       },
-       
-       /**
-        * Validates value once confirmation input focus is lost.
-        * 
-        * @param       object          event
-        */
-       _blurConfirm: function(event) {
-               var $value = this._confirmElement.val();
-               if (!$value) {
-                       return this._showError(this._confirmElement, WCF.Language.get('wcf.global.form.error.empty'));
-               }
-               
-               this._blur(event);
-       },
-       
-       /**
-        * Handles AJAX responses.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               if (data.returnValues.isValid) {
-                       this._showSuccess(this._element);
-                       if (this._confirmElement !== null && this._confirmElement.val()) {
-                               this._showSuccess(this._confirmElement);
-                       }
-               }
-               else {
-                       this._showError(this._element, WCF.Language.get(this._errorMessages.ajaxError + data.returnValues.error));
-               }
-       },
-       
-       /**
-        * Shows an error message.
-        * 
-        * @param       jQuery          element
-        * @param       string          message
-        */
-       _showError: function(element, message) {
-               element.parent().parent().addClass('formError').removeClass('formSuccess');
-               
-               var $innerError = element.parent().find('small.innerError');
-               if (!$innerError.length) {
-                       $innerError = $('<small />').addClass('innerError').insertAfter(element);
-               }
-               
-               $innerError.text(message);
-       },
-       
-       /**
-        * Displays a success message.
-        * 
-        * @param       jQuery          element
-        */
-       _showSuccess: function(element) {
-               element.parent().parent().addClass('formSuccess').removeClass('formError');
-               element.next('small.innerError').remove();
-       }
-});
-
-/**
- * Username validation for registration.
- * 
- * @see        WCF.User.Registration.Validation
- */
-WCF.User.Registration.Validation.Username = WCF.User.Registration.Validation.extend({
-       /**
-        * @see WCF.User.Registration.Validation._actionName
-        */
-       _actionName: 'validateUsername',
-       
-       /**
-        * @see WCF.User.Registration.Validation._className
-        */
-       _className: 'wcf\\data\\user\\UserRegistrationAction',
-       
-       /**
-        * @see WCF.User.Registration.Validation._setOptions()
-        */
-       _setOptions: function(options) {
-               this._options = $.extend(true, {
-                       minlength: 3,
-                       maxlength: 25
-               }, options);
-       },
-       
-       /**
-        * @see WCF.User.Registration.Validation._setErrorMessages()
-        */
-       _setErrorMessages: function() {
-               this._errorMessages = {
-                       ajaxError: 'wcf.user.username.error.'
-               };
-       },
-       
-       /**
-        * @see WCF.User.Registration.Validation._validateOptions()
-        */
-       _validateOptions: function() {
-               var $value = this._element.val();
-               if ($value.length < this._options.minlength || $value.length > this._options.maxlength) {
-                       this._showError(this._element, WCF.Language.get('wcf.user.username.error.notValid'));
-                       return false;
-               }
-               
-               return true;
-       },
-       
-       /**
-        * @see WCF.User.Registration.Validation._getParameters()
-        */
-       _getParameters: function() {
-               return {
-                       username: this._element.val()
-               };
-       }
-});
-
-/**
- * Email validation for registration.
- * 
- * @see        WCF.User.Registration.Validation
- */
-WCF.User.Registration.Validation.EmailAddress = WCF.User.Registration.Validation.extend({
-       /**
-        * @see WCF.User.Registration.Validation._actionName
-        */
-       _actionName: 'validateEmailAddress',
-       
-       /**
-        * @see WCF.User.Registration.Validation._className
-        */
-       _className: 'wcf\\data\\user\\UserRegistrationAction',
-       
-       /**
-        * @see WCF.User.Registration.Validation._getParameters()
-        */
-       _getParameters: function() {
-               return {
-                       email: this._element.val()
-               };
-       },
-       
-       /**
-        * @see WCF.User.Registration.Validation._setErrorMessages()
-        */
-       _setErrorMessages: function() {
-               this._errorMessages = {
-                       ajaxError: 'wcf.user.email.error.',
-                       notEqual: WCF.Language.get('wcf.user.confirmEmail.error.notEqual')
-               };
-       }
-});
-
-/**
- * Password validation for registration.
- * 
- * @see        WCF.User.Registration.Validation
- */
-WCF.User.Registration.Validation.Password = WCF.User.Registration.Validation.extend({
-       /**
-        * @see WCF.User.Registration.Validation._actionName
-        */
-       _actionName: 'validatePassword',
-       
-       /**
-        * @see WCF.User.Registration.Validation._className
-        */
-       _className: 'wcf\\data\\user\\UserRegistrationAction',
-       
-       /**
-        * @see WCF.User.Registration.Validation._getParameters()
-        */
-       _getParameters: function() {
-               return {
-                       password: this._element.val()
-               };
-       },
-       
-       /**
-        * @see WCF.User.Registration.Validation._setErrorMessages()
-        */
-       _setErrorMessages: function() {
-               this._errorMessages = {
-                       ajaxError: 'wcf.user.password.error.',
-                       notEqual: WCF.Language.get('wcf.user.confirmPassword.error.notEqual')
-               };
-       }
-});
-
-/**
- * Toggles input fields for lost password form.
- */
-WCF.User.Registration.LostPassword = Class.extend({
-       /**
-        * email input
-        * @var jQuery
-        */
-       _email: null,
-       
-       /**
-        * username input
-        * @var jQuery
-        */
-       _username: null,
-       
-       /**
-        * Initializes LostPassword-form class.
-        */
-       init: function() {
-               // bind input fields
-               this._email = $('#emailInput');
-               this._username = $('#usernameInput');
-               
-               // bind event listener
-               this._email.keyup($.proxy(this._checkEmail, this));
-               this._username.keyup($.proxy(this._checkUsername, this));
-               
-               if ($.browser.mozilla && $.browser.touch) {
-                       this._email.on('input', $.proxy(this._checkEmail, this));
-                       this._username.on('input', $.proxy(this._checkUsername, this));
-               }
-               
-               // toggle fields on init
-               this._checkEmail();
-               this._checkUsername();
-       },
-       
-       /**
-        * Checks for content in email field and toggles username.
-        */
-       _checkEmail: function() {
-               if (this._email.val() == '') {
-                       this._username.enable();
-                       this._username.parents('dl:eq(0)').removeClass('disabled');
-               }
-               else {
-                       this._username.disable();
-                       this._username.parents('dl:eq(0)').addClass('disabled');
-               }
-       },
-       
-       /**
-        * Checks for content in username field and toggles email.
-        */
-       _checkUsername: function() {
-               if (this._username.val() == '') {
-                       this._email.enable();
-                       this._email.parents('dl:eq(0)').removeClass('disabled');
-               }
-               else {
-                       this._email.disable();
-                       this._email.parents('dl:eq(0)').addClass('disabled');
-               }
-       }
-});
-
-/**
- * Notification system for WCF.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2014 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- */
-WCF.Notification = {};
-
-/**
- * Loads notification for the user panel.
- * 
- * @see        WCF.UserPanel
- */
-WCF.Notification.UserPanel = WCF.UserPanel.extend({
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * link to show all notifications
-        * @var string
-        */
-       _showAllLink: '',
-       
-       /**
-        * @see WCF.UserPanel.init()
-        */
-       init: function(showAllLink) {
-               this._noItems = 'wcf.user.notification.noMoreNotifications';
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               this._showAllLink = showAllLink;
-               
-               this._super('userNotifications');
-               
-               // update page title
-               if (this._container.data('count')) {
-                       document.title = '(' + this._container.data('count') + ') ' + document.title;
-               }
-       },
-       
-       /**
-        * @see WCF.UserPanel._addDefaultItems()
-        */
-       _addDefaultItems: function(dropdownMenu) {
-               this._addDivider(dropdownMenu);
-               if (this._container.data('count')) {
-                       $('<li><a href="' + this._showAllLink + '">' + WCF.Language.get('wcf.user.notification.showAll') + '</a></li>').appendTo(dropdownMenu);
-                       this._addDivider(dropdownMenu);
-               }
-               $('<li id="userNotificationsMarkAllAsConfirmed"><a>' + WCF.Language.get('wcf.user.notification.markAllAsConfirmed') + '</a></li>').click($.proxy(this._markAllAsConfirmed, this)).appendTo(dropdownMenu);
-       },
-       
-       /**
-        * @see WCF.UserPanel._getParameters()
-        */
-       _getParameters: function() {
-               return {
-                       actionName: 'getOutstandingNotifications',
-                       className: 'wcf\\data\\user\\notification\\UserNotificationAction'
-               };
-       },
-       
-       /**
-        * @see WCF.UserPanel._after()
-        */
-       _after: function(dropdownMenu) {
-               WCF.Dropdown.getDropdownMenu(this._container.wcfIdentify()).children('li.jsNotificationItem').click($.proxy(this._markAsConfirmed, this));
-       },
-       
-       /**
-        * Marks a notification as confirmed.
-        * 
-        * @param       object          event
-        */
-       _markAsConfirmed: function(event) {
-               this._proxy.setOption('data', {
-                       actionName: 'markAsConfirmed',
-                       className: 'wcf\\data\\user\\notification\\UserNotificationAction',
-                       parameters: {
-                               notificationID: $(event.currentTarget).data('notificationID')
-                       }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Marks all notifications as confirmed.
-        */
-       _markAllAsConfirmed: function() {
-               WCF.System.Confirmation.show(WCF.Language.get('wcf.user.notification.markAllAsConfirmed.confirmMessage'), $.proxy(function(action) {
-                       if (action === 'confirm') {
-                               this._proxy.setOption('data', {
-                                       actionName: 'markAllAsConfirmed',
-                                       className: 'wcf\\data\\user\\notification\\UserNotificationAction'
-                               });
-                               this._proxy.sendRequest();
-                       }
-               }, this));
-       },
-       
-       /**
-        * @see WCF.UserPanel._success()
-        */
-       _success: function(data, textStatus, jqXHR) {
-               switch (data.actionName) {
-                       case 'markAllAsConfirmed':
-                               $('.jsNotificationItem').remove();
-                               // remove notification count
-                               document.title = document.title.replace(/^\(([0-9]+)\) /, '');
-                       // fall through
-                       case 'getOutstandingNotifications':
-                               if (!data.returnValues || !data.returnValues.template) {
-                                       $('#userNotificationsMarkAllAsConfirmed').prev('.dropdownDivider').remove();
-                                       $('#userNotificationsMarkAllAsConfirmed').remove();
-                               }
-                               
-                               this._super(data, textStatus, jqXHR);
-                       break;
-                       
-                       case 'markAsConfirmed':
-                               WCF.Dropdown.getDropdownMenu(this._container.wcfIdentify()).children('li.jsNotificationItem').each(function(index, item) {
-                                       var $item = $(item);
-                                       if (data.returnValues.notificationID == $item.data('notificationID')) {
-                                               window.location = $item.data('link');
-                                               return false;
-                                       }
-                               });
-                       break;
-               }
-       }
-});
-
-/**
- * Handles notification list actions.
- */
-WCF.Notification.List = Class.extend({
-       /**
-        * notification count
-        * @var jQuery
-        */
-       _badge: null,
-       
-       /**
-        * list of notification items
-        * @var object
-        */
-       _items: { },
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * Initializes the notification list.
-        */
-       init: function() {
-               var $containers = $('li.jsNotificationItem');
-               if (!$containers.length) {
-                       return;
-               }
-               
-               $containers.each($.proxy(function(index, container) {
-                       var $container = $(container);
-                       this._items[$container.data('notificationID')] = $container;
-                       
-                       $container.find('.jsMarkAsConfirmed').data('notificationID', $container.data('notificationID')).click($.proxy(this._click, this));
-                       $container.find('p').html(function(index, oldHTML) {
-                               return '<a>' + oldHTML + '</a>';
-                       }).children('a').data('notificationID', $container.data('notificationID')).click($.proxy(this._clickLink, this));
-               }, this));
-               
-               this._badge = $('.jsNotificationsBadge:eq(0)');
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               // mark all as confirmed button
-               $('.contentNavigation .jsMarkAllAsConfirmed').click($.proxy(this._markAllAsConfirmed, this));
-       },
-       
-       /**
-        * Handles clicks on the text link.
-        * 
-        * @param       object          event
-        */
-       _clickLink: function(event) {
-               this._items[$(event.currentTarget).data('notificationID')].data('redirect', true);
-               this._click(event);
-       },
-       
-       /**
-        * Handles button actions.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               this._proxy.setOption('data', {
-                       actionName: 'markAsConfirmed',
-                       className: 'wcf\\data\\user\\notification\\UserNotificationAction',
-                       parameters: {
-                               notificationID: $(event.currentTarget).data('notificationID')
-                       }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Marks all notifications as confirmed.
-        */
-       _markAllAsConfirmed: function() {
-               WCF.System.Confirmation.show(WCF.Language.get('wcf.user.notification.markAllAsConfirmed.confirmMessage'), $.proxy(function(action) {
-                       if (action === 'confirm') {
-                               this._proxy.setOption('data', {
-                                       actionName: 'markAllAsConfirmed',
-                                       className: 'wcf\\data\\user\\notification\\UserNotificationAction'
-                               });
-                               this._proxy.sendRequest();
-                       }
-               }, this));
-       },
-       
-       /**
-        * Handles successful button actions.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               switch (data.actionName) {
-                       case 'markAllAsConfirmed':
-                               window.location.reload();
-                       break;
-                       
-                       case 'markAsConfirmed':
-                               var $item = this._items[data.returnValues.notificationID];
-                               if ($item.data('redirect')) {
-                                       window.location = $item.data('link');
-                                       return;
-                               }
-                               
-                               this._items[data.returnValues.notificationID].remove();
-                               delete this._items[data.returnValues.notificationID];
-                               
-                               // reduce badge count
-                               this._badge.html(data.returnValues.totalCount);
-                               
-                               // remove previous notification count
-                               document.title = document.title.replace(/^\(([0-9]+)\) /, '');
-                               
-                               // update page title
-                               if (data.returnValues.totalCount > 0) {
-                                       document.title = '(' + data.returnValues.totalCount + ') ' + document.title;
-                               }
-                       break;
-               }
-       }
-});
-
-/**
- * Signature preview.
- * 
- * @see        WCF.Message.Preview
- */
-WCF.User.SignaturePreview = WCF.Message.Preview.extend({
-       /**
-        * @see WCF.Message.Preview._handleResponse()
-        */
-       _handleResponse: function(data) {
-               // get preview container
-               var $preview = $('#previewContainer');
-               if (!$preview.length) {
-                       $preview = $('<fieldset id="previewContainer"><legend>' + WCF.Language.get('wcf.global.preview') + '</legend><div></div></fieldset>').insertBefore($('#signatureContainer')).wcfFadeIn();
-               }
-               
-               $preview.children('div').first().html(data.returnValues.message);
-       }
-});
-
-/**
- * Loads recent activity events once the user scrolls to the very bottom.
- * 
- * @param      integer         userID
- */
-WCF.User.RecentActivityLoader = Class.extend({
-       /**
-        * container object
-        * @var jQuery
-        */
-       _container: null,
-       
-       /**
-        * true if list should be filtered by followed users
-        * @var boolean
-        */
-       _filteredByFollowedUsers: false,
-       
-       /**
-        * button to load next events
-        * @var jQuery
-        */
-       _loadButton: null,
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * user id
-        * @var integer
-        */
-       _userID: 0,
-       
-       /**
-        * Initializes a new RecentActivityLoader object.
-        * 
-        * @param       integer         userID
-        * @param       boolean         filteredByFollowedUsers
-        */
-       init: function(userID, filteredByFollowedUsers) {
-               this._container = $('#recentActivities');
-               this._filteredByFollowedUsers = (filteredByFollowedUsers === true);
-               this._userID = userID;
-               
-               if (this._userID !== null && !this._userID) {
-                       console.debug("[WCF.User.RecentActivityLoader] Invalid parameter 'userID' given.");
-                       return;
-               }
-               
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               this._loadButton = $('<li class="recentActivitiesMore"><button class="small">' + WCF.Language.get('wcf.user.recentActivity.more') + '</button></li>').appendTo(this._container);
-               this._loadButton = this._loadButton.children('button').click($.proxy(this._click, this));
-       },
-       
-       /**
-        * Loads next activity events.
-        */
-       _click: function() {
-               this._loadButton.enable();
-               
-               var $parameters = {
-                       lastEventTime: this._container.data('lastEventTime')
-               };
-               if (this._userID) {
-                       $parameters.userID = this._userID;
-               }
-               else if (this._filteredByFollowedUsers) {
-                       $parameters.filteredByFollowedUsers = 1;
-               }
-               
-               this._proxy.setOption('data', {
-                       actionName: 'load',
-                       className: 'wcf\\data\\user\\activity\\event\\UserActivityEventAction',
-                       parameters: $parameters
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               if (data.returnValues.template) {
-                       $(data.returnValues.template).insertBefore(this._loadButton.parent());
-                       
-                       this._container.data('lastEventTime', data.returnValues.lastEventTime);
-                       this._loadButton.enable();
-               }
-               else {
-                       $('<small>' + WCF.Language.get('wcf.user.recentActivity.noMoreEntries') + '</small>').appendTo(this._loadButton.parent());
-                       this._loadButton.remove();
-               }
-       }
-});
-
-/**
- * Loads user profile previews.
- * 
- * @see        WCF.Popover
- */
-WCF.User.ProfilePreview = WCF.Popover.extend({
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * list of user profiles
-        * @var object
-        */
-       _userProfiles: { },
-       
-       /**
-        * @see WCF.Popover.init()
-        */
-       init: function() {
-               this._super('.userLink');
-               
-               this._proxy = new WCF.Action.Proxy({
-                       showLoadingOverlay: false
-               });
-       },
-       
-       /**
-        * @see WCF.Popover._loadContent()
-        */
-       _loadContent: function() {
-               var $element = $('#' + this._activeElementID);
-               var $userID = $element.data('userID');
-               
-               if (this._userProfiles[$userID]) {
-                       // use cached user profile
-                       this._insertContent(this._activeElementID, this._userProfiles[$userID], true);
-               }
-               else {
-                       this._proxy.setOption('data', {
-                               actionName: 'getUserProfile',
-                               className: 'wcf\\data\\user\\UserProfileAction',
-                               objectIDs: [ $userID ]
-                       });
-                       
-                       var $elementID = this._activeElementID;
-                       var self = this;
-                       this._proxy.setOption('success', function(data, textStatus, jqXHR) {
-                               // cache user profile
-                               self._userProfiles[$userID] = data.returnValues.template;
-                               
-                               // show user profile
-                               self._insertContent($elementID, data.returnValues.template, true);
-                       });
-                       this._proxy.setOption('failure', function(data, jqXHR, textStatus, errorThrown) {
-                               // cache user profile
-                               self._userProfiles[$userID] = data.message;
-                               
-                               // show user profile
-                               self._insertContent($elementID, data.message, true);
-                               
-                               return false;
-                       });
-                       this._proxy.sendRequest();
-               }
-       }
-});
-
-/**
- * Initalizes WCF.User.Action namespace.
- */
-WCF.User.Action = {};
-
-/**
- * Handles user follow and unfollow links.
- */
-WCF.User.Action.Follow = Class.extend({
-       /**
-        * list with elements containing follow and unfollow buttons
-        * @var array
-        */
-       _containerList: null,
-       
-       /**
-        * CSS selector for follow buttons
-        * @var string
-        */
-       _followButtonSelector: '.jsFollowButton',
-       
-       /**
-        * id of the user that is currently being followed/unfollowed
-        * @var integer
-        */
-       _userID: 0,
-       
-       /**
-        * Initializes new WCF.User.Action.Follow object.
-        * 
-        * @param       array           containerList
-        * @param       string          followButtonSelector
-        */
-       init: function(containerList, followButtonSelector) {
-               if (!containerList.length) {
-                       return;
-               }
-               this._containerList = containerList;
-               
-               if (followButtonSelector) {
-                       this._followButtonSelector = followButtonSelector;
-               }
-               
-               // initialize proxy
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               // bind event listeners
-               this._containerList.each($.proxy(function(index, container) {
-                       $(container).find(this._followButtonSelector).click($.proxy(this._click, this));
-               }, this));
-       },
-       
-       /**
-        * Handles a click on a follow or unfollow button.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               var link = $(event.target);
-               if (!link.is('a')) {
-                       link = link.closest('a');
-               }
-               this._userID = link.data('objectID');
-               
-               this._proxy.setOption('data', {
-                       'actionName': link.data('following') ? 'unfollow' : 'follow',
-                       'className': 'wcf\\data\\user\\follow\\UserFollowAction',
-                       'parameters': {
-                               data: {
-                                       userID: this._userID
-                               }
-                       }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Handles the successful (un)following of a user.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               this._containerList.each($.proxy(function(index, container) {
-                       var button = $(container).find(this._followButtonSelector).get(0);
-                       
-                       if (button && $(button).data('objectID') == this._userID) {
-                               button = $(button);
-                               
-                               // toogle icon title
-                               if (data.returnValues.following) {
-                                       button.data('tooltip', WCF.Language.get('wcf.user.button.unfollow')).children('.icon').removeClass('icon-plus').addClass('icon-minus');
-                               }
-                               else {
-                                       button.data('tooltip', WCF.Language.get('wcf.user.button.follow')).children('.icon').removeClass('icon-minus').addClass('icon-plus');
-                               }
-                               
-                               button.data('following', data.returnValues.following);
-                               
-                               return false;
-                       }
-               }, this));
-               
-               var $notification = new WCF.System.Notification();
-               $notification.show();
-       }
-});
-
-/**
- * Handles user ignore and unignore links.
- */
-WCF.User.Action.Ignore = Class.extend({
-       /**
-        * list with elements containing ignore and unignore buttons
-        * @var array
-        */
-       _containerList: null,
-       
-       /**
-        * CSS selector for ignore buttons
-        * @var string
-        */
-       _ignoreButtonSelector: '.jsIgnoreButton',
-       
-       /**
-        * id of the user that is currently being ignored/unignored
-        * @var integer
-        */
-       _userID: 0,
-       
-       /**
-        * Initializes new WCF.User.Action.Ignore object.
-        * 
-        * @param       array           containerList
-        * @param       string          ignoreButtonSelector
-        */
-       init: function(containerList, ignoreButtonSelector) {
-               if (!containerList.length) {
-                       return;
-               }
-               this._containerList = containerList;
-               
-               if (ignoreButtonSelector) {
-                       this._ignoreButtonSelector = ignoreButtonSelector;
-               }
-               
-               // initialize proxy
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               // bind event listeners
-               this._containerList.each($.proxy(function(index, container) {
-                       $(container).find(this._ignoreButtonSelector).click($.proxy(this._click, this));
-               }, this));
-       },
-       
-       /**
-        * Handles a click on a ignore or unignore button.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               var link = $(event.target);
-               if (!link.is('a')) {
-                       link = link.closest('a');
-               }
-               this._userID = link.data('objectID');
-               
-               this._proxy.setOption('data', {
-                       'actionName': link.data('ignored') ? 'unignore' : 'ignore',
-                       'className': 'wcf\\data\\user\\ignore\\UserIgnoreAction',
-                       'parameters': {
-                               data: {
-                                       ignoreUserID: this._userID
-                               }
-                       }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Handles the successful (un)ignoring of a user.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               this._containerList.each($.proxy(function(index, container) {
-                       var button = $(container).find(this._ignoreButtonSelector).get(0);
-                       
-                       if (button && $(button).data('objectID') == this._userID) {
-                               button = $(button);
-                               
-                               // toogle icon title
-                               if (data.returnValues.isIgnoredUser) {
-                                       button.data('tooltip', WCF.Language.get('wcf.user.button.unignore')).children('.icon').removeClass('icon-ban-circle').addClass('icon-circle-blank');
-                               }
-                               else {
-                                       button.data('tooltip', WCF.Language.get('wcf.user.button.ignore')).children('.icon').removeClass('icon-circle-blank').addClass('icon-ban-circle');
-                               }
-                               
-                               button.data('ignored', data.returnValues.isIgnoredUser);
-                               
-                               return false;
-                       }
-               }, this));
-               
-               var $notification = new WCF.System.Notification();
-               $notification.show();
-       }
-});
-
-/**
- * Namespace for avatar functions.
- */
-WCF.User.Avatar = {};
-
-/**
- * Handles cropping an avatar.
- */
-WCF.User.Avatar.Crop = Class.extend({
-       /**
-        * current crop setting in x-direction
-        * @var integer
-        */
-       _cropX: 0,
-       
-       /**
-        * current crop setting in y-direction
-        * @var integer
-        */
-       _cropY: 0,
-       
-       /**
-        * avatar crop dialog
-        * @var jQuery
-        */
-       _dialog: null,
-       
-       /**
-        * action proxy to send the crop AJAX requests
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * maximum size of thumbnails
-        * @var integer
-        */
-       MAX_THUMBNAIL_SIZE: 128,
-       
-       /**
-        * Creates a new instance of WCF.User.Avatar.Crop.
-        * 
-        * @param       integer         avatarID
-        */
-       init: function(avatarID) {
-               this._avatarID = avatarID;
-               
-               if (this._dialog) {
-                       this.destroy();
-               }
-               this._dialog = null;
-               
-               // check if object already had been initialized
-               if (!this._proxy) {
-                       this._proxy = new WCF.Action.Proxy({
-                               success: $.proxy(this._success, this)
-                       });
-               }
-               
-               $('.userAvatarCrop').click($.proxy(this._showCropDialog, this));
-       },
-       
-       /**
-        * Destroys the avatar crop interface.
-        */
-       destroy: function() {
-               this._dialog.remove();
-       },
-       
-       /**
-        * Sends AJAX request to crop avatar.
-        * 
-        * @param       object          event
-        */
-       _crop: function(event) {
-               this._proxy.setOption('data', {
-                       actionName: 'cropAvatar',
-                       className: 'wcf\\data\\user\\avatar\\UserAvatarAction',
-                       objectIDs: [ this._avatarID ],
-                       parameters: {
-                               cropX: this._cropX,
-                               cropY: this._cropY
-                       }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Initializes the dialog after a successful 'getCropDialog' request.
-        * 
-        * @param       object          data
-        */
-       _getCropDialog: function(data) {
-               if (!this._dialog) {
-                       this._dialog = $('<div />').hide().appendTo(document.body);
-                       this._dialog.wcfDialog({
-                               title: WCF.Language.get('wcf.user.avatar.type.custom.crop')
-                       });
-               }
-               
-               this._dialog.html(data.returnValues.template);
-               this._dialog.find('button[data-type="save"]').click($.proxy(this._crop, this));
-               
-               this._cropX = data.returnValues.cropX;
-               this._cropY = data.returnValues.cropY;
-               
-               var $image = $('#userAvatarCropSelection > img');
-               $('#userAvatarCropSelection').css({
-                       height: $image.height() + 'px',
-                       width: $image.width() + 'px'
-               });
-               $('#userAvatarCropOverlaySelection').css({
-                       'background-image': 'url(' + $image.attr('src') + ')',
-                       'background-position': -this._cropX + 'px ' + -this._cropY + 'px',
-                       'left': this._cropX + 'px',
-                       'top': this._cropY + 'px'
-               }).draggable({
-                       containment: 'parent',
-                       drag : $.proxy(this._updateSelection, this),
-                       stop : $.proxy(this._updateSelection, this)
-               });
-               
-               this._dialog.find('button[data-type="save"]').click($.proxy(this._save, this));
-               
-               this._dialog.wcfDialog('render');
-       },
-       
-       /**
-        * Shows the cropping dialog.
-        */
-       _showCropDialog: function() {
-               if (!this._dialog) {
-                       this._proxy.setOption('data', {
-                               actionName: 'getCropDialog',
-                               className: 'wcf\\data\\user\\avatar\\UserAvatarAction',
-                               objectIDs: [ this._avatarID ]
-                       });
-                       this._proxy.sendRequest();
-               }
-               else {
-                       this._dialog.wcfDialog('open');
-               }
-       },
-       
-       /**
-        * Handles successful AJAX request.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               switch (data.actionName) {
-                       case 'getCropDialog':
-                               this._getCropDialog(data);
-                       break;
-                       
-                       case 'cropAvatar':
-                               $('#avatarUpload > dt > img').replaceWith($('<img src="' + data.returnValues.url + '" alt="" class="userAvatarCrop jsTooltip" title="' + WCF.Language.get('wcf.user.avatar.type.custom.crop') + '" />').css({
-                                       width: '96px',
-                                       height: '96px'
-                               }).click($.proxy(this._showCropDialog, this)));
-                               
-                               WCF.DOMNodeInsertedHandler.execute();
-                               
-                               this._dialog.wcfDialog('close');
-                               
-                               var $notification = new WCF.System.Notification();
-                               $notification.show();
-                       break;
-               }
-       },
-       
-       /**
-        * Updates the current crop selection if the selection overlay is dragged.
-        * 
-        * @param       object          event
-        * @param       object          ui
-        */
-       _updateSelection: function(event, ui) {
-               this._cropX = ui.position.left;
-               this._cropY = ui.position.top;
-               
-               $('#userAvatarCropOverlaySelection').css({
-                       'background-position': -ui.position.left + 'px ' + -ui.position.top + 'px'
-               });
-       }
-});
-
-/**
- * Avatar upload function
- * 
- * @see        WCF.Upload
- */
-WCF.User.Avatar.Upload = WCF.Upload.extend({
-       /**
-        * handles cropping the avatar
-        * @var WCF.User.Avatar.Crop
-        */
-       _avatarCrop: null,
-       
-       /**
-        * user id of avatar owner
-        * @var integer
-        */
-       _userID: 0,
-       
-       /**
-        * Initalizes a new WCF.User.Avatar.Upload object.
-        * 
-        * @param       integer                 userID
-        * @param       WCF.User.Avatar.Crop    avatarCrop
-        */
-       init: function(userID, avatarCrop) {
-               this._super($('#avatarUpload > dd > div'), undefined, 'wcf\\data\\user\\avatar\\UserAvatarAction');
-               this._userID = userID || 0;
-               this._avatarCrop = avatarCrop;
-               
-               $('#avatarForm input[type=radio]').change(function() {
-                       if ($(this).val() == 'custom') {
-                               $('#avatarUpload > dd > div').show();
-                       }
-                       else {
-                               $('#avatarUpload > dd > div').hide();
-                       }
-               });
-               if (!$('#avatarForm input[type=radio][value=custom]:checked').length) {
-                       $('#avatarUpload > dd > div').hide();
-               }
-       },
-       
-       /**
-        * @see WCF.Upload._initFile()
-        */
-       _initFile: function(file) {
-               return $('#avatarUpload > dt > img');
-       },
-       
-       /**
-        * @see WCF.Upload._success()
-        */
-       _success: function(uploadID, data) {
-               if (data.returnValues.url) {
-                       this._updateImage(data.returnValues.url, data.returnValues.canCrop);
-                       
-                       if (data.returnValues.canCrop) {
-                               if (!this._avatarCrop) {
-                                       this._avatarCrop = new WCF.User.Avatar.Crop(data.returnValues.avatarID);
-                               }
-                               else {
-                                       this._avatarCrop.init(data.returnValues.avatarID);
-                               }
-                       }
-                       else if (this._avatarCrop) {
-                               this._avatarCrop.destroy();
-                               this._avatarCrop = null;
-                       }
-                       
-                       // hide error
-                       $('#avatarUpload > dd > .innerError').remove();
-                       
-                       // show success message
-                       var $notification = new WCF.System.Notification(WCF.Language.get('wcf.user.avatar.upload.success'));
-                       $notification.show();
-               }
-               else if (data.returnValues.errorType) {
-                       // show error
-                       this._getInnerErrorElement().text(WCF.Language.get('wcf.user.avatar.upload.error.' + data.returnValues.errorType));
-               }
-       },
-       
-       /**
-        * Updates the displayed avatar image.
-        * 
-        * @param       string          url
-        * @param       boolean         canCrop
-        */
-       _updateImage: function(url, canCrop) {
-               $('#avatarUpload > dt > img').remove();
-               var $image = $('<img src="' + url + '" alt="" />').css({
-                       'height': 'auto',
-                       'max-height': '96px',
-                       'max-width': '96px',
-                       'width': 'auto'
-               });
-               if (canCrop) {
-                       $image.addClass('userAvatarCrop').addClass('jsTooltip');
-                       $image.attr('title', WCF.Language.get('wcf.user.avatar.type.custom.crop'));
-               }
-               
-               $('#avatarUpload > dt').prepend($image);
-               
-               WCF.DOMNodeInsertedHandler.execute();
-       },
-       
-       /**
-        * Returns the inner error element.
-        * 
-        * @return      jQuery
-        */
-       _getInnerErrorElement: function() {
-               var $span = $('#avatarUpload > dd > .innerError');
-               if (!$span.length) {
-                       $span = $('<small class="innerError"></span>');
-                       $('#avatarUpload > dd').append($span);
-               }
-               
-               return $span;
-       },
-       
-       /**
-        * @see WCF.Upload._getParameters()
-        */
-       _getParameters: function() {
-               return {
-                       userID: this._userID
-               };
-       },
-});
-
-/**
- * Generic implementation for grouped user lists.
- * 
- * @param      string          className
- * @param      string          dialogTitle
- * @param      object          additionalParameters
- */
-WCF.User.List = Class.extend({
-       /**
-        * list of additional parameters
-        * @var object
-        */
-       _additionalParameters: { },
-       
-       /**
-        * list of cached pages
-        * @var object
-        */
-       _cache: { },
-       
-       /**
-        * action class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * dialog overlay
-        * @var jQuery
-        */
-       _dialog: null,
-       
-       /**
-        * dialog title
-        * @var string
-        */
-       _dialogTitle: '',
-       
-       /**
-        * page count
-        * @var integer
-        */
-       _pageCount: 0,
-       
-       /**
-        * current page no
-        * @var integer
-        */
-       _pageNo: 1,
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * Initializes a new grouped user list.
-        * 
-        * @param       string          className
-        * @param       string          dialogTitle
-        * @param       object          additionalParameters
-        */
-       init: function(className, dialogTitle, additionalParameters) {
-               this._additionalParameters = additionalParameters || { };
-               this._cache = { };
-               this._className = className;
-               this._dialog = null;
-               this._dialogTitle = dialogTitle;
-               this._pageCount = 0;
-               this._pageNo = 1;
-               
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-       },
-       
-       /**
-        * Opens the dialog overlay.
-        */
-       open: function() {
-               this._pageNo = 1;
-               this._showPage();
-       },
-       
-       /**
-        * Displays the specified page.
-        * 
-        * @param       object          event
-        * @param       object          data
-        */
-       _showPage: function(event, data) {
-               if (data && data.activePage) {
-                       this._pageNo = data.activePage;
-               }
-               
-               if (this._pageCount != 0 && (this._pageNo < 1 || this._pageNo > this._pageCount)) {
-                       console.debug("[WCF.User.List] Cannot access page " + this._pageNo + " of " + this._pageCount);
-                       return;
-               }
-               
-               if (this._cache[this._pageNo]) {
-                       var $dialogCreated = false;
-                       if (this._dialog === null) {
-                               //this._dialog = $('<div id="userList' + this._className.hashCode() + '" style="min-width: 600px;" />').hide().appendTo(document.body);
-                               this._dialog = $('<div id="userList' + this._className.hashCode() + '" />').hide().appendTo(document.body);
-                               $dialogCreated = true;
-                       }
-                       
-                       // remove current view
-                       this._dialog.empty();
-                       
-                       // insert HTML
-                       this._dialog.html(this._cache[this._pageNo]);
-                       
-                       // add pagination
-                       if (this._pageCount > 1) {
-                               this._dialog.find('.jsPagination').wcfPages({
-                                       activePage: this._pageNo,
-                                       maxPage: this._pageCount
-                               }).bind('wcfpagesswitched', $.proxy(this._showPage, this));
-                       }
-                       
-                       // show dialog
-                       if ($dialogCreated) {
-                               this._dialog.wcfDialog({
-                                       title: this._dialogTitle
-                               });
-                       }
-                       else {
-                               this._dialog.wcfDialog('open').wcfDialog('render');
-                       }
-               }
-               else {
-                       this._additionalParameters.pageNo = this._pageNo;
-                       
-                       // load template via AJAX
-                       this._proxy.setOption('data', {
-                               actionName: 'getGroupedUserList',
-                               className: this._className,
-                               interfaceName: 'wcf\\data\\IGroupedUserListAction',
-                               parameters: this._additionalParameters
-                       });
-                       this._proxy.sendRequest();
-               }
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               if (data.returnValues.pageCount) {
-                       this._pageCount = data.returnValues.pageCount;
-               }
-               
-               this._cache[this._pageNo] = data.returnValues.template;
-               this._showPage();
-       }
-});
-
-/**
- * Namespace for object watch functions.
- */
-WCF.User.ObjectWatch = {};
-
-/**
- * Handles subscribe/unsubscribe links.
- */
-WCF.User.ObjectWatch.Subscribe = Class.extend({
-       /**
-        * CSS selector for subscribe buttons
-        * @var string
-        */
-       _buttonSelector: '.jsSubscribeButton',
-       
-       /**
-        * list of buttons
-        * @var object
-        */
-       _buttons: { },
-       
-       /**
-        * dialog overlay
-        * @var object
-        */
-       _dialog: null,
-       
-       /**
-        * system notification
-        * @var WCF.System.Notification
-        */
-       _notification: null,
-       
-       /**
-        * WCF.User.ObjectWatch.Subscribe object.
-        */
-       init: function() {
-               this._buttons = { };
-               this._notification = null;
-               
-               // initialize proxy
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               // bind event listeners
-               $(this._buttonSelector).each($.proxy(function(index, button) {
-                       var $button = $(button);
-                       var $objectID = $button.data('objectID');
-                       this._buttons[$objectID] = $button.click($.proxy(this._click, this));
-               }, this));
-       },
-       
-       /**
-        * Handles a click on a subscribe button.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               var $button = $(event.currentTarget);
-               
-               this._proxy.setOption('data', {
-                       actionName: 'manageSubscription',
-                       className: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
-                       parameters: {
-                               objectID: $button.data('objectID'),
-                               objectType: $button.data('objectType')
-                       }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               if (data.actionName === 'manageSubscription') {
-                       if (this._dialog === null) {
-                               this._dialog = $('<div>' + data.returnValues.template + '</div>').hide().appendTo(document.body);
-                               this._dialog.wcfDialog({
-                                       title: WCF.Language.get('wcf.user.objectWatch.manageSubscription')
-                               });
-                       }
-                       else {
-                               this._dialog.html(data.returnValues.template);
-                               this._dialog.wcfDialog('open');
-                       }
-                       
-                       // bind event listener
-                       this._dialog.find('.formSubmit > .jsButtonSave').data('objectID', data.returnValues.objectID).click($.proxy(this._save, this));
-                       var $enableNotification = this._dialog.find('input[name=enableNotification]').disable();
-                       
-                       // toggle subscription
-                       this._dialog.find('input[name=subscribe]').change(function(event) {
-                               var $input = $(event.currentTarget);
-                               if ($input.val() == 1) {
-                                       $enableNotification.enable();
-                               }
-                               else {
-                                       $enableNotification.disable();
-                               }
-                       });
-                       
-                       // setup
-                       var $selectedOption = this._dialog.find('input[name=subscribe]:checked');
-                       if ($selectedOption.length && $selectedOption.val() == 1) {
-                               $enableNotification.enable();
-                       }
-               }
-               else if (data.actionName === 'saveSubscription' && this._dialog.is(':visible')) {
-                       this._dialog.wcfDialog('close');
-                       
-                       if (this._notification === null) {
-                               this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success.edit'));
-                       }
-                       
-                       this._notification.show();
-               }
-       },
-       
-       /**
-        * Saves the subscription.
-        * 
-        * @param       object          event
-        */
-       _save: function(event) {
-               var $button = this._buttons[$(event.currentTarget).data('objectID')];
-               var $subscribe = this._dialog.find('input[name=subscribe]:checked').val();
-               var $enableNotification = (this._dialog.find('input[name=enableNotification]').is(':checked')) ? 1 : 0;
-               
-               this._proxy.setOption('data', {
-                       actionName: 'saveSubscription',
-                       className: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
-                       parameters: {
-                               enableNotification: $enableNotification,
-                               objectID: $button.data('objectID'),
-                               objectType: $button.data('objectType'),
-                               subscribe: $subscribe
-                       }
-               });
-               this._proxy.sendRequest();
-       }
-});
-
-/**
- * Handles inline editing of users.
- */
-WCF.User.InlineEditor = WCF.InlineEditor.extend({
-       /**
-        * list of permissions
-        * @var object
-        */
-       _permissions: { },
-       
-       /**
-        * @see WCF.InlineEditor._execute()
-        */
-       _execute: function(elementID, optionName) {
-               if (!this._validate(elementID, optionName)) {
-                       return false;
-               }
-               
-               var $data = { };
-               var $element = $('#' + elementID);
-               switch (optionName) {
-                       case 'unban':
-                       case 'enableAvatar':
-                       case 'enableSignature':
-                               switch (optionName) {
-                                       case 'unban':
-                                               $data.banned = 0;
-                                       break;
-                                       
-                                       case 'enableAvatar':
-                                               $data.disableAvatar = 0;
-                                       break;
-                                       
-                                       case 'enableSignature':
-                                               $data.disableSignature = 0;
-                                       break;
-                               }
-                               
-                               this._proxy.setOption('data', {
-                                       actionName: optionName,
-                                       className: 'wcf\\data\\user\\UserAction',
-                                       objectIDs: [ $element.data('objectID') ]
-                               });
-                               this._proxy.sendRequest();
-                       break;
-                       
-                       case 'ban':
-                       case 'disableAvatar':
-                       case 'disableSignature':
-                               if (optionName == 'ban') {
-                                       $data.banned = 1;
-                               }
-                               else {
-                                       $data[optionName] = 1;
-                               }
-                               
-                               this._showReasonDialog($element.data('objectID'), optionName);
-                       break;
-                       
-                       case 'advanced':
-                               window.location = this._getTriggerElement($element).attr('href');
-                       break;
-               }
-               
-               if ($.getLength($data)) {
-                       this._updateData.push({
-                               data: $data,
-                               elementID: elementID,
-                       });
-               }
-       },
-       
-       /**
-        * Executes an action with a reason.
-        * 
-        * @param       integer         userID
-        * @param       string          optionName
-        * @param       string          reason
-        */
-       _executeReasonAction: function(userID, optionName, reason) {
-               var $parameters = { };
-               $parameters[optionName + WCF.String.ucfirst('reason')] = reason;
-               
-               this._proxy.setOption('data', {
-                       actionName: optionName,
-                       className: 'wcf\\data\\user\\UserAction',
-                       objectIDs: [ userID ],
-                       parameters: $parameters
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Returns a specific permission.
-        * 
-        * @param       string          permission
-        * @return      integer
-        */
-       _getPermission: function(permission) {
-               if (this._permissions[permission]) {
-                       return this._permissions[permission];
-               }
-               
-               return 0;
-       },
-       
-       /**
-        * @see WCF.InlineEditor._getTriggerElement()
-        */
-       _getTriggerElement: function(element) {
-               return element.find('.jsUserInlineEditor');
-       },
-       
-       /**
-        * @see WCF.InlineEditor._setOptions()
-        */
-       _setOptions: function() {
-               this._options = [
-                       // banning
-                       { label: WCF.Language.get('wcf.user.ban'), optionName: 'ban' },
-                       { label: WCF.Language.get('wcf.user.unban'), optionName: 'unban' },
-                       
-                       // disabling avatar
-                       { label: WCF.Language.get('wcf.user.disableAvatar'), optionName: 'disableAvatar' },
-                       { label: WCF.Language.get('wcf.user.enableAvatar'), optionName: 'enableAvatar' },
-                       
-                       // disabling signature
-                       { label: WCF.Language.get('wcf.user.disableSignature'), optionName: 'disableSignature' },
-                       { label: WCF.Language.get('wcf.user.enableSignature'), optionName: 'enableSignature' },
-                       
-                       // divider
-                       { optionName: 'divider' },
-                       
-                       // overlay
-                       { label: WCF.Language.get('wcf.user.edit'), optionName: 'advanced' }
-               ];
-       },
-       
-       /**
-        * @see WCF.InlineEditor._show()
-        */
-       _show: function(event) {
-               var $element = $(event.currentTarget);
-               var $elementID = $element.data('elementID');
-               
-               if (!this._dropdowns[$elementID]) {
-                       var $dropdownMenu = $element.next('.dropdownMenu');
-                       
-                       if ($dropdownMenu) {
-                               this._dropdowns[$elementID] = $dropdownMenu;
-                               WCF.Dropdown.initDropdown(this._getTriggerElement(this._elements[$elementID]), true);
-                       }
-               }
-               
-               return this._super(event);
-       },
-       
-       /**
-        * Shows the dialog to enter a reason for executing the option with the
-        * given name.
-        * 
-        * @param       string          optionName
-        */
-       _showReasonDialog: function(userID, optionName) {
-               var $languageItem = 'wcf.user.' + optionName + '.reason.description';
-               var $reasonDescription = WCF.Language.get($languageItem);
-               
-               WCF.System.Confirmation.show(WCF.Language.get('wcf.user.' + optionName + '.confirmMessage'), $.proxy(function(action) {
-                       if (action === 'confirm') {
-                               this._executeReasonAction(userID, optionName, $('#wcfSystemConfirmationContent').find('textarea').val());
-                       }
-               }, this), { }, $('<fieldset><dl><dt>' + WCF.Language.get('wcf.global.reason') + '</dt><dd><textarea cols="40" rows="4" />' + ($reasonDescription != $languageItem ? '<small>' + $reasonDescription + '</small>' : '') + '</dd></dl></fieldset>'));
-       },
-       
-       /**
-        * @see WCF.InlineEditor._updateState()
-        */
-       _updateState: function(data) {
-               this._notification.show();
-               
-               for (var $i = 0, $length = this._updateData.length; $i < $length; $i++) {
-                       var $data = this._updateData[$i];
-                       var $element = $('#' + $data.elementID);
-                       
-                       for (var $property in $data.data) {
-                               $element.data($property, $data.data[$property]);
-                       }
-               }
-       },
-       
-       /**
-        * @see WCF.InlineEditor._validate()
-        */
-       _validate: function(elementID, optionName) {
-               var $user = $('#' + elementID);
-               
-               switch (optionName) {
-                       case 'ban':
-                       case 'unban':
-                               if (!this._getPermission('canBanUser')) {
-                                       return false;
-                               }
-                               
-                               if (optionName == 'ban') {
-                                       return !$user.data('banned');
-                               }
-                               else {
-                                       return $user.data('banned');
-                               }
-                       break;
-                       
-                       case 'disableAvatar':
-                       case 'enableAvatar':
-                               if (!this._getPermission('canDisableAvatar')) {
-                                       return false;
-                               }
-                               
-                               if (optionName == 'disableAvatar') {
-                                       return !$user.data('disableAvatar');
-                               }
-                               else {
-                                       return $user.data('disableAvatar');
-                               }
-                       break;
-                       
-                       case 'disableSignature':
-                       case 'enableSignature':
-                               if (!this._getPermission('canDisableSignature')) {
-                                       return false;
-                               }
-                               
-                               if (optionName == 'disableSignature') {
-                                       return !$user.data('disableSignature');
-                               }
-                               else {
-                                       return $user.data('disableSignature');
-                               }
-                       break;
-                       
-                       case 'advanced':
-                               return this._getPermission('canEditUser');
-                       break;
-               }
-               
-               return false;
-       },
-       
-       /**
-        * Sets a permission.
-        * 
-        * @param       string          permission
-        * @param       integer         value
-        */
-       setPermission: function(permission, value) {
-               this._permissions[permission] = value;
-       },
-       
-       /**
-        * Sets permissions.
-        * 
-        * @param       object          permissions
-        */
-       setPermissions: function(permissions) {
-               for (var $permission in permissions) {
-                       this.setPermission($permission, permissions[$permission]);
-               }
-       }
-});
-
-