--- /dev/null
+// WCF.Combined.js created on Wed, 30 Apr 2014 01:34:58 +0200 -- 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, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
+ },
+
+ /**
+ * 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(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/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><b>Test</b>
+ *
+ * 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]);
+ }
+ }
+});
+
+
+// WCF.Combined.min.js created on Wed, 30 Apr 2014 01:34:58 +0200 -- DO NOT EDIT
+
+// included files:
+// - WCF.min.js
+// - WCF.Like.min.js
+// - WCF.ACL.min.js
+// - WCF.Attachment.min.js
+// - WCF.ColorPicker.min.js
+// - WCF.Comment.min.js
+// - WCF.ImageViewer.min.js
+// - WCF.Label.min.js
+// - WCF.Location.min.js
+// - WCF.Message.min.js
+// - WCF.Moderation.min.js
+// - WCF.Poll.min.js
+// - WCF.Search.Message.min.js
+// - WCF.Tagging.min.js
+// - WCF.User.min.js
+
+// WCF.js
(function(){var c=jQuery.fn.data;jQuery.fn.data=function(f,h){if(f){switch(typeof f){case"object":for(var g in f){if(g.match(/ID$/)){var e=f[g];delete f[g];g=g.replace(/ID$/,"-id");f[g]=e}}arguments[0]=f;break;case"string":if(f.match(/ID$/)){arguments[0]=f.replace(/ID$/,"-id")}break}}var d=c.apply(this,arguments);if(f===undefined){for(var g in d){if(g.match(/Id$/)){d[g.replace(/Id$/,"ID")]=d[g];delete d[g]}}}return d};if(!window.console){window.console={}}var b=["log","info","warn","exception","assert","dir","dirxml","trace","group","groupEnd","groupCollapsed","profile","profileEnd","count","clear","time","timeEnd","timeStamp","table","error"];for(var a=0;a<b.length;a++){if(typeof(console[b[a]])==="undefined"){console[b[a]]=function(){}}}if(typeof(console.debug)==="undefined"){console.debug=function(d){console.log(d)}}})();(function(){var d=false,c=/xyz/.test(function(){xyz})?/\b_super\b/:/.*/;this.Class=function(){};Class.extend=function(j){function a(){if(!d&&this.init){this.init.apply(this,arguments)}}var i=this.prototype;d=true;var h=new this;d=false;for(var b in j){h[b]=typeof j[b]=="function"&&typeof i[b]=="function"&&c.test(j[b])?function(f,e){return function(){var k=this._super;this._super=i[f];var g=e.apply(this,arguments);this._super=k;return g}}(b,j[b]):j[b]}a.prototype=h;a.prototype.constructor=a;a.extend=arguments.callee;return a}})();
/*! 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(){var c=window.styleMedia||window.media;if(!c){var a=document.createElement("style"),d=document.getElementsByTagName("script")[0],b=null;a.type="text/css";a.id="matchmediajs-test";d.parentNode.insertBefore(a,d);b="getComputedStyle" in window&&window.getComputedStyle(a,null)||a.currentStyle;c={matchMedium:function(f){var g="@media "+f+"{ #matchmediajs-test { width: 1px; } }";if(a.styleSheet){a.styleSheet.cssText=g}else{a.textContent=g}return b.width==="1px"}}}return function(e){return{matches:c.matchMedium(e||"all"),media:e||"all"}}}());
*/
(function(b,a,d){var c=a.matchMedia;"undefined"!=typeof module&&module.exports?module.exports=d(c):"function"==typeof define&&define.amd?define(function(){return a[b]=d(c)}):a[b]=d(c)})("enquire",this,function(b){function a(k,j){var o,m=0,l=k.length;for(m;l>m&&(o=j(k[m],m),o!==!1);m++){}}function h(e){return"[object Array]"===Object.prototype.toString.apply(e)}function f(e){return"function"==typeof e}function c(e){this.options=e,!e.deferSetup&&this.setup()}function g(j,l){this.query=j,this.isUnconditional=l,this.handlers=[],this.mql=b(j);var k=this;this.listener=function(e){k.mql=e,k.assess()},this.mql.addListener(this.listener)}function d(){if(!b){throw Error("matchMedia not present, legacy browsers require a polyfill")}this.queries={},this.browserIsIncapable=!b("only all").matches}return c.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(e){return this.options===e||this.options.match===e}},g.prototype={addHandler:function(j){var e=new c(j);this.handlers.push(e),this.matches()&&e.on()},removeHandler:function(e){var i=this.handlers;a(i,function(j,k){return j.equals(e)?(j.destroy(),!i.splice(k,1)):void 0})},matches:function(){return this.mql.matches||this.isUnconditional},clear:function(){a(this.handlers,function(e){e.destroy()}),this.mql.removeListener(this.listener),this.handlers.length=0},assess:function(){var e=this.matches()?"on":"off";a(this.handlers,function(j){j[e]()})}},d.prototype={register:function(i,k,l){var j=this.queries,e=l&&this.browserIsIncapable;return j[i]||(j[i]=new g(i,e)),f(k)&&(k={match:k}),h(k)||(k=[k]),a(k,function(m){j[i].addHandler(m)}),this},unregister:function(j,e){var k=this.queries[j];return k&&(e?k.removeHandler(e):(k.clear(),delete this.queries[j])),this}},new d});
/*! head.load - v1.0.3 */
-(function(S,L){function I(){}function K(d,b){if(d){typeof d=="object"&&(d=[].slice.call(d));for(var a=0,c=d.length;a<c;a++){b.call(d,d[a],a)}}}function C(c,a){var b=Object.prototype.toString.call(a).slice(8,-1);return a!==L&&a!==null&&b===c}function M(a){return C("Function",a)}function ag(a){return C("Array",a)}function P(d){var b=d.split("/"),a=b[b.length-1],c=a.indexOf("?");return c!==-1?a.substring(0,c):a}function Z(a){(a=a||I,a._done)||(a(),a._done=1)}function E(h,b,c,a){var d=typeof h=="object"?h:{test:h,success:!b?!1:ag(b)?b:[b],failure:!c?!1:ag(c)?c:[c],callback:a||I},g=!!d.test;return g&&!!d.success?(d.success.push(d.callback),W.load.apply(null,d.success)):g||!d.failure?a():(d.failure.push(d.callback),W.load.apply(null,d.failure)),W}function J(d){var b={},a,c;if(typeof d=="object"){for(a in d){!d[a]||(b={name:a,url:d[a]})}}else{b={name:P(d),url:d}}return(c=ae[b.name],c&&c.url===b.url)?c:(ae[b.name]=b,b)}function H(b){b=b||ae;for(var a in b){if(b.hasOwnProperty(a)&&b[a].state!==T){return !1}}return !0}function q(a){a.state=B;K(a.onpreload,function(b){b.call()})}function R(a){a.state===L&&(a.state=V,a.onpreload=[],F({url:a.url,type:"cache"},function(){q(a)}))}function A(){var d=arguments,a=d[d.length-1],b=[].slice.call(d,1),c=b[0];return(M(a)||(a=null),ag(d[0]))?(d[0].push(a),W.load.apply(null,d[0]),W):(c?(K(b,function(e){M(e)||!e||R(J(e))}),af(J(d[0]),M(c)?c:function(){W.load.apply(null,b)})):af(J(d[0])),W)}function D(){var c=arguments,a=c[c.length-1],b={};return(M(a)||(a=null),ag(c[0]))?(c[0].push(a),W.load.apply(null,c[0]),W):(K(c,function(d){d!==a&&(d=J(d),b[d.name]=d)}),K(c,function(d){d!==a&&(d=J(d),af(d,function(){H(b)&&Z(a)}))}),W)}function af(b,a){if(a=a||I,b.state===T){a();return}if(b.state===aa){W.ready(b.name,a);return}if(b.state===V){b.onpreload.push(function(){af(b,a)});return}b.state=aa;F(b,function(){b.state=T;a();K(X[b.name],function(c){Z(c)});Q&&H()&&K(X.ALL,function(c){Z(c)})})}function j(b){b=b||"";var a=b.split("?")[0].split(".");return a[a.length-1].toLowerCase()}function F(c,b){function l(e){e=e||S.event;a.onload=a.onreadystatechange=a.onerror=null;b()}function n(e){e=e||S.event;(e.type==="load"||/loaded|complete/.test(a.readyState)&&(!N.documentMode||N.documentMode<9))&&(S.clearTimeout(c.errorTimeout),S.clearTimeout(c.cssTimeout),a.onload=a.onreadystatechange=a.onerror=null,b())}function g(){if(c.state!==T&&c.cssRetries<=20){for(var e=0,h=N.styleSheets.length;e<h;e++){if(N.styleSheets[e].href===a.href){n({type:"load"});return}}c.cssRetries++;c.cssTimeout=S.setTimeout(g,250)}}var a,d,k;b=b||I;d=j(c.url);d==="css"?(a=N.createElement("link"),a.type="text/"+(c.type||"css"),a.rel="stylesheet",a.href=c.url,c.cssRetries=0,c.cssTimeout=S.setTimeout(g,500)):(a=N.createElement("script"),a.type="text/"+(c.type||"javascript"),a.src=c.url);a.onload=a.onreadystatechange=n;a.onerror=l;a.async=!1;a.defer=!1;c.errorTimeout=S.setTimeout(function(){l({type:"timeout"})},7000);k=N.head||N.getElementsByTagName("head")[0];k.insertBefore(a,k.lastChild)}function x(){for(var b,a=N.getElementsByTagName("script"),d=0,c=a.length;d<c;d++){if(b=a[d].getAttribute("data-headjs-load"),!!b){W.load(b);return}}}function z(f,b){var a,d,c;return f===N?(Q?Z(b):ad.push(b),W):(M(f)&&(b=f,f="ALL"),ag(f))?(a={},K(f,function(e){a[e]=ae[e];W.ready(e,function(){H(a)&&Z(b)})}),W):typeof f!="string"||!M(b)?W:(d=ae[f],d&&d.state===T||f==="ALL"&&H()&&Q)?(Z(b),W):(c=X[f],c?c.push(b):c=X[f]=[b],W)}function ab(){if(!N.body){S.clearTimeout(W.readyTimeout);W.readyTimeout=S.setTimeout(ab,50);return}Q||(Q=!0,x(),K(ad,function(a){Z(a)}))}function U(){N.addEventListener?(N.removeEventListener("DOMContentLoaded",U,!1),ab()):N.readyState==="complete"&&(N.detachEvent("onreadystatechange",U),ab())}var N=S.document,ad=[],X={},ae={},G="async" in N.createElement("script")||"MozAppearance" in N.documentElement.style||S.opera,Q,Y=S.head_conf&&S.head_conf.head||"head",W=S[Y]=S[Y]||function(){W.ready.apply(null,arguments)},V=1,B=2,aa=3,T=4,O;if(N.readyState==="complete"){ab()}else{if(N.addEventListener){N.addEventListener("DOMContentLoaded",U,!1),S.addEventListener("load",ab,!1)}else{N.attachEvent("onreadystatechange",U);S.attachEvent("onload",ab);O=!1;try{O=!S.frameElement&&N.documentElement}catch(ac){}O&&O.doScroll&&function m(){if(!Q){try{O.doScroll("left")}catch(a){S.clearTimeout(W.readyTimeout);W.readyTimeout=S.setTimeout(m,50);return}ab()}}()}}W.load=W.js=G?D:A;W.test=E;W.ready=z;W.ready(N,function(){H()&&K(X.ALL,function(a){Z(a)});W.feature&&W.feature("domloaded",!0)})})(window);String.prototype.hashCode=function(){var a;var b=0;if(this.length){for(var d=0,c=this.length;d<c;d++){a=this.charCodeAt(d);b=((b<<5)-b)+a;b=b&b}}return b};function shuffle(d){var c=d.length,b,a;while(0!==c){a=Math.floor(Math.random()*c);c-=1;b=d[c];d[c]=d[a];d[a]=b}return this}(function(){var c=navigator.userAgent.toLowerCase();var b=/(chrome)[ \/]([\w.]+)/.exec(c)||/(webkit)[ \/]([\w.]+)/.exec(c)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(c)||/(msie) ([\w.]+)/.exec(c)||c.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(c)||[];var a={browser:b[1]||"",version:b[2]||"0"};browser={};if(a.browser){browser[a.browser]=true;browser.version=a.version}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));jQuery.browser.smartphone=($("html").css("caption-side")=="bottom");jQuery.browser.editor="redactor";jQuery.browser.ckeditor=false;jQuery.browser.redactor=true;if(jQuery.browser.mozilla&&c.match(/trident/)){jQuery.browser.mozilla=false;jQuery.browser.msie=true}})();(function(b){(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(b)||/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(b.substr(0,4))})(navigator.userAgent||navigator.vendor||window.opera);var WCF={};$.extend(true,{removeArrayValue:function(b,a){return $.grep(b,function(d,c){return a!==d})},wcfEscapeID:function(a){return a.replace(/(:|\.)/g,"\\$1")},wcfIsset:function(a){return !!$("#"+$.wcfEscapeID(a)).length},getLength:function(a){var c=0;for(var b in a){if(a.hasOwnProperty(b)){c++}}return c}});$.fn.extend({getTagName:function(){return(this.length)?this.get(0).tagName.toLowerCase():""},getDimensions:function(b){var c=css={};var a=false;if(this.is(":hidden")){css=WCF.getInlineCSS(this);a=true;this.css({display:"block",visibility:"hidden"})}switch(b){case"inner":c={height:this.innerHeight(),width:this.innerWidth()};break;case"outer":c={height:this.outerHeight(),width:this.outerWidth()};break;default:c={height:this.height(),width:this.width()};break}if(a){WCF.revertInlineCSS(this,css,["display","visibility"])}return c},getOffsets:function(b){var c=css={};var a=false;if(this.is(":hidden")){css=WCF.getInlineCSS(this);a=true;this.css({display:"block",visibility:"hidden"})}switch(b){case"offset":c=this.offset();break;case"position":default:c=this.position();break}if(a){WCF.revertInlineCSS(this,css,["display","visibility"])}return c},makePositioned:function(a,b){if(a!="absolute"&&a!="fixed"){a="absolute"}var c=this.getOffsets("position");this.css({position:a,left:c.left,margin:0,top:c.top});if(b){this.remove().appentTo("body")}return this},disable:function(){return this.attr("disabled","disabled")},enable:function(){return this.removeAttr("disabled")},wcfIdentify:function(){if(!this.attr("id")){this.attr("id",WCF.getRandomID())}return this.attr("id")},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 c=0;var b=this.get(0);if(document.selection){this.focus();var a=document.selection.createRange();a.moveStart("character",-this.val().length);c=a.text.length}else{if(b.selectionStart||b.selectionStart=="0"){c=parseInt(b.selectionStart)}}return c},setCaret:function(b){if(this.is("input")){if(this.attr("type")!="text"&&this.attr("type")!="password"){return false}}else{if(!this.is("textarea")){return false}}var c=this.get(0);this.focus();if(document.selection){var a=document.selection.createRange();a.moveStart("character",b);a.moveEnd("character",0);a.select()}else{if(c.selectionStart||c.selectionStart=="0"){c.selectionStart=b;c.selectionEnd=b}}return true},wcfDropIn:function(b,c,a){if(!b){b="up"}if(!a||!parseInt(a)){a=200}return this.show(WCF.getEffect(this,"drop"),{direction:b},a,c)},wcfDropOut:function(b,c,a){if(!b){b="down"}if(!a||!parseInt(a)){a=200}return this.hide(WCF.getEffect(this,"drop"),{direction:b},a,c)},wcfBlindIn:function(b,c,a){if(!b){b="vertical"}if(!a||!parseInt(a)){a=200}return this.show(WCF.getEffect(this,"blind"),{direction:b},a,c)},wcfBlindOut:function(b,c,a){if(!b){b="vertical"}if(!a||!parseInt(a)){a=200}return this.hide(WCF.getEffect(this,"blind"),{direction:b},a,c)},wcfHighlight:function(a,b){return this.effect("highlight",a,600,b)},wcfFadeIn:function(b,a){if(!a||!parseInt(a)){a=200}return this.show(WCF.getEffect(this,"fade"),{},a,b)},wcfFadeOut:function(b,a){if(!a||!parseInt(a)){a=200}return this.hide(WCF.getEffect(this,"fade"),{},a,b)}});$.extend(WCF,{activeDialogs:0,_idCounter:0,getRandomID:function(){var a="";do{a="wcf"+this._idCounter++}while($.wcfIsset(a));return a},inArray:function(b,a){return($.inArray(b,a)!=-1)},getEffect:function(a,b){if(a.is("tr")){return"highlight"}return b},getInlineCSS:function(b){var c={};var a=b.attr("style");if(!a){return{}}a=a.split(";");for(var f=0,d=a.length;f<d;f++){var e=$.trim(a[f]);if(e==""){continue}e=e.split(":");c[$.trim(e[0])]=$.trim(e[1])}return c},revertInlineCSS:function(a,c,e){for(var f=0,d=e.length;f<d;f++){var b=e[f];if(c[b]){a.css(b,c[b])}else{a.css(b,"")}}}});WCF.Browser={_isChrome:null,isChrome:function(){if(this._isChrome===null){this._isChrome=false;if(/chrom(e|ium)/.test(navigator.userAgent.toLowerCase())){this._isChrome=true}}return this._isChrome}};WCF.Dropdown={_callbacks:{},_didInit:false,_dropdowns:{},_menuContainer:null,_menus:{},init:function(){if(this._menuContainer===null){this._menuContainer=$('<div id="dropdownMenuContainer" />').appendTo(document.body)}var a=this;$(".dropdownToggle:not(.jsDropdownEnabled)").each(function(b,c){a.initDropdown($(c),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))}},_dialogScroll:function(b){var a=$(b.currentTarget);a.find(".dropdown.dropdownOpen").each(function(d,f){var i=$(f);var e=i.wcfIdentify();var g=i.offset();var c=a.offset();var h=$(f).height()/2;if(g.top+h<=c.top){WCF.Dropdown.toggleDropdown(e)}else{if(g.top>=c.top+a.height()){WCF.Dropdown.toggleDropdown(e)}else{if(g.left<=c.left){WCF.Dropdown.toggleDropdown(e)}else{if(g.left>=c.left+a.width()){WCF.Dropdown.toggleDropdown(e)}else{WCF.Dropdown.setAlignmentByID(i.wcfIdentify())}}}}})},_scroll:function(b){for(var a in this._dropdowns){var c=this._dropdowns[a];if(c.data("isOverlayDropdownButton")&&c.hasClass("dropdownOpen")){this.setAlignmentByID(a)}}},initDropdown:function(b,d){if(b.hasClass("jsDropdownEnabled")||b.data("target")){return}var e=b.parents(".dropdown");if(!e.length){console.debug("[WCF.Dropdown] Invalid dropdown passed, button '"+b.wcfIdentify()+"' does not have a parent with .dropdown, aborting.");return}var a=b.next(".dropdownMenu");if(!a.length){console.debug("[WCF.Dropdown] Invalid dropdown passed, dropdown '"+e.wcfIdentify()+"' does not have a dropdown menu, aborting.");return}a.detach().appendTo(this._menuContainer);var c=e.wcfIdentify();if(!this._dropdowns[c]){b.addClass("jsDropdownEnabled").click($.proxy(this._toggle,this));this._dropdowns[c]=e;this._menus[c]=a}b.data("target",c);if(d){b.trigger("click")}},removeDropdown:function(a){if(this._menus[a]){$(this._menus[a]).remove();delete this._menus[a];delete this._dropdowns[a]}},initDropdownFragment:function(c,a){var b=c.wcfIdentify();if(this._dropdowns[b]){console.debug("[WCF.Dropdown] Cannot register dropdown identified by '"+b+"' as a fragement.");return}this._dropdowns[b]=c;this._menus[b]=a.detach().appendTo(this._menuContainer)},registerCallback:function(a,b){if(!$.isFunction(b)){console.debug("[WCF.Dropdown] Callback for '"+a+"' is invalid");return false}if(!this._callbacks[a]){this._callbacks[a]=[]}this._callbacks[a].push(b)},_toggle:function(g,e){var b=(g===null)?e:$(g.currentTarget).data("target");var a=this._dropdowns[b];if(a&&a.data("isOverlayDropdownButton")===undefined){var d=a.parents(".dialogContent");a.data("isOverlayDropdownButton",d.length>0);if(d.length){d.on("scroll",this._dialogScroll)}}for(var f in this._dropdowns){var h=this._dropdowns[f];var c=this._menus[f];if(h.hasClass("dropdownOpen")){h.removeClass("dropdownOpen");c.removeClass("dropdownOpen");this._notifyCallbacks(f,"close")}else{if(f===b){h.addClass("dropdownOpen");c.addClass("dropdownOpen");this._notifyCallbacks(f,"open");this.setAlignment(h,c)}}}if(g!==null){g.stopPropagation();return false}},toggleDropdown:function(a){this._toggle(null,a)},getDropdown:function(a){if(this._dropdowns[a]){return this._dropdowns[a]}return null},getDropdownMenu:function(a){if(this._menus[a]){return this._menus[a]}return null},setAlignmentByID:function(a){var c=this.getDropdown(a);if(c===null){console.debug("[WCF.Dropdown] Unable to find dropdown identified by '"+a+"'")}var b=this.getDropdownMenu(a);if(b===null){console.debug("[WCF.Dropdown] Unable to find dropdown menu identified by '"+a+"'")}this.setAlignment(c,b)},setAlignment:function(o,f){if(!f.data("isInitialized")){f.data("isInitialized",true).css({left:0,top:0})}var c=o.getDimensions("outer");var j=o.getOffsets("offset");var k=f.getDimensions("outer");var d=$(window).width();var a=o.find(".dropdownToggle");if(a.hasClass("dropdownCaptionTextarea")){c=a.getDimensions("outer")}var h="left";if((j.left+k.width)>d){h="right"}var b="auto";var n="auto";if(h==="left"){f.removeClass("dropdownArrowRight");b=j.left}else{f.addClass("dropdownArrowRight");n=(d-(j.left+c.width))}if(WCF.Language.get("wcf.global.pageDirection")=="rtl"){var l=b;var g=n;if(b=="auto"){f.removeClass("dropdownArrowRight")}else{n=d-(j.left+c.width);b="auto";if(n+k.width>d){b=l;n=g;f.addClass("dropdownArrowRight")}}}if(b=="auto"){n+="px"}else{b+="px"}var m=true;if(f.hasClass("dropdownOpen")){m=false;f.removeClass("dropdownOpen")}var i="auto";var e=j.top+c.height+7;if(e+k.height>$(window).height()+$(document).scrollTop()){i=$(window).height()-j.top+10;e="auto";f.addClass("dropdownArrowBottom")}else{f.removeClass("dropdownArrowBottom")}if(!m){f.addClass("dropdownOpen")}f.css({bottom:i,left:b,right:n,top:e})},_closeAll:function(){for(var a in this._dropdowns){var b=this._dropdowns[a];if(b.hasClass("dropdownOpen")){b.removeClass("dropdownOpen");this._menus[a].removeClass("dropdownOpen");this._notifyCallbacks(a,"close")}}},close:function(a){if(!this._dropdowns[a]){return}this._dropdowns[a].removeClass("dropdownMenu");this._menus[a].removeClass("dropdownMenu")},_notifyCallbacks:function(a,b){if(!this._callbacks[a]){return}for(var d=0,c=this._callbacks[a].length;d<c;d++){this._callbacks[a][d](a,b)}}};WCF.Clipboard={_actionProxy:null,_actionObjects:{},_containers:null,_containerData:{},_hasMarkedItems:false,_markedObjectIDs:{},_page:"",_pageObjectID:0,_proxy:null,_trackedElements:{},init:function(d,b,e,c){this._page=d;this._actionObjects=e||{};this._hasMarkedItems=(b>0);this._pageObjectID=parseInt(c)||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});this._containers=$(".jsClipboardContainer").each($.proxy(function(g,f){this._initContainer(f)},this));if(this._hasMarkedItems&&this._containers.length){this._loadMarkedItems()}var a=this;WCF.DOMNodeInsertedHandler.addCallback("WCF.Clipboard",function(){a._containers=$(".jsClipboardContainer").each($.proxy(function(g,f){a._initContainer(f)},a))})},_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})},reload:function(){if(this._containers===null){return}this._loadMarkedItems()},_loadMarkedItemsSuccess:function(d,f,c){this._resetMarkings();for(var a in d.markedItems){if(!this._markedObjectIDs[a]){this._markedObjectIDs[a]=[]}var b=d.markedItems[a];for(var e in b){this._markedObjectIDs[a].push(b[e])}this._containers.each($.proxy(function(h,g){var i=$(g);if(i.data("type")!=a){return true}i.find("input.jsClipboardItem").each($.proxy(function(l,k){var j=$(k);if(WCF.inArray(j.data("objectID"),this._markedObjectIDs[a])){j.prop("checked",true);j.parents(".jsClipboardObject").addClass("jsMarked")}},this));i.find("input.jsClipboardMarkAll").each(function(l,k){var j=true;i.find("input.jsClipboardItem").each(function(n,o){var m=$(o);if(!m.prop("checked")){j=false}});if(j){$(k).prop("checked",true)}})},this))}this._success(d,f,c)},_resetMarkings:function(){this._containers.each($.proxy(function(b,a){var c=$(a);this._markedObjectIDs[c.data("type")]=[];c.find("input.jsClipboardItem, input.jsClipboardMarkAll").prop("checked",false);c.find(".jsClipboardObject").removeClass("jsMarked")},this))},_initContainer:function(a){var c=$(a);var b=c.wcfIdentify();if(!this._trackedElements[b]){c.find(".jsClipboardMarkAll").data("hasContainer",b).click($.proxy(this._markAll,this));this._markedObjectIDs[c.data("type")]=[];this._containerData[c.data("type")]={};$.each(c.data(),$.proxy(function(d,e){if(d.match(/^type(.+)/)){this._containerData[c.data("type")][WCF.String.lcfirst(d.replace(/^type/,""))]=e}},this));this._trackedElements[b]=[]}c.find("input.jsClipboardItem").each($.proxy(function(f,e){var g=$(e);var d=g.wcfIdentify();if(!WCF.inArray(d,this._trackedElements[b])){this._trackedElements[b].push(d);g.data("hasContainer",b).click($.proxy(this._click,this))}},this))},_click:function(f){var b=$(f.target);var e=b.data("objectID");var h=(b.prop("checked"))?true:false;var d=[e];if(b.data("hasContainer")){var g=$("#"+b.data("hasContainer"));var a=g.data("type")}else{var a=b.data("type")}if(h){this._markedObjectIDs[a].push(e);b.parents(".jsClipboardObject").addClass("jsMarked")}else{this._markedObjectIDs[a]=$.removeArrayValue(this._markedObjectIDs[a],e);b.parents(".jsClipboardObject").removeClass("jsMarked")}if(b.data("hasContainer")){var c=true;g.find("input.jsClipboardItem").each(function(j,i){var k=$(i);if(!k.prop("checked")){c=false}});g.find(".jsClipboardMarkAll").each(function(i,j){if(c){$(j).prop("checked",true)}else{$(j).prop("checked",false)}})}this._saveState(a,d,h)},_markAll:function(d){var b=$(d.target);var c=[];var f=true;if(b.is("input")){f=b.prop("checked")}if(b.data("hasContainer")){var e=$("#"+b.data("hasContainer"));var a=e.data("type")}else{var a=b.data("type")}if(b.data("hasContainer")){e.find("input.jsClipboardItem").each($.proxy(function(h,g){var j=$(g);var i=j.data("objectID");if(f){if(!j.prop("checked")){j.prop("checked",true);this._markedObjectIDs[a].push(i);c.push(i)}}else{if(j.prop("checked")){j.prop("checked",false);this._markedObjectIDs[a]=$.removeArrayValue(this._markedObjectIDs[a],i);c.push(i)}}},this));if(f){e.find(".jsClipboardObject").addClass("jsMarked")}else{e.find(".jsClipboardObject").removeClass("jsMarked")}}this._saveState(a,c,f)},_saveState:function(b,c,a){this._proxy.setOption("data",{action:(a)?"mark":"unmark",containerData:this._containerData,objectIDs:c,pageClassName:this._page,pageObjectID:this._pageObjectID,type:b});this._proxy.sendRequest()},_success:function(data,textStatus,jqXHR){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()});if(!data.items){return}for(var $typeName in data.items){if(!$containers[$typeName]){continue}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);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);$listItem.click($.proxy(this._executeAction,this))}$('<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));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)}},_closeLists:function(){$(".jsClipboardEditor ul").removeClass("dropdownOpen")},_executeAction:function(e){var c=$(e.currentTarget);var d=c.data("url");if(d){window.location.href=d}if(c.data("parameters").className&&c.data("parameters").actionName){if(c.data("parameters").actionName==="unmarkAll"||c.data("parameters").objectIDs){var b=c.data("internalData")["confirmMessage"];if(b){var a=c.data("internalData")["template"];if(a){a=$(a)}WCF.System.Confirmation.show(b,$.proxy(function(g){if(g==="confirm"){var f={};if(a&&a.length){$("#wcfSystemConfirmationContent").find("input, select, textarea").each(function(i,j){var h=$(j);f[h.prop("name")]=h.val()})}this._executeAJAXActions(c,f)}},this),"",a)}else{this._executeAJAXActions(c,{})}}}c.data("container").trigger("clipboardAction",[c.data("type"),c.data("actionName"),c.data("parameters")])},_executeAJAXActions:function(d,e){e=e||{};var a=[];if(d.data("parameters").actionName!=="unmarkAll"){$.each(d.data("parameters").objectIDs,function(g,h){a.push(parseInt(h))})}var b={data:e,containerData:this._containerData[d.data("type")]};var f=d.data("internalData")["parameters"];if(f!==undefined){for(var c in f){b[c]=f[c]}}new WCF.Action.Proxy({autoSend:true,data:{actionName:d.data("parameters").actionName,className:d.data("parameters").className,objectIDs:a,parameters:b},success:$.proxy(function(g){if(d.data("parameters").actionName!=="unmarkAll"){d.data("container").trigger("clipboardActionResponse",[g,d.data("type"),d.data("actionName"),d.data("parameters")])}this._loadMarkedItems()},this)});if(this._actionObjects[d.data("objectType")]&&this._actionObjects[d.data("objectType")][d.data("parameters").actionName]){this._actionObjects[d.data("objectType")][d.data("parameters").actionName].triggerEffect(a)}},sendRequest:function(b){var a=$(b);this._actionProxy.setOption("data",{parameters:a.data("parameters"),typeName:a.data("type")});this._actionProxy.sendRequest()}};WCF.PeriodicalExecuter=Class.extend({_callback:null,_delay:0,_intervalID:null,_isExecuting:false,init:function(b,a){if(!$.isFunction(b)){console.debug("[WCF.PeriodicalExecuter] Given callback is invalid, aborting.");return}this._callback=b;this._interval=a;this.resume()},_execute:function(){if(!this._isExecuting){try{this._isExecuting=true;this._callback(this);this._isExecuting=false}catch(a){this._isExecuting=false;throw a}}},stop:function(){if(!this._intervalID){return}clearInterval(this._intervalID)},resume:function(){if(this._intervalID){this.stop()}this._intervalID=setInterval($.proxy(this._execute,this),this._interval)}});WCF.LoadingOverlayHandler={_activeRequests:0,_loadingOverlay:null,_pending:null,show:function(){if(this._loadingOverlay===null){this._loadingOverlay=$('<div class="spinner"><span class="icon icon48 icon-spinner" /> <span>'+WCF.Language.get("wcf.global.loading")+"</span></div>").appendTo($("body"));var b=this._loadingOverlay.outerWidth();if(b<70){b=70}this._loadingOverlay.css({marginLeft:Math.ceil(-1*b/2),width:b}).hide()}this._activeRequests++;if(this._activeRequests==1){if(this._pending===null){var a=this;this._pending=new WCF.PeriodicalExecuter(function(c){if(a._activeRequests){a._loadingOverlay.stop(true,true).fadeIn(100)}c.stop();a._pending=null},250)}}},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)}},updateIcon:function(b,c){var a=(c===undefined||c?"addClass":"removeClass");b.find(".icon")[a]("icon-spinner");if(b.hasClass("icon")){b[a]("icon-spinner")}}};WCF.Action={};WCF.Action.Proxy=Class.extend({_showLoadingOverlayOnce:false,_suppressErrors:false,_lastRequest:null,init:function(b){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},b);this.confirmationDialog=null;this.loading=null;this._showLoadingOverlayOnce=false;this._suppressErrors=(this.options.suppressErrors===true);if(this.options.autoSend){this.sendRequest()}var a=this;$(window).on("beforeunload",function(){a._suppressErrors=true})},sendRequest:function(a){this._init();if(a||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},abortPrevious:function(){if(this._lastRequest!==null){this._lastRequest.abort();this._lastRequest=null}},showLoadingOverlayOnce:function(){this._showLoadingOverlayOnce=true},suppressErrors:function(){this._suppressErrors=true},_init:function(){if($.isFunction(this.options.init)){this.options.init(this)}if(this.options.showLoadingOverlay||this._showLoadingOverlayOnce){WCF.LoadingOverlayHandler.show()}},_failure:function(d,i,h){if(i=="abort"){if($.isFunction(this.options.aborted)){this.options.aborted(d)}return}try{var c=$.parseJSON(d.responseText);var f=true;if($.isFunction(this.options.failure)){f=this.options.failure(c,d,i,h)}if(!this._suppressErrors&&f!==false){var a="";if(c.stacktrace){a="<br /><p>Stacktrace:</p><p>"+c.stacktrace+"</p>"}else{if(c.exceptionID){a="<br /><p>Exception ID: <code>"+c.exceptionID+"</code></p>"}}$('<div class="ajaxDebugMessage"><p>'+c.message+"</p>"+a+"</div>").wcfDialog({title:WCF.Language.get("wcf.global.error.title")})}}catch(g){var f=true;if($.isFunction(this.options.failure)){f=this.options.failure(null,d,i,h)}if(!this._suppressErrors&&f!==false){var b=(i==="timeout")?WCF.Language.get("wcf.global.error.timeout"):d.responseText;if(b&&b!="undefined"){$('<div class="ajaxDebugMessage"><p>'+b+"</p></div>").wcfDialog({title:WCF.Language.get("wcf.global.error.title")})}}}this._after()},_success:function(b,c,a){if($.isFunction(this.options.success)){if(b&&b.returnValues&&b.returnValues.template!==undefined){b.returnValues.template=$.trim(b.returnValues.template)}this.options.success(b,c,a)}this._after()},_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();$("a[href*=#]").each(function(c,e){var b=$(e);if(b.prop("href").indexOf("AJAXProxy")!=-1){var d=b.prop("href").substr(b.prop("href").indexOf("#"));var a=document.location.toString().replace(/#.*/,"");b.prop("href",a+d)}})},setOption:function(a,b){this.options[a]=b}});WCF.Action.SimpleProxy=Class.extend({init:function(a,b){this.options=$.extend(true,{action:"",className:"",elements:null,eventName:"click"},a);this.callbacks=$.extend(true,{after:null,failure:null,init:null,success:null},b);if(!this.options.elements){return}this.proxy=new WCF.Action.Proxy(this.callbacks);this.options.elements.each($.proxy(function(c,d){$(d).bind(this.options.eventName,$.proxy(this._handleEvent,this))},this))},_handleEvent:function(a){this.proxy.setOption("data",{actionName:this.options.action,className:this.options.className,objectIDs:[$(a.target).data("objectID")]});this.proxy.sendRequest()}});WCF.Action.Delete=Class.extend({_buttonSelector:"",_className:"",_containerSelector:"",_containers:[],init:function(c,a,b){this._containerSelector=a;this._className=c;this._buttonSelector=(b)?b:".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))},_initElements:function(){var a=this;$(this._containerSelector).each(function(c,b){var e=$(b);var d=e.wcfIdentify();if(!WCF.inArray(d,a._containers)){a._containers.push(d);e.find(a._buttonSelector).click($.proxy(a._click,a))}})},_click:function(b){var a=$(b.currentTarget);b.preventDefault();if(a.data("confirmMessage")){WCF.System.Confirmation.show(a.data("confirmMessage"),$.proxy(this._execute,this),{target:a})}else{WCF.LoadingOverlayHandler.updateIcon(a);this._sendRequest(a)}},_didTriggerEffect:function(a){},_execute:function(b,a){if(b==="cancel"){return}WCF.LoadingOverlayHandler.updateIcon(a.target);this._sendRequest(a.target)},_sendRequest:function(a){this.proxy.setOption("data",{actionName:"delete",className:this._className,interfaceName:"wcf\\data\\IDeleteAction",objectIDs:[$(a).data("objectID")]});this.proxy.sendRequest()},_success:function(b,c,a){this.triggerEffect(b.objectIDs)},triggerEffect:function(c){for(var a in this._containers){var d=$("#"+this._containers[a]);if(WCF.inArray(d.find(this._buttonSelector).data("objectID"),c)){var b=this;d.wcfBlindOut("up",function(){$(this).remove();b._containers.splice(b._containers.indexOf($(this).wcfIdentify()),1);b._didTriggerEffect($(this))})}}}});WCF.Action.NestedDelete=WCF.Action.Delete.extend({triggerEffect:function(c){for(var a in this._containers){var d=$("#"+this._containers[a]);if(WCF.inArray(d.find(this._buttonSelector).data("objectID"),c)){if(d.has("ol").has("li").length){if(d.is(":only-child")){d.parent().replaceWith(d.find("> ol"))}else{d.replaceWith(d.find("> ol > li"))}this._containers.splice(this._containers.indexOf(d.wcfIdentify()),1);this._didTriggerEffect(d)}else{var b=this;d.wcfBlindOut("up",function(){$(this).remove();b._containers.splice(b._containers.indexOf($(this).wcfIdentify()),1);b._didTriggerEffect($(this))})}}}}});WCF.Action.Toggle=Class.extend({_buttonSelector:".jsToggleButton",_className:"",_containerSelector:"",_containers:[],init:function(d,a,c){this._containerSelector=a;this._className=d;this._buttonSelector=(c)?c:".jsToggleButton";this._containers=[];var b={success:$.proxy(this._success,this)};this.proxy=new WCF.Action.Proxy(b);this._initElements();WCF.DOMNodeInsertedHandler.addCallback("WCF.Action.Toggle"+this._className.hashCode(),$.proxy(this._initElements,this))},_initElements:function(){$(this._containerSelector).each($.proxy(function(b,a){var d=$(a);var c=d.wcfIdentify();if(!WCF.inArray(c,this._containers)){this._containers.push(c);d.find(this._buttonSelector).click($.proxy(this._click,this))}},this))},_click:function(b){var a=$(b.currentTarget);b.preventDefault();if(a.data("confirmMessage")){WCF.System.Confirmation.show(a.data("confirmMessage"),$.proxy(this._execute,this),{target:a})}else{WCF.LoadingOverlayHandler.updateIcon(a);this._sendRequest(a)}},_execute:function(b,a){if(b==="cancel"){return}WCF.LoadingOverlayHandler.updateIcon(a.target);this._sendRequest(a.target)},_sendRequest:function(a){this.proxy.setOption("data",{actionName:"toggle",className:this._className,interfaceName:"wcf\\data\\IToggleAction",objectIDs:[$(a).data("objectID")]});this.proxy.sendRequest()},_success:function(b,c,a){this.triggerEffect(b.objectIDs)},triggerEffect:function(b){for(var a in this._containers){var d=$("#"+this._containers[a]);var c=d.find(this._buttonSelector);if(WCF.inArray(c.data("objectID"),b)){d.wcfHighlight();this._toggleButton(d,c)}}},_toggleButton:function(b,a){WCF.LoadingOverlayHandler.updateIcon(a,false);if(a.hasClass("icon-check-empty")){a.removeClass("icon-check-empty").addClass("icon-check");$newTitle=(a.data("disableTitle")?a.data("disableTitle"):WCF.Language.get("wcf.global.button.disable"));a.attr("title",$newTitle)}else{a.removeClass("icon-check").addClass("icon-check-empty");$newTitle=(a.data("enableTitle")?a.data("enableTitle"):WCF.Language.get("wcf.global.button.enable"));a.attr("title",$newTitle)}b.toggleClass("disabled")}});WCF.Action.Scroll=Class.extend({_callback:null,_reference:null,_target:null,_threshold:0,init:function(b,d,a,c){this._threshold=parseInt(b);if(this._threshold===0){console.debug("[WCF.Action.Scroll] Given threshold is invalid, aborting.");return}if($.isFunction(d)){this._callback=d}if(this._callback===null){console.debug("[WCF.Action.Scroll] Given callback is invalid, aborting.");return}this._reference=$((a)?a:window);this._target=$((c)?c:document);this.start();this._scroll()},_scroll:function(){var a=this._target.height();var b=this._reference.scrollTop();var c=this._reference.height();if((a-(c+b))<this._threshold){this._callback(this)}},start:function(){this._reference.on("scroll",$.proxy(this._scroll,this))},stop:function(){this._reference.off("scroll")}});WCF.Date={};WCF.Date.Picker={_dateFormat:"yy-mm-dd",_timeFormat:"g:ia",init:function(){var a=$.timepicker.log;$.timepicker.log=function(b){if(b.indexOf("Error parsing the date/time string: Unexpected literal at position")==-1&&b.indexOf("Error parsing the date/time string: Unknown name at position")==-1){a(b)}};this._convertDateFormat();this._initDatePicker();WCF.DOMNodeInsertedHandler.addCallback("WCF.Date.Picker",$.proxy(this._initDatePicker,this))},_convertDateFormat:function(){var a={a:"tt",A:"TT",g:"h",G:"H",h:"hh",H:"HH",i:"mm",s:"ss",u:"l",d:"dd",D:"D",j:"d",l:"DD",z:"o",S:"",F:"MM",m:"mm",M:"M",n:"m",o:"yy",Y:"yy",y:"y",U:"@"};this._dateFormat=WCF.Language.get("wcf.date.dateFormat").replace(/([^dDjlzSFmMnoYyU\\]*(?:\\.[^dDjlzSFmMnoYyU\\]*)*)([dDjlzSFmMnoYyU])/g,function(c,f,e,g,b){for(var d in a){if(e==d){e=a[d]}}return f+e});this._timeFormat=WCF.Language.get("wcf.date.timeFormat").replace(/([^aAgGhHisu\\]*(?:\\.[^aAgGhHisu\\]*)*)([aAgGhHisu])/g,function(c,f,e,g,b){for(var d in a){if(e==d){e=a[d]}}return f+e})},_initDatePicker:function(){$("input[type=date]:not(.jsDatePicker), input[type=datetime]:not(.jsDatePicker)").each($.proxy(function(d,g){var f=$(g);var b=f.prop("name");var j=f.val();var a=f.attr("type")=="datetime";f.prop("type","text").addClass("jsDatePicker");if(f.data("placeholder")){f.attr("placeholder",f.data("placeholder"))}f.removeAttr("name");f.before('<input type="hidden" id="'+f.wcfIdentify()+'DatePicker" name="'+b+'" value="'+j+'" />');var c=f.attr("max")?new Date(f.attr("max").replace(" ","T")):null;var k=f.attr("min")?new Date(f.attr("min").replace(" ","T")):null;$options={altField:"#"+f.wcfIdentify()+"DatePicker",altFormat:"yy-mm-dd",beforeShow:function(n,m){setTimeout(function(){m.dpDiv.position({my:"left top",at:"left bottom",collision:"none",of:n})},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:c,minDate:k,monthNames:WCF.Language.get("__months"),monthNamesShort:WCF.Language.get("__monthsShort"),showButtonPanel:false,onClose:function(n,m){if(n==""){$(m.settings.altField).val(n)}},showOtherMonths:true,yearRange:(f.hasClass("birthday")?"-100:+0":"1900:2038")};if(a){if(/[0-9]{2}:[0-9]{2}:[0-9]{2}$/.test(j)){j=j.replace(/:[0-9]{2}$/,"");f.val(j)}j=j.replace(" ","T");if(f.data("ignoreTimezone")){var i=new Date().getTimezoneOffset();var l=(i>0)?"-":"+";i=Math.abs(i);var e=(Math.floor(i/60)).toString();var h=(i%60).toString();l+=(e.length==2)?e:"0"+e;l+=":";l+=(h.length==2)?h:"0"+h;j=j.replace(/[+-][0-9]{2}:[0-9]{2}$/,l)}$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:(f.hasClass("birthday")?"-100:+0":"1900:2038")})}if(a){f.datetimepicker($options)}else{f.datepicker($options)}if(j){if(!a){j=new Date(j);j.setMinutes(j.getMinutes()+j.getTimezoneOffset())}f.datepicker("setDate",j)}f.datepicker("widget").hide()},this))}};WCF.Date.Util={gmdate:function(a){var b=(a)?a:new Date();return Math.round(Date.UTC(b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDay(),b.getUTCHours(),b.getUTCMinutes(),b.getUTCSeconds())/1000)},getTimezoneDate:function(b,c){var d=new Date(b);var a=d.getTimezoneOffset()*60000;return new Date((b+a+c))}};WCF.Date.Time=Class.extend({_date:0,_elements:null,_offset:null,_timestamp:0,init:function(){this._elements=$("time.datetime");this._offset=null;this._timestamp=0;this._refresh();new WCF.PeriodicalExecuter($.proxy(this._refresh,this),60000);WCF.DOMNodeInsertedHandler.addCallback("WCF.Date.Time",$.proxy(this._domNodeInserted,this))},_domNodeInserted:function(){this._elements=$("time.datetime");this._refresh()},_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))},_refreshElement:function(i,f){var n=$(f);if(!n.attr("title")){n.attr("title",n.text())}var d=n.data("timestamp")+this._offset;var b=n.data("date");var c=n.data("time");var o=n.data("offset");if(n.data("isFutureDate")){return}if(d>=this._timestamp||this._timestamp<(d+60)){n.text(WCF.Language.get("wcf.date.relative.now"))}else{if(this._timestamp<(d+3540)){var m=Math.max(Math.round((this._timestamp-d)/60),1);n.text(WCF.Language.get("wcf.date.relative.minutes",{minutes:m}))}else{if(this._timestamp<(d+86400)){var k=Math.round((this._timestamp-d)/3600);n.text(WCF.Language.get("wcf.date.relative.hours",{hours:k}))}else{if(this._timestamp<(d+518400)){var j=new Date(this._date.getFullYear(),this._date.getMonth(),this._date.getDate());var l=Math.ceil((j/1000-d)/86400);var g=WCF.Date.Util.getTimezoneDate((d*1000),o*1000);var h=g.getDay();var e=WCF.Language.get("__days")[h];n.text(WCF.Language.get("wcf.date.relative.pastDays",{days:l,day:e,time:c}))}else{var a=WCF.Language.get("wcf.date.shortDateTimeFormat");n.text(a.replace(/\%date\%/,b).replace(/\%time\%/,c))}}}}}});WCF.Dictionary=Class.extend({_variables:{},init:function(){this._variables={}},add:function(a,b){this._variables[a]=b},addObject:function(a){for(var b in a){this.add(b,a[b])}},addDictionary:function(a){a.each($.proxy(function(b){this.add(b.key,b.value)},this))},get:function(a){if(this.isset(a)){return this._variables[a]}return null},isset:function(a){return this._variables.hasOwnProperty(a)},remove:function(a){delete this._variables[a]},each:function(d){if(!$.isFunction(d)){return}for(var c in this._variables){var a=this._variables[c];var b={key:c,value:a};d(b)}},count:function(){return $.getLength(this._variables)},isEmpty:function(){return !this.count()}});WCF.Language={_variables:new WCF.Dictionary(),add:function(a,b){this._variables.add(a,b)},addObject:function(a){this._variables.addObject(a)},get:function(a,b){if(b==null){var b={}}var c=this._variables.get(a);if(c===null){return a}else{if(typeof c==="string"){this.add(a,new WCF.Template(c));return this.get(a,b)}else{if(typeof c.fetch==="function"){c=c.fetch(b)}}}return c}};WCF.MultipleLanguageInput=Class.extend({_availableLanguages:{},_button:null,_didInit:false,_element:null,_insertedDataAfterInit:false,_isEnabled:false,_forceSelection:false,_languageID:0,_list:null,_values:null,init:function(c,b,d,f){this._button=null;this._element=$("#"+$.wcfEscapeID(c));this._forceSelection=b;this._values=d;this._availableLanguages=f;if($.getLength(this._values)){for(var e in this._values){this._values[e]=WCF.String.unescapeHTML(this._values[e])}}this._languageID=LANGUAGE_ID;if(this._element.length==0){console.debug("[WCF.MultipleLanguageInput] element id '"+c+"' is unknown");return}var a=($.getLength(this._values)>0)?true:false;this._insertedDataAfterInit=a;this._prepareElement(a);this._element.parents("form").submit($.proxy(this._submit,this));this._didInit=true},_prepareElement:function(a){this._element.wrap('<div class="dropdown preInput" />');var c=this._element.parent();this._button=$('<p class="button dropdownToggle"><span>'+WCF.Language.get("wcf.global.button.disabledI18n")+"</span></p>").prependTo(c);this._list=$('<ul class="dropdownMenu"></ul>').insertAfter(this._button);if(this._button.nextAll("textarea").length){this._button.addClass("dropdownCaptionTextarea")}else{this._button.addClass("dropdownCaption")}for(var b in this._availableLanguages){$("<li><span>"+this._availableLanguages[b]+"</span></li>").data("languageID",b).click($.proxy(this._changeLanguage,this)).appendTo(this._list)}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,a);if(a||this._forceSelection){this._isEnabled=true;this._list.children("li").each($.proxy(function(d,f){var e=$(f);if(e.data("languageID")==this._languageID){e.trigger("click")}},this))}WCF.Dropdown.registerCallback(c.wcfIdentify(),$.proxy(this._handleAction,this))},_handleAction:function(a,b){if(b==="open"){this._enable()}else{this._closeSelection()}},_enable:function(a){if(!this._isEnabled){var b=(this._button.is("p"))?this._button.children("span:eq(0)"):this._button;b.addClass("active");this._isEnabled=true}if(this._list.is(":visible")){this._showSelection()}},_showSelection:function(){if(this._isEnabled){this._list.children("li").each($.proxy(function(b,d){var c=$(d);var a=c.data("languageID");if(a){if(this._values[a]&&this._values[a]!=""){c.removeClass("missingValue")}else{c.addClass("missingValue")}}},this))}},_closeSelection:function(){this._disable()},_changeLanguage:function(a){var b=$(a.currentTarget);this._insertedDataAfterInit=true;if(this._didInit){this._values[this._languageID]=this._element.val()}this._languageID=b.data("languageID");if(this._values[this._languageID]){this._element.val(this._values[this._languageID])}else{this._element.val("")}this._list.children("li").removeClass("active");b.addClass("active");this._button.children("span").addClass("active").text(this._availableLanguages[this._languageID]);if(this._didInit){this._element.blur().focus()}},_disable:function(a){if(a===undefined&&this._insertedDataAfterInit){a=null}if(this._forceSelection||!this._list||a===null){return}this._button.children("span").removeClass("active").text(WCF.Language.get("wcf.global.button.disabledI18n"));if(this._values[LANGUAGE_ID]){this._element.val(this._values[LANGUAGE_ID])}else{this._element.val()}if(a){this._list.children("li").removeClass("active");$(a.currentTarget).addClass("active")}this._element.blur().focus();this._insertedDataAfterInit=false;this._isEnabled=false;this._values={}},_submit:function(){if(!this._isEnabled){return 3735928559}if(this._languageID){this._values[this._languageID]=this._element.val()}var b=$(this._element.parents("form")[0]);var c=this._element.wcfIdentify();for(var a in this._availableLanguages){if(this._values[a]===undefined){this._values[a]=""}$('<input type="hidden" name="'+c+"_i18n["+a+']" value="'+WCF.String.escapeHTML(this._values[a])+'" />').appendTo(b)}this._element.removeAttr("name")}});WCF.Number={round:function(b,a){a=Math.pow(10,(a||0));return Math.round(b*a)/a}};WCF.String={addThousandsSeparator:function(a){return String(a).replace(/(^-?\d{1,3}|\d{3})(?=(?:\d{3})+(?:$|\.))/g,"$1"+WCF.Language.get("wcf.global.thousandsSeparator"))},escapeHTML:function(a){return String(a).replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")},escapeRegExp:function(a){return String(a).replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")},formatNumeric:function(b,a){b=String(WCF.Number.round(b,a||2));numberParts=b.split(".");b=this.addThousandsSeparator(numberParts[0]);if(numberParts.length>1){b+=WCF.Language.get("wcf.global.decimalPoint")+numberParts[1]}b=b.replace("-","\u2212");return b},lcfirst:function(a){return String(a).substring(0,1).toLowerCase()+a.substring(1)},ucfirst:function(a){return String(a).substring(0,1).toUpperCase()+a.substring(1)},unescapeHTML:function(a){return String(a).replace(/&/g,"&").replace(/"/g,'"').replace(/</g,"<").replace(/>/g,">")}};WCF.TabMenu={_containers:{},_didInit:false,init:function(){var d=$(".tabMenuContainer:not(.staticTabMenuContainer)");var c=this;d.each(function(f,e){var h=$(e);var g=h.wcfIdentify();if(c._containers[g]){return true}if(h.data("store")&&!$("#"+h.data("store")).length){$('<input type="hidden" name="'+h.data("store")+'" value="" id="'+h.data("store")+'" />').appendTo(h.parents("form").find(".formSubmit"))}c._containers[g]=h;h.wcfTabs({active:false,activate:function(j,i){var l=$(i.newPanel);var k=l.closest(".tabMenuContainer");var m=k;while(true){if(m.data("isParent")===undefined){break}if(m.data("isParent")){if(m.data("store")){$("#"+m.data("store")).val(l.attr("id"))}break}else{m=m.data("parent")}}if(WCF.TabMenu._didInit){if(l.data("inTabMenu")==undefined){l.data("inTabMenu",(l.parents(".dialogContainer").length))}if(!l.data("inTabMenu")){if(window.history){window.history.pushState(null,document.title,window.location.toString().replace(/#.+$/,"")+"#"+l.attr("id"))}else{location.hash="#"+l.attr("id")}}}}});h.data("isParent",(h.children(".tabMenuContainer, .tabMenuContent").length>0)).data("parent",false);if(!h.data("isParent")){if(h.parent().hasClass("tabMenuContainer")){h.data("parent",h.parent())}}});if(!this._didInit){this._selectActiveTab();$(window).bind("hashchange",$.proxy(this.selectTabs,this));if(!this._selectErroneousTab()){this.selectTabs()}if($.browser.mozilla&&location.hash){var a=$(location.hash);if(a.length&&a.hasClass("tabMenuContent")){var b=a.offset();window.scrollTo(b.left,b.top)}}}this._didInit=true},reload:function(){this._containers={};this.init()},_selectErroneousTab:function(){var d=false;for(var a in this._containers){var c=this._containers[a];if(c.find(".formError").length){d=true;if(!c.data("isParent")){while(true){if(c.data("parent")===false){break}c=c.data("parent").wcfTabs("selectTab",c.wcfIdentify())}return true}}}if(d){for(var a in this._containers){var c=this._containers[a];var b=c.find(".formError:eq(0)");if(b.length){c.wcfTabs("selectTab",b.parents(".tabMenuContent").wcfIdentify());while(true){if(c.data("parent")===false){break}c=c.data("parent").wcfTabs("selectTab",c.wcfIdentify())}return true}}}return false},_selectActiveTab:function(){for(var b in this._containers){var e=this._containers[b];if(e.data("active")){var a=e.data("active");var d=null;if(/-/.test(a)){var c=a.split("-");a=c[0];d=c[1]}e.find(".tabMenuContent").each(function(h,g){var f=$(g);if(f.wcfIdentify()==a){e.wcfTabs("select",h);if(d!==null){if(f.hasClass("tabMenuContainer")){f.wcfTabs("selectTab",e.data("active"))}else{e.wcfTabs("selectTab",e.data("active"))}}return false}})}}},selectTabs:function(){if(location.hash){var a=location.hash.substr(1);var b=$("#"+$.wcfEscapeID(a));if(b.length===1&&b.hasClass("ui-tabs-panel")){b=b.parent(".ui-tabs");if(b.length){b.wcfTabs("selectTab",a);if(b.hasClass("ui-tabs-panel")){a=b.wcfIdentify();b=b.parent(".ui-tabs");if(b.length){b.wcfTabs("selectTab",a)}}return true}}}return false}};WCF.Template=Class.extend({init:function(d){var f=new WCF.Dictionary();var a=0;d=d.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/(\r\n|\n|\r)/g,"\\n");d=d.replace(/\{literal\}(.*?)\{\/literal\}/g,$.proxy(function(e){var i="@@@@@@@@@@@"+Math.random()+"@@@@@@@@@@@";f.add(i,e.replace(/\{\/?literal\}/g,""));return i},this));d=d.replace(/\{\*.*?\*\}/g,"");var c=function(q){var l=q.split("");var i={};var k=true;var s="";var o="";var m=false;var n=false;var j=false;for(var p=0,e=l.length;p<e;p++){var r=l[p];if(k&&r!="="&&r!=" "){s+=r}else{if(k&&r=="="){k=false;n=false;m=false;j=false}else{if(!k&&!n&&!m&&r==" "){k=true;i[s]=o;o=s=""}else{if(!k&&n&&!j&&r=="'"){n=false;o+=r}else{if(!k&&!n&&!m&&r=="'"){n=true;o+=r}else{if(!k&&m&&!j&&r=='"'){m=false;o+=r}else{if(!k&&!n&&!m&&r=='"'){m=true;o+=r}else{if(!k&&(m||n)&&!j&&r=="\\"){j=true;o+=r}else{if(!k){j=false;o+=r}}}}}}}}}}i[s]=o;if(m||n||j){throw new Error('Syntax error in parameterList: "'+q+'"')}return i};var h=function(e){return e.replace(/\\n/g,"\n").replace(/\\\\/g,"\\").replace(/\\'/g,"'")};d=d.replace(/\{(\$[^\}]+?)\}/g,function(e,i){i=h(i.replace(/\$([^.\[\s]+)/g,"(v['$1'])"));return"' + WCF.String.escapeHTML("+i+") + '"}).replace(/\{#(\$[^\}]+?)\}/g,function(e,i){i=h(i.replace(/\$([^.\[\s]+)/g,"(v['$1'])"));return"' + WCF.String.formatNumeric("+i+") + '"}).replace(/\{@(\$[^\}]+?)\}/g,function(e,i){i=h(i.replace(/\$([^.\[\s]+)/g,"(v['$1'])"));return"' + "+i+" + '"}).replace(/{lang}(.+?){\/lang}/g,function(e,i){return"' + WCF.Language.get('"+h(i)+"') + '"}).replace(/\{if (.+?)\}/g,function(e,i){i=h(i.replace(/\$([^.\[\s]+)/g,"(v['$1'])"));return"';\nif ("+i+") {\n $output += '"}).replace(/\{else ?if (.+?)\}/g,function(e,i){i=h(i.replace(/\$([^.\[\s]+)/g,"(v['$1'])"));return"';\n}\nelse if ("+i+") {\n $output += '"}).replace(/\{implode (.+?)\}/g,function(e,j){a++;j=j.replace(/\\\\/g,"\\").replace(/\\'/g,"'");var i=c(j);if(typeof i.from==="undefined"){throw new Error("Missing from attribute in implode-tag")}if(typeof i.item==="undefined"){throw new Error("Missing item attribute in implode-tag")}if(typeof i.glue==="undefined"){i.glue="', '"}i.from=i.from.replace(/\$([^.\[\s]+)/g,"(v.$1)");return"';\nvar $implode_"+a+" = false;\nfor ($implodeKey_"+a+" in "+i.from+") {\n v["+i.item+"] = "+i.from+"[$implodeKey_"+a+"];\n"+(typeof i.key!=="undefined"?" v["+i.key+"] = $implodeKey_"+a+";\n":"")+" if ($implode_"+a+") $output += "+i.glue+";\n $implode_"+a+" = true;\n $output += '"}).replace(/\{foreach (.+?)\}/g,function(e,j){a++;j=j.replace(/\\\\/g,"\\").replace(/\\'/g,"'");var i=c(j);if(typeof i.from==="undefined"){throw new Error("Missing from attribute in foreach-tag")}if(typeof i.item==="undefined"){throw new Error("Missing item attribute in foreach-tag")}i.from=i.from.replace(/\$([^.\[\s]+)/g,"(v.$1)");return"';\n$foreach_"+a+" = false;\nfor ($foreachKey_"+a+" in "+i.from+") {\n $foreach_"+a+" = true;\n break;\n}\nif ($foreach_"+a+") {\n for ($foreachKey_"+a+" in "+i.from+") {\n v["+i.item+"] = "+i.from+"[$foreachKey_"+a+"];\n"+(typeof i.key!=="undefined"?" v["+i.key+"] = $foreachKey_"+a+";\n":"")+" $output += '"}).replace(/\{foreachelse\}/g,"';\n }\n}\nelse {\n {\n $output += '").replace(/\{\/foreach\}/g,"';\n }\n}\n$output += '").replace(/\{else\}/g,"';\n}\nelse {\n $output += '").replace(/\{\/(if|implode)\}/g,"';\n}\n$output += '");for(var b in WCF.Template.callbacks){d=WCF.Template.callbacks[b](d)}d=d.replace("{ldelim}","{").replace("{rdelim}","}");f.each(function(e){d=d.replace(e.key,e.value)});d="$output += '"+d+"';";try{this.fetch=new Function("v","if (typeof v != 'object') { v = {}; } v.__window = window; v.__wcf = window.WCF; var $output = ''; "+d+" return $output;")}catch(g){console.debug("var $output = ''; "+d+" return $output;");throw g}},fetch:function(a){}});WCF.Template.callbacks=[];WCF.ToggleOptions=Class.extend({_element:null,_showItems:[],_hideItems:[],_callback:null,init:function(c,a,b,d){this._element=$("#"+c);this._showItems=a;this._hideItems=b;if(d!==undefined){this._callback=d}this._element.click($.proxy(this._toggle,this));this._toggle()},_toggle:function(){if(!this._element.prop("checked")){return}for(var c=0,b=this._showItems.length;c<b;c++){var a=this._showItems[c];$("#"+a).show()}for(var c=0,b=this._hideItems.length;c<b;c++){var a=this._hideItems[c];$("#"+a).hide()}if(this._callback!==null){this._callback()}}});WCF.Collapsible={};WCF.Collapsible.Simple={init:function(){$(".jsCollapsible").each($.proxy(function(a,b){this._initButton(b)},this))},_initButton:function(a){var c=$(a);var b=c.data("isOpen");if(!b){$("#"+c.data("collapsibleContainer")).hide()}c.click($.proxy(this._toggle,this))},_toggle:function(b){var d=$(b.currentTarget);var c=d.data("isOpen");var a=$("#"+$.wcfEscapeID(d.data("collapsibleContainer")));if(c){a.stop().wcfBlindOut("vertical",$.proxy(function(){this._toggleImage(d)},this));c=false}else{a.stop().wcfBlindIn("vertical",$.proxy(function(){this._toggleImage(d)},this));c=true}d.data("isOpen",c);b.stopPropagation();return false},_toggleImage:function(b){var a=b.find("span.icon");if(b.data("isOpen")){a.removeClass("icon-chevron-right").addClass("icon-chevron-down")}else{a.removeClass("icon-chevron-down").addClass("icon-chevron-right")}}};WCF.Collapsible.Remote=Class.extend({_className:"",_containers:{},_containerData:{},_proxy:null,init:function(a){this._className=a;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._init();WCF.DOMNodeInsertedHandler.addCallback("WCF.Collapsible.Remote",$.proxy(this._init,this))},_init:function(a){this._getContainers().each($.proxy(function(c,b){var e=$(b);var d=e.wcfIdentify();if(this._containers[d]===undefined){this._containers[d]=e;this._initContainer(d)}},this))},_initContainer:function(b){var a=this._getTarget(b);var c=this._getButtonContainer(b);var d=this._createButton(b,c);this._containerData[b]={button:d,buttonContainer:c,isOpen:this._containers[b].data("isOpen"),target:a};if(!this._containers[b].data("isOpen")){$("#"+b).addClass("jsCollapsed")}},_getContainers:function(){},_getTarget:function(a){},_getButtonContainer:function(a){},_createButton:function(a,b){var d=this._containers[a].data("isOpen");var c=$('<span class="collapsibleButton jsTooltip pointer icon icon16 icon-'+(d?"chevron-down":"chevron-right")+'" title="'+WCF.Language.get("wcf.global.button.collapsible")+'">').prependTo(b);c.data("containerID",a).click($.proxy(this._toggleContainer,this));return c},_toggleContainer:function(d){var f=$(d.currentTarget);var b=f.data("containerID");var e=this._containerData[b].isOpen;var a=(e)?"open":"close";var c=(e)?"close":"open";this._proxy.setOption("data",{actionName:"loadContainer",className:this._className,interfaceName:"wcf\\data\\ILoadableContainerAction",objectIDs:[this._getObjectID(b)],parameters:$.extend(true,{containerID:b,currentState:a,newState:c},this._getAdditionalParameters(b))});this._proxy.sendRequest();$("#"+b).toggleClass("jsCollapsed")},_exchangeIcon:function(b,a){a=a||"spinner";b.removeClass("icon-chevron-down icon-chevron-right icon-spinner").addClass("icon-"+a)},_getObjectID:function(a){return $("#"+a).data("objectID")},_getAdditionalParameters:function(a){return{}},_updateContent:function(a,b,c){this._containerData[a].target.html(b)},_success:function(d,e,c){if(!d.returnValues.containerID){return}var a=d.returnValues.containerID;if(!this._containers[a]){return}this._containerData[a].isOpen=(d.returnValues.isOpen)?true:false;var b=(d.returnValues.isOpen)?"open":"close";this._updateContent(a,$.trim(d.returnValues.content),b);this._exchangeIcon(this._containerData[a].button,(d.returnValues.isOpen?"chevron-down":"chevron-right"))}});WCF.Collapsible.SimpleRemote=WCF.Collapsible.Remote.extend({init:function(a){this._super(a);this._proxy=new WCF.Action.Proxy({showLoadingOverlay:false})},_initContainer:function(a){this._super(a);if(!this._containerData[a].isOpen){this._containerData[a].target.hide();this._exchangeIcon(this._containerData[a].button,"chevron-right")}},_toggleContainer:function(d){var f=$(d.currentTarget);var b=f.data("containerID");var e=this._containerData[b].isOpen;var a=(e)?"open":"close";var c=(e)?"close":"open";this._proxy.setOption("data",{actionName:"toggleContainer",className:this._className,interfaceName:"wcf\\data\\IToggleContainerAction",objectIDs:[this._getObjectID(b)],parameters:$.extend(true,{containerID:b,currentState:a,newState:c},this._getAdditionalParameters(b))});this._proxy.sendRequest();this._exchangeIcon(this._containerData[b].button,(c==="open"?"chevron-down":"chevron-right"));if(c==="open"){this._containerData[b].target.show()}else{this._containerData[b].target.hide()}$("#"+b).toggleClass("jsCollapsed");this._containerData[b].isOpen=(c==="open"?true:false)}});WCF.Collapsible.Sidebar=Class.extend({_button:null,_buttonHeight:0,_isOpen:false,_mainContainer:null,_proxy:null,_sidebar:null,_sidebarHeight:0,_sidebarName:"",_sidebarOffset:0,_userPanelHeight:0,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();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();var a=$(window);this._sidebar.on("webkitTransitionEnd transitionend msTransitionEnd oTransitionEnd",function(){a.trigger("resize")})},_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()},_scroll:function(){var e=$(window);var b=e.scrollTop();var a=Math.max(b-this._sidebarOffset,0);var c=Math.min(this._mainContainer.height(),(e.height()+b)-this._sidebarOffset);var f=0;if(c===a){f=this._sidebarOffset+this._sidebarHeight}else{f=a+((c-a)/2);var d=Math.max(Math.min(a-this._userPanelHeight,this._userPanelHeight),0);if(d>0){f+=(d/2)}}if((c-a-this._userPanelHeight)<this._buttonHeight){f=f-this._buttonHeight}else{f=Math.max(f-(this._buttonHeight/2),0)}this._button.css({top:f+"px"})},_renderSidebar:function(){if(this._isOpen){$(".sidebarOrientationLeft, .sidebarOrientationRight").removeClass("sidebarCollapsed")}else{$(".sidebarOrientationLeft, .sidebarOrientationRight").addClass("sidebarCollapsed")}this._scroll();if($.browser.msie&&$.browser.version.indexOf("9")===0){$(window).trigger("resize")}}});WCF.User={userID:0,username:"",init:function(a,b){this.userID=a;this.username=b}};WCF.Effect={};WCF.Effect.Scroll=Class.extend({scrollTo:function(c,a,b){if(!c.length){return true}var d=c.getOffsets("offset").top;var f=$(document).height();var e=$(window).height();if(d>f-e){d=f-e;if(d<0){d=0}}if(b===true){$("html,body").scrollTop(d)}else{$("html,body").animate({scrollTop:d},400,function(h,i,g,k,j){return -k*((i=i/j-1)*i*i*i-1)+g})}return false}});WCF.Effect.SmoothScroll=WCF.Effect.Scroll.extend({init:function(){var a=this;$(document).on("click","a[href$=#top],a[href$=#bottom]",function(){var b=$(this.hash);a.scrollTo(b,true);return false})}});WCF.Effect.BalloonTooltip=Class.extend({_didInit:false,_tooltip:null,_viewportDimensions:{},init:function(){if(jQuery.browser.mobile){return}if(!this._didInit){this._tooltip=$('<div id="balloonTooltip" class="balloonTooltip"><span id="balloonTooltipText"></span><span class="pointer"><span></span></span></div>').appendTo($("body")).hide();this._updateViewportDimensions();$(window).resize($.proxy(this._updateViewportDimensions,this));WCF.DOMNodeInsertedHandler.addCallback("WCF.Effect.BalloonTooltip",$.proxy(this.init,this));this._didInit=true}$(".jsTooltip").each($.proxy(this._initTooltip,this))},_updateViewportDimensions:function(){this._viewportDimensions=$(document).getDimensions()},_initTooltip:function(b,c){var a=$(c);if(a.hasClass("jsTooltip")){a.removeClass("jsTooltip");var d=a.attr("title");if(d!==""){a.data("tooltip",d);a.removeAttr("title");a.hover($.proxy(this._mouseEnterHandler,this),$.proxy(this._mouseLeaveHandler,this));a.click($.proxy(this._mouseLeaveHandler,this))}}},_mouseEnterHandler:function(c){var n=$(c.currentTarget);var j=n.attr("title");if(j&&j!==""){n.data("tooltip",j);n.removeAttr("title")}this._tooltip.css({top:"0px",left:"0px"});if(!n.data("tooltip")){this._tooltip.hide();return}this._tooltip.children("span:eq(0)").text(n.data("tooltip"));var l=this._tooltip.find(".pointer");this._tooltip.show();var b=l.outerWidth();this._tooltip.hide();var f=n.getOffsets("offset");var k=n.getDimensions("outer");var m=this._tooltip.getDimensions("outer");var a=this._tooltip.getDimensions("inner");var h=f.left+Math.ceil(k.width/2);var i=Math.ceil(m.width/2);var g="center";if((h-i)<5){g="left"}else{if((this._viewportDimensions.width-5)<(h+i)){g="right"}}if(f.top+k.height+m.height-$(document).scrollTop()<$(window).height()){var e=f.top+k.height+7;this._tooltip.removeClass("inverse");l.css("top",-5)}else{var e=f.top-m.height-7;this._tooltip.addClass("inverse");l.css("top",m.height)}switch(g){case"center":var d=Math.round(f.left-i+(k.width/2));l.css({left:(a.width/2-b/2)+"px"});break;case"left":var d=f.left;l.css({left:"5px"});break;case"right":var d=f.left+k.width-m.width;l.css({left:(a.width-b-5)+"px"});break}this._tooltip.css({top:e+"px",left:d+"px"});this._tooltip.wcfFadeIn()},_mouseLeaveHandler:function(a){this._tooltip.stop().hide().css({opacity:1})}});WCF.CloseOverlayHandler={_callbacks:new WCF.Dictionary(),_isListening:false,addCallback:function(a,b){this._bindListener();if(this._callbacks.isset(a)){console.debug("[WCF.CloseOverlayHandler] identifier '"+a+"' is already bound to a callback");return false}this._callbacks.add(a,b)},removeCallback:function(a){if(this._callbacks.isset(a)){this._callbacks.remove(a)}},_bindListener:function(){if(this._isListening){return}$("body").click($.proxy(this._executeCallbacks,this));this._isListening=true},_executeCallbacks:function(a){this._callbacks.each(function(b){b.value()})}};WCF.DOMNodeInsertedHandler={_callbacks:[],_isExecuting:false,addCallback:function(a,b){this._callbacks.push(b)},_executeCallbacks:function(){if(this._isExecuting){return}this._isExecuting=true;for(var b=0,a=this._callbacks.length;b<a;b++){this._callbacks[b]()}this._isExecuting=false},execute:function(){this._executeCallbacks()}};WCF.DOMNodeRemovedHandler={_callbacks:new WCF.Dictionary(),_isExecuting:false,_isListening:false,addCallback:function(a,b){this._bindListener();if(this._callbacks.isset(a)){console.debug("[WCF.DOMNodeRemovedHandler] identifier '"+a+"' is already bound to a callback");return false}this._callbacks.add(a,b)},removeCallback:function(a){if(this._callbacks.isset(a)){this._callbacks.remove(a)}},_bindListener:function(){if(this._isListening){return}$(document).bind("DOMNodeRemoved",$.proxy(this._executeCallbacks,this));this._isListening=true},_executeCallbacks:function(a){if(this._isExecuting){return}this._isExecuting=true;this._callbacks.each(function(b){b.value(a)});this._isExecuting=false}};WCF.PageVisibilityHandler={_callbacks:new WCF.Dictionary(),_isListening:false,_hiddenFieldName:"",addCallback:function(a,b){this._bindListener();if(this._callbacks.isset(a)){console.debug("[WCF.PageVisibilityHandler] identifier '"+a+"' is already bound to a callback");return false}this._callbacks.add(a,b)},removeCallback:function(a){if(this._callbacks.isset(a)){this._callbacks.remove(a)}},_bindListener:function(){if(this._isListening){return}var a=null;if(typeof document.hidden!=="undefined"){this._hiddenFieldName="hidden";a="visibilitychange"}else{if(typeof document.mozHidden!=="undefined"){this._hiddenFieldName="mozHidden";a="mozvisibilitychange"}else{if(typeof document.msHidden!=="undefined"){this._hiddenFieldName="msHidden";a="msvisibilitychange"}else{if(typeof document.webkitHidden!=="undefined"){this._hiddenFieldName="webkitHidden";a="webkitvisibilitychange"}}}}if(a===null){console.debug("[WCF.PageVisibilityHandler] This browser does not support the page visibility API.")}else{$(document).on(a,$.proxy(this._executeCallbacks,this))}this._isListening=true},_executeCallbacks:function(b){if(this._isExecuting){return}this._isExecuting=true;var a=document[this._hiddenFieldName];this._callbacks.each(function(c){c.value(a)});this._isExecuting=false}};WCF.Table={};WCF.Table.EmptyTableHandler=Class.extend({_options:{},_rowClassName:"",init:function(b,c,a){this._rowClassName=c;this._tableContainer=b;this._options=$.extend(true,{emptyMessage:null,messageType:"info",refreshPage:false,updatePageNumber:false},a||{});WCF.DOMNodeRemovedHandler.addCallback("WCF.Table.EmptyTableHandler."+c,$.proxy(this._remove,this))},_remove:function(e){var d=$(e.target);if(d.hasClass(this._rowClassName)){var c=d.parents("tbody:eq(0)");if(c.children("tr").length==1){if(this._options.emptyMessage){this._tableContainer.replaceWith($("<p />").addClass(this._options.messageType).text(this._options.emptyMessage))}else{if(this._options.refreshPage){if(this._options.updatePageNumber){var a=window.location.href.match(/(\?|&)pageNo=(\d+)/g);if(a){var b=a[a.length-1].match(/\d+/g);if(this._options.updatePageNumber>0){b++}else{b--}window.location=window.location.href.replace(a[a.length-1],a[a.length-1][0]+"pageNo="+b)}}else{window.location.reload()}}else{this._tableContainer.remove()}}}}}});WCF.Search={};WCF.Search.Base=Class.extend({_callback:null,_className:"",_commaSeperated:false,_delay:0,_excludedSearchValues:[],_itemCount:0,_itemIndex:-1,_list:null,_oldSearchString:[],_proxy:null,_searchInput:null,_triggerLength:3,_timer:null,init:function(b,e,a,c,d){if(e!==null&&e!==undefined&&!$.isFunction(e)){console.debug("[WCF.Search.Base] The given callback is invalid, aborting.");return}this._callback=(e)?e:null;this._delay=0;this._excludedSearchValues=[];if(a){this._excludedSearchValues=a}this._searchInput=$(b);if(!this._searchInput.length){console.debug("[WCF.Search.Base] Selector '"+b+"' 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=(c)?true:false;this._oldSearchString=[];this._itemCount=0;this._itemIndex=-1;this._proxy=new WCF.Action.Proxy({showLoadingOverlay:(d!==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)},_blur:function(){var a=this;new WCF.PeriodicalExecuter(function(b){if(a._list.is(":visible")){a._clearList(false)}b.stop()},250)},_keyDown:function(a){if(a.which===$.ui.keyCode.ENTER){var b=this._searchInput.parents(".dropdown");if(b.data("disableAutoFocus")){if(this._itemIndex!==-1){a.preventDefault()}}else{if(b.data("preventSubmit")||this._itemIndex!==-1){a.preventDefault()}}}},_keyUp:function(d){switch(d.which){case 37:case 39:return;break;case 38:this._selectPreviousItem();return;break;case 40:this._selectNextItem();return;break;case 13:return this._selectElement(d);break}var b=this._getSearchString(d);if(b===""){this._clearList(true)}else{if(b.length>=this._triggerLength){var c={data:{excludedSearchValues:this._excludedSearchValues,searchString:b}};if(this._delay){if(this._timer!==null){this._timer.stop()}var a=this;this._timer=new WCF.PeriodicalExecuter(function(){a._queryServer(c);a._timer.stop();a._timer=null},this._delay)}else{this._queryServer(c)}}else{this._clearList(false)}}},_queryServer:function(a){this._searchInput.parents(".searchBar").addClass("loading");this._proxy.setOption("data",{actionName:"getSearchResultList",className:this._className,interfaceName:"wcf\\data\\ISearchAction",parameters:this._getParameters(a)});this._proxy.sendRequest()},setDelay:function(a){this._delay=a},_selectNextItem:function(){if(this._itemCount===0){return}this._itemIndex++;if(this._itemIndex===this._itemCount){this._itemIndex=0}this._highlightSelectedElement()},_selectPreviousItem:function(){if(this._itemCount===0){return}this._itemIndex--;if(this._itemIndex===-1){this._itemIndex=this._itemCount-1}this._highlightSelectedElement()},_highlightSelectedElement:function(){this._list.find("li").removeClass("dropdownNavigationItem");this._list.find("li:eq("+this._itemIndex+")").addClass("dropdownNavigationItem")},_selectElement:function(a){if(this._itemCount===0){return true}this._list.find("li.dropdownNavigationItem").trigger("click");return false},_getSearchString:function(d){var a=$.trim(this._searchInput.val());if(this._commaSeperated){var c=d.keyCode||d.which;if(c==$.ui.keyCode.COMMA){return""}var b=a.split(",");var e=b.length;for(var g=0;g<e;g++){b[g]=$.trim(b[g])}for(var g=0;g<e;g++){var f=b[g];if(this._oldSearchString[g]){if(f!=this._oldSearchString[g]){a=f;break}}else{a=f;break}}this._oldSearchString=b}return a},_getParameters:function(a){return a},_success:function(d,f,c){this._clearList(false);this._searchInput.parents(".searchBar").removeClass("loading");if($.getLength(d.returnValues)){for(var e in d.returnValues){var a=d.returnValues[e];this._createListItem(a)}}else{if(!this._handleEmptyResult()){return}}WCF.CloseOverlayHandler.addCallback("WCF.Search.Base",$.proxy(function(){this._clearList()},this));var b=this._searchInput.parents(".dropdown").wcfIdentify();if(!WCF.Dropdown.getDropdownMenu(b).hasClass("dropdownOpen")){WCF.Dropdown.toggleDropdown(b)}this._itemIndex=-1;if(!WCF.Dropdown.getDropdown(b).data("disableAutoFocus")){this._selectNextItem()}},_handleEmptyResult:function(){return false},_createListItem:function(b){var a=$("<li><span>"+WCF.String.escapeHTML(b.label)+"</span></li>").appendTo(this._list);a.data("objectID",b.objectID).data("label",b.label).click($.proxy(this._executeCallback,this));this._itemCount++;return a},_executeCallback:function(d){var a=false;var c=$(d.currentTarget);if(this._commaSeperated){var b=c.data("label");for(var h=0,f=this._oldSearchString.length;h<f;h++){var g=this._oldSearchString[h];if(b.toLowerCase().indexOf(g.toLowerCase())===0){this._oldSearchString[h]=b;this._searchInput.val(this._oldSearchString.join(", "));if($.browser.webkit){this._searchInput.css({display:"block"})}var e=this._searchInput.val().toLowerCase().indexOf(b.toLowerCase())+b.length;this._searchInput.focus().setCaret(e);break}}}else{if(this._callback===null){this._searchInput.val(c.data("label"))}else{a=(this._callback(c.data())===true)?true:false}}this._clearList(a)},_clearList:function(a){if(a&&!this._commaSeperated){this._searchInput.val("")}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");this._itemCount=0;this._itemIndex=-1},addExcludedSearchValue:function(a){if(!WCF.inArray(a,this._excludedSearchValues)){this._excludedSearchValues.push(a)}},removeExcludedSearchValue:function(b){var a=$.inArray(b,this._excludedSearchValues);if(a!=-1){this._excludedSearchValues.splice(a,1)}}});WCF.Search.User=WCF.Search.Base.extend({_className:"wcf\\data\\user\\UserAction",_includeUserGroups:false,init:function(c,e,a,b,d){this._includeUserGroups=a;this._super(c,e,b,d)},_getParameters:function(a){a.data.includeUserGroups=this._includeUserGroups?1:0;return a},_createListItem:function(e){var d=this._super(e);var b=null;if(e.icon){b=$(e.icon)}else{if(this._includeUserGroups&&e.type==="group"){b=$('<span class="icon icon16 icon-group" />')}}if(b){var a=d.find("span").detach();var c=$("<div />").addClass("box16").appendTo(d);c.append(b);c.append($("<div />").append(a))}d.data("type",e.type);return d}});WCF.System={};WCF.System.Dependency={};WCF.System.Dependency.Manager={_callbacks:{},_loaded:[],_setupCallbacks:{},register:function(a,b){if(!$.isFunction(b)){console.debug("[WCF.System.Dependency.Manager] Callback for identifier '"+a+"' is invalid, aborting.");return}if(WCF.inArray(a,this._loaded)){setTimeout(function(){b()},1)}else{if(!this._callbacks[a]){this._callbacks[a]=[]}this._callbacks[a].push(b)}},setup:function(a,b){if(!$.isFunction(b)){console.debug("[WCF.System.Dependency.Manager] Setup callback for identifier '"+a+"' is invalid, aborting.");return}if(!this._setupCallbacks[a]){this._setupCallbacks[a]=[]}this._setupCallbacks[a].push(b)},invoke:function(a){if(this._setupCallbacks[a]){for(var c=0,b=this._setupCallbacks[a].length;c<b;c++){this._setupCallbacks[a][c]()}delete this._setupCallbacks[a]}this._loaded.push(a);if(this._callbacks[a]){for(var c=0,b=this._callbacks[a].length;c<b;c++){this._callbacks[a][c]()}delete this._callbacks[a]}}};WCF.System.FlexibleMenu={_containers:{},_containerIDs:[],_dropdowns:{},_dropdownMenus:{},_hasHiddenItems:{},_isWorking:false,_menuItems:{},init:function(){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))},_registerTabMenus:function(){$(".tabMenuContainer:not(.jsFlexibleMenuEnabled)").each(function(a,b){var c=$(b).children("nav");if(c.length&&c.find("> ul:eq(0) > li").length){WCF.System.FlexibleMenu.registerMenu(c.wcfIdentify())}})},registerMenu:function(a){var b=$("#"+a);if(!b.length){console.debug("[WCF.System.FlexibleMenu] Unable to find container identified by '"+a+"', aborting.");return}this._containerIDs.push(a);this._containers[a]=b;this._menuItems[a]=b.find("> ul:eq(0) > li");this._dropdowns[a]=$('<li class="dropdown"><a class="icon icon16 icon-list" /></li>').data("containerID",a).click($.proxy(this._click,this));this._dropdownMenus[a]=$('<ul class="dropdownMenu" />').appendTo(this._dropdowns[a]);this._hasHiddenItems[a]=false;this.rebuild(a);WCF.Dropdown.initDropdown(this._dropdowns[a].children("a"))},rebuildAll:function(){if(this._isWorking){return}this._isWorking=true;for(var b=0,a=this._containerIDs.length;b<a;b++){this.rebuild(this._containerIDs[b])}this._isWorking=false},rebuild:function(i){if(!this._containers[i]){console.debug("[WCF.System.FlexibleMenu] Cannot rebuild unknown container identified by '"+i+"'");return}var c=false;var j=this._containers[i];var b=0;var d=this._menuItems[i].filter(":visible");for(var h=0,e=d.length;h<e;h++){b+=$(d[h]).outerWidth(true)}if(!this._hasHiddenItems[i]){this._dropdowns[i].appendTo(j.children("ul:eq(0)"))}var f=this._dropdowns[i].outerWidth(true);if(!this._hasHiddenItems[i]){this._dropdowns[i].detach()}var a=j.parent().innerWidth();a-=parseInt(j.parent().css("padding-left").replace(/px$/,""))+parseInt(j.parent().css("padding-right").replace(/px$/,""));a-=parseInt(j.css("margin-left").replace(/px$/,""))+parseInt(j.css("margin-right").replace(/px$/,""));a-=parseInt(j.css("padding-left").replace(/px$/,""))+parseInt(j.css("padding-right").replace(/px$/,""));a-=parseInt(j.children("ul:eq(0)").css("padding-left").replace(/px$/,""))+parseInt(j.children("ul:eq(0)").css("padding-right").replace(/px$/,""));if(b>a||(this._hasHiddenItems[i]&&(b>a-f))){var d=d.filter(":not(.active):not(.ui-state-active):visible");a-=f;for(var h=(d.length-1);h>=0;h--){if(b>a){var g=$(d[h]);b-=g.outerWidth(true);g.hide();c=true;this._hasHiddenItems[i]=true}else{break}}if(this._hasHiddenItems[i]){this._dropdowns[i].appendTo(j.children("ul:eq(0)"))}}else{if(this._hasHiddenItems[i]&&b<a){var k=this._menuItems[i].filter(":not(:visible)");a-=f;for(var h=0,e=k.length;h<e;h++){var g=$(k[h]);b+=g.outerWidth();if(h+1==e){a+=f}if(b<a){g.css("display","");c=true}else{break}}if(c){this._hasHiddenItems[i]=(this._menuItems[i].filter(":not(:visible)").length>0);if(!this._hasHiddenItems[i]){this._dropdowns[i].detach()}}}}if(c){this._dropdownMenus[i].empty();this._menuItems[i].filter(":not(:visible)").each($.proxy(function(l,m){$("<li>"+$(m).html()+"</li>").appendTo(this._dropdownMenus[i])},this))}}};WCF.System.Mobile={};WCF.System.Mobile.UX={_enabled:false,_main:null,_sidebar:null,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){this._sidebar.css("display","none").css("display","")}},_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))},_enable:function(){this._enabled=true;if($.browser.msie){this._sidebar.css("display","none").css("display","")}},_disable:function(){this._enabled=false;if($.browser.msie){this._sidebar.css("display","none").css("display","")}},_initSidebarToggleButtons:function(){var c=this._main.hasClass("sidebarOrientationLeft");var b=this._main.hasClass("sidebarOrientationRight");if(c||b){var d="wcf.global.sidebar.show"+(c?"Left":"Right")+"Sidebar";if(d===WCF.Language.get(d)||WCF.Language.get(d)===""){d='<span class="icon icon16 icon-double-angle-'+(c?"left":"right")+'" />'}var e="wcf.global.sidebar.hide"+(c?"Left":"Right")+"Sidebar";if(e===WCF.Language.get(e)||WCF.Language.get(e)===""){e='<span class="icon icon16 icon-double-angle-'+(c?"right":"left")+'" />'}var a=this;$('<span class="button small mobileSidebarToggleButton">'+d+"</span>").appendTo($(".content")).click(function(){a._main.addClass("mobileShowSidebar")});$('<span class="button small mobileSidebarToggleButton">'+e+"</span>").appendTo($(".sidebar")).click(function(){a._main.removeClass("mobileShowSidebar")})}},_initSearchBar:function(){var b=$(".searchBar:eq(0)");var a=this;b.click(function(){if(a._enabled){b.addClass("searchBarOpen")}});this._main.click(function(){b.removeClass("searchBarOpen")})},_initButtonGroupNavigation:function(){$(".buttonGroupNavigation:not(.jsMobileButtonGroupNavigation)").each(function(b,a){var d=$(a).addClass("jsMobileButtonGroupNavigation");var c=$('<a class="dropdownLabel"><span class="icon icon24 icon-list" /></a>').prependTo(d);c.click(function(){c.next().toggleClass("open");return false})})},_closeMenus:function(){$(".jsMobileButtonGroupNavigation > ul.open").removeClass("open")}};WCF.System.Page={};WCF.System.Page.Multiple=Class.extend({_cache:{},_options:{},_pageNo:1,_pages:0,init:function(a){this._options=$.extend({container:null,pagination:null,loadItems:null},a)},_showPage:function(a,b){if(b&&b.activePage){if(!b.template){this._previousPageNo=this._pageNo}this._pageNo=b.activePage}if(this._cache[this._pageNo]||(b&&b.template)){this._cache[this._previousPageNo]=this._list.children().detach();if(b&&b.template){this._list.html(b.template)}else{this._list.append(this._cache[this._pageNo])}}else{this._loadItems()}},showPage:function(a,b){this._showPage(null,{activePage:a,template:b})}});WCF.System.Notification=Class.extend({_callback:null,_cssClassNames:"",_message:"",_overlay:null,init:function(a,b){this._cssClassNames=b||"success";this._message=a||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)}},show:function(d,b,a,c){b=parseInt(b);if(!b){b=2000}if(d&&$.isFunction(d)){this._callback=d}this._overlay.children("p").html((a||this._message));this._overlay.children("p").removeClass().addClass((c||this._cssClassNames));new WCF.PeriodicalExecuter($.proxy(this._hide,this),b);this._overlay.wcfFadeIn(undefined,300)},_hide:function(a){if(this._callback!==null){this._callback()}this._overlay.wcfFadeOut(undefined,300);a.stop()}});WCF.System.Confirmation={_callback:null,_dialog:null,_parameters:null,_visible:false,_confirmationButton:null,show:function(c,e,b,a){if(this._visible){console.debug("[WCF.System.Confirmation] Confirmation dialog is already open, refusing action.");return}if(!$.isFunction(e)){console.debug("[WCF.System.Confirmation] Given callback is invalid, aborting.");return}this._callback=e;this._parameters=b;var d=true;if(this._dialog===null){this._createDialog();d=false}this._dialog.find("#wcfSystemConfirmationContent").empty().hide();if(a&&a.length){a.appendTo(this._dialog.find("#wcfSystemConfirmationContent").show())}this._dialog.find("p").text(c);this._dialog.wcfDialog({onClose:$.proxy(this._close,this),onShow:$.proxy(this._show,this),title:WCF.Language.get("wcf.global.confirmation.title")});if(d){this._dialog.wcfDialog("render")}this._confirmationButton.focus();this._visible=true},_createDialog:function(){this._dialog=$('<div id="wcfSystemConfirmation" class="systemConfirmation"><p /><div id="wcfSystemConfirmationContent" /></div>').hide().appendTo(document.body);var a=$('<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(a);$("<button>"+WCF.Language.get("wcf.global.confirmation.cancel")+"</button>").data("action","cancel").click($.proxy(this._click,this)).appendTo(a)},_click:function(a){this._notify($(a.currentTarget).data("action"))},_close:function(){if(this._visible){this._notify("cancel")}},_notify:function(a){this._visible=false;this._dialog.wcfDialog("close");this._callback(a,this._parameters)},_show:function(){this._dialog.find("button.buttonPrimary").blur().focus()}};WCF.System.DisableScrolling={_depth:0,_oldOverflow:null,disable:function(){if($.browser.touch){return}if(this._depth===0){this._oldOverflow=$(document.body).css("overflow");$(document.body).css("overflow","hidden")}this._depth++},enable:function(){if(this._depth===0){return}this._depth--;if(this._depth===0){$(document.body).css("overflow",this._oldOverflow)}}};WCF.System.PageNavigation={_button:null,_description:null,_dialog:null,_elementID:"",_elements:{},_pageNo:null,init:function(a,c){var b=$(a);if(!b.length){return}c=c||null;if(c!==null&&!$.isFunction(c)){console.debug("[WCF.System.PageNavigation] Callback for selector '"+a+"' is invalid, aborting.");return}this._initElements(b,c)},_initElements:function(b,c){var a=this;b.each(function(e,f){var d=$(f);var g=d.wcfIdentify();if(a._elements[g]===undefined){a._elements[g]=d;d.find("li.jumpTo").data("elementID",g).click($.proxy(a._click,a))}}).data("callback",c)},_click:function(b){this._elementID=$(b.currentTarget).data("elementID");if(this._dialog===null){this._dialog=$('<div id="pageNavigationOverlay" />').hide().appendTo(document.body);var c=$("<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(c);this._pageNo=$('<input type="number" id="jsPageNavigationPageNo" value="1" min="1" max="1" class="tiny" />').keyup($.proxy(this._keyUp,this)).appendTo(c.find("dd"));this._description=$("<small></small>").insertAfter(this._pageNo);var a=$('<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(a)}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")})},_keyUp:function(b){if(b.which==$.ui.keyCode.ENTER&&!this._button.prop("disabled")){this._submit();return}var a=parseInt(this._pageNo.val())||0;if(a<1||a>this._pageNo.attr("max")){this._button.disable()}else{this._button.enable()}},_submit:function(){var b=this._elements[this._elementID];if(b.data("callback")===null){var a=b.data("link").replace(/pageNo=%d/,"pageNo="+this._pageNo.val());window.location=a}else{b.data("callback")(this._pageNo.val());this._dialog.wcfDialog("close")}}};WCF.System.KeepAlive=Class.extend({init:function(a){new WCF.PeriodicalExecuter(function(b){new WCF.Action.Proxy({autoSend:true,data:{actionName:"keepAlive",className:"wcf\\data\\session\\SessionAction"},failure:function(){b.stop()},showLoadingOverlay:false,suppressErrors:true})},(a*1000))}});WCF.InlineEditor=Class.extend({_callbacks:[],_dropdowns:{},_elements:{},_notification:null,_options:[],_proxy:null,_updateData:[],init:function(d){var c=$(d);if(!c.length){return}this._setOptions();var b="";for(var f=0,e=this._options.length;f<e;f++){if(this._options[f].isQuickOption){b=this._options[f].optionName;break}}var a=this;c.each(function(i,j){var h=$(j);var k=h.wcfIdentify();var g=a._getTriggerElement(h);if(g===null||g.length!==1){return}g.click($.proxy(a._show,a)).data("elementID",k);if(b){g.disableSelection().data("optionName",b).dblclick($.proxy(a._click,a))}a._elements[k]=h});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")},_closeAll:function(){for(var a in this._elements){this._hide(a)}},_setOptions:function(){this._options=[]},registerCallback:function(a){if($.isFunction(a)){this._callbacks.push(a)}},_getTriggerElement:function(a){return null},_show:function(b){var g=$(b.currentTarget).data("elementID");var h=null;if(!this._dropdowns[g]){h=this._getTriggerElement(this._elements[g]).addClass("dropdownToggle").wrap('<span class="dropdown" />');this._dropdowns[g]=$('<ul class="dropdownMenu" />').insertAfter(h)}this._dropdowns[g].empty();var c=false;var d="";for(var k=0,i=this._options.length;k<i;k++){var a=this._options[k];if(a.optionName==="divider"){if(d!==""&&d!=="divider"){$('<li class="dropdownDivider" />').appendTo(this._dropdowns[g]);d=a.optionName}}else{if(this._validate(g,a.optionName)||this._validateCallbacks(g,a.optionName)){var f=$("<li><span>"+a.label+"</span></li>").appendTo(this._dropdowns[g]);f.data("elementID",g).data("optionName",a.optionName).data("isQuickOption",(a.isQuickOption?true:false)).click($.proxy(this._click,this));c=true;d=a.optionName}}}if(c){var l=this._dropdowns[g].children().last();if(l.hasClass("dropdownDivider")){l.remove()}var j=null;var e=0;this._dropdowns[g].children().each(function(m,o){var n=$(o);if(!n.hasClass("dropdownDivider")){if(n.data("isQuickOption")){j=n}else{e++}}});if(!e){j.trigger("click");if(h!==null){WCF.Dropdown.close(h.parents(".dropdown").wcfIdentify())}return false}}if(h!==null){WCF.Dropdown.initDropdown(h,true)}return false},_validate:function(a,b){return false},_validateCallbacks:function(a,b){var c=this._callbacks.length;if(c){for(var d=0;d<c;d++){if(this._callbacks[d].validate(this._elements[a],b)){return true}}}return false},_success:function(b,d,a){var c=this._updateData.length;if(!c){return}this._updateState(b);this._updateData=[]},_updateState:function(a){},_click:function(c){var b=$(c.currentTarget);var d=b.data("elementID");var a=b.data("optionName");if(!this._execute(d,a)){this._executeCallback(d,a)}this._hide(d)},_execute:function(a,b){return false},_executeCallback:function(a,b){var c=this._callbacks.length;if(c){for(var d=0;d<c;d++){if(this._callbacks[d].execute(this._elements[a],b)){return true}}}return false},_hide:function(a){if(this._dropdowns[a]){this._dropdowns[a].empty().removeClass("dropdownOpen")}}});WCF.Upload=Class.extend({_name:"__files[]",_buttonSelector:null,_fileListSelector:null,_fileUpload:null,_className:"",_iframe:null,_internalFileID:0,_options:{},_uploadMatrix:[],_supportsAJAXUpload:true,_overlay:null,init:function(c,e,d,b){this._buttonSelector=c;this._fileListSelector=e;this._className=d;this._internalFileID=0;this._options=$.extend(true,{action:"upload",multiple:false,url:"index.php/AJAXUpload/?t="+SECURITY_TOKEN+SID_ARG_2ND},b||{});var a=new XMLHttpRequest();this._supportsAJAXUpload=(a&&("upload" in a)&&("onprogress" in a.upload));this._createButton()},_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 a=$('<p class="button uploadButton"><span>'+WCF.Language.get("wcf.global.button.upload")+"</span></p>");a.prepend(this._fileUpload)}else{var a=$('<p class="button uploadFallbackButton"><span>'+WCF.Language.get("wcf.global.button.upload")+"</span></p>");a.click($.proxy(this._showOverlay,this))}this._insertButton(a)},_insertButton:function(a){this._buttonSelector.append(a)},_removeButton:function(){var a=".uploadButton";if(!this._supportsAJAXUpload){a=".uploadFallbackButton"}this._buttonSelector.find(a).remove()},_upload:function(){var a=this._fileUpload.prop("files");if(a.length){var i=new FormData();var f=this._createUploadMatrix(a);if(!this._uploadMatrix[f].length){return}for(var d=0,c=a.length;d<c;d++){if(this._uploadMatrix[f][d]){var e=this._uploadMatrix[f][d].data("internalFileID");i.append("__files["+e+"]",a[d])}}i.append("actionName",this._options.action);i.append("className",this._className);var b=this._getParameters();for(var h in b){i.append("parameters["+h+"]",b[h])}var g=this;$.ajax({type:"POST",url:this._options.url,enctype:"multipart/form-data",data:i,contentType:false,processData:false,success:function(k,l,j){g._success(f,k)},error:$.proxy(this._error,this),xhr:function(){var j=$.ajaxSettings.xhr();if(j){j.upload.addEventListener("progress",function(k){g._progress(f,k)},false)}return j}})}},_createUploadMatrix:function(b){if(b.length){var c=this._uploadMatrix.length;this._uploadMatrix[c]=[];for(var f=0,d=b.length;f<d;f++){var a=b[f];var e=this._initFile(a);if(!e.hasClass("uploadFailed")){e.data("filename",a.name).data("internalFileID",this._internalFileID++);this._uploadMatrix[c][f]=e}}return c}return null},_success:function(b,a){},_error:function(a,c,b){},_progress:function(c,a){var b=Math.round(a.loaded*100/a.total);for(var d in this._uploadMatrix[c]){this._uploadMatrix[c][d].find("progress").attr("value",b)}},_getParameters:function(){return{}},_initFile:function(a){return $("<li>"+a.name+" ("+a.size+')<progress max="100" /></li>').appendTo(this._fileListSelector)},_showOverlay:function(){if(this._iframe===null){this._iframe=$('<iframe name="__fileUploadIFrame" />').hide().appendTo(document.body)}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 a=this._overlay.find("form");$('<dl class="wide"><dd><input type="file" id="__fileUpload" name="'+this._name+'" '+(this._options.multiple?'multiple="true" ':"")+"/></dd></dl>").appendTo(a);$('<div class="formSubmit"><input type="submit" value="Upload" accesskey="s" /></div></form>').appendTo(a);$('<input type="hidden" name="isFallback" value="1" />').appendTo(a);$('<input type="hidden" name="actionName" value="'+this._options.action+'" />').appendTo(a);$('<input type="hidden" name="className" value="'+this._className+'" />').appendTo(a);var b=this._getParameters();for(var c in b){$('<input type="hidden" name="'+c+'" value="'+b[c]+'" />').appendTo(a)}a.submit($.proxy(function(){var d={name:this._getFilename(),size:""};var f=this._createUploadMatrix([d]);var e=this;this._iframe.data("loading",true).off("load").load(function(){e._evaluateResponse(f)});this._overlay.wcfDialog("close")},this))}this._overlay.wcfDialog({title:WCF.Language.get("wcf.global.button.upload")})},_evaluateResponse:function(b){var a=$.parseJSON(this._iframe.contents().find("pre").html());this._success(b,a)},_getFilename:function(){return $("#__fileUpload").val().split("\\").pop()}});WCF.Upload.Parallel=WCF.Upload.extend({init:function(b,d,c,a){a=$.extend(true,a||{},{multiple:true});this._super(b,d,c,a)},_upload:function(){var b=this._fileUpload.prop("files");for(var h=0,e=b.length;h<e;h++){var a=b[h];var c=new FormData();var g=this._createUploadMatrix(a);if(!this._uploadMatrix[g].length){continue}c.append("__files["+g+"]",a);c.append("actionName",this._options.action);c.append("className",this._className);var d=this._getParameters();for(var f in d){c.append("parameters["+f+"]",d[f])}this._sendRequest(g,c)}},_sendRequest:function(c,b){var a=this;$.ajax({type:"POST",url:this._options.url,enctype:"multipart/form-data",data:b,contentType:false,processData:false,success:function(e,f,d){a._success(c,e)},error:$.proxy(this._error,this),xhr:function(){var d=$.ajaxSettings.xhr();if(d){d.upload.addEventListener("progress",function(e){a._progress(c,e)},false)}return d}})},_createUploadMatrix:function(a){var b=this._initFile(a);if(!b.hasClass("uploadFailed")){b.data("filename",a.name).data("internalFileID",this._internalFileID);this._uploadMatrix[this._internalFileID++]=b;return this._internalFileID-1}return null},_success:function(b,a){},_progress:function(b,a){var c=Math.round(a.loaded*100/a.total);this._uploadMatrix[b].find("progress").attr("value",c)},_showOverlay:function(){if(this._iframe===null){this._iframe=$('<iframe name="__fileUploadIFrame" />').hide().appendTo(document.body)}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 a=this._overlay.find("form");$('<dl class="wide"><dd><input type="file" id="__fileUpload" name="'+this._name+'" '+(this._options.multiple?'multiple="true" ':"")+"/></dd></dl>").appendTo(a);$('<div class="formSubmit"><input type="submit" value="Upload" accesskey="s" /></div></form>').appendTo(a);$('<input type="hidden" name="isFallback" value="1" />').appendTo(a);$('<input type="hidden" name="actionName" value="'+this._options.action+'" />').appendTo(a);$('<input type="hidden" name="className" value="'+this._className+'" />').appendTo(a);var b=this._getParameters();for(var c in b){$('<input type="hidden" name="'+c+'" value="'+b[c]+'" />').appendTo(a)}a.submit($.proxy(function(){var d={name:this._getFilename(),size:""};var f=this._createUploadMatrix(d);var e=this;this._iframe.data("loading",true).off("load").load(function(){e._evaluateResponse(f)});this._overlay.wcfDialog("close")},this))}this._overlay.wcfDialog({title:WCF.Language.get("wcf.global.button.upload")})},_evaluateResponse:function(b){var a=$.parseJSON(this._iframe.contents().find("pre").html());this._success(b,a)}});WCF.Sortable={};WCF.Sortable.List=Class.extend({_additionalParameters:{},_className:"",_containerID:"",_container:null,_notification:null,_offset:0,_options:{},_proxy:null,_structure:{},init:function(a,f,g,d,e,c){this._additionalParameters=c||{};this._containerID=$.wcfEscapeID(a);this._container=$("#"+this._containerID);this._className=f;this._offset=(g)?g:0;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._structure={};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:0.6,placeholder:"sortablePlaceholder",tolerance:"pointer",toleranceElement:"> span"},d||{});if(e){$("#"+this._containerID+" .sortableList").sortable(this._options)}else{$("#"+this._containerID+" > .sortableList").nestedSortable(this._options)}if(this._className){var b=this._container.find(".formSubmit");if(!b.length){b=this._container.next(".formSubmit");if(!b.length){console.debug("[WCF.Sortable.Simple] Unable to find form submit for saving, aborting.");return}}b.children('button[data-type="submit"]').click($.proxy(this._submit,this))}},_submit:function(){this._structure={};this._container.find(".sortableList").each($.proxy(function(c,e){var b=$(e);var d=b.data("objectID");if(d!==undefined){b.children(this._options.items).each($.proxy(function(f,h){var g=$(h).data("objectID");if(!this._structure[d]){this._structure[d]=[]}this._structure[d].push(g)},this))}},this));var a=$.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:a});this._proxy.sendRequest()},_success:function(b,c,a){if(this._notification===null){this._notification=new WCF.System.Notification(WCF.Language.get("wcf.global.success.edit"))}this._notification.show()}});WCF.Popover=Class.extend({_activeElementID:"",_cancelPopover:false,_data:{},_defaultDimensions:{height:150,width:450},_defaultOrientation:{x:"right",y:"top"},_delay:{show:800,hide:500},_hoverElement:false,_hoverElementID:"",_hoverPopover:false,_margin:20,_peOut:null,_peOverElement:null,_popover:null,_popoverContent:null,_popoverOffset:10,_selector:"",init:function(a){if($.browser.mobile){return}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=a;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."+a,$.proxy(this._initContainers,this))},_initContainers:function(){if($.browser.mobile){return}var a=$(this._selector);if(!a.length){return}a.each($.proxy(function(c,d){var b=$(d);var e=b.wcfIdentify();if(!this._data[e]){this._data[e]={content:null,isLoading:false};b.hover($.proxy(this._overElement,this),$.proxy(this._out,this));if(b.is("a")&&b.attr("href")){b.click($.proxy(this._cancel,this))}}},this))},_cancel:function(a){this._cancelPopover=true;this._hide(true)},_overElement:function(a){if(this._cancelPopover){return}if(this._peOverElement!==null){this._peOverElement.stop()}var b=$(a.currentTarget).wcfIdentify();this._hoverElementID=b;this._peOverElement=new WCF.PeriodicalExecuter($.proxy(function(c){c.stop();if(this._hoverElementID===b){this._activeElementID=b;this._prepare()}},this),this._delay.show);this._hoverElement=true;this._hoverPopover=false},_prepare:function(){if(this._cancelPopover){return}if(this._peOut!==null){this._peOut.stop()}if(this._popover.is(":visible")){this._hide(true)}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}var a=this._popover.show().getDimensions();if(this._data[this._activeElementID].loading){a={height:Math.max(a.height,this._defaultDimensions.height),width:Math.max(a.width,this._defaultDimensions.width)}}else{a=this._fixElementDimensions(this._popover,a)}this._popover.hide();var b=this._getOrientation(a.height,a.width);this._popover.css(this._getCSS(b.x,b.y));this._popover.removeClass("bottom left right top").addClass(b.x).addClass(b.y);this._show()},_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})}},_loadContent:function(){},_insertContent:function(a,e,c){this._data[a]={content:e,loading:false};if(this._activeElementID===a){if(c){var d=this._popoverContent.getDimensions();this._popoverContent.css({height:"auto",width:"auto"});this._popoverContent.html(this._data[a].content);var f=this._popoverContent.getDimensions();this._popoverContent.html("").css({height:d.height+"px",width:d.width+"px"});var b=this;this._popoverContent.animate({height:f.height+"px",width:f.width+"px"},300,function(){b._popover.children("span").hide();b._popoverContent.html(b._data[a].content).css({opacity:0}).animate({opacity:1},200);WCF.DOMNodeInsertedHandler.execute()})}else{this._popover.children("span").hide();this._popoverContent.html(this._data[a].content);WCF.DOMNodeInsertedHandler.execute()}}},_hide:function(b){var a=this;this._popoverContent.stop();this._popover.stop();if(b){a._popover.css({opacity:0}).hide();a._popoverContent.empty().css({height:"auto",opacity:0,width:"auto"})}else{this._popover.wcfFadeOut(function(){a._popoverContent.empty().css({height:"auto",opacity:0,width:"auto"});a._popover.hide()})}},_overPopover:function(){if(this._peOut!==null){this._peOut.stop()}this._hoverElement=false;this._hoverPopover=true},_out:function(a){if(this._cancelPopover){return}this._hoverElementID="";this._hoverElement=false;this._hoverPopover=false;this._peOut=new WCF.PeriodicalExecuter($.proxy(function(b){b.stop();if(!this._hoverElement&&!this._hoverPopover){this._hide(false)}},this),this._delay.hide)},_getOrientation:function(i,a){var h=$("#"+this._activeElementID);var g=h.getOffsets("offset");var f=h.getDimensions();var b=$(document).getDimensions();var e=(this._defaultOrientation.x==="left")?"left":"right";var d=(this._defaultOrientation.y==="bottom")?"bottom":"top";var c=this._evaluateOrientation(e,d,g,f,b,i,a);if(c.flawed){e=(e==="left")?"right":"left";c=this._evaluateOrientation(e,d,g,f,b,i,a);if(c.flawed){e=(e==="right")?"left":"right";d=(d==="bottom")?"top":"bottom";c=this._evaluateOrientation(e,d,g,f,b,i,a);if(c.flawed){e=(e==="left")?"right":"left";c=this._evaluateOrientation(e,d,g,f,b,i,a);if(c.flawed){e=(this._defaultOrientationX==="left")?"left":"right";d=(this._defaultOrientationY==="bottom")?"bottom":"top"}}}}return{x:e,y:d}},_evaluateOrientation:function(g,f,b,h,j,i,a){var d=0,e=0;switch(g){case"left":e=b.left-a;break;case"right":e=j.width-(b.left+a);break}switch(f){case"bottom":d=j.height-(b.top+h.height+this._popoverOffset+i);break;case"top":d=b.top-(i-this._popoverOffset);break}var c=false;if(d<this._margin||e<this._margin){c=true}return{flawed:c,x:e,y:d}},_getCSS:function(d,c){var g={bottom:"auto",left:"auto",right:"auto",top:"auto"};var b=$("#"+this._activeElementID);var e=b.getOffsets("offset");var f=this._fixElementDimensions(b,b.getDimensions());var a=$(window).getDimensions();switch(d){case"left":g.right=a.width-(e.left+f.width);break;case"right":g.left=e.left;break}switch(c){case"bottom":g.top=e.top+(f.height+this._popoverOffset);break;case"top":g.bottom=a.height-(e.top-this._popoverOffset);break}return g},_fixElementDimensions:function(a,b){var c=a.parent().getDimensions();if(c.height<b.height){b.height=c.height}if(c.width<b.width){b.width=c.width}return b}});WCF.EditableItemList=Class.extend({_allowCustomInput:false,_className:"",_data:{},_form:null,_itemList:null,_objectID:0,_objectTypeID:0,_search:null,_searchInput:null,init:function(c,a){this._itemList=$(c);this._searchInput=$(a);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();this._itemList.find(".jsEditableItem").click($.proxy(this._click,this));if(!this._itemList.children("ul").length){$("<ul />").appendTo(this._itemList)}this._itemList=this._itemList.children("ul");this._form=this._itemList.parents("form").submit($.proxy(this._submit,this));if(this._allowCustomInput){var b=this;this._searchInput.keydown($.proxy(this._keyDown,this)).on("paste",function(){setTimeout(function(){b._onPaste()},100)})}this._searchInput.parents(".dropdown").data("preventSubmit",true)},_keyDown:function(b){if(b===null||b.which===188||b.which===$.ui.keyCode.ENTER){if(b!==null&&b.which===$.ui.keyCode.ENTER&&this._search){if(this._search._itemIndex!==-1){return false}}var a=$.trim(this._searchInput.val());if(b&&b.which===188){a=a.substring(0,this._searchInput.getCaret())}if(a===""){return true}this.addItem({objectID:0,label:a});if(b&&b.which===188){this._searchInput.val($.trim(this._searchInput.val().substr(this._searchInput.getCaret())))}else{this._searchInput.val("")}if(b!==null){b.stopPropagation()}return false}return true},_onPaste:function(){var b=$.trim(this._searchInput.val());b=b.split(",");for(var d=0,c=b.length;d<c;d++){var a=$.trim(b[d]);if(a===""){continue}this.addItem({objectID:0,label:a})}this._searchInput.val("")},load:function(a){},_click:function(d){var b=$(d.currentTarget);var c=b.data("objectID");var a=b.data("label");if(this._search){this._search.removeExcludedSearchValue(a)}this._removeItem(c,a);b.remove();d.stopPropagation();return false},_getObjectID:function(){return 0},_getObjectTypeID:function(){return 0},addItem:function(b){if(this._data[b.objectID]){if(!(b.objectID===0&&this._allowCustomInput)){return true}}var a=$('<li class="badge">'+WCF.String.escapeHTML(b.label)+"</li>").data("objectID",b.objectID).data("label",b.label).appendTo(this._itemList);a.click($.proxy(this._click,this));if(this._search){this._search.addExcludedSearchValue(b.label)}this._addItem(b.objectID,b.label);return true},clearList:function(){this._itemList.children("li").each($.proxy(function(b,c){var a=$(c);if(this._search){this._search.removeExcludedSearchValue(a.data("label"))}a.remove();this._removeItem(a.data("objectID"),a.data("label"))},this))},_submit:function(){this._keyDown(null)},_addItem:function(b,a){this._data[b]=a},_removeItem:function(b,a){delete this._data[b]},getSearchInput:function(){return this._searchInput}});WCF.Sitemap=Class.extend({_cache:[],_dialog:null,_didInit:false,_proxy:null,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)})},_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")}},_success:function(b,c,a){if(this._didInit){this._cache.push(b.returnValues.sitemapName);this._dialog.find("#sitemap_"+b.returnValues.sitemapName).html(b.returnValues.template);this._dialog.wcfDialog("render")}else{this._cache.push(b.returnValues.sitemapName);this._dialog.html(b.returnValues.template);this._dialog.find(".sitemapNavigation").click($.proxy(this._navigate,this));this._dialog.find(".tabMenuContainer").wcfTabs("select","sitemap_"+b.returnValues.sitemapName);this._dialog.wcfDialog({title:WCF.Language.get("wcf.page.sitemap")});this._didInit=true}},_navigate:function(a){var b=$(a.currentTarget).data("sitemapName");if(WCF.inArray(b,this._cache)){this._dialog.find(".tabMenuContainer").wcfTabs("select","sitemap_"+b);this._dialog.wcfDialog("render")}else{this._proxy.setOption("data",{actionName:"getSitemap",className:"wcf\\data\\sitemap\\SitemapAction",parameters:{sitemapName:b}});this._proxy.sendRequest()}}});WCF.Language.Chooser=Class.extend({_callback:null,_dropdown:null,_input:null,init:function(l,g,f,b,k,e){var m=$("#"+l);if(m.length!=1){console.debug("[WCF.Language.Chooser] Invalid container id '"+l+"' given");return}this._input=$("#"+g);if(!this._input.length){this._input=$('<input type="hidden" name="'+g+'" value="'+f+'" />').appendTo(m)}if(k!==undefined){if(!$.isFunction(k)){console.debug("[WCF.Language.Chooser] Given callback is invalid");return}this._callback=k}this._dropdown=$('<div class="dropdown" id="'+l+'-languageChooser" />').appendTo(m);$('<div class="dropdownToggle boxFlag box24" data-toggle="'+l+'-languageChooser"></div>').appendTo(this._dropdown);var c=$('<ul class="dropdownMenu" />').appendTo(this._dropdown);for(var a in b){var i=b[a];var h=$('<li class="boxFlag"><a class="box24"><div class="framed"><img src="'+i.iconPath+'" alt="" class="iconFlag" /></div> <div><h3>'+i.languageName+"</h3></div></a></li>").appendTo(c);h.data("languageID",a).click($.proxy(this._click,this));if(a==f){var d=$(""+h.html());var j=d.children().detach();this._dropdown.children(".dropdownToggle").empty().append(j)}}if(e){$('<li class="dropdownDivider" />').appendTo(c);var h=$("<li><a>"+WCF.Language.get("wcf.global.language.noSelection")+"</a></li>").data("languageID",0).click($.proxy(this._click,this)).appendTo(c);if(f===0){this._dropdown.children(".dropdownToggle").empty().append(h.html())}}WCF.Dropdown.init()},_click:function(e){var d=$(e.currentTarget);var c=d.data("languageID");this._input.val(c);var b=$(""+d.html());var a=(c===0)?b:b.children().detach();this._dropdown.children(".dropdownToggle").empty().append(a);if(this._callback!==null){this._callback(d)}}});WCF.Style={};WCF.Style.Chooser=Class.extend({_dialog:null,_proxy:null,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)})},_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")})}},_loadDialog:function(){this._proxy.setOption("data",{actionName:"getStyleChooser",className:"wcf\\data\\style\\StyleAction"});this._proxy.sendRequest()},_success:function(b,c,a){if(b.actionName==="changeStyle"){window.location.reload();return}this._dialog.html(b.returnValues.template);this._dialog.find("li").addClass("pointer").click($.proxy(this._click,this));this._showDialog()},_click:function(a){this._proxy.setOption("data",{actionName:"changeStyle",className:"wcf\\data\\style\\StyleAction",objectIDs:[$(a.currentTarget).data("styleID")]});this._proxy.sendRequest()}});WCF.UserPanel=Class.extend({_container:null,_didLoad:false,_link:null,_noItems:"",_revertOnEmpty:true,init:function(a){this._container=$("#"+a);this._didLoad=false;this._revertOnEmpty=true;if(this._container.length!=1){console.debug("[WCF.UserPanel] Unable to find container identfied by '"+a+"', aborting.");return}this._convert()},_convert:function(){this._container.addClass("dropdown");this._link=this._container.children("a").remove();var b=$('<a class="dropdownToggle">'+this._link.html()+"</a>").appendTo(this._container).click($.proxy(this._click,this));var a=$('<ul class="dropdownMenu" />').appendTo(this._container);$('<li class="jsDropdownPlaceholder"><span>'+WCF.Language.get("wcf.global.loading")+"</span></li>").appendTo(a);this._addDefaultItems(a);this._container.dblclick($.proxy(function(){window.location=this._link.attr("href");return false},this));WCF.Dropdown.initDropdown(b,false)},_addDefaultItems:function(a){},_addDivider:function(a){$('<li class="dropdownDivider" />').appendTo(a)},_click:function(){if(this._didLoad){return}new WCF.Action.Proxy({autoSend:true,data:this._getParameters(),success:$.proxy(this._success,this)});this._didLoad=true},_getParameters:function(){return{}},_success:function(c,e,b){var a=WCF.Dropdown.getDropdownMenu(this._container.wcfIdentify());a.children(".jsDropdownPlaceholder").remove();if(c.returnValues&&c.returnValues.template){$(""+c.returnValues.template).prependTo(a);var d=this._container.find(".badge");if(!d.length){d=$('<span class="badge badgeInverse" />').appendTo(this._container.children(".dropdownToggle"));d.before(" ")}d.html(c.returnValues.totalCount);this._after(a)}else{$("<li><span>"+WCF.Language.get(this._noItems)+"</span></li>").prependTo(a);this._container.find(".badge").remove()}},_after:function(a){}});$.widget("ui.wcfDialog",{_closeButton:null,_container:null,_content:null,_overlay:null,_title:null,_titlebar:null,_isOpen:false,options:{autoOpen:true,closable:true,closeButtonLabel:null,closeConfirmMessage:null,closeViaModal:true,hideTitle:false,modal:true,title:"",zIndex:400,onClose:null,onShow:null},_createWidget:function(a,b){if($(b).getTagName()==="script"){console.debug("[ui.wcfDialog] Ignored script tag");this.element=false;return null}$.Widget.prototype._createWidget.apply(this,arguments)},_init:function(){if(this.options.autoOpen){this.open()}$(window).resize($.proxy(this._resize,this))},_create:function(){if(this.options.closeButtonLabel===null){this.options.closeButtonLabel=WCF.Language.get("wcf.global.button.close")}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);var a=this.element.detach();this._content.html(a);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(b){if(b.keyCode&&b.keyCode===$.ui.keyCode.ESCAPE){this.close();b.preventDefault()}},this))}}WCF.DOMNodeInsertedHandler.execute()},_setOption:function(a,b){this.options[a]=b;if(a=="hideTitle"||a=="title"){if(!this.options.hideTitle&&this.options.title!=""){this._title.html(this.options.title).show()}else{this._title.html("")}}else{if(a=="closable"||a=="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},open:function(){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},isOpen:function(){return this._isOpen},close:function(a){if(!this.isOpen()){return}if(this.options.closeConfirmMessage){WCF.System.Confirmation.show(this.options.closeConfirmMessage,$.proxy(function(b){if(b==="confirm"){this._close()}},this))}else{this._close()}if(a!==undefined){a.preventDefault()}},_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()}},_resize:function(){if(this.isOpen()){this.render()}},render:function(){if(!this._container.is(":visible")&&this._container.css("top")==="0px"){window.scrollTo(0,0)}this._container.show();this._content.children().show();this._content.css({height:"auto",width:"auto"});this._container.stop();this._content.stop();this._container.show().css("opacity",1);var e=0;if(this._content.find(".formSubmit").length){e=this._content.find(".formSubmit").outerHeight();this._content.addClass("dialogForm").css({marginBottom:e+"px"})}else{this._content.removeClass("dialogForm").css({marginBottom:"0px"})}var b=$(window).getDimensions();if(b.width*0.9>800){this._container.css("maxWidth","800px")}var h=this._container.getDimensions("outer");var d=this._content.getDimensions();var e=h.height-d.height;var g=b.height-e-120;this._content.css({maxHeight:g+"px"});this._determineOverflow();h=this._container.getDimensions("outer");var f=Math.round((b.width-h.width)/2);var a=Math.round((b.height-h.height)/2);var c=Math.round((b.height/100)*20);if(c<a){a=c}this._container.css({left:f+"px",top:a+"px"});this._content.css({height:"auto",width:"auto"});if(!this.isOpen()){this._container.hide();this._container.wcfFadeIn($.proxy(function(){if(this.options.onShow!==null){this.options.onShow()}},this))}},_determineOverflow:function(){var b=$(window).getDimensions();var c=this._content.css("maxHeight");this._content.css("maxHeight","none");var e=this._container.getDimensions("outer");var d="visible";if((b.height*0.8<e.height)||(b.width*0.8<e.width)){d="auto"}this._content.css("overflow",d);this._content.css("maxHeight",c);if(d==="visible"){var a=0;this._content.children().each(function(f,g){a+=$(g).outerHeight()});if(this._content.height()<a){this._content.css("overflow","auto")}}},_getContentDimensions:function(a){var b=this._content.getDimensions();if(a&&b.height>a){b.height=a}return b}});$.widget("ui.wcfSlideshow",{_buttonList:null,_count:0,_index:0,_itemList:null,_items:null,_timer:null,_width:0,options:{cycle:true,cycleInterval:5,itemGap:50,},_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()}},_initSlideshow:function(){var b=$(this._items.get(0)).outerHeight();this._items.addClass("slideshowItem");this._width=this.element.css("height",b).innerWidth();this._itemList.addClass("slideshowItemList").css("left",0);this._items.each($.proxy(function(d,e){$(e).show().css({height:b,left:((this._width+this.options.itemGap)*d),width:this._width})},this));this.element.css({height:b,width:this._width}).hover($.proxy(this._hoverIn,this),$.proxy(this._hoverOut,this));this._buttonList=$('<ul class="slideshowButtonList" />').appendTo(this.element);for(var c=0;c<this._count;c++){var a=$('<li><a><span class="icon icon16 icon-circle" /></a></li>').data("index",c).click($.proxy(this._click,this)).appendTo(this._buttonList);if(c==0){a.find(".icon").addClass("active")}}this._resetTimer();$(window).resize($.proxy(this._resize,this))},_resize:function(){this._width=this.element.css("width","auto").innerWidth();this._items.each($.proxy(function(a,b){$(b).css({left:((this._width+this.options.itemGap)*a),width:this._width})},this));this._index--;this.moveTo(null)},_hoverIn:function(){if(this._timer!==null){this._timer.stop()}},_hoverOut:function(){this._resetTimer()},_resetTimer:function(){if(!this.options.cycle){return}if(this._timer!==null){this._timer.stop()}var a=this;this._timer=new WCF.PeriodicalExecuter(function(){a.moveTo(null)},this.options.cycleInterval*1000)},_click:function(a){this.moveTo($(a.currentTarget).data("index"));this._resetTimer()},moveTo:function(a){this._index=(a===null)?this._index+1:a;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})},getItem:function(a){if(this._items[a]){return this._items[a]}return null}});$.widget("ui.wcfTabs",$.ui.tabs,{_sanitizeSelector:function(a){return a.replace(/([:\.])/g,"\\$1")},select:function(a){if(!$.isNumeric(a)){this.panels.each(function(c,b){if($(b).wcfIdentify()===a){a=c;return false}});if(!$.isNumeric(a)){console.debug("[ui.wcfTabs] Unable to find panel identified by '"+a+"', aborting.");return}}this._setOption("active",a)},selectTab:function(a){a="#"+a;this.anchors.each(function(c,b){var d=$(b);if(d.prop("hash")===a){d.trigger("click");return false}})},getCurrentIndex:function(){return this.lis.index(this.lis.filter(".ui-tabs-selected"))},hasAnchor:function(b,a){var c=false;this.anchors.each(function(e,d){var g=$(d).attr("href");if(/#.+/.test(g)){var f=g.split("#",2);if(a){f=f[1].split("-",2)}if(f[1]===b){c=true;return false}}});return c},revertToDefault:function(){var a=this.element.data("active");if(!a||a===""){a=0}this.select(a)},_processTabs:function(){var a=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(f,e){var b,c,d=$(e).uniqueId().attr("id"),g=$(e).closest("li"),h=g.attr("aria-controls");b=e.hash;c=a.element.find(a._sanitizeSelector(b));if(c.length){a.panels=a.panels.add(c)}if(h){g.data("ui-tabs-aria-controls",h)}g.attr({"aria-controls":b.substring(1),"aria-labelledby":d});c.attr("aria-labelledby",d)});this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").attr("role","tabpanel")},load:function(a,b){return}});$.widget("ui.wcfPages",{SHOW_LINKS:11,SHOW_SUB_LINKS:20,options:{activePage:1,maxPage:1,nextPage:null,previousPage:null},_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()},destroy:function(){$.Widget.prototype.destroy.apply(this,arguments);this.element.children().remove()},_render:function(){if(!this.options.disabled&&this.options.maxPage>1){var g=false;if(this.element.hasClass("hidden")){this.element.removeClass("hidden")}this.element.show();this.element.children().remove();var o=$("<ul />");this.element.append(o);var n=$('<li class="button skip" />');o.append(n);if(this.options.activePage>1){var d=$("<a"+((this.options.previousPage!=null)?(' title="'+this.options.previousPage+'"'):(""))+"></a>");n.append(d);this._bindSwitchPage(d,this.options.activePage-1);var l=$('<span class="icon icon16 icon-double-angle-left" />');d.append(l)}else{var l=$('<span class="icon icon16 icon-double-angle-left" />');n.append(l);n.addClass("disabled").removeClass("button");l.addClass("disabled")}o.append(this._renderLink(1));var a=this.SHOW_LINKS-4;var e=this.options.activePage-2;if(e<0){e=0}var i=this.options.maxPage-(this.options.activePage+1);if(i<0){i=0}if(this.options.activePage>1&&this.options.activePage<this.options.maxPage){a--}var k=a/2;var c=this.options.activePage;var j=this.options.activePage;if(c<1){c=1}if(j<1){j=1}if(j>this.options.maxPage-1){j=this.options.maxPage-1}if(e>=k){c-=k}else{c-=e;j+=k-e}if(i>=k){j+=k}else{j+=i;c-=k-i}j=Math.ceil(j);c=Math.ceil(c);if(c<1){c=1}if(j>this.options.maxPage){j=this.options.maxPage}if(c>1){if(c-1<2){o.append(this._renderLink(2))}else{$('<li class="button jumpTo"><a title="'+WCF.Language.get("wcf.global.page.jumpTo")+'" class="jsTooltip">...</a></li>').appendTo(o);g=true}}for(var m=c+1;m<j;m++){o.append(this._renderLink(m))}if(j<this.options.maxPage){if(this.options.maxPage-j<2){o.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(o);g=true}}o.append(this._renderLink(this.options.maxPage));var h=$('<li class="button skip" />');o.append(h);if(this.options.activePage<this.options.maxPage){var f=$("<a"+((this.options.nextPage!=null)?(' title="'+this.options.nextPage+'"'):(""))+"></a>");h.append(f);this._bindSwitchPage(f,this.options.activePage+1);var b=$('<span class="icon icon16 icon-double-angle-right" />');f.append(b)}else{var b=$('<span class="icon icon16 icon-double-angle-right" />');h.append(b);h.addClass("disabled").removeClass("button");b.addClass("disabled")}if(g){o.data("pages",this.options.maxPage);WCF.System.PageNavigation.init("#"+o.wcfIdentify(),$.proxy(function(p){this.switchPage(p)},this))}}else{this.element.hide()}},_renderLink:function(d,a){var e=$('<li class="button"></li>');if(a!=undefined&&a){e.addClass("break")}if(d!=this.options.activePage){var b=$("<a>"+WCF.String.addThousandsSeparator(d)+"</a>");e.append(b);this._bindSwitchPage(b,d)}else{e.addClass("active");var c=$("<span>"+WCF.String.addThousandsSeparator(d)+"</span>");e.append(c)}return e},_bindSwitchPage:function(a,b){var c=this;a.click(function(){c.switchPage(b)})},switchPage:function(a){this._setOption("activePage",a)},_setOption:function(b,c){if(b=="activePage"){if(c!=this.options[b]&&c>0&&c<=this.options.maxPage){var a=this._trigger("shouldSwitch",undefined,{nextPage:c});if(a||a!==undefined){this.options[b]=c;this._render();this._trigger("switched",undefined,{activePage:c})}else{this._trigger("notSwitched",undefined,{activePage:c})}}}else{this.options[b]=c;if(b=="disabled"){if(c){this.element.children().remove()}else{this._render()}}else{if(b=="maxPage"){this._render()}}}return this},_startInput:function(b){var a=$(b.currentTarget);if(!a.is("a")){a=a.parent("a")}a.hide();var c=a.parent("li").children("input").css("display","block").val("");c.focus()},_stopInput:function(b){var c=$(b.currentTarget);c.css("display","none");var a=c.parent("li");if(a!=undefined&&a!=null){a.children("a").show()}},_handleInput:function(b){var a=($.browser.msie&&$.browser.version=="7.0");if(b.type!="keyup"||a){if(!a||((b.which==13||b.which==27)&&b.type=="keyup")){if(b.which==13){this.switchPage(parseInt($(b.currentTarget).val()))}if(b.which==13||b.which==27){this._stopInput(b);b.stopPropagation()}}}}});WCF.Category={};WCF.Category.NestedList=Class.extend({_categories:{},init:function(){var a=this;$(".jsCategory").each(function(c,d){var b=$(d).data("parentCategoryID",null).change($.proxy(a._updateSelection,a));a._categories[b.val()]=b;var e=[];b.parents("li").find(".jsChildCategory").each(function(h,g){var f=$(g).data("parentCategoryID",b.val()).change($.proxy(a._updateSelection,a));a._categories[f.val()]=f;e.push(f.val());if(f.is(":checked")){b.prop("checked","checked")}});b.data("childCategoryIDs",e)})},_updateSelection:function(b){var a=$(b.currentTarget);var f=a.data("parentCategoryID");if(a.is(":checked")){if(f!==null){this._categories[f].prop("checked","checked")}}else{if(f===null){var e=a.data("childCategoryIDs");for(var d=0,c=e.length;d<c;d++){this._categories[e[d]].prop("checked",false)}}}}});function wcfEval(expression){return eval(expression)}WCF.Like=Class.extend({_allowForOwnContent:false,_canLike:false,_containers:{},_containerData:{},_enableDislikes:true,_isBusy:false,_likeDetails:{},_proxy:null,_showSummary:true,init:function(a,c,f,e){this._canLike=a;this._enableDislikes=c;this._isBusy=false;this._likeDetails={};this._showSummary=f;this._allowForOwnContent=e;var d=this._getContainers();this._initContainers(d);this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});var g=new Date();var b=g.toString().hashCode+g.getUTCMilliseconds();WCF.DOMNodeInsertedHandler.addCallback("WCF.Like"+b,$.proxy(this._domNodeInserted,this))},_domNodeInserted:function(){var a=this._getContainers();this._initContainers(a)},_initContainers:function(containers){var $createdWidgets=false;containers.each($.proxy(function(index,container){var $container=$(container);var $containerID=$container.wcfIdentify();if(!this._containers[$containerID]){this._containers[$containerID]=$container;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")};this._createWidget($containerID);$createdWidgets=true}},this));if($createdWidgets){new WCF.PeriodicalExecuter(function(pe){pe.stop();WCF.DOMNodeInsertedHandler.execute()},250)}},_getContainers:function(){},_getWidgetContainer:function(a){},_getObjectID:function(a){},_addWidget:function(a,b){var c=this._getWidgetContainer(a);b.appendTo(c)},_buildWidget:function(b,a,d,c,e){var f=$('<aside class="likesWidget"><ul></ul></aside>');if(this._canLike){a.appendTo(f.find("ul"));d.appendTo(f.find("ul"))}c.appendTo(f);this._addWidget(b,f)},_createWidget:function(b){var e=$('<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 c=$('<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){c.hide()}if(!this._allowForOwnContent&&(WCF.User.userID==this._containers[b].data("userID"))){e=$("");c=$("")}var d=$('<a class="badge jsTooltip likesBadge" />').data("containerID",b).click($.proxy(this._showLikeDetails,this));var a=null;if(this._showSummary){a=$('<p class="likesSummary"><span class="pointer" /></p>');a.children("span").data("containerID",b).click($.proxy(this._showLikeDetails,this))}this._buildWidget(b,e,c,d,a);this._containerData[b].likeButton=e;this._containerData[b].dislikeButton=c;this._containerData[b].badge=d;this._containerData[b].summary=a;e.data("containerID",b).data("type","like").click($.proxy(this._click,this));c.data("containerID",b).data("type","dislike").click($.proxy(this._click,this));this._setActiveState(e,c,this._containerData[b].liked);this._updateBadge(b);if(this._showSummary){this._updateSummary(b)}},_showLikeDetails:function(c,a){var b=(c===null)?a:$(c.currentTarget).data("containerID");if(this._likeDetails[b]===undefined){this._likeDetails[b]=new WCF.User.List("wcf\\data\\like\\LikeAction",WCF.Language.get("wcf.like.details"),{data:{containerID:b,objectID:this._containerData[b].objectID,objectType:this._containerData[b].objectType}})}this._likeDetails[b].open()},_click:function(a){var b=$(a.currentTarget);if(b===null){console.debug("[WCF.Like] Unable to find target button, aborting.");return}this._sendRequest(b.data("containerID"),b.data("type"))},_sendRequest:function(a,b){if(this._isBusy){return}this._isBusy=true;this._proxy.setOption("data",{actionName:b,className:"wcf\\data\\like\\LikeAction",parameters:{data:{containerID:a,objectID:this._containerData[a].objectID,objectType:this._containerData[a].objectType}}});this._proxy.sendRequest()},_success:function(d,g,b){var a=d.returnValues.containerID;if(!this._containers[a]){return}switch(d.actionName){case"dislike":case"like":this._containerData[a].likes=parseInt(d.returnValues.likes);this._containerData[a].dislikes=parseInt(d.returnValues.dislikes);this._containerData[a].users=d.returnValues.users;this._updateBadge(a);if(this._showSummary){this._updateSummary(a)}var f=this._containerData[a].likeButton;var e=this._containerData[a].dislikeButton;var c=0;if(d.returnValues.isLiked){c=1}else{if(d.returnValues.isDisliked){c=-1}}this._setActiveState(f,e,c);if(this._likeDetails[a]!==undefined){delete this._likeDetails[a]}this._isBusy=false;break}},_updateBadge:function(a){if(!this._containerData[a].likes&&!this._containerData[a].dislikes){this._containerData[a].badge.hide()}else{this._containerData[a].badge.show();var c=this._containerData[a].likes-this._containerData[a].dislikes;var d=this._containerData[a].badge;d.removeClass("green red");if(c>0){d.text("+"+WCF.String.formatNumeric(c));d.addClass("green")}else{if(c<0){d.text(WCF.String.formatNumeric(c));d.addClass("red")}else{d.text("\u00B10")}}var e=this._containerData[a].likes;var b=this._containerData[a].dislikes;d.data("tooltip",WCF.Language.get("wcf.like.tooltip",{likes:e,dislikes:b}))}},_updateSummary:function(a){if(!this._containerData[a].likes){this._containerData[a].summary.hide()}else{this._containerData[a].summary.show();var d=this._containerData[a].users;var c=[];for(var b in d){c.push(d[b].username)}var e=this._containerData[a].likes-c.length;this._containerData[a].summary.children("span").html(WCF.Language.get("wcf.like.summary",{users:c,others:e}))}},_setActiveState:function(a,b,c){a.removeClass("active");b.removeClass("active");if(c==1){a.addClass("active")}else{if(c==-1){b.addClass("active")}}}});WCF.ACL={};WCF.ACL.List=Class.extend({_categoryName:"",_container:null,_containerElements:{},_objectID:0,_objectTypeID:null,_options:{},_proxy:null,_search:null,_values:{group:{},user:{}},init:function(g,i,d,b,c,j){this._objectID=b||0;this._objectTypeID=i;this._categoryName=d;if(c===undefined){c=true}this._values={group:{},user:{}};this._proxy=new WCF.Action.Proxy({showLoadingOverlay:false,success:$.proxy(this._success,this)});this._container=$(g).hide().addClass("aclContainer");var f=this._container.children("dd");var a=$('<ul class="aclList container" />').appendTo(f);var k=$('<input type="text" class="long" placeholder="'+WCF.Language.get("wcf.acl.search."+(!c?"user.":"")+"description")+'" />').appendTo(f);var e=$('<ul class="aclPermissionList container" />').hide().appendTo(f);this._containerElements={aclList:a,denyAll:null,grantAll:null,permissionList:e,searchInput:k};this._search=new WCF.Search.User(k,$.proxy(this.addObject,this),c);var l=this._container.parents("form:eq(0)");l.submit($.proxy(this.submit,this));var h=l.find("input[type=reset]:eq(0)");if(h.length){h.click($.proxy(this._reset,this))}if(j){this._success(j)}else{this._loadACL()}},_reset:function(){this._values={group:{},user:{}};this._containerElements.aclList.empty();this._containerElements.searchInput.val("");this._containerElements.permissionList.hide().find("input[type=checkbox]").prop("checked",false)},_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()},addObject:function(b){var a=this._createListItem(b.objectID,b.label,b.type);this._savePermissions();this._containerElements.aclList.children("li").removeClass("active");a.addClass("active");this._search.addExcludedSearchValue(b.label);this._containerElements.permissionList.find("input[type=checkbox]").prop("checked",false);this._containerElements.searchInput.val("");this._containerElements.permissionList.show();WCF.DOMNodeInsertedHandler.execute()},_createListItem:function(d,a,c){var b=$('<li><span class="icon icon16 icon-'+(c==="group"?"group":"user")+'" /> <span>'+a+"</span></li>").appendTo(this._containerElements.aclList);b.data("objectID",d).data("type",c).data("label",a).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(b);return b},_removeItem:function(d){var b=$(d.currentTarget).parent();var a=b.data("type");var c=b.data("objectID");this._search.removeExcludedSearchValue(b.data("label"));b.remove();if(this._values[a][c]){delete this._values[a][c]}this._selectFirstEntry()},_selectFirstEntry:function(){var a=this._containerElements.aclList.children("li:eq(0)");if(a.length){this._select(a,false)}else{this._reset()}},_success:function(g,f,l){if(!$.getLength(g.returnValues.options)){return}var i=0;var n={};for(var h in g.returnValues.options){var a=g.returnValues.options[h];var j=$("<li><span>"+a.label+"</span></li>").data("optionID",h).data("optionName",a.optionName);var d=$('<input type="checkbox" id="grant'+h+'" />').appendTo(j).wrap('<label for="grant'+h+'" class="jsTooltip" title="'+WCF.Language.get("wcf.acl.option.grant")+'" />');var b=$('<input type="checkbox" id="deny'+h+'" />').appendTo(j).wrap('<label for="deny'+h+'" class="jsTooltip" title="'+WCF.Language.get("wcf.acl.option.deny")+'" />');d.data("type","grant").data("optionID",h).change($.proxy(this._change,this));b.data("type","deny").data("optionID",h).change($.proxy(this._change,this));if(!n[a.categoryName]){n[a.categoryName]=[]}if(a.categoryName===""){j.appendTo(this._containerElements.permissionList)}else{n[a.categoryName].push(j)}i++}if(i>1){var j=$('<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(j).wrap('<label for="grantAll" class="jsTooltip" title="'+WCF.Language.get("wcf.acl.option.grant")+'" />');this._containerElements.denyAll=$('<input type="checkbox" id="denyAll" />').appendTo(j).wrap('<label for="denyAll" class="jsTooltip" title="'+WCF.Language.get("wcf.acl.option.deny")+'" />');this._containerElements.grantAll.data("type","grant").change($.proxy(this._changeAll,this));this._containerElements.denyAll.data("type","deny").change($.proxy(this._changeAll,this))}if($.getLength(n)){for(var e in n){var c=n[e];if(g.returnValues.categories[e]){$('<li class="aclCategory">'+g.returnValues.categories[e]+"</li>").appendTo(this._containerElements.permissionList)}for(var m=0,k=c.length;m<k;m++){c[m].appendTo(this._containerElements.permissionList)}}}this._parseData(g,"group");this._parseData(g,"user");this._container.show();this._selectFirstEntry()},_parseData:function(c,a){if(!$.getLength(c.returnValues[a].option)){return}for(var b in c.returnValues[a].label){this._createListItem(b,c.returnValues[a].label[b],a);this._search.addExcludedSearchValue(c.returnValues[a].label[b])}this._values[a]=c.returnValues[a].option;WCF.DOMNodeInsertedHandler.execute()},_click:function(b){var a=$(b.currentTarget);if(a.hasClass("active")){return}this._select(a,true)},_select:function(b,a){if(a){this._savePermissions()}this._containerElements.aclList.children("li").removeClass("active");b.addClass("active");this._setupPermissions(b.data("type"),b.data("objectID"))},_change:function(d){var c=$(d.currentTarget);var b=c.data("optionID");var a=c.data("type");if(c.is(":checked")){if(a==="deny"){$("#grant"+b).prop("checked",false);if(this._containerElements.grantAll!==null){this._containerElements.grantAll.prop("checked",false)}}else{$("#deny"+b).prop("checked",false);if(this._containerElements.denyAll!==null){this._containerElements.denyAll.prop("checked",false)}}}else{if(a==="deny"&&this._containerElements.denyAll!==null){this._containerElements.denyAll.prop("checked",false)}else{if(a==="grant"&&this._containerElements.grantAll!==null){this._containerElements.grantAll.prop("checked",false)}}}var e=true;this._containerElements.permissionList.find("input[type=checkbox]").each(function(g,h){var f=$(h);if(f.data("type")===a&&f.attr("id")!==a+"All"){if(!f.is(":checked")){e=false;return false}}});if(a=="deny"){if(this._containerElements.denyAll!==null){if(e){this._containerElements.denyAll.prop("checked",true)}else{this._containerElements.denyAll.prop("checked",false)}}}else{if(this._containerElements.grantAll!==null){if(e){this._containerElements.grantAll.prop("checked",true)}else{this._containerElements.grantAll.prop("checked",false)}}}},_changeAll:function(c){var b=$(c.currentTarget);var a=b.data("type");if(b.is(":checked")){if(a==="deny"){this._containerElements.grantAll.prop("checked",false);this._containerElements.permissionList.find("input[type=checkbox]").each(function(e,f){var d=$(f);if(d.data("type")==="deny"&&d.attr("id")!=="denyAll"){d.prop("checked",true).trigger("change")}})}else{this._containerElements.denyAll.prop("checked",false);this._containerElements.permissionList.find("input[type=checkbox]").each(function(e,f){var d=$(f);if(d.data("type")==="grant"&&d.attr("id")!=="grantAll"){d.prop("checked",true).trigger("change")}})}}else{if(a==="deny"){this._containerElements.grantAll.prop("checked",false);this._containerElements.permissionList.find("input[type=checkbox]").each(function(e,f){var d=$(f);if(d.data("type")==="deny"&&d.attr("id")!=="denyAll"){d.prop("checked",false).trigger("change")}})}else{this._containerElements.denyAll.prop("checked",false);this._containerElements.permissionList.find("input[type=checkbox]").each(function(e,f){var d=$(f);if(d.data("type")==="grant"&&d.attr("id")!=="grantAll"){d.prop("checked",false).trigger("change")}})}}},_setupPermissions:function(b,c){this._containerElements.permissionList.find("input[type='checkbox']").prop("checked",false);if(this._values[b]&&this._values[b][c]){for(var a in this._values[b][c]){if(this._values[b][c][a]==1){$("#grant"+a).prop("checked",true).trigger("change")}else{$("#deny"+a).prop("checked",true).trigger("change")}}}this._containerElements.permissionList.show()},_savePermissions:function(){var c=this._containerElements.aclList.find("li.active");if(!c.length){return}var d=c.data("objectID");var a=c.data("type");this._values[a][d]={};var b=this;this._containerElements.permissionList.find("input[type='checkbox']").each(function(e,i){var h=$(i);if(h.attr("id")!="grantAll"&&h.attr("id")!="denyAll"){var g=(h.data("type")==="deny")?0:1;var f=h.data("optionID");if(h.is(":checked")){b._values[a][d][f]=g;h.prop("checked",false)}else{if(b._values[a]&&b._values[a][d]&&b._values[a][d][f]&&b._values[a][d][f]==g){delete b._values[a][d][f]}}}})},submit:function(a){this._savePermissions();this._save("group");this._save("user")},_save:function(a){if($.getLength(this._values[a])){var b=this._container.parents("form:eq(0)");for(var e in this._values[a]){var d=this._values[a][e];for(var c in d){$('<input type="hidden" name="aclValues['+a+"]["+e+"]["+c+']" value="'+d[c]+'" />').appendTo(b)}}}}});WCF.Attachment={};WCF.Attachment.Upload=WCF.Upload.extend({_objectType:"",_objectID:0,_tmpHash:"",_parentObjectID:0,_wysiwygContainerID:"",init:function(c,g,a,h,d,e,f,b){this._super(c,g,"wcf\\data\\attachment\\AttachmentAction",{multiple:true,maxUploads:f});this._objectType=a;this._objectID=h;this._tmpHash=d;this._parentObjectID=e;this._wysiwygContainerID=b;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))},_validateLimit:function(){var c=this._buttonSelector.next("small.innerError");var a=this._options.maxUploads-this._fileListSelector.children("li:not(.uploadFailed)").length;var d=(this._fileUpload)?this._fileUpload.prop("files").length:0;if(a<=0||a<d){var b=(a<=0)?WCF.Language.get("wcf.attachment.upload.error.reachedLimit"):WCF.Language.get("wcf.attachment.upload.error.reachedRemainingLimit").replace(/#remaining#/,a);if(!c.length){c=$('<small class="innerError" />').insertAfter(this._buttonSelector)}c.html(b);return false}c.remove();return true},_removeLimitError:function(b){var a=$(b.target);if(a.is("li.box48")&&a.parent().wcfIdentify()===this._fileListSelector.wcfIdentify()){this._buttonSelector.next("small.innerError").remove()}},_upload:function(){if(this._validateLimit()){this._super()}if(this._fileUpload){this._removeButton();this._createButton()}},_createUploadMatrix:function(a){this._fileListSelector.children("li.uploadFailed").remove();return this._super(a)},_getParameters:function(){return{objectType:this._objectType,objectID:this._objectID,tmpHash:this._tmpHash,parentObjectID:this._parentObjectID}},_initFile:function(a){var b=$('<li class="box48"><span class="icon icon48 icon-spinner" /><div><div><p>'+a.name+'</p><small><progress max="100"></progress></small></div><ul></ul></div></li>').data("filename",a.name);this._fileListSelector.append(b);this._fileListSelector.show();if(this._buttonSelector.data("maxSize")<a.size){b.find("progress").remove();b.children(".icon-spinner").removeClass("icon-spinner").addClass("icon-ban-circle");b.find("div > div").append($('<small class="innerError">'+WCF.Language.get("wcf.attachment.upload.error.tooLarge")+"</small>"));b.addClass("uploadFailed")}return b},_success:function(b,c){for(var i in this._uploadMatrix[b]){var g=this._uploadMatrix[b][i];g.find("progress").remove();var h=g.data("filename");var j=g.data("internalFileID");if(c.returnValues&&c.returnValues.attachments[j]){if(c.returnValues.attachments[j]["tinyURL"]){g.children(".icon-spinner").replaceWith($('<img src="'+c.returnValues.attachments[j]["tinyURL"]+'" alt="" class="attachmentTinyThumbnail" />'))}else{g.children(".icon-spinner").removeClass("icon-spinner").addClass("icon-paper-clip")}var e=$('<a href=""></a>');e.text(h).attr("href",c.returnValues.attachments[j]["url"]);if(c.returnValues.attachments[j]["isImage"]!=0){e.addClass("jsImageViewer").attr("title",h)}g.find("p").empty().append(e);g.find("small").append(c.returnValues.attachments[j]["formattedFilesize"]);var f=$('<li><span class="icon icon16 icon-remove pointer jsTooltip jsDeleteButton" title="'+WCF.Language.get("wcf.global.button.delete")+'" data-object-id="'+c.returnValues.attachments[j]["attachmentID"]+'" data-confirm-message="'+WCF.Language.get("wcf.attachment.delete.sure")+'" /></li>');g.find("ul").append(f);if(this._wysiwygContainerID){var a=$('<li><span class="icon icon16 icon-paste pointer jsTooltip jsButtonInsertAttachment" title="'+WCF.Language.get("wcf.attachment.insert")+'" data-object-id="'+c.returnValues.attachments[j]["attachmentID"]+'" /></li>');a.children(".jsButtonInsertAttachment").click($.proxy(this._insert,this));g.find("ul").append(a)}}else{g.children(".icon-spinner").removeClass("icon-spinner").addClass("icon-ban-circle");var d="";if(c.returnValues&&c.returnValues.errors[j]){d=c.returnValues.errors[j]["errorType"]}else{d="uploadFailed"}g.find("div > div").append($('<small class="innerError">'+WCF.Language.get("wcf.attachment.upload.error."+d)+"</small>"));g.addClass("uploadFailed")}g.css("display","block")}WCF.DOMNodeInsertedHandler.execute()},_insert:function(e){var d=$(e.currentTarget).data("objectID");var c="[attach="+d+"][/attach]";var a=($.browser.mobile)?null:$("#"+this._wysiwygContainerID).ckeditorGet();if(a!==null&&a.mode==="wysiwyg"){a.insertText(c)}else{var g=($.browser.mobile)?$("#"+this._wysiwygContainerID):$("#"+this._wysiwygContainerID).next(".cke_editor_text").find("textarea");var b=g.val();if(b.length==0){g.val(c)}else{var f=g.getCaret();g.val(b.substr(0,f)+c+b.substr(f))}}},_error:function(a){this._fileListSelector.find("li").each(function(b,d){var c=$(d);if(c.children(".icon-spinner").length){c.addClass("uploadFailed").children(".icon-spinner").removeClass("icon-spinner").addClass("icon-ban-circle");c.find("div > div").append($('<small class="innerError">'+(a.responseJSON&&a.responseJSON.message?a.responseJSON.message:WCF.Language.get("wcf.attachment.upload.error.uploadFailed"))+"</small>"))}})}});WCF.ColorPicker=Class.extend({_bar:null,_barActive:false,_barSelector:null,_dialog:null,_didInit:false,_elementID:"",_gradient:null,_gradientActive:false,_gradientSelector:null,_hex:null,_hsv:{},_newColor:null,_oldColor:null,_rgba:{},_rgbaRegExp:null,init:function(a){this._elementID="";this._hsv={h:0,s:100,v:100};this._position={};var b=$(a);if(!b.length){console.debug("[WCF.ColorPicker] Selector does not match any element, aborting.");return}b.click($.proxy(this._open,this))},_open:function(c){if(!this._didInit){this._initColorPicker();this._didInit=true}var a=$(c.currentTarget);this._elementID=a.wcfIdentify();this._parseColor(a);var b=this.hsvToRgb(this._hsv.h,this._hsv.s,this._hsv.v);this._oldColor.css({backgroundColor:"rgb("+b.r+", "+b.g+", "+b.b+")"});this._dialog.wcfDialog({title:WCF.Language.get("wcf.style.colorPicker")})},_parseColor:function(b){if(b.data("hsv")&&b.data("rgb")){var d=b.data("hsv");for(var a in d){this._hsv[a]=d[a]}this._updateValues(b.data("rgb"),true,true);this._rgba.a.val(parseInt(b.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})\\)$")}this._rgbaRegExp.exec(b.data("color"));var c=RegExp.$4;if(c.indexOf(".")===0){c="0"+c}c*=100;this._updateValues({r:RegExp.$1,g:RegExp.$2,b:RegExp.$3,a:Math.round(c)},true,true)}},_initColorPicker:function(){this._dialog=$('<div id="colorPickerContainer" />').hide().appendTo(document.body);this._gradient=$('<div id="colorPickerGradient" />').appendTo(this._dialog);this._gradientSelector=$('<span id="colorPickerGradientSelector"><span></span></span>').appendTo(this._gradient);this._bar=$('<div id="colorPickerBar" />').appendTo(this._dialog);this._barSelector=$('<span id="colorPickerBarSelector" />').appendTo(this._bar);this._gradient.mousedown($.proxy(this._mouseDownGradient,this));this._bar.mousedown($.proxy(this._mouseDownBar,this));var a=this;$(document).mouseup(function(b){if(a._barActive){a._barActive=false;a._mouseBar(b)}else{if(a._gradientActive){a._gradientActive=false;a._mouseGradient(b)}}}).mousemove(function(b){if(a._barActive){a._mouseBar(b)}else{if(a._gradientActive){a._mouseGradient(b)}}});this._initColorPickerForm()},_initColorPickerForm:function(){var c=$('<div id="colorPickerForm" />').appendTo(this._dialog);$("<small>"+WCF.Language.get("wcf.style.colorPicker.new")+"</small>").appendTo(c);var d=$('<ul class="colors" />').appendTo(c);this._newColor=$('<li class="new" />').appendTo(d);this._oldColor=$('<li class="old" />').appendTo(d);$("<small>"+WCF.Language.get("wcf.style.colorPicker.current")+"</small>").appendTo(c);var a=$('<ul class="rgba" />').appendTo(c);this._createInputElement("r","R",0,255).appendTo(a);this._createInputElement("g","G",0,255).appendTo(a);this._createInputElement("b","B",0,255).appendTo(a);this._createInputElement("a","a",0,100).appendTo(a);var e=$('<ul class="hex"><li><label><span>#</span></label></li></ul>').appendTo(c);this._hex=$('<input type="text" maxlength="6" />').appendTo(e.find("label"));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));var f=$('<div class="formSubmit" />').appendTo(this._dialog);$('<button class="buttonPrimary">'+WCF.Language.get("wcf.style.colorPicker.button.apply")+"</button>").appendTo(f).click($.proxy(this._submit,this));var b=this;this._hex.on("paste",function(){b._hex.attr("maxlength","7");setTimeout(function(){var g=b._hex.val();if(g.substring(0,1)=="#"){g=g.substr(1)}if(g.length>6){g=g.substring(0,6)}b._hex.attr("maxlength","6").val(g)},50)})},_keyUpRGBA:function(a){if(a.which==13){this._blurRgba();this._submit()}},_keyUpHex:function(a){if(a.which==13){this._blurHex();this._submit()}},_submit:function(){var d=this.hsvToRgb(this._hsv.h,this._hsv.s,this._hsv.v);var c={};for(var b in this._hsv){c[b]=this._hsv[b]}var a=$("#"+this._elementID);a.data("hsv",c).css({backgroundColor:"rgb("+d.r+", "+d.g+", "+d.b+")"}).data("alpha",parseInt(this._rgba.a.val()));a.data("rgb",{r:this._rgba.r.val(),g:this._rgba.g.val(),b:this._rgba.b.val()});$("#"+a.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")},_createInputElement:function(f,c,e,b){var d=$('<li class="'+f+'" />');var a=$("<label />").appendTo(d);$("<span>"+c+"</span>").appendTo(a);this._rgba[f]=$('<input type="number" value="0" min="'+e+'" max="'+b+'" step="1" />').appendTo(a);return d},_mouseDownGradient:function(a){this._gradientActive=true;this._mouseGradient(a)},_mouseGradient:function(b){var c=this._gradient.getOffsets("offset");var a=Math.max(Math.min(b.pageX-c.left,255),0);var d=Math.max(Math.min(b.pageY-c.top,255),0);this._hsv.s=Math.max(0,Math.min(1,a/255))*100;this._hsv.v=Math.max(0,Math.min(1,(255-d)/255))*100;this._updateValues(null)},_mouseDownBar:function(a){this._barActive=true;this._mouseBar(a)},_mouseBar:function(a){var b=this._bar.getOffsets("offset");var c=Math.max(Math.min(a.pageY-b.top,255),0);this._barSelector.css({top:c+"px"});this._hsv.h=Math.max(0,Math.min(359,Math.round((255-c)/255*360)));this._updateValues(null)},_blurRgba:function(){for(var a in this._rgba){var b=parseInt(this._rgba[a].val())||0;if(a==="a"){this._rgba[a].val(Math.max(0,Math.min(100,b)))}else{this._rgba[a].val(Math.max(0,Math.min(255,b)))}}this._updateValues({r:this._rgba.r.val(),g:this._rgba.g.val(),b:this._rgba.b.val()},true,true)},_blurHex:function(){var a=this.hexToRgb(this._hex.val());if(a!==Number.NaN){this._updateValues(a,true,true)}},_updateValues:function(c,e,a){e=(e===true)?true:false;a=(a===true)?true:false;if(c===null){c=this.hsvToRgb(this._hsv.h,this._hsv.s,this._hsv.v)}if(c.a===undefined){c.a=this._rgba.a.val()}for(var b in c){this._rgba[b].val(c[b])}this._hex.val(this.rgbToHex(c.r,c.g,c.b));if(e||a){var g=this.rgbToHsv(c.r,c.g,c.b);if(e){this._hsv.h=g.h}if(a){this._hsv.s=g.s;this._hsv.v=g.v}}var h=Math.max(0,Math.min(255,255-(this._hsv.h/360)*255));this._barSelector.css({top:h+"px"});var d=Math.max(0,Math.min(255,(this._hsv.s/100)*255));var h=Math.max(0,Math.min(255,255-((this._hsv.v/100)*255)));this._gradientSelector.css({left:(d-6)+"px",top:(h-6)+"px"});this._newColor.css({backgroundColor:"rgb("+c.r+", "+c.g+", "+c.b+")"});var f=this.hsvToRgb(this._hsv.h,100,100);this._gradient.css({backgroundColor:"rgb("+f.r+", "+f.g+", "+f.b+")"})},hsvToRgb:function(d,j,g){var b={r:0,g:0,b:0};var i,a,f,e,c;i=Math.floor(d/60);a=d/60-i;j/=100;g/=100;f=g*(1-j);e=g*(1-j*a);c=g*(1-j*(1-a));if(j==0){b.r=b.g=b.b=g}else{switch(i){case 1:b.r=e;b.g=g;b.b=f;break;case 2:b.r=f;b.g=g;b.b=c;break;case 3:b.r=f;b.g=e;b.b=g;break;case 4:b.r=c;b.g=f;b.b=g;break;case 5:b.r=g;b.g=f;b.b=e;break;case 0:case 6:b.r=g;b.g=c;b.b=f;break}}return{r:Math.round(b.r*255),g:Math.round(b.g*255),b:Math.round(b.b*255)}},rgbToHsv:function(a,f,i){var j,e,c;var d,k,h;a/=255;f/=255;i/=255;d=Math.max(Math.max(a,f),i);k=Math.min(Math.min(a,f),i);h=d-k;j=0;if(d!==k){switch(d){case a:j=60*(0+(f-i)/h);break;case f:j=60*(2+(i-a)/h);break;case i:j=60*(4+(a-f)/h);break}if(j<0){j+=360}}if(d===0){e=0}else{e=h/d}c=d;return{h:Math.round(j),s:Math.round(e*100),v:Math.round(c*100)}},hexToRgb:function(a){if(/^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(a)){a=a.split("");if(a[0]==="#"){a.shift()}if(a.length===3){return{r:parseInt(a[0]+""+a[0],16),g:parseInt(a[1]+""+a[1],16),b:parseInt(a[2]+""+a[2],16)}}else{return{r:parseInt(a[0]+""+a[1],16),g:parseInt(a[2]+""+a[3],16),b:parseInt(a[4]+""+a[5],16)}}}return Number.NaN},rgbToHex:function(d,c,a){return("0123456789ABCDEF".charAt((d-d%16)/16)+""+"0123456789ABCDEF".charAt(d%16))+""+("0123456789ABCDEF".charAt((c-c%16)/16)+""+"0123456789ABCDEF".charAt(c%16))+""+("0123456789ABCDEF".charAt((a-a%16)/16)+""+"0123456789ABCDEF".charAt(a%16))}});WCF.Comment={};WCF.Comment.Handler=Class.extend({_commentAdd:null,_commentButtonList:{},_comments:{},_container:null,_containerID:"",_displayedComments:0,_loadNextComments:null,_loadNextResponses:{},_proxy:null,_responses:{},_userAvatar:"",_commentData:{},_guestDialog:null,_useRecaptcha:true,init:function(a,b){this._commentAdd=null;this._commentButtonList={};this._comments={};this._containerID=a;this._displayedComments=0;this._loadNextComments=null;this._loadNextResponses={};this._responses={};this._userAvatar=b;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();if(this._container.data("canAdd")){this._initAddComment()}WCF.DOMNodeInsertedHandler.execute();WCF.DOMNodeInsertedHandler.addCallback("WCF.Comment.Handler",$.proxy(this._domNodeInserted,this))},_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()}}},_handleLoadNextResponses:function(a){var b=this._comments[a];b.data("displayedResponses",b.find("ul.commentResponseList > li").length);if(b.data("displayedResponses")<b.data("responses")){if(this._loadNextResponses[a]===undefined){var d=b.data("responses")-b.data("displayedResponses");this._loadNextResponses[a]=$('<li class="jsCommentLoadNextResponses"><a>'+WCF.Language.get("wcf.comment.response.more",{count:d})+"</a></li>").appendTo(this._commentButtonList[a]);this._loadNextResponses[a].children("a").data("commentID",a).click($.proxy(this._loadResponses,this));this._commentButtonList[a].parent().show()}}else{if(this._loadNextResponses[a]!==undefined){var c=this._loadNextResponses[a].next();this._loadNextResponses[a].remove();if(c.length){c.trigger("click")}}}},_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()},_loadResponses:function(a){this._loadResponsesExecute($(a.currentTarget).disable().data("commentID"),false)},_loadResponsesExecute:function(a,b){this._proxy.setOption("data",{actionName:"loadResponses",className:"wcf\\data\\comment\\response\\CommentResponseAction",parameters:{data:{commentID:a,lastResponseTime:this._comments[a].data("lastResponseTime"),loadAllResponses:(b?1:0)}}});this._proxy.sendRequest()},_domNodeInserted:function(){this._initComments();this._initResponses()},_initComments:function(){var a=this;var b=false;this._container.find(".jsComment").each(function(e,g){var f=$(g).removeClass("jsComment");var c=f.data("commentID");a._comments[c]=f;var d=f.find("ul.commentResponseList");if(!d.length){d=f.find(".commentContent")}$container=$('<div class="commentOptionContainer" />').hide().insertAfter(d);a._commentButtonList[c]=$("<ul />").appendTo($container);a._handleLoadNextResponses(c);a._initComment(c,f);a._displayedComments++;b=true});if(b){this._handleLoadNextComments()}},_initComment:function(a,d){if(this._container.data("canAdd")){this._initAddResponse(a,d)}if(d.data("canEdit")){var b=$('<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>");b.data("commentID",a).appendTo(d.find("ul.commentOptions:eq(0)")).click($.proxy(this._prepareEdit,this))}if(d.data("canDelete")){var c=$('<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>");c.data("commentID",a).appendTo(d.find("ul.commentOptions:eq(0)")).click($.proxy(this._delete,this))}},_initResponses:function(){var a=this;this._container.find(".jsCommentResponse").each(function(d,c){var b=$(c).removeClass("jsCommentResponse");var e=b.data("responseID");a._responses[e]=b;a._initResponse(e,b)})},_initResponse:function(a,c){if(c.data("canEdit")){var d=$('<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 b=this;d.data("responseID",a).appendTo(c.find("ul.commentOptions:eq(0)")).click(function(f){b._prepareEdit(f,true)})}if(c.data("canDelete")){var e=$('<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 b=this;e.data("responseID",a).appendTo(c.find("ul.commentOptions:eq(0)")).click(function(f){b._delete(f,true)})}},_initAddComment:function(){this._commentAdd=$('<li class="box32 jsCommentAdd"><span class="framed">'+this._userAvatar+"</span><div /></li>").prependTo(this._container);var a=this._commentAdd.children("div");var b=$('<input type="text" placeholder="'+WCF.Language.get("wcf.comment.add")+'" maxlength="65535" class="long" />').appendTo(a);$("<small>"+WCF.Language.get("wcf.comment.description")+"</small>").appendTo(a);b.keyup($.proxy(this._keyUp,this))},_initAddResponse:function(d,g){var c=null;if(!g.data("responses")||this._loadNextResponses[d]){c=$('<li class="jsCommentShowAddResponse"><a>'+WCF.Language.get("wcf.comment.button.response.add")+"</a></li>").data("commentID",d).click($.proxy(this._showAddResponse,this)).appendTo(this._commentButtonList[d])}var e=$('<div class="box32 commentResponseAdd jsCommentResponseAdd"><span class="framed">'+this._userAvatar+"</span><div /></div>");if(c!==null){e.hide()}else{this._commentButtonList[d].parent().addClass("jsAddResponseActive")}e.appendTo(this._commentButtonList[d].parent().show());var a=e.children("div");var f=$('<input type="text" placeholder="'+WCF.Language.get("wcf.comment.response.add")+'" maxlength="65535" class="long" />').data("commentID",d).appendTo(a);$("<small>"+WCF.Language.get("wcf.comment.description")+"</small>").appendTo(a);var b=this;f.keyup(function(h){b._keyUp(h,true)});g.data("responsePlaceholder",c).data("responseInput",e)},_prepareEdit:function(c,a){var d=$(c.currentTarget);var b={objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID")};if(a===true){b.responseID=d.data("responseID")}else{b.commentID=d.data("commentID")}this._proxy.setOption("data",{actionName:"prepareEdit",className:"wcf\\data\\comment\\CommentAction",parameters:{data:b}});this._proxy.sendRequest()},_showAddResponse:function(c){var b=$(c.currentTarget);var a=b.data("commentID");if(b.prev().hasClass("jsCommentLoadNextResponses")){this._loadResponsesExecute(a,true);b.parent().children(".button").disable()}b.remove();var d=this._comments[a].data("responseInput").show();d.find("input").focus();d.parents(".commentOptionContainer").addClass("jsAddResponseActive")},_keyUp:function(e,b){if(e.which!==13&&e.which!==27){return}var f=$(e.currentTarget);if(e.which===27){f.val("").trigger("blur",e);return}var d=$.trim(f.val());if(d==""){return}var a="addComment";var c={message:d,objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID")};if(b===true){a="addResponse";c.commentID=f.data("commentID")}if(!WCF.User.userID){this._commentData=c;if(this._guestDialog===null){this._proxy.setOption("data",{actionName:"getGuestDialog",className:"wcf\\data\\comment\\CommentAction",parameters:{data:{message:d,objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID")}}});this._proxy.sendRequest()}else{if(this._useRecaptcha){Recaptcha.reload()}this._guestDialog.find('input[type="submit"]').enable();this._guestDialog.wcfDialog("open")}}else{this._proxy.setOption("data",{actionName:a,className:"wcf\\data\\comment\\CommentAction",parameters:{data:c}});this._proxy.sendRequest()}},_delete:function(b,a){WCF.System.Confirmation.show(WCF.Language.get("wcf.comment.delete.confirmMessage"),$.proxy(function(d){if(d==="confirm"){var c={objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID")};if(a!==true){c.commentID=$(b.currentTarget).data("commentID")}else{c.responseID=$(b.currentTarget).data("responseID")}this._proxy.setOption("data",{actionName:"remove",className:"wcf\\data\\comment\\CommentAction",parameters:{data:c}});this._proxy.sendRequest()}},this))},_failure:function(b,a,d,c){if(!WCF.User.userID&&this._guestDialog){this._guestDialog.find('input[type="submit"]').enable()}return true},_success:function(d,e,b){switch(d.actionName){case"addComment":if(d.returnValues.errors){this._handleGuestDialogErrors(d.returnValues.errors)}else{this._commentAdd.find("input").val("").blur();$(d.returnValues.template).insertAfter(this._commentAdd).wcfFadeIn();if(!WCF.User.userID){this._guestDialog.wcfDialog("close")}}break;case"addResponse":if(d.returnValues.errors){this._handleGuestDialogErrors(d.returnValues.errors)}else{var c=this._comments[d.returnValues.commentID];c.find(".jsCommentResponseAdd input").val("").blur();var a=c.find("ul.commentResponseList");if(!a.length){a=$('<ul class="commentResponseList" />').insertBefore(c.find(".commentOptionContainer"))}$(d.returnValues.template).appendTo(a).wcfFadeIn()}if(!WCF.User.userID){this._guestDialog.wcfDialog("close")}break;case"edit":this._update(d);break;case"loadComments":this._insertComments(d);break;case"loadResponses":this._insertResponses(d);break;case"prepareEdit":this._edit(d);break;case"remove":this._remove(d);break;case"getGuestDialog":this._createGuestDialog(d);break}WCF.DOMNodeInsertedHandler.execute()},_insertComments:function(a){$(a.returnValues.template).insertBefore(this._loadNextComments);this._container.data("lastCommentTime",a.returnValues.lastCommentTime)},_insertResponses:function(b){var a=this._comments[b.returnValues.commentID];$(b.returnValues.template).appendTo(a.find("ul.commentResponseList"));a.data("lastResponseTime",b.returnValues.lastResponseTime);this._handleLoadNextResponses(b.returnValues.commentID)},_remove:function(a){if(a.returnValues.commentID){this._comments[a.returnValues.commentID].remove();delete this._comments[a.returnValues.commentID]}else{this._responses[a.returnValues.responseID].remove();delete this._responses[a.returnValues.responseID]}},_edit:function(b){if(b.returnValues.commentID){var a=this._comments[b.returnValues.commentID].find(".commentContent:eq(0) .userMessage:eq(0)")}else{var a=this._responses[b.returnValues.responseID].find(".commentContent:eq(0) .userMessage:eq(0)")}a.html($.proxy(function(d,c){var e=$('<input type="text" class="long" maxlength="65535" /><small>'+WCF.Language.get("wcf.comment.description")+"</small>").val(b.returnValues.message);e.data("__html",c).keyup($.proxy(this._saveEdit,this));if(b.returnValues.commentID){e.data("commentID",b.returnValues.commentID)}else{e.data("responseID",b.returnValues.responseID)}return e},this));a.children("input").focus();a.parent().find(".containerHeadline:eq(0)").hide();a.parent().find(".buttonGroupNavigation:eq(0)").hide()},_update:function(a){if(a.returnValues.commentID){var b=this._comments[a.returnValues.commentID].find(".commentContent:eq(0) .userMessage:eq(0) > input")}else{var b=this._responses[a.returnValues.responseID].find(".commentContent:eq(0) .userMessage:eq(0) > input")}b.data("__html",a.returnValues.message);this._cancelEdit(b)},_createGuestDialog:function(a){this._guestDialog=$('<div id="commentAddGuestDialog" />').append(a.returnValues.template).hide().appendTo(document.body);this._guestDialog.find('input[type="submit"]').click($.proxy(this._submit,this));this._guestDialog.find('input[type="text"]').keydown($.proxy(this._keyDown,this));this._useRecaptcha=this._guestDialog.find("dl.reCaptcha").length>0;this._guestDialog.wcfDialog({title:WCF.Language.get("wcf.comment.guestDialog.title")})},_keyDown:function(a){if(a.which===$.ui.keyCode.ENTER){this._submit()}},_handleGuestDialogErrors:function(d){if(d.username){var c=this._guestDialog.find('input[name="username"]');var b=c.next(".innerError");if(!b.length){b=$('<small class="innerError" />').text(d.username).insertAfter(c)}else{b.text(d.username).show()}}if(d.recaptcha){Recaptcha.reload();var a=this._guestDialog.find('input[name="recaptcha_response_field"]');var b=a.next(".innerError");if(!b.length){b=$('<small class="innerError" />').text(d.recaptcha).insertAfter(a)}else{b.text(d.recaptcha).show()}}this._guestDialog.find('input[type="submit"]').enable()},_submit:function(b){var c=true;this._guestDialog.find('input[type="submit"]').enable();var j=this._guestDialog.find('input[name="username"]');var a=j.val();var g=j.next(".innerError");if(!a){c=false;if(!g.length){g=$('<small class="innerError" />').text(WCF.Language.get("wcf.global.form.error.empty")).insertAfter(j)}else{g.text(WCF.Language.get("wcf.global.form.error.empty")).show()}}if(this._useRecaptcha){var e=this._guestDialog.find('input[name="recaptcha_response_field"]');var d=e.val();var i=e.next(".innerError");if(!d){c=false;if(!i.length){i=$('<small class="innerError" />').text(WCF.Language.get("wcf.global.form.error.empty")).insertAfter(e)}else{i.text(WCF.Language.get("wcf.global.form.error.empty")).show()}}}if(c){if(g.length){g.hide()}if(this._useRecaptcha&&i.length){i.hide()}var f=this._commentData;f.username=a;var h={data:f};if(this._useRecaptcha){h.recaptchaChallenge=Recaptcha.get_challenge();h.recaptchaResponse=Recaptcha.get_response()}this._proxy.setOption("data",{actionName:this._commentData.commentID?"addResponse":"addComment",className:"wcf\\data\\comment\\CommentAction",parameters:h});this._proxy.sendRequest();this._guestDialog.find('input[type="submit"]').disable()}},_saveEdit:function(c){var d=$(c.currentTarget);if(c.which===27){this._cancelEdit(d);return}else{if(c.which!==13){return}}var b=$.trim(d.val());if(b===""){return}var a={message:b,objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID")};if(d.data("commentID")){a.commentID=d.data("commentID")}else{a.responseID=d.data("responseID")}this._proxy.setOption("data",{actionName:"edit",className:"wcf\\data\\comment\\CommentAction",parameters:{data:a}});this._proxy.sendRequest()},_cancelEdit:function(a){a.parent().prev(".containerHeadline:eq(0)").show();a.parent().next(".buttonGroupNavigation:eq(0)").show();a.parent().html(a.data("__html"))}});WCF.Comment.Like=WCF.Like.extend({_getContainers:function(){return $(".commentList > li.comment")},_getObjectID:function(a){return this._containers[a].data("commentID")},_buildWidget:function(b,a,d,c,e){this._containers[b].find(".containerHeadline:eq(0) > h3").append(c);if(this._canLike){a.appendTo(this._containers[b].find(".commentOptions:eq(0)"));d.appendTo(this._containers[b].find(".commentOptions:eq(0)"))}},_getWidgetContainer:function(a){},_addWidget:function(a,b){}});WCF.Comment.Response={};WCF.Comment.Response.Like=WCF.Like.extend({_addWidget:function(a,b){},_buildWidget:function(b,a,d,c,e){this._containers[b].find(".containerHeadline:eq(0) > h3").append(c);if(this._canLike){a.appendTo(this._containers[b].find(".commentOptions:eq(0)"));d.appendTo(this._containers[b].find(".commentOptions:eq(0)"))}},_getContainers:function(){return $(".commentResponseList > li.commentResponse")},_getObjectID:function(a){return this._containers[a].data("responseID")},_getWidgetContainer:function(a){}});WCF.ImageViewer=Class.extend({init:function(){$('<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"));$('<span class="icon icon32 icon-remove jsTooltip" title="'+WCF.Language.get("wcf.imageViewer.close")+'" />').appendTo($("#lbCloseLink"));var a=$('<span class="icon icon32 icon-resize-full jsTooltip" title="'+WCF.Language.get("wcf.imageViewer.enlarge")+'" id="lbEnlarge" />').insertAfter($("#lbCloseLink"));a.click($.proxy(this._enlarge,this));this._initImageViewer();WCF.DOMNodeInsertedHandler.addCallback("WCF.ImageViewer",$.proxy(this._domNodeInserted,this));WCF.DOMNodeInsertedHandler.execute()},_domNodeInserted:function(){this._initImageSizeCheck();this._initImageViewer()},_initImageViewer:function(){if($.browser.touch&&/[Mm]obile/.test(navigator.userAgent)){if(!/iPad/.test(navigator.userAgent)){return}}var a=$("a.jsImageViewer");if(a.length){a.removeClass("jsImageViewer").slimbox({counterText:WCF.Language.get("wcf.imageViewer.counter"),loop:true})}},_enlarge:function(){var a=$("#lbImage").css("backgroundImage");if(a){a=a.replace(/^url\((["']?)(.*)\1\)$/,"$2");window.location=a}},_initImageSizeCheck:function(){$(".jsResizeImage").each($.proxy(function(a,b){if(b.complete){this._checkImageSize({currentTarget:b})}},this));$(".jsResizeImage").on("load",$.proxy(this._checkImageSize,this))},_checkImageSize:function(d){var c=$(d.currentTarget);if(!c.is(":visible")){c.off("load");return}c.removeClass("jsResizeImage");var b=c.getDimensions();var a=c.parents("div").innerWidth();if(b.width>a){c.css({height:Math.round(b.height*(a/b.width))+"px",width:a+"px"});if(!c.parents("a").length){c.wrap('<a href="'+c.attr("src")+'" />');c.parent().slimbox()}}}});$.widget("ui.wcfImageViewer",{_active:-1,_activeImage:null,_container:null,_didInit:false,_disableSlideshow:false,_eventNamespace:"",_images:[],_isOpen:false,_items:-1,_maxDimensions:{height:0,width:0},_proxy:null,_slideshowEnabled:false,_thumbnailContainerWidth:0,_thumbnailMarginRight:0,_thumbnailOffset:0,_thumbnailWidth:0,_timer:null,_ui:{buttonNext:null,buttonPrevious:null,header:null,image:null,imageContainer:null,imageList:null,slideshow:{container:null,enlarge:null,next:null,previous:null,toggle:null}},options:{shiftBy:5,enableSlideshow:1,speed:5,className:""},_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))},open:function(a){if(a){a.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},close:function(a){if(a){a.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},startSlideshow:function(){if(this._disableSlideshow||this._slideshowEnabled){return false}if(this._timer===null){this._timer=new WCF.PeriodicalExecuter($.proxy(function(){var a=this._active+1;if(a==this._items){a=0}this.showImage(a)},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},stopSlideshow:function(a){if(!this._slideshowEnabled){return false}this._timer.stop();if(a){this._ui.slideshow.toggle.children("span").removeClass("icon-pause").addClass("icon-play")}this._slideshowEnabled=false;return true},_bindListener:function(){$(document).on("keydown."+this._eventNamespace,$.proxy(this._keyDown,this));$(window).on("resize."+this._eventNamespace,$.proxy(this._renderImage,this))},_unbindListener:function(){$(document).off("keydown."+this._eventNamespace);$(window).off("resize."+this._eventNamespace)},_keyDown:function(b){switch(b.which){case $.ui.keyCode.ESCAPE:this.close();break;case $.ui.keyCode.LEFT:this._previousImage();break;case $.ui.keyCode.RIGHT:this._nextImage();break;case $.ui.keyCode.UP:if(!this._container.hasClass("maximized")){this._toggleView()}break;case $.ui.keyCode.DOWN:if(this._container.hasClass("maximized")){this._toggleView()}break;case $.ui.keyCode.ENTER:var a=this._ui.header.find("> div > h1 > a");if(a.length==1){window.location=a.prop("href")}else{this._ui.slideshow.full.trigger("click")}break;case 80:this._ui.slideshow.toggle.trigger("click");break;default:return true;break}return false},_render:function(c,a){this._container.addClass("open");var b=null;if(c){b=this._ui.imageList.children("li:eq(0)");this._thumbnailMarginRight=parseInt(b.css("marginRight").replace(/px$/,""))||0;this._thumbnailWidth=b.outerWidth(true);this._thumbnailContainerWidth=this._ui.imageList.parent().innerWidth();if(this._items>1&&this.options.enableSlideshow&&!a){this.startSlideshow()}}if(a){this._ui.imageList.children("li").each($.proxy(function(e,f){var d=$(f);if(d.data("objectID")==a){d.trigger("click");this.moveToImage(d.data("index"));return false}},this))}else{if(b!==null){b.trigger("click")}}this._toggleButtons();this._preload()},_preload:function(){if(this._images.length<this._items){var a=this._images.length*this._thumbnailWidth;if(a-this._thumbnailOffset<this._thumbnailContainerWidth){this._loadNextImages(false)}}},_showImage:function(a){this.showImage($(a.currentTarget).data("index"),true)},showImage:function(e,c){if(this._active==e){return false}this.stopSlideshow(c||false);if(this._active!=-1){this._images[this._active].listItem.removeClass("active")}this._active=e;var b=this._images[e];this._ui.imageList.children("li").removeClass("active");b.listItem.addClass("active");var g=this._ui.imageContainer.getDimensions("inner");var i=(this._activeImage?0:1);if(this._activeImage!==null){this._ui.images[this._activeImage].removeClass("active")}this._activeImage=i;var a=this._active;this._ui.imageContainer.addClass("loading");this._ui.images[i].off("load").prop("src",false).on("load",$.proxy(function(){this._imageOnLoad(a,i)},this));this._renderImage(i,b,g);var f=this._ui.header.find("> div > a").prop("href",b.user.link).prop("title",b.user.username);f.children("img").prop("src",b.user.avatarURL);var h=WCF.String.escapeHTML(b.image.title);if(b.image.link){h='<a href="'+b.image.link+'">'+b.image.title+"</a>"}this._ui.header.find("> div > h1").html(h);var d=(b.series&&b.series.title?WCF.String.escapeHTML(b.series.title):"");if(b.series.link){d='<a href="'+b.series.link+'">'+d+"</a>"}this._ui.header.find("> div > h2").html(d);this._ui.header.find("> div > h3").text(WCF.Language.get("wcf.imageViewer.seriesIndex").replace(/{x}/,b.listItem.data("index")+1).replace(/{y}/,this._items));this._ui.slideshow.full.data("link",(b.image.fullURL?b.image.fullURL:b.image.url));this.moveToImage(b.listItem.data("index"));this._toggleButtons();return true},_imageOnLoad:function(b,a){if(b!=this._active){return}this._ui.imageContainer.removeClass("loading");this._ui.images[a].addClass("active");this.startSlideshow()},_renderImage:function(c,g,e){if(!g){c=this._activeImage;g=this._images[this._active];e={height:$(window).height()-(this._container.hasClass("maximized")?0:200),width:this._ui.imageContainer.innerWidth()}}e.height-=22;e.width-=20;this._ui.images[c].prop("src",g.image.url);var d=g.image.height;var b=g.image.width;var a=0;if(d>e.height){a=e.height/d;d=e.height;b=Math.floor(b*a)}if(b>e.width){a=e.width/b;b=e.width;d=Math.floor(d*a)}var f=Math.floor((e.width-b)/2);this._ui.images[c].css({height:d+"px",left:(f+10)+"px",marginTop:(Math.round(d/2)*-1)+"px",width:b+"px"})},_initUI:function(){if(this._didInit){return false}this._didInit=true;this._container=$('<div class="wcfImageViewer" />').appendTo(document.body);var c=$('<div><img class="active" /><img /></div>').appendTo(this._container);var g=$('<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 e=$("<ul />").appendTo(c);var d=$('<li class="wcfImageViewerSlideshowButtonPrevious"><span class="icon icon48 icon-angle-left" /></li>').appendTo(e);var f=$('<li class="wcfImageViewerSlideshowButtonToggle pointer"><span class="icon icon48 icon-play" /></li>').appendTo(e);var b=$('<li class="wcfImageViewerSlideshowButtonNext"><span class="icon icon48 icon-angle-right" /></li>').appendTo(e);var h=$('<li class="wcfImageViewerSlideshowButtonEnlarge pointer jsTooltip" title="'+WCF.Language.get("wcf.imageViewer.button.enlarge")+'"><span class="icon icon48 icon-resize-full" /></li>').appendTo(e);var a=$('<li class="wcfImageViewerSlideshowButtonFull pointer jsTooltip" title="'+WCF.Language.get("wcf.imageViewer.button.full")+'"><span class="icon icon48 icon-external-link" /></li>').appendTo(e);this._ui={buttonNext:g.children("span.wcfImageViewerButtonNext"),buttonPrevious:g.children("span.wcfImageViewerButtonPrevious"),header:$('<header><div class="box64"><a class="framed jsTooltip"><img /></a><h1 /><h2 /><h3 /></div></header>').appendTo(this._container),imageContainer:c,images:[c.children("img:eq(0)").on("webkitTransitionEnd transitionend msTransitionEnd oTransitionEnd",function(){$(this).removeClass("animateTransformation")}),c.children("img:eq(1)").on("webkitTransitionEnd transitionend msTransitionEnd oTransitionEnd",function(){$(this).removeClass("animateTransformation")})],imageList:g.find("> div > ul"),slideshow:{container:e,enlarge:h,full:a,next:b,previous:d,toggle:f}};this._ui.buttonNext.click($.proxy(this._next,this));this._ui.buttonPrevious.click($.proxy(this._previous,this));b.click($.proxy(this._nextImage,this));d.click($.proxy(this._previousImage,this));h.click($.proxy(this._toggleView,this));f.click($.proxy(function(){if(this._slideshowEnabled){this.stopSlideshow(true)}else{this._disableSlideshow=false;this.startSlideshow()}},this));a.click(function(i){window.location=$(i.currentTarget).data("link")});$('<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},_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)},_next:function(c,b){if(this._ui.buttonNext.hasClass("pointer")){if(b==undefined){this.stopSlideshow(true)}var a=Math.max((this._items*this._thumbnailWidth)-this._thumbnailContainerWidth-this._thumbnailMarginRight,0);this._thumbnailOffset=Math.min(this._thumbnailOffset+(this._thumbnailWidth*(b?b:this.options.shiftBy)),a);this._ui.imageList.css("marginLeft",(this._thumbnailOffset*-1))}this._preload();this._toggleButtons()},_previous:function(b,a){if(this._ui.buttonPrevious.hasClass("pointer")){if(a==undefined){this.stopSlideshow(true)}this._thumbnailOffset=Math.max(this._thumbnailOffset-(this._thumbnailWidth*(a?a:this.options.shiftBy)),0);this._ui.imageList.css("marginLeft",(this._thumbnailOffset*-1))}this._toggleButtons()},_nextImage:function(a){if(this._ui.slideshow.next.hasClass("pointer")){this._disableSlideshow=true;this.stopSlideshow(true);this.showImage(this._active+1)}},_previousImage:function(a){if(this._ui.slideshow.previous.hasClass("pointer")){this._disableSlideshow=true;this.stopSlideshow(true);this.showImage(this._active-1)}},moveToImage:function(b){var f=(b-3)*this._thumbnailWidth;var d=f+(this._thumbnailWidth*5);var e=this._thumbnailOffset;var a=this._thumbnailOffset+this._thumbnailContainerWidth;var g=false;if(f<e||d>a){g=true}if(g){var c=0;if(f<e){while(f<e){c++;e-=this._thumbnailWidth}this._previous(null,c)}else{while(d>a){c++;a+=this._thumbnailWidth}this._next(null,c)}}},_toggleButtons:function(){if(this._thumbnailOffset>0){this._ui.buttonPrevious.addClass("pointer")}else{this._ui.buttonPrevious.removeClass("pointer")}var a=(this._images.length*this._thumbnailWidth)-this._thumbnailContainerWidth-this._thumbnailMarginRight;if(this._thumbnailOffset>=a){this._ui.buttonNext.removeClass("pointer")}else{this._ui.buttonNext.addClass("pointer")}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")}},_createThumbnails:function(a){for(var f=0,e=a.length;f<e;f++){var d=a[f];var c=$('<li class="loading pointer"><img src="'+d.thumbnail.url+'" /></li>').appendTo(this._ui.imageList);c.data("index",this._images.length).data("objectID",d.objectID).click($.proxy(this._showImage,this));var b=c.children("img");if(b.get(0).complete){c.removeClass("loading")}else{b.on("load",function(){$(this).parent().removeClass("loading")})}d.listItem=c;this._images.push(d)}},_loadNextImages:function(a){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:(a&&this.element.data("targetImageID")?this.element.data("targetImageID"):0)}});this._proxy.setOption("showLoadingOverlay",false);this._proxy.sendRequest()},_success:function(c,e,b){if(c.returnValues.items){this._items=c.returnValues.items}var a=this._initUI();this._createThumbnails(c.returnValues.images);var d=(c.returnValues.targetImageID?c.returnValues.targetImageID:0);this._render(a,d)}});WCF.Label={};WCF.Label.ACPList=Class.extend({_labelInput:null,_labelList:[],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(b,a){var c=$(a);if(c.prop("value")!=="custom"){this._labelList.push($(c.next("span")))}},this))},_keyPressed:function(){var a=this._labelInput.prop("value");if(a===""){a=WCF.Language.get("wcf.acp.label.defaultValue")}for(var c=0,b=this._labelList.length;c<b;c++){this._labelList[c].text(a)}}});WCF.Label.ACPList.Connect=Class.extend({init:function(){var a=$("#connect .structuredList li");if(!a.length){return}a.each($.proxy(function(b,c){$(c).find('input[type="checkbox"]').click($.proxy(this._click,this))},this))},_click:function(c){var a=$(c.currentTarget);if(a.is(":checked")){a=a.parents("li");var b=a.data("depth");while(true){a=a.next();if(!a.length){return true}if(a.data("depth")<=b){return true}a.find('input[type="checkbox"]').prop("checked","checked")}}}});WCF.Label.Chooser=Class.extend({_container:null,_groups:{},_showWithoutSelection:false,init:function(f,b,e,h){this._container=null;this._groups={};this._showWithoutSelection=(h===true);this._initContainers(b);if($.getLength(f)){for(var a in f){var d=this._groups[a];if(d){WCF.Dropdown.getDropdownMenu(d.wcfIdentify()).find("> ul > li:not(.dropdownDivider)").each($.proxy(function(k,j){var i=$(j);var l=i.data("labelID")||0;if(l&&f[a]==l){this._selectLabel(i,true)}},this))}}}for(var c in this._containers){var g=this._containers[c];if(g.data("labelID")===undefined){g.data("labelID",0)}}this._container=$(b);if(e){$(e).click($.proxy(this._submit,this))}else{if(this._container.is("form")){this._container.submit($.proxy(this._submit,this))}}},_initContainers:function(a){$(a).find(".labelChooser").each($.proxy(function(d,i){var f=$(i);var b=f.data("groupID");if(!this._groups[b]){var e=f.wcfIdentify();var c=WCF.Dropdown.getDropdownMenu(e);if(c===null){WCF.Dropdown.initDropdown(f.find(".dropdownToggle"));c=WCF.Dropdown.getDropdownMenu(e)}var h=c;if(c.getTagName()=="div"&&c.children(".scrollableDropdownMenu").length){h=$("<ul />").appendTo(c);c=c.children(".scrollableDropdownMenu")}this._groups[b]=f;c.children("li").data("groupID",b).click($.proxy(this._click,this));if(!f.data("forceSelection")||this._showWithoutSelection){$('<li class="dropdownDivider" />').appendTo(h)}if(this._showWithoutSelection){$('<li data-label-id="-1"><span><span class="badge label">'+WCF.Language.get("wcf.label.withoutSelection")+"</span></span></li>").data("groupID",b).appendTo(h).click($.proxy(this._click,this))}if(!f.data("forceSelection")){var g=$('<li data-label-id="0"><span><span class="badge label">'+WCF.Language.get("wcf.label.none")+"</span></span></li>").data("groupID",b).appendTo(h);g.click($.proxy(this._click,this))}}},this))},_click:function(a){this._selectLabel($(a.currentTarget),false)},_selectLabel:function(a,c){var b=this._groups[a.data("groupID")];if(c&&b.data("labelID")!==undefined){return}if(a.data("labelID")){b.data("labelID",a.data("labelID"))}else{b.data("labelID",0)}a=a.find("span > span");b.find(".dropdownToggle > span").removeClass().addClass(a.attr("class")).text(a.text())},_submit:function(){var b=this._container.find(".formSubmit");b.find('input[type="hidden"]').each(function(e,d){var f=$(d);if(f.attr("name").indexOf("labelIDs[")===0){f.remove()}});for(var a in this._groups){var c=this._groups[a];if(c.data("labelID")){$('<input type="hidden" name="labelIDs['+a+']" value="'+c.data("labelID")+'" />').appendTo(b)}}}});WCF.Location={};WCF.Location.Util={getLocation:function(b,a){if(navigator.geolocation){navigator.geolocation.getCurrentPosition(function(c){b(c.coords.latitude,c.coords.longitude)},function(){b(undefined,undefined)},{timeout:a||5000})}else{b(undefined,undefined)}}};WCF.Location.GoogleMaps={};WCF.Location.GoogleMaps.Settings={_settings:{},get:function(a){if(a===undefined){return this._settings}if(this._settings[a]!==undefined){return this._settings[a]}return null},set:function(b,c){if($.isPlainObject(b)){for(var a in b){this._settings[a]=b[a]}}else{this._settings[b]=c}}};WCF.Location.GoogleMaps.Map=Class.extend({_map:null,_markers:[],init:function(b,a){this._mapContainer=$("#"+b);this._mapOptions=$.extend(true,this._getDefaultMapOptions(),a);this._map=new google.maps.Map(this._mapContainer[0],this._mapOptions);this._markers=[];if(this._mapContainer.parents(".sidebar").length){enquire.register("screen and (max-width: 800px)",{setup:$.proxy(this._addSidebarMapListener,this),deferSetup:true})}this.refresh()},_addSidebarMapListener:function(){$(".content > .mobileSidebarToggleButton").click($.proxy(this.refresh,this))},_getDefaultMapOptions:function(){var a={};a.center=new google.maps.LatLng(WCF.Location.GoogleMaps.Settings.get("defaultLatitude"),WCF.Location.GoogleMaps.Settings.get("defaultLongitude"));a.disableDoubleClickZoom=WCF.Location.GoogleMaps.Settings.get("disableDoubleClickZoom");a.draggable=WCF.Location.GoogleMaps.Settings.get("draggable");switch(WCF.Location.GoogleMaps.Settings.get("mapType")){case"map":a.mapTypeId=google.maps.MapTypeId.ROADMAP;break;case"satellite":a.mapTypeId=google.maps.MapTypeId.SATELLITE;break;case"physical":a.mapTypeId=google.maps.MapTypeId.TERRAIN;break;case"hybrid":default:a.mapTypeId=google.maps.MapTypeId.HYBRID;break}a.mapTypeControl=WCF.Location.GoogleMaps.Settings.get("mapTypeControl")!="off";if(a.mapTypeControl){switch(WCF.Location.GoogleMaps.Settings.get("mapTypeControl")){case"dropdown":a.mapTypeControlOptions={style:google.maps.MapTypeControlStyle.DROPDOWN_MENU};break;case"horizontalBar":a.mapTypeControlOptions={style:google.maps.MapTypeControlStyle.HORIZONTAL_BAR};break;default:a.mapTypeControlOptions={style:google.maps.MapTypeControlStyle.DEFAULT};break}}a.scaleControl=WCF.Location.GoogleMaps.Settings.get("scaleControl");a.scrollwheel=WCF.Location.GoogleMaps.Settings.get("scrollwheel");a.zoom=WCF.Location.GoogleMaps.Settings.get("zoom");return a},addDraggableMarker:function(c,b){var a=new google.maps.Marker({clickable:false,draggable:true,map:this._map,position:new google.maps.LatLng(c,b),zIndex:1});this._markers.push(a);return a},addMarker:function(g,e,f,d,c){var b=new google.maps.Marker({map:this._map,position:new google.maps.LatLng(g,e),title:f});if(d){b.setIcon(d)}if(c){var a=new google.maps.InfoWindow({content:c});google.maps.event.addListener(b,"click",$.proxy(function(){a.open(this._map,b)},this));b.infoWindow=a}this._markers.push(b);return b},getMarkers:function(){return this._markers},getMap:function(){return this._map},refresh:function(){var a=this._map.getCenter();google.maps.event.trigger(this._map,"resize");this._map.setCenter(a)},refreshBounds:function(){var f=null;var c=null;var d=null;var g=null;for(var a in this._markers){var e=this._markers[a];var h=e.getPosition().lat();var b=e.getPosition().lng();if(f===null){f=c=h;d=g=b}else{if(f>h){f=h}else{if(c<h){c=h}}if(d>h){d=h}else{if(g<b){g=b}}}}this._map.fitBounds(new google.maps.LatLngBounds(new google.maps.LatLng(f,d),new google.maps.LatLng(c,g)))},removeMarkers:function(){for(var a in this._markers){this._markers[a].setMap(null)}this._markers=[]},setCenter:function(b,a){this._map.setCenter(new google.maps.LatLng(b,a))}});WCF.Location.GoogleMaps.LargeMap=WCF.Location.GoogleMaps.Map.extend({_actionClassName:null,_locationSearch:null,_locationSearchInputSelector:null,_markerClusterer:null,_objectIDs:[],_previousNorthEast:null,_previousSouthWest:null,init:function(d,a,c,b){this._super(d,a);this._actionClassName=c;this._locationSearchInputSelector=b||"";this._objectIDs=[];if(this._locationSearchInputSelector){this._locationSearch=new WCF.Location.GoogleMaps.LocationSearch(b,$.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))},_centerMap:function(a){this.setCenter(a.location.lat(),a.location.lng());$(this._locationSearchInputSelector).val(a.label)},_loadMarkers:function(){var a=this._map.getBounds().getNorthEast();var b=this._map.getBounds().getSouthWest();if(this._previousNorthEast&&this._previousNorthEast.lat()>=a.lat()&&this._previousNorthEast.lng()>=a.lng()&&this._previousSouthWest.lat()<=b.lat()&&this._previousSouthWest.lng()<=b.lng()){return}this._previousNorthEast=a;this._previousSouthWest=b;this._proxy.setOption("data",{actionName:"getMapMarkers",className:this._actionClassName,parameters:{excludedObjectIDs:this._objectIDs,eastLongitude:a.lng(),northLatitude:a.lat(),southLatitude:b.lat(),westLongitude:b.lng()}});this._proxy.sendRequest()},_success:function(d,e,c){if(d.returnValues&&d.returnValues.markers){for(var a in d.returnValues.markers){var b=d.returnValues.markers[a];this.addMarker(b.latitude,b.longitude,b.title,null,b.infoWindow);if(b.objectID){this._objectIDs.push(b.objectID)}else{if(b.objectIDs){this._objectIDs=this._objectIDs.concat(b.objectIDs)}}}}},addMarker:function(f,d,e,c,b){var a=this._super(f,d,e,c,b);this._markerClusterer.addMarker(a);return a}});WCF.Location.GoogleMaps.LocationSearch=WCF.Search.Base.extend({_geocoder:null,init:function(b,e,a,c,d){this._super(b,e,a,c,d);this._geocoder=new google.maps.Geocoder()},_createListItem:function(b){var a=$("<li><span>"+WCF.String.escapeHTML(b.formatted_address)+"</span></li>").appendTo(this._list);a.data("location",b.geometry.location).data("label",b.formatted_address).click($.proxy(this._executeCallback,this));this._itemCount++;return a},_keyUp:function(b){switch(b.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(b);break}var a=this._getSearchString(b);if(a===""){this._clearList(true)}else{if(a.length>=this._triggerLength){this._clearList(false);this._geocoder.geocode({address:a},$.proxy(this._success,this))}else{this._clearList(false)}}},_success:function(d,b){if(b!=google.maps.GeocoderStatus.OK){return}if($.getLength(d)){var c=0;for(var a in d){this._createListItem(d[a]);if(++c==10){break}}}else{if(!this._handleEmptyResult()){return}}WCF.CloseOverlayHandler.addCallback("WCF.Search.Base",$.proxy(function(){this._clearList()},this));var e=this._searchInput.parents(".dropdown").wcfIdentify();if(!WCF.Dropdown.getDropdownMenu(e).hasClass("dropdownOpen")){WCF.Dropdown.toggleDropdown(e)}this._itemIndex=-1;if(!WCF.Dropdown.getDropdown(e).data("disableAutoFocus")){this._selectNextItem()}}});WCF.Location.GoogleMaps.LocationInput=Class.extend({_locationSearch:null,_map:null,_marker:null,init:function(d,b,a,e,c){this._searchInput=a;this._map=new WCF.Location.GoogleMaps.Map(d,b);this._locationSearch=new WCF.Location.GoogleMaps.LocationSearch(a,$.proxy(this._setMarkerByLocation,this));if(e&&c){this._marker=this._map.addDraggableMarker(e,c)}else{this._marker=this._map.addDraggableMarker(0,0);WCF.Location.Util.getLocation($.proxy(function(g,f){if(g!==undefined&&f!==undefined){WCF.Location.GoogleMaps.Util.moveMarker(this._marker,g,f);WCF.Location.GoogleMaps.Util.focusMarker(this._marker)}},this))}this._marker.addListener("dragend",$.proxy(this._updateLocation,this))},getMap:function(){return this._map},getMarker:function(){return this._marker},_updateLocation:function(){WCF.Location.GoogleMaps.Util.reverseGeocoding($.proxy(function(a){if(a!==null){$(this._searchInput).val(a)}},this),this._marker)},_setMarkerByLocation:function(a){this._marker.setPosition(a.location);WCF.Location.GoogleMaps.Util.focusMarker(this._marker);$(this._searchInput).val(a.label)}});WCF.Location.GoogleMaps.Util={_geocoder:null,focusMarker:function(a){a.getMap().setCenter(a.getPosition())},getMarkerPosition:function(a){return{latitude:a.getPosition().lat(),longitude:a.getPosition().lng()}},moveMarker:function(a,d,b,c){a.setPosition(new google.maps.LatLng(d,b));if(c){google.maps.event.trigger(a,"dragend")}},reverseGeocoding:function(f,a,e,c,b){if(a){e=a.getPosition().lat();c=a.getPosition().lng()}if(this._geocoder===null){this._geocoder=new google.maps.Geocoder()}var d=new google.maps.LatLng(e,c);this._geocoder.geocode({latLng:d},function(h,g){if(g==google.maps.GeocoderStatus.OK){f((b?h:h[0].formatted_address))}else{f(null)}})}};WCF.Message={};WCF.Message.BBCode={};WCF.Message.BBCode.CodeViewer=Class.extend({_dialog:null,init:function(){this._dialog=null;this._initCodeBoxes();WCF.DOMNodeInsertedHandler.addCallback("WCF.Message.BBCode.CodeViewer",$.proxy(this._initCodeBoxes,this));WCF.DOMNodeInsertedHandler.execute()},_initCodeBoxes:function(){$(".codeBox:not(.jsCodeViewer)").each($.proxy(function(a,c){var b=$(c).addClass("jsCodeViewer");$('<span class="icon icon16 icon-copy pointer jsTooltip" title="'+WCF.Language.get("wcf.message.bbcode.code.copy")+'" />').appendTo(b.find("div > h3")).click($.proxy(this._click,this))},this))},_click:function(b){var a="";$(b.currentTarget).parents("div").next("ol").children("li").each(function(c,d){if(a){a+="\n"}a+=$(d).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(a);this._dialog.wcfDialog({title:WCF.Language.get("wcf.message.bbcode.code.copy")})}else{this._dialog.children("textarea").val(a);this._dialog.wcfDialog("open")}this._dialog.children("textarea").select()}});WCF.Message.FormGuard=Class.extend({init:function(){var a=$("form.jsFormGuard").removeClass("jsFormGuard").submit(function(){$(this).find(".formSubmit input[type=submit]").disable()});$(window).unload(function(){a.find(".formSubmit input[type=submit]").enable()})}});WCF.Message.Preview=Class.extend({_className:"",_messageFieldID:"",_messageField:null,_proxy:null,_previewButton:null,_previewButtonLabel:"",init:function(b,a,c){this._className=b;this._messageFieldID=$.wcfEscapeID(a);this._messageField=$("#"+this._messageFieldID);if(!this._messageField.length){console.debug("[WCF.Message.Preview] Unable to find message field identified by '"+this._messageFieldID+"'");return}c=$.wcfEscapeID(c);this._previewButton=$("#"+c);if(!this._previewButton.length){console.debug("[WCF.Message.Preview] Unable to find preview button identified by '"+c+"'");return}this._previewButton.click($.proxy(this._click,this));this._proxy=new WCF.Action.Proxy({failure:$.proxy(this._failure,this),success:$.proxy(this._success,this)})},_click:function(b){var a=this._getMessage();if(a===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(a)});this._proxy.sendRequest();this._previewButtonLabel=this._previewButton.html();this._previewButton.html(WCF.Language.get("wcf.global.loading")).disable();b.stopPropagation();return false},_getParameters:function(b){var a={};$("#settings").find("input[type=checkbox]").each(function(c,e){var d=$(e);if(d.is(":checked")){a[d.prop("name")]=d.prop("value")}});return{data:{message:b},options:a}},_getMessage:function(){if(!$.browser.redactor){return this._messageField.val()}else{if(this._messageField.data("redactor")){return this._messageField.redactor("getText")}}return null},_success:function(b,c,a){this._previewButton.html(this._previewButtonLabel).enable();this._messageField.parent().children("small.innerError").remove();this._handleResponse(b)},_handleResponse:function(a){},_failure:function(a){if(a===null||a.returnValues===undefined||a.returnValues.errorType===undefined){return true}this._previewButton.html(this._previewButtonLabel).enable();var b=this._messageField.next("small.innerError").empty();if(!b.length){b=$('<small class="innerError" />').appendTo(this._messageField.parent())}b.html(a.returnValues.errorType);return false}});WCF.Message.DefaultPreview=WCF.Message.Preview.extend({_attachmentObjectType:null,_attachmentObjectID:null,_tmpHash:null,init:function(b,a,c){this._super("wcf\\data\\bbcode\\MessagePreviewAction","text","previewButton");this._attachmentObjectType=b||null;this._attachmentObjectID=a||null;this._tmpHash=c||null},_handleResponse:function(b){var a=$("#previewContainer");if(!a.length){a=$('<div class="container containerPadding marginTop" id="previewContainer"><fieldset><legend>'+WCF.Language.get("wcf.global.preview")+"</legend><div></div></fieldset>").prependTo($("#messageContainer")).wcfFadeIn()}a.find("div:eq(0)").html(b.returnValues.message);new WCF.Effect.Scroll().scrollTo(a)},_getParameters:function(b){var a=this._super(b);if(this._attachmentObjectType!=null){a.attachmentObjectType=this._attachmentObjectType;a.attachmentObjectID=this._attachmentObjectID;a.tmpHash=this._tmpHash}return a}});WCF.Message.Multilingualism=Class.extend({_availableLanguages:{},_languageID:0,_languageInput:null,init:function(c,d,a){this._availableLanguages=d;this._languageID=c||0;this._languageInput=$("#languageID");this._updateLabel();this._languageInput.find(".dropdownMenu > li").click($.proxy(this._click,this));if(!a){var b=this._languageInput.find(".dropdownMenu");$('<li class="dropdownDivider" />').appendTo(b);$('<li><span><span class="badge">'+this._availableLanguages[0]+"</span></span></li>").click($.proxy(this._disable,this)).appendTo(b)}this._languageInput.parents("form").submit($.proxy(this._submit,this))},_click:function(a){this._languageID=$(a.currentTarget).data("languageID");this._updateLabel()},_disable:function(){this._languageID=0;this._updateLabel()},_updateLabel:function(){this._languageInput.find(".dropdownToggle > span").text(this._availableLanguages[this._languageID])},_submit:function(){this._languageInput.next("input[name=languageID]").prop("value",this._languageID)}});WCF.Message.SmileyCategories=Class.extend({_cache:[],_proxy:null,init:function(){this._cache=[];this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});$("#smilies").on("wcftabsbeforeactivate",$.proxy(this._click,this));var a=this;new WCF.PeriodicalExecuter(function(b){b.stop();a._click({},{newTab:$("#smilies > .menu li.ui-state-active")})},100)},_click:function(b,c){var a=parseInt($(c.newTab).children("a").data("smileyCategoryID"));if(a&&!WCF.inArray(a,this._cache)){this._proxy.setOption("data",{actionName:"getSmilies",className:"wcf\\data\\smiley\\category\\SmileyCategoryAction",objectIDs:[a]});this._proxy.sendRequest()}},_success:function(c,d,b){var a=parseInt(c.returnValues.smileyCategoryID);this._cache.push(a);$("#smilies-"+a).html(c.returnValues.template)}});WCF.Message.Smilies=Class.extend({_redactor:null,_wysiwygSelector:"",init:function(a){this._wysiwygSelector=a;WCF.System.Dependency.Manager.register("Redactor_"+this._wysiwygSelector,$.proxy(function(){this._redactor=$("#"+this._wysiwygSelector).redactor("getObject");$(document).on("click",".jsSmiley",$.proxy(this._smileyClick,this))},this))},_smileyClick:function(d){var b=$(d.currentTarget);var a=b.data("smileyCode");var c=b.data("smileyPath");this._redactor.insertSmiley(a,c,true)}});WCF.Message.QuickReply=Class.extend({_container:null,_messageField:null,_notification:null,_pendingSave:false,_proxy:null,_quoteManager:null,_scrollHandler:null,_successMessageNonVisible:"",init:function(c,b){this._container=$("#messageQuickReply");this._container.children(".message").addClass("jsInvalidQuoteTarget");this._messageField=$("#text");this._pendingSave=false;if(!this._container||!this._messageField){return}var a=this._container.find(".formSubmit");a.find("button[data-type=save]").click($.proxy(this._save,this));if(c){a.find("button[data-type=extended]").click($.proxy(this._prepareExtended,this))}a.find("button[data-type=cancel]").click($.proxy(this._cancel,this));if(b){this._quoteManager=b}$(".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=""},click:function(b){this._container.toggle();if(this._container.is(":visible")){this._scroll.scrollTo(this._container,true);WCF.Message.Submit.registerButton("text",this._container.find(".formSubmit button[data-type=save]"));if(this._quoteManager){var a=true;if($.browser.redactor){if(this._messageField.data("redactor")){a=(!$.trim(this._messageField.redactor("getText")));this._editorCallback(a)}}else{a=(!this._messageField.val().length);this._editorCallback(a)}}}if(b!==null){b.stopPropagation();return false}},_editorCallback:function(a){if(a){this._quoteManager.insertQuotes(this._getClassName(),this._getObjectID(),$.proxy(this._insertQuotes,this))}if($.browser.redactor){this._messageField.redactor("focus")}else{this._messageField.focus()}},getContainer:function(){return this._container},_insertQuotes:function(a){if(!a.returnValues.template){return}if($.browser.redactor){this._messageField.redactor("insertDynamic",a.returnValues.template)}else{this._messageField.val(a.returnValues.template)}},_save:function(){if(this._pendingSave){return}var a="";if($.browser.redactor){a=this._messageField.redactor("getText")}else{a=$.trim(this._messageField.val())}var c=this._messageField.parent().find("small.innerError");if(a===""||a==="0"){if(!c.length){c=$('<small class="innerError" />').appendTo(this._messageField.parent())}c.html(WCF.Language.get("wcf.global.form.error.empty"));return}else{c.remove()}this._pendingSave=true;this._proxy.setOption("data",{actionName:"quickReply",className:this._getClassName(),interfaceName:"wcf\\data\\IMessageQuickReplyAction",parameters:this._getParameters(a)});this._proxy.sendRequest();var b=this._container.find(".messageQuickReplyContent .messageBody");$('<span class="icon icon48 icon-spinner" />').appendTo(b);b.children(".redactor_box").hide().end().next().hide()},_getParameters:function(b){var a={objectID:this._getObjectID(),data:{message:b},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")){a.anchor=this._container.data("anchor")}return a},_cancel:function(){this._revertQuickReply(true);if($.browser.redactor){this._messageField.redactor("reset")}else{this._messageField.val("")}},_revertQuickReply:function(b){var a=this._container.find(".messageQuickReplyContent .messageBody");if(b){this._container.hide();a.children("small.innerError").remove()}a.children(".icon-spinner").remove();a.children(".redactor_box").show();a.next().show()},_prepareExtended:function(){this._pendingSave=true;if(this._quoteManager!==null){this._quoteManager.markQuotesForRemoval()}var a="";if($.browser.redactor){a=this._messageField.redactor("getText")}else{a=this._messageField.val()}new WCF.Action.Proxy({autoSend:true,data:{actionName:"jumpToExtended",className:this._getClassName(),interfaceName:"wcf\\data\\IExtendedMessageQuickReplyAction",parameters:{containerID:this._getObjectID(),message:a}},success:function(c,d,b){window.location=c.returnValues.url}})},_success:function(c,d,b){if($.browser.redactor){this._messageField.redactor("autosavePurge")}if(c.returnValues.url){window.location=c.returnValues.url}else{if(c.returnValues.template){var a=$(""+c.returnValues.template);if(this._container.data("sortOrder")=="DESC"){a.insertAfter(this._container)}else{a.insertBefore(this._container)}this._container.data("lastPostTime",c.returnValues.lastPostTime);this._notification.show(undefined,undefined,WCF.Language.get("wcf.global.success.add"));this._updateHistory(a.wcfIdentify())}else{var a=(this._successMessageNonVisible)?this._successMessageNonVisible:"wcf.global.success.add";this._notification.show(undefined,5000,WCF.Language.get(a))}if($.browser.redactor){this._messageField.redactor("reset")}else{this._messageField.val("")}this._revertQuickReply(true);if(this._quoteManager!==null){this._quoteManager.countQuotes()}this._pendingSave=false}},_failure:function(b){this._pendingSave=false;this._revertQuickReply(false);if(b===null||b.returnValues===undefined||b.returnValues.errorType===undefined){return true}var a=this._container.find(".messageQuickReplyContent .messageBody");var c=a.children("small.innerError").empty();if(!c.length){c=$('<small class="innerError" />').appendTo(a)}c.html(b.returnValues.errorType);return false},_getClassName:function(){return""},_getObjectID:function(){return 0},_updateHistory:function(a){window.location.hash=a}});WCF.Message.InlineEditor=Class.extend({_activeElementID:"",_cache:"",_container:{},_containerID:0,_dropdowns:{},_messageContainerSelector:".jsMessage",_messageEditorIDPrefix:"messageEditor",_notification:null,_proxy:null,_quoteManager:null,_supportExtendedForm:false,init:function(a,c,b){this._activeElementID="";this._cache="";this._container={};this._containerID=parseInt(a);this._dropdowns={};this._quoteManager=b||null;this._supportExtendedForm=(c)?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))},initContainers:function(){$(this._messageContainerSelector).each($.proxy(function(b,a){var e=$(a);var c=e.wcfIdentify();if(!this._container[c]){this._container[c]=e;if(e.data("canEditInline")){var d=e.find(".jsMessageEditButton:eq(0)").data("containerID",c).click($.proxy(this._clickInline,this));if(e.data("canEdit")){d.dblclick($.proxy(this._click,this))}}else{if(e.data("canEdit")){e.find(".jsMessageEditButton:eq(0)").data("containerID",c).click($.proxy(this._click,this))}}}},this))},_click:function(c,a){var b=(c===null)?a:$(c.currentTarget).data("containerID");if(this._activeElementID===""){this._activeElementID=b;this._prepare();this._proxy.setOption("data",{actionName:"beginEdit",className:this._getClassName(),interfaceName:"wcf\\data\\IMessageInlineEditorAction",parameters:{containerID:this._containerID,objectID:this._container[b].data("objectID")}});this._proxy.setOption("failure",$.proxy(function(){this._cancel()},this));this._proxy.sendRequest()}else{var d=new WCF.System.Notification(WCF.Language.get("wcf.message.error.editorAlreadyInUse"),"warning");d.show()}if(this._dropdowns[this._container[b].data("objectID")]){this._dropdowns[this._container[b].data("objectID")].removeClass("dropdownOpen")}if(c!==null){c.stopPropagation();return false}},_clickInline:function(c){var d=$(c.currentTarget);if(!d.hasClass("dropdownToggle")){var b=d.data("containerID");d.addClass("dropdownToggle").parent().addClass("dropdown");var a=$('<ul class="dropdownMenu" />').insertAfter(d);this._initDropdownMenu(b,a);WCF.DOMNodeInsertedHandler.execute();this._dropdowns[this._container[b].data("objectID")]=a;WCF.Dropdown.registerCallback(d.parent().wcfIdentify(),$.proxy(this._toggleDropdown,this));d.trigger("click")}c.stopPropagation();return false},_failure:function(b){this._revertEditor();if(b===null||b.returnValues===undefined||b.returnValues.errorType===undefined){return true}var a=this._container[this._activeElementID].find(".messageBody .messageInlineEditor");var c=a.children("small.innerError").empty();if(!c.length){c=$('<small class="innerError" />').insertBefore(a.children(".formSubmit"))}c.html(b.returnValues.errorType);return false},_toggleDropdown:function(a,b){WCF.Dropdown.getDropdown(a).parents(".messageOptions").toggleClass("forceOpen")},_initDropdownMenu:function(a,b){},_prepare:function(){var b=this._container[this._activeElementID].find(".messageBody");$('<span class="icon icon48 icon-spinner" />').appendTo(b);var a=b.find(".messageText");a.parent().children(".jsInlineEditorHideContent").hide();b.children(".attachmentThumbnailList, .attachmentFileList").hide();this._cache=a.detach()},_cancel:function(){var c=this._container[this._activeElementID].removeClass("jsInvalidQuoteTarget");var a=$("#"+this._messageEditorIDPrefix+c.data("objectID"));a.redactor("autosavePurge");a.redactor("destroy");var b=c.find(".messageBody");b.children(".icon-spinner").remove();b.children("div:eq(0)").html(this._cache);b.children(".attachmentThumbnailList, .attachmentFileList").show();b.find(".jsInlineEditorHideContent").show();this._container[this._activeElementID].find(".messageOptions").removeClass("forceHidden");this._activeElementID="";if(this._quoteManager){this._quoteManager.clearAlternativeEditor()}},_success:function(b,c,a){switch(b.returnValues.actionName){case"beginEdit":this._showEditor(b);break;case"save":this._showMessage(b);break}},_showEditor:function(f){this._proxy.setOption("failure",$.proxy(this._failure,this));var d=this._container[this._activeElementID].addClass("jsInvalidQuoteTarget").find(".messageBody");d.children(".icon-spinner").remove();var c=d.children("div:eq(0)");$(""+f.returnValues.template).appendTo(c);var b=c.find(".formSubmit");var e=b.find("button[data-type=save]").click($.proxy(this._save,this));if(this._supportExtendedForm){b.find("button[data-type=extended]").click($.proxy(this._prepareExtended,this))}b.find("button[data-type=cancel]").click($.proxy(this._cancel,this));WCF.Message.Submit.registerButton(this._messageEditorIDPrefix+this._container[this._activeElementID].data("objectID"),e);this._container[this._activeElementID].find(".messageOptions").addClass("forceHidden");var a=$("#"+this._messageEditorIDPrefix+this._container[this._activeElementID].data("objectID"));if($.browser.redactor){new WCF.PeriodicalExecuter($.proxy(function(g){g.stop();if(this._quoteManager){this._quoteManager.setAlternativeEditor(a)}},this),250)}else{a.focus()}},_revertEditor:function(){var a=this._container[this._activeElementID].removeClass("jsInvalidQuoteTarget").find(".messageBody");a.children("span.icon-spinner").remove();a.children("div:eq(0)").children().show();a.children(".attachmentThumbnailList, .attachmentFileList").show();a.find(".jsInlineEditorHideContent").show();if(this._quoteManager){this._quoteManager.clearAlternativeEditor()}},_save:function(){var c=this._container[this._activeElementID];var b=c.data("objectID");var a="";if($.browser.redactor){a=$("#"+this._messageEditorIDPrefix+b).redactor("getText")}else{a=$("#"+this._messageEditorIDPrefix+b).val()}this._proxy.setOption("data",{actionName:"save",className:this._getClassName(),interfaceName:"wcf\\data\\IMessageInlineEditorAction",parameters:{containerID:this._containerID,data:{message:a},objectID:b}});this._proxy.sendRequest();this._hideEditor()},_prepareExtended:function(){var c=this._container[this._activeElementID];var b=c.data("objectID");var a="";if($.browser.redactor){a=$("#"+this._messageEditorIDPrefix+b).redactor("getText")}else{a=$("#"+this._messageEditorIDPrefix+b).val()}new WCF.Action.Proxy({autoSend:true,data:{actionName:"jumpToExtended",className:this._getClassName(),parameters:{containerID:this._containerID,message:a,messageID:b}},success:function(e,f,d){window.location=e.returnValues.url}})},_hideEditor:function(){var a=this._container[this._activeElementID].removeClass("jsInvalidQuoteTarget").find(".messageBody");$('<span class="icon icon48 icon-spinner" />').appendTo(a);a.children("div:eq(0)").children().hide();a.children(".attachmentThumbnailList, .attachmentFileList").show();a.find(".jsInlineEditorHideContent").show();if(this._quoteManager){this._quoteManager.clearAlternativeEditor()}},_showMessage:function(c){var d=this._container[this._activeElementID].removeClass("jsInvalidQuoteTarget");var b=d.find(".messageBody");b.children(".icon-spinner").remove();var a=b.children("div:eq(0)");a.parent().children(".jsInlineEditorHideContent").show();this._container[this._activeElementID].find(".messageOptions").removeClass("forceHidden");if($.browser.redactor){$("#"+this._messageEditorIDPrefix+d.data("objectID")).redactor("destroy")}a.empty();a.html('<div class="messageText">'+c.returnValues.message+"</div>");if(c.returnValues.attachmentList==undefined){b.children(".attachmentThumbnailList, .attachmentFileList").show()}else{b.children(".attachmentThumbnailList, .attachmentFileList").remove();if(c.returnValues.attachmentList){$(c.returnValues.attachmentList).insertAfter(b.children("div:eq(0)"))}}this._activeElementID="";this._updateHistory(this._getHash(d.data("objectID")));this._notification.show();if(this._quoteManager){this._quoteManager.clearAlternativeEditor()}},_getClassName:function(){return""},_getHash:function(a){return"#message"+a},_updateHistory:function(a){window.location.hash=a}});WCF.Message.Submit={_buttons:{},registerButton:function(b,a){if(!WCF.Browser.isChrome()){return}this._buttons[b]=$(a)},execute:function(a){if(!this._buttons[a]){return}this._buttons[a].trigger("click")}};WCF.Message.Quote={};WCF.Message.Quote.Handler=Class.extend({_activeContainerID:"",_className:"",_containers:{},_containerSelector:"",_copyQuote:null,_message:"",_messageBodySelector:"",_objectID:0,_objectType:"",_proxy:null,_quoteManager:null,init:function(e,d,b,a,c,f){this._className=d;if(this._className==""){console.debug("[WCF.Message.QuoteManager] Empty class name given, aborting.");return}this._objectType=b;if(this._objectType==""){console.debug("[WCF.Message.QuoteManager] Empty object type name given, aborting.");return}this._containerSelector=a;this._message="";this._messageBodySelector=c;this._messageContentSelector=f;this._objectID=0;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._initContainers();this._initCopyQuote();$(document).mouseup($.proxy(this._mouseUp,this));this._quoteManager=e;this._quoteManager.register(this._objectType,this);WCF.DOMNodeInsertedHandler.addCallback("WCF.Message.Quote.Handler"+b.hashCode(),$.proxy(this._initContainers,this))},_initContainers:function(){var a=this;$(this._containerSelector).each(function(c,b){var e=$(b);var d=e.wcfIdentify();if(!a._containers[d]){a._containers[d]=e;if(e.hasClass("jsInvalidQuoteTarget")){return true}if(a._messageBodySelector!==null){e=e.find(a._messageBodySelector).data("containerID",d)}e.mousedown($.proxy(a._mouseDown,a));a._containers[d].find(".jsQuoteMessage").click($.proxy(a._saveFullQuote,a))}})},_mouseDown:function(a){this._copyQuote.hide();var b=$(a.currentTarget);if(this._messageBodySelector){b=this._containers[b.data("containerID")]}if(b.hasClass("jsInvalidQuoteTarget")){this._activeContainerID="";return}this._activeContainerID=b.wcfIdentify();if($.browser.mozilla){b.find("img").each(function(){var c=$(this);c.data("__alt",c.attr("alt")).removeAttr("alt")})}},_getNodeText:function(d){var c="";for(var b=0;b<d.childNodes.length;b++){if(d.childNodes[b].nodeType==3){c+=d.childNodes[b].nodeValue}else{if(!d.childNodes[b].tagName){continue}var a=d.childNodes[b].tagName.toLowerCase();if(a==="li"){c+="\r\n"}else{if(a==="td"&&!$.browser.msie){c+="\r\n"}}c+=this._getNodeText(d.childNodes[b]);if(a==="ul"){c+="\n"}}}return c},_mouseUp:function(a){if(this._activeContainerID==""){this._copyQuote.hide();return}var i=this._containers[this._activeContainerID];var c=this._getSelectedText();var f=$.trim(c);if(f==""){this._copyQuote.hide();return}var d=null;if(this._messageBodySelector){d=this._getNodeText(i.find(this._messageContentSelector).get(0))}else{d=this._getNodeText(i.get(0))}if(this._normalize(d).indexOf(this._normalize(f))===-1){return}this._copyQuote.show();var g=this._getBoundingRectangle(i,c);var e=this._copyQuote.getDimensions("outer");var b=(g.right-g.left)/2-(e.width/2)+g.left;this._copyQuote.css({top:g.top-e.height-7+"px",left:b+"px"});this._copyQuote.hide();this._activeContainerID="";var h=this;new WCF.PeriodicalExecuter(function(j){j.stop();var k=$.trim(h._getSelectedText());if(k!=""){h._copyQuote.show();h._message=k;h._objectID=i.data("objectID");if($.browser.mozilla){i.find("img").each(function(){var l=$(this);l.attr("alt",l.data("__alt"))})}}},10)},_normalize:function(a){return a.replace(/\r?\n|\r/g,"\n").replace(/\s/g," ").replace(/\s{2,}/g," ")},_getOffset:function(c,d){c.collapse(d);var f=WCF.getRandomID();var a=document.createElement("span");a.innerHTML='<span id="'+f+'"></span>';var g=document.createDocumentFragment(),b;while(b=a.firstChild){g.appendChild(b)}c.insertNode(g);a=$("#"+f);var e=a.offset();e.top=e.top-$(window).scrollTop();a.remove();return e},_getBoundingRectangle:function(b,e){var d=null;if(document.createRange&&typeof document.createRange().getBoundingClientRect!="undefined"){if(e.rangeCount>0){var h=e.getRangeAt(0).getClientRects();var c=e.getRangeAt(0).getBoundingClientRect();var f=$(document);var a=f.scrollTop();d={left:c.left,right:c.right,top:c.top+a}}}else{if(document.selection&&document.selection.type!="Control"){var g=document.selection.createRange();d={left:g.boundingLeft,right:g.boundingRight,top:g.boundingTop}}}return d},_saveSelection:function(d){if(window.getSelection&&document.createRange){var c=window.getSelection().getRangeAt(0);var b=c.cloneRange();b.selectNodeContents(d);b.setEnd(c.startContainer,c.startOffset);var f=b.toString().length;return{start:f,end:f+c.toString().length}}else{var a=document.selection.createRange();var e=document.body.createTextRange();e.moveToElementText(d);e.setEndPoint("EndToStart",a);var f=e.text.length;return{start:f,end:f+a.text.length}}},_restoreSelection:function(j,k){if(window.getSelection&&document.createRange){var a=0,g=document.createRange();g.setStart(j,0);g.collapse(true);var f=[j],c,d=false,l=false;while(!l&&(c=f.pop())){if(c.nodeType==3){var h=a+c.length;if(!d&&k.start>=a&&k.start<=h){g.setStart(c,k.start-a);d=true}if(d&&k.end>=a&&k.end<=h){g.setEnd(c,k.end-a);l=true}a=h}else{var e=c.childNodes.length;while(e--){f.push(c.childNodes[e])}}}var b=window.getSelection();b.removeAllRanges();b.addRange(g)}else{var m=document.body.createTextRange();m.moveToElementText(j);m.collapse(true);m.moveEnd("character",k.end);m.moveStart("character",k.start);m.select()}},_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))}},_getSelectedText:function(){if(window.getSelection){return window.getSelection()}else{if(document.getSelection){return document.getSelection()}else{if(document.selection){return document.selection.createRange().text}}}return""},_saveFullQuote:function(b){var a=$(b.currentTarget);this._proxy.setOption("data",{actionName:"saveFullQuote",className:this._className,interfaceName:"wcf\\data\\IMessageQuoteAction",objectIDs:[a.data("objectID")]});this._proxy.sendRequest();if(a.data("isQuoted")){a.data("isQuoted",false).children("a").removeClass("active")}else{a.data("isQuoted",true).children("a").addClass("active")}b.stopPropagation();return false},_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()},_success:function(c,d,b){if(c.returnValues.count!==undefined){var a=(c.fullQuoteObjectIDs!==undefined)?c.fullQuoteObjectIDs:{};this._quoteManager.updateCount(c.returnValues.count,a)}},updateFullQuoteObjectIDs:function(b){for(var a in this._containers){this._containers[a].find(".jsQuoteMessage").each(function(c,d){var e=$(d).data("isQuoted",0);e.children("a").removeClass("active");if(WCF.inArray(e.data("objectID"),b)){e.data("isQuoted",1).children("a").addClass("active")}})}}});WCF.Message.Quote.Manager=Class.extend({_buttons:{},_count:0,_dialog:null,_editorElement:null,_editorElementAlternative:null,_form:null,_handlers:{},_hasTemplate:false,_insertQuotes:true,_proxy:null,_removeOnSubmit:[],_showQuotes:null,_supportPaste:false,init:function(c,a,b,d){this._buttons={insert:null,remove:null};this._count=parseInt(c)||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(a){this._editorElement=$("#"+a);if(this._editorElement.length){this._supportPaste=true;this._form=this._editorElement.parents("form:eq(0)");if(this._form.length){this._form.submit($.proxy(this._submit,this));this._removeOnSubmit=d||[]}else{this._form=null;this._supportPaste=(b===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()},setAlternativeEditor:function(a){this._editorElementAlternative=a},clearAlternativeEditor:function(){this._editorElementAlternative=null},register:function(a,b){this._handlers[a]=b},updateCount:function(c,b){this._count=parseInt(c)||0;this._toggleShowQuotes();for(var a in this._handlers){if(b[a]){this._handlers[a].updateFullQuoteObjectIDs(b[a])}}},insertQuotes:function(a,b,c){if(!this._insertQuotes){this._insertQuotes=true;return}new WCF.Action.Proxy({autoSend:true,data:{actionName:"getRenderedQuotes",className:a,interfaceName:"wcf\\data\\IMessageQuoteAction",parameters:{parentObjectID:b}},success:c})},_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 a=WCF.Language.get("wcf.message.quote.showQuotes").replace(/#count#/,this._count);this._showQuotes.text(a).show()}this._hasTemplate=false},_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()}},renderDialog:function(c){if(this._dialog===null){this._dialog=$("#messageQuoteList");if(!this._dialog.length){this._dialog=$('<div id="messageQuoteList" />').hide().appendTo(document.body)}}this._dialog.html(c);var a=$('<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(a)}this._buttons.remove=$("<button>"+WCF.Language.get("wcf.message.quote.removeAllQuotes")+"</button>").click($.proxy(this._removeSelected,this)).appendTo(a);this._dialog.wcfDialog({title:WCF.Language.get("wcf.message.quote.manageQuotes")});this._dialog.wcfDialog("render");this._hasTemplate=true;var d=this._dialog.find(".jsInsertQuote");if(this._supportPaste){d.click($.proxy(this._insertQuote,this))}else{d.hide()}this._dialog.find("input.jsCheckbox").change($.proxy(this._changeButtons,this));if(this._removeOnSubmit.length){var b=this;this._dialog.find("input.jsRemoveQuote").each(function(f,e){var g=$(e).change($.proxy(this._change,this));if(WCF.inArray(g.parent("li").attr("data-quote-id"),b._removeOnSubmit)){g.attr("checked","checked")}})}},_changeButtons:function(){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{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"))}},_change:function(c){var d=$(c.currentTarget);var b=d.parent("li").attr("data-quote-id");if(d.prop("checked")){this._removeOnSubmit.push(b)}else{for(var a in this._removeOnSubmit){if(this._removeOnSubmit[a]==b){delete this._removeOnSubmit[a];break}}}},_insertSelected:function(){if(this._editorElementAlternative===null){var a=$(".jsQuickReply:eq(0)").data("__api");if(a&&!a.getContainer().is(":visible")){this._insertQuotes=false;a.click(null)}}if(!this._dialog.find("input.jsCheckbox:checked").length){this._dialog.find("input.jsCheckbox").prop("checked","checked")}this._dialog.find("input.jsCheckbox:checked").each($.proxy(function(c,b){this._insertQuote(null,b)},this));this._dialog.wcfDialog("close")},_insertQuote:function(a,h){if(a!==null&&this._editorElementAlternative===null){var c=$(".jsQuickReply:eq(0)").data("__api");if(c&&!c.getContainer().is(":visible")){this._insertQuotes=false;c.click(null)}}var f=(a===null)?$(h).parents("li"):$(a.currentTarget).parents("li");var i=$.trim(f.children("div.jsFullQuote").text());var e=f.parents("article.message");i="[quote='"+e.attr("data-username")+"','"+e.data("link")+"']"+i+"[/quote]";if($.browser.redactor){if(this._editorElementAlternative===null){this._editorElement.redactor("insertDynamic",i)}else{this._editorElementAlternative.redactor("insertDynamic",i)}}else{var d=(this._editorElementAlternative===null)?this._editorElement:this._editorElementAlternative;var g=d.val();i+="\n\n";if(g.length==0){d.val(i)}else{var b=d.getCaret();d.val(g.substr(0,b)+i+g.substr(b))}}this._removeOnSubmit.push(f.attr("data-quote-id"));if(a!==null){this._dialog.wcfDialog("close")}},_removeSelected:function(){if(!this._dialog.find("input.jsCheckbox:checked").length){this._dialog.find("input.jsCheckbox").prop("checked","checked")}var b=[];this._dialog.find("input.jsCheckbox:checked").each(function(e,d){b.push($(d).parents("li").attr("data-quote-id"))});if(b.length){var c=[];for(var a in this._handlers){c.push(a)}this._proxy.setOption("data",{actionName:"remove",getFullQuoteObjectIDs:this._handlers.length>0,objectTypes:c,quoteIDs:b});this._proxy.sendRequest();this._dialog.wcfDialog("close")}},_submit:function(){if(this._supportPaste&&this._removeOnSubmit.length>0){var a=this._form.find(".formSubmit");for(var b in this._removeOnSubmit){$('<input type="hidden" name="__removeQuoteIDs[]" value="'+this._removeOnSubmit[b]+'" />').appendTo(a)}}},getQuotesMarkedForRemoval:function(){return this._removeOnSubmit},markQuotesForRemoval:function(){if(this._removeOnSubmit.length){this._proxy.setOption("data",{actionName:"markForRemoval",quoteIDs:this._removeOnSubmit});this._proxy.suppressErrors();this._proxy.sendRequest()}},removeMarkedQuotes:function(){if(this._removeOnSubmit.length){this._proxy.setOption("data",{actionName:"removeMarkedQuotes",getFullQuoteObjectIDs:this._handlers.length>0});this._proxy.sendRequest()}},countQuotes:function(){var b=[];for(var a in this._handlers){b.push(a)}this._proxy.setOption("data",{actionName:"count",getFullQuoteObjectIDs:this._handlers.length>0,objectTypes:b});this._proxy.sendRequest()},_success:function(c,d,b){if(c===null){return}if(c.count!==undefined){var a=(c.fullQuoteObjectIDs!==undefined)?c.fullQuoteObjectIDs:{};this.updateCount(c.count,a)}if(c.template!==undefined){if($.trim(c.template)==""){this.updateCount(0,{})}else{this.renderDialog(c.template)}}}});WCF.Message.Share={};WCF.Message.Share.Content=Class.extend({_cache:{},_dialog:null,init:function(){this._cache={};this._dialog=null;this._initLinks();WCF.DOMNodeInsertedHandler.addCallback("WCF.Message.Share.Content",$.proxy(this._initLinks,this))},_initLinks:function(){$("a.jsButtonShare").removeClass("jsButtonShare").click($.proxy(this._click,this))},_click:function(e){e.preventDefault();var a=$(e.currentTarget);var b=a.prop("href");var d=(a.data("linkTitle")?a.data("linkTitle"):b);var c=b.hashCode();if(this._cache[c]===undefined){var g=false;if(this._dialog===null){this._dialog=$("<div />").hide().appendTo(document.body);g=true}else{this._dialog.empty()}var f=$('<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",b).appendTo(f);var f=$('<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='"+b+"']"+d+"[/url]").appendTo(f);var f=$('<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="'+b+'">'+WCF.String.escapeHTML(d)+"</a>").appendTo(f);this._cache[c]=this._dialog.html();if(g){this._dialog.wcfDialog({title:WCF.Language.get("wcf.message.share")})}else{this._dialog.wcfDialog("open")}}else{this._dialog.html(this._cache[c]).wcfDialog("open")}this._enableSelection()},_enableSelection:function(){var a=this._dialog.find("input").click(function(){$(this).select()});if(navigator.userAgent.match(/iP(ad|hone|od)/)){a.keydown(function(){return false}).removeAttr("readonly").click(function(){this.setSelectionRange(0,9999)})}}});WCF.Message.Share.Page=Class.extend({_ui:{},_pageDescription:"",_pageURL:"",init:function(a){this._pageDescription=encodeURIComponent($('meta[property="og:title"]').prop("content"));this._pageURL=encodeURIComponent($('meta[property="og:url"]').prop("content"));var b=$(".messageShareButtons");this._ui={facebook:b.find(".jsShareFacebook"),google:b.find(".jsShareGoogle"),reddit:b.find(".jsShareReddit"),twitter:b.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(a===true){this._fetchFacebook();this._fetchTwitter();this._fetchReddit()}},_share:function(c,a,b){window.open(a.replace(/{pageURL}/,this._pageURL).replace(/{text}/,this._pageDescription+(b?" "+this._pageURL:"")),"height=600,width=600")},_shareFacebook:function(){this._share("facebook","https://www.facebook.com/sharer.php?u={pageURL}&t={text}",true)},_shareGoogle:function(){this._share("google","https://plus.google.com/share?url={pageURL}",true)},_shareReddit:function(){this._share("reddit","https://ssl.reddit.com/submit?url={pageURL}",true)},_shareTwitter:function(){this._share("twitter","https://twitter.com/share?url={pageURL}&text={text}",false)},_fetchCount:function(b,d,c){var a={autoSend:true,dataType:"jsonp",showLoadingOverlay:false,success:d,suppressErrors:true,type:"GET",url:b.replace(/{pageURL}/,this._pageURL)};if(c){a.jsonp=c}new WCF.Action.Proxy(a)},_fetchFacebook:function(){this._fetchCount("https://graph.facebook.com/?id={pageURL}",$.proxy(function(a){if(a.shares){this._ui.facebook.children("span.badge").show().text(a.shares)}},this))},_fetchTwitter:function(){if(window.location.protocol.match(/^https/)){return}this._fetchCount("http://urls.api.twitter.com/1/urls/count.json?url={pageURL}",$.proxy(function(a){if(a.count){this._ui.twitter.children("span.badge").show().text(a.count)}},this))},_fetchReddit:function(){if(window.location.protocol.match(/^https/)){return}this._fetchCount("http://www.reddit.com/api/info.json?url={pageURL}",$.proxy(function(a){if(a.data.children.length){this._ui.reddit.children("span.badge").show().text(a.data.children[0].data.score)}},this),"jsonp")}});WCF.Message.UserMention=Class.extend({_caretPosition:null,_className:"wcf\\data\\user\\UserAction",_dropdown:null,_dropdownMenu:null,_itemIndex:-1,_lineHeight:null,_mentionStart:"",_redactor:null,init:function(a){this._textarea=$("#"+a);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)})},_clearList:function(){this._hideList();this._dropdownMenu.empty()},_click:function(a){this._redactor.replaceRangesWith(this._caretPosition);this._setUsername($(a.currentTarget).data("username"))},_createListItem:function(c){var b=$("<li />").data("username",c.label).click($.proxy(this._click,this)).appendTo(this._dropdownMenu);var a=$("<div />").addClass("box16").appendTo(b);a.append($(c.icon).addClass("framed"));a.append($("<div />").append($("<span />").text(c.label)))},_getDropdownMenuPosition:function(){var e=getSelection().getRangeAt(0).cloneRange();var a=document.createRange();a.setStart(e.startContainer,e.startOffset-(this._mentionStart.length+1));a.setEnd(e.startContainer,e.startOffset);this._redactor.replaceRangesWith(a);var f=getSelection().getRangeAt(0);var b=f.getBoundingClientRect();var d=$(window);var c={top:Math.round(b.bottom)+d.scrollTop(),left:Math.round(b.left)+d.scrollLeft()};if(this._lineHeight===null){this._lineHeight=Math.round(b.bottom-b.top)}this._redactor.replaceRangesWith(e);this._caretPosition=e;return c},_setUsername:function(e){var c=getSelection().getRangeAt(0).cloneRange();this._redactor.bufferSet();var a=document.createRange();a.setStart(c.startContainer,c.startOffset-(this._mentionStart.length+1));a.setEnd(c.startContainer,c.startOffset);this._redactor.replaceRangesWith(a);var d=getSelection().getRangeAt(0);d.deleteContents();d.collapse(true);if(e.indexOf("'")!==-1){e=e.replace(/'/g,"''");e="'"+e+"'"}else{if(e.indexOf(" ")!==-1){e="'"+e+"'"}}var b=document.createTextNode("@"+e);d.insertNode(b);var a=document.createRange();a.setStart(b,e.length+1);a.setEnd(b,e.length+1);this._redactor.replaceRangesWith(a);this._hideList()},_getParameters:function(){return{data:{includeUserGroups:false,searchString:this._mentionStart}}},_getTextLineInFrontOfCaret:function(){if(this._redactor.getSelectionHtml().length){return""}var e=this._redactor.getSelection().getRangeAt(0);var b=e.startContainer.textContent.substr(0,e.startOffset);var a=b;b="";for(var d=0;d<a.length;d++){var c=a.charCodeAt(d).toString(16);if(c!="200b"&&!/\s/.test(a[d])){if(a[d]==="@"&&d&&/\s/.test(a[d-1])){b=""}b+=a[d]}else{b=""}}return b},_hideList:function(){this._dropdown.removeClass("dropdownOpen");this._dropdownMenu.removeClass("dropdownOpen");this._itemIndex=-1},_keydown:function(a){if(this._redactor.inPlainMode()){return true}if(this._dropdownMenu.is(":visible")){switch(a.which){case $.ui.keyCode.ENTER:a.preventDefault();this._dropdownMenu.children("li").eq(this._itemIndex).trigger("click");return false;break;case $.ui.keyCode.UP:a.preventDefault();this._selectItem(this._itemIndex-1);return false;break;case $.ui.keyCode.DOWN:a.preventDefault();this._selectItem(this._itemIndex+1);return false;break}}return true},_keyup:function(c){if(this._redactor.inPlainMode()){return true}if(c.which===$.ui.keyCode.ENTER){return}if(this._dropdownMenu.is(":visible")&&c.which in {13:1,38:1,40:1}){return}var b=this._getTextLineInFrontOfCaret();if(b){var a=b.match(/@([^,]{3,})$/);if(a){if(!a.index||b[a.index-1].match(/\s/)){this._mentionStart=a[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()}},_selectItem:function(a){var b=this._dropdownMenu.children("li");if(a<0){a=b.length-1}else{if(a+1>b.length){a=0}}b.removeClass("dropdownNavigationItem");b.eq(a).addClass("dropdownNavigationItem");this._itemIndex=a},_showList:function(){this._dropdown.addClass("dropdownOpen");this._dropdownMenu.addClass("dropdownOpen")},_success:function(c,e,b){this._clearList(false);if($.getLength(c.returnValues)){for(var d in c.returnValues){var a=c.returnValues[d];this._createListItem(a)}this._updateSuggestionListPosition();this._showList()}},_updateSuggestionListPosition:function(){try{var b=this._getDropdownMenuPosition();b.top+=5;this._dropdownMenu.css(b);this._selectItem(0);if(b.top+this._dropdownMenu.outerHeight()+10>$(window).height()+$(document).scrollTop()){this._dropdownMenu.addClass("dropdownArrowBottom");this._dropdownMenu.css({top:b.top-this._dropdownMenu.outerHeight()-2*this._lineHeight+5})}else{this._dropdownMenu.removeClass("dropdownArrowBottom")}}catch(a){}}});WCF.Moderation={};WCF.Moderation.Management=Class.extend({_buttonSelector:"",_className:"",_confirmationTemplate:{},_languageItem:"",_proxy:null,_queueID:0,_redirectURL:"",init:function(a,c,b){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=a;this._redirectURL=c;this._languageItem=b;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});$(this._buttonSelector).click($.proxy(this._click,this))},_click:function(c){var b=$(c.currentTarget).wcfIdentify();var a="";if(this._confirmationTemplate[b]){a=this._confirmationTemplate[b]}WCF.System.Confirmation.show(WCF.Language.get(this._languageItem.replace(/{actionName}/,b)),$.proxy(function(e){if(e==="confirm"){var d={actionName:b,className:this._className,objectIDs:[this._queueID]};if(this._confirmationTemplate[b]){d.parameters={};a.find("input, textarea").each(function(g,i){var f=$(i);var h=f.val();if(f.getTagName()==="input"&&f.attr("type")==="checkbox"){if(!f.is(":checked")){h=null}}if(h!==null){d.parameters[f.attr("name")]=h}})}this._proxy.setOption("data",d);this._proxy.sendRequest();$(this._buttonSelector).disable()}},this),{},a)},_success:function(c,e,b){var d=new WCF.System.Notification(WCF.Language.get("wcf.global.success"));var a=this;d.show(function(){window.location=a._redirectURL})}});WCF.Moderation.Activation={};WCF.Moderation.Activation.Management=WCF.Moderation.Management.extend({init:function(a,b){this._buttonSelector="#enableContent, #removeContent";this._className="wcf\\data\\moderation\\queue\\ModerationQueueActivationAction";this._super(a,b,"wcf.moderation.activation.{actionName}.confirmMessage")}});WCF.Moderation.Report={};WCF.Moderation.Report.Content=Class.extend({_buttons:{},_buttonSelector:"",_dialog:null,_notification:null,_objectID:0,_objectType:"",_proxy:null,init:function(a,b){this._objectType=a;this._buttonSelector=b;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))},_initButtons:function(){var a=this;$(this._buttonSelector).each(function(c,d){var e=$(d);var b=e.wcfIdentify();if(!a._buttons[b]){a._buttons[b]=e;e.click($.proxy(a._click,a))}})},_click:function(a){this._objectID=$(a.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()},_success:function(b,c,a){if(b.returnValues.reported){if(this._notification===null){this._notification=new WCF.System.Notification(WCF.Language.get("wcf.moderation.report.success"))}this._dialog.wcfDialog("close");this._notification.show()}else{if(b.returnValues.template){this._showDialog(b.returnValues.template);if(!b.returnValues.alreadyReported){this._dialog.find(".jsSubmitReport").click($.proxy(this._submit,this))}}}},_showDialog:function(a){if(this._dialog===null){this._dialog=$("#moderationReport");if(!this._dialog.length){this._dialog=$('<div id="moderationReport" />').hide().appendTo(document.body)}}this._dialog.html(a).wcfDialog({title:WCF.Language.get("wcf.moderation.report.reportContent")}).wcfDialog("render")},_submit:function(){var a=this._dialog.find(".jsReportMessage").val();if(a==""){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:a,objectID:this._objectID,objectType:this._objectType}});this._proxy.sendRequest()}});WCF.Moderation.Report.Management=WCF.Moderation.Management.extend({init:function(a,b){this._buttonSelector="#removeContent, #removeReport";this._className="wcf\\data\\moderation\\queue\\ModerationQueueReportAction";this._super(a,b,"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>')}});WCF.Moderation.UserPanel=WCF.UserPanel.extend({_showAllLink:"",_deletedContentLink:"",init:function(a,b){this._noItems="wcf.moderation.noMoreItems";this._showAllLink=a;this._deletedContentLink=b;this._super("outstandingModeration")},_addDefaultItems:function(a){this._addDivider(a);$('<li><a href="'+this._showAllLink+'">'+WCF.Language.get("wcf.moderation.showAll")+"</a></li>").appendTo(a);this._addDivider(a);$('<li><a href="'+this._deletedContentLink+'">'+WCF.Language.get("wcf.moderation.showDeletedContent")+"</a></li>").appendTo(a)},_getParameters:function(){return{actionName:"getOutstandingQueues",className:"wcf\\data\\moderation\\queue\\ModerationQueueAction"}}});WCF.Poll={};WCF.Poll.Management=Class.extend({_container:null,_count:0,_inputSize:0,_maxOptions:0,init:function(a,e,f){this._count=0;this._maxOptions=f||-1;this._container=$("#"+a).children("ol:eq(0)");if(!this._container.length){console.debug("[WCF.Poll.Management] Invalid container id given, aborting.");return}e=e||[];this._createOptionList(e);$(window).resize($.proxy(this._resize,this));this._container.parents("form").submit($.proxy(this._submit,this));new WCF.Sortable.List(a,"",undefined,undefined,true);this._resize();var d=this._container.parents(".tabMenuContent:eq(0)");var c=d.wcfIdentify();var b=this;d.parents(".tabMenuContainer:eq(0)").on("wcftabsactivate",function(g,h){if(h.newPanel.wcfIdentify()==c){b._resize()}})},_createOptionList:function(b){for(var d=0,c=b.length;d<c;d++){var a=b[d];this._createOption(a.optionValue,a.optionID)}this._createOption()},_createOption:function(f,d,c){f=f||"";d=parseInt(d)||0;c=c||null;var b=$('<li class="sortableNode" />').data("optionID",d);if(c===null){b.appendTo(this._container)}else{b.insertAfter(c)}var a=$('<span class="sortableButtonContainer" />').appendTo(b);$('<span class="icon icon16 icon-plus jsTooltip jsAddOption pointer" title="'+WCF.Language.get("wcf.poll.button.addOption")+'" />').click($.proxy(this._addOption,this)).appendTo(a);$('<span class="icon icon16 icon-remove jsTooltip jsDeleteOption pointer" title="'+WCF.Language.get("wcf.poll.button.removeOption")+'" />').click($.proxy(this._removeOption,this)).appendTo(a);var e=$('<input type="text" value="'+f+'" maxlength="255" />').css({width:this._inputSize+"px"}).keydown($.proxy(this._keyDown,this)).appendTo(b);if(c!==null){e.focus()}WCF.DOMNodeInsertedHandler.execute();this._count++;if(this._count===this._maxOptions){this._container.find("span.jsAddOption").removeClass("pointer").addClass("disabled")}},_keyDown:function(a){if(a.which!==13){return true}$(a.currentTarget).prev(".sortableButtonContainer").children(".jsAddOption").trigger("click");a.preventDefault();a.stopPropagation();return false},_addOption:function(b){if(this._count===this._maxOptions){return false}var a=$(b.currentTarget).parents("li");this._createOption(undefined,undefined,a)},_removeOption:function(a){$(a.currentTarget).parents("li").remove();this._count--;this._container.find("span.jsAddOption").addClass("pointer").removeClass("disabled");if(this._container.children("li").length==0){this._createOption()}},_resize:function(){var d=this._container.innerWidth();var c=this._container.children("li:eq(0)");var b=c.children(".sortableButtonContainer").outerWidth();var a=d-b;if(a!=this._inputSize){this._inputSize=a;this._container.find("li > input").css({width:this._inputSize+"px"})}},_submit:function(){var a=[];this._container.children("li").each(function(f,i){var h=$(i);var g=$.trim(h.children("input").val());if(g!=""){a.push({optionID:h.data("optionID"),optionValue:g})}});if(a.length){var b=this._container.parents("form").find(".formSubmit");for(var e=0,d=a.length;e<d;e++){var c=a[e];$('<input type="hidden" name="pollOptions['+e+']" />').val(c.optionID+"_"+c.optionValue).appendTo(b)}}}});WCF.Poll.Manager=Class.extend({_cache:{},_canViewParticipants:{},_canViewResult:{},_canVote:{},_inputElements:{},_participants:{},_polls:{},_proxy:null,init:function(a){var c=$(a);if(!c.length){console.debug("[WCF.Poll.Manager] Given selector '"+a+"' 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});var b=this;c.each(function(e,g){var f=$(g);var d=f.data("pollID");if(b._polls[d]===undefined){b._cache[d]={result:"",vote:""};b._polls[d]=f;b._canViewParticipants[d]=(f.data("canViewParticipants"))?true:false;b._canViewResult[d]=(f.data("canViewResult"))?true:false;b._canVote[d]=(f.data("canVote"))?true:false;b._bindListeners(d);if(f.data("inVote")){b._prepareVote(d)}b._toggleButtons(d)}})},_bindListeners:function(a){this._polls[a].find(".jsButtonPollShowParticipants").data("pollID",a).click($.proxy(this._showParticipants,this));this._polls[a].find(".jsButtonPollShowResult").data("pollID",a).click($.proxy(this._showResult,this));this._polls[a].find(".jsButtonPollShowVote").data("pollID",a).click($.proxy(this._showVote,this));this._polls[a].find(".jsButtonPollVote").data("pollID",a).click($.proxy(this._vote,this))},_showResult:function(b,c){var a=(b===null)?c:$(b.currentTarget).data("pollID");if(!this._canViewResult[a]){return}if(!this._polls[a].data("inVote")){return}if(!this._cache[a].result){this._proxy.setOption("data",{actionName:"getResult",pollID:a});this._proxy.sendRequest()}else{this._polls[a].find(".pollInnerContainer").html(this._cache[a].result);this._polls[a].data("inVote",false);this._toggleButtons(a)}},_showParticipants:function(b){var a=$(b.currentTarget).data("pollID");if(!this._participants[a]){this._participants[a]=new WCF.User.List("wcf\\data\\poll\\PollAction",this._polls[a].data("question"),{pollID:a})}this._participants[a].open()},_showVote:function(b,c){var a=(b===null)?c:$(b.currentTarget).data("pollID");if(!this._canVote[a]){return}if(this._polls[a].data("inVote")){return}if(!this._cache[a].vote){this._proxy.setOption("data",{actionName:"getVote",pollID:a});this._proxy.sendRequest()}else{this._polls[a].find(".pollInnerContainer").html(this._cache[a].vote);this._polls[a].data("inVote",true);this._prepareVote(a);this._toggleButtons(a)}},_success:function(c,d,b){if(!c||!c.actionName){return}var a=c.pollID;if(c.resultTemplate){this._cache[a].result=c.resultTemplate}if(c.voteTemplate){this._cache[a].vote=c.voteTemplate}switch(c.actionName){case"getResult":this._showResult(null,a);break;case"getVote":this._showVote(null,a);break;case"vote":this._canViewResult[a]=true;this._canVote[a]=(c.canVote)?true:false;this._showResult(null,a);break}},_prepareVote:function(c){this._polls[c].find(".jsButtonPollVote").disable();var d=this._polls[c].find(".pollInnerContainer > .jsPollVote");var a=this;this._inputElements[c]=d.find("input").change(function(){a._handleVoteButton(c)});this._handleVoteButton(c);var b=d.data("maxVotes");if(this._inputElements[c].filter("[type=checkbox]").length){this._inputElements[c].change(function(){a._enforceMaxVotes(c,b)});this._enforceMaxVotes(c,b)}},_enforceMaxVotes:function(c,b){var a=this._inputElements[c];if(a.filter(":checked").length==b){a.filter(":not(:checked)").disable()}else{a.enable()}},_handleVoteButton:function(b){var a=this._inputElements[b];var c=this._polls[b].find(".jsButtonPollVote");if(a.filter(":checked").length){c.enable()}else{c.disable()}},_toggleButtons:function(c){var a=this._polls[c].children(".formSubmit");a.find(".jsButtonPollShowParticipants, .jsButtonPollShowResult, .jsButtonPollShowVote, .jsButtonPollVote").hide();var b=true;if(this._polls[c].data("inVote")){b=false;a.find(".jsButtonPollVote").show();if(this._canViewResult[c]){a.find(".jsButtonPollShowResult").show()}}else{if(this._canVote[c]){b=false;a.find(".jsButtonPollShowVote").show()}if(this._canViewParticipants[c]){b=false;a.find(".jsButtonPollShowParticipants").show()}}if(b){a.hide()}},_vote:function(c){var a=$(c.currentTarget).data("pollID");if(!this._canVote[a]){return}var b=[];this._inputElements[a].each(function(e,d){var f=$(d);if(f.is(":checked")){b.push(f.data("optionID"))}});if(b.length){this._proxy.setOption("data",{actionName:"vote",optionIDs:b,pollID:a});this._proxy.sendRequest()}}});WCF.Search.Message={};WCF.Search.Message.KeywordList=WCF.Search.Base.extend({_className:"wcf\\data\\search\\keyword\\SearchKeywordAction",_divider:null,_forceSubmit:false,init:function(c,e,b){if(!$.isFunction(e)){console.debug("[WCF.Search.Message.KeywordList] The given callback is invalid, aborting.");return}this._callback=e;this._excludedSearchValues=[];if(b){this._excludedSearchValues=b}this._searchInput=$(c).keyup($.proxy(this._keyUp,this)).keydown($.proxy(function(f){if(f.which===13){if(this._itemCount&&this._itemIndex!==-1){f.preventDefault()}}},this));var a=WCF.Dropdown.getDropdownMenu(this._searchInput.parents(".dropdown").wcfIdentify());var d=a.find("li.dropdownDivider").last();this._divider=$('<li class="dropdownDivider" />').hide().insertBefore(d);this._list=$('<li class="dropdownList"><ul /></li>').hide().insertBefore(d).children("ul");a.find("input, label").on("click",function(f){f.stopPropagation()});this._proxy=new WCF.Action.Proxy({showLoadingOverlay:false,success:$.proxy(this._success,this)})},_createListItem:function(a){this._divider.show();this._list.parent().show();this._super(a)},_clearList:function(a){if(a){this._searchInput.val("")}this._divider.hide();this._list.empty().parent().hide();WCF.CloseOverlayHandler.removeCallback("WCF.Search.Base");this._itemCount=0;this._itemIndex=-1}});WCF.Search.Message.SearchArea=Class.extend({_searchArea:null,init:function(d){this._searchArea=d;var a=new WCF.Search.Message.KeywordList(this._searchArea.find("input[type=search]"),$.proxy(this._callback,this));a.setDelay(500);var c=this;var f=this._searchArea.find("input[type=search]");this._searchArea.click(function(g){if(g.target==c._searchArea[0]){f.focus().trigger("click");return false}});if(this._searchArea.hasClass("dropdown")){var e=this._searchArea.wcfIdentify();var b=this._searchArea.find("form");b.submit(function(){var g=WCF.Dropdown.getDropdownMenu(e);g.find("input[type=hidden]").appendTo(b);g.find("input[type=checkbox]:checked").each(function(i,h){var j=$(h);$('<input type="hidden" name="'+j.attr("name")+'" value="'+j.attr("value")+'" />').appendTo(b)})})}},_callback:function(a){this._searchArea.find("input[type=search]").val(a.label);this._searchArea.find("input[type=search]").focus();return false}});WCF.Tagging={};WCF.Tagging.TagList=WCF.EditableItemList.extend({_className:"wcf\\data\\tag\\TagAction",_maxLength:0,init:function(c,a,b){this._allowCustomInput=true;this._maxLength=b;this._super(c,a);this._data=[];this._search=new WCF.Tagging.TagSearch(this._searchInput,$.proxy(this.addItem,this));this._itemList.addClass("tagList");$(c).data("__api",this)},_keyDown:function(b){if(this._super(b)){if(b===null){return true}var a=b.which;if(a===8||a===27||a===13||a===46){return true}else{if(a>36&&a<41){return true}}if(this._searchInput.val().length>=this._maxLength){return false}return true}return false},_submit:function(){this._super();for(var b=0,a=this._data.length;b<a;b++){if(this._data[b]){$('<input type="hidden" name="tags[]" />').val(this._data[b]).appendTo(this._form)}}},addItem:function(b){if(!b.objectID&&b.label.length>this._maxLength){b.label=b.label.substr(0,this._maxLength)}var a=this._super(b);$(this._itemList).find(".badge:not(tag)").addClass("tag");return a},_addItem:function(b,a){this._data.push(a)},clearList:function(){this._super();this._data=[]},getTags:function(){return this._data},_removeItem:function(d,a){for(var c=0,b=this._data.length;c<b;c++){if(this._data[c]===a){this._data.splice(c,1);return}}},load:function(a){if(a&&a.length){for(var c=0,b=a.length;c<b;c++){this.addItem({objectID:0,label:WCF.String.unescapeHTML(a[c])})}}}});WCF.Tagging.TagSearch=WCF.Search.Base.extend({_className:"wcf\\data\\tag\\TagAction",init:function(b,d,a,c){this._super(b,d,a,c,false)}});WCF.User.Login=Class.extend({_loginSubmitButton:null,_password:null,_passwordContainer:null,_useCookies:null,_useCookiesContainer:null,init:function(b){this._loginSubmitButton=$("#loginSubmitButton");this._password=$("#password"),this._passwordContainer=this._password.parents("dl");this._useCookies=$("#useCookies");this._useCookiesContainer=this._useCookies.parents("dl");var a=$("#loginForm");a.find("input[name=action]").change($.proxy(this._change,this));if(b){WCF.User.QuickLogin.init()}},_change:function(a){if($(a.currentTarget).val()==="register"){this._setState(false,WCF.Language.get("wcf.user.button.register"))}else{this._setState(true,WCF.Language.get("wcf.user.button.login"))}},_setState:function(b,a){if(b){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(a)}});WCF.User.QuickLogin={_dialog:null,_loginMessage:null,init:function(){$(".loginLink").click($.proxy(this._render,this));$("#loginForm input[name=url]").val(function(a,b){return window.location.protocol+"//"+window.location.host+b})},show:function(a){if(a){if(this._loginMessage===null){this._loginMessage=$('<p class="info" />').hide().prependTo($("#loginForm > form"))}this._loginMessage.show().text(a)}else{if(this._loginMessage!==null){this._loginMessage.hide()}}this._render()},_render:function(a){if(a!==undefined){a.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")}}};WCF.User.Profile={};WCF.User.Profile.ActivityPointList={_cache:{},_dialog:null,_didInit:false,_proxy:null,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},_init:function(){$(".activityPointsDisplay").removeClass("activityPointsDisplay").click($.proxy(this._click,this))},_click:function(b){var a=$(b.currentTarget).data("userID");if(this._cache[a]===undefined){this._proxy.setOption("data",{actionName:"getDetailedActivityPointList",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[a]});this._proxy.sendRequest()}else{this._show(a)}},_show:function(a){if(this._dialog===null){this._dialog=$("<div>"+this._cache[a]+"</div>").hide().appendTo(document.body);this._dialog.wcfDialog({title:WCF.Language.get("wcf.user.activityPoint")})}else{this._dialog.html(this._cache[a]);this._dialog.wcfDialog("open")}},_success:function(b,c,a){this._cache[b.returnValues.userID]=b.returnValues.template;this._show(b.returnValues.userID)}};WCF.User.Profile.Follow=Class.extend({_button:null,_following:false,_proxy:null,_userID:0,init:function(a,b){this._following=b;this._userID=a;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._createButton();this._showButton()},_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))},_execute:function(){var a=(this._following)?"unfollow":"follow";this._proxy.setOption("data",{actionName:a,className:"wcf\\data\\user\\follow\\UserFollowAction",parameters:{data:{userID:this._userID}}});this._proxy.sendRequest()},_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")}},_success:function(b,d,a){this._following=b.returnValues.following;this._showButton();var c=new WCF.System.Notification();c.show()}});WCF.User.Profile.IgnoreUser=Class.extend({_button:null,_isIgnoredUser:false,_proxy:null,_userID:0,init:function(b,a){this._userID=b;this._isIgnoredUser=a;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._updateButton();this._button.click($.proxy(this._click,this))},_click:function(){var a=(this._isIgnoredUser)?"unignore":"ignore";this._proxy.setOption("data",{actionName:a,className:"wcf\\data\\user\\ignore\\UserIgnoreAction",parameters:{data:{ignoreUserID:this._userID}}});this._proxy.sendRequest()},_success:function(b,d,a){this._isIgnoredUser=b.returnValues.isIgnoredUser;this._updateButton();var c=new WCF.System.Notification();c.show()},_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")}}});WCF.User.Profile.TabMenu=Class.extend({_hasContent:{},_profileContent:null,_proxy:null,_userID:0,init:function(a){this._profileContent=$("#profileContent");this._userID=a;var c=this._profileContent.data("active");var b=false;this._profileContent.find("div.tabMenuContent").each($.proxy(function(e,d){var f=$(d).wcfIdentify();if(c===f){this._hasContent[f]=true}else{this._hasContent[f]=false;b=true}},this));if(b){this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._profileContent.bind("wcftabsbeforeactivate",$.proxy(this._loadContent,this))}},_loadContent:function(b,c){var d=$(c.newPanel);var a=d.attr("id");if(!this._hasContent[a]){this._proxy.setOption("data",{actionName:"getContent",className:"wcf\\data\\user\\profile\\menu\\item\\UserProfileMenuItemAction",parameters:{data:{containerID:a,menuItem:d.data("menuItem"),userID:this._userID}}});this._proxy.sendRequest()}},_success:function(d,e,c){var b=d.returnValues.containerID;this._hasContent[b]=true;var a=this._profileContent.find("#"+b);$("<div>"+d.returnValues.template+"</div>").hide().appendTo(a);a.children("div").wcfBlindIn()}});WCF.User.Profile.Editor=Class.extend({_actionName:"",_buttons:{},_cachedTemplate:"",_proxy:null,_tab:null,_userID:0,init:function(a,b){this._actionName="";this._cachedTemplate="";this._tab=$("#about");this._userID=a;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._initButtons();if(b){this._beginEdit()}},_initButtons:function(){var a=$("#profileButtonContainer");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(a)}},_beginEdit:function(){this._actionName="beginEdit";this._buttons.beginEdit.hide();$("#profileContent").wcfTabs("select","about");this._proxy.setOption("data",{actionName:"beginEdit",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[this._userID]});this._proxy.sendRequest()},_save:function(){this._actionName="save";var b=/values\[([a-zA-Z0-9._-]+)\]/;var a={};this._tab.find("input, textarea, select").each(function(e,f){var d=$(f);if(d.getTagName()==="input"){var c=d.attr("type");if((c==="radio"||c==="checkbox")&&!d.prop("checked")){return}}var g=d.attr("name");if(b.test(g)){a[RegExp.$1]=d.val()}});this._proxy.setOption("data",{actionName:"save",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[this._userID],parameters:{values:a}});this._proxy.sendRequest()},_restore:function(){this._actionName="restore";this._buttons.beginEdit.show();this._destroyCKEditor();this._tab.html(this._cachedTemplate).children().css({height:"auto"})},_success:function(b,c,a){switch(this._actionName){case"beginEdit":this._prepareEdit(b);break;case"save":if(b.returnValues.success){this._cachedTemplate=b.returnValues.template;this._restore()}else{this._prepareEdit(b,true)}break}},_prepareEdit:function(b,c){this._destroyCKEditor();var a=this;this._tab.html(function(e,d){if(c!==true){a._cachedTemplate=d}return b.returnValues.template});this._tab.find("input[type=text]").attr("autocomplete","off");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(d){if(d.which===13){a._save();d.preventDefault();return false}})},_destroyCKEditor:function(){this._tab.find("textarea + .cke").each(function(c,b){var a=$(b).attr("id").replace(/cke_/,"");if(CKEDITOR.instances[a]){CKEDITOR.instances[a].destroy()}})}});WCF.User.Registration={};WCF.User.Registration.Validation=Class.extend({_actionName:"",_className:"",_confirmElement:null,_element:null,_errorMessages:{},_options:{},_proxy:null,init:function(b,c,a){this._element=b;this._element.blur($.proxy(this._blur,this));this._confirmElement=c||null;if(this._confirmElement!==null){this._confirmElement.blur($.proxy(this._blurConfirm,this))}a=a||{};this._setOptions(a);this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this),showLoadingOverlay:false});this._setErrorMessages()},_setOptions:function(a){},_setErrorMessages:function(){this._errorMessages={ajaxError:"",notEqual:""}},_blur:function(b){var a=this._element.val();if(!a){return this._showError(this._element,WCF.Language.get("wcf.global.form.error.empty"))}if(this._confirmElement!==null){var c=this._confirmElement.val();if(c!=""&&a!=c){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()},_getParameters:function(){return{}},_validateOptions:function(){return true},_blurConfirm:function(b){var a=this._confirmElement.val();if(!a){return this._showError(this._confirmElement,WCF.Language.get("wcf.global.form.error.empty"))}this._blur(b)},_success:function(b,c,a){if(b.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+b.returnValues.error))}},_showError:function(a,b){a.parent().parent().addClass("formError").removeClass("formSuccess");var c=a.parent().find("small.innerError");if(!c.length){c=$("<small />").addClass("innerError").insertAfter(a)}c.text(b)},_showSuccess:function(a){a.parent().parent().addClass("formSuccess").removeClass("formError");a.next("small.innerError").remove()}});WCF.User.Registration.Validation.Username=WCF.User.Registration.Validation.extend({_actionName:"validateUsername",_className:"wcf\\data\\user\\UserRegistrationAction",_setOptions:function(a){this._options=$.extend(true,{minlength:3,maxlength:25},a)},_setErrorMessages:function(){this._errorMessages={ajaxError:"wcf.user.username.error."}},_validateOptions:function(){var a=this._element.val();if(a.length<this._options.minlength||a.length>this._options.maxlength){this._showError(this._element,WCF.Language.get("wcf.user.username.error.notValid"));return false}return true},_getParameters:function(){return{username:this._element.val()}}});WCF.User.Registration.Validation.EmailAddress=WCF.User.Registration.Validation.extend({_actionName:"validateEmailAddress",_className:"wcf\\data\\user\\UserRegistrationAction",_getParameters:function(){return{email:this._element.val()}},_setErrorMessages:function(){this._errorMessages={ajaxError:"wcf.user.email.error.",notEqual:WCF.Language.get("wcf.user.confirmEmail.error.notEqual")}}});WCF.User.Registration.Validation.Password=WCF.User.Registration.Validation.extend({_actionName:"validatePassword",_className:"wcf\\data\\user\\UserRegistrationAction",_getParameters:function(){return{password:this._element.val()}},_setErrorMessages:function(){this._errorMessages={ajaxError:"wcf.user.password.error.",notEqual:WCF.Language.get("wcf.user.confirmPassword.error.notEqual")}}});WCF.User.Registration.LostPassword=Class.extend({_email:null,_username:null,init:function(){this._email=$("#emailInput");this._username=$("#usernameInput");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))}this._checkEmail();this._checkUsername()},_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")}},_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")}}});WCF.Notification={};WCF.Notification.UserPanel=WCF.UserPanel.extend({_proxy:null,_showAllLink:"",init:function(a){this._noItems="wcf.user.notification.noMoreNotifications";this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._showAllLink=a;this._super("userNotifications");if(this._container.data("count")){document.title="("+this._container.data("count")+") "+document.title}},_addDefaultItems:function(a){this._addDivider(a);if(this._container.data("count")){$('<li><a href="'+this._showAllLink+'">'+WCF.Language.get("wcf.user.notification.showAll")+"</a></li>").appendTo(a);this._addDivider(a)}$('<li id="userNotificationsMarkAllAsConfirmed"><a>'+WCF.Language.get("wcf.user.notification.markAllAsConfirmed")+"</a></li>").click($.proxy(this._markAllAsConfirmed,this)).appendTo(a)},_getParameters:function(){return{actionName:"getOutstandingNotifications",className:"wcf\\data\\user\\notification\\UserNotificationAction"}},_after:function(a){WCF.Dropdown.getDropdownMenu(this._container.wcfIdentify()).children("li.jsNotificationItem").click($.proxy(this._markAsConfirmed,this))},_markAsConfirmed:function(a){this._proxy.setOption("data",{actionName:"markAsConfirmed",className:"wcf\\data\\user\\notification\\UserNotificationAction",parameters:{notificationID:$(a.currentTarget).data("notificationID")}});this._proxy.sendRequest()},_markAllAsConfirmed:function(){WCF.System.Confirmation.show(WCF.Language.get("wcf.user.notification.markAllAsConfirmed.confirmMessage"),$.proxy(function(a){if(a==="confirm"){this._proxy.setOption("data",{actionName:"markAllAsConfirmed",className:"wcf\\data\\user\\notification\\UserNotificationAction"});this._proxy.sendRequest()}},this))},_success:function(b,c,a){switch(b.actionName){case"markAllAsConfirmed":$(".jsNotificationItem").remove();document.title=document.title.replace(/^\(([0-9]+)\) /,"");case"getOutstandingNotifications":if(!b.returnValues||!b.returnValues.template){$("#userNotificationsMarkAllAsConfirmed").prev(".dropdownDivider").remove();$("#userNotificationsMarkAllAsConfirmed").remove()}this._super(b,c,a);break;case"markAsConfirmed":WCF.Dropdown.getDropdownMenu(this._container.wcfIdentify()).children("li.jsNotificationItem").each(function(e,f){var d=$(f);if(b.returnValues.notificationID==d.data("notificationID")){window.location=d.data("link");return false}});break}}});WCF.Notification.List=Class.extend({_badge:null,_items:{},_proxy:null,init:function(){var a=$("li.jsNotificationItem");if(!a.length){return}a.each($.proxy(function(c,b){var d=$(b);this._items[d.data("notificationID")]=d;d.find(".jsMarkAsConfirmed").data("notificationID",d.data("notificationID")).click($.proxy(this._click,this));d.find("p").html(function(f,e){return"<a>"+e+"</a>"}).children("a").data("notificationID",d.data("notificationID")).click($.proxy(this._clickLink,this))},this));this._badge=$(".jsNotificationsBadge:eq(0)");this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});$(".contentNavigation .jsMarkAllAsConfirmed").click($.proxy(this._markAllAsConfirmed,this))},_clickLink:function(a){this._items[$(a.currentTarget).data("notificationID")].data("redirect",true);this._click(a)},_click:function(a){this._proxy.setOption("data",{actionName:"markAsConfirmed",className:"wcf\\data\\user\\notification\\UserNotificationAction",parameters:{notificationID:$(a.currentTarget).data("notificationID")}});this._proxy.sendRequest()},_markAllAsConfirmed:function(){WCF.System.Confirmation.show(WCF.Language.get("wcf.user.notification.markAllAsConfirmed.confirmMessage"),$.proxy(function(a){if(a==="confirm"){this._proxy.setOption("data",{actionName:"markAllAsConfirmed",className:"wcf\\data\\user\\notification\\UserNotificationAction"});this._proxy.sendRequest()}},this))},_success:function(c,d,b){switch(c.actionName){case"markAllAsConfirmed":window.location.reload();break;case"markAsConfirmed":var a=this._items[c.returnValues.notificationID];if(a.data("redirect")){window.location=a.data("link");return}this._items[c.returnValues.notificationID].remove();delete this._items[c.returnValues.notificationID];this._badge.html(c.returnValues.totalCount);document.title=document.title.replace(/^\(([0-9]+)\) /,"");if(c.returnValues.totalCount>0){document.title="("+c.returnValues.totalCount+") "+document.title}break}}});WCF.User.SignaturePreview=WCF.Message.Preview.extend({_handleResponse:function(b){var a=$("#previewContainer");if(!a.length){a=$('<fieldset id="previewContainer"><legend>'+WCF.Language.get("wcf.global.preview")+"</legend><div></div></fieldset>").insertBefore($("#signatureContainer")).wcfFadeIn()}a.children("div").first().html(b.returnValues.message)}});WCF.User.RecentActivityLoader=Class.extend({_container:null,_filteredByFollowedUsers:false,_loadButton:null,_proxy:null,_userID:0,init:function(b,a){this._container=$("#recentActivities");this._filteredByFollowedUsers=(a===true);this._userID=b;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))},_click:function(){this._loadButton.enable();var a={lastEventTime:this._container.data("lastEventTime")};if(this._userID){a.userID=this._userID}else{if(this._filteredByFollowedUsers){a.filteredByFollowedUsers=1}}this._proxy.setOption("data",{actionName:"load",className:"wcf\\data\\user\\activity\\event\\UserActivityEventAction",parameters:a});this._proxy.sendRequest()},_success:function(b,c,a){if(b.returnValues.template){$(b.returnValues.template).insertBefore(this._loadButton.parent());this._container.data("lastEventTime",b.returnValues.lastEventTime);this._loadButton.enable()}else{$("<small>"+WCF.Language.get("wcf.user.recentActivity.noMoreEntries")+"</small>").appendTo(this._loadButton.parent());this._loadButton.remove()}}});WCF.User.ProfilePreview=WCF.Popover.extend({_proxy:null,_userProfiles:{},init:function(){this._super(".userLink");this._proxy=new WCF.Action.Proxy({showLoadingOverlay:false})},_loadContent:function(){var a=$("#"+this._activeElementID);var c=a.data("userID");if(this._userProfiles[c]){this._insertContent(this._activeElementID,this._userProfiles[c],true)}else{this._proxy.setOption("data",{actionName:"getUserProfile",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[c]});var d=this._activeElementID;var b=this;this._proxy.setOption("success",function(f,g,e){b._userProfiles[c]=f.returnValues.template;b._insertContent(d,f.returnValues.template,true)});this._proxy.setOption("failure",function(f,e,h,g){b._userProfiles[c]=f.message;b._insertContent(d,f.message,true);return false});this._proxy.sendRequest()}}});WCF.User.Action={};WCF.User.Action.Follow=Class.extend({_containerList:null,_followButtonSelector:".jsFollowButton",_userID:0,init:function(b,a){if(!b.length){return}this._containerList=b;if(a){this._followButtonSelector=a}this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._containerList.each($.proxy(function(d,c){$(c).find(this._followButtonSelector).click($.proxy(this._click,this))},this))},_click:function(b){var a=$(b.target);if(!a.is("a")){a=a.closest("a")}this._userID=a.data("objectID");this._proxy.setOption("data",{actionName:a.data("following")?"unfollow":"follow",className:"wcf\\data\\user\\follow\\UserFollowAction",parameters:{data:{userID:this._userID}}});this._proxy.sendRequest()},_success:function(b,d,a){this._containerList.each($.proxy(function(f,e){var g=$(e).find(this._followButtonSelector).get(0);if(g&&$(g).data("objectID")==this._userID){g=$(g);if(b.returnValues.following){g.data("tooltip",WCF.Language.get("wcf.user.button.unfollow")).children(".icon").removeClass("icon-plus").addClass("icon-minus")}else{g.data("tooltip",WCF.Language.get("wcf.user.button.follow")).children(".icon").removeClass("icon-minus").addClass("icon-plus")}g.data("following",b.returnValues.following);return false}},this));var c=new WCF.System.Notification();c.show()}});WCF.User.Action.Ignore=Class.extend({_containerList:null,_ignoreButtonSelector:".jsIgnoreButton",_userID:0,init:function(a,b){if(!a.length){return}this._containerList=a;if(b){this._ignoreButtonSelector=b}this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._containerList.each($.proxy(function(d,c){$(c).find(this._ignoreButtonSelector).click($.proxy(this._click,this))},this))},_click:function(b){var a=$(b.target);if(!a.is("a")){a=a.closest("a")}this._userID=a.data("objectID");this._proxy.setOption("data",{actionName:a.data("ignored")?"unignore":"ignore",className:"wcf\\data\\user\\ignore\\UserIgnoreAction",parameters:{data:{ignoreUserID:this._userID}}});this._proxy.sendRequest()},_success:function(b,d,a){this._containerList.each($.proxy(function(f,e){var g=$(e).find(this._ignoreButtonSelector).get(0);if(g&&$(g).data("objectID")==this._userID){g=$(g);if(b.returnValues.isIgnoredUser){g.data("tooltip",WCF.Language.get("wcf.user.button.unignore")).children(".icon").removeClass("icon-ban-circle").addClass("icon-circle-blank")}else{g.data("tooltip",WCF.Language.get("wcf.user.button.ignore")).children(".icon").removeClass("icon-circle-blank").addClass("icon-ban-circle")}g.data("ignored",b.returnValues.isIgnoredUser);return false}},this));var c=new WCF.System.Notification();c.show()}});WCF.User.Avatar={};WCF.User.Avatar.Crop=Class.extend({_cropX:0,_cropY:0,_dialog:null,_proxy:null,MAX_THUMBNAIL_SIZE:128,init:function(a){this._avatarID=a;if(this._dialog){this.destroy()}this._dialog=null;if(!this._proxy){this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)})}$(".userAvatarCrop").click($.proxy(this._showCropDialog,this))},destroy:function(){this._dialog.remove()},_crop:function(a){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()},_getCropDialog:function(b){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(b.returnValues.template);this._dialog.find('button[data-type="save"]').click($.proxy(this._crop,this));this._cropX=b.returnValues.cropX;this._cropY=b.returnValues.cropY;var a=$("#userAvatarCropSelection > img");$("#userAvatarCropSelection").css({height:a.height()+"px",width:a.width()+"px"});$("#userAvatarCropOverlaySelection").css({"background-image":"url("+a.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")},_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")}},_success:function(b,d,a){switch(b.actionName){case"getCropDialog":this._getCropDialog(b);break;case"cropAvatar":$("#avatarUpload > dt > img").replaceWith($('<img src="'+b.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 c=new WCF.System.Notification();c.show();break}},_updateSelection:function(a,b){this._cropX=b.position.left;this._cropY=b.position.top;$("#userAvatarCropOverlaySelection").css({"background-position":-b.position.left+"px "+-b.position.top+"px"})}});WCF.User.Avatar.Upload=WCF.Upload.extend({_avatarCrop:null,_userID:0,init:function(b,a){this._super($("#avatarUpload > dd > div"),undefined,"wcf\\data\\user\\avatar\\UserAvatarAction");this._userID=b||0;this._avatarCrop=a;$("#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()}},_initFile:function(a){return $("#avatarUpload > dt > img")},_success:function(c,a){if(a.returnValues.url){this._updateImage(a.returnValues.url,a.returnValues.canCrop);if(a.returnValues.canCrop){if(!this._avatarCrop){this._avatarCrop=new WCF.User.Avatar.Crop(a.returnValues.avatarID)}else{this._avatarCrop.init(a.returnValues.avatarID)}}else{if(this._avatarCrop){this._avatarCrop.destroy();this._avatarCrop=null}}$("#avatarUpload > dd > .innerError").remove();var b=new WCF.System.Notification(WCF.Language.get("wcf.user.avatar.upload.success"));b.show()}else{if(a.returnValues.errorType){this._getInnerErrorElement().text(WCF.Language.get("wcf.user.avatar.upload.error."+a.returnValues.errorType))}}},_updateImage:function(b,a){$("#avatarUpload > dt > img").remove();var c=$('<img src="'+b+'" alt="" />').css({height:"auto","max-height":"96px","max-width":"96px",width:"auto"});if(a){c.addClass("userAvatarCrop").addClass("jsTooltip");c.attr("title",WCF.Language.get("wcf.user.avatar.type.custom.crop"))}$("#avatarUpload > dt").prepend(c);WCF.DOMNodeInsertedHandler.execute()},_getInnerErrorElement:function(){var a=$("#avatarUpload > dd > .innerError");if(!a.length){a=$('<small class="innerError"></span>');$("#avatarUpload > dd").append(a)}return a},_getParameters:function(){return{userID:this._userID}},});WCF.User.List=Class.extend({_additionalParameters:{},_cache:{},_className:"",_dialog:null,_dialogTitle:"",_pageCount:0,_pageNo:1,_proxy:null,init:function(c,a,b){this._additionalParameters=b||{};this._cache={};this._className=c;this._dialog=null;this._dialogTitle=a;this._pageCount=0;this._pageNo=1;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)})},open:function(){this._pageNo=1;this._showPage()},_showPage:function(b,c){if(c&&c.activePage){this._pageNo=c.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 a=false;if(this._dialog===null){this._dialog=$('<div id="userList'+this._className.hashCode()+'" />').hide().appendTo(document.body);a=true}this._dialog.empty();this._dialog.html(this._cache[this._pageNo]);if(this._pageCount>1){this._dialog.find(".jsPagination").wcfPages({activePage:this._pageNo,maxPage:this._pageCount}).bind("wcfpagesswitched",$.proxy(this._showPage,this))}if(a){this._dialog.wcfDialog({title:this._dialogTitle})}else{this._dialog.wcfDialog("open").wcfDialog("render")}}else{this._additionalParameters.pageNo=this._pageNo;this._proxy.setOption("data",{actionName:"getGroupedUserList",className:this._className,interfaceName:"wcf\\data\\IGroupedUserListAction",parameters:this._additionalParameters});this._proxy.sendRequest()}},_success:function(b,c,a){if(b.returnValues.pageCount){this._pageCount=b.returnValues.pageCount}this._cache[this._pageNo]=b.returnValues.template;this._showPage()}});WCF.User.ObjectWatch={};WCF.User.ObjectWatch.Subscribe=Class.extend({_buttonSelector:".jsSubscribeButton",_buttons:{},_dialog:null,_notification:null,init:function(){this._buttons={};this._notification=null;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});$(this._buttonSelector).each($.proxy(function(a,b){var d=$(b);var c=d.data("objectID");this._buttons[c]=d.click($.proxy(this._click,this))},this))},_click:function(a){var b=$(a.currentTarget);this._proxy.setOption("data",{actionName:"manageSubscription",className:"wcf\\data\\user\\object\\watch\\UserObjectWatchAction",parameters:{objectID:b.data("objectID"),objectType:b.data("objectType")}});this._proxy.sendRequest()},_success:function(c,e,b){if(c.actionName==="manageSubscription"){if(this._dialog===null){this._dialog=$("<div>"+c.returnValues.template+"</div>").hide().appendTo(document.body);this._dialog.wcfDialog({title:WCF.Language.get("wcf.user.objectWatch.manageSubscription")})}else{this._dialog.html(c.returnValues.template);this._dialog.wcfDialog("open")}this._dialog.find(".formSubmit > .jsButtonSave").data("objectID",c.returnValues.objectID).click($.proxy(this._save,this));var a=this._dialog.find("input[name=enableNotification]").disable();this._dialog.find("input[name=subscribe]").change(function(f){var g=$(f.currentTarget);if(g.val()==1){a.enable()}else{a.disable()}});var d=this._dialog.find("input[name=subscribe]:checked");if(d.length&&d.val()==1){a.enable()}}else{if(c.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()}}},_save:function(b){var d=this._buttons[$(b.currentTarget).data("objectID")];var c=this._dialog.find("input[name=subscribe]:checked").val();var a=(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:a,objectID:d.data("objectID"),objectType:d.data("objectType"),subscribe:c}});this._proxy.sendRequest()}});WCF.User.InlineEditor=WCF.InlineEditor.extend({_permissions:{},_execute:function(b,d){if(!this._validate(b,d)){return false}var c={};var a=$("#"+b);switch(d){case"unban":case"enableAvatar":case"enableSignature":switch(d){case"unban":c.banned=0;break;case"enableAvatar":c.disableAvatar=0;break;case"enableSignature":c.disableSignature=0;break}this._proxy.setOption("data",{actionName:d,className:"wcf\\data\\user\\UserAction",objectIDs:[a.data("objectID")]});this._proxy.sendRequest();break;case"ban":case"disableAvatar":case"disableSignature":if(d=="ban"){c.banned=1}else{c[d]=1}this._showReasonDialog(a.data("objectID"),d);break;case"advanced":window.location=this._getTriggerElement(a).attr("href");break}if($.getLength(c)){this._updateData.push({data:c,elementID:b,})}},_executeReasonAction:function(a,b,d){var c={};c[b+WCF.String.ucfirst("reason")]=d;this._proxy.setOption("data",{actionName:b,className:"wcf\\data\\user\\UserAction",objectIDs:[a],parameters:c});this._proxy.sendRequest()},_getPermission:function(a){if(this._permissions[a]){return this._permissions[a]}return 0},_getTriggerElement:function(a){return a.find(".jsUserInlineEditor")},_setOptions:function(){this._options=[{label:WCF.Language.get("wcf.user.ban"),optionName:"ban"},{label:WCF.Language.get("wcf.user.unban"),optionName:"unban"},{label:WCF.Language.get("wcf.user.disableAvatar"),optionName:"disableAvatar"},{label:WCF.Language.get("wcf.user.enableAvatar"),optionName:"enableAvatar"},{label:WCF.Language.get("wcf.user.disableSignature"),optionName:"disableSignature"},{label:WCF.Language.get("wcf.user.enableSignature"),optionName:"enableSignature"},{optionName:"divider"},{label:WCF.Language.get("wcf.user.edit"),optionName:"advanced"}]},_show:function(c){var a=$(c.currentTarget);var d=a.data("elementID");if(!this._dropdowns[d]){var b=a.next(".dropdownMenu");if(b){this._dropdowns[d]=b;WCF.Dropdown.initDropdown(this._getTriggerElement(this._elements[d]),true)}}return this._super(c)},_showReasonDialog:function(b,c){var d="wcf.user."+c+".reason.description";var a=WCF.Language.get(d);WCF.System.Confirmation.show(WCF.Language.get("wcf.user."+c+".confirmMessage"),$.proxy(function(e){if(e==="confirm"){this._executeReasonAction(b,c,$("#wcfSystemConfirmationContent").find("textarea").val())}},this),{},$("<fieldset><dl><dt>"+WCF.Language.get("wcf.global.reason")+'</dt><dd><textarea cols="40" rows="4" />'+(a!=d?"<small>"+a+"</small>":"")+"</dd></dl></fieldset>"))},_updateState:function(d){this._notification.show();for(var f=0,e=this._updateData.length;f<e;f++){var b=this._updateData[f];var a=$("#"+b.elementID);for(var c in b.data){a.data(c,b.data[c])}}},_validate:function(a,b){var c=$("#"+a);switch(b){case"ban":case"unban":if(!this._getPermission("canBanUser")){return false}if(b=="ban"){return !c.data("banned")}else{return c.data("banned")}break;case"disableAvatar":case"enableAvatar":if(!this._getPermission("canDisableAvatar")){return false}if(b=="disableAvatar"){return !c.data("disableAvatar")}else{return c.data("disableAvatar")}break;case"disableSignature":case"enableSignature":if(!this._getPermission("canDisableSignature")){return false}if(b=="disableSignature"){return !c.data("disableSignature")}else{return c.data("disableSignature")}break;case"advanced":return this._getPermission("canEditUser");break}return false},setPermission:function(a,b){this._permissions[a]=b},setPermissions:function(a){for(var b in a){this.setPermission(b,a[b])}}});
\ No newline at end of file
+(function(S,L){function I(){}function K(d,b){if(d){typeof d=="object"&&(d=[].slice.call(d));for(var a=0,c=d.length;a<c;a++){b.call(d,d[a],a)}}}function C(c,a){var b=Object.prototype.toString.call(a).slice(8,-1);return a!==L&&a!==null&&b===c}function M(a){return C("Function",a)}function ag(a){return C("Array",a)}function P(d){var b=d.split("/"),a=b[b.length-1],c=a.indexOf("?");return c!==-1?a.substring(0,c):a}function Z(a){(a=a||I,a._done)||(a(),a._done=1)}function E(h,b,c,a){var d=typeof h=="object"?h:{test:h,success:!b?!1:ag(b)?b:[b],failure:!c?!1:ag(c)?c:[c],callback:a||I},g=!!d.test;return g&&!!d.success?(d.success.push(d.callback),W.load.apply(null,d.success)):g||!d.failure?a():(d.failure.push(d.callback),W.load.apply(null,d.failure)),W}function J(d){var b={},a,c;if(typeof d=="object"){for(a in d){!d[a]||(b={name:a,url:d[a]})}}else{b={name:P(d),url:d}}return(c=ae[b.name],c&&c.url===b.url)?c:(ae[b.name]=b,b)}function H(b){b=b||ae;for(var a in b){if(b.hasOwnProperty(a)&&b[a].state!==T){return !1}}return !0}function q(a){a.state=B;K(a.onpreload,function(b){b.call()})}function R(a){a.state===L&&(a.state=V,a.onpreload=[],F({url:a.url,type:"cache"},function(){q(a)}))}function A(){var d=arguments,a=d[d.length-1],b=[].slice.call(d,1),c=b[0];return(M(a)||(a=null),ag(d[0]))?(d[0].push(a),W.load.apply(null,d[0]),W):(c?(K(b,function(e){M(e)||!e||R(J(e))}),af(J(d[0]),M(c)?c:function(){W.load.apply(null,b)})):af(J(d[0])),W)}function D(){var c=arguments,a=c[c.length-1],b={};return(M(a)||(a=null),ag(c[0]))?(c[0].push(a),W.load.apply(null,c[0]),W):(K(c,function(d){d!==a&&(d=J(d),b[d.name]=d)}),K(c,function(d){d!==a&&(d=J(d),af(d,function(){H(b)&&Z(a)}))}),W)}function af(b,a){if(a=a||I,b.state===T){a();return}if(b.state===aa){W.ready(b.name,a);return}if(b.state===V){b.onpreload.push(function(){af(b,a)});return}b.state=aa;F(b,function(){b.state=T;a();K(X[b.name],function(c){Z(c)});Q&&H()&&K(X.ALL,function(c){Z(c)})})}function j(b){b=b||"";var a=b.split("?")[0].split(".");return a[a.length-1].toLowerCase()}function F(c,b){function l(e){e=e||S.event;a.onload=a.onreadystatechange=a.onerror=null;b()}function n(e){e=e||S.event;(e.type==="load"||/loaded|complete/.test(a.readyState)&&(!N.documentMode||N.documentMode<9))&&(S.clearTimeout(c.errorTimeout),S.clearTimeout(c.cssTimeout),a.onload=a.onreadystatechange=a.onerror=null,b())}function g(){if(c.state!==T&&c.cssRetries<=20){for(var e=0,h=N.styleSheets.length;e<h;e++){if(N.styleSheets[e].href===a.href){n({type:"load"});return}}c.cssRetries++;c.cssTimeout=S.setTimeout(g,250)}}var a,d,k;b=b||I;d=j(c.url);d==="css"?(a=N.createElement("link"),a.type="text/"+(c.type||"css"),a.rel="stylesheet",a.href=c.url,c.cssRetries=0,c.cssTimeout=S.setTimeout(g,500)):(a=N.createElement("script"),a.type="text/"+(c.type||"javascript"),a.src=c.url);a.onload=a.onreadystatechange=n;a.onerror=l;a.async=!1;a.defer=!1;c.errorTimeout=S.setTimeout(function(){l({type:"timeout"})},7000);k=N.head||N.getElementsByTagName("head")[0];k.insertBefore(a,k.lastChild)}function x(){for(var b,a=N.getElementsByTagName("script"),d=0,c=a.length;d<c;d++){if(b=a[d].getAttribute("data-headjs-load"),!!b){W.load(b);return}}}function z(f,b){var a,d,c;return f===N?(Q?Z(b):ad.push(b),W):(M(f)&&(b=f,f="ALL"),ag(f))?(a={},K(f,function(e){a[e]=ae[e];W.ready(e,function(){H(a)&&Z(b)})}),W):typeof f!="string"||!M(b)?W:(d=ae[f],d&&d.state===T||f==="ALL"&&H()&&Q)?(Z(b),W):(c=X[f],c?c.push(b):c=X[f]=[b],W)}function ab(){if(!N.body){S.clearTimeout(W.readyTimeout);W.readyTimeout=S.setTimeout(ab,50);return}Q||(Q=!0,x(),K(ad,function(a){Z(a)}))}function U(){N.addEventListener?(N.removeEventListener("DOMContentLoaded",U,!1),ab()):N.readyState==="complete"&&(N.detachEvent("onreadystatechange",U),ab())}var N=S.document,ad=[],X={},ae={},G="async" in N.createElement("script")||"MozAppearance" in N.documentElement.style||S.opera,Q,Y=S.head_conf&&S.head_conf.head||"head",W=S[Y]=S[Y]||function(){W.ready.apply(null,arguments)},V=1,B=2,aa=3,T=4,O;if(N.readyState==="complete"){ab()}else{if(N.addEventListener){N.addEventListener("DOMContentLoaded",U,!1),S.addEventListener("load",ab,!1)}else{N.attachEvent("onreadystatechange",U);S.attachEvent("onload",ab);O=!1;try{O=!S.frameElement&&N.documentElement}catch(ac){}O&&O.doScroll&&function m(){if(!Q){try{O.doScroll("left")}catch(a){S.clearTimeout(W.readyTimeout);W.readyTimeout=S.setTimeout(m,50);return}ab()}}()}}W.load=W.js=G?D:A;W.test=E;W.ready=z;W.ready(N,function(){H()&&K(X.ALL,function(a){Z(a)});W.feature&&W.feature("domloaded",!0)})})(window);String.prototype.hashCode=function(){var a;var b=0;if(this.length){for(var d=0,c=this.length;d<c;d++){a=this.charCodeAt(d);b=((b<<5)-b)+a;b=b&b}}return b};function shuffle(d){var c=d.length,b,a;while(0!==c){a=Math.floor(Math.random()*c);c-=1;b=d[c];d[c]=d[a];d[a]=b}return this}(function(){var c=navigator.userAgent.toLowerCase();var b=/(chrome)[ \/]([\w.]+)/.exec(c)||/(webkit)[ \/]([\w.]+)/.exec(c)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(c)||/(msie) ([\w.]+)/.exec(c)||c.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(c)||[];var a={browser:b[1]||"",version:b[2]||"0"};browser={};if(a.browser){browser[a.browser]=true;browser.version=a.version}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));jQuery.browser.smartphone=($("html").css("caption-side")=="bottom");jQuery.browser.editor="redactor";jQuery.browser.ckeditor=false;jQuery.browser.redactor=true;if(jQuery.browser.mozilla&&c.match(/trident/)){jQuery.browser.mozilla=false;jQuery.browser.msie=true}})();(function(b){(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(b)||/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(b.substr(0,4))})(navigator.userAgent||navigator.vendor||window.opera);var WCF={};$.extend(true,{removeArrayValue:function(b,a){return $.grep(b,function(d,c){return a!==d})},wcfEscapeID:function(a){return a.replace(/(:|\.)/g,"\\$1")},wcfIsset:function(a){return !!$("#"+$.wcfEscapeID(a)).length},getLength:function(a){var c=0;for(var b in a){if(a.hasOwnProperty(b)){c++}}return c}});$.fn.extend({getTagName:function(){return(this.length)?this.get(0).tagName.toLowerCase():""},getDimensions:function(b){var c=css={};var a=false;if(this.is(":hidden")){css=WCF.getInlineCSS(this);a=true;this.css({display:"block",visibility:"hidden"})}switch(b){case"inner":c={height:this.innerHeight(),width:this.innerWidth()};break;case"outer":c={height:this.outerHeight(),width:this.outerWidth()};break;default:c={height:this.height(),width:this.width()};break}if(a){WCF.revertInlineCSS(this,css,["display","visibility"])}return c},getOffsets:function(b){var c=css={};var a=false;if(this.is(":hidden")){css=WCF.getInlineCSS(this);a=true;this.css({display:"block",visibility:"hidden"})}switch(b){case"offset":c=this.offset();break;case"position":default:c=this.position();break}if(a){WCF.revertInlineCSS(this,css,["display","visibility"])}return c},makePositioned:function(a,b){if(a!="absolute"&&a!="fixed"){a="absolute"}var c=this.getOffsets("position");this.css({position:a,left:c.left,margin:0,top:c.top});if(b){this.remove().appentTo("body")}return this},disable:function(){return this.attr("disabled","disabled")},enable:function(){return this.removeAttr("disabled")},wcfIdentify:function(){if(!this.attr("id")){this.attr("id",WCF.getRandomID())}return this.attr("id")},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 c=0;var b=this.get(0);if(document.selection){this.focus();var a=document.selection.createRange();a.moveStart("character",-this.val().length);c=a.text.length}else{if(b.selectionStart||b.selectionStart=="0"){c=parseInt(b.selectionStart)}}return c},setCaret:function(b){if(this.is("input")){if(this.attr("type")!="text"&&this.attr("type")!="password"){return false}}else{if(!this.is("textarea")){return false}}var c=this.get(0);this.focus();if(document.selection){var a=document.selection.createRange();a.moveStart("character",b);a.moveEnd("character",0);a.select()}else{if(c.selectionStart||c.selectionStart=="0"){c.selectionStart=b;c.selectionEnd=b}}return true},wcfDropIn:function(b,c,a){if(!b){b="up"}if(!a||!parseInt(a)){a=200}return this.show(WCF.getEffect(this,"drop"),{direction:b},a,c)},wcfDropOut:function(b,c,a){if(!b){b="down"}if(!a||!parseInt(a)){a=200}return this.hide(WCF.getEffect(this,"drop"),{direction:b},a,c)},wcfBlindIn:function(b,c,a){if(!b){b="vertical"}if(!a||!parseInt(a)){a=200}return this.show(WCF.getEffect(this,"blind"),{direction:b},a,c)},wcfBlindOut:function(b,c,a){if(!b){b="vertical"}if(!a||!parseInt(a)){a=200}return this.hide(WCF.getEffect(this,"blind"),{direction:b},a,c)},wcfHighlight:function(a,b){return this.effect("highlight",a,600,b)},wcfFadeIn:function(b,a){if(!a||!parseInt(a)){a=200}return this.show(WCF.getEffect(this,"fade"),{},a,b)},wcfFadeOut:function(b,a){if(!a||!parseInt(a)){a=200}return this.hide(WCF.getEffect(this,"fade"),{},a,b)}});$.extend(WCF,{activeDialogs:0,_idCounter:0,getRandomID:function(){var a="";do{a="wcf"+this._idCounter++}while($.wcfIsset(a));return a},inArray:function(b,a){return($.inArray(b,a)!=-1)},getEffect:function(a,b){if(a.is("tr")){return"highlight"}return b},getInlineCSS:function(b){var c={};var a=b.attr("style");if(!a){return{}}a=a.split(";");for(var f=0,d=a.length;f<d;f++){var e=$.trim(a[f]);if(e==""){continue}e=e.split(":");c[$.trim(e[0])]=$.trim(e[1])}return c},revertInlineCSS:function(a,c,e){for(var f=0,d=e.length;f<d;f++){var b=e[f];if(c[b]){a.css(b,c[b])}else{a.css(b,"")}}}});WCF.Browser={_isChrome:null,isChrome:function(){if(this._isChrome===null){this._isChrome=false;if(/chrom(e|ium)/.test(navigator.userAgent.toLowerCase())){this._isChrome=true}}return this._isChrome}};WCF.Dropdown={_callbacks:{},_didInit:false,_dropdowns:{},_menuContainer:null,_menus:{},init:function(){if(this._menuContainer===null){this._menuContainer=$('<div id="dropdownMenuContainer" />').appendTo(document.body)}var a=this;$(".dropdownToggle:not(.jsDropdownEnabled)").each(function(b,c){a.initDropdown($(c),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))}},_dialogScroll:function(b){var a=$(b.currentTarget);a.find(".dropdown.dropdownOpen").each(function(d,f){var i=$(f);var e=i.wcfIdentify();var g=i.offset();var c=a.offset();var h=$(f).height()/2;if(g.top+h<=c.top){WCF.Dropdown.toggleDropdown(e)}else{if(g.top>=c.top+a.height()){WCF.Dropdown.toggleDropdown(e)}else{if(g.left<=c.left){WCF.Dropdown.toggleDropdown(e)}else{if(g.left>=c.left+a.width()){WCF.Dropdown.toggleDropdown(e)}else{WCF.Dropdown.setAlignmentByID(i.wcfIdentify())}}}}})},_scroll:function(b){for(var a in this._dropdowns){var c=this._dropdowns[a];if(c.data("isOverlayDropdownButton")&&c.hasClass("dropdownOpen")){this.setAlignmentByID(a)}}},initDropdown:function(b,d){if(b.hasClass("jsDropdownEnabled")||b.data("target")){return}var e=b.parents(".dropdown");if(!e.length){console.debug("[WCF.Dropdown] Invalid dropdown passed, button '"+b.wcfIdentify()+"' does not have a parent with .dropdown, aborting.");return}var a=b.next(".dropdownMenu");if(!a.length){console.debug("[WCF.Dropdown] Invalid dropdown passed, dropdown '"+e.wcfIdentify()+"' does not have a dropdown menu, aborting.");return}a.detach().appendTo(this._menuContainer);var c=e.wcfIdentify();if(!this._dropdowns[c]){b.addClass("jsDropdownEnabled").click($.proxy(this._toggle,this));this._dropdowns[c]=e;this._menus[c]=a}b.data("target",c);if(d){b.trigger("click")}},removeDropdown:function(a){if(this._menus[a]){$(this._menus[a]).remove();delete this._menus[a];delete this._dropdowns[a]}},initDropdownFragment:function(c,a){var b=c.wcfIdentify();if(this._dropdowns[b]){console.debug("[WCF.Dropdown] Cannot register dropdown identified by '"+b+"' as a fragement.");return}this._dropdowns[b]=c;this._menus[b]=a.detach().appendTo(this._menuContainer)},registerCallback:function(a,b){if(!$.isFunction(b)){console.debug("[WCF.Dropdown] Callback for '"+a+"' is invalid");return false}if(!this._callbacks[a]){this._callbacks[a]=[]}this._callbacks[a].push(b)},_toggle:function(g,e){var b=(g===null)?e:$(g.currentTarget).data("target");var a=this._dropdowns[b];if(a&&a.data("isOverlayDropdownButton")===undefined){var d=a.parents(".dialogContent");a.data("isOverlayDropdownButton",d.length>0);if(d.length){d.on("scroll",this._dialogScroll)}}for(var f in this._dropdowns){var h=this._dropdowns[f];var c=this._menus[f];if(h.hasClass("dropdownOpen")){h.removeClass("dropdownOpen");c.removeClass("dropdownOpen");this._notifyCallbacks(f,"close")}else{if(f===b){h.addClass("dropdownOpen");c.addClass("dropdownOpen");this._notifyCallbacks(f,"open");this.setAlignment(h,c)}}}if(g!==null){g.stopPropagation();return false}},toggleDropdown:function(a){this._toggle(null,a)},getDropdown:function(a){if(this._dropdowns[a]){return this._dropdowns[a]}return null},getDropdownMenu:function(a){if(this._menus[a]){return this._menus[a]}return null},setAlignmentByID:function(a){var c=this.getDropdown(a);if(c===null){console.debug("[WCF.Dropdown] Unable to find dropdown identified by '"+a+"'")}var b=this.getDropdownMenu(a);if(b===null){console.debug("[WCF.Dropdown] Unable to find dropdown menu identified by '"+a+"'")}this.setAlignment(c,b)},setAlignment:function(o,f){if(!f.data("isInitialized")){f.data("isInitialized",true).css({left:0,top:0})}var c=o.getDimensions("outer");var j=o.getOffsets("offset");var k=f.getDimensions("outer");var d=$(window).width();var a=o.find(".dropdownToggle");if(a.hasClass("dropdownCaptionTextarea")){c=a.getDimensions("outer")}var h="left";if((j.left+k.width)>d){h="right"}var b="auto";var n="auto";if(h==="left"){f.removeClass("dropdownArrowRight");b=j.left}else{f.addClass("dropdownArrowRight");n=(d-(j.left+c.width))}if(WCF.Language.get("wcf.global.pageDirection")=="rtl"){var l=b;var g=n;if(b=="auto"){f.removeClass("dropdownArrowRight")}else{n=d-(j.left+c.width);b="auto";if(n+k.width>d){b=l;n=g;f.addClass("dropdownArrowRight")}}}if(b=="auto"){n+="px"}else{b+="px"}var m=true;if(f.hasClass("dropdownOpen")){m=false;f.removeClass("dropdownOpen")}var i="auto";var e=j.top+c.height+7;if(e+k.height>$(window).height()+$(document).scrollTop()){i=$(window).height()-j.top+10;e="auto";f.addClass("dropdownArrowBottom")}else{f.removeClass("dropdownArrowBottom")}if(!m){f.addClass("dropdownOpen")}f.css({bottom:i,left:b,right:n,top:e})},_closeAll:function(){for(var a in this._dropdowns){var b=this._dropdowns[a];if(b.hasClass("dropdownOpen")){b.removeClass("dropdownOpen");this._menus[a].removeClass("dropdownOpen");this._notifyCallbacks(a,"close")}}},close:function(a){if(!this._dropdowns[a]){return}this._dropdowns[a].removeClass("dropdownMenu");this._menus[a].removeClass("dropdownMenu")},_notifyCallbacks:function(a,b){if(!this._callbacks[a]){return}for(var d=0,c=this._callbacks[a].length;d<c;d++){this._callbacks[a][d](a,b)}}};WCF.Clipboard={_actionProxy:null,_actionObjects:{},_containers:null,_containerData:{},_hasMarkedItems:false,_markedObjectIDs:{},_page:"",_pageObjectID:0,_proxy:null,_trackedElements:{},init:function(d,b,e,c){this._page=d;this._actionObjects=e||{};this._hasMarkedItems=(b>0);this._pageObjectID=parseInt(c)||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});this._containers=$(".jsClipboardContainer").each($.proxy(function(g,f){this._initContainer(f)},this));if(this._hasMarkedItems&&this._containers.length){this._loadMarkedItems()}var a=this;WCF.DOMNodeInsertedHandler.addCallback("WCF.Clipboard",function(){a._containers=$(".jsClipboardContainer").each($.proxy(function(g,f){a._initContainer(f)},a))})},_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})},reload:function(){if(this._containers===null){return}this._loadMarkedItems()},_loadMarkedItemsSuccess:function(d,f,c){this._resetMarkings();for(var a in d.markedItems){if(!this._markedObjectIDs[a]){this._markedObjectIDs[a]=[]}var b=d.markedItems[a];for(var e in b){this._markedObjectIDs[a].push(b[e])}this._containers.each($.proxy(function(h,g){var i=$(g);if(i.data("type")!=a){return true}i.find("input.jsClipboardItem").each($.proxy(function(l,k){var j=$(k);if(WCF.inArray(j.data("objectID"),this._markedObjectIDs[a])){j.prop("checked",true);j.parents(".jsClipboardObject").addClass("jsMarked")}},this));i.find("input.jsClipboardMarkAll").each(function(l,k){var j=true;i.find("input.jsClipboardItem").each(function(n,o){var m=$(o);if(!m.prop("checked")){j=false}});if(j){$(k).prop("checked",true)}})},this))}this._success(d,f,c)},_resetMarkings:function(){this._containers.each($.proxy(function(b,a){var c=$(a);this._markedObjectIDs[c.data("type")]=[];c.find("input.jsClipboardItem, input.jsClipboardMarkAll").prop("checked",false);c.find(".jsClipboardObject").removeClass("jsMarked")},this))},_initContainer:function(a){var c=$(a);var b=c.wcfIdentify();if(!this._trackedElements[b]){c.find(".jsClipboardMarkAll").data("hasContainer",b).click($.proxy(this._markAll,this));this._markedObjectIDs[c.data("type")]=[];this._containerData[c.data("type")]={};$.each(c.data(),$.proxy(function(d,e){if(d.match(/^type(.+)/)){this._containerData[c.data("type")][WCF.String.lcfirst(d.replace(/^type/,""))]=e}},this));this._trackedElements[b]=[]}c.find("input.jsClipboardItem").each($.proxy(function(f,e){var g=$(e);var d=g.wcfIdentify();if(!WCF.inArray(d,this._trackedElements[b])){this._trackedElements[b].push(d);g.data("hasContainer",b).click($.proxy(this._click,this))}},this))},_click:function(f){var b=$(f.target);var e=b.data("objectID");var h=(b.prop("checked"))?true:false;var d=[e];if(b.data("hasContainer")){var g=$("#"+b.data("hasContainer"));var a=g.data("type")}else{var a=b.data("type")}if(h){this._markedObjectIDs[a].push(e);b.parents(".jsClipboardObject").addClass("jsMarked")}else{this._markedObjectIDs[a]=$.removeArrayValue(this._markedObjectIDs[a],e);b.parents(".jsClipboardObject").removeClass("jsMarked")}if(b.data("hasContainer")){var c=true;g.find("input.jsClipboardItem").each(function(j,i){var k=$(i);if(!k.prop("checked")){c=false}});g.find(".jsClipboardMarkAll").each(function(i,j){if(c){$(j).prop("checked",true)}else{$(j).prop("checked",false)}})}this._saveState(a,d,h)},_markAll:function(d){var b=$(d.target);var c=[];var f=true;if(b.is("input")){f=b.prop("checked")}if(b.data("hasContainer")){var e=$("#"+b.data("hasContainer"));var a=e.data("type")}else{var a=b.data("type")}if(b.data("hasContainer")){e.find("input.jsClipboardItem").each($.proxy(function(h,g){var j=$(g);var i=j.data("objectID");if(f){if(!j.prop("checked")){j.prop("checked",true);this._markedObjectIDs[a].push(i);c.push(i)}}else{if(j.prop("checked")){j.prop("checked",false);this._markedObjectIDs[a]=$.removeArrayValue(this._markedObjectIDs[a],i);c.push(i)}}},this));if(f){e.find(".jsClipboardObject").addClass("jsMarked")}else{e.find(".jsClipboardObject").removeClass("jsMarked")}}this._saveState(a,c,f)},_saveState:function(b,c,a){this._proxy.setOption("data",{action:(a)?"mark":"unmark",containerData:this._containerData,objectIDs:c,pageClassName:this._page,pageObjectID:this._pageObjectID,type:b});this._proxy.sendRequest()},_success:function(data,textStatus,jqXHR){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()});if(!data.items){return}for(var $typeName in data.items){if(!$containers[$typeName]){continue}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);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);$listItem.click($.proxy(this._executeAction,this))}$('<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));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)}},_closeLists:function(){$(".jsClipboardEditor ul").removeClass("dropdownOpen")},_executeAction:function(e){var c=$(e.currentTarget);var d=c.data("url");if(d){window.location.href=d}if(c.data("parameters").className&&c.data("parameters").actionName){if(c.data("parameters").actionName==="unmarkAll"||c.data("parameters").objectIDs){var b=c.data("internalData")["confirmMessage"];if(b){var a=c.data("internalData")["template"];if(a){a=$(a)}WCF.System.Confirmation.show(b,$.proxy(function(g){if(g==="confirm"){var f={};if(a&&a.length){$("#wcfSystemConfirmationContent").find("input, select, textarea").each(function(i,j){var h=$(j);f[h.prop("name")]=h.val()})}this._executeAJAXActions(c,f)}},this),"",a)}else{this._executeAJAXActions(c,{})}}}c.data("container").trigger("clipboardAction",[c.data("type"),c.data("actionName"),c.data("parameters")])},_executeAJAXActions:function(d,e){e=e||{};var a=[];if(d.data("parameters").actionName!=="unmarkAll"){$.each(d.data("parameters").objectIDs,function(g,h){a.push(parseInt(h))})}var b={data:e,containerData:this._containerData[d.data("type")]};var f=d.data("internalData")["parameters"];if(f!==undefined){for(var c in f){b[c]=f[c]}}new WCF.Action.Proxy({autoSend:true,data:{actionName:d.data("parameters").actionName,className:d.data("parameters").className,objectIDs:a,parameters:b},success:$.proxy(function(g){if(d.data("parameters").actionName!=="unmarkAll"){d.data("container").trigger("clipboardActionResponse",[g,d.data("type"),d.data("actionName"),d.data("parameters")])}this._loadMarkedItems()},this)});if(this._actionObjects[d.data("objectType")]&&this._actionObjects[d.data("objectType")][d.data("parameters").actionName]){this._actionObjects[d.data("objectType")][d.data("parameters").actionName].triggerEffect(a)}},sendRequest:function(b){var a=$(b);this._actionProxy.setOption("data",{parameters:a.data("parameters"),typeName:a.data("type")});this._actionProxy.sendRequest()}};WCF.PeriodicalExecuter=Class.extend({_callback:null,_delay:0,_intervalID:null,_isExecuting:false,init:function(b,a){if(!$.isFunction(b)){console.debug("[WCF.PeriodicalExecuter] Given callback is invalid, aborting.");return}this._callback=b;this._interval=a;this.resume()},_execute:function(){if(!this._isExecuting){try{this._isExecuting=true;this._callback(this);this._isExecuting=false}catch(a){this._isExecuting=false;throw a}}},stop:function(){if(!this._intervalID){return}clearInterval(this._intervalID)},resume:function(){if(this._intervalID){this.stop()}this._intervalID=setInterval($.proxy(this._execute,this),this._interval)}});WCF.LoadingOverlayHandler={_activeRequests:0,_loadingOverlay:null,_pending:null,show:function(){if(this._loadingOverlay===null){this._loadingOverlay=$('<div class="spinner"><span class="icon icon48 icon-spinner" /> <span>'+WCF.Language.get("wcf.global.loading")+"</span></div>").appendTo($("body"));var b=this._loadingOverlay.outerWidth();if(b<70){b=70}this._loadingOverlay.css({marginLeft:Math.ceil(-1*b/2),width:b}).hide()}this._activeRequests++;if(this._activeRequests==1){if(this._pending===null){var a=this;this._pending=new WCF.PeriodicalExecuter(function(c){if(a._activeRequests){a._loadingOverlay.stop(true,true).fadeIn(100)}c.stop();a._pending=null},250)}}},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)}},updateIcon:function(b,c){var a=(c===undefined||c?"addClass":"removeClass");b.find(".icon")[a]("icon-spinner");if(b.hasClass("icon")){b[a]("icon-spinner")}}};WCF.Action={};WCF.Action.Proxy=Class.extend({_showLoadingOverlayOnce:false,_suppressErrors:false,_lastRequest:null,init:function(b){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},b);this.confirmationDialog=null;this.loading=null;this._showLoadingOverlayOnce=false;this._suppressErrors=(this.options.suppressErrors===true);if(this.options.autoSend){this.sendRequest()}var a=this;$(window).on("beforeunload",function(){a._suppressErrors=true})},sendRequest:function(a){this._init();if(a||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},abortPrevious:function(){if(this._lastRequest!==null){this._lastRequest.abort();this._lastRequest=null}},showLoadingOverlayOnce:function(){this._showLoadingOverlayOnce=true},suppressErrors:function(){this._suppressErrors=true},_init:function(){if($.isFunction(this.options.init)){this.options.init(this)}if(this.options.showLoadingOverlay||this._showLoadingOverlayOnce){WCF.LoadingOverlayHandler.show()}},_failure:function(d,i,h){if(i=="abort"){if($.isFunction(this.options.aborted)){this.options.aborted(d)}return}try{var c=$.parseJSON(d.responseText);var f=true;if($.isFunction(this.options.failure)){f=this.options.failure(c,d,i,h)}if(!this._suppressErrors&&f!==false){var a="";if(c.stacktrace){a="<br /><p>Stacktrace:</p><p>"+c.stacktrace+"</p>"}else{if(c.exceptionID){a="<br /><p>Exception ID: <code>"+c.exceptionID+"</code></p>"}}$('<div class="ajaxDebugMessage"><p>'+c.message+"</p>"+a+"</div>").wcfDialog({title:WCF.Language.get("wcf.global.error.title")})}}catch(g){var f=true;if($.isFunction(this.options.failure)){f=this.options.failure(null,d,i,h)}if(!this._suppressErrors&&f!==false){var b=(i==="timeout")?WCF.Language.get("wcf.global.error.timeout"):d.responseText;if(b&&b!="undefined"){$('<div class="ajaxDebugMessage"><p>'+b+"</p></div>").wcfDialog({title:WCF.Language.get("wcf.global.error.title")})}}}this._after()},_success:function(b,c,a){if($.isFunction(this.options.success)){if(b&&b.returnValues&&b.returnValues.template!==undefined){b.returnValues.template=$.trim(b.returnValues.template)}this.options.success(b,c,a)}this._after()},_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();$("a[href*=#]").each(function(c,e){var b=$(e);if(b.prop("href").indexOf("AJAXProxy")!=-1){var d=b.prop("href").substr(b.prop("href").indexOf("#"));var a=document.location.toString().replace(/#.*/,"");b.prop("href",a+d)}})},setOption:function(a,b){this.options[a]=b}});WCF.Action.SimpleProxy=Class.extend({init:function(a,b){this.options=$.extend(true,{action:"",className:"",elements:null,eventName:"click"},a);this.callbacks=$.extend(true,{after:null,failure:null,init:null,success:null},b);if(!this.options.elements){return}this.proxy=new WCF.Action.Proxy(this.callbacks);this.options.elements.each($.proxy(function(c,d){$(d).bind(this.options.eventName,$.proxy(this._handleEvent,this))},this))},_handleEvent:function(a){this.proxy.setOption("data",{actionName:this.options.action,className:this.options.className,objectIDs:[$(a.target).data("objectID")]});this.proxy.sendRequest()}});WCF.Action.Delete=Class.extend({_buttonSelector:"",_className:"",_containerSelector:"",_containers:[],init:function(c,a,b){this._containerSelector=a;this._className=c;this._buttonSelector=(b)?b:".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))},_initElements:function(){var a=this;$(this._containerSelector).each(function(c,b){var e=$(b);var d=e.wcfIdentify();if(!WCF.inArray(d,a._containers)){a._containers.push(d);e.find(a._buttonSelector).click($.proxy(a._click,a))}})},_click:function(b){var a=$(b.currentTarget);b.preventDefault();if(a.data("confirmMessage")){WCF.System.Confirmation.show(a.data("confirmMessage"),$.proxy(this._execute,this),{target:a})}else{WCF.LoadingOverlayHandler.updateIcon(a);this._sendRequest(a)}},_didTriggerEffect:function(a){},_execute:function(b,a){if(b==="cancel"){return}WCF.LoadingOverlayHandler.updateIcon(a.target);this._sendRequest(a.target)},_sendRequest:function(a){this.proxy.setOption("data",{actionName:"delete",className:this._className,interfaceName:"wcf\\data\\IDeleteAction",objectIDs:[$(a).data("objectID")]});this.proxy.sendRequest()},_success:function(b,c,a){this.triggerEffect(b.objectIDs)},triggerEffect:function(c){for(var a in this._containers){var d=$("#"+this._containers[a]);if(WCF.inArray(d.find(this._buttonSelector).data("objectID"),c)){var b=this;d.wcfBlindOut("up",function(){$(this).remove();b._containers.splice(b._containers.indexOf($(this).wcfIdentify()),1);b._didTriggerEffect($(this))})}}}});WCF.Action.NestedDelete=WCF.Action.Delete.extend({triggerEffect:function(c){for(var a in this._containers){var d=$("#"+this._containers[a]);if(WCF.inArray(d.find(this._buttonSelector).data("objectID"),c)){if(d.has("ol").has("li").length){if(d.is(":only-child")){d.parent().replaceWith(d.find("> ol"))}else{d.replaceWith(d.find("> ol > li"))}this._containers.splice(this._containers.indexOf(d.wcfIdentify()),1);this._didTriggerEffect(d)}else{var b=this;d.wcfBlindOut("up",function(){$(this).remove();b._containers.splice(b._containers.indexOf($(this).wcfIdentify()),1);b._didTriggerEffect($(this))})}}}}});WCF.Action.Toggle=Class.extend({_buttonSelector:".jsToggleButton",_className:"",_containerSelector:"",_containers:[],init:function(d,a,c){this._containerSelector=a;this._className=d;this._buttonSelector=(c)?c:".jsToggleButton";this._containers=[];var b={success:$.proxy(this._success,this)};this.proxy=new WCF.Action.Proxy(b);this._initElements();WCF.DOMNodeInsertedHandler.addCallback("WCF.Action.Toggle"+this._className.hashCode(),$.proxy(this._initElements,this))},_initElements:function(){$(this._containerSelector).each($.proxy(function(b,a){var d=$(a);var c=d.wcfIdentify();if(!WCF.inArray(c,this._containers)){this._containers.push(c);d.find(this._buttonSelector).click($.proxy(this._click,this))}},this))},_click:function(b){var a=$(b.currentTarget);b.preventDefault();if(a.data("confirmMessage")){WCF.System.Confirmation.show(a.data("confirmMessage"),$.proxy(this._execute,this),{target:a})}else{WCF.LoadingOverlayHandler.updateIcon(a);this._sendRequest(a)}},_execute:function(b,a){if(b==="cancel"){return}WCF.LoadingOverlayHandler.updateIcon(a.target);this._sendRequest(a.target)},_sendRequest:function(a){this.proxy.setOption("data",{actionName:"toggle",className:this._className,interfaceName:"wcf\\data\\IToggleAction",objectIDs:[$(a).data("objectID")]});this.proxy.sendRequest()},_success:function(b,c,a){this.triggerEffect(b.objectIDs)},triggerEffect:function(b){for(var a in this._containers){var d=$("#"+this._containers[a]);var c=d.find(this._buttonSelector);if(WCF.inArray(c.data("objectID"),b)){d.wcfHighlight();this._toggleButton(d,c)}}},_toggleButton:function(b,a){WCF.LoadingOverlayHandler.updateIcon(a,false);if(a.hasClass("icon-check-empty")){a.removeClass("icon-check-empty").addClass("icon-check");$newTitle=(a.data("disableTitle")?a.data("disableTitle"):WCF.Language.get("wcf.global.button.disable"));a.attr("title",$newTitle)}else{a.removeClass("icon-check").addClass("icon-check-empty");$newTitle=(a.data("enableTitle")?a.data("enableTitle"):WCF.Language.get("wcf.global.button.enable"));a.attr("title",$newTitle)}b.toggleClass("disabled")}});WCF.Action.Scroll=Class.extend({_callback:null,_reference:null,_target:null,_threshold:0,init:function(b,d,a,c){this._threshold=parseInt(b);if(this._threshold===0){console.debug("[WCF.Action.Scroll] Given threshold is invalid, aborting.");return}if($.isFunction(d)){this._callback=d}if(this._callback===null){console.debug("[WCF.Action.Scroll] Given callback is invalid, aborting.");return}this._reference=$((a)?a:window);this._target=$((c)?c:document);this.start();this._scroll()},_scroll:function(){var a=this._target.height();var b=this._reference.scrollTop();var c=this._reference.height();if((a-(c+b))<this._threshold){this._callback(this)}},start:function(){this._reference.on("scroll",$.proxy(this._scroll,this))},stop:function(){this._reference.off("scroll")}});WCF.Date={};WCF.Date.Picker={_dateFormat:"yy-mm-dd",_timeFormat:"g:ia",init:function(){var a=$.timepicker.log;$.timepicker.log=function(b){if(b.indexOf("Error parsing the date/time string: Unexpected literal at position")==-1&&b.indexOf("Error parsing the date/time string: Unknown name at position")==-1){a(b)}};this._convertDateFormat();this._initDatePicker();WCF.DOMNodeInsertedHandler.addCallback("WCF.Date.Picker",$.proxy(this._initDatePicker,this))},_convertDateFormat:function(){var a={a:"tt",A:"TT",g:"h",G:"H",h:"hh",H:"HH",i:"mm",s:"ss",u:"l",d:"dd",D:"D",j:"d",l:"DD",z:"o",S:"",F:"MM",m:"mm",M:"M",n:"m",o:"yy",Y:"yy",y:"y",U:"@"};this._dateFormat=WCF.Language.get("wcf.date.dateFormat").replace(/([^dDjlzSFmMnoYyU\\]*(?:\\.[^dDjlzSFmMnoYyU\\]*)*)([dDjlzSFmMnoYyU])/g,function(c,f,e,g,b){for(var d in a){if(e==d){e=a[d]}}return f+e});this._timeFormat=WCF.Language.get("wcf.date.timeFormat").replace(/([^aAgGhHisu\\]*(?:\\.[^aAgGhHisu\\]*)*)([aAgGhHisu])/g,function(c,f,e,g,b){for(var d in a){if(e==d){e=a[d]}}return f+e})},_initDatePicker:function(){$("input[type=date]:not(.jsDatePicker), input[type=datetime]:not(.jsDatePicker)").each($.proxy(function(d,g){var f=$(g);var b=f.prop("name");var j=f.val();var a=f.attr("type")=="datetime";f.prop("type","text").addClass("jsDatePicker");if(f.data("placeholder")){f.attr("placeholder",f.data("placeholder"))}f.removeAttr("name");f.before('<input type="hidden" id="'+f.wcfIdentify()+'DatePicker" name="'+b+'" value="'+j+'" />');var c=f.attr("max")?new Date(f.attr("max").replace(" ","T")):null;var k=f.attr("min")?new Date(f.attr("min").replace(" ","T")):null;$options={altField:"#"+f.wcfIdentify()+"DatePicker",altFormat:"yy-mm-dd",beforeShow:function(n,m){setTimeout(function(){m.dpDiv.position({my:"left top",at:"left bottom",collision:"none",of:n})},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:c,minDate:k,monthNames:WCF.Language.get("__months"),monthNamesShort:WCF.Language.get("__monthsShort"),showButtonPanel:false,onClose:function(n,m){if(n==""){$(m.settings.altField).val(n)}},showOtherMonths:true,yearRange:(f.hasClass("birthday")?"-100:+0":"1900:2038")};if(a){if(/[0-9]{2}:[0-9]{2}:[0-9]{2}$/.test(j)){j=j.replace(/:[0-9]{2}$/,"");f.val(j)}j=j.replace(" ","T");if(f.data("ignoreTimezone")){var i=new Date().getTimezoneOffset();var l=(i>0)?"-":"+";i=Math.abs(i);var e=(Math.floor(i/60)).toString();var h=(i%60).toString();l+=(e.length==2)?e:"0"+e;l+=":";l+=(h.length==2)?h:"0"+h;j=j.replace(/[+-][0-9]{2}:[0-9]{2}$/,l)}$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:(f.hasClass("birthday")?"-100:+0":"1900:2038")})}if(a){f.datetimepicker($options)}else{f.datepicker($options)}if(j){if(!a){j=new Date(j);j.setMinutes(j.getMinutes()+j.getTimezoneOffset())}f.datepicker("setDate",j)}f.datepicker("widget").hide()},this))}};WCF.Date.Util={gmdate:function(a){var b=(a)?a:new Date();return Math.round(Date.UTC(b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDay(),b.getUTCHours(),b.getUTCMinutes(),b.getUTCSeconds())/1000)},getTimezoneDate:function(b,c){var d=new Date(b);var a=d.getTimezoneOffset()*60000;return new Date((b+a+c))}};WCF.Date.Time=Class.extend({_date:0,_elements:null,_offset:null,_timestamp:0,init:function(){this._elements=$("time.datetime");this._offset=null;this._timestamp=0;this._refresh();new WCF.PeriodicalExecuter($.proxy(this._refresh,this),60000);WCF.DOMNodeInsertedHandler.addCallback("WCF.Date.Time",$.proxy(this._domNodeInserted,this))},_domNodeInserted:function(){this._elements=$("time.datetime");this._refresh()},_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))},_refreshElement:function(i,f){var n=$(f);if(!n.attr("title")){n.attr("title",n.text())}var d=n.data("timestamp")+this._offset;var b=n.data("date");var c=n.data("time");var o=n.data("offset");if(n.data("isFutureDate")){return}if(d>=this._timestamp||this._timestamp<(d+60)){n.text(WCF.Language.get("wcf.date.relative.now"))}else{if(this._timestamp<(d+3540)){var m=Math.max(Math.round((this._timestamp-d)/60),1);n.text(WCF.Language.get("wcf.date.relative.minutes",{minutes:m}))}else{if(this._timestamp<(d+86400)){var k=Math.round((this._timestamp-d)/3600);n.text(WCF.Language.get("wcf.date.relative.hours",{hours:k}))}else{if(this._timestamp<(d+518400)){var j=new Date(this._date.getFullYear(),this._date.getMonth(),this._date.getDate());var l=Math.ceil((j/1000-d)/86400);var g=WCF.Date.Util.getTimezoneDate((d*1000),o*1000);var h=g.getDay();var e=WCF.Language.get("__days")[h];n.text(WCF.Language.get("wcf.date.relative.pastDays",{days:l,day:e,time:c}))}else{var a=WCF.Language.get("wcf.date.shortDateTimeFormat");n.text(a.replace(/\%date\%/,b).replace(/\%time\%/,c))}}}}}});WCF.Dictionary=Class.extend({_variables:{},init:function(){this._variables={}},add:function(a,b){this._variables[a]=b},addObject:function(a){for(var b in a){this.add(b,a[b])}},addDictionary:function(a){a.each($.proxy(function(b){this.add(b.key,b.value)},this))},get:function(a){if(this.isset(a)){return this._variables[a]}return null},isset:function(a){return this._variables.hasOwnProperty(a)},remove:function(a){delete this._variables[a]},each:function(d){if(!$.isFunction(d)){return}for(var c in this._variables){var a=this._variables[c];var b={key:c,value:a};d(b)}},count:function(){return $.getLength(this._variables)},isEmpty:function(){return !this.count()}});WCF.Language={_variables:new WCF.Dictionary(),add:function(a,b){this._variables.add(a,b)},addObject:function(a){this._variables.addObject(a)},get:function(a,b){if(b==null){var b={}}var c=this._variables.get(a);if(c===null){return a}else{if(typeof c==="string"){this.add(a,new WCF.Template(c));return this.get(a,b)}else{if(typeof c.fetch==="function"){c=c.fetch(b)}}}return c}};WCF.MultipleLanguageInput=Class.extend({_availableLanguages:{},_button:null,_didInit:false,_element:null,_insertedDataAfterInit:false,_isEnabled:false,_forceSelection:false,_languageID:0,_list:null,_values:null,init:function(c,b,d,f){this._button=null;this._element=$("#"+$.wcfEscapeID(c));this._forceSelection=b;this._values=d;this._availableLanguages=f;if($.getLength(this._values)){for(var e in this._values){this._values[e]=WCF.String.unescapeHTML(this._values[e])}}this._languageID=LANGUAGE_ID;if(this._element.length==0){console.debug("[WCF.MultipleLanguageInput] element id '"+c+"' is unknown");return}var a=($.getLength(this._values)>0)?true:false;this._insertedDataAfterInit=a;this._prepareElement(a);this._element.parents("form").submit($.proxy(this._submit,this));this._didInit=true},_prepareElement:function(a){this._element.wrap('<div class="dropdown preInput" />');var c=this._element.parent();this._button=$('<p class="button dropdownToggle"><span>'+WCF.Language.get("wcf.global.button.disabledI18n")+"</span></p>").prependTo(c);this._list=$('<ul class="dropdownMenu"></ul>').insertAfter(this._button);if(this._button.nextAll("textarea").length){this._button.addClass("dropdownCaptionTextarea")}else{this._button.addClass("dropdownCaption")}for(var b in this._availableLanguages){$("<li><span>"+this._availableLanguages[b]+"</span></li>").data("languageID",b).click($.proxy(this._changeLanguage,this)).appendTo(this._list)}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,a);if(a||this._forceSelection){this._isEnabled=true;this._list.children("li").each($.proxy(function(d,f){var e=$(f);if(e.data("languageID")==this._languageID){e.trigger("click")}},this))}WCF.Dropdown.registerCallback(c.wcfIdentify(),$.proxy(this._handleAction,this))},_handleAction:function(a,b){if(b==="open"){this._enable()}else{this._closeSelection()}},_enable:function(a){if(!this._isEnabled){var b=(this._button.is("p"))?this._button.children("span:eq(0)"):this._button;b.addClass("active");this._isEnabled=true}if(this._list.is(":visible")){this._showSelection()}},_showSelection:function(){if(this._isEnabled){this._list.children("li").each($.proxy(function(b,d){var c=$(d);var a=c.data("languageID");if(a){if(this._values[a]&&this._values[a]!=""){c.removeClass("missingValue")}else{c.addClass("missingValue")}}},this))}},_closeSelection:function(){this._disable()},_changeLanguage:function(a){var b=$(a.currentTarget);this._insertedDataAfterInit=true;if(this._didInit){this._values[this._languageID]=this._element.val()}this._languageID=b.data("languageID");if(this._values[this._languageID]){this._element.val(this._values[this._languageID])}else{this._element.val("")}this._list.children("li").removeClass("active");b.addClass("active");this._button.children("span").addClass("active").text(this._availableLanguages[this._languageID]);if(this._didInit){this._element.blur().focus()}},_disable:function(a){if(a===undefined&&this._insertedDataAfterInit){a=null}if(this._forceSelection||!this._list||a===null){return}this._button.children("span").removeClass("active").text(WCF.Language.get("wcf.global.button.disabledI18n"));if(this._values[LANGUAGE_ID]){this._element.val(this._values[LANGUAGE_ID])}else{this._element.val()}if(a){this._list.children("li").removeClass("active");$(a.currentTarget).addClass("active")}this._element.blur().focus();this._insertedDataAfterInit=false;this._isEnabled=false;this._values={}},_submit:function(){if(!this._isEnabled){return 3735928559}if(this._languageID){this._values[this._languageID]=this._element.val()}var b=$(this._element.parents("form")[0]);var c=this._element.wcfIdentify();for(var a in this._availableLanguages){if(this._values[a]===undefined){this._values[a]=""}$('<input type="hidden" name="'+c+"_i18n["+a+']" value="'+WCF.String.escapeHTML(this._values[a])+'" />').appendTo(b)}this._element.removeAttr("name")}});WCF.Number={round:function(b,a){a=Math.pow(10,(a||0));return Math.round(b*a)/a}};WCF.String={addThousandsSeparator:function(a){return String(a).replace(/(^-?\d{1,3}|\d{3})(?=(?:\d{3})+(?:$|\.))/g,"$1"+WCF.Language.get("wcf.global.thousandsSeparator"))},escapeHTML:function(a){return String(a).replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")},escapeRegExp:function(a){return String(a).replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")},formatNumeric:function(b,a){b=String(WCF.Number.round(b,a||2));numberParts=b.split(".");b=this.addThousandsSeparator(numberParts[0]);if(numberParts.length>1){b+=WCF.Language.get("wcf.global.decimalPoint")+numberParts[1]}b=b.replace("-","\u2212");return b},lcfirst:function(a){return String(a).substring(0,1).toLowerCase()+a.substring(1)},ucfirst:function(a){return String(a).substring(0,1).toUpperCase()+a.substring(1)},unescapeHTML:function(a){return String(a).replace(/&/g,"&").replace(/"/g,'"').replace(/</g,"<").replace(/>/g,">")}};WCF.TabMenu={_containers:{},_didInit:false,init:function(){var d=$(".tabMenuContainer:not(.staticTabMenuContainer)");var c=this;d.each(function(f,e){var h=$(e);var g=h.wcfIdentify();if(c._containers[g]){return true}if(h.data("store")&&!$("#"+h.data("store")).length){$('<input type="hidden" name="'+h.data("store")+'" value="" id="'+h.data("store")+'" />').appendTo(h.parents("form").find(".formSubmit"))}c._containers[g]=h;h.wcfTabs({active:false,activate:function(j,i){var l=$(i.newPanel);var k=l.closest(".tabMenuContainer");var m=k;while(true){if(m.data("isParent")===undefined){break}if(m.data("isParent")){if(m.data("store")){$("#"+m.data("store")).val(l.attr("id"))}break}else{m=m.data("parent")}}if(WCF.TabMenu._didInit){if(l.data("inTabMenu")==undefined){l.data("inTabMenu",(l.parents(".dialogContainer").length))}if(!l.data("inTabMenu")){if(window.history){window.history.pushState(null,document.title,window.location.toString().replace(/#.+$/,"")+"#"+l.attr("id"))}else{location.hash="#"+l.attr("id")}}}}});h.data("isParent",(h.children(".tabMenuContainer, .tabMenuContent").length>0)).data("parent",false);if(!h.data("isParent")){if(h.parent().hasClass("tabMenuContainer")){h.data("parent",h.parent())}}});if(!this._didInit){this._selectActiveTab();$(window).bind("hashchange",$.proxy(this.selectTabs,this));if(!this._selectErroneousTab()){this.selectTabs()}if($.browser.mozilla&&location.hash){var a=$(location.hash);if(a.length&&a.hasClass("tabMenuContent")){var b=a.offset();window.scrollTo(b.left,b.top)}}}this._didInit=true},reload:function(){this._containers={};this.init()},_selectErroneousTab:function(){var d=false;for(var a in this._containers){var c=this._containers[a];if(c.find(".formError").length){d=true;if(!c.data("isParent")){while(true){if(c.data("parent")===false){break}c=c.data("parent").wcfTabs("selectTab",c.wcfIdentify())}return true}}}if(d){for(var a in this._containers){var c=this._containers[a];var b=c.find(".formError:eq(0)");if(b.length){c.wcfTabs("selectTab",b.parents(".tabMenuContent").wcfIdentify());while(true){if(c.data("parent")===false){break}c=c.data("parent").wcfTabs("selectTab",c.wcfIdentify())}return true}}}return false},_selectActiveTab:function(){for(var b in this._containers){var e=this._containers[b];if(e.data("active")){var a=e.data("active");var d=null;if(/-/.test(a)){var c=a.split("-");a=c[0];d=c[1]}e.find(".tabMenuContent").each(function(h,g){var f=$(g);if(f.wcfIdentify()==a){e.wcfTabs("select",h);if(d!==null){if(f.hasClass("tabMenuContainer")){f.wcfTabs("selectTab",e.data("active"))}else{e.wcfTabs("selectTab",e.data("active"))}}return false}})}}},selectTabs:function(){if(location.hash){var a=location.hash.substr(1);var b=$("#"+$.wcfEscapeID(a));if(b.length===1&&b.hasClass("ui-tabs-panel")){b=b.parent(".ui-tabs");if(b.length){b.wcfTabs("selectTab",a);if(b.hasClass("ui-tabs-panel")){a=b.wcfIdentify();b=b.parent(".ui-tabs");if(b.length){b.wcfTabs("selectTab",a)}}return true}}}return false}};WCF.Template=Class.extend({init:function(d){var f=new WCF.Dictionary();var a=0;d=d.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/(\r\n|\n|\r)/g,"\\n");d=d.replace(/\{literal\}(.*?)\{\/literal\}/g,$.proxy(function(e){var i="@@@@@@@@@@@"+Math.random()+"@@@@@@@@@@@";f.add(i,e.replace(/\{\/?literal\}/g,""));return i},this));d=d.replace(/\{\*.*?\*\}/g,"");var c=function(q){var l=q.split("");var i={};var k=true;var s="";var o="";var m=false;var n=false;var j=false;for(var p=0,e=l.length;p<e;p++){var r=l[p];if(k&&r!="="&&r!=" "){s+=r}else{if(k&&r=="="){k=false;n=false;m=false;j=false}else{if(!k&&!n&&!m&&r==" "){k=true;i[s]=o;o=s=""}else{if(!k&&n&&!j&&r=="'"){n=false;o+=r}else{if(!k&&!n&&!m&&r=="'"){n=true;o+=r}else{if(!k&&m&&!j&&r=='"'){m=false;o+=r}else{if(!k&&!n&&!m&&r=='"'){m=true;o+=r}else{if(!k&&(m||n)&&!j&&r=="\\"){j=true;o+=r}else{if(!k){j=false;o+=r}}}}}}}}}}i[s]=o;if(m||n||j){throw new Error('Syntax error in parameterList: "'+q+'"')}return i};var h=function(e){return e.replace(/\\n/g,"\n").replace(/\\\\/g,"\\").replace(/\\'/g,"'")};d=d.replace(/\{(\$[^\}]+?)\}/g,function(e,i){i=h(i.replace(/\$([^.\[\s]+)/g,"(v['$1'])"));return"' + WCF.String.escapeHTML("+i+") + '"}).replace(/\{#(\$[^\}]+?)\}/g,function(e,i){i=h(i.replace(/\$([^.\[\s]+)/g,"(v['$1'])"));return"' + WCF.String.formatNumeric("+i+") + '"}).replace(/\{@(\$[^\}]+?)\}/g,function(e,i){i=h(i.replace(/\$([^.\[\s]+)/g,"(v['$1'])"));return"' + "+i+" + '"}).replace(/{lang}(.+?){\/lang}/g,function(e,i){return"' + WCF.Language.get('"+h(i)+"') + '"}).replace(/\{if (.+?)\}/g,function(e,i){i=h(i.replace(/\$([^.\[\s]+)/g,"(v['$1'])"));return"';\nif ("+i+") {\n $output += '"}).replace(/\{else ?if (.+?)\}/g,function(e,i){i=h(i.replace(/\$([^.\[\s]+)/g,"(v['$1'])"));return"';\n}\nelse if ("+i+") {\n $output += '"}).replace(/\{implode (.+?)\}/g,function(e,j){a++;j=j.replace(/\\\\/g,"\\").replace(/\\'/g,"'");var i=c(j);if(typeof i.from==="undefined"){throw new Error("Missing from attribute in implode-tag")}if(typeof i.item==="undefined"){throw new Error("Missing item attribute in implode-tag")}if(typeof i.glue==="undefined"){i.glue="', '"}i.from=i.from.replace(/\$([^.\[\s]+)/g,"(v.$1)");return"';\nvar $implode_"+a+" = false;\nfor ($implodeKey_"+a+" in "+i.from+") {\n v["+i.item+"] = "+i.from+"[$implodeKey_"+a+"];\n"+(typeof i.key!=="undefined"?" v["+i.key+"] = $implodeKey_"+a+";\n":"")+" if ($implode_"+a+") $output += "+i.glue+";\n $implode_"+a+" = true;\n $output += '"}).replace(/\{foreach (.+?)\}/g,function(e,j){a++;j=j.replace(/\\\\/g,"\\").replace(/\\'/g,"'");var i=c(j);if(typeof i.from==="undefined"){throw new Error("Missing from attribute in foreach-tag")}if(typeof i.item==="undefined"){throw new Error("Missing item attribute in foreach-tag")}i.from=i.from.replace(/\$([^.\[\s]+)/g,"(v.$1)");return"';\n$foreach_"+a+" = false;\nfor ($foreachKey_"+a+" in "+i.from+") {\n $foreach_"+a+" = true;\n break;\n}\nif ($foreach_"+a+") {\n for ($foreachKey_"+a+" in "+i.from+") {\n v["+i.item+"] = "+i.from+"[$foreachKey_"+a+"];\n"+(typeof i.key!=="undefined"?" v["+i.key+"] = $foreachKey_"+a+";\n":"")+" $output += '"}).replace(/\{foreachelse\}/g,"';\n }\n}\nelse {\n {\n $output += '").replace(/\{\/foreach\}/g,"';\n }\n}\n$output += '").replace(/\{else\}/g,"';\n}\nelse {\n $output += '").replace(/\{\/(if|implode)\}/g,"';\n}\n$output += '");for(var b in WCF.Template.callbacks){d=WCF.Template.callbacks[b](d)}d=d.replace("{ldelim}","{").replace("{rdelim}","}");f.each(function(e){d=d.replace(e.key,e.value)});d="$output += '"+d+"';";try{this.fetch=new Function("v","if (typeof v != 'object') { v = {}; } v.__window = window; v.__wcf = window.WCF; var $output = ''; "+d+" return $output;")}catch(g){console.debug("var $output = ''; "+d+" return $output;");throw g}},fetch:function(a){}});WCF.Template.callbacks=[];WCF.ToggleOptions=Class.extend({_element:null,_showItems:[],_hideItems:[],_callback:null,init:function(c,a,b,d){this._element=$("#"+c);this._showItems=a;this._hideItems=b;if(d!==undefined){this._callback=d}this._element.click($.proxy(this._toggle,this));this._toggle()},_toggle:function(){if(!this._element.prop("checked")){return}for(var c=0,b=this._showItems.length;c<b;c++){var a=this._showItems[c];$("#"+a).show()}for(var c=0,b=this._hideItems.length;c<b;c++){var a=this._hideItems[c];$("#"+a).hide()}if(this._callback!==null){this._callback()}}});WCF.Collapsible={};WCF.Collapsible.Simple={init:function(){$(".jsCollapsible").each($.proxy(function(a,b){this._initButton(b)},this))},_initButton:function(a){var c=$(a);var b=c.data("isOpen");if(!b){$("#"+c.data("collapsibleContainer")).hide()}c.click($.proxy(this._toggle,this))},_toggle:function(b){var d=$(b.currentTarget);var c=d.data("isOpen");var a=$("#"+$.wcfEscapeID(d.data("collapsibleContainer")));if(c){a.stop().wcfBlindOut("vertical",$.proxy(function(){this._toggleImage(d)},this));c=false}else{a.stop().wcfBlindIn("vertical",$.proxy(function(){this._toggleImage(d)},this));c=true}d.data("isOpen",c);b.stopPropagation();return false},_toggleImage:function(b){var a=b.find("span.icon");if(b.data("isOpen")){a.removeClass("icon-chevron-right").addClass("icon-chevron-down")}else{a.removeClass("icon-chevron-down").addClass("icon-chevron-right")}}};WCF.Collapsible.Remote=Class.extend({_className:"",_containers:{},_containerData:{},_proxy:null,init:function(a){this._className=a;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._init();WCF.DOMNodeInsertedHandler.addCallback("WCF.Collapsible.Remote",$.proxy(this._init,this))},_init:function(a){this._getContainers().each($.proxy(function(c,b){var e=$(b);var d=e.wcfIdentify();if(this._containers[d]===undefined){this._containers[d]=e;this._initContainer(d)}},this))},_initContainer:function(b){var a=this._getTarget(b);var c=this._getButtonContainer(b);var d=this._createButton(b,c);this._containerData[b]={button:d,buttonContainer:c,isOpen:this._containers[b].data("isOpen"),target:a};if(!this._containers[b].data("isOpen")){$("#"+b).addClass("jsCollapsed")}},_getContainers:function(){},_getTarget:function(a){},_getButtonContainer:function(a){},_createButton:function(a,b){var d=this._containers[a].data("isOpen");var c=$('<span class="collapsibleButton jsTooltip pointer icon icon16 icon-'+(d?"chevron-down":"chevron-right")+'" title="'+WCF.Language.get("wcf.global.button.collapsible")+'">').prependTo(b);c.data("containerID",a).click($.proxy(this._toggleContainer,this));return c},_toggleContainer:function(d){var f=$(d.currentTarget);var b=f.data("containerID");var e=this._containerData[b].isOpen;var a=(e)?"open":"close";var c=(e)?"close":"open";this._proxy.setOption("data",{actionName:"loadContainer",className:this._className,interfaceName:"wcf\\data\\ILoadableContainerAction",objectIDs:[this._getObjectID(b)],parameters:$.extend(true,{containerID:b,currentState:a,newState:c},this._getAdditionalParameters(b))});this._proxy.sendRequest();$("#"+b).toggleClass("jsCollapsed")},_exchangeIcon:function(b,a){a=a||"spinner";b.removeClass("icon-chevron-down icon-chevron-right icon-spinner").addClass("icon-"+a)},_getObjectID:function(a){return $("#"+a).data("objectID")},_getAdditionalParameters:function(a){return{}},_updateContent:function(a,b,c){this._containerData[a].target.html(b)},_success:function(d,e,c){if(!d.returnValues.containerID){return}var a=d.returnValues.containerID;if(!this._containers[a]){return}this._containerData[a].isOpen=(d.returnValues.isOpen)?true:false;var b=(d.returnValues.isOpen)?"open":"close";this._updateContent(a,$.trim(d.returnValues.content),b);this._exchangeIcon(this._containerData[a].button,(d.returnValues.isOpen?"chevron-down":"chevron-right"))}});WCF.Collapsible.SimpleRemote=WCF.Collapsible.Remote.extend({init:function(a){this._super(a);this._proxy=new WCF.Action.Proxy({showLoadingOverlay:false})},_initContainer:function(a){this._super(a);if(!this._containerData[a].isOpen){this._containerData[a].target.hide();this._exchangeIcon(this._containerData[a].button,"chevron-right")}},_toggleContainer:function(d){var f=$(d.currentTarget);var b=f.data("containerID");var e=this._containerData[b].isOpen;var a=(e)?"open":"close";var c=(e)?"close":"open";this._proxy.setOption("data",{actionName:"toggleContainer",className:this._className,interfaceName:"wcf\\data\\IToggleContainerAction",objectIDs:[this._getObjectID(b)],parameters:$.extend(true,{containerID:b,currentState:a,newState:c},this._getAdditionalParameters(b))});this._proxy.sendRequest();this._exchangeIcon(this._containerData[b].button,(c==="open"?"chevron-down":"chevron-right"));if(c==="open"){this._containerData[b].target.show()}else{this._containerData[b].target.hide()}$("#"+b).toggleClass("jsCollapsed");this._containerData[b].isOpen=(c==="open"?true:false)}});WCF.Collapsible.Sidebar=Class.extend({_button:null,_buttonHeight:0,_isOpen:false,_mainContainer:null,_proxy:null,_sidebar:null,_sidebarHeight:0,_sidebarName:"",_sidebarOffset:0,_userPanelHeight:0,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();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();var a=$(window);this._sidebar.on("webkitTransitionEnd transitionend msTransitionEnd oTransitionEnd",function(){a.trigger("resize")})},_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()},_scroll:function(){var e=$(window);var b=e.scrollTop();var a=Math.max(b-this._sidebarOffset,0);var c=Math.min(this._mainContainer.height(),(e.height()+b)-this._sidebarOffset);var f=0;if(c===a){f=this._sidebarOffset+this._sidebarHeight}else{f=a+((c-a)/2);var d=Math.max(Math.min(a-this._userPanelHeight,this._userPanelHeight),0);if(d>0){f+=(d/2)}}if((c-a-this._userPanelHeight)<this._buttonHeight){f=f-this._buttonHeight}else{f=Math.max(f-(this._buttonHeight/2),0)}this._button.css({top:f+"px"})},_renderSidebar:function(){if(this._isOpen){$(".sidebarOrientationLeft, .sidebarOrientationRight").removeClass("sidebarCollapsed")}else{$(".sidebarOrientationLeft, .sidebarOrientationRight").addClass("sidebarCollapsed")}this._scroll();if($.browser.msie&&$.browser.version.indexOf("9")===0){$(window).trigger("resize")}}});WCF.User={userID:0,username:"",init:function(a,b){this.userID=a;this.username=b}};WCF.Effect={};WCF.Effect.Scroll=Class.extend({scrollTo:function(c,a,b){if(!c.length){return true}var d=c.getOffsets("offset").top;var f=$(document).height();var e=$(window).height();if(d>f-e){d=f-e;if(d<0){d=0}}if(b===true){$("html,body").scrollTop(d)}else{$("html,body").animate({scrollTop:d},400,function(h,i,g,k,j){return -k*((i=i/j-1)*i*i*i-1)+g})}return false}});WCF.Effect.SmoothScroll=WCF.Effect.Scroll.extend({init:function(){var a=this;$(document).on("click","a[href$=#top],a[href$=#bottom]",function(){var b=$(this.hash);a.scrollTo(b,true);return false})}});WCF.Effect.BalloonTooltip=Class.extend({_didInit:false,_tooltip:null,_viewportDimensions:{},init:function(){if(jQuery.browser.mobile){return}if(!this._didInit){this._tooltip=$('<div id="balloonTooltip" class="balloonTooltip"><span id="balloonTooltipText"></span><span class="pointer"><span></span></span></div>').appendTo($("body")).hide();this._updateViewportDimensions();$(window).resize($.proxy(this._updateViewportDimensions,this));WCF.DOMNodeInsertedHandler.addCallback("WCF.Effect.BalloonTooltip",$.proxy(this.init,this));this._didInit=true}$(".jsTooltip").each($.proxy(this._initTooltip,this))},_updateViewportDimensions:function(){this._viewportDimensions=$(document).getDimensions()},_initTooltip:function(b,c){var a=$(c);if(a.hasClass("jsTooltip")){a.removeClass("jsTooltip");var d=a.attr("title");if(d!==""){a.data("tooltip",d);a.removeAttr("title");a.hover($.proxy(this._mouseEnterHandler,this),$.proxy(this._mouseLeaveHandler,this));a.click($.proxy(this._mouseLeaveHandler,this))}}},_mouseEnterHandler:function(c){var n=$(c.currentTarget);var j=n.attr("title");if(j&&j!==""){n.data("tooltip",j);n.removeAttr("title")}this._tooltip.css({top:"0px",left:"0px"});if(!n.data("tooltip")){this._tooltip.hide();return}this._tooltip.children("span:eq(0)").text(n.data("tooltip"));var l=this._tooltip.find(".pointer");this._tooltip.show();var b=l.outerWidth();this._tooltip.hide();var f=n.getOffsets("offset");var k=n.getDimensions("outer");var m=this._tooltip.getDimensions("outer");var a=this._tooltip.getDimensions("inner");var h=f.left+Math.ceil(k.width/2);var i=Math.ceil(m.width/2);var g="center";if((h-i)<5){g="left"}else{if((this._viewportDimensions.width-5)<(h+i)){g="right"}}if(f.top+k.height+m.height-$(document).scrollTop()<$(window).height()){var e=f.top+k.height+7;this._tooltip.removeClass("inverse");l.css("top",-5)}else{var e=f.top-m.height-7;this._tooltip.addClass("inverse");l.css("top",m.height)}switch(g){case"center":var d=Math.round(f.left-i+(k.width/2));l.css({left:(a.width/2-b/2)+"px"});break;case"left":var d=f.left;l.css({left:"5px"});break;case"right":var d=f.left+k.width-m.width;l.css({left:(a.width-b-5)+"px"});break}this._tooltip.css({top:e+"px",left:d+"px"});this._tooltip.wcfFadeIn()},_mouseLeaveHandler:function(a){this._tooltip.stop().hide().css({opacity:1})}});WCF.CloseOverlayHandler={_callbacks:new WCF.Dictionary(),_isListening:false,addCallback:function(a,b){this._bindListener();if(this._callbacks.isset(a)){console.debug("[WCF.CloseOverlayHandler] identifier '"+a+"' is already bound to a callback");return false}this._callbacks.add(a,b)},removeCallback:function(a){if(this._callbacks.isset(a)){this._callbacks.remove(a)}},_bindListener:function(){if(this._isListening){return}$("body").click($.proxy(this._executeCallbacks,this));this._isListening=true},_executeCallbacks:function(a){this._callbacks.each(function(b){b.value()})}};WCF.DOMNodeInsertedHandler={_callbacks:[],_isExecuting:false,addCallback:function(a,b){this._callbacks.push(b)},_executeCallbacks:function(){if(this._isExecuting){return}this._isExecuting=true;for(var b=0,a=this._callbacks.length;b<a;b++){this._callbacks[b]()}this._isExecuting=false},execute:function(){this._executeCallbacks()}};WCF.DOMNodeRemovedHandler={_callbacks:new WCF.Dictionary(),_isExecuting:false,_isListening:false,addCallback:function(a,b){this._bindListener();if(this._callbacks.isset(a)){console.debug("[WCF.DOMNodeRemovedHandler] identifier '"+a+"' is already bound to a callback");return false}this._callbacks.add(a,b)},removeCallback:function(a){if(this._callbacks.isset(a)){this._callbacks.remove(a)}},_bindListener:function(){if(this._isListening){return}$(document).bind("DOMNodeRemoved",$.proxy(this._executeCallbacks,this));this._isListening=true},_executeCallbacks:function(a){if(this._isExecuting){return}this._isExecuting=true;this._callbacks.each(function(b){b.value(a)});this._isExecuting=false}};WCF.PageVisibilityHandler={_callbacks:new WCF.Dictionary(),_isListening:false,_hiddenFieldName:"",addCallback:function(a,b){this._bindListener();if(this._callbacks.isset(a)){console.debug("[WCF.PageVisibilityHandler] identifier '"+a+"' is already bound to a callback");return false}this._callbacks.add(a,b)},removeCallback:function(a){if(this._callbacks.isset(a)){this._callbacks.remove(a)}},_bindListener:function(){if(this._isListening){return}var a=null;if(typeof document.hidden!=="undefined"){this._hiddenFieldName="hidden";a="visibilitychange"}else{if(typeof document.mozHidden!=="undefined"){this._hiddenFieldName="mozHidden";a="mozvisibilitychange"}else{if(typeof document.msHidden!=="undefined"){this._hiddenFieldName="msHidden";a="msvisibilitychange"}else{if(typeof document.webkitHidden!=="undefined"){this._hiddenFieldName="webkitHidden";a="webkitvisibilitychange"}}}}if(a===null){console.debug("[WCF.PageVisibilityHandler] This browser does not support the page visibility API.")}else{$(document).on(a,$.proxy(this._executeCallbacks,this))}this._isListening=true},_executeCallbacks:function(b){if(this._isExecuting){return}this._isExecuting=true;var a=document[this._hiddenFieldName];this._callbacks.each(function(c){c.value(a)});this._isExecuting=false}};WCF.Table={};WCF.Table.EmptyTableHandler=Class.extend({_options:{},_rowClassName:"",init:function(b,c,a){this._rowClassName=c;this._tableContainer=b;this._options=$.extend(true,{emptyMessage:null,messageType:"info",refreshPage:false,updatePageNumber:false},a||{});WCF.DOMNodeRemovedHandler.addCallback("WCF.Table.EmptyTableHandler."+c,$.proxy(this._remove,this))},_remove:function(e){var d=$(e.target);if(d.hasClass(this._rowClassName)){var c=d.parents("tbody:eq(0)");if(c.children("tr").length==1){if(this._options.emptyMessage){this._tableContainer.replaceWith($("<p />").addClass(this._options.messageType).text(this._options.emptyMessage))}else{if(this._options.refreshPage){if(this._options.updatePageNumber){var a=window.location.href.match(/(\?|&)pageNo=(\d+)/g);if(a){var b=a[a.length-1].match(/\d+/g);if(this._options.updatePageNumber>0){b++}else{b--}window.location=window.location.href.replace(a[a.length-1],a[a.length-1][0]+"pageNo="+b)}}else{window.location.reload()}}else{this._tableContainer.remove()}}}}}});WCF.Search={};WCF.Search.Base=Class.extend({_callback:null,_className:"",_commaSeperated:false,_delay:0,_excludedSearchValues:[],_itemCount:0,_itemIndex:-1,_list:null,_oldSearchString:[],_proxy:null,_searchInput:null,_triggerLength:3,_timer:null,init:function(b,e,a,c,d){if(e!==null&&e!==undefined&&!$.isFunction(e)){console.debug("[WCF.Search.Base] The given callback is invalid, aborting.");return}this._callback=(e)?e:null;this._delay=0;this._excludedSearchValues=[];if(a){this._excludedSearchValues=a}this._searchInput=$(b);if(!this._searchInput.length){console.debug("[WCF.Search.Base] Selector '"+b+"' 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=(c)?true:false;this._oldSearchString=[];this._itemCount=0;this._itemIndex=-1;this._proxy=new WCF.Action.Proxy({showLoadingOverlay:(d!==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)},_blur:function(){var a=this;new WCF.PeriodicalExecuter(function(b){if(a._list.is(":visible")){a._clearList(false)}b.stop()},250)},_keyDown:function(a){if(a.which===$.ui.keyCode.ENTER){var b=this._searchInput.parents(".dropdown");if(b.data("disableAutoFocus")){if(this._itemIndex!==-1){a.preventDefault()}}else{if(b.data("preventSubmit")||this._itemIndex!==-1){a.preventDefault()}}}},_keyUp:function(d){switch(d.which){case 37:case 39:return;break;case 38:this._selectPreviousItem();return;break;case 40:this._selectNextItem();return;break;case 13:return this._selectElement(d);break}var b=this._getSearchString(d);if(b===""){this._clearList(true)}else{if(b.length>=this._triggerLength){var c={data:{excludedSearchValues:this._excludedSearchValues,searchString:b}};if(this._delay){if(this._timer!==null){this._timer.stop()}var a=this;this._timer=new WCF.PeriodicalExecuter(function(){a._queryServer(c);a._timer.stop();a._timer=null},this._delay)}else{this._queryServer(c)}}else{this._clearList(false)}}},_queryServer:function(a){this._searchInput.parents(".searchBar").addClass("loading");this._proxy.setOption("data",{actionName:"getSearchResultList",className:this._className,interfaceName:"wcf\\data\\ISearchAction",parameters:this._getParameters(a)});this._proxy.sendRequest()},setDelay:function(a){this._delay=a},_selectNextItem:function(){if(this._itemCount===0){return}this._itemIndex++;if(this._itemIndex===this._itemCount){this._itemIndex=0}this._highlightSelectedElement()},_selectPreviousItem:function(){if(this._itemCount===0){return}this._itemIndex--;if(this._itemIndex===-1){this._itemIndex=this._itemCount-1}this._highlightSelectedElement()},_highlightSelectedElement:function(){this._list.find("li").removeClass("dropdownNavigationItem");this._list.find("li:eq("+this._itemIndex+")").addClass("dropdownNavigationItem")},_selectElement:function(a){if(this._itemCount===0){return true}this._list.find("li.dropdownNavigationItem").trigger("click");return false},_getSearchString:function(d){var a=$.trim(this._searchInput.val());if(this._commaSeperated){var c=d.keyCode||d.which;if(c==$.ui.keyCode.COMMA){return""}var b=a.split(",");var e=b.length;for(var g=0;g<e;g++){b[g]=$.trim(b[g])}for(var g=0;g<e;g++){var f=b[g];if(this._oldSearchString[g]){if(f!=this._oldSearchString[g]){a=f;break}}else{a=f;break}}this._oldSearchString=b}return a},_getParameters:function(a){return a},_success:function(d,f,c){this._clearList(false);this._searchInput.parents(".searchBar").removeClass("loading");if($.getLength(d.returnValues)){for(var e in d.returnValues){var a=d.returnValues[e];this._createListItem(a)}}else{if(!this._handleEmptyResult()){return}}WCF.CloseOverlayHandler.addCallback("WCF.Search.Base",$.proxy(function(){this._clearList()},this));var b=this._searchInput.parents(".dropdown").wcfIdentify();if(!WCF.Dropdown.getDropdownMenu(b).hasClass("dropdownOpen")){WCF.Dropdown.toggleDropdown(b)}this._itemIndex=-1;if(!WCF.Dropdown.getDropdown(b).data("disableAutoFocus")){this._selectNextItem()}},_handleEmptyResult:function(){return false},_createListItem:function(b){var a=$("<li><span>"+WCF.String.escapeHTML(b.label)+"</span></li>").appendTo(this._list);a.data("objectID",b.objectID).data("label",b.label).click($.proxy(this._executeCallback,this));this._itemCount++;return a},_executeCallback:function(d){var a=false;var c=$(d.currentTarget);if(this._commaSeperated){var b=c.data("label");for(var h=0,f=this._oldSearchString.length;h<f;h++){var g=this._oldSearchString[h];if(b.toLowerCase().indexOf(g.toLowerCase())===0){this._oldSearchString[h]=b;this._searchInput.val(this._oldSearchString.join(", "));if($.browser.webkit){this._searchInput.css({display:"block"})}var e=this._searchInput.val().toLowerCase().indexOf(b.toLowerCase())+b.length;this._searchInput.focus().setCaret(e);break}}}else{if(this._callback===null){this._searchInput.val(c.data("label"))}else{a=(this._callback(c.data())===true)?true:false}}this._clearList(a)},_clearList:function(a){if(a&&!this._commaSeperated){this._searchInput.val("")}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");this._itemCount=0;this._itemIndex=-1},addExcludedSearchValue:function(a){if(!WCF.inArray(a,this._excludedSearchValues)){this._excludedSearchValues.push(a)}},removeExcludedSearchValue:function(b){var a=$.inArray(b,this._excludedSearchValues);if(a!=-1){this._excludedSearchValues.splice(a,1)}}});WCF.Search.User=WCF.Search.Base.extend({_className:"wcf\\data\\user\\UserAction",_includeUserGroups:false,init:function(c,e,a,b,d){this._includeUserGroups=a;this._super(c,e,b,d)},_getParameters:function(a){a.data.includeUserGroups=this._includeUserGroups?1:0;return a},_createListItem:function(e){var d=this._super(e);var b=null;if(e.icon){b=$(e.icon)}else{if(this._includeUserGroups&&e.type==="group"){b=$('<span class="icon icon16 icon-group" />')}}if(b){var a=d.find("span").detach();var c=$("<div />").addClass("box16").appendTo(d);c.append(b);c.append($("<div />").append(a))}d.data("type",e.type);return d}});WCF.System={};WCF.System.Dependency={};WCF.System.Dependency.Manager={_callbacks:{},_loaded:[],_setupCallbacks:{},register:function(a,b){if(!$.isFunction(b)){console.debug("[WCF.System.Dependency.Manager] Callback for identifier '"+a+"' is invalid, aborting.");return}if(WCF.inArray(a,this._loaded)){setTimeout(function(){b()},1)}else{if(!this._callbacks[a]){this._callbacks[a]=[]}this._callbacks[a].push(b)}},setup:function(a,b){if(!$.isFunction(b)){console.debug("[WCF.System.Dependency.Manager] Setup callback for identifier '"+a+"' is invalid, aborting.");return}if(!this._setupCallbacks[a]){this._setupCallbacks[a]=[]}this._setupCallbacks[a].push(b)},invoke:function(a){if(this._setupCallbacks[a]){for(var c=0,b=this._setupCallbacks[a].length;c<b;c++){this._setupCallbacks[a][c]()}delete this._setupCallbacks[a]}this._loaded.push(a);if(this._callbacks[a]){for(var c=0,b=this._callbacks[a].length;c<b;c++){this._callbacks[a][c]()}delete this._callbacks[a]}}};WCF.System.FlexibleMenu={_containers:{},_containerIDs:[],_dropdowns:{},_dropdownMenus:{},_hasHiddenItems:{},_isWorking:false,_menuItems:{},init:function(){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))},_registerTabMenus:function(){$(".tabMenuContainer:not(.jsFlexibleMenuEnabled)").each(function(a,b){var c=$(b).children("nav");if(c.length&&c.find("> ul:eq(0) > li").length){WCF.System.FlexibleMenu.registerMenu(c.wcfIdentify())}})},registerMenu:function(a){var b=$("#"+a);if(!b.length){console.debug("[WCF.System.FlexibleMenu] Unable to find container identified by '"+a+"', aborting.");return}this._containerIDs.push(a);this._containers[a]=b;this._menuItems[a]=b.find("> ul:eq(0) > li");this._dropdowns[a]=$('<li class="dropdown"><a class="icon icon16 icon-list" /></li>').data("containerID",a).click($.proxy(this._click,this));this._dropdownMenus[a]=$('<ul class="dropdownMenu" />').appendTo(this._dropdowns[a]);this._hasHiddenItems[a]=false;this.rebuild(a);WCF.Dropdown.initDropdown(this._dropdowns[a].children("a"))},rebuildAll:function(){if(this._isWorking){return}this._isWorking=true;for(var b=0,a=this._containerIDs.length;b<a;b++){this.rebuild(this._containerIDs[b])}this._isWorking=false},rebuild:function(i){if(!this._containers[i]){console.debug("[WCF.System.FlexibleMenu] Cannot rebuild unknown container identified by '"+i+"'");return}var c=false;var j=this._containers[i];var b=0;var d=this._menuItems[i].filter(":visible");for(var h=0,e=d.length;h<e;h++){b+=$(d[h]).outerWidth(true)}if(!this._hasHiddenItems[i]){this._dropdowns[i].appendTo(j.children("ul:eq(0)"))}var f=this._dropdowns[i].outerWidth(true);if(!this._hasHiddenItems[i]){this._dropdowns[i].detach()}var a=j.parent().innerWidth();a-=parseInt(j.parent().css("padding-left").replace(/px$/,""))+parseInt(j.parent().css("padding-right").replace(/px$/,""));a-=parseInt(j.css("margin-left").replace(/px$/,""))+parseInt(j.css("margin-right").replace(/px$/,""));a-=parseInt(j.css("padding-left").replace(/px$/,""))+parseInt(j.css("padding-right").replace(/px$/,""));a-=parseInt(j.children("ul:eq(0)").css("padding-left").replace(/px$/,""))+parseInt(j.children("ul:eq(0)").css("padding-right").replace(/px$/,""));if(b>a||(this._hasHiddenItems[i]&&(b>a-f))){var d=d.filter(":not(.active):not(.ui-state-active):visible");a-=f;for(var h=(d.length-1);h>=0;h--){if(b>a){var g=$(d[h]);b-=g.outerWidth(true);g.hide();c=true;this._hasHiddenItems[i]=true}else{break}}if(this._hasHiddenItems[i]){this._dropdowns[i].appendTo(j.children("ul:eq(0)"))}}else{if(this._hasHiddenItems[i]&&b<a){var k=this._menuItems[i].filter(":not(:visible)");a-=f;for(var h=0,e=k.length;h<e;h++){var g=$(k[h]);b+=g.outerWidth();if(h+1==e){a+=f}if(b<a){g.css("display","");c=true}else{break}}if(c){this._hasHiddenItems[i]=(this._menuItems[i].filter(":not(:visible)").length>0);if(!this._hasHiddenItems[i]){this._dropdowns[i].detach()}}}}if(c){this._dropdownMenus[i].empty();this._menuItems[i].filter(":not(:visible)").each($.proxy(function(l,m){$("<li>"+$(m).html()+"</li>").appendTo(this._dropdownMenus[i])},this))}}};WCF.System.Mobile={};WCF.System.Mobile.UX={_enabled:false,_main:null,_sidebar:null,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){this._sidebar.css("display","none").css("display","")}},_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))},_enable:function(){this._enabled=true;if($.browser.msie){this._sidebar.css("display","none").css("display","")}},_disable:function(){this._enabled=false;if($.browser.msie){this._sidebar.css("display","none").css("display","")}},_initSidebarToggleButtons:function(){var c=this._main.hasClass("sidebarOrientationLeft");var b=this._main.hasClass("sidebarOrientationRight");if(c||b){var d="wcf.global.sidebar.show"+(c?"Left":"Right")+"Sidebar";if(d===WCF.Language.get(d)||WCF.Language.get(d)===""){d='<span class="icon icon16 icon-double-angle-'+(c?"left":"right")+'" />'}var e="wcf.global.sidebar.hide"+(c?"Left":"Right")+"Sidebar";if(e===WCF.Language.get(e)||WCF.Language.get(e)===""){e='<span class="icon icon16 icon-double-angle-'+(c?"right":"left")+'" />'}var a=this;$('<span class="button small mobileSidebarToggleButton">'+d+"</span>").appendTo($(".content")).click(function(){a._main.addClass("mobileShowSidebar")});$('<span class="button small mobileSidebarToggleButton">'+e+"</span>").appendTo($(".sidebar")).click(function(){a._main.removeClass("mobileShowSidebar")})}},_initSearchBar:function(){var b=$(".searchBar:eq(0)");var a=this;b.click(function(){if(a._enabled){b.addClass("searchBarOpen")}});this._main.click(function(){b.removeClass("searchBarOpen")})},_initButtonGroupNavigation:function(){$(".buttonGroupNavigation:not(.jsMobileButtonGroupNavigation)").each(function(b,a){var d=$(a).addClass("jsMobileButtonGroupNavigation");var c=$('<a class="dropdownLabel"><span class="icon icon24 icon-list" /></a>').prependTo(d);c.click(function(){c.next().toggleClass("open");return false})})},_closeMenus:function(){$(".jsMobileButtonGroupNavigation > ul.open").removeClass("open")}};WCF.System.Page={};WCF.System.Page.Multiple=Class.extend({_cache:{},_options:{},_pageNo:1,_pages:0,init:function(a){this._options=$.extend({container:null,pagination:null,loadItems:null},a)},_showPage:function(a,b){if(b&&b.activePage){if(!b.template){this._previousPageNo=this._pageNo}this._pageNo=b.activePage}if(this._cache[this._pageNo]||(b&&b.template)){this._cache[this._previousPageNo]=this._list.children().detach();if(b&&b.template){this._list.html(b.template)}else{this._list.append(this._cache[this._pageNo])}}else{this._loadItems()}},showPage:function(a,b){this._showPage(null,{activePage:a,template:b})}});WCF.System.Notification=Class.extend({_callback:null,_cssClassNames:"",_message:"",_overlay:null,init:function(a,b){this._cssClassNames=b||"success";this._message=a||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)}},show:function(d,b,a,c){b=parseInt(b);if(!b){b=2000}if(d&&$.isFunction(d)){this._callback=d}this._overlay.children("p").html((a||this._message));this._overlay.children("p").removeClass().addClass((c||this._cssClassNames));new WCF.PeriodicalExecuter($.proxy(this._hide,this),b);this._overlay.wcfFadeIn(undefined,300)},_hide:function(a){if(this._callback!==null){this._callback()}this._overlay.wcfFadeOut(undefined,300);a.stop()}});WCF.System.Confirmation={_callback:null,_dialog:null,_parameters:null,_visible:false,_confirmationButton:null,show:function(c,e,b,a){if(this._visible){console.debug("[WCF.System.Confirmation] Confirmation dialog is already open, refusing action.");return}if(!$.isFunction(e)){console.debug("[WCF.System.Confirmation] Given callback is invalid, aborting.");return}this._callback=e;this._parameters=b;var d=true;if(this._dialog===null){this._createDialog();d=false}this._dialog.find("#wcfSystemConfirmationContent").empty().hide();if(a&&a.length){a.appendTo(this._dialog.find("#wcfSystemConfirmationContent").show())}this._dialog.find("p").text(c);this._dialog.wcfDialog({onClose:$.proxy(this._close,this),onShow:$.proxy(this._show,this),title:WCF.Language.get("wcf.global.confirmation.title")});if(d){this._dialog.wcfDialog("render")}this._confirmationButton.focus();this._visible=true},_createDialog:function(){this._dialog=$('<div id="wcfSystemConfirmation" class="systemConfirmation"><p /><div id="wcfSystemConfirmationContent" /></div>').hide().appendTo(document.body);var a=$('<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(a);$("<button>"+WCF.Language.get("wcf.global.confirmation.cancel")+"</button>").data("action","cancel").click($.proxy(this._click,this)).appendTo(a)},_click:function(a){this._notify($(a.currentTarget).data("action"))},_close:function(){if(this._visible){this._notify("cancel")}},_notify:function(a){this._visible=false;this._dialog.wcfDialog("close");this._callback(a,this._parameters)},_show:function(){this._dialog.find("button.buttonPrimary").blur().focus()}};WCF.System.DisableScrolling={_depth:0,_oldOverflow:null,disable:function(){if($.browser.touch){return}if(this._depth===0){this._oldOverflow=$(document.body).css("overflow");$(document.body).css("overflow","hidden")}this._depth++},enable:function(){if(this._depth===0){return}this._depth--;if(this._depth===0){$(document.body).css("overflow",this._oldOverflow)}}};WCF.System.PageNavigation={_button:null,_description:null,_dialog:null,_elementID:"",_elements:{},_pageNo:null,init:function(a,c){var b=$(a);if(!b.length){return}c=c||null;if(c!==null&&!$.isFunction(c)){console.debug("[WCF.System.PageNavigation] Callback for selector '"+a+"' is invalid, aborting.");return}this._initElements(b,c)},_initElements:function(b,c){var a=this;b.each(function(e,f){var d=$(f);var g=d.wcfIdentify();if(a._elements[g]===undefined){a._elements[g]=d;d.find("li.jumpTo").data("elementID",g).click($.proxy(a._click,a))}}).data("callback",c)},_click:function(b){this._elementID=$(b.currentTarget).data("elementID");if(this._dialog===null){this._dialog=$('<div id="pageNavigationOverlay" />').hide().appendTo(document.body);var c=$("<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(c);this._pageNo=$('<input type="number" id="jsPageNavigationPageNo" value="1" min="1" max="1" class="tiny" />').keyup($.proxy(this._keyUp,this)).appendTo(c.find("dd"));this._description=$("<small></small>").insertAfter(this._pageNo);var a=$('<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(a)}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")})},_keyUp:function(b){if(b.which==$.ui.keyCode.ENTER&&!this._button.prop("disabled")){this._submit();return}var a=parseInt(this._pageNo.val())||0;if(a<1||a>this._pageNo.attr("max")){this._button.disable()}else{this._button.enable()}},_submit:function(){var b=this._elements[this._elementID];if(b.data("callback")===null){var a=b.data("link").replace(/pageNo=%d/,"pageNo="+this._pageNo.val());window.location=a}else{b.data("callback")(this._pageNo.val());this._dialog.wcfDialog("close")}}};WCF.System.KeepAlive=Class.extend({init:function(a){new WCF.PeriodicalExecuter(function(b){new WCF.Action.Proxy({autoSend:true,data:{actionName:"keepAlive",className:"wcf\\data\\session\\SessionAction"},failure:function(){b.stop()},showLoadingOverlay:false,suppressErrors:true})},(a*1000))}});WCF.InlineEditor=Class.extend({_callbacks:[],_dropdowns:{},_elements:{},_notification:null,_options:[],_proxy:null,_updateData:[],init:function(d){var c=$(d);if(!c.length){return}this._setOptions();var b="";for(var f=0,e=this._options.length;f<e;f++){if(this._options[f].isQuickOption){b=this._options[f].optionName;break}}var a=this;c.each(function(i,j){var h=$(j);var k=h.wcfIdentify();var g=a._getTriggerElement(h);if(g===null||g.length!==1){return}g.click($.proxy(a._show,a)).data("elementID",k);if(b){g.disableSelection().data("optionName",b).dblclick($.proxy(a._click,a))}a._elements[k]=h});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")},_closeAll:function(){for(var a in this._elements){this._hide(a)}},_setOptions:function(){this._options=[]},registerCallback:function(a){if($.isFunction(a)){this._callbacks.push(a)}},_getTriggerElement:function(a){return null},_show:function(b){var g=$(b.currentTarget).data("elementID");var h=null;if(!this._dropdowns[g]){h=this._getTriggerElement(this._elements[g]).addClass("dropdownToggle").wrap('<span class="dropdown" />');this._dropdowns[g]=$('<ul class="dropdownMenu" />').insertAfter(h)}this._dropdowns[g].empty();var c=false;var d="";for(var k=0,i=this._options.length;k<i;k++){var a=this._options[k];if(a.optionName==="divider"){if(d!==""&&d!=="divider"){$('<li class="dropdownDivider" />').appendTo(this._dropdowns[g]);d=a.optionName}}else{if(this._validate(g,a.optionName)||this._validateCallbacks(g,a.optionName)){var f=$("<li><span>"+a.label+"</span></li>").appendTo(this._dropdowns[g]);f.data("elementID",g).data("optionName",a.optionName).data("isQuickOption",(a.isQuickOption?true:false)).click($.proxy(this._click,this));c=true;d=a.optionName}}}if(c){var l=this._dropdowns[g].children().last();if(l.hasClass("dropdownDivider")){l.remove()}var j=null;var e=0;this._dropdowns[g].children().each(function(m,o){var n=$(o);if(!n.hasClass("dropdownDivider")){if(n.data("isQuickOption")){j=n}else{e++}}});if(!e){j.trigger("click");if(h!==null){WCF.Dropdown.close(h.parents(".dropdown").wcfIdentify())}return false}}if(h!==null){WCF.Dropdown.initDropdown(h,true)}return false},_validate:function(a,b){return false},_validateCallbacks:function(a,b){var c=this._callbacks.length;if(c){for(var d=0;d<c;d++){if(this._callbacks[d].validate(this._elements[a],b)){return true}}}return false},_success:function(b,d,a){var c=this._updateData.length;if(!c){return}this._updateState(b);this._updateData=[]},_updateState:function(a){},_click:function(c){var b=$(c.currentTarget);var d=b.data("elementID");var a=b.data("optionName");if(!this._execute(d,a)){this._executeCallback(d,a)}this._hide(d)},_execute:function(a,b){return false},_executeCallback:function(a,b){var c=this._callbacks.length;if(c){for(var d=0;d<c;d++){if(this._callbacks[d].execute(this._elements[a],b)){return true}}}return false},_hide:function(a){if(this._dropdowns[a]){this._dropdowns[a].empty().removeClass("dropdownOpen")}}});WCF.Upload=Class.extend({_name:"__files[]",_buttonSelector:null,_fileListSelector:null,_fileUpload:null,_className:"",_iframe:null,_internalFileID:0,_options:{},_uploadMatrix:[],_supportsAJAXUpload:true,_overlay:null,init:function(c,e,d,b){this._buttonSelector=c;this._fileListSelector=e;this._className=d;this._internalFileID=0;this._options=$.extend(true,{action:"upload",multiple:false,url:"index.php/AJAXUpload/?t="+SECURITY_TOKEN+SID_ARG_2ND},b||{});var a=new XMLHttpRequest();this._supportsAJAXUpload=(a&&("upload" in a)&&("onprogress" in a.upload));this._createButton()},_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 a=$('<p class="button uploadButton"><span>'+WCF.Language.get("wcf.global.button.upload")+"</span></p>");a.prepend(this._fileUpload)}else{var a=$('<p class="button uploadFallbackButton"><span>'+WCF.Language.get("wcf.global.button.upload")+"</span></p>");a.click($.proxy(this._showOverlay,this))}this._insertButton(a)},_insertButton:function(a){this._buttonSelector.append(a)},_removeButton:function(){var a=".uploadButton";if(!this._supportsAJAXUpload){a=".uploadFallbackButton"}this._buttonSelector.find(a).remove()},_upload:function(){var a=this._fileUpload.prop("files");if(a.length){var i=new FormData();var f=this._createUploadMatrix(a);if(!this._uploadMatrix[f].length){return}for(var d=0,c=a.length;d<c;d++){if(this._uploadMatrix[f][d]){var e=this._uploadMatrix[f][d].data("internalFileID");i.append("__files["+e+"]",a[d])}}i.append("actionName",this._options.action);i.append("className",this._className);var b=this._getParameters();for(var h in b){i.append("parameters["+h+"]",b[h])}var g=this;$.ajax({type:"POST",url:this._options.url,enctype:"multipart/form-data",data:i,contentType:false,processData:false,success:function(k,l,j){g._success(f,k)},error:$.proxy(this._error,this),xhr:function(){var j=$.ajaxSettings.xhr();if(j){j.upload.addEventListener("progress",function(k){g._progress(f,k)},false)}return j}})}},_createUploadMatrix:function(b){if(b.length){var c=this._uploadMatrix.length;this._uploadMatrix[c]=[];for(var f=0,d=b.length;f<d;f++){var a=b[f];var e=this._initFile(a);if(!e.hasClass("uploadFailed")){e.data("filename",a.name).data("internalFileID",this._internalFileID++);this._uploadMatrix[c][f]=e}}return c}return null},_success:function(b,a){},_error:function(a,c,b){},_progress:function(c,a){var b=Math.round(a.loaded*100/a.total);for(var d in this._uploadMatrix[c]){this._uploadMatrix[c][d].find("progress").attr("value",b)}},_getParameters:function(){return{}},_initFile:function(a){return $("<li>"+a.name+" ("+a.size+')<progress max="100" /></li>').appendTo(this._fileListSelector)},_showOverlay:function(){if(this._iframe===null){this._iframe=$('<iframe name="__fileUploadIFrame" />').hide().appendTo(document.body)}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 a=this._overlay.find("form");$('<dl class="wide"><dd><input type="file" id="__fileUpload" name="'+this._name+'" '+(this._options.multiple?'multiple="true" ':"")+"/></dd></dl>").appendTo(a);$('<div class="formSubmit"><input type="submit" value="Upload" accesskey="s" /></div></form>').appendTo(a);$('<input type="hidden" name="isFallback" value="1" />').appendTo(a);$('<input type="hidden" name="actionName" value="'+this._options.action+'" />').appendTo(a);$('<input type="hidden" name="className" value="'+this._className+'" />').appendTo(a);var b=this._getParameters();for(var c in b){$('<input type="hidden" name="'+c+'" value="'+b[c]+'" />').appendTo(a)}a.submit($.proxy(function(){var d={name:this._getFilename(),size:""};var f=this._createUploadMatrix([d]);var e=this;this._iframe.data("loading",true).off("load").load(function(){e._evaluateResponse(f)});this._overlay.wcfDialog("close")},this))}this._overlay.wcfDialog({title:WCF.Language.get("wcf.global.button.upload")})},_evaluateResponse:function(b){var a=$.parseJSON(this._iframe.contents().find("pre").html());this._success(b,a)},_getFilename:function(){return $("#__fileUpload").val().split("\\").pop()}});WCF.Upload.Parallel=WCF.Upload.extend({init:function(b,d,c,a){a=$.extend(true,a||{},{multiple:true});this._super(b,d,c,a)},_upload:function(){var b=this._fileUpload.prop("files");for(var h=0,e=b.length;h<e;h++){var a=b[h];var c=new FormData();var g=this._createUploadMatrix(a);if(!this._uploadMatrix[g].length){continue}c.append("__files["+g+"]",a);c.append("actionName",this._options.action);c.append("className",this._className);var d=this._getParameters();for(var f in d){c.append("parameters["+f+"]",d[f])}this._sendRequest(g,c)}},_sendRequest:function(c,b){var a=this;$.ajax({type:"POST",url:this._options.url,enctype:"multipart/form-data",data:b,contentType:false,processData:false,success:function(e,f,d){a._success(c,e)},error:$.proxy(this._error,this),xhr:function(){var d=$.ajaxSettings.xhr();if(d){d.upload.addEventListener("progress",function(e){a._progress(c,e)},false)}return d}})},_createUploadMatrix:function(a){var b=this._initFile(a);if(!b.hasClass("uploadFailed")){b.data("filename",a.name).data("internalFileID",this._internalFileID);this._uploadMatrix[this._internalFileID++]=b;return this._internalFileID-1}return null},_success:function(b,a){},_progress:function(b,a){var c=Math.round(a.loaded*100/a.total);this._uploadMatrix[b].find("progress").attr("value",c)},_showOverlay:function(){if(this._iframe===null){this._iframe=$('<iframe name="__fileUploadIFrame" />').hide().appendTo(document.body)}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 a=this._overlay.find("form");$('<dl class="wide"><dd><input type="file" id="__fileUpload" name="'+this._name+'" '+(this._options.multiple?'multiple="true" ':"")+"/></dd></dl>").appendTo(a);$('<div class="formSubmit"><input type="submit" value="Upload" accesskey="s" /></div></form>').appendTo(a);$('<input type="hidden" name="isFallback" value="1" />').appendTo(a);$('<input type="hidden" name="actionName" value="'+this._options.action+'" />').appendTo(a);$('<input type="hidden" name="className" value="'+this._className+'" />').appendTo(a);var b=this._getParameters();for(var c in b){$('<input type="hidden" name="'+c+'" value="'+b[c]+'" />').appendTo(a)}a.submit($.proxy(function(){var d={name:this._getFilename(),size:""};var f=this._createUploadMatrix(d);var e=this;this._iframe.data("loading",true).off("load").load(function(){e._evaluateResponse(f)});this._overlay.wcfDialog("close")},this))}this._overlay.wcfDialog({title:WCF.Language.get("wcf.global.button.upload")})},_evaluateResponse:function(b){var a=$.parseJSON(this._iframe.contents().find("pre").html());this._success(b,a)}});WCF.Sortable={};WCF.Sortable.List=Class.extend({_additionalParameters:{},_className:"",_containerID:"",_container:null,_notification:null,_offset:0,_options:{},_proxy:null,_structure:{},init:function(a,f,g,d,e,c){this._additionalParameters=c||{};this._containerID=$.wcfEscapeID(a);this._container=$("#"+this._containerID);this._className=f;this._offset=(g)?g:0;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._structure={};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:0.6,placeholder:"sortablePlaceholder",tolerance:"pointer",toleranceElement:"> span"},d||{});if(e){$("#"+this._containerID+" .sortableList").sortable(this._options)}else{$("#"+this._containerID+" > .sortableList").nestedSortable(this._options)}if(this._className){var b=this._container.find(".formSubmit");if(!b.length){b=this._container.next(".formSubmit");if(!b.length){console.debug("[WCF.Sortable.Simple] Unable to find form submit for saving, aborting.");return}}b.children('button[data-type="submit"]').click($.proxy(this._submit,this))}},_submit:function(){this._structure={};this._container.find(".sortableList").each($.proxy(function(c,e){var b=$(e);var d=b.data("objectID");if(d!==undefined){b.children(this._options.items).each($.proxy(function(f,h){var g=$(h).data("objectID");if(!this._structure[d]){this._structure[d]=[]}this._structure[d].push(g)},this))}},this));var a=$.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:a});this._proxy.sendRequest()},_success:function(b,c,a){if(this._notification===null){this._notification=new WCF.System.Notification(WCF.Language.get("wcf.global.success.edit"))}this._notification.show()}});WCF.Popover=Class.extend({_activeElementID:"",_cancelPopover:false,_data:{},_defaultDimensions:{height:150,width:450},_defaultOrientation:{x:"right",y:"top"},_delay:{show:800,hide:500},_hoverElement:false,_hoverElementID:"",_hoverPopover:false,_margin:20,_peOut:null,_peOverElement:null,_popover:null,_popoverContent:null,_popoverOffset:10,_selector:"",init:function(a){if($.browser.mobile){return}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=a;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."+a,$.proxy(this._initContainers,this))},_initContainers:function(){if($.browser.mobile){return}var a=$(this._selector);if(!a.length){return}a.each($.proxy(function(c,d){var b=$(d);var e=b.wcfIdentify();if(!this._data[e]){this._data[e]={content:null,isLoading:false};b.hover($.proxy(this._overElement,this),$.proxy(this._out,this));if(b.is("a")&&b.attr("href")){b.click($.proxy(this._cancel,this))}}},this))},_cancel:function(a){this._cancelPopover=true;this._hide(true)},_overElement:function(a){if(this._cancelPopover){return}if(this._peOverElement!==null){this._peOverElement.stop()}var b=$(a.currentTarget).wcfIdentify();this._hoverElementID=b;this._peOverElement=new WCF.PeriodicalExecuter($.proxy(function(c){c.stop();if(this._hoverElementID===b){this._activeElementID=b;this._prepare()}},this),this._delay.show);this._hoverElement=true;this._hoverPopover=false},_prepare:function(){if(this._cancelPopover){return}if(this._peOut!==null){this._peOut.stop()}if(this._popover.is(":visible")){this._hide(true)}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}var a=this._popover.show().getDimensions();if(this._data[this._activeElementID].loading){a={height:Math.max(a.height,this._defaultDimensions.height),width:Math.max(a.width,this._defaultDimensions.width)}}else{a=this._fixElementDimensions(this._popover,a)}this._popover.hide();var b=this._getOrientation(a.height,a.width);this._popover.css(this._getCSS(b.x,b.y));this._popover.removeClass("bottom left right top").addClass(b.x).addClass(b.y);this._show()},_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})}},_loadContent:function(){},_insertContent:function(a,e,c){this._data[a]={content:e,loading:false};if(this._activeElementID===a){if(c){var d=this._popoverContent.getDimensions();this._popoverContent.css({height:"auto",width:"auto"});this._popoverContent.html(this._data[a].content);var f=this._popoverContent.getDimensions();this._popoverContent.html("").css({height:d.height+"px",width:d.width+"px"});var b=this;this._popoverContent.animate({height:f.height+"px",width:f.width+"px"},300,function(){b._popover.children("span").hide();b._popoverContent.html(b._data[a].content).css({opacity:0}).animate({opacity:1},200);WCF.DOMNodeInsertedHandler.execute()})}else{this._popover.children("span").hide();this._popoverContent.html(this._data[a].content);WCF.DOMNodeInsertedHandler.execute()}}},_hide:function(b){var a=this;this._popoverContent.stop();this._popover.stop();if(b){a._popover.css({opacity:0}).hide();a._popoverContent.empty().css({height:"auto",opacity:0,width:"auto"})}else{this._popover.wcfFadeOut(function(){a._popoverContent.empty().css({height:"auto",opacity:0,width:"auto"});a._popover.hide()})}},_overPopover:function(){if(this._peOut!==null){this._peOut.stop()}this._hoverElement=false;this._hoverPopover=true},_out:function(a){if(this._cancelPopover){return}this._hoverElementID="";this._hoverElement=false;this._hoverPopover=false;this._peOut=new WCF.PeriodicalExecuter($.proxy(function(b){b.stop();if(!this._hoverElement&&!this._hoverPopover){this._hide(false)}},this),this._delay.hide)},_getOrientation:function(i,a){var h=$("#"+this._activeElementID);var g=h.getOffsets("offset");var f=h.getDimensions();var b=$(document).getDimensions();var e=(this._defaultOrientation.x==="left")?"left":"right";var d=(this._defaultOrientation.y==="bottom")?"bottom":"top";var c=this._evaluateOrientation(e,d,g,f,b,i,a);if(c.flawed){e=(e==="left")?"right":"left";c=this._evaluateOrientation(e,d,g,f,b,i,a);if(c.flawed){e=(e==="right")?"left":"right";d=(d==="bottom")?"top":"bottom";c=this._evaluateOrientation(e,d,g,f,b,i,a);if(c.flawed){e=(e==="left")?"right":"left";c=this._evaluateOrientation(e,d,g,f,b,i,a);if(c.flawed){e=(this._defaultOrientationX==="left")?"left":"right";d=(this._defaultOrientationY==="bottom")?"bottom":"top"}}}}return{x:e,y:d}},_evaluateOrientation:function(g,f,b,h,j,i,a){var d=0,e=0;switch(g){case"left":e=b.left-a;break;case"right":e=j.width-(b.left+a);break}switch(f){case"bottom":d=j.height-(b.top+h.height+this._popoverOffset+i);break;case"top":d=b.top-(i-this._popoverOffset);break}var c=false;if(d<this._margin||e<this._margin){c=true}return{flawed:c,x:e,y:d}},_getCSS:function(d,c){var g={bottom:"auto",left:"auto",right:"auto",top:"auto"};var b=$("#"+this._activeElementID);var e=b.getOffsets("offset");var f=this._fixElementDimensions(b,b.getDimensions());var a=$(window).getDimensions();switch(d){case"left":g.right=a.width-(e.left+f.width);break;case"right":g.left=e.left;break}switch(c){case"bottom":g.top=e.top+(f.height+this._popoverOffset);break;case"top":g.bottom=a.height-(e.top-this._popoverOffset);break}return g},_fixElementDimensions:function(a,b){var c=a.parent().getDimensions();if(c.height<b.height){b.height=c.height}if(c.width<b.width){b.width=c.width}return b}});WCF.EditableItemList=Class.extend({_allowCustomInput:false,_className:"",_data:{},_form:null,_itemList:null,_objectID:0,_objectTypeID:0,_search:null,_searchInput:null,init:function(c,a){this._itemList=$(c);this._searchInput=$(a);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();this._itemList.find(".jsEditableItem").click($.proxy(this._click,this));if(!this._itemList.children("ul").length){$("<ul />").appendTo(this._itemList)}this._itemList=this._itemList.children("ul");this._form=this._itemList.parents("form").submit($.proxy(this._submit,this));if(this._allowCustomInput){var b=this;this._searchInput.keydown($.proxy(this._keyDown,this)).on("paste",function(){setTimeout(function(){b._onPaste()},100)})}this._searchInput.parents(".dropdown").data("preventSubmit",true)},_keyDown:function(b){if(b===null||b.which===188||b.which===$.ui.keyCode.ENTER){if(b!==null&&b.which===$.ui.keyCode.ENTER&&this._search){if(this._search._itemIndex!==-1){return false}}var a=$.trim(this._searchInput.val());if(b&&b.which===188){a=a.substring(0,this._searchInput.getCaret())}if(a===""){return true}this.addItem({objectID:0,label:a});if(b&&b.which===188){this._searchInput.val($.trim(this._searchInput.val().substr(this._searchInput.getCaret())))}else{this._searchInput.val("")}if(b!==null){b.stopPropagation()}return false}return true},_onPaste:function(){var b=$.trim(this._searchInput.val());b=b.split(",");for(var d=0,c=b.length;d<c;d++){var a=$.trim(b[d]);if(a===""){continue}this.addItem({objectID:0,label:a})}this._searchInput.val("")},load:function(a){},_click:function(d){var b=$(d.currentTarget);var c=b.data("objectID");var a=b.data("label");if(this._search){this._search.removeExcludedSearchValue(a)}this._removeItem(c,a);b.remove();d.stopPropagation();return false},_getObjectID:function(){return 0},_getObjectTypeID:function(){return 0},addItem:function(b){if(this._data[b.objectID]){if(!(b.objectID===0&&this._allowCustomInput)){return true}}var a=$('<li class="badge">'+WCF.String.escapeHTML(b.label)+"</li>").data("objectID",b.objectID).data("label",b.label).appendTo(this._itemList);a.click($.proxy(this._click,this));if(this._search){this._search.addExcludedSearchValue(b.label)}this._addItem(b.objectID,b.label);return true},clearList:function(){this._itemList.children("li").each($.proxy(function(b,c){var a=$(c);if(this._search){this._search.removeExcludedSearchValue(a.data("label"))}a.remove();this._removeItem(a.data("objectID"),a.data("label"))},this))},_submit:function(){this._keyDown(null)},_addItem:function(b,a){this._data[b]=a},_removeItem:function(b,a){delete this._data[b]},getSearchInput:function(){return this._searchInput}});WCF.Sitemap=Class.extend({_cache:[],_dialog:null,_didInit:false,_proxy:null,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)})},_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")}},_success:function(b,c,a){if(this._didInit){this._cache.push(b.returnValues.sitemapName);this._dialog.find("#sitemap_"+b.returnValues.sitemapName).html(b.returnValues.template);this._dialog.wcfDialog("render")}else{this._cache.push(b.returnValues.sitemapName);this._dialog.html(b.returnValues.template);this._dialog.find(".sitemapNavigation").click($.proxy(this._navigate,this));this._dialog.find(".tabMenuContainer").wcfTabs("select","sitemap_"+b.returnValues.sitemapName);this._dialog.wcfDialog({title:WCF.Language.get("wcf.page.sitemap")});this._didInit=true}},_navigate:function(a){var b=$(a.currentTarget).data("sitemapName");if(WCF.inArray(b,this._cache)){this._dialog.find(".tabMenuContainer").wcfTabs("select","sitemap_"+b);this._dialog.wcfDialog("render")}else{this._proxy.setOption("data",{actionName:"getSitemap",className:"wcf\\data\\sitemap\\SitemapAction",parameters:{sitemapName:b}});this._proxy.sendRequest()}}});WCF.Language.Chooser=Class.extend({_callback:null,_dropdown:null,_input:null,init:function(l,g,f,b,k,e){var m=$("#"+l);if(m.length!=1){console.debug("[WCF.Language.Chooser] Invalid container id '"+l+"' given");return}this._input=$("#"+g);if(!this._input.length){this._input=$('<input type="hidden" name="'+g+'" value="'+f+'" />').appendTo(m)}if(k!==undefined){if(!$.isFunction(k)){console.debug("[WCF.Language.Chooser] Given callback is invalid");return}this._callback=k}this._dropdown=$('<div class="dropdown" id="'+l+'-languageChooser" />').appendTo(m);$('<div class="dropdownToggle boxFlag box24" data-toggle="'+l+'-languageChooser"></div>').appendTo(this._dropdown);var c=$('<ul class="dropdownMenu" />').appendTo(this._dropdown);for(var a in b){var i=b[a];var h=$('<li class="boxFlag"><a class="box24"><div class="framed"><img src="'+i.iconPath+'" alt="" class="iconFlag" /></div> <div><h3>'+i.languageName+"</h3></div></a></li>").appendTo(c);h.data("languageID",a).click($.proxy(this._click,this));if(a==f){var d=$(""+h.html());var j=d.children().detach();this._dropdown.children(".dropdownToggle").empty().append(j)}}if(e){$('<li class="dropdownDivider" />').appendTo(c);var h=$("<li><a>"+WCF.Language.get("wcf.global.language.noSelection")+"</a></li>").data("languageID",0).click($.proxy(this._click,this)).appendTo(c);if(f===0){this._dropdown.children(".dropdownToggle").empty().append(h.html())}}WCF.Dropdown.init()},_click:function(e){var d=$(e.currentTarget);var c=d.data("languageID");this._input.val(c);var b=$(""+d.html());var a=(c===0)?b:b.children().detach();this._dropdown.children(".dropdownToggle").empty().append(a);if(this._callback!==null){this._callback(d)}}});WCF.Style={};WCF.Style.Chooser=Class.extend({_dialog:null,_proxy:null,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)})},_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")})}},_loadDialog:function(){this._proxy.setOption("data",{actionName:"getStyleChooser",className:"wcf\\data\\style\\StyleAction"});this._proxy.sendRequest()},_success:function(b,c,a){if(b.actionName==="changeStyle"){window.location.reload();return}this._dialog.html(b.returnValues.template);this._dialog.find("li").addClass("pointer").click($.proxy(this._click,this));this._showDialog()},_click:function(a){this._proxy.setOption("data",{actionName:"changeStyle",className:"wcf\\data\\style\\StyleAction",objectIDs:[$(a.currentTarget).data("styleID")]});this._proxy.sendRequest()}});WCF.UserPanel=Class.extend({_container:null,_didLoad:false,_link:null,_noItems:"",_revertOnEmpty:true,init:function(a){this._container=$("#"+a);this._didLoad=false;this._revertOnEmpty=true;if(this._container.length!=1){console.debug("[WCF.UserPanel] Unable to find container identfied by '"+a+"', aborting.");return}this._convert()},_convert:function(){this._container.addClass("dropdown");this._link=this._container.children("a").remove();var b=$('<a class="dropdownToggle">'+this._link.html()+"</a>").appendTo(this._container).click($.proxy(this._click,this));var a=$('<ul class="dropdownMenu" />').appendTo(this._container);$('<li class="jsDropdownPlaceholder"><span>'+WCF.Language.get("wcf.global.loading")+"</span></li>").appendTo(a);this._addDefaultItems(a);this._container.dblclick($.proxy(function(){window.location=this._link.attr("href");return false},this));WCF.Dropdown.initDropdown(b,false)},_addDefaultItems:function(a){},_addDivider:function(a){$('<li class="dropdownDivider" />').appendTo(a)},_click:function(){if(this._didLoad){return}new WCF.Action.Proxy({autoSend:true,data:this._getParameters(),success:$.proxy(this._success,this)});this._didLoad=true},_getParameters:function(){return{}},_success:function(c,e,b){var a=WCF.Dropdown.getDropdownMenu(this._container.wcfIdentify());a.children(".jsDropdownPlaceholder").remove();if(c.returnValues&&c.returnValues.template){$(""+c.returnValues.template).prependTo(a);var d=this._container.find(".badge");if(!d.length){d=$('<span class="badge badgeInverse" />').appendTo(this._container.children(".dropdownToggle"));d.before(" ")}d.html(c.returnValues.totalCount);this._after(a)}else{$("<li><span>"+WCF.Language.get(this._noItems)+"</span></li>").prependTo(a);this._container.find(".badge").remove()}},_after:function(a){}});$.widget("ui.wcfDialog",{_closeButton:null,_container:null,_content:null,_overlay:null,_title:null,_titlebar:null,_isOpen:false,options:{autoOpen:true,closable:true,closeButtonLabel:null,closeConfirmMessage:null,closeViaModal:true,hideTitle:false,modal:true,title:"",zIndex:400,onClose:null,onShow:null},_createWidget:function(a,b){if($(b).getTagName()==="script"){console.debug("[ui.wcfDialog] Ignored script tag");this.element=false;return null}$.Widget.prototype._createWidget.apply(this,arguments)},_init:function(){if(this.options.autoOpen){this.open()}$(window).resize($.proxy(this._resize,this))},_create:function(){if(this.options.closeButtonLabel===null){this.options.closeButtonLabel=WCF.Language.get("wcf.global.button.close")}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);var a=this.element.detach();this._content.html(a);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(b){if(b.keyCode&&b.keyCode===$.ui.keyCode.ESCAPE){this.close();b.preventDefault()}},this))}}WCF.DOMNodeInsertedHandler.execute()},_setOption:function(a,b){this.options[a]=b;if(a=="hideTitle"||a=="title"){if(!this.options.hideTitle&&this.options.title!=""){this._title.html(this.options.title).show()}else{this._title.html("")}}else{if(a=="closable"||a=="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},open:function(){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},isOpen:function(){return this._isOpen},close:function(a){if(!this.isOpen()){return}if(this.options.closeConfirmMessage){WCF.System.Confirmation.show(this.options.closeConfirmMessage,$.proxy(function(b){if(b==="confirm"){this._close()}},this))}else{this._close()}if(a!==undefined){a.preventDefault()}},_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()}},_resize:function(){if(this.isOpen()){this.render()}},render:function(){if(!this._container.is(":visible")&&this._container.css("top")==="0px"){window.scrollTo(0,0)}this._container.show();this._content.children().show();this._content.css({height:"auto",width:"auto"});this._container.stop();this._content.stop();this._container.show().css("opacity",1);var e=0;if(this._content.find(".formSubmit").length){e=this._content.find(".formSubmit").outerHeight();this._content.addClass("dialogForm").css({marginBottom:e+"px"})}else{this._content.removeClass("dialogForm").css({marginBottom:"0px"})}var b=$(window).getDimensions();if(b.width*0.9>800){this._container.css("maxWidth","800px")}var h=this._container.getDimensions("outer");var d=this._content.getDimensions();var e=h.height-d.height;var g=b.height-e-120;this._content.css({maxHeight:g+"px"});this._determineOverflow();h=this._container.getDimensions("outer");var f=Math.round((b.width-h.width)/2);var a=Math.round((b.height-h.height)/2);var c=Math.round((b.height/100)*20);if(c<a){a=c}this._container.css({left:f+"px",top:a+"px"});this._content.css({height:"auto",width:"auto"});if(!this.isOpen()){this._container.hide();this._container.wcfFadeIn($.proxy(function(){if(this.options.onShow!==null){this.options.onShow()}},this))}},_determineOverflow:function(){var b=$(window).getDimensions();var c=this._content.css("maxHeight");this._content.css("maxHeight","none");var e=this._container.getDimensions("outer");var d="visible";if((b.height*0.8<e.height)||(b.width*0.8<e.width)){d="auto"}this._content.css("overflow",d);this._content.css("maxHeight",c);if(d==="visible"){var a=0;this._content.children().each(function(f,g){a+=$(g).outerHeight()});if(this._content.height()<a){this._content.css("overflow","auto")}}},_getContentDimensions:function(a){var b=this._content.getDimensions();if(a&&b.height>a){b.height=a}return b}});$.widget("ui.wcfSlideshow",{_buttonList:null,_count:0,_index:0,_itemList:null,_items:null,_timer:null,_width:0,options:{cycle:true,cycleInterval:5,itemGap:50,},_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()}},_initSlideshow:function(){var b=$(this._items.get(0)).outerHeight();this._items.addClass("slideshowItem");this._width=this.element.css("height",b).innerWidth();this._itemList.addClass("slideshowItemList").css("left",0);this._items.each($.proxy(function(d,e){$(e).show().css({height:b,left:((this._width+this.options.itemGap)*d),width:this._width})},this));this.element.css({height:b,width:this._width}).hover($.proxy(this._hoverIn,this),$.proxy(this._hoverOut,this));this._buttonList=$('<ul class="slideshowButtonList" />').appendTo(this.element);for(var c=0;c<this._count;c++){var a=$('<li><a><span class="icon icon16 icon-circle" /></a></li>').data("index",c).click($.proxy(this._click,this)).appendTo(this._buttonList);if(c==0){a.find(".icon").addClass("active")}}this._resetTimer();$(window).resize($.proxy(this._resize,this))},_resize:function(){this._width=this.element.css("width","auto").innerWidth();this._items.each($.proxy(function(a,b){$(b).css({left:((this._width+this.options.itemGap)*a),width:this._width})},this));this._index--;this.moveTo(null)},_hoverIn:function(){if(this._timer!==null){this._timer.stop()}},_hoverOut:function(){this._resetTimer()},_resetTimer:function(){if(!this.options.cycle){return}if(this._timer!==null){this._timer.stop()}var a=this;this._timer=new WCF.PeriodicalExecuter(function(){a.moveTo(null)},this.options.cycleInterval*1000)},_click:function(a){this.moveTo($(a.currentTarget).data("index"));this._resetTimer()},moveTo:function(a){this._index=(a===null)?this._index+1:a;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})},getItem:function(a){if(this._items[a]){return this._items[a]}return null}});$.widget("ui.wcfTabs",$.ui.tabs,{_sanitizeSelector:function(a){return a.replace(/([:\.])/g,"\\$1")},select:function(a){if(!$.isNumeric(a)){this.panels.each(function(c,b){if($(b).wcfIdentify()===a){a=c;return false}});if(!$.isNumeric(a)){console.debug("[ui.wcfTabs] Unable to find panel identified by '"+a+"', aborting.");return}}this._setOption("active",a)},selectTab:function(a){a="#"+a;this.anchors.each(function(c,b){var d=$(b);if(d.prop("hash")===a){d.trigger("click");return false}})},getCurrentIndex:function(){return this.lis.index(this.lis.filter(".ui-tabs-selected"))},hasAnchor:function(b,a){var c=false;this.anchors.each(function(e,d){var g=$(d).attr("href");if(/#.+/.test(g)){var f=g.split("#",2);if(a){f=f[1].split("-",2)}if(f[1]===b){c=true;return false}}});return c},revertToDefault:function(){var a=this.element.data("active");if(!a||a===""){a=0}this.select(a)},_processTabs:function(){var a=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(f,e){var b,c,d=$(e).uniqueId().attr("id"),g=$(e).closest("li"),h=g.attr("aria-controls");b=e.hash;c=a.element.find(a._sanitizeSelector(b));if(c.length){a.panels=a.panels.add(c)}if(h){g.data("ui-tabs-aria-controls",h)}g.attr({"aria-controls":b.substring(1),"aria-labelledby":d});c.attr("aria-labelledby",d)});this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").attr("role","tabpanel")},load:function(a,b){return}});$.widget("ui.wcfPages",{SHOW_LINKS:11,SHOW_SUB_LINKS:20,options:{activePage:1,maxPage:1,nextPage:null,previousPage:null},_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()},destroy:function(){$.Widget.prototype.destroy.apply(this,arguments);this.element.children().remove()},_render:function(){if(!this.options.disabled&&this.options.maxPage>1){var g=false;if(this.element.hasClass("hidden")){this.element.removeClass("hidden")}this.element.show();this.element.children().remove();var o=$("<ul />");this.element.append(o);var n=$('<li class="button skip" />');o.append(n);if(this.options.activePage>1){var d=$("<a"+((this.options.previousPage!=null)?(' title="'+this.options.previousPage+'"'):(""))+"></a>");n.append(d);this._bindSwitchPage(d,this.options.activePage-1);var l=$('<span class="icon icon16 icon-double-angle-left" />');d.append(l)}else{var l=$('<span class="icon icon16 icon-double-angle-left" />');n.append(l);n.addClass("disabled").removeClass("button");l.addClass("disabled")}o.append(this._renderLink(1));var a=this.SHOW_LINKS-4;var e=this.options.activePage-2;if(e<0){e=0}var i=this.options.maxPage-(this.options.activePage+1);if(i<0){i=0}if(this.options.activePage>1&&this.options.activePage<this.options.maxPage){a--}var k=a/2;var c=this.options.activePage;var j=this.options.activePage;if(c<1){c=1}if(j<1){j=1}if(j>this.options.maxPage-1){j=this.options.maxPage-1}if(e>=k){c-=k}else{c-=e;j+=k-e}if(i>=k){j+=k}else{j+=i;c-=k-i}j=Math.ceil(j);c=Math.ceil(c);if(c<1){c=1}if(j>this.options.maxPage){j=this.options.maxPage}if(c>1){if(c-1<2){o.append(this._renderLink(2))}else{$('<li class="button jumpTo"><a title="'+WCF.Language.get("wcf.global.page.jumpTo")+'" class="jsTooltip">...</a></li>').appendTo(o);g=true}}for(var m=c+1;m<j;m++){o.append(this._renderLink(m))}if(j<this.options.maxPage){if(this.options.maxPage-j<2){o.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(o);g=true}}o.append(this._renderLink(this.options.maxPage));var h=$('<li class="button skip" />');o.append(h);if(this.options.activePage<this.options.maxPage){var f=$("<a"+((this.options.nextPage!=null)?(' title="'+this.options.nextPage+'"'):(""))+"></a>");h.append(f);this._bindSwitchPage(f,this.options.activePage+1);var b=$('<span class="icon icon16 icon-double-angle-right" />');f.append(b)}else{var b=$('<span class="icon icon16 icon-double-angle-right" />');h.append(b);h.addClass("disabled").removeClass("button");b.addClass("disabled")}if(g){o.data("pages",this.options.maxPage);WCF.System.PageNavigation.init("#"+o.wcfIdentify(),$.proxy(function(p){this.switchPage(p)},this))}}else{this.element.hide()}},_renderLink:function(d,a){var e=$('<li class="button"></li>');if(a!=undefined&&a){e.addClass("break")}if(d!=this.options.activePage){var b=$("<a>"+WCF.String.addThousandsSeparator(d)+"</a>");e.append(b);this._bindSwitchPage(b,d)}else{e.addClass("active");var c=$("<span>"+WCF.String.addThousandsSeparator(d)+"</span>");e.append(c)}return e},_bindSwitchPage:function(a,b){var c=this;a.click(function(){c.switchPage(b)})},switchPage:function(a){this._setOption("activePage",a)},_setOption:function(b,c){if(b=="activePage"){if(c!=this.options[b]&&c>0&&c<=this.options.maxPage){var a=this._trigger("shouldSwitch",undefined,{nextPage:c});if(a||a!==undefined){this.options[b]=c;this._render();this._trigger("switched",undefined,{activePage:c})}else{this._trigger("notSwitched",undefined,{activePage:c})}}}else{this.options[b]=c;if(b=="disabled"){if(c){this.element.children().remove()}else{this._render()}}else{if(b=="maxPage"){this._render()}}}return this},_startInput:function(b){var a=$(b.currentTarget);if(!a.is("a")){a=a.parent("a")}a.hide();var c=a.parent("li").children("input").css("display","block").val("");c.focus()},_stopInput:function(b){var c=$(b.currentTarget);c.css("display","none");var a=c.parent("li");if(a!=undefined&&a!=null){a.children("a").show()}},_handleInput:function(b){var a=($.browser.msie&&$.browser.version=="7.0");if(b.type!="keyup"||a){if(!a||((b.which==13||b.which==27)&&b.type=="keyup")){if(b.which==13){this.switchPage(parseInt($(b.currentTarget).val()))}if(b.which==13||b.which==27){this._stopInput(b);b.stopPropagation()}}}}});WCF.Category={};WCF.Category.NestedList=Class.extend({_categories:{},init:function(){var a=this;$(".jsCategory").each(function(c,d){var b=$(d).data("parentCategoryID",null).change($.proxy(a._updateSelection,a));a._categories[b.val()]=b;var e=[];b.parents("li").find(".jsChildCategory").each(function(h,g){var f=$(g).data("parentCategoryID",b.val()).change($.proxy(a._updateSelection,a));a._categories[f.val()]=f;e.push(f.val());if(f.is(":checked")){b.prop("checked","checked")}});b.data("childCategoryIDs",e)})},_updateSelection:function(b){var a=$(b.currentTarget);var f=a.data("parentCategoryID");if(a.is(":checked")){if(f!==null){this._categories[f].prop("checked","checked")}}else{if(f===null){var e=a.data("childCategoryIDs");for(var d=0,c=e.length;d<c;d++){this._categories[e[d]].prop("checked",false)}}}}});function wcfEval(expression){return eval(expression)};
+
+// WCF.Like.js
+WCF.Like=Class.extend({_allowForOwnContent:false,_canLike:false,_containers:{},_containerData:{},_enableDislikes:true,_isBusy:false,_likeDetails:{},_proxy:null,_showSummary:true,init:function(a,c,f,e){this._canLike=a;this._enableDislikes=c;this._isBusy=false;this._likeDetails={};this._showSummary=f;this._allowForOwnContent=e;var d=this._getContainers();this._initContainers(d);this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});var g=new Date();var b=g.toString().hashCode+g.getUTCMilliseconds();WCF.DOMNodeInsertedHandler.addCallback("WCF.Like"+b,$.proxy(this._domNodeInserted,this))},_domNodeInserted:function(){var a=this._getContainers();this._initContainers(a)},_initContainers:function(containers){var $createdWidgets=false;containers.each($.proxy(function(index,container){var $container=$(container);var $containerID=$container.wcfIdentify();if(!this._containers[$containerID]){this._containers[$containerID]=$container;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")};this._createWidget($containerID);$createdWidgets=true}},this));if($createdWidgets){new WCF.PeriodicalExecuter(function(pe){pe.stop();WCF.DOMNodeInsertedHandler.execute()},250)}},_getContainers:function(){},_getWidgetContainer:function(a){},_getObjectID:function(a){},_addWidget:function(a,b){var c=this._getWidgetContainer(a);b.appendTo(c)},_buildWidget:function(b,a,d,c,e){var f=$('<aside class="likesWidget"><ul></ul></aside>');if(this._canLike){a.appendTo(f.find("ul"));d.appendTo(f.find("ul"))}c.appendTo(f);this._addWidget(b,f)},_createWidget:function(b){var e=$('<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 c=$('<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){c.hide()}if(!this._allowForOwnContent&&(WCF.User.userID==this._containers[b].data("userID"))){e=$("");c=$("")}var d=$('<a class="badge jsTooltip likesBadge" />').data("containerID",b).click($.proxy(this._showLikeDetails,this));var a=null;if(this._showSummary){a=$('<p class="likesSummary"><span class="pointer" /></p>');a.children("span").data("containerID",b).click($.proxy(this._showLikeDetails,this))}this._buildWidget(b,e,c,d,a);this._containerData[b].likeButton=e;this._containerData[b].dislikeButton=c;this._containerData[b].badge=d;this._containerData[b].summary=a;e.data("containerID",b).data("type","like").click($.proxy(this._click,this));c.data("containerID",b).data("type","dislike").click($.proxy(this._click,this));this._setActiveState(e,c,this._containerData[b].liked);this._updateBadge(b);if(this._showSummary){this._updateSummary(b)}},_showLikeDetails:function(c,a){var b=(c===null)?a:$(c.currentTarget).data("containerID");if(this._likeDetails[b]===undefined){this._likeDetails[b]=new WCF.User.List("wcf\\data\\like\\LikeAction",WCF.Language.get("wcf.like.details"),{data:{containerID:b,objectID:this._containerData[b].objectID,objectType:this._containerData[b].objectType}})}this._likeDetails[b].open()},_click:function(a){var b=$(a.currentTarget);if(b===null){console.debug("[WCF.Like] Unable to find target button, aborting.");return}this._sendRequest(b.data("containerID"),b.data("type"))},_sendRequest:function(a,b){if(this._isBusy){return}this._isBusy=true;this._proxy.setOption("data",{actionName:b,className:"wcf\\data\\like\\LikeAction",parameters:{data:{containerID:a,objectID:this._containerData[a].objectID,objectType:this._containerData[a].objectType}}});this._proxy.sendRequest()},_success:function(d,g,b){var a=d.returnValues.containerID;if(!this._containers[a]){return}switch(d.actionName){case"dislike":case"like":this._containerData[a].likes=parseInt(d.returnValues.likes);this._containerData[a].dislikes=parseInt(d.returnValues.dislikes);this._containerData[a].users=d.returnValues.users;this._updateBadge(a);if(this._showSummary){this._updateSummary(a)}var f=this._containerData[a].likeButton;var e=this._containerData[a].dislikeButton;var c=0;if(d.returnValues.isLiked){c=1}else{if(d.returnValues.isDisliked){c=-1}}this._setActiveState(f,e,c);if(this._likeDetails[a]!==undefined){delete this._likeDetails[a]}this._isBusy=false;break}},_updateBadge:function(a){if(!this._containerData[a].likes&&!this._containerData[a].dislikes){this._containerData[a].badge.hide()}else{this._containerData[a].badge.show();var c=this._containerData[a].likes-this._containerData[a].dislikes;var d=this._containerData[a].badge;d.removeClass("green red");if(c>0){d.text("+"+WCF.String.formatNumeric(c));d.addClass("green")}else{if(c<0){d.text(WCF.String.formatNumeric(c));d.addClass("red")}else{d.text("\u00B10")}}var e=this._containerData[a].likes;var b=this._containerData[a].dislikes;d.data("tooltip",WCF.Language.get("wcf.like.tooltip",{likes:e,dislikes:b}))}},_updateSummary:function(a){if(!this._containerData[a].likes){this._containerData[a].summary.hide()}else{this._containerData[a].summary.show();var d=this._containerData[a].users;var c=[];for(var b in d){c.push(d[b].username)}var e=this._containerData[a].likes-c.length;this._containerData[a].summary.children("span").html(WCF.Language.get("wcf.like.summary",{users:c,others:e}))}},_setActiveState:function(a,b,c){a.removeClass("active");b.removeClass("active");if(c==1){a.addClass("active")}else{if(c==-1){b.addClass("active")}}}});
+
+// WCF.ACL.js
+WCF.ACL={};WCF.ACL.List=Class.extend({_categoryName:"",_container:null,_containerElements:{},_objectID:0,_objectTypeID:null,_options:{},_proxy:null,_search:null,_values:{group:{},user:{}},init:function(g,i,d,b,c,j){this._objectID=b||0;this._objectTypeID=i;this._categoryName=d;if(c===undefined){c=true}this._values={group:{},user:{}};this._proxy=new WCF.Action.Proxy({showLoadingOverlay:false,success:$.proxy(this._success,this)});this._container=$(g).hide().addClass("aclContainer");var f=this._container.children("dd");var a=$('<ul class="aclList container" />').appendTo(f);var k=$('<input type="text" class="long" placeholder="'+WCF.Language.get("wcf.acl.search."+(!c?"user.":"")+"description")+'" />').appendTo(f);var e=$('<ul class="aclPermissionList container" />').hide().appendTo(f);this._containerElements={aclList:a,denyAll:null,grantAll:null,permissionList:e,searchInput:k};this._search=new WCF.Search.User(k,$.proxy(this.addObject,this),c);var l=this._container.parents("form:eq(0)");l.submit($.proxy(this.submit,this));var h=l.find("input[type=reset]:eq(0)");if(h.length){h.click($.proxy(this._reset,this))}if(j){this._success(j)}else{this._loadACL()}},_reset:function(){this._values={group:{},user:{}};this._containerElements.aclList.empty();this._containerElements.searchInput.val("");this._containerElements.permissionList.hide().find("input[type=checkbox]").prop("checked",false)},_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()},addObject:function(b){var a=this._createListItem(b.objectID,b.label,b.type);this._savePermissions();this._containerElements.aclList.children("li").removeClass("active");a.addClass("active");this._search.addExcludedSearchValue(b.label);this._containerElements.permissionList.find("input[type=checkbox]").prop("checked",false);this._containerElements.searchInput.val("");this._containerElements.permissionList.show();WCF.DOMNodeInsertedHandler.execute()},_createListItem:function(d,a,c){var b=$('<li><span class="icon icon16 icon-'+(c==="group"?"group":"user")+'" /> <span>'+a+"</span></li>").appendTo(this._containerElements.aclList);b.data("objectID",d).data("type",c).data("label",a).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(b);return b},_removeItem:function(d){var b=$(d.currentTarget).parent();var a=b.data("type");var c=b.data("objectID");this._search.removeExcludedSearchValue(b.data("label"));b.remove();if(this._values[a][c]){delete this._values[a][c]}this._selectFirstEntry()},_selectFirstEntry:function(){var a=this._containerElements.aclList.children("li:eq(0)");if(a.length){this._select(a,false)}else{this._reset()}},_success:function(g,f,l){if(!$.getLength(g.returnValues.options)){return}var i=0;var n={};for(var h in g.returnValues.options){var a=g.returnValues.options[h];var j=$("<li><span>"+a.label+"</span></li>").data("optionID",h).data("optionName",a.optionName);var d=$('<input type="checkbox" id="grant'+h+'" />').appendTo(j).wrap('<label for="grant'+h+'" class="jsTooltip" title="'+WCF.Language.get("wcf.acl.option.grant")+'" />');var b=$('<input type="checkbox" id="deny'+h+'" />').appendTo(j).wrap('<label for="deny'+h+'" class="jsTooltip" title="'+WCF.Language.get("wcf.acl.option.deny")+'" />');d.data("type","grant").data("optionID",h).change($.proxy(this._change,this));b.data("type","deny").data("optionID",h).change($.proxy(this._change,this));if(!n[a.categoryName]){n[a.categoryName]=[]}if(a.categoryName===""){j.appendTo(this._containerElements.permissionList)}else{n[a.categoryName].push(j)}i++}if(i>1){var j=$('<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(j).wrap('<label for="grantAll" class="jsTooltip" title="'+WCF.Language.get("wcf.acl.option.grant")+'" />');this._containerElements.denyAll=$('<input type="checkbox" id="denyAll" />').appendTo(j).wrap('<label for="denyAll" class="jsTooltip" title="'+WCF.Language.get("wcf.acl.option.deny")+'" />');this._containerElements.grantAll.data("type","grant").change($.proxy(this._changeAll,this));this._containerElements.denyAll.data("type","deny").change($.proxy(this._changeAll,this))}if($.getLength(n)){for(var e in n){var c=n[e];if(g.returnValues.categories[e]){$('<li class="aclCategory">'+g.returnValues.categories[e]+"</li>").appendTo(this._containerElements.permissionList)}for(var m=0,k=c.length;m<k;m++){c[m].appendTo(this._containerElements.permissionList)}}}this._parseData(g,"group");this._parseData(g,"user");this._container.show();this._selectFirstEntry()},_parseData:function(c,a){if(!$.getLength(c.returnValues[a].option)){return}for(var b in c.returnValues[a].label){this._createListItem(b,c.returnValues[a].label[b],a);this._search.addExcludedSearchValue(c.returnValues[a].label[b])}this._values[a]=c.returnValues[a].option;WCF.DOMNodeInsertedHandler.execute()},_click:function(b){var a=$(b.currentTarget);if(a.hasClass("active")){return}this._select(a,true)},_select:function(b,a){if(a){this._savePermissions()}this._containerElements.aclList.children("li").removeClass("active");b.addClass("active");this._setupPermissions(b.data("type"),b.data("objectID"))},_change:function(d){var c=$(d.currentTarget);var b=c.data("optionID");var a=c.data("type");if(c.is(":checked")){if(a==="deny"){$("#grant"+b).prop("checked",false);if(this._containerElements.grantAll!==null){this._containerElements.grantAll.prop("checked",false)}}else{$("#deny"+b).prop("checked",false);if(this._containerElements.denyAll!==null){this._containerElements.denyAll.prop("checked",false)}}}else{if(a==="deny"&&this._containerElements.denyAll!==null){this._containerElements.denyAll.prop("checked",false)}else{if(a==="grant"&&this._containerElements.grantAll!==null){this._containerElements.grantAll.prop("checked",false)}}}var e=true;this._containerElements.permissionList.find("input[type=checkbox]").each(function(g,h){var f=$(h);if(f.data("type")===a&&f.attr("id")!==a+"All"){if(!f.is(":checked")){e=false;return false}}});if(a=="deny"){if(this._containerElements.denyAll!==null){if(e){this._containerElements.denyAll.prop("checked",true)}else{this._containerElements.denyAll.prop("checked",false)}}}else{if(this._containerElements.grantAll!==null){if(e){this._containerElements.grantAll.prop("checked",true)}else{this._containerElements.grantAll.prop("checked",false)}}}},_changeAll:function(c){var b=$(c.currentTarget);var a=b.data("type");if(b.is(":checked")){if(a==="deny"){this._containerElements.grantAll.prop("checked",false);this._containerElements.permissionList.find("input[type=checkbox]").each(function(e,f){var d=$(f);if(d.data("type")==="deny"&&d.attr("id")!=="denyAll"){d.prop("checked",true).trigger("change")}})}else{this._containerElements.denyAll.prop("checked",false);this._containerElements.permissionList.find("input[type=checkbox]").each(function(e,f){var d=$(f);if(d.data("type")==="grant"&&d.attr("id")!=="grantAll"){d.prop("checked",true).trigger("change")}})}}else{if(a==="deny"){this._containerElements.grantAll.prop("checked",false);this._containerElements.permissionList.find("input[type=checkbox]").each(function(e,f){var d=$(f);if(d.data("type")==="deny"&&d.attr("id")!=="denyAll"){d.prop("checked",false).trigger("change")}})}else{this._containerElements.denyAll.prop("checked",false);this._containerElements.permissionList.find("input[type=checkbox]").each(function(e,f){var d=$(f);if(d.data("type")==="grant"&&d.attr("id")!=="grantAll"){d.prop("checked",false).trigger("change")}})}}},_setupPermissions:function(b,c){this._containerElements.permissionList.find("input[type='checkbox']").prop("checked",false);if(this._values[b]&&this._values[b][c]){for(var a in this._values[b][c]){if(this._values[b][c][a]==1){$("#grant"+a).prop("checked",true).trigger("change")}else{$("#deny"+a).prop("checked",true).trigger("change")}}}this._containerElements.permissionList.show()},_savePermissions:function(){var c=this._containerElements.aclList.find("li.active");if(!c.length){return}var d=c.data("objectID");var a=c.data("type");this._values[a][d]={};var b=this;this._containerElements.permissionList.find("input[type='checkbox']").each(function(e,i){var h=$(i);if(h.attr("id")!="grantAll"&&h.attr("id")!="denyAll"){var g=(h.data("type")==="deny")?0:1;var f=h.data("optionID");if(h.is(":checked")){b._values[a][d][f]=g;h.prop("checked",false)}else{if(b._values[a]&&b._values[a][d]&&b._values[a][d][f]&&b._values[a][d][f]==g){delete b._values[a][d][f]}}}})},submit:function(a){this._savePermissions();this._save("group");this._save("user")},_save:function(a){if($.getLength(this._values[a])){var b=this._container.parents("form:eq(0)");for(var e in this._values[a]){var d=this._values[a][e];for(var c in d){$('<input type="hidden" name="aclValues['+a+"]["+e+"]["+c+']" value="'+d[c]+'" />').appendTo(b)}}}}});
+
+// WCF.Attachment.js
+WCF.Attachment={};WCF.Attachment.Upload=WCF.Upload.extend({_objectType:"",_objectID:0,_tmpHash:"",_parentObjectID:0,_wysiwygContainerID:"",init:function(c,g,a,h,d,e,f,b){this._super(c,g,"wcf\\data\\attachment\\AttachmentAction",{multiple:true,maxUploads:f});this._objectType=a;this._objectID=h;this._tmpHash=d;this._parentObjectID=e;this._wysiwygContainerID=b;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))},_validateLimit:function(){var c=this._buttonSelector.next("small.innerError");var a=this._options.maxUploads-this._fileListSelector.children("li:not(.uploadFailed)").length;var d=(this._fileUpload)?this._fileUpload.prop("files").length:0;if(a<=0||a<d){var b=(a<=0)?WCF.Language.get("wcf.attachment.upload.error.reachedLimit"):WCF.Language.get("wcf.attachment.upload.error.reachedRemainingLimit").replace(/#remaining#/,a);if(!c.length){c=$('<small class="innerError" />').insertAfter(this._buttonSelector)}c.html(b);return false}c.remove();return true},_removeLimitError:function(b){var a=$(b.target);if(a.is("li.box48")&&a.parent().wcfIdentify()===this._fileListSelector.wcfIdentify()){this._buttonSelector.next("small.innerError").remove()}},_upload:function(){if(this._validateLimit()){this._super()}if(this._fileUpload){this._removeButton();this._createButton()}},_createUploadMatrix:function(a){this._fileListSelector.children("li.uploadFailed").remove();return this._super(a)},_getParameters:function(){return{objectType:this._objectType,objectID:this._objectID,tmpHash:this._tmpHash,parentObjectID:this._parentObjectID}},_initFile:function(a){var b=$('<li class="box48"><span class="icon icon48 icon-spinner" /><div><div><p>'+a.name+'</p><small><progress max="100"></progress></small></div><ul></ul></div></li>').data("filename",a.name);this._fileListSelector.append(b);this._fileListSelector.show();if(this._buttonSelector.data("maxSize")<a.size){b.find("progress").remove();b.children(".icon-spinner").removeClass("icon-spinner").addClass("icon-ban-circle");b.find("div > div").append($('<small class="innerError">'+WCF.Language.get("wcf.attachment.upload.error.tooLarge")+"</small>"));b.addClass("uploadFailed")}return b},_success:function(b,c){for(var i in this._uploadMatrix[b]){var g=this._uploadMatrix[b][i];g.find("progress").remove();var h=g.data("filename");var j=g.data("internalFileID");if(c.returnValues&&c.returnValues.attachments[j]){if(c.returnValues.attachments[j]["tinyURL"]){g.children(".icon-spinner").replaceWith($('<img src="'+c.returnValues.attachments[j]["tinyURL"]+'" alt="" class="attachmentTinyThumbnail" />'))}else{g.children(".icon-spinner").removeClass("icon-spinner").addClass("icon-paper-clip")}var e=$('<a href=""></a>');e.text(h).attr("href",c.returnValues.attachments[j]["url"]);if(c.returnValues.attachments[j]["isImage"]!=0){e.addClass("jsImageViewer").attr("title",h)}g.find("p").empty().append(e);g.find("small").append(c.returnValues.attachments[j]["formattedFilesize"]);var f=$('<li><span class="icon icon16 icon-remove pointer jsTooltip jsDeleteButton" title="'+WCF.Language.get("wcf.global.button.delete")+'" data-object-id="'+c.returnValues.attachments[j]["attachmentID"]+'" data-confirm-message="'+WCF.Language.get("wcf.attachment.delete.sure")+'" /></li>');g.find("ul").append(f);if(this._wysiwygContainerID){var a=$('<li><span class="icon icon16 icon-paste pointer jsTooltip jsButtonInsertAttachment" title="'+WCF.Language.get("wcf.attachment.insert")+'" data-object-id="'+c.returnValues.attachments[j]["attachmentID"]+'" /></li>');a.children(".jsButtonInsertAttachment").click($.proxy(this._insert,this));g.find("ul").append(a)}}else{g.children(".icon-spinner").removeClass("icon-spinner").addClass("icon-ban-circle");var d="";if(c.returnValues&&c.returnValues.errors[j]){d=c.returnValues.errors[j]["errorType"]}else{d="uploadFailed"}g.find("div > div").append($('<small class="innerError">'+WCF.Language.get("wcf.attachment.upload.error."+d)+"</small>"));g.addClass("uploadFailed")}g.css("display","block")}WCF.DOMNodeInsertedHandler.execute()},_insert:function(e){var d=$(e.currentTarget).data("objectID");var c="[attach="+d+"][/attach]";var a=($.browser.mobile)?null:$("#"+this._wysiwygContainerID).ckeditorGet();if(a!==null&&a.mode==="wysiwyg"){a.insertText(c)}else{var g=($.browser.mobile)?$("#"+this._wysiwygContainerID):$("#"+this._wysiwygContainerID).next(".cke_editor_text").find("textarea");var b=g.val();if(b.length==0){g.val(c)}else{var f=g.getCaret();g.val(b.substr(0,f)+c+b.substr(f))}}},_error:function(a){this._fileListSelector.find("li").each(function(b,d){var c=$(d);if(c.children(".icon-spinner").length){c.addClass("uploadFailed").children(".icon-spinner").removeClass("icon-spinner").addClass("icon-ban-circle");c.find("div > div").append($('<small class="innerError">'+(a.responseJSON&&a.responseJSON.message?a.responseJSON.message:WCF.Language.get("wcf.attachment.upload.error.uploadFailed"))+"</small>"))}})}});
+
+// WCF.ColorPicker.js
+WCF.ColorPicker=Class.extend({_bar:null,_barActive:false,_barSelector:null,_dialog:null,_didInit:false,_elementID:"",_gradient:null,_gradientActive:false,_gradientSelector:null,_hex:null,_hsv:{},_newColor:null,_oldColor:null,_rgba:{},_rgbaRegExp:null,init:function(a){this._elementID="";this._hsv={h:0,s:100,v:100};this._position={};var b=$(a);if(!b.length){console.debug("[WCF.ColorPicker] Selector does not match any element, aborting.");return}b.click($.proxy(this._open,this))},_open:function(c){if(!this._didInit){this._initColorPicker();this._didInit=true}var a=$(c.currentTarget);this._elementID=a.wcfIdentify();this._parseColor(a);var b=this.hsvToRgb(this._hsv.h,this._hsv.s,this._hsv.v);this._oldColor.css({backgroundColor:"rgb("+b.r+", "+b.g+", "+b.b+")"});this._dialog.wcfDialog({title:WCF.Language.get("wcf.style.colorPicker")})},_parseColor:function(b){if(b.data("hsv")&&b.data("rgb")){var d=b.data("hsv");for(var a in d){this._hsv[a]=d[a]}this._updateValues(b.data("rgb"),true,true);this._rgba.a.val(parseInt(b.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})\\)$")}this._rgbaRegExp.exec(b.data("color"));var c=RegExp.$4;if(c.indexOf(".")===0){c="0"+c}c*=100;this._updateValues({r:RegExp.$1,g:RegExp.$2,b:RegExp.$3,a:Math.round(c)},true,true)}},_initColorPicker:function(){this._dialog=$('<div id="colorPickerContainer" />').hide().appendTo(document.body);this._gradient=$('<div id="colorPickerGradient" />').appendTo(this._dialog);this._gradientSelector=$('<span id="colorPickerGradientSelector"><span></span></span>').appendTo(this._gradient);this._bar=$('<div id="colorPickerBar" />').appendTo(this._dialog);this._barSelector=$('<span id="colorPickerBarSelector" />').appendTo(this._bar);this._gradient.mousedown($.proxy(this._mouseDownGradient,this));this._bar.mousedown($.proxy(this._mouseDownBar,this));var a=this;$(document).mouseup(function(b){if(a._barActive){a._barActive=false;a._mouseBar(b)}else{if(a._gradientActive){a._gradientActive=false;a._mouseGradient(b)}}}).mousemove(function(b){if(a._barActive){a._mouseBar(b)}else{if(a._gradientActive){a._mouseGradient(b)}}});this._initColorPickerForm()},_initColorPickerForm:function(){var c=$('<div id="colorPickerForm" />').appendTo(this._dialog);$("<small>"+WCF.Language.get("wcf.style.colorPicker.new")+"</small>").appendTo(c);var d=$('<ul class="colors" />').appendTo(c);this._newColor=$('<li class="new" />').appendTo(d);this._oldColor=$('<li class="old" />').appendTo(d);$("<small>"+WCF.Language.get("wcf.style.colorPicker.current")+"</small>").appendTo(c);var a=$('<ul class="rgba" />').appendTo(c);this._createInputElement("r","R",0,255).appendTo(a);this._createInputElement("g","G",0,255).appendTo(a);this._createInputElement("b","B",0,255).appendTo(a);this._createInputElement("a","a",0,100).appendTo(a);var e=$('<ul class="hex"><li><label><span>#</span></label></li></ul>').appendTo(c);this._hex=$('<input type="text" maxlength="6" />').appendTo(e.find("label"));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));var f=$('<div class="formSubmit" />').appendTo(this._dialog);$('<button class="buttonPrimary">'+WCF.Language.get("wcf.style.colorPicker.button.apply")+"</button>").appendTo(f).click($.proxy(this._submit,this));var b=this;this._hex.on("paste",function(){b._hex.attr("maxlength","7");setTimeout(function(){var g=b._hex.val();if(g.substring(0,1)=="#"){g=g.substr(1)}if(g.length>6){g=g.substring(0,6)}b._hex.attr("maxlength","6").val(g)},50)})},_keyUpRGBA:function(a){if(a.which==13){this._blurRgba();this._submit()}},_keyUpHex:function(a){if(a.which==13){this._blurHex();this._submit()}},_submit:function(){var d=this.hsvToRgb(this._hsv.h,this._hsv.s,this._hsv.v);var c={};for(var b in this._hsv){c[b]=this._hsv[b]}var a=$("#"+this._elementID);a.data("hsv",c).css({backgroundColor:"rgb("+d.r+", "+d.g+", "+d.b+")"}).data("alpha",parseInt(this._rgba.a.val()));a.data("rgb",{r:this._rgba.r.val(),g:this._rgba.g.val(),b:this._rgba.b.val()});$("#"+a.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")},_createInputElement:function(f,c,e,b){var d=$('<li class="'+f+'" />');var a=$("<label />").appendTo(d);$("<span>"+c+"</span>").appendTo(a);this._rgba[f]=$('<input type="number" value="0" min="'+e+'" max="'+b+'" step="1" />').appendTo(a);return d},_mouseDownGradient:function(a){this._gradientActive=true;this._mouseGradient(a)},_mouseGradient:function(b){var c=this._gradient.getOffsets("offset");var a=Math.max(Math.min(b.pageX-c.left,255),0);var d=Math.max(Math.min(b.pageY-c.top,255),0);this._hsv.s=Math.max(0,Math.min(1,a/255))*100;this._hsv.v=Math.max(0,Math.min(1,(255-d)/255))*100;this._updateValues(null)},_mouseDownBar:function(a){this._barActive=true;this._mouseBar(a)},_mouseBar:function(a){var b=this._bar.getOffsets("offset");var c=Math.max(Math.min(a.pageY-b.top,255),0);this._barSelector.css({top:c+"px"});this._hsv.h=Math.max(0,Math.min(359,Math.round((255-c)/255*360)));this._updateValues(null)},_blurRgba:function(){for(var a in this._rgba){var b=parseInt(this._rgba[a].val())||0;if(a==="a"){this._rgba[a].val(Math.max(0,Math.min(100,b)))}else{this._rgba[a].val(Math.max(0,Math.min(255,b)))}}this._updateValues({r:this._rgba.r.val(),g:this._rgba.g.val(),b:this._rgba.b.val()},true,true)},_blurHex:function(){var a=this.hexToRgb(this._hex.val());if(a!==Number.NaN){this._updateValues(a,true,true)}},_updateValues:function(c,e,a){e=(e===true)?true:false;a=(a===true)?true:false;if(c===null){c=this.hsvToRgb(this._hsv.h,this._hsv.s,this._hsv.v)}if(c.a===undefined){c.a=this._rgba.a.val()}for(var b in c){this._rgba[b].val(c[b])}this._hex.val(this.rgbToHex(c.r,c.g,c.b));if(e||a){var g=this.rgbToHsv(c.r,c.g,c.b);if(e){this._hsv.h=g.h}if(a){this._hsv.s=g.s;this._hsv.v=g.v}}var h=Math.max(0,Math.min(255,255-(this._hsv.h/360)*255));this._barSelector.css({top:h+"px"});var d=Math.max(0,Math.min(255,(this._hsv.s/100)*255));var h=Math.max(0,Math.min(255,255-((this._hsv.v/100)*255)));this._gradientSelector.css({left:(d-6)+"px",top:(h-6)+"px"});this._newColor.css({backgroundColor:"rgb("+c.r+", "+c.g+", "+c.b+")"});var f=this.hsvToRgb(this._hsv.h,100,100);this._gradient.css({backgroundColor:"rgb("+f.r+", "+f.g+", "+f.b+")"})},hsvToRgb:function(d,j,g){var b={r:0,g:0,b:0};var i,a,f,e,c;i=Math.floor(d/60);a=d/60-i;j/=100;g/=100;f=g*(1-j);e=g*(1-j*a);c=g*(1-j*(1-a));if(j==0){b.r=b.g=b.b=g}else{switch(i){case 1:b.r=e;b.g=g;b.b=f;break;case 2:b.r=f;b.g=g;b.b=c;break;case 3:b.r=f;b.g=e;b.b=g;break;case 4:b.r=c;b.g=f;b.b=g;break;case 5:b.r=g;b.g=f;b.b=e;break;case 0:case 6:b.r=g;b.g=c;b.b=f;break}}return{r:Math.round(b.r*255),g:Math.round(b.g*255),b:Math.round(b.b*255)}},rgbToHsv:function(a,f,i){var j,e,c;var d,k,h;a/=255;f/=255;i/=255;d=Math.max(Math.max(a,f),i);k=Math.min(Math.min(a,f),i);h=d-k;j=0;if(d!==k){switch(d){case a:j=60*(0+(f-i)/h);break;case f:j=60*(2+(i-a)/h);break;case i:j=60*(4+(a-f)/h);break}if(j<0){j+=360}}if(d===0){e=0}else{e=h/d}c=d;return{h:Math.round(j),s:Math.round(e*100),v:Math.round(c*100)}},hexToRgb:function(a){if(/^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(a)){a=a.split("");if(a[0]==="#"){a.shift()}if(a.length===3){return{r:parseInt(a[0]+""+a[0],16),g:parseInt(a[1]+""+a[1],16),b:parseInt(a[2]+""+a[2],16)}}else{return{r:parseInt(a[0]+""+a[1],16),g:parseInt(a[2]+""+a[3],16),b:parseInt(a[4]+""+a[5],16)}}}return Number.NaN},rgbToHex:function(d,c,a){return("0123456789ABCDEF".charAt((d-d%16)/16)+""+"0123456789ABCDEF".charAt(d%16))+""+("0123456789ABCDEF".charAt((c-c%16)/16)+""+"0123456789ABCDEF".charAt(c%16))+""+("0123456789ABCDEF".charAt((a-a%16)/16)+""+"0123456789ABCDEF".charAt(a%16))}});
+
+// WCF.Comment.js
+WCF.Comment={};WCF.Comment.Handler=Class.extend({_commentAdd:null,_commentButtonList:{},_comments:{},_container:null,_containerID:"",_displayedComments:0,_loadNextComments:null,_loadNextResponses:{},_proxy:null,_responses:{},_userAvatar:"",_commentData:{},_guestDialog:null,_useRecaptcha:true,init:function(a,b){this._commentAdd=null;this._commentButtonList={};this._comments={};this._containerID=a;this._displayedComments=0;this._loadNextComments=null;this._loadNextResponses={};this._responses={};this._userAvatar=b;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();if(this._container.data("canAdd")){this._initAddComment()}WCF.DOMNodeInsertedHandler.execute();WCF.DOMNodeInsertedHandler.addCallback("WCF.Comment.Handler",$.proxy(this._domNodeInserted,this))},_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()}}},_handleLoadNextResponses:function(a){var b=this._comments[a];b.data("displayedResponses",b.find("ul.commentResponseList > li").length);if(b.data("displayedResponses")<b.data("responses")){if(this._loadNextResponses[a]===undefined){var d=b.data("responses")-b.data("displayedResponses");this._loadNextResponses[a]=$('<li class="jsCommentLoadNextResponses"><a>'+WCF.Language.get("wcf.comment.response.more",{count:d})+"</a></li>").appendTo(this._commentButtonList[a]);this._loadNextResponses[a].children("a").data("commentID",a).click($.proxy(this._loadResponses,this));this._commentButtonList[a].parent().show()}}else{if(this._loadNextResponses[a]!==undefined){var c=this._loadNextResponses[a].next();this._loadNextResponses[a].remove();if(c.length){c.trigger("click")}}}},_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()},_loadResponses:function(a){this._loadResponsesExecute($(a.currentTarget).disable().data("commentID"),false)},_loadResponsesExecute:function(a,b){this._proxy.setOption("data",{actionName:"loadResponses",className:"wcf\\data\\comment\\response\\CommentResponseAction",parameters:{data:{commentID:a,lastResponseTime:this._comments[a].data("lastResponseTime"),loadAllResponses:(b?1:0)}}});this._proxy.sendRequest()},_domNodeInserted:function(){this._initComments();this._initResponses()},_initComments:function(){var a=this;var b=false;this._container.find(".jsComment").each(function(e,g){var f=$(g).removeClass("jsComment");var c=f.data("commentID");a._comments[c]=f;var d=f.find("ul.commentResponseList");if(!d.length){d=f.find(".commentContent")}$container=$('<div class="commentOptionContainer" />').hide().insertAfter(d);a._commentButtonList[c]=$("<ul />").appendTo($container);a._handleLoadNextResponses(c);a._initComment(c,f);a._displayedComments++;b=true});if(b){this._handleLoadNextComments()}},_initComment:function(a,d){if(this._container.data("canAdd")){this._initAddResponse(a,d)}if(d.data("canEdit")){var b=$('<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>");b.data("commentID",a).appendTo(d.find("ul.commentOptions:eq(0)")).click($.proxy(this._prepareEdit,this))}if(d.data("canDelete")){var c=$('<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>");c.data("commentID",a).appendTo(d.find("ul.commentOptions:eq(0)")).click($.proxy(this._delete,this))}},_initResponses:function(){var a=this;this._container.find(".jsCommentResponse").each(function(d,c){var b=$(c).removeClass("jsCommentResponse");var e=b.data("responseID");a._responses[e]=b;a._initResponse(e,b)})},_initResponse:function(a,c){if(c.data("canEdit")){var d=$('<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 b=this;d.data("responseID",a).appendTo(c.find("ul.commentOptions:eq(0)")).click(function(f){b._prepareEdit(f,true)})}if(c.data("canDelete")){var e=$('<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 b=this;e.data("responseID",a).appendTo(c.find("ul.commentOptions:eq(0)")).click(function(f){b._delete(f,true)})}},_initAddComment:function(){this._commentAdd=$('<li class="box32 jsCommentAdd"><span class="framed">'+this._userAvatar+"</span><div /></li>").prependTo(this._container);var a=this._commentAdd.children("div");var b=$('<input type="text" placeholder="'+WCF.Language.get("wcf.comment.add")+'" maxlength="65535" class="long" />').appendTo(a);$("<small>"+WCF.Language.get("wcf.comment.description")+"</small>").appendTo(a);b.keyup($.proxy(this._keyUp,this))},_initAddResponse:function(d,g){var c=null;if(!g.data("responses")||this._loadNextResponses[d]){c=$('<li class="jsCommentShowAddResponse"><a>'+WCF.Language.get("wcf.comment.button.response.add")+"</a></li>").data("commentID",d).click($.proxy(this._showAddResponse,this)).appendTo(this._commentButtonList[d])}var e=$('<div class="box32 commentResponseAdd jsCommentResponseAdd"><span class="framed">'+this._userAvatar+"</span><div /></div>");if(c!==null){e.hide()}else{this._commentButtonList[d].parent().addClass("jsAddResponseActive")}e.appendTo(this._commentButtonList[d].parent().show());var a=e.children("div");var f=$('<input type="text" placeholder="'+WCF.Language.get("wcf.comment.response.add")+'" maxlength="65535" class="long" />').data("commentID",d).appendTo(a);$("<small>"+WCF.Language.get("wcf.comment.description")+"</small>").appendTo(a);var b=this;f.keyup(function(h){b._keyUp(h,true)});g.data("responsePlaceholder",c).data("responseInput",e)},_prepareEdit:function(c,a){var d=$(c.currentTarget);var b={objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID")};if(a===true){b.responseID=d.data("responseID")}else{b.commentID=d.data("commentID")}this._proxy.setOption("data",{actionName:"prepareEdit",className:"wcf\\data\\comment\\CommentAction",parameters:{data:b}});this._proxy.sendRequest()},_showAddResponse:function(c){var b=$(c.currentTarget);var a=b.data("commentID");if(b.prev().hasClass("jsCommentLoadNextResponses")){this._loadResponsesExecute(a,true);b.parent().children(".button").disable()}b.remove();var d=this._comments[a].data("responseInput").show();d.find("input").focus();d.parents(".commentOptionContainer").addClass("jsAddResponseActive")},_keyUp:function(e,b){if(e.which!==13&&e.which!==27){return}var f=$(e.currentTarget);if(e.which===27){f.val("").trigger("blur",e);return}var d=$.trim(f.val());if(d==""){return}var a="addComment";var c={message:d,objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID")};if(b===true){a="addResponse";c.commentID=f.data("commentID")}if(!WCF.User.userID){this._commentData=c;if(this._guestDialog===null){this._proxy.setOption("data",{actionName:"getGuestDialog",className:"wcf\\data\\comment\\CommentAction",parameters:{data:{message:d,objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID")}}});this._proxy.sendRequest()}else{if(this._useRecaptcha){Recaptcha.reload()}this._guestDialog.find('input[type="submit"]').enable();this._guestDialog.wcfDialog("open")}}else{this._proxy.setOption("data",{actionName:a,className:"wcf\\data\\comment\\CommentAction",parameters:{data:c}});this._proxy.sendRequest()}},_delete:function(b,a){WCF.System.Confirmation.show(WCF.Language.get("wcf.comment.delete.confirmMessage"),$.proxy(function(d){if(d==="confirm"){var c={objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID")};if(a!==true){c.commentID=$(b.currentTarget).data("commentID")}else{c.responseID=$(b.currentTarget).data("responseID")}this._proxy.setOption("data",{actionName:"remove",className:"wcf\\data\\comment\\CommentAction",parameters:{data:c}});this._proxy.sendRequest()}},this))},_failure:function(b,a,d,c){if(!WCF.User.userID&&this._guestDialog){this._guestDialog.find('input[type="submit"]').enable()}return true},_success:function(d,e,b){switch(d.actionName){case"addComment":if(d.returnValues.errors){this._handleGuestDialogErrors(d.returnValues.errors)}else{this._commentAdd.find("input").val("").blur();$(d.returnValues.template).insertAfter(this._commentAdd).wcfFadeIn();if(!WCF.User.userID){this._guestDialog.wcfDialog("close")}}break;case"addResponse":if(d.returnValues.errors){this._handleGuestDialogErrors(d.returnValues.errors)}else{var c=this._comments[d.returnValues.commentID];c.find(".jsCommentResponseAdd input").val("").blur();var a=c.find("ul.commentResponseList");if(!a.length){a=$('<ul class="commentResponseList" />').insertBefore(c.find(".commentOptionContainer"))}$(d.returnValues.template).appendTo(a).wcfFadeIn()}if(!WCF.User.userID){this._guestDialog.wcfDialog("close")}break;case"edit":this._update(d);break;case"loadComments":this._insertComments(d);break;case"loadResponses":this._insertResponses(d);break;case"prepareEdit":this._edit(d);break;case"remove":this._remove(d);break;case"getGuestDialog":this._createGuestDialog(d);break}WCF.DOMNodeInsertedHandler.execute()},_insertComments:function(a){$(a.returnValues.template).insertBefore(this._loadNextComments);this._container.data("lastCommentTime",a.returnValues.lastCommentTime)},_insertResponses:function(b){var a=this._comments[b.returnValues.commentID];$(b.returnValues.template).appendTo(a.find("ul.commentResponseList"));a.data("lastResponseTime",b.returnValues.lastResponseTime);this._handleLoadNextResponses(b.returnValues.commentID)},_remove:function(a){if(a.returnValues.commentID){this._comments[a.returnValues.commentID].remove();delete this._comments[a.returnValues.commentID]}else{this._responses[a.returnValues.responseID].remove();delete this._responses[a.returnValues.responseID]}},_edit:function(b){if(b.returnValues.commentID){var a=this._comments[b.returnValues.commentID].find(".commentContent:eq(0) .userMessage:eq(0)")}else{var a=this._responses[b.returnValues.responseID].find(".commentContent:eq(0) .userMessage:eq(0)")}a.html($.proxy(function(d,c){var e=$('<input type="text" class="long" maxlength="65535" /><small>'+WCF.Language.get("wcf.comment.description")+"</small>").val(b.returnValues.message);e.data("__html",c).keyup($.proxy(this._saveEdit,this));if(b.returnValues.commentID){e.data("commentID",b.returnValues.commentID)}else{e.data("responseID",b.returnValues.responseID)}return e},this));a.children("input").focus();a.parent().find(".containerHeadline:eq(0)").hide();a.parent().find(".buttonGroupNavigation:eq(0)").hide()},_update:function(a){if(a.returnValues.commentID){var b=this._comments[a.returnValues.commentID].find(".commentContent:eq(0) .userMessage:eq(0) > input")}else{var b=this._responses[a.returnValues.responseID].find(".commentContent:eq(0) .userMessage:eq(0) > input")}b.data("__html",a.returnValues.message);this._cancelEdit(b)},_createGuestDialog:function(a){this._guestDialog=$('<div id="commentAddGuestDialog" />').append(a.returnValues.template).hide().appendTo(document.body);this._guestDialog.find('input[type="submit"]').click($.proxy(this._submit,this));this._guestDialog.find('input[type="text"]').keydown($.proxy(this._keyDown,this));this._useRecaptcha=this._guestDialog.find("dl.reCaptcha").length>0;this._guestDialog.wcfDialog({title:WCF.Language.get("wcf.comment.guestDialog.title")})},_keyDown:function(a){if(a.which===$.ui.keyCode.ENTER){this._submit()}},_handleGuestDialogErrors:function(d){if(d.username){var c=this._guestDialog.find('input[name="username"]');var b=c.next(".innerError");if(!b.length){b=$('<small class="innerError" />').text(d.username).insertAfter(c)}else{b.text(d.username).show()}}if(d.recaptcha){Recaptcha.reload();var a=this._guestDialog.find('input[name="recaptcha_response_field"]');var b=a.next(".innerError");if(!b.length){b=$('<small class="innerError" />').text(d.recaptcha).insertAfter(a)}else{b.text(d.recaptcha).show()}}this._guestDialog.find('input[type="submit"]').enable()},_submit:function(b){var c=true;this._guestDialog.find('input[type="submit"]').enable();var j=this._guestDialog.find('input[name="username"]');var a=j.val();var g=j.next(".innerError");if(!a){c=false;if(!g.length){g=$('<small class="innerError" />').text(WCF.Language.get("wcf.global.form.error.empty")).insertAfter(j)}else{g.text(WCF.Language.get("wcf.global.form.error.empty")).show()}}if(this._useRecaptcha){var e=this._guestDialog.find('input[name="recaptcha_response_field"]');var d=e.val();var i=e.next(".innerError");if(!d){c=false;if(!i.length){i=$('<small class="innerError" />').text(WCF.Language.get("wcf.global.form.error.empty")).insertAfter(e)}else{i.text(WCF.Language.get("wcf.global.form.error.empty")).show()}}}if(c){if(g.length){g.hide()}if(this._useRecaptcha&&i.length){i.hide()}var f=this._commentData;f.username=a;var h={data:f};if(this._useRecaptcha){h.recaptchaChallenge=Recaptcha.get_challenge();h.recaptchaResponse=Recaptcha.get_response()}this._proxy.setOption("data",{actionName:this._commentData.commentID?"addResponse":"addComment",className:"wcf\\data\\comment\\CommentAction",parameters:h});this._proxy.sendRequest();this._guestDialog.find('input[type="submit"]').disable()}},_saveEdit:function(c){var d=$(c.currentTarget);if(c.which===27){this._cancelEdit(d);return}else{if(c.which!==13){return}}var b=$.trim(d.val());if(b===""){return}var a={message:b,objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID")};if(d.data("commentID")){a.commentID=d.data("commentID")}else{a.responseID=d.data("responseID")}this._proxy.setOption("data",{actionName:"edit",className:"wcf\\data\\comment\\CommentAction",parameters:{data:a}});this._proxy.sendRequest()},_cancelEdit:function(a){a.parent().prev(".containerHeadline:eq(0)").show();a.parent().next(".buttonGroupNavigation:eq(0)").show();a.parent().html(a.data("__html"))}});WCF.Comment.Like=WCF.Like.extend({_getContainers:function(){return $(".commentList > li.comment")},_getObjectID:function(a){return this._containers[a].data("commentID")},_buildWidget:function(b,a,d,c,e){this._containers[b].find(".containerHeadline:eq(0) > h3").append(c);if(this._canLike){a.appendTo(this._containers[b].find(".commentOptions:eq(0)"));d.appendTo(this._containers[b].find(".commentOptions:eq(0)"))}},_getWidgetContainer:function(a){},_addWidget:function(a,b){}});WCF.Comment.Response={};WCF.Comment.Response.Like=WCF.Like.extend({_addWidget:function(a,b){},_buildWidget:function(b,a,d,c,e){this._containers[b].find(".containerHeadline:eq(0) > h3").append(c);if(this._canLike){a.appendTo(this._containers[b].find(".commentOptions:eq(0)"));d.appendTo(this._containers[b].find(".commentOptions:eq(0)"))}},_getContainers:function(){return $(".commentResponseList > li.commentResponse")},_getObjectID:function(a){return this._containers[a].data("responseID")},_getWidgetContainer:function(a){}});
+
+// WCF.ImageViewer.js
+WCF.ImageViewer=Class.extend({init:function(){$('<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"));$('<span class="icon icon32 icon-remove jsTooltip" title="'+WCF.Language.get("wcf.imageViewer.close")+'" />').appendTo($("#lbCloseLink"));var a=$('<span class="icon icon32 icon-resize-full jsTooltip" title="'+WCF.Language.get("wcf.imageViewer.enlarge")+'" id="lbEnlarge" />').insertAfter($("#lbCloseLink"));a.click($.proxy(this._enlarge,this));this._initImageViewer();WCF.DOMNodeInsertedHandler.addCallback("WCF.ImageViewer",$.proxy(this._domNodeInserted,this));WCF.DOMNodeInsertedHandler.execute()},_domNodeInserted:function(){this._initImageSizeCheck();this._initImageViewer()},_initImageViewer:function(){if($.browser.touch&&/[Mm]obile/.test(navigator.userAgent)){if(!/iPad/.test(navigator.userAgent)){return}}var a=$("a.jsImageViewer");if(a.length){a.removeClass("jsImageViewer").slimbox({counterText:WCF.Language.get("wcf.imageViewer.counter"),loop:true})}},_enlarge:function(){var a=$("#lbImage").css("backgroundImage");if(a){a=a.replace(/^url\((["']?)(.*)\1\)$/,"$2");window.location=a}},_initImageSizeCheck:function(){$(".jsResizeImage").each($.proxy(function(a,b){if(b.complete){this._checkImageSize({currentTarget:b})}},this));$(".jsResizeImage").on("load",$.proxy(this._checkImageSize,this))},_checkImageSize:function(d){var c=$(d.currentTarget);if(!c.is(":visible")){c.off("load");return}c.removeClass("jsResizeImage");var b=c.getDimensions();var a=c.parents("div").innerWidth();if(b.width>a){c.css({height:Math.round(b.height*(a/b.width))+"px",width:a+"px"});if(!c.parents("a").length){c.wrap('<a href="'+c.attr("src")+'" />');c.parent().slimbox()}}}});$.widget("ui.wcfImageViewer",{_active:-1,_activeImage:null,_container:null,_didInit:false,_disableSlideshow:false,_eventNamespace:"",_images:[],_isOpen:false,_items:-1,_maxDimensions:{height:0,width:0},_proxy:null,_slideshowEnabled:false,_thumbnailContainerWidth:0,_thumbnailMarginRight:0,_thumbnailOffset:0,_thumbnailWidth:0,_timer:null,_ui:{buttonNext:null,buttonPrevious:null,header:null,image:null,imageContainer:null,imageList:null,slideshow:{container:null,enlarge:null,next:null,previous:null,toggle:null}},options:{shiftBy:5,enableSlideshow:1,speed:5,className:""},_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))},open:function(a){if(a){a.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},close:function(a){if(a){a.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},startSlideshow:function(){if(this._disableSlideshow||this._slideshowEnabled){return false}if(this._timer===null){this._timer=new WCF.PeriodicalExecuter($.proxy(function(){var a=this._active+1;if(a==this._items){a=0}this.showImage(a)},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},stopSlideshow:function(a){if(!this._slideshowEnabled){return false}this._timer.stop();if(a){this._ui.slideshow.toggle.children("span").removeClass("icon-pause").addClass("icon-play")}this._slideshowEnabled=false;return true},_bindListener:function(){$(document).on("keydown."+this._eventNamespace,$.proxy(this._keyDown,this));$(window).on("resize."+this._eventNamespace,$.proxy(this._renderImage,this))},_unbindListener:function(){$(document).off("keydown."+this._eventNamespace);$(window).off("resize."+this._eventNamespace)},_keyDown:function(b){switch(b.which){case $.ui.keyCode.ESCAPE:this.close();break;case $.ui.keyCode.LEFT:this._previousImage();break;case $.ui.keyCode.RIGHT:this._nextImage();break;case $.ui.keyCode.UP:if(!this._container.hasClass("maximized")){this._toggleView()}break;case $.ui.keyCode.DOWN:if(this._container.hasClass("maximized")){this._toggleView()}break;case $.ui.keyCode.ENTER:var a=this._ui.header.find("> div > h1 > a");if(a.length==1){window.location=a.prop("href")}else{this._ui.slideshow.full.trigger("click")}break;case 80:this._ui.slideshow.toggle.trigger("click");break;default:return true;break}return false},_render:function(c,a){this._container.addClass("open");var b=null;if(c){b=this._ui.imageList.children("li:eq(0)");this._thumbnailMarginRight=parseInt(b.css("marginRight").replace(/px$/,""))||0;this._thumbnailWidth=b.outerWidth(true);this._thumbnailContainerWidth=this._ui.imageList.parent().innerWidth();if(this._items>1&&this.options.enableSlideshow&&!a){this.startSlideshow()}}if(a){this._ui.imageList.children("li").each($.proxy(function(e,f){var d=$(f);if(d.data("objectID")==a){d.trigger("click");this.moveToImage(d.data("index"));return false}},this))}else{if(b!==null){b.trigger("click")}}this._toggleButtons();this._preload()},_preload:function(){if(this._images.length<this._items){var a=this._images.length*this._thumbnailWidth;if(a-this._thumbnailOffset<this._thumbnailContainerWidth){this._loadNextImages(false)}}},_showImage:function(a){this.showImage($(a.currentTarget).data("index"),true)},showImage:function(e,c){if(this._active==e){return false}this.stopSlideshow(c||false);if(this._active!=-1){this._images[this._active].listItem.removeClass("active")}this._active=e;var b=this._images[e];this._ui.imageList.children("li").removeClass("active");b.listItem.addClass("active");var g=this._ui.imageContainer.getDimensions("inner");var i=(this._activeImage?0:1);if(this._activeImage!==null){this._ui.images[this._activeImage].removeClass("active")}this._activeImage=i;var a=this._active;this._ui.imageContainer.addClass("loading");this._ui.images[i].off("load").prop("src",false).on("load",$.proxy(function(){this._imageOnLoad(a,i)},this));this._renderImage(i,b,g);var f=this._ui.header.find("> div > a").prop("href",b.user.link).prop("title",b.user.username);f.children("img").prop("src",b.user.avatarURL);var h=WCF.String.escapeHTML(b.image.title);if(b.image.link){h='<a href="'+b.image.link+'">'+b.image.title+"</a>"}this._ui.header.find("> div > h1").html(h);var d=(b.series&&b.series.title?WCF.String.escapeHTML(b.series.title):"");if(b.series.link){d='<a href="'+b.series.link+'">'+d+"</a>"}this._ui.header.find("> div > h2").html(d);this._ui.header.find("> div > h3").text(WCF.Language.get("wcf.imageViewer.seriesIndex").replace(/{x}/,b.listItem.data("index")+1).replace(/{y}/,this._items));this._ui.slideshow.full.data("link",(b.image.fullURL?b.image.fullURL:b.image.url));this.moveToImage(b.listItem.data("index"));this._toggleButtons();return true},_imageOnLoad:function(b,a){if(b!=this._active){return}this._ui.imageContainer.removeClass("loading");this._ui.images[a].addClass("active");this.startSlideshow()},_renderImage:function(c,g,e){if(!g){c=this._activeImage;g=this._images[this._active];e={height:$(window).height()-(this._container.hasClass("maximized")?0:200),width:this._ui.imageContainer.innerWidth()}}e.height-=22;e.width-=20;this._ui.images[c].prop("src",g.image.url);var d=g.image.height;var b=g.image.width;var a=0;if(d>e.height){a=e.height/d;d=e.height;b=Math.floor(b*a)}if(b>e.width){a=e.width/b;b=e.width;d=Math.floor(d*a)}var f=Math.floor((e.width-b)/2);this._ui.images[c].css({height:d+"px",left:(f+10)+"px",marginTop:(Math.round(d/2)*-1)+"px",width:b+"px"})},_initUI:function(){if(this._didInit){return false}this._didInit=true;this._container=$('<div class="wcfImageViewer" />').appendTo(document.body);var c=$('<div><img class="active" /><img /></div>').appendTo(this._container);var g=$('<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 e=$("<ul />").appendTo(c);var d=$('<li class="wcfImageViewerSlideshowButtonPrevious"><span class="icon icon48 icon-angle-left" /></li>').appendTo(e);var f=$('<li class="wcfImageViewerSlideshowButtonToggle pointer"><span class="icon icon48 icon-play" /></li>').appendTo(e);var b=$('<li class="wcfImageViewerSlideshowButtonNext"><span class="icon icon48 icon-angle-right" /></li>').appendTo(e);var h=$('<li class="wcfImageViewerSlideshowButtonEnlarge pointer jsTooltip" title="'+WCF.Language.get("wcf.imageViewer.button.enlarge")+'"><span class="icon icon48 icon-resize-full" /></li>').appendTo(e);var a=$('<li class="wcfImageViewerSlideshowButtonFull pointer jsTooltip" title="'+WCF.Language.get("wcf.imageViewer.button.full")+'"><span class="icon icon48 icon-external-link" /></li>').appendTo(e);this._ui={buttonNext:g.children("span.wcfImageViewerButtonNext"),buttonPrevious:g.children("span.wcfImageViewerButtonPrevious"),header:$('<header><div class="box64"><a class="framed jsTooltip"><img /></a><h1 /><h2 /><h3 /></div></header>').appendTo(this._container),imageContainer:c,images:[c.children("img:eq(0)").on("webkitTransitionEnd transitionend msTransitionEnd oTransitionEnd",function(){$(this).removeClass("animateTransformation")}),c.children("img:eq(1)").on("webkitTransitionEnd transitionend msTransitionEnd oTransitionEnd",function(){$(this).removeClass("animateTransformation")})],imageList:g.find("> div > ul"),slideshow:{container:e,enlarge:h,full:a,next:b,previous:d,toggle:f}};this._ui.buttonNext.click($.proxy(this._next,this));this._ui.buttonPrevious.click($.proxy(this._previous,this));b.click($.proxy(this._nextImage,this));d.click($.proxy(this._previousImage,this));h.click($.proxy(this._toggleView,this));f.click($.proxy(function(){if(this._slideshowEnabled){this.stopSlideshow(true)}else{this._disableSlideshow=false;this.startSlideshow()}},this));a.click(function(i){window.location=$(i.currentTarget).data("link")});$('<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},_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)},_next:function(c,b){if(this._ui.buttonNext.hasClass("pointer")){if(b==undefined){this.stopSlideshow(true)}var a=Math.max((this._items*this._thumbnailWidth)-this._thumbnailContainerWidth-this._thumbnailMarginRight,0);this._thumbnailOffset=Math.min(this._thumbnailOffset+(this._thumbnailWidth*(b?b:this.options.shiftBy)),a);this._ui.imageList.css("marginLeft",(this._thumbnailOffset*-1))}this._preload();this._toggleButtons()},_previous:function(b,a){if(this._ui.buttonPrevious.hasClass("pointer")){if(a==undefined){this.stopSlideshow(true)}this._thumbnailOffset=Math.max(this._thumbnailOffset-(this._thumbnailWidth*(a?a:this.options.shiftBy)),0);this._ui.imageList.css("marginLeft",(this._thumbnailOffset*-1))}this._toggleButtons()},_nextImage:function(a){if(this._ui.slideshow.next.hasClass("pointer")){this._disableSlideshow=true;this.stopSlideshow(true);this.showImage(this._active+1)}},_previousImage:function(a){if(this._ui.slideshow.previous.hasClass("pointer")){this._disableSlideshow=true;this.stopSlideshow(true);this.showImage(this._active-1)}},moveToImage:function(b){var f=(b-3)*this._thumbnailWidth;var d=f+(this._thumbnailWidth*5);var e=this._thumbnailOffset;var a=this._thumbnailOffset+this._thumbnailContainerWidth;var g=false;if(f<e||d>a){g=true}if(g){var c=0;if(f<e){while(f<e){c++;e-=this._thumbnailWidth}this._previous(null,c)}else{while(d>a){c++;a+=this._thumbnailWidth}this._next(null,c)}}},_toggleButtons:function(){if(this._thumbnailOffset>0){this._ui.buttonPrevious.addClass("pointer")}else{this._ui.buttonPrevious.removeClass("pointer")}var a=(this._images.length*this._thumbnailWidth)-this._thumbnailContainerWidth-this._thumbnailMarginRight;if(this._thumbnailOffset>=a){this._ui.buttonNext.removeClass("pointer")}else{this._ui.buttonNext.addClass("pointer")}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")}},_createThumbnails:function(a){for(var f=0,e=a.length;f<e;f++){var d=a[f];var c=$('<li class="loading pointer"><img src="'+d.thumbnail.url+'" /></li>').appendTo(this._ui.imageList);c.data("index",this._images.length).data("objectID",d.objectID).click($.proxy(this._showImage,this));var b=c.children("img");if(b.get(0).complete){c.removeClass("loading")}else{b.on("load",function(){$(this).parent().removeClass("loading")})}d.listItem=c;this._images.push(d)}},_loadNextImages:function(a){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:(a&&this.element.data("targetImageID")?this.element.data("targetImageID"):0)}});this._proxy.setOption("showLoadingOverlay",false);this._proxy.sendRequest()},_success:function(c,e,b){if(c.returnValues.items){this._items=c.returnValues.items}var a=this._initUI();this._createThumbnails(c.returnValues.images);var d=(c.returnValues.targetImageID?c.returnValues.targetImageID:0);this._render(a,d)}});
+
+// WCF.Label.js
+WCF.Label={};WCF.Label.ACPList=Class.extend({_labelInput:null,_labelList:[],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(b,a){var c=$(a);if(c.prop("value")!=="custom"){this._labelList.push($(c.next("span")))}},this))},_keyPressed:function(){var a=this._labelInput.prop("value");if(a===""){a=WCF.Language.get("wcf.acp.label.defaultValue")}for(var c=0,b=this._labelList.length;c<b;c++){this._labelList[c].text(a)}}});WCF.Label.ACPList.Connect=Class.extend({init:function(){var a=$("#connect .structuredList li");if(!a.length){return}a.each($.proxy(function(b,c){$(c).find('input[type="checkbox"]').click($.proxy(this._click,this))},this))},_click:function(c){var a=$(c.currentTarget);if(a.is(":checked")){a=a.parents("li");var b=a.data("depth");while(true){a=a.next();if(!a.length){return true}if(a.data("depth")<=b){return true}a.find('input[type="checkbox"]').prop("checked","checked")}}}});WCF.Label.Chooser=Class.extend({_container:null,_groups:{},_showWithoutSelection:false,init:function(f,b,e,h){this._container=null;this._groups={};this._showWithoutSelection=(h===true);this._initContainers(b);if($.getLength(f)){for(var a in f){var d=this._groups[a];if(d){WCF.Dropdown.getDropdownMenu(d.wcfIdentify()).find("> ul > li:not(.dropdownDivider)").each($.proxy(function(k,j){var i=$(j);var l=i.data("labelID")||0;if(l&&f[a]==l){this._selectLabel(i,true)}},this))}}}for(var c in this._containers){var g=this._containers[c];if(g.data("labelID")===undefined){g.data("labelID",0)}}this._container=$(b);if(e){$(e).click($.proxy(this._submit,this))}else{if(this._container.is("form")){this._container.submit($.proxy(this._submit,this))}}},_initContainers:function(a){$(a).find(".labelChooser").each($.proxy(function(d,i){var f=$(i);var b=f.data("groupID");if(!this._groups[b]){var e=f.wcfIdentify();var c=WCF.Dropdown.getDropdownMenu(e);if(c===null){WCF.Dropdown.initDropdown(f.find(".dropdownToggle"));c=WCF.Dropdown.getDropdownMenu(e)}var h=c;if(c.getTagName()=="div"&&c.children(".scrollableDropdownMenu").length){h=$("<ul />").appendTo(c);c=c.children(".scrollableDropdownMenu")}this._groups[b]=f;c.children("li").data("groupID",b).click($.proxy(this._click,this));if(!f.data("forceSelection")||this._showWithoutSelection){$('<li class="dropdownDivider" />').appendTo(h)}if(this._showWithoutSelection){$('<li data-label-id="-1"><span><span class="badge label">'+WCF.Language.get("wcf.label.withoutSelection")+"</span></span></li>").data("groupID",b).appendTo(h).click($.proxy(this._click,this))}if(!f.data("forceSelection")){var g=$('<li data-label-id="0"><span><span class="badge label">'+WCF.Language.get("wcf.label.none")+"</span></span></li>").data("groupID",b).appendTo(h);g.click($.proxy(this._click,this))}}},this))},_click:function(a){this._selectLabel($(a.currentTarget),false)},_selectLabel:function(a,c){var b=this._groups[a.data("groupID")];if(c&&b.data("labelID")!==undefined){return}if(a.data("labelID")){b.data("labelID",a.data("labelID"))}else{b.data("labelID",0)}a=a.find("span > span");b.find(".dropdownToggle > span").removeClass().addClass(a.attr("class")).text(a.text())},_submit:function(){var b=this._container.find(".formSubmit");b.find('input[type="hidden"]').each(function(e,d){var f=$(d);if(f.attr("name").indexOf("labelIDs[")===0){f.remove()}});for(var a in this._groups){var c=this._groups[a];if(c.data("labelID")){$('<input type="hidden" name="labelIDs['+a+']" value="'+c.data("labelID")+'" />').appendTo(b)}}}});
+
+// WCF.Location.js
+WCF.Location={};WCF.Location.Util={getLocation:function(b,a){if(navigator.geolocation){navigator.geolocation.getCurrentPosition(function(c){b(c.coords.latitude,c.coords.longitude)},function(){b(undefined,undefined)},{timeout:a||5000})}else{b(undefined,undefined)}}};WCF.Location.GoogleMaps={};WCF.Location.GoogleMaps.Settings={_settings:{},get:function(a){if(a===undefined){return this._settings}if(this._settings[a]!==undefined){return this._settings[a]}return null},set:function(b,c){if($.isPlainObject(b)){for(var a in b){this._settings[a]=b[a]}}else{this._settings[b]=c}}};WCF.Location.GoogleMaps.Map=Class.extend({_map:null,_markers:[],init:function(b,a){this._mapContainer=$("#"+b);this._mapOptions=$.extend(true,this._getDefaultMapOptions(),a);this._map=new google.maps.Map(this._mapContainer[0],this._mapOptions);this._markers=[];if(this._mapContainer.parents(".sidebar").length){enquire.register("screen and (max-width: 800px)",{setup:$.proxy(this._addSidebarMapListener,this),deferSetup:true})}this.refresh()},_addSidebarMapListener:function(){$(".content > .mobileSidebarToggleButton").click($.proxy(this.refresh,this))},_getDefaultMapOptions:function(){var a={};a.center=new google.maps.LatLng(WCF.Location.GoogleMaps.Settings.get("defaultLatitude"),WCF.Location.GoogleMaps.Settings.get("defaultLongitude"));a.disableDoubleClickZoom=WCF.Location.GoogleMaps.Settings.get("disableDoubleClickZoom");a.draggable=WCF.Location.GoogleMaps.Settings.get("draggable");switch(WCF.Location.GoogleMaps.Settings.get("mapType")){case"map":a.mapTypeId=google.maps.MapTypeId.ROADMAP;break;case"satellite":a.mapTypeId=google.maps.MapTypeId.SATELLITE;break;case"physical":a.mapTypeId=google.maps.MapTypeId.TERRAIN;break;case"hybrid":default:a.mapTypeId=google.maps.MapTypeId.HYBRID;break}a.mapTypeControl=WCF.Location.GoogleMaps.Settings.get("mapTypeControl")!="off";if(a.mapTypeControl){switch(WCF.Location.GoogleMaps.Settings.get("mapTypeControl")){case"dropdown":a.mapTypeControlOptions={style:google.maps.MapTypeControlStyle.DROPDOWN_MENU};break;case"horizontalBar":a.mapTypeControlOptions={style:google.maps.MapTypeControlStyle.HORIZONTAL_BAR};break;default:a.mapTypeControlOptions={style:google.maps.MapTypeControlStyle.DEFAULT};break}}a.scaleControl=WCF.Location.GoogleMaps.Settings.get("scaleControl");a.scrollwheel=WCF.Location.GoogleMaps.Settings.get("scrollwheel");a.zoom=WCF.Location.GoogleMaps.Settings.get("zoom");return a},addDraggableMarker:function(c,b){var a=new google.maps.Marker({clickable:false,draggable:true,map:this._map,position:new google.maps.LatLng(c,b),zIndex:1});this._markers.push(a);return a},addMarker:function(g,e,f,d,c){var b=new google.maps.Marker({map:this._map,position:new google.maps.LatLng(g,e),title:f});if(d){b.setIcon(d)}if(c){var a=new google.maps.InfoWindow({content:c});google.maps.event.addListener(b,"click",$.proxy(function(){a.open(this._map,b)},this));b.infoWindow=a}this._markers.push(b);return b},getMarkers:function(){return this._markers},getMap:function(){return this._map},refresh:function(){var a=this._map.getCenter();google.maps.event.trigger(this._map,"resize");this._map.setCenter(a)},refreshBounds:function(){var f=null;var c=null;var d=null;var g=null;for(var a in this._markers){var e=this._markers[a];var h=e.getPosition().lat();var b=e.getPosition().lng();if(f===null){f=c=h;d=g=b}else{if(f>h){f=h}else{if(c<h){c=h}}if(d>h){d=h}else{if(g<b){g=b}}}}this._map.fitBounds(new google.maps.LatLngBounds(new google.maps.LatLng(f,d),new google.maps.LatLng(c,g)))},removeMarkers:function(){for(var a in this._markers){this._markers[a].setMap(null)}this._markers=[]},setCenter:function(b,a){this._map.setCenter(new google.maps.LatLng(b,a))}});WCF.Location.GoogleMaps.LargeMap=WCF.Location.GoogleMaps.Map.extend({_actionClassName:null,_locationSearch:null,_locationSearchInputSelector:null,_markerClusterer:null,_objectIDs:[],_previousNorthEast:null,_previousSouthWest:null,init:function(d,a,c,b){this._super(d,a);this._actionClassName=c;this._locationSearchInputSelector=b||"";this._objectIDs=[];if(this._locationSearchInputSelector){this._locationSearch=new WCF.Location.GoogleMaps.LocationSearch(b,$.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))},_centerMap:function(a){this.setCenter(a.location.lat(),a.location.lng());$(this._locationSearchInputSelector).val(a.label)},_loadMarkers:function(){var a=this._map.getBounds().getNorthEast();var b=this._map.getBounds().getSouthWest();if(this._previousNorthEast&&this._previousNorthEast.lat()>=a.lat()&&this._previousNorthEast.lng()>=a.lng()&&this._previousSouthWest.lat()<=b.lat()&&this._previousSouthWest.lng()<=b.lng()){return}this._previousNorthEast=a;this._previousSouthWest=b;this._proxy.setOption("data",{actionName:"getMapMarkers",className:this._actionClassName,parameters:{excludedObjectIDs:this._objectIDs,eastLongitude:a.lng(),northLatitude:a.lat(),southLatitude:b.lat(),westLongitude:b.lng()}});this._proxy.sendRequest()},_success:function(d,e,c){if(d.returnValues&&d.returnValues.markers){for(var a in d.returnValues.markers){var b=d.returnValues.markers[a];this.addMarker(b.latitude,b.longitude,b.title,null,b.infoWindow);if(b.objectID){this._objectIDs.push(b.objectID)}else{if(b.objectIDs){this._objectIDs=this._objectIDs.concat(b.objectIDs)}}}}},addMarker:function(f,d,e,c,b){var a=this._super(f,d,e,c,b);this._markerClusterer.addMarker(a);return a}});WCF.Location.GoogleMaps.LocationSearch=WCF.Search.Base.extend({_geocoder:null,init:function(b,e,a,c,d){this._super(b,e,a,c,d);this._geocoder=new google.maps.Geocoder()},_createListItem:function(b){var a=$("<li><span>"+WCF.String.escapeHTML(b.formatted_address)+"</span></li>").appendTo(this._list);a.data("location",b.geometry.location).data("label",b.formatted_address).click($.proxy(this._executeCallback,this));this._itemCount++;return a},_keyUp:function(b){switch(b.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(b);break}var a=this._getSearchString(b);if(a===""){this._clearList(true)}else{if(a.length>=this._triggerLength){this._clearList(false);this._geocoder.geocode({address:a},$.proxy(this._success,this))}else{this._clearList(false)}}},_success:function(d,b){if(b!=google.maps.GeocoderStatus.OK){return}if($.getLength(d)){var c=0;for(var a in d){this._createListItem(d[a]);if(++c==10){break}}}else{if(!this._handleEmptyResult()){return}}WCF.CloseOverlayHandler.addCallback("WCF.Search.Base",$.proxy(function(){this._clearList()},this));var e=this._searchInput.parents(".dropdown").wcfIdentify();if(!WCF.Dropdown.getDropdownMenu(e).hasClass("dropdownOpen")){WCF.Dropdown.toggleDropdown(e)}this._itemIndex=-1;if(!WCF.Dropdown.getDropdown(e).data("disableAutoFocus")){this._selectNextItem()}}});WCF.Location.GoogleMaps.LocationInput=Class.extend({_locationSearch:null,_map:null,_marker:null,init:function(d,b,a,e,c){this._searchInput=a;this._map=new WCF.Location.GoogleMaps.Map(d,b);this._locationSearch=new WCF.Location.GoogleMaps.LocationSearch(a,$.proxy(this._setMarkerByLocation,this));if(e&&c){this._marker=this._map.addDraggableMarker(e,c)}else{this._marker=this._map.addDraggableMarker(0,0);WCF.Location.Util.getLocation($.proxy(function(g,f){if(g!==undefined&&f!==undefined){WCF.Location.GoogleMaps.Util.moveMarker(this._marker,g,f);WCF.Location.GoogleMaps.Util.focusMarker(this._marker)}},this))}this._marker.addListener("dragend",$.proxy(this._updateLocation,this))},getMap:function(){return this._map},getMarker:function(){return this._marker},_updateLocation:function(){WCF.Location.GoogleMaps.Util.reverseGeocoding($.proxy(function(a){if(a!==null){$(this._searchInput).val(a)}},this),this._marker)},_setMarkerByLocation:function(a){this._marker.setPosition(a.location);WCF.Location.GoogleMaps.Util.focusMarker(this._marker);$(this._searchInput).val(a.label)}});WCF.Location.GoogleMaps.Util={_geocoder:null,focusMarker:function(a){a.getMap().setCenter(a.getPosition())},getMarkerPosition:function(a){return{latitude:a.getPosition().lat(),longitude:a.getPosition().lng()}},moveMarker:function(a,d,b,c){a.setPosition(new google.maps.LatLng(d,b));if(c){google.maps.event.trigger(a,"dragend")}},reverseGeocoding:function(f,a,e,c,b){if(a){e=a.getPosition().lat();c=a.getPosition().lng()}if(this._geocoder===null){this._geocoder=new google.maps.Geocoder()}var d=new google.maps.LatLng(e,c);this._geocoder.geocode({latLng:d},function(h,g){if(g==google.maps.GeocoderStatus.OK){f((b?h:h[0].formatted_address))}else{f(null)}})}};
+
+// WCF.Message.js
+WCF.Message={};WCF.Message.BBCode={};WCF.Message.BBCode.CodeViewer=Class.extend({_dialog:null,init:function(){this._dialog=null;this._initCodeBoxes();WCF.DOMNodeInsertedHandler.addCallback("WCF.Message.BBCode.CodeViewer",$.proxy(this._initCodeBoxes,this));WCF.DOMNodeInsertedHandler.execute()},_initCodeBoxes:function(){$(".codeBox:not(.jsCodeViewer)").each($.proxy(function(a,c){var b=$(c).addClass("jsCodeViewer");$('<span class="icon icon16 icon-copy pointer jsTooltip" title="'+WCF.Language.get("wcf.message.bbcode.code.copy")+'" />').appendTo(b.find("div > h3")).click($.proxy(this._click,this))},this))},_click:function(b){var a="";$(b.currentTarget).parents("div").next("ol").children("li").each(function(c,d){if(a){a+="\n"}a+=$(d).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(a);this._dialog.wcfDialog({title:WCF.Language.get("wcf.message.bbcode.code.copy")})}else{this._dialog.children("textarea").val(a);this._dialog.wcfDialog("open")}this._dialog.children("textarea").select()}});WCF.Message.FormGuard=Class.extend({init:function(){var a=$("form.jsFormGuard").removeClass("jsFormGuard").submit(function(){$(this).find(".formSubmit input[type=submit]").disable()});$(window).unload(function(){a.find(".formSubmit input[type=submit]").enable()})}});WCF.Message.Preview=Class.extend({_className:"",_messageFieldID:"",_messageField:null,_proxy:null,_previewButton:null,_previewButtonLabel:"",init:function(b,a,c){this._className=b;this._messageFieldID=$.wcfEscapeID(a);this._messageField=$("#"+this._messageFieldID);if(!this._messageField.length){console.debug("[WCF.Message.Preview] Unable to find message field identified by '"+this._messageFieldID+"'");return}c=$.wcfEscapeID(c);this._previewButton=$("#"+c);if(!this._previewButton.length){console.debug("[WCF.Message.Preview] Unable to find preview button identified by '"+c+"'");return}this._previewButton.click($.proxy(this._click,this));this._proxy=new WCF.Action.Proxy({failure:$.proxy(this._failure,this),success:$.proxy(this._success,this)})},_click:function(b){var a=this._getMessage();if(a===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(a)});this._proxy.sendRequest();this._previewButtonLabel=this._previewButton.html();this._previewButton.html(WCF.Language.get("wcf.global.loading")).disable();b.stopPropagation();return false},_getParameters:function(b){var a={};$("#settings").find("input[type=checkbox]").each(function(c,e){var d=$(e);if(d.is(":checked")){a[d.prop("name")]=d.prop("value")}});return{data:{message:b},options:a}},_getMessage:function(){if(!$.browser.redactor){return this._messageField.val()}else{if(this._messageField.data("redactor")){return this._messageField.redactor("getText")}}return null},_success:function(b,c,a){this._previewButton.html(this._previewButtonLabel).enable();this._messageField.parent().children("small.innerError").remove();this._handleResponse(b)},_handleResponse:function(a){},_failure:function(a){if(a===null||a.returnValues===undefined||a.returnValues.errorType===undefined){return true}this._previewButton.html(this._previewButtonLabel).enable();var b=this._messageField.next("small.innerError").empty();if(!b.length){b=$('<small class="innerError" />').appendTo(this._messageField.parent())}b.html(a.returnValues.errorType);return false}});WCF.Message.DefaultPreview=WCF.Message.Preview.extend({_attachmentObjectType:null,_attachmentObjectID:null,_tmpHash:null,init:function(b,a,c){this._super("wcf\\data\\bbcode\\MessagePreviewAction","text","previewButton");this._attachmentObjectType=b||null;this._attachmentObjectID=a||null;this._tmpHash=c||null},_handleResponse:function(b){var a=$("#previewContainer");if(!a.length){a=$('<div class="container containerPadding marginTop" id="previewContainer"><fieldset><legend>'+WCF.Language.get("wcf.global.preview")+"</legend><div></div></fieldset>").prependTo($("#messageContainer")).wcfFadeIn()}a.find("div:eq(0)").html(b.returnValues.message);new WCF.Effect.Scroll().scrollTo(a)},_getParameters:function(b){var a=this._super(b);if(this._attachmentObjectType!=null){a.attachmentObjectType=this._attachmentObjectType;a.attachmentObjectID=this._attachmentObjectID;a.tmpHash=this._tmpHash}return a}});WCF.Message.Multilingualism=Class.extend({_availableLanguages:{},_languageID:0,_languageInput:null,init:function(c,d,a){this._availableLanguages=d;this._languageID=c||0;this._languageInput=$("#languageID");this._updateLabel();this._languageInput.find(".dropdownMenu > li").click($.proxy(this._click,this));if(!a){var b=this._languageInput.find(".dropdownMenu");$('<li class="dropdownDivider" />').appendTo(b);$('<li><span><span class="badge">'+this._availableLanguages[0]+"</span></span></li>").click($.proxy(this._disable,this)).appendTo(b)}this._languageInput.parents("form").submit($.proxy(this._submit,this))},_click:function(a){this._languageID=$(a.currentTarget).data("languageID");this._updateLabel()},_disable:function(){this._languageID=0;this._updateLabel()},_updateLabel:function(){this._languageInput.find(".dropdownToggle > span").text(this._availableLanguages[this._languageID])},_submit:function(){this._languageInput.next("input[name=languageID]").prop("value",this._languageID)}});WCF.Message.SmileyCategories=Class.extend({_cache:[],_proxy:null,init:function(){this._cache=[];this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});$("#smilies").on("wcftabsbeforeactivate",$.proxy(this._click,this));var a=this;new WCF.PeriodicalExecuter(function(b){b.stop();a._click({},{newTab:$("#smilies > .menu li.ui-state-active")})},100)},_click:function(b,c){var a=parseInt($(c.newTab).children("a").data("smileyCategoryID"));if(a&&!WCF.inArray(a,this._cache)){this._proxy.setOption("data",{actionName:"getSmilies",className:"wcf\\data\\smiley\\category\\SmileyCategoryAction",objectIDs:[a]});this._proxy.sendRequest()}},_success:function(c,d,b){var a=parseInt(c.returnValues.smileyCategoryID);this._cache.push(a);$("#smilies-"+a).html(c.returnValues.template)}});WCF.Message.Smilies=Class.extend({_redactor:null,_wysiwygSelector:"",init:function(a){this._wysiwygSelector=a;WCF.System.Dependency.Manager.register("Redactor_"+this._wysiwygSelector,$.proxy(function(){this._redactor=$("#"+this._wysiwygSelector).redactor("getObject");$(document).on("click",".jsSmiley",$.proxy(this._smileyClick,this))},this))},_smileyClick:function(d){var b=$(d.currentTarget);var a=b.data("smileyCode");var c=b.data("smileyPath");this._redactor.insertSmiley(a,c,true)}});WCF.Message.QuickReply=Class.extend({_container:null,_messageField:null,_notification:null,_pendingSave:false,_proxy:null,_quoteManager:null,_scrollHandler:null,_successMessageNonVisible:"",init:function(c,b){this._container=$("#messageQuickReply");this._container.children(".message").addClass("jsInvalidQuoteTarget");this._messageField=$("#text");this._pendingSave=false;if(!this._container||!this._messageField){return}var a=this._container.find(".formSubmit");a.find("button[data-type=save]").click($.proxy(this._save,this));if(c){a.find("button[data-type=extended]").click($.proxy(this._prepareExtended,this))}a.find("button[data-type=cancel]").click($.proxy(this._cancel,this));if(b){this._quoteManager=b}$(".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=""},click:function(b){this._container.toggle();if(this._container.is(":visible")){this._scroll.scrollTo(this._container,true);WCF.Message.Submit.registerButton("text",this._container.find(".formSubmit button[data-type=save]"));if(this._quoteManager){var a=true;if($.browser.redactor){if(this._messageField.data("redactor")){a=(!$.trim(this._messageField.redactor("getText")));this._editorCallback(a)}}else{a=(!this._messageField.val().length);this._editorCallback(a)}}}if(b!==null){b.stopPropagation();return false}},_editorCallback:function(a){if(a){this._quoteManager.insertQuotes(this._getClassName(),this._getObjectID(),$.proxy(this._insertQuotes,this))}if($.browser.redactor){this._messageField.redactor("focus")}else{this._messageField.focus()}},getContainer:function(){return this._container},_insertQuotes:function(a){if(!a.returnValues.template){return}if($.browser.redactor){this._messageField.redactor("insertDynamic",a.returnValues.template)}else{this._messageField.val(a.returnValues.template)}},_save:function(){if(this._pendingSave){return}var a="";if($.browser.redactor){a=this._messageField.redactor("getText")}else{a=$.trim(this._messageField.val())}var c=this._messageField.parent().find("small.innerError");if(a===""||a==="0"){if(!c.length){c=$('<small class="innerError" />').appendTo(this._messageField.parent())}c.html(WCF.Language.get("wcf.global.form.error.empty"));return}else{c.remove()}this._pendingSave=true;this._proxy.setOption("data",{actionName:"quickReply",className:this._getClassName(),interfaceName:"wcf\\data\\IMessageQuickReplyAction",parameters:this._getParameters(a)});this._proxy.sendRequest();var b=this._container.find(".messageQuickReplyContent .messageBody");$('<span class="icon icon48 icon-spinner" />').appendTo(b);b.children(".redactor_box").hide().end().next().hide()},_getParameters:function(b){var a={objectID:this._getObjectID(),data:{message:b},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")){a.anchor=this._container.data("anchor")}return a},_cancel:function(){this._revertQuickReply(true);if($.browser.redactor){this._messageField.redactor("reset")}else{this._messageField.val("")}},_revertQuickReply:function(b){var a=this._container.find(".messageQuickReplyContent .messageBody");if(b){this._container.hide();a.children("small.innerError").remove()}a.children(".icon-spinner").remove();a.children(".redactor_box").show();a.next().show()},_prepareExtended:function(){this._pendingSave=true;if(this._quoteManager!==null){this._quoteManager.markQuotesForRemoval()}var a="";if($.browser.redactor){a=this._messageField.redactor("getText")}else{a=this._messageField.val()}new WCF.Action.Proxy({autoSend:true,data:{actionName:"jumpToExtended",className:this._getClassName(),interfaceName:"wcf\\data\\IExtendedMessageQuickReplyAction",parameters:{containerID:this._getObjectID(),message:a}},success:function(c,d,b){window.location=c.returnValues.url}})},_success:function(c,d,b){if($.browser.redactor){this._messageField.redactor("autosavePurge")}if(c.returnValues.url){window.location=c.returnValues.url}else{if(c.returnValues.template){var a=$(""+c.returnValues.template);if(this._container.data("sortOrder")=="DESC"){a.insertAfter(this._container)}else{a.insertBefore(this._container)}this._container.data("lastPostTime",c.returnValues.lastPostTime);this._notification.show(undefined,undefined,WCF.Language.get("wcf.global.success.add"));this._updateHistory(a.wcfIdentify())}else{var a=(this._successMessageNonVisible)?this._successMessageNonVisible:"wcf.global.success.add";this._notification.show(undefined,5000,WCF.Language.get(a))}if($.browser.redactor){this._messageField.redactor("reset")}else{this._messageField.val("")}this._revertQuickReply(true);if(this._quoteManager!==null){this._quoteManager.countQuotes()}this._pendingSave=false}},_failure:function(b){this._pendingSave=false;this._revertQuickReply(false);if(b===null||b.returnValues===undefined||b.returnValues.errorType===undefined){return true}var a=this._container.find(".messageQuickReplyContent .messageBody");var c=a.children("small.innerError").empty();if(!c.length){c=$('<small class="innerError" />').appendTo(a)}c.html(b.returnValues.errorType);return false},_getClassName:function(){return""},_getObjectID:function(){return 0},_updateHistory:function(a){window.location.hash=a}});WCF.Message.InlineEditor=Class.extend({_activeElementID:"",_cache:"",_container:{},_containerID:0,_dropdowns:{},_messageContainerSelector:".jsMessage",_messageEditorIDPrefix:"messageEditor",_notification:null,_proxy:null,_quoteManager:null,_supportExtendedForm:false,init:function(a,c,b){this._activeElementID="";this._cache="";this._container={};this._containerID=parseInt(a);this._dropdowns={};this._quoteManager=b||null;this._supportExtendedForm=(c)?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))},initContainers:function(){$(this._messageContainerSelector).each($.proxy(function(b,a){var e=$(a);var c=e.wcfIdentify();if(!this._container[c]){this._container[c]=e;if(e.data("canEditInline")){var d=e.find(".jsMessageEditButton:eq(0)").data("containerID",c).click($.proxy(this._clickInline,this));if(e.data("canEdit")){d.dblclick($.proxy(this._click,this))}}else{if(e.data("canEdit")){e.find(".jsMessageEditButton:eq(0)").data("containerID",c).click($.proxy(this._click,this))}}}},this))},_click:function(c,a){var b=(c===null)?a:$(c.currentTarget).data("containerID");if(this._activeElementID===""){this._activeElementID=b;this._prepare();this._proxy.setOption("data",{actionName:"beginEdit",className:this._getClassName(),interfaceName:"wcf\\data\\IMessageInlineEditorAction",parameters:{containerID:this._containerID,objectID:this._container[b].data("objectID")}});this._proxy.setOption("failure",$.proxy(function(){this._cancel()},this));this._proxy.sendRequest()}else{var d=new WCF.System.Notification(WCF.Language.get("wcf.message.error.editorAlreadyInUse"),"warning");d.show()}if(this._dropdowns[this._container[b].data("objectID")]){this._dropdowns[this._container[b].data("objectID")].removeClass("dropdownOpen")}if(c!==null){c.stopPropagation();return false}},_clickInline:function(c){var d=$(c.currentTarget);if(!d.hasClass("dropdownToggle")){var b=d.data("containerID");d.addClass("dropdownToggle").parent().addClass("dropdown");var a=$('<ul class="dropdownMenu" />').insertAfter(d);this._initDropdownMenu(b,a);WCF.DOMNodeInsertedHandler.execute();this._dropdowns[this._container[b].data("objectID")]=a;WCF.Dropdown.registerCallback(d.parent().wcfIdentify(),$.proxy(this._toggleDropdown,this));d.trigger("click")}c.stopPropagation();return false},_failure:function(b){this._revertEditor();if(b===null||b.returnValues===undefined||b.returnValues.errorType===undefined){return true}var a=this._container[this._activeElementID].find(".messageBody .messageInlineEditor");var c=a.children("small.innerError").empty();if(!c.length){c=$('<small class="innerError" />').insertBefore(a.children(".formSubmit"))}c.html(b.returnValues.errorType);return false},_toggleDropdown:function(a,b){WCF.Dropdown.getDropdown(a).parents(".messageOptions").toggleClass("forceOpen")},_initDropdownMenu:function(a,b){},_prepare:function(){var b=this._container[this._activeElementID].find(".messageBody");$('<span class="icon icon48 icon-spinner" />').appendTo(b);var a=b.find(".messageText");a.parent().children(".jsInlineEditorHideContent").hide();b.children(".attachmentThumbnailList, .attachmentFileList").hide();this._cache=a.detach()},_cancel:function(){var c=this._container[this._activeElementID].removeClass("jsInvalidQuoteTarget");var a=$("#"+this._messageEditorIDPrefix+c.data("objectID"));a.redactor("autosavePurge");a.redactor("destroy");var b=c.find(".messageBody");b.children(".icon-spinner").remove();b.children("div:eq(0)").html(this._cache);b.children(".attachmentThumbnailList, .attachmentFileList").show();b.find(".jsInlineEditorHideContent").show();this._container[this._activeElementID].find(".messageOptions").removeClass("forceHidden");this._activeElementID="";if(this._quoteManager){this._quoteManager.clearAlternativeEditor()}},_success:function(b,c,a){switch(b.returnValues.actionName){case"beginEdit":this._showEditor(b);break;case"save":this._showMessage(b);break}},_showEditor:function(f){this._proxy.setOption("failure",$.proxy(this._failure,this));var d=this._container[this._activeElementID].addClass("jsInvalidQuoteTarget").find(".messageBody");d.children(".icon-spinner").remove();var c=d.children("div:eq(0)");$(""+f.returnValues.template).appendTo(c);var b=c.find(".formSubmit");var e=b.find("button[data-type=save]").click($.proxy(this._save,this));if(this._supportExtendedForm){b.find("button[data-type=extended]").click($.proxy(this._prepareExtended,this))}b.find("button[data-type=cancel]").click($.proxy(this._cancel,this));WCF.Message.Submit.registerButton(this._messageEditorIDPrefix+this._container[this._activeElementID].data("objectID"),e);this._container[this._activeElementID].find(".messageOptions").addClass("forceHidden");var a=$("#"+this._messageEditorIDPrefix+this._container[this._activeElementID].data("objectID"));if($.browser.redactor){new WCF.PeriodicalExecuter($.proxy(function(g){g.stop();if(this._quoteManager){this._quoteManager.setAlternativeEditor(a)}},this),250)}else{a.focus()}},_revertEditor:function(){var a=this._container[this._activeElementID].removeClass("jsInvalidQuoteTarget").find(".messageBody");a.children("span.icon-spinner").remove();a.children("div:eq(0)").children().show();a.children(".attachmentThumbnailList, .attachmentFileList").show();a.find(".jsInlineEditorHideContent").show();if(this._quoteManager){this._quoteManager.clearAlternativeEditor()}},_save:function(){var c=this._container[this._activeElementID];var b=c.data("objectID");var a="";if($.browser.redactor){a=$("#"+this._messageEditorIDPrefix+b).redactor("getText")}else{a=$("#"+this._messageEditorIDPrefix+b).val()}this._proxy.setOption("data",{actionName:"save",className:this._getClassName(),interfaceName:"wcf\\data\\IMessageInlineEditorAction",parameters:{containerID:this._containerID,data:{message:a},objectID:b}});this._proxy.sendRequest();this._hideEditor()},_prepareExtended:function(){var c=this._container[this._activeElementID];var b=c.data("objectID");var a="";if($.browser.redactor){a=$("#"+this._messageEditorIDPrefix+b).redactor("getText")}else{a=$("#"+this._messageEditorIDPrefix+b).val()}new WCF.Action.Proxy({autoSend:true,data:{actionName:"jumpToExtended",className:this._getClassName(),parameters:{containerID:this._containerID,message:a,messageID:b}},success:function(e,f,d){window.location=e.returnValues.url}})},_hideEditor:function(){var a=this._container[this._activeElementID].removeClass("jsInvalidQuoteTarget").find(".messageBody");$('<span class="icon icon48 icon-spinner" />').appendTo(a);a.children("div:eq(0)").children().hide();a.children(".attachmentThumbnailList, .attachmentFileList").show();a.find(".jsInlineEditorHideContent").show();if(this._quoteManager){this._quoteManager.clearAlternativeEditor()}},_showMessage:function(c){var d=this._container[this._activeElementID].removeClass("jsInvalidQuoteTarget");var b=d.find(".messageBody");b.children(".icon-spinner").remove();var a=b.children("div:eq(0)");a.parent().children(".jsInlineEditorHideContent").show();this._container[this._activeElementID].find(".messageOptions").removeClass("forceHidden");if($.browser.redactor){$("#"+this._messageEditorIDPrefix+d.data("objectID")).redactor("destroy")}a.empty();a.html('<div class="messageText">'+c.returnValues.message+"</div>");if(c.returnValues.attachmentList==undefined){b.children(".attachmentThumbnailList, .attachmentFileList").show()}else{b.children(".attachmentThumbnailList, .attachmentFileList").remove();if(c.returnValues.attachmentList){$(c.returnValues.attachmentList).insertAfter(b.children("div:eq(0)"))}}this._activeElementID="";this._updateHistory(this._getHash(d.data("objectID")));this._notification.show();if(this._quoteManager){this._quoteManager.clearAlternativeEditor()}},_getClassName:function(){return""},_getHash:function(a){return"#message"+a},_updateHistory:function(a){window.location.hash=a}});WCF.Message.Submit={_buttons:{},registerButton:function(b,a){if(!WCF.Browser.isChrome()){return}this._buttons[b]=$(a)},execute:function(a){if(!this._buttons[a]){return}this._buttons[a].trigger("click")}};WCF.Message.Quote={};WCF.Message.Quote.Handler=Class.extend({_activeContainerID:"",_className:"",_containers:{},_containerSelector:"",_copyQuote:null,_message:"",_messageBodySelector:"",_objectID:0,_objectType:"",_proxy:null,_quoteManager:null,init:function(e,d,b,a,c,f){this._className=d;if(this._className==""){console.debug("[WCF.Message.QuoteManager] Empty class name given, aborting.");return}this._objectType=b;if(this._objectType==""){console.debug("[WCF.Message.QuoteManager] Empty object type name given, aborting.");return}this._containerSelector=a;this._message="";this._messageBodySelector=c;this._messageContentSelector=f;this._objectID=0;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._initContainers();this._initCopyQuote();$(document).mouseup($.proxy(this._mouseUp,this));this._quoteManager=e;this._quoteManager.register(this._objectType,this);WCF.DOMNodeInsertedHandler.addCallback("WCF.Message.Quote.Handler"+b.hashCode(),$.proxy(this._initContainers,this))},_initContainers:function(){var a=this;$(this._containerSelector).each(function(c,b){var e=$(b);var d=e.wcfIdentify();if(!a._containers[d]){a._containers[d]=e;if(e.hasClass("jsInvalidQuoteTarget")){return true}if(a._messageBodySelector!==null){e=e.find(a._messageBodySelector).data("containerID",d)}e.mousedown($.proxy(a._mouseDown,a));a._containers[d].find(".jsQuoteMessage").click($.proxy(a._saveFullQuote,a))}})},_mouseDown:function(a){this._copyQuote.hide();var b=$(a.currentTarget);if(this._messageBodySelector){b=this._containers[b.data("containerID")]}if(b.hasClass("jsInvalidQuoteTarget")){this._activeContainerID="";return}this._activeContainerID=b.wcfIdentify();if($.browser.mozilla){b.find("img").each(function(){var c=$(this);c.data("__alt",c.attr("alt")).removeAttr("alt")})}},_getNodeText:function(d){var c="";for(var b=0;b<d.childNodes.length;b++){if(d.childNodes[b].nodeType==3){c+=d.childNodes[b].nodeValue}else{if(!d.childNodes[b].tagName){continue}var a=d.childNodes[b].tagName.toLowerCase();if(a==="li"){c+="\r\n"}else{if(a==="td"&&!$.browser.msie){c+="\r\n"}}c+=this._getNodeText(d.childNodes[b]);if(a==="ul"){c+="\n"}}}return c},_mouseUp:function(a){if(this._activeContainerID==""){this._copyQuote.hide();return}var i=this._containers[this._activeContainerID];var c=this._getSelectedText();var f=$.trim(c);if(f==""){this._copyQuote.hide();return}var d=null;if(this._messageBodySelector){d=this._getNodeText(i.find(this._messageContentSelector).get(0))}else{d=this._getNodeText(i.get(0))}if(this._normalize(d).indexOf(this._normalize(f))===-1){return}this._copyQuote.show();var g=this._getBoundingRectangle(i,c);var e=this._copyQuote.getDimensions("outer");var b=(g.right-g.left)/2-(e.width/2)+g.left;this._copyQuote.css({top:g.top-e.height-7+"px",left:b+"px"});this._copyQuote.hide();this._activeContainerID="";var h=this;new WCF.PeriodicalExecuter(function(j){j.stop();var k=$.trim(h._getSelectedText());if(k!=""){h._copyQuote.show();h._message=k;h._objectID=i.data("objectID");if($.browser.mozilla){i.find("img").each(function(){var l=$(this);l.attr("alt",l.data("__alt"))})}}},10)},_normalize:function(a){return a.replace(/\r?\n|\r/g,"\n").replace(/\s/g," ").replace(/\s{2,}/g," ")},_getOffset:function(c,d){c.collapse(d);var f=WCF.getRandomID();var a=document.createElement("span");a.innerHTML='<span id="'+f+'"></span>';var g=document.createDocumentFragment(),b;while(b=a.firstChild){g.appendChild(b)}c.insertNode(g);a=$("#"+f);var e=a.offset();e.top=e.top-$(window).scrollTop();a.remove();return e},_getBoundingRectangle:function(b,e){var d=null;if(document.createRange&&typeof document.createRange().getBoundingClientRect!="undefined"){if(e.rangeCount>0){var h=e.getRangeAt(0).getClientRects();var c=e.getRangeAt(0).getBoundingClientRect();var f=$(document);var a=f.scrollTop();d={left:c.left,right:c.right,top:c.top+a}}}else{if(document.selection&&document.selection.type!="Control"){var g=document.selection.createRange();d={left:g.boundingLeft,right:g.boundingRight,top:g.boundingTop}}}return d},_saveSelection:function(d){if(window.getSelection&&document.createRange){var c=window.getSelection().getRangeAt(0);var b=c.cloneRange();b.selectNodeContents(d);b.setEnd(c.startContainer,c.startOffset);var f=b.toString().length;return{start:f,end:f+c.toString().length}}else{var a=document.selection.createRange();var e=document.body.createTextRange();e.moveToElementText(d);e.setEndPoint("EndToStart",a);var f=e.text.length;return{start:f,end:f+a.text.length}}},_restoreSelection:function(j,k){if(window.getSelection&&document.createRange){var a=0,g=document.createRange();g.setStart(j,0);g.collapse(true);var f=[j],c,d=false,l=false;while(!l&&(c=f.pop())){if(c.nodeType==3){var h=a+c.length;if(!d&&k.start>=a&&k.start<=h){g.setStart(c,k.start-a);d=true}if(d&&k.end>=a&&k.end<=h){g.setEnd(c,k.end-a);l=true}a=h}else{var e=c.childNodes.length;while(e--){f.push(c.childNodes[e])}}}var b=window.getSelection();b.removeAllRanges();b.addRange(g)}else{var m=document.body.createTextRange();m.moveToElementText(j);m.collapse(true);m.moveEnd("character",k.end);m.moveStart("character",k.start);m.select()}},_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))}},_getSelectedText:function(){if(window.getSelection){return window.getSelection()}else{if(document.getSelection){return document.getSelection()}else{if(document.selection){return document.selection.createRange().text}}}return""},_saveFullQuote:function(b){var a=$(b.currentTarget);this._proxy.setOption("data",{actionName:"saveFullQuote",className:this._className,interfaceName:"wcf\\data\\IMessageQuoteAction",objectIDs:[a.data("objectID")]});this._proxy.sendRequest();if(a.data("isQuoted")){a.data("isQuoted",false).children("a").removeClass("active")}else{a.data("isQuoted",true).children("a").addClass("active")}b.stopPropagation();return false},_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()},_success:function(c,d,b){if(c.returnValues.count!==undefined){var a=(c.fullQuoteObjectIDs!==undefined)?c.fullQuoteObjectIDs:{};this._quoteManager.updateCount(c.returnValues.count,a)}},updateFullQuoteObjectIDs:function(b){for(var a in this._containers){this._containers[a].find(".jsQuoteMessage").each(function(c,d){var e=$(d).data("isQuoted",0);e.children("a").removeClass("active");if(WCF.inArray(e.data("objectID"),b)){e.data("isQuoted",1).children("a").addClass("active")}})}}});WCF.Message.Quote.Manager=Class.extend({_buttons:{},_count:0,_dialog:null,_editorElement:null,_editorElementAlternative:null,_form:null,_handlers:{},_hasTemplate:false,_insertQuotes:true,_proxy:null,_removeOnSubmit:[],_showQuotes:null,_supportPaste:false,init:function(c,a,b,d){this._buttons={insert:null,remove:null};this._count=parseInt(c)||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(a){this._editorElement=$("#"+a);if(this._editorElement.length){this._supportPaste=true;this._form=this._editorElement.parents("form:eq(0)");if(this._form.length){this._form.submit($.proxy(this._submit,this));this._removeOnSubmit=d||[]}else{this._form=null;this._supportPaste=(b===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()},setAlternativeEditor:function(a){this._editorElementAlternative=a},clearAlternativeEditor:function(){this._editorElementAlternative=null},register:function(a,b){this._handlers[a]=b},updateCount:function(c,b){this._count=parseInt(c)||0;this._toggleShowQuotes();for(var a in this._handlers){if(b[a]){this._handlers[a].updateFullQuoteObjectIDs(b[a])}}},insertQuotes:function(a,b,c){if(!this._insertQuotes){this._insertQuotes=true;return}new WCF.Action.Proxy({autoSend:true,data:{actionName:"getRenderedQuotes",className:a,interfaceName:"wcf\\data\\IMessageQuoteAction",parameters:{parentObjectID:b}},success:c})},_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 a=WCF.Language.get("wcf.message.quote.showQuotes").replace(/#count#/,this._count);this._showQuotes.text(a).show()}this._hasTemplate=false},_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()}},renderDialog:function(c){if(this._dialog===null){this._dialog=$("#messageQuoteList");if(!this._dialog.length){this._dialog=$('<div id="messageQuoteList" />').hide().appendTo(document.body)}}this._dialog.html(c);var a=$('<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(a)}this._buttons.remove=$("<button>"+WCF.Language.get("wcf.message.quote.removeAllQuotes")+"</button>").click($.proxy(this._removeSelected,this)).appendTo(a);this._dialog.wcfDialog({title:WCF.Language.get("wcf.message.quote.manageQuotes")});this._dialog.wcfDialog("render");this._hasTemplate=true;var d=this._dialog.find(".jsInsertQuote");if(this._supportPaste){d.click($.proxy(this._insertQuote,this))}else{d.hide()}this._dialog.find("input.jsCheckbox").change($.proxy(this._changeButtons,this));if(this._removeOnSubmit.length){var b=this;this._dialog.find("input.jsRemoveQuote").each(function(f,e){var g=$(e).change($.proxy(this._change,this));if(WCF.inArray(g.parent("li").attr("data-quote-id"),b._removeOnSubmit)){g.attr("checked","checked")}})}},_changeButtons:function(){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{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"))}},_change:function(c){var d=$(c.currentTarget);var b=d.parent("li").attr("data-quote-id");if(d.prop("checked")){this._removeOnSubmit.push(b)}else{for(var a in this._removeOnSubmit){if(this._removeOnSubmit[a]==b){delete this._removeOnSubmit[a];break}}}},_insertSelected:function(){if(this._editorElementAlternative===null){var a=$(".jsQuickReply:eq(0)").data("__api");if(a&&!a.getContainer().is(":visible")){this._insertQuotes=false;a.click(null)}}if(!this._dialog.find("input.jsCheckbox:checked").length){this._dialog.find("input.jsCheckbox").prop("checked","checked")}this._dialog.find("input.jsCheckbox:checked").each($.proxy(function(c,b){this._insertQuote(null,b)},this));this._dialog.wcfDialog("close")},_insertQuote:function(a,h){if(a!==null&&this._editorElementAlternative===null){var c=$(".jsQuickReply:eq(0)").data("__api");if(c&&!c.getContainer().is(":visible")){this._insertQuotes=false;c.click(null)}}var f=(a===null)?$(h).parents("li"):$(a.currentTarget).parents("li");var i=$.trim(f.children("div.jsFullQuote").text());var e=f.parents("article.message");i="[quote='"+e.attr("data-username")+"','"+e.data("link")+"']"+i+"[/quote]";if($.browser.redactor){if(this._editorElementAlternative===null){this._editorElement.redactor("insertDynamic",i)}else{this._editorElementAlternative.redactor("insertDynamic",i)}}else{var d=(this._editorElementAlternative===null)?this._editorElement:this._editorElementAlternative;var g=d.val();i+="\n\n";if(g.length==0){d.val(i)}else{var b=d.getCaret();d.val(g.substr(0,b)+i+g.substr(b))}}this._removeOnSubmit.push(f.attr("data-quote-id"));if(a!==null){this._dialog.wcfDialog("close")}},_removeSelected:function(){if(!this._dialog.find("input.jsCheckbox:checked").length){this._dialog.find("input.jsCheckbox").prop("checked","checked")}var b=[];this._dialog.find("input.jsCheckbox:checked").each(function(e,d){b.push($(d).parents("li").attr("data-quote-id"))});if(b.length){var c=[];for(var a in this._handlers){c.push(a)}this._proxy.setOption("data",{actionName:"remove",getFullQuoteObjectIDs:this._handlers.length>0,objectTypes:c,quoteIDs:b});this._proxy.sendRequest();this._dialog.wcfDialog("close")}},_submit:function(){if(this._supportPaste&&this._removeOnSubmit.length>0){var a=this._form.find(".formSubmit");for(var b in this._removeOnSubmit){$('<input type="hidden" name="__removeQuoteIDs[]" value="'+this._removeOnSubmit[b]+'" />').appendTo(a)}}},getQuotesMarkedForRemoval:function(){return this._removeOnSubmit},markQuotesForRemoval:function(){if(this._removeOnSubmit.length){this._proxy.setOption("data",{actionName:"markForRemoval",quoteIDs:this._removeOnSubmit});this._proxy.suppressErrors();this._proxy.sendRequest()}},removeMarkedQuotes:function(){if(this._removeOnSubmit.length){this._proxy.setOption("data",{actionName:"removeMarkedQuotes",getFullQuoteObjectIDs:this._handlers.length>0});this._proxy.sendRequest()}},countQuotes:function(){var b=[];for(var a in this._handlers){b.push(a)}this._proxy.setOption("data",{actionName:"count",getFullQuoteObjectIDs:this._handlers.length>0,objectTypes:b});this._proxy.sendRequest()},_success:function(c,d,b){if(c===null){return}if(c.count!==undefined){var a=(c.fullQuoteObjectIDs!==undefined)?c.fullQuoteObjectIDs:{};this.updateCount(c.count,a)}if(c.template!==undefined){if($.trim(c.template)==""){this.updateCount(0,{})}else{this.renderDialog(c.template)}}}});WCF.Message.Share={};WCF.Message.Share.Content=Class.extend({_cache:{},_dialog:null,init:function(){this._cache={};this._dialog=null;this._initLinks();WCF.DOMNodeInsertedHandler.addCallback("WCF.Message.Share.Content",$.proxy(this._initLinks,this))},_initLinks:function(){$("a.jsButtonShare").removeClass("jsButtonShare").click($.proxy(this._click,this))},_click:function(e){e.preventDefault();var a=$(e.currentTarget);var b=a.prop("href");var d=(a.data("linkTitle")?a.data("linkTitle"):b);var c=b.hashCode();if(this._cache[c]===undefined){var g=false;if(this._dialog===null){this._dialog=$("<div />").hide().appendTo(document.body);g=true}else{this._dialog.empty()}var f=$('<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",b).appendTo(f);var f=$('<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='"+b+"']"+d+"[/url]").appendTo(f);var f=$('<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="'+b+'">'+WCF.String.escapeHTML(d)+"</a>").appendTo(f);this._cache[c]=this._dialog.html();if(g){this._dialog.wcfDialog({title:WCF.Language.get("wcf.message.share")})}else{this._dialog.wcfDialog("open")}}else{this._dialog.html(this._cache[c]).wcfDialog("open")}this._enableSelection()},_enableSelection:function(){var a=this._dialog.find("input").click(function(){$(this).select()});if(navigator.userAgent.match(/iP(ad|hone|od)/)){a.keydown(function(){return false}).removeAttr("readonly").click(function(){this.setSelectionRange(0,9999)})}}});WCF.Message.Share.Page=Class.extend({_ui:{},_pageDescription:"",_pageURL:"",init:function(a){this._pageDescription=encodeURIComponent($('meta[property="og:title"]').prop("content"));this._pageURL=encodeURIComponent($('meta[property="og:url"]').prop("content"));var b=$(".messageShareButtons");this._ui={facebook:b.find(".jsShareFacebook"),google:b.find(".jsShareGoogle"),reddit:b.find(".jsShareReddit"),twitter:b.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(a===true){this._fetchFacebook();this._fetchTwitter();this._fetchReddit()}},_share:function(c,a,b){window.open(a.replace(/{pageURL}/,this._pageURL).replace(/{text}/,this._pageDescription+(b?" "+this._pageURL:"")),"height=600,width=600")},_shareFacebook:function(){this._share("facebook","https://www.facebook.com/sharer.php?u={pageURL}&t={text}",true)},_shareGoogle:function(){this._share("google","https://plus.google.com/share?url={pageURL}",true)},_shareReddit:function(){this._share("reddit","https://ssl.reddit.com/submit?url={pageURL}",true)},_shareTwitter:function(){this._share("twitter","https://twitter.com/share?url={pageURL}&text={text}",false)},_fetchCount:function(b,d,c){var a={autoSend:true,dataType:"jsonp",showLoadingOverlay:false,success:d,suppressErrors:true,type:"GET",url:b.replace(/{pageURL}/,this._pageURL)};if(c){a.jsonp=c}new WCF.Action.Proxy(a)},_fetchFacebook:function(){this._fetchCount("https://graph.facebook.com/?id={pageURL}",$.proxy(function(a){if(a.shares){this._ui.facebook.children("span.badge").show().text(a.shares)}},this))},_fetchTwitter:function(){if(window.location.protocol.match(/^https/)){return}this._fetchCount("http://urls.api.twitter.com/1/urls/count.json?url={pageURL}",$.proxy(function(a){if(a.count){this._ui.twitter.children("span.badge").show().text(a.count)}},this))},_fetchReddit:function(){if(window.location.protocol.match(/^https/)){return}this._fetchCount("http://www.reddit.com/api/info.json?url={pageURL}",$.proxy(function(a){if(a.data.children.length){this._ui.reddit.children("span.badge").show().text(a.data.children[0].data.score)}},this),"jsonp")}});WCF.Message.UserMention=Class.extend({_caretPosition:null,_className:"wcf\\data\\user\\UserAction",_dropdown:null,_dropdownMenu:null,_itemIndex:-1,_lineHeight:null,_mentionStart:"",_redactor:null,init:function(a){this._textarea=$("#"+a);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)})},_clearList:function(){this._hideList();this._dropdownMenu.empty()},_click:function(a){this._redactor.replaceRangesWith(this._caretPosition);this._setUsername($(a.currentTarget).data("username"))},_createListItem:function(c){var b=$("<li />").data("username",c.label).click($.proxy(this._click,this)).appendTo(this._dropdownMenu);var a=$("<div />").addClass("box16").appendTo(b);a.append($(c.icon).addClass("framed"));a.append($("<div />").append($("<span />").text(c.label)))},_getDropdownMenuPosition:function(){var e=getSelection().getRangeAt(0).cloneRange();var a=document.createRange();a.setStart(e.startContainer,e.startOffset-(this._mentionStart.length+1));a.setEnd(e.startContainer,e.startOffset);this._redactor.replaceRangesWith(a);var f=getSelection().getRangeAt(0);var b=f.getBoundingClientRect();var d=$(window);var c={top:Math.round(b.bottom)+d.scrollTop(),left:Math.round(b.left)+d.scrollLeft()};if(this._lineHeight===null){this._lineHeight=Math.round(b.bottom-b.top)}this._redactor.replaceRangesWith(e);this._caretPosition=e;return c},_setUsername:function(e){var c=getSelection().getRangeAt(0).cloneRange();this._redactor.bufferSet();var a=document.createRange();a.setStart(c.startContainer,c.startOffset-(this._mentionStart.length+1));a.setEnd(c.startContainer,c.startOffset);this._redactor.replaceRangesWith(a);var d=getSelection().getRangeAt(0);d.deleteContents();d.collapse(true);if(e.indexOf("'")!==-1){e=e.replace(/'/g,"''");e="'"+e+"'"}else{if(e.indexOf(" ")!==-1){e="'"+e+"'"}}var b=document.createTextNode("@"+e);d.insertNode(b);var a=document.createRange();a.setStart(b,e.length+1);a.setEnd(b,e.length+1);this._redactor.replaceRangesWith(a);this._hideList()},_getParameters:function(){return{data:{includeUserGroups:false,searchString:this._mentionStart}}},_getTextLineInFrontOfCaret:function(){if(this._redactor.getSelectionHtml().length){return""}var e=this._redactor.getSelection().getRangeAt(0);var b=e.startContainer.textContent.substr(0,e.startOffset);var a=b;b="";for(var d=0;d<a.length;d++){var c=a.charCodeAt(d).toString(16);if(c!="200b"&&!/\s/.test(a[d])){if(a[d]==="@"&&d&&/\s/.test(a[d-1])){b=""}b+=a[d]}else{b=""}}return b},_hideList:function(){this._dropdown.removeClass("dropdownOpen");this._dropdownMenu.removeClass("dropdownOpen");this._itemIndex=-1},_keydown:function(a){if(this._redactor.inPlainMode()){return true}if(this._dropdownMenu.is(":visible")){switch(a.which){case $.ui.keyCode.ENTER:a.preventDefault();this._dropdownMenu.children("li").eq(this._itemIndex).trigger("click");return false;break;case $.ui.keyCode.UP:a.preventDefault();this._selectItem(this._itemIndex-1);return false;break;case $.ui.keyCode.DOWN:a.preventDefault();this._selectItem(this._itemIndex+1);return false;break}}return true},_keyup:function(c){if(this._redactor.inPlainMode()){return true}if(c.which===$.ui.keyCode.ENTER){return}if(this._dropdownMenu.is(":visible")&&c.which in {13:1,38:1,40:1}){return}var b=this._getTextLineInFrontOfCaret();if(b){var a=b.match(/@([^,]{3,})$/);if(a){if(!a.index||b[a.index-1].match(/\s/)){this._mentionStart=a[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()}},_selectItem:function(a){var b=this._dropdownMenu.children("li");if(a<0){a=b.length-1}else{if(a+1>b.length){a=0}}b.removeClass("dropdownNavigationItem");b.eq(a).addClass("dropdownNavigationItem");this._itemIndex=a},_showList:function(){this._dropdown.addClass("dropdownOpen");this._dropdownMenu.addClass("dropdownOpen")},_success:function(c,e,b){this._clearList(false);if($.getLength(c.returnValues)){for(var d in c.returnValues){var a=c.returnValues[d];this._createListItem(a)}this._updateSuggestionListPosition();this._showList()}},_updateSuggestionListPosition:function(){try{var b=this._getDropdownMenuPosition();b.top+=5;this._dropdownMenu.css(b);this._selectItem(0);if(b.top+this._dropdownMenu.outerHeight()+10>$(window).height()+$(document).scrollTop()){this._dropdownMenu.addClass("dropdownArrowBottom");this._dropdownMenu.css({top:b.top-this._dropdownMenu.outerHeight()-2*this._lineHeight+5})}else{this._dropdownMenu.removeClass("dropdownArrowBottom")}}catch(a){}}});
+
+// WCF.Moderation.js
+WCF.Moderation={};WCF.Moderation.Management=Class.extend({_buttonSelector:"",_className:"",_confirmationTemplate:{},_languageItem:"",_proxy:null,_queueID:0,_redirectURL:"",init:function(a,c,b){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=a;this._redirectURL=c;this._languageItem=b;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});$(this._buttonSelector).click($.proxy(this._click,this))},_click:function(c){var b=$(c.currentTarget).wcfIdentify();var a="";if(this._confirmationTemplate[b]){a=this._confirmationTemplate[b]}WCF.System.Confirmation.show(WCF.Language.get(this._languageItem.replace(/{actionName}/,b)),$.proxy(function(e){if(e==="confirm"){var d={actionName:b,className:this._className,objectIDs:[this._queueID]};if(this._confirmationTemplate[b]){d.parameters={};a.find("input, textarea").each(function(g,i){var f=$(i);var h=f.val();if(f.getTagName()==="input"&&f.attr("type")==="checkbox"){if(!f.is(":checked")){h=null}}if(h!==null){d.parameters[f.attr("name")]=h}})}this._proxy.setOption("data",d);this._proxy.sendRequest();$(this._buttonSelector).disable()}},this),{},a)},_success:function(c,e,b){var d=new WCF.System.Notification(WCF.Language.get("wcf.global.success"));var a=this;d.show(function(){window.location=a._redirectURL})}});WCF.Moderation.Activation={};WCF.Moderation.Activation.Management=WCF.Moderation.Management.extend({init:function(a,b){this._buttonSelector="#enableContent, #removeContent";this._className="wcf\\data\\moderation\\queue\\ModerationQueueActivationAction";this._super(a,b,"wcf.moderation.activation.{actionName}.confirmMessage")}});WCF.Moderation.Report={};WCF.Moderation.Report.Content=Class.extend({_buttons:{},_buttonSelector:"",_dialog:null,_notification:null,_objectID:0,_objectType:"",_proxy:null,init:function(a,b){this._objectType=a;this._buttonSelector=b;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))},_initButtons:function(){var a=this;$(this._buttonSelector).each(function(c,d){var e=$(d);var b=e.wcfIdentify();if(!a._buttons[b]){a._buttons[b]=e;e.click($.proxy(a._click,a))}})},_click:function(a){this._objectID=$(a.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()},_success:function(b,c,a){if(b.returnValues.reported){if(this._notification===null){this._notification=new WCF.System.Notification(WCF.Language.get("wcf.moderation.report.success"))}this._dialog.wcfDialog("close");this._notification.show()}else{if(b.returnValues.template){this._showDialog(b.returnValues.template);if(!b.returnValues.alreadyReported){this._dialog.find(".jsSubmitReport").click($.proxy(this._submit,this))}}}},_showDialog:function(a){if(this._dialog===null){this._dialog=$("#moderationReport");if(!this._dialog.length){this._dialog=$('<div id="moderationReport" />').hide().appendTo(document.body)}}this._dialog.html(a).wcfDialog({title:WCF.Language.get("wcf.moderation.report.reportContent")}).wcfDialog("render")},_submit:function(){var a=this._dialog.find(".jsReportMessage").val();if(a==""){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:a,objectID:this._objectID,objectType:this._objectType}});this._proxy.sendRequest()}});WCF.Moderation.Report.Management=WCF.Moderation.Management.extend({init:function(a,b){this._buttonSelector="#removeContent, #removeReport";this._className="wcf\\data\\moderation\\queue\\ModerationQueueReportAction";this._super(a,b,"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>')}});WCF.Moderation.UserPanel=WCF.UserPanel.extend({_showAllLink:"",_deletedContentLink:"",init:function(a,b){this._noItems="wcf.moderation.noMoreItems";this._showAllLink=a;this._deletedContentLink=b;this._super("outstandingModeration")},_addDefaultItems:function(a){this._addDivider(a);$('<li><a href="'+this._showAllLink+'">'+WCF.Language.get("wcf.moderation.showAll")+"</a></li>").appendTo(a);this._addDivider(a);$('<li><a href="'+this._deletedContentLink+'">'+WCF.Language.get("wcf.moderation.showDeletedContent")+"</a></li>").appendTo(a)},_getParameters:function(){return{actionName:"getOutstandingQueues",className:"wcf\\data\\moderation\\queue\\ModerationQueueAction"}}});
+
+// WCF.Poll.js
+WCF.Poll={};WCF.Poll.Management=Class.extend({_container:null,_count:0,_inputSize:0,_maxOptions:0,init:function(a,e,f){this._count=0;this._maxOptions=f||-1;this._container=$("#"+a).children("ol:eq(0)");if(!this._container.length){console.debug("[WCF.Poll.Management] Invalid container id given, aborting.");return}e=e||[];this._createOptionList(e);$(window).resize($.proxy(this._resize,this));this._container.parents("form").submit($.proxy(this._submit,this));new WCF.Sortable.List(a,"",undefined,undefined,true);this._resize();var d=this._container.parents(".tabMenuContent:eq(0)");var c=d.wcfIdentify();var b=this;d.parents(".tabMenuContainer:eq(0)").on("wcftabsactivate",function(g,h){if(h.newPanel.wcfIdentify()==c){b._resize()}})},_createOptionList:function(b){for(var d=0,c=b.length;d<c;d++){var a=b[d];this._createOption(a.optionValue,a.optionID)}this._createOption()},_createOption:function(f,d,c){f=f||"";d=parseInt(d)||0;c=c||null;var b=$('<li class="sortableNode" />').data("optionID",d);if(c===null){b.appendTo(this._container)}else{b.insertAfter(c)}var a=$('<span class="sortableButtonContainer" />').appendTo(b);$('<span class="icon icon16 icon-plus jsTooltip jsAddOption pointer" title="'+WCF.Language.get("wcf.poll.button.addOption")+'" />').click($.proxy(this._addOption,this)).appendTo(a);$('<span class="icon icon16 icon-remove jsTooltip jsDeleteOption pointer" title="'+WCF.Language.get("wcf.poll.button.removeOption")+'" />').click($.proxy(this._removeOption,this)).appendTo(a);var e=$('<input type="text" value="'+f+'" maxlength="255" />').css({width:this._inputSize+"px"}).keydown($.proxy(this._keyDown,this)).appendTo(b);if(c!==null){e.focus()}WCF.DOMNodeInsertedHandler.execute();this._count++;if(this._count===this._maxOptions){this._container.find("span.jsAddOption").removeClass("pointer").addClass("disabled")}},_keyDown:function(a){if(a.which!==13){return true}$(a.currentTarget).prev(".sortableButtonContainer").children(".jsAddOption").trigger("click");a.preventDefault();a.stopPropagation();return false},_addOption:function(b){if(this._count===this._maxOptions){return false}var a=$(b.currentTarget).parents("li");this._createOption(undefined,undefined,a)},_removeOption:function(a){$(a.currentTarget).parents("li").remove();this._count--;this._container.find("span.jsAddOption").addClass("pointer").removeClass("disabled");if(this._container.children("li").length==0){this._createOption()}},_resize:function(){var d=this._container.innerWidth();var c=this._container.children("li:eq(0)");var b=c.children(".sortableButtonContainer").outerWidth();var a=d-b;if(a!=this._inputSize){this._inputSize=a;this._container.find("li > input").css({width:this._inputSize+"px"})}},_submit:function(){var a=[];this._container.children("li").each(function(f,i){var h=$(i);var g=$.trim(h.children("input").val());if(g!=""){a.push({optionID:h.data("optionID"),optionValue:g})}});if(a.length){var b=this._container.parents("form").find(".formSubmit");for(var e=0,d=a.length;e<d;e++){var c=a[e];$('<input type="hidden" name="pollOptions['+e+']" />').val(c.optionID+"_"+c.optionValue).appendTo(b)}}}});WCF.Poll.Manager=Class.extend({_cache:{},_canViewParticipants:{},_canViewResult:{},_canVote:{},_inputElements:{},_participants:{},_polls:{},_proxy:null,init:function(a){var c=$(a);if(!c.length){console.debug("[WCF.Poll.Manager] Given selector '"+a+"' 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});var b=this;c.each(function(e,g){var f=$(g);var d=f.data("pollID");if(b._polls[d]===undefined){b._cache[d]={result:"",vote:""};b._polls[d]=f;b._canViewParticipants[d]=(f.data("canViewParticipants"))?true:false;b._canViewResult[d]=(f.data("canViewResult"))?true:false;b._canVote[d]=(f.data("canVote"))?true:false;b._bindListeners(d);if(f.data("inVote")){b._prepareVote(d)}b._toggleButtons(d)}})},_bindListeners:function(a){this._polls[a].find(".jsButtonPollShowParticipants").data("pollID",a).click($.proxy(this._showParticipants,this));this._polls[a].find(".jsButtonPollShowResult").data("pollID",a).click($.proxy(this._showResult,this));this._polls[a].find(".jsButtonPollShowVote").data("pollID",a).click($.proxy(this._showVote,this));this._polls[a].find(".jsButtonPollVote").data("pollID",a).click($.proxy(this._vote,this))},_showResult:function(b,c){var a=(b===null)?c:$(b.currentTarget).data("pollID");if(!this._canViewResult[a]){return}if(!this._polls[a].data("inVote")){return}if(!this._cache[a].result){this._proxy.setOption("data",{actionName:"getResult",pollID:a});this._proxy.sendRequest()}else{this._polls[a].find(".pollInnerContainer").html(this._cache[a].result);this._polls[a].data("inVote",false);this._toggleButtons(a)}},_showParticipants:function(b){var a=$(b.currentTarget).data("pollID");if(!this._participants[a]){this._participants[a]=new WCF.User.List("wcf\\data\\poll\\PollAction",this._polls[a].data("question"),{pollID:a})}this._participants[a].open()},_showVote:function(b,c){var a=(b===null)?c:$(b.currentTarget).data("pollID");if(!this._canVote[a]){return}if(this._polls[a].data("inVote")){return}if(!this._cache[a].vote){this._proxy.setOption("data",{actionName:"getVote",pollID:a});this._proxy.sendRequest()}else{this._polls[a].find(".pollInnerContainer").html(this._cache[a].vote);this._polls[a].data("inVote",true);this._prepareVote(a);this._toggleButtons(a)}},_success:function(c,d,b){if(!c||!c.actionName){return}var a=c.pollID;if(c.resultTemplate){this._cache[a].result=c.resultTemplate}if(c.voteTemplate){this._cache[a].vote=c.voteTemplate}switch(c.actionName){case"getResult":this._showResult(null,a);break;case"getVote":this._showVote(null,a);break;case"vote":this._canViewResult[a]=true;this._canVote[a]=(c.canVote)?true:false;this._showResult(null,a);break}},_prepareVote:function(c){this._polls[c].find(".jsButtonPollVote").disable();var d=this._polls[c].find(".pollInnerContainer > .jsPollVote");var a=this;this._inputElements[c]=d.find("input").change(function(){a._handleVoteButton(c)});this._handleVoteButton(c);var b=d.data("maxVotes");if(this._inputElements[c].filter("[type=checkbox]").length){this._inputElements[c].change(function(){a._enforceMaxVotes(c,b)});this._enforceMaxVotes(c,b)}},_enforceMaxVotes:function(c,b){var a=this._inputElements[c];if(a.filter(":checked").length==b){a.filter(":not(:checked)").disable()}else{a.enable()}},_handleVoteButton:function(b){var a=this._inputElements[b];var c=this._polls[b].find(".jsButtonPollVote");if(a.filter(":checked").length){c.enable()}else{c.disable()}},_toggleButtons:function(c){var a=this._polls[c].children(".formSubmit");a.find(".jsButtonPollShowParticipants, .jsButtonPollShowResult, .jsButtonPollShowVote, .jsButtonPollVote").hide();var b=true;if(this._polls[c].data("inVote")){b=false;a.find(".jsButtonPollVote").show();if(this._canViewResult[c]){a.find(".jsButtonPollShowResult").show()}}else{if(this._canVote[c]){b=false;a.find(".jsButtonPollShowVote").show()}if(this._canViewParticipants[c]){b=false;a.find(".jsButtonPollShowParticipants").show()}}if(b){a.hide()}},_vote:function(c){var a=$(c.currentTarget).data("pollID");if(!this._canVote[a]){return}var b=[];this._inputElements[a].each(function(e,d){var f=$(d);if(f.is(":checked")){b.push(f.data("optionID"))}});if(b.length){this._proxy.setOption("data",{actionName:"vote",optionIDs:b,pollID:a});this._proxy.sendRequest()}}});
+
+// WCF.Search.Message.js
+WCF.Search.Message={};WCF.Search.Message.KeywordList=WCF.Search.Base.extend({_className:"wcf\\data\\search\\keyword\\SearchKeywordAction",_divider:null,_forceSubmit:false,init:function(c,e,b){if(!$.isFunction(e)){console.debug("[WCF.Search.Message.KeywordList] The given callback is invalid, aborting.");return}this._callback=e;this._excludedSearchValues=[];if(b){this._excludedSearchValues=b}this._searchInput=$(c).keyup($.proxy(this._keyUp,this)).keydown($.proxy(function(f){if(f.which===13){if(this._itemCount&&this._itemIndex!==-1){f.preventDefault()}}},this));var a=WCF.Dropdown.getDropdownMenu(this._searchInput.parents(".dropdown").wcfIdentify());var d=a.find("li.dropdownDivider").last();this._divider=$('<li class="dropdownDivider" />').hide().insertBefore(d);this._list=$('<li class="dropdownList"><ul /></li>').hide().insertBefore(d).children("ul");a.find("input, label").on("click",function(f){f.stopPropagation()});this._proxy=new WCF.Action.Proxy({showLoadingOverlay:false,success:$.proxy(this._success,this)})},_createListItem:function(a){this._divider.show();this._list.parent().show();this._super(a)},_clearList:function(a){if(a){this._searchInput.val("")}this._divider.hide();this._list.empty().parent().hide();WCF.CloseOverlayHandler.removeCallback("WCF.Search.Base");this._itemCount=0;this._itemIndex=-1}});WCF.Search.Message.SearchArea=Class.extend({_searchArea:null,init:function(d){this._searchArea=d;var a=new WCF.Search.Message.KeywordList(this._searchArea.find("input[type=search]"),$.proxy(this._callback,this));a.setDelay(500);var c=this;var f=this._searchArea.find("input[type=search]");this._searchArea.click(function(g){if(g.target==c._searchArea[0]){f.focus().trigger("click");return false}});if(this._searchArea.hasClass("dropdown")){var e=this._searchArea.wcfIdentify();var b=this._searchArea.find("form");b.submit(function(){var g=WCF.Dropdown.getDropdownMenu(e);g.find("input[type=hidden]").appendTo(b);g.find("input[type=checkbox]:checked").each(function(i,h){var j=$(h);$('<input type="hidden" name="'+j.attr("name")+'" value="'+j.attr("value")+'" />').appendTo(b)})})}},_callback:function(a){this._searchArea.find("input[type=search]").val(a.label);this._searchArea.find("input[type=search]").focus();return false}});
+
+// WCF.Tagging.js
+WCF.Tagging={};WCF.Tagging.TagList=WCF.EditableItemList.extend({_className:"wcf\\data\\tag\\TagAction",_maxLength:0,init:function(c,a,b){this._allowCustomInput=true;this._maxLength=b;this._super(c,a);this._data=[];this._search=new WCF.Tagging.TagSearch(this._searchInput,$.proxy(this.addItem,this));this._itemList.addClass("tagList");$(c).data("__api",this)},_keyDown:function(b){if(this._super(b)){if(b===null){return true}var a=b.which;if(a===8||a===27||a===13||a===46){return true}else{if(a>36&&a<41){return true}}if(this._searchInput.val().length>=this._maxLength){return false}return true}return false},_submit:function(){this._super();for(var b=0,a=this._data.length;b<a;b++){if(this._data[b]){$('<input type="hidden" name="tags[]" />').val(this._data[b]).appendTo(this._form)}}},addItem:function(b){if(!b.objectID&&b.label.length>this._maxLength){b.label=b.label.substr(0,this._maxLength)}var a=this._super(b);$(this._itemList).find(".badge:not(tag)").addClass("tag");return a},_addItem:function(b,a){this._data.push(a)},clearList:function(){this._super();this._data=[]},getTags:function(){return this._data},_removeItem:function(d,a){for(var c=0,b=this._data.length;c<b;c++){if(this._data[c]===a){this._data.splice(c,1);return}}},load:function(a){if(a&&a.length){for(var c=0,b=a.length;c<b;c++){this.addItem({objectID:0,label:WCF.String.unescapeHTML(a[c])})}}}});WCF.Tagging.TagSearch=WCF.Search.Base.extend({_className:"wcf\\data\\tag\\TagAction",init:function(b,d,a,c){this._super(b,d,a,c,false)}});
+
+// WCF.User.js
+WCF.User.Login=Class.extend({_loginSubmitButton:null,_password:null,_passwordContainer:null,_useCookies:null,_useCookiesContainer:null,init:function(b){this._loginSubmitButton=$("#loginSubmitButton");this._password=$("#password"),this._passwordContainer=this._password.parents("dl");this._useCookies=$("#useCookies");this._useCookiesContainer=this._useCookies.parents("dl");var a=$("#loginForm");a.find("input[name=action]").change($.proxy(this._change,this));if(b){WCF.User.QuickLogin.init()}},_change:function(a){if($(a.currentTarget).val()==="register"){this._setState(false,WCF.Language.get("wcf.user.button.register"))}else{this._setState(true,WCF.Language.get("wcf.user.button.login"))}},_setState:function(b,a){if(b){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(a)}});WCF.User.QuickLogin={_dialog:null,_loginMessage:null,init:function(){$(".loginLink").click($.proxy(this._render,this));$("#loginForm input[name=url]").val(function(a,b){return window.location.protocol+"//"+window.location.host+b})},show:function(a){if(a){if(this._loginMessage===null){this._loginMessage=$('<p class="info" />').hide().prependTo($("#loginForm > form"))}this._loginMessage.show().text(a)}else{if(this._loginMessage!==null){this._loginMessage.hide()}}this._render()},_render:function(a){if(a!==undefined){a.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")}}};WCF.User.Profile={};WCF.User.Profile.ActivityPointList={_cache:{},_dialog:null,_didInit:false,_proxy:null,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},_init:function(){$(".activityPointsDisplay").removeClass("activityPointsDisplay").click($.proxy(this._click,this))},_click:function(b){var a=$(b.currentTarget).data("userID");if(this._cache[a]===undefined){this._proxy.setOption("data",{actionName:"getDetailedActivityPointList",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[a]});this._proxy.sendRequest()}else{this._show(a)}},_show:function(a){if(this._dialog===null){this._dialog=$("<div>"+this._cache[a]+"</div>").hide().appendTo(document.body);this._dialog.wcfDialog({title:WCF.Language.get("wcf.user.activityPoint")})}else{this._dialog.html(this._cache[a]);this._dialog.wcfDialog("open")}},_success:function(b,c,a){this._cache[b.returnValues.userID]=b.returnValues.template;this._show(b.returnValues.userID)}};WCF.User.Profile.Follow=Class.extend({_button:null,_following:false,_proxy:null,_userID:0,init:function(a,b){this._following=b;this._userID=a;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._createButton();this._showButton()},_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))},_execute:function(){var a=(this._following)?"unfollow":"follow";this._proxy.setOption("data",{actionName:a,className:"wcf\\data\\user\\follow\\UserFollowAction",parameters:{data:{userID:this._userID}}});this._proxy.sendRequest()},_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")}},_success:function(b,d,a){this._following=b.returnValues.following;this._showButton();var c=new WCF.System.Notification();c.show()}});WCF.User.Profile.IgnoreUser=Class.extend({_button:null,_isIgnoredUser:false,_proxy:null,_userID:0,init:function(b,a){this._userID=b;this._isIgnoredUser=a;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._updateButton();this._button.click($.proxy(this._click,this))},_click:function(){var a=(this._isIgnoredUser)?"unignore":"ignore";this._proxy.setOption("data",{actionName:a,className:"wcf\\data\\user\\ignore\\UserIgnoreAction",parameters:{data:{ignoreUserID:this._userID}}});this._proxy.sendRequest()},_success:function(b,d,a){this._isIgnoredUser=b.returnValues.isIgnoredUser;this._updateButton();var c=new WCF.System.Notification();c.show()},_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")}}});WCF.User.Profile.TabMenu=Class.extend({_hasContent:{},_profileContent:null,_proxy:null,_userID:0,init:function(a){this._profileContent=$("#profileContent");this._userID=a;var c=this._profileContent.data("active");var b=false;this._profileContent.find("div.tabMenuContent").each($.proxy(function(e,d){var f=$(d).wcfIdentify();if(c===f){this._hasContent[f]=true}else{this._hasContent[f]=false;b=true}},this));if(b){this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._profileContent.bind("wcftabsbeforeactivate",$.proxy(this._loadContent,this))}},_loadContent:function(b,c){var d=$(c.newPanel);var a=d.attr("id");if(!this._hasContent[a]){this._proxy.setOption("data",{actionName:"getContent",className:"wcf\\data\\user\\profile\\menu\\item\\UserProfileMenuItemAction",parameters:{data:{containerID:a,menuItem:d.data("menuItem"),userID:this._userID}}});this._proxy.sendRequest()}},_success:function(d,e,c){var b=d.returnValues.containerID;this._hasContent[b]=true;var a=this._profileContent.find("#"+b);$("<div>"+d.returnValues.template+"</div>").hide().appendTo(a);a.children("div").wcfBlindIn()}});WCF.User.Profile.Editor=Class.extend({_actionName:"",_buttons:{},_cachedTemplate:"",_proxy:null,_tab:null,_userID:0,init:function(a,b){this._actionName="";this._cachedTemplate="";this._tab=$("#about");this._userID=a;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._initButtons();if(b){this._beginEdit()}},_initButtons:function(){var a=$("#profileButtonContainer");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(a)}},_beginEdit:function(){this._actionName="beginEdit";this._buttons.beginEdit.hide();$("#profileContent").wcfTabs("select","about");this._proxy.setOption("data",{actionName:"beginEdit",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[this._userID]});this._proxy.sendRequest()},_save:function(){this._actionName="save";var b=/values\[([a-zA-Z0-9._-]+)\]/;var a={};this._tab.find("input, textarea, select").each(function(e,f){var d=$(f);if(d.getTagName()==="input"){var c=d.attr("type");if((c==="radio"||c==="checkbox")&&!d.prop("checked")){return}}var g=d.attr("name");if(b.test(g)){a[RegExp.$1]=d.val()}});this._proxy.setOption("data",{actionName:"save",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[this._userID],parameters:{values:a}});this._proxy.sendRequest()},_restore:function(){this._actionName="restore";this._buttons.beginEdit.show();this._destroyCKEditor();this._tab.html(this._cachedTemplate).children().css({height:"auto"})},_success:function(b,c,a){switch(this._actionName){case"beginEdit":this._prepareEdit(b);break;case"save":if(b.returnValues.success){this._cachedTemplate=b.returnValues.template;this._restore()}else{this._prepareEdit(b,true)}break}},_prepareEdit:function(b,c){this._destroyCKEditor();var a=this;this._tab.html(function(e,d){if(c!==true){a._cachedTemplate=d}return b.returnValues.template});this._tab.find("input[type=text]").attr("autocomplete","off");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(d){if(d.which===13){a._save();d.preventDefault();return false}})},_destroyCKEditor:function(){this._tab.find("textarea + .cke").each(function(c,b){var a=$(b).attr("id").replace(/cke_/,"");if(CKEDITOR.instances[a]){CKEDITOR.instances[a].destroy()}})}});WCF.User.Registration={};WCF.User.Registration.Validation=Class.extend({_actionName:"",_className:"",_confirmElement:null,_element:null,_errorMessages:{},_options:{},_proxy:null,init:function(b,c,a){this._element=b;this._element.blur($.proxy(this._blur,this));this._confirmElement=c||null;if(this._confirmElement!==null){this._confirmElement.blur($.proxy(this._blurConfirm,this))}a=a||{};this._setOptions(a);this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this),showLoadingOverlay:false});this._setErrorMessages()},_setOptions:function(a){},_setErrorMessages:function(){this._errorMessages={ajaxError:"",notEqual:""}},_blur:function(b){var a=this._element.val();if(!a){return this._showError(this._element,WCF.Language.get("wcf.global.form.error.empty"))}if(this._confirmElement!==null){var c=this._confirmElement.val();if(c!=""&&a!=c){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()},_getParameters:function(){return{}},_validateOptions:function(){return true},_blurConfirm:function(b){var a=this._confirmElement.val();if(!a){return this._showError(this._confirmElement,WCF.Language.get("wcf.global.form.error.empty"))}this._blur(b)},_success:function(b,c,a){if(b.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+b.returnValues.error))}},_showError:function(a,b){a.parent().parent().addClass("formError").removeClass("formSuccess");var c=a.parent().find("small.innerError");if(!c.length){c=$("<small />").addClass("innerError").insertAfter(a)}c.text(b)},_showSuccess:function(a){a.parent().parent().addClass("formSuccess").removeClass("formError");a.next("small.innerError").remove()}});WCF.User.Registration.Validation.Username=WCF.User.Registration.Validation.extend({_actionName:"validateUsername",_className:"wcf\\data\\user\\UserRegistrationAction",_setOptions:function(a){this._options=$.extend(true,{minlength:3,maxlength:25},a)},_setErrorMessages:function(){this._errorMessages={ajaxError:"wcf.user.username.error."}},_validateOptions:function(){var a=this._element.val();if(a.length<this._options.minlength||a.length>this._options.maxlength){this._showError(this._element,WCF.Language.get("wcf.user.username.error.notValid"));return false}return true},_getParameters:function(){return{username:this._element.val()}}});WCF.User.Registration.Validation.EmailAddress=WCF.User.Registration.Validation.extend({_actionName:"validateEmailAddress",_className:"wcf\\data\\user\\UserRegistrationAction",_getParameters:function(){return{email:this._element.val()}},_setErrorMessages:function(){this._errorMessages={ajaxError:"wcf.user.email.error.",notEqual:WCF.Language.get("wcf.user.confirmEmail.error.notEqual")}}});WCF.User.Registration.Validation.Password=WCF.User.Registration.Validation.extend({_actionName:"validatePassword",_className:"wcf\\data\\user\\UserRegistrationAction",_getParameters:function(){return{password:this._element.val()}},_setErrorMessages:function(){this._errorMessages={ajaxError:"wcf.user.password.error.",notEqual:WCF.Language.get("wcf.user.confirmPassword.error.notEqual")}}});WCF.User.Registration.LostPassword=Class.extend({_email:null,_username:null,init:function(){this._email=$("#emailInput");this._username=$("#usernameInput");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))}this._checkEmail();this._checkUsername()},_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")}},_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")}}});WCF.Notification={};WCF.Notification.UserPanel=WCF.UserPanel.extend({_proxy:null,_showAllLink:"",init:function(a){this._noItems="wcf.user.notification.noMoreNotifications";this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._showAllLink=a;this._super("userNotifications");if(this._container.data("count")){document.title="("+this._container.data("count")+") "+document.title}},_addDefaultItems:function(a){this._addDivider(a);if(this._container.data("count")){$('<li><a href="'+this._showAllLink+'">'+WCF.Language.get("wcf.user.notification.showAll")+"</a></li>").appendTo(a);this._addDivider(a)}$('<li id="userNotificationsMarkAllAsConfirmed"><a>'+WCF.Language.get("wcf.user.notification.markAllAsConfirmed")+"</a></li>").click($.proxy(this._markAllAsConfirmed,this)).appendTo(a)},_getParameters:function(){return{actionName:"getOutstandingNotifications",className:"wcf\\data\\user\\notification\\UserNotificationAction"}},_after:function(a){WCF.Dropdown.getDropdownMenu(this._container.wcfIdentify()).children("li.jsNotificationItem").click($.proxy(this._markAsConfirmed,this))},_markAsConfirmed:function(a){this._proxy.setOption("data",{actionName:"markAsConfirmed",className:"wcf\\data\\user\\notification\\UserNotificationAction",parameters:{notificationID:$(a.currentTarget).data("notificationID")}});this._proxy.sendRequest()},_markAllAsConfirmed:function(){WCF.System.Confirmation.show(WCF.Language.get("wcf.user.notification.markAllAsConfirmed.confirmMessage"),$.proxy(function(a){if(a==="confirm"){this._proxy.setOption("data",{actionName:"markAllAsConfirmed",className:"wcf\\data\\user\\notification\\UserNotificationAction"});this._proxy.sendRequest()}},this))},_success:function(b,c,a){switch(b.actionName){case"markAllAsConfirmed":$(".jsNotificationItem").remove();document.title=document.title.replace(/^\(([0-9]+)\) /,"");case"getOutstandingNotifications":if(!b.returnValues||!b.returnValues.template){$("#userNotificationsMarkAllAsConfirmed").prev(".dropdownDivider").remove();$("#userNotificationsMarkAllAsConfirmed").remove()}this._super(b,c,a);break;case"markAsConfirmed":WCF.Dropdown.getDropdownMenu(this._container.wcfIdentify()).children("li.jsNotificationItem").each(function(e,f){var d=$(f);if(b.returnValues.notificationID==d.data("notificationID")){window.location=d.data("link");return false}});break}}});WCF.Notification.List=Class.extend({_badge:null,_items:{},_proxy:null,init:function(){var a=$("li.jsNotificationItem");if(!a.length){return}a.each($.proxy(function(c,b){var d=$(b);this._items[d.data("notificationID")]=d;d.find(".jsMarkAsConfirmed").data("notificationID",d.data("notificationID")).click($.proxy(this._click,this));d.find("p").html(function(f,e){return"<a>"+e+"</a>"}).children("a").data("notificationID",d.data("notificationID")).click($.proxy(this._clickLink,this))},this));this._badge=$(".jsNotificationsBadge:eq(0)");this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});$(".contentNavigation .jsMarkAllAsConfirmed").click($.proxy(this._markAllAsConfirmed,this))},_clickLink:function(a){this._items[$(a.currentTarget).data("notificationID")].data("redirect",true);this._click(a)},_click:function(a){this._proxy.setOption("data",{actionName:"markAsConfirmed",className:"wcf\\data\\user\\notification\\UserNotificationAction",parameters:{notificationID:$(a.currentTarget).data("notificationID")}});this._proxy.sendRequest()},_markAllAsConfirmed:function(){WCF.System.Confirmation.show(WCF.Language.get("wcf.user.notification.markAllAsConfirmed.confirmMessage"),$.proxy(function(a){if(a==="confirm"){this._proxy.setOption("data",{actionName:"markAllAsConfirmed",className:"wcf\\data\\user\\notification\\UserNotificationAction"});this._proxy.sendRequest()}},this))},_success:function(c,d,b){switch(c.actionName){case"markAllAsConfirmed":window.location.reload();break;case"markAsConfirmed":var a=this._items[c.returnValues.notificationID];if(a.data("redirect")){window.location=a.data("link");return}this._items[c.returnValues.notificationID].remove();delete this._items[c.returnValues.notificationID];this._badge.html(c.returnValues.totalCount);document.title=document.title.replace(/^\(([0-9]+)\) /,"");if(c.returnValues.totalCount>0){document.title="("+c.returnValues.totalCount+") "+document.title}break}}});WCF.User.SignaturePreview=WCF.Message.Preview.extend({_handleResponse:function(b){var a=$("#previewContainer");if(!a.length){a=$('<fieldset id="previewContainer"><legend>'+WCF.Language.get("wcf.global.preview")+"</legend><div></div></fieldset>").insertBefore($("#signatureContainer")).wcfFadeIn()}a.children("div").first().html(b.returnValues.message)}});WCF.User.RecentActivityLoader=Class.extend({_container:null,_filteredByFollowedUsers:false,_loadButton:null,_proxy:null,_userID:0,init:function(b,a){this._container=$("#recentActivities");this._filteredByFollowedUsers=(a===true);this._userID=b;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))},_click:function(){this._loadButton.enable();var a={lastEventTime:this._container.data("lastEventTime")};if(this._userID){a.userID=this._userID}else{if(this._filteredByFollowedUsers){a.filteredByFollowedUsers=1}}this._proxy.setOption("data",{actionName:"load",className:"wcf\\data\\user\\activity\\event\\UserActivityEventAction",parameters:a});this._proxy.sendRequest()},_success:function(b,c,a){if(b.returnValues.template){$(b.returnValues.template).insertBefore(this._loadButton.parent());this._container.data("lastEventTime",b.returnValues.lastEventTime);this._loadButton.enable()}else{$("<small>"+WCF.Language.get("wcf.user.recentActivity.noMoreEntries")+"</small>").appendTo(this._loadButton.parent());this._loadButton.remove()}}});WCF.User.ProfilePreview=WCF.Popover.extend({_proxy:null,_userProfiles:{},init:function(){this._super(".userLink");this._proxy=new WCF.Action.Proxy({showLoadingOverlay:false})},_loadContent:function(){var a=$("#"+this._activeElementID);var c=a.data("userID");if(this._userProfiles[c]){this._insertContent(this._activeElementID,this._userProfiles[c],true)}else{this._proxy.setOption("data",{actionName:"getUserProfile",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[c]});var d=this._activeElementID;var b=this;this._proxy.setOption("success",function(f,g,e){b._userProfiles[c]=f.returnValues.template;b._insertContent(d,f.returnValues.template,true)});this._proxy.setOption("failure",function(f,e,h,g){b._userProfiles[c]=f.message;b._insertContent(d,f.message,true);return false});this._proxy.sendRequest()}}});WCF.User.Action={};WCF.User.Action.Follow=Class.extend({_containerList:null,_followButtonSelector:".jsFollowButton",_userID:0,init:function(b,a){if(!b.length){return}this._containerList=b;if(a){this._followButtonSelector=a}this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._containerList.each($.proxy(function(d,c){$(c).find(this._followButtonSelector).click($.proxy(this._click,this))},this))},_click:function(b){var a=$(b.target);if(!a.is("a")){a=a.closest("a")}this._userID=a.data("objectID");this._proxy.setOption("data",{actionName:a.data("following")?"unfollow":"follow",className:"wcf\\data\\user\\follow\\UserFollowAction",parameters:{data:{userID:this._userID}}});this._proxy.sendRequest()},_success:function(b,d,a){this._containerList.each($.proxy(function(f,e){var g=$(e).find(this._followButtonSelector).get(0);if(g&&$(g).data("objectID")==this._userID){g=$(g);if(b.returnValues.following){g.data("tooltip",WCF.Language.get("wcf.user.button.unfollow")).children(".icon").removeClass("icon-plus").addClass("icon-minus")}else{g.data("tooltip",WCF.Language.get("wcf.user.button.follow")).children(".icon").removeClass("icon-minus").addClass("icon-plus")}g.data("following",b.returnValues.following);return false}},this));var c=new WCF.System.Notification();c.show()}});WCF.User.Action.Ignore=Class.extend({_containerList:null,_ignoreButtonSelector:".jsIgnoreButton",_userID:0,init:function(a,b){if(!a.length){return}this._containerList=a;if(b){this._ignoreButtonSelector=b}this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._containerList.each($.proxy(function(d,c){$(c).find(this._ignoreButtonSelector).click($.proxy(this._click,this))},this))},_click:function(b){var a=$(b.target);if(!a.is("a")){a=a.closest("a")}this._userID=a.data("objectID");this._proxy.setOption("data",{actionName:a.data("ignored")?"unignore":"ignore",className:"wcf\\data\\user\\ignore\\UserIgnoreAction",parameters:{data:{ignoreUserID:this._userID}}});this._proxy.sendRequest()},_success:function(b,d,a){this._containerList.each($.proxy(function(f,e){var g=$(e).find(this._ignoreButtonSelector).get(0);if(g&&$(g).data("objectID")==this._userID){g=$(g);if(b.returnValues.isIgnoredUser){g.data("tooltip",WCF.Language.get("wcf.user.button.unignore")).children(".icon").removeClass("icon-ban-circle").addClass("icon-circle-blank")}else{g.data("tooltip",WCF.Language.get("wcf.user.button.ignore")).children(".icon").removeClass("icon-circle-blank").addClass("icon-ban-circle")}g.data("ignored",b.returnValues.isIgnoredUser);return false}},this));var c=new WCF.System.Notification();c.show()}});WCF.User.Avatar={};WCF.User.Avatar.Crop=Class.extend({_cropX:0,_cropY:0,_dialog:null,_proxy:null,MAX_THUMBNAIL_SIZE:128,init:function(a){this._avatarID=a;if(this._dialog){this.destroy()}this._dialog=null;if(!this._proxy){this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)})}$(".userAvatarCrop").click($.proxy(this._showCropDialog,this))},destroy:function(){this._dialog.remove()},_crop:function(a){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()},_getCropDialog:function(b){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(b.returnValues.template);this._dialog.find('button[data-type="save"]').click($.proxy(this._crop,this));this._cropX=b.returnValues.cropX;this._cropY=b.returnValues.cropY;var a=$("#userAvatarCropSelection > img");$("#userAvatarCropSelection").css({height:a.height()+"px",width:a.width()+"px"});$("#userAvatarCropOverlaySelection").css({"background-image":"url("+a.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")},_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")}},_success:function(b,d,a){switch(b.actionName){case"getCropDialog":this._getCropDialog(b);break;case"cropAvatar":$("#avatarUpload > dt > img").replaceWith($('<img src="'+b.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 c=new WCF.System.Notification();c.show();break}},_updateSelection:function(a,b){this._cropX=b.position.left;this._cropY=b.position.top;$("#userAvatarCropOverlaySelection").css({"background-position":-b.position.left+"px "+-b.position.top+"px"})}});WCF.User.Avatar.Upload=WCF.Upload.extend({_avatarCrop:null,_userID:0,init:function(b,a){this._super($("#avatarUpload > dd > div"),undefined,"wcf\\data\\user\\avatar\\UserAvatarAction");this._userID=b||0;this._avatarCrop=a;$("#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()}},_initFile:function(a){return $("#avatarUpload > dt > img")},_success:function(c,a){if(a.returnValues.url){this._updateImage(a.returnValues.url,a.returnValues.canCrop);if(a.returnValues.canCrop){if(!this._avatarCrop){this._avatarCrop=new WCF.User.Avatar.Crop(a.returnValues.avatarID)}else{this._avatarCrop.init(a.returnValues.avatarID)}}else{if(this._avatarCrop){this._avatarCrop.destroy();this._avatarCrop=null}}$("#avatarUpload > dd > .innerError").remove();var b=new WCF.System.Notification(WCF.Language.get("wcf.user.avatar.upload.success"));b.show()}else{if(a.returnValues.errorType){this._getInnerErrorElement().text(WCF.Language.get("wcf.user.avatar.upload.error."+a.returnValues.errorType))}}},_updateImage:function(b,a){$("#avatarUpload > dt > img").remove();var c=$('<img src="'+b+'" alt="" />').css({height:"auto","max-height":"96px","max-width":"96px",width:"auto"});if(a){c.addClass("userAvatarCrop").addClass("jsTooltip");c.attr("title",WCF.Language.get("wcf.user.avatar.type.custom.crop"))}$("#avatarUpload > dt").prepend(c);WCF.DOMNodeInsertedHandler.execute()},_getInnerErrorElement:function(){var a=$("#avatarUpload > dd > .innerError");if(!a.length){a=$('<small class="innerError"></span>');$("#avatarUpload > dd").append(a)}return a},_getParameters:function(){return{userID:this._userID}},});WCF.User.List=Class.extend({_additionalParameters:{},_cache:{},_className:"",_dialog:null,_dialogTitle:"",_pageCount:0,_pageNo:1,_proxy:null,init:function(c,a,b){this._additionalParameters=b||{};this._cache={};this._className=c;this._dialog=null;this._dialogTitle=a;this._pageCount=0;this._pageNo=1;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)})},open:function(){this._pageNo=1;this._showPage()},_showPage:function(b,c){if(c&&c.activePage){this._pageNo=c.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 a=false;if(this._dialog===null){this._dialog=$('<div id="userList'+this._className.hashCode()+'" />').hide().appendTo(document.body);a=true}this._dialog.empty();this._dialog.html(this._cache[this._pageNo]);if(this._pageCount>1){this._dialog.find(".jsPagination").wcfPages({activePage:this._pageNo,maxPage:this._pageCount}).bind("wcfpagesswitched",$.proxy(this._showPage,this))}if(a){this._dialog.wcfDialog({title:this._dialogTitle})}else{this._dialog.wcfDialog("open").wcfDialog("render")}}else{this._additionalParameters.pageNo=this._pageNo;this._proxy.setOption("data",{actionName:"getGroupedUserList",className:this._className,interfaceName:"wcf\\data\\IGroupedUserListAction",parameters:this._additionalParameters});this._proxy.sendRequest()}},_success:function(b,c,a){if(b.returnValues.pageCount){this._pageCount=b.returnValues.pageCount}this._cache[this._pageNo]=b.returnValues.template;this._showPage()}});WCF.User.ObjectWatch={};WCF.User.ObjectWatch.Subscribe=Class.extend({_buttonSelector:".jsSubscribeButton",_buttons:{},_dialog:null,_notification:null,init:function(){this._buttons={};this._notification=null;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});$(this._buttonSelector).each($.proxy(function(a,b){var d=$(b);var c=d.data("objectID");this._buttons[c]=d.click($.proxy(this._click,this))},this))},_click:function(a){var b=$(a.currentTarget);this._proxy.setOption("data",{actionName:"manageSubscription",className:"wcf\\data\\user\\object\\watch\\UserObjectWatchAction",parameters:{objectID:b.data("objectID"),objectType:b.data("objectType")}});this._proxy.sendRequest()},_success:function(c,e,b){if(c.actionName==="manageSubscription"){if(this._dialog===null){this._dialog=$("<div>"+c.returnValues.template+"</div>").hide().appendTo(document.body);this._dialog.wcfDialog({title:WCF.Language.get("wcf.user.objectWatch.manageSubscription")})}else{this._dialog.html(c.returnValues.template);this._dialog.wcfDialog("open")}this._dialog.find(".formSubmit > .jsButtonSave").data("objectID",c.returnValues.objectID).click($.proxy(this._save,this));var a=this._dialog.find("input[name=enableNotification]").disable();this._dialog.find("input[name=subscribe]").change(function(f){var g=$(f.currentTarget);if(g.val()==1){a.enable()}else{a.disable()}});var d=this._dialog.find("input[name=subscribe]:checked");if(d.length&&d.val()==1){a.enable()}}else{if(c.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()}}},_save:function(b){var d=this._buttons[$(b.currentTarget).data("objectID")];var c=this._dialog.find("input[name=subscribe]:checked").val();var a=(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:a,objectID:d.data("objectID"),objectType:d.data("objectType"),subscribe:c}});this._proxy.sendRequest()}});WCF.User.InlineEditor=WCF.InlineEditor.extend({_permissions:{},_execute:function(b,d){if(!this._validate(b,d)){return false}var c={};var a=$("#"+b);switch(d){case"unban":case"enableAvatar":case"enableSignature":switch(d){case"unban":c.banned=0;break;case"enableAvatar":c.disableAvatar=0;break;case"enableSignature":c.disableSignature=0;break}this._proxy.setOption("data",{actionName:d,className:"wcf\\data\\user\\UserAction",objectIDs:[a.data("objectID")]});this._proxy.sendRequest();break;case"ban":case"disableAvatar":case"disableSignature":if(d=="ban"){c.banned=1}else{c[d]=1}this._showReasonDialog(a.data("objectID"),d);break;case"advanced":window.location=this._getTriggerElement(a).attr("href");break}if($.getLength(c)){this._updateData.push({data:c,elementID:b,})}},_executeReasonAction:function(a,b,d){var c={};c[b+WCF.String.ucfirst("reason")]=d;this._proxy.setOption("data",{actionName:b,className:"wcf\\data\\user\\UserAction",objectIDs:[a],parameters:c});this._proxy.sendRequest()},_getPermission:function(a){if(this._permissions[a]){return this._permissions[a]}return 0},_getTriggerElement:function(a){return a.find(".jsUserInlineEditor")},_setOptions:function(){this._options=[{label:WCF.Language.get("wcf.user.ban"),optionName:"ban"},{label:WCF.Language.get("wcf.user.unban"),optionName:"unban"},{label:WCF.Language.get("wcf.user.disableAvatar"),optionName:"disableAvatar"},{label:WCF.Language.get("wcf.user.enableAvatar"),optionName:"enableAvatar"},{label:WCF.Language.get("wcf.user.disableSignature"),optionName:"disableSignature"},{label:WCF.Language.get("wcf.user.enableSignature"),optionName:"enableSignature"},{optionName:"divider"},{label:WCF.Language.get("wcf.user.edit"),optionName:"advanced"}]},_show:function(c){var a=$(c.currentTarget);var d=a.data("elementID");if(!this._dropdowns[d]){var b=a.next(".dropdownMenu");if(b){this._dropdowns[d]=b;WCF.Dropdown.initDropdown(this._getTriggerElement(this._elements[d]),true)}}return this._super(c)},_showReasonDialog:function(b,c){var d="wcf.user."+c+".reason.description";var a=WCF.Language.get(d);WCF.System.Confirmation.show(WCF.Language.get("wcf.user."+c+".confirmMessage"),$.proxy(function(e){if(e==="confirm"){this._executeReasonAction(b,c,$("#wcfSystemConfirmationContent").find("textarea").val())}},this),{},$("<fieldset><dl><dt>"+WCF.Language.get("wcf.global.reason")+'</dt><dd><textarea cols="40" rows="4" />'+(a!=d?"<small>"+a+"</small>":"")+"</dd></dl></fieldset>"))},_updateState:function(d){this._notification.show();for(var f=0,e=this._updateData.length;f<e;f++){var b=this._updateData[f];var a=$("#"+b.elementID);for(var c in b.data){a.data(c,b.data[c])}}},_validate:function(a,b){var c=$("#"+a);switch(b){case"ban":case"unban":if(!this._getPermission("canBanUser")){return false}if(b=="ban"){return !c.data("banned")}else{return c.data("banned")}break;case"disableAvatar":case"enableAvatar":if(!this._getPermission("canDisableAvatar")){return false}if(b=="disableAvatar"){return !c.data("disableAvatar")}else{return c.data("disableAvatar")}break;case"disableSignature":case"enableSignature":if(!this._getPermission("canDisableSignature")){return false}if(b=="disableSignature"){return !c.data("disableSignature")}else{return c.data("disableSignature")}break;case"advanced":return this._getPermission("canEditUser");break}return false},setPermission:function(a,b){this._permissions[a]=b},setPermissions:function(a){for(var b in a){this.setPermission(b,a[b])}}});
+