Merge branch 'jsPages' of https://github.com/wbb3addons/WCF into wbb3addons-jsPages
authorAlexander Ebert <ebert@woltlab.com>
Mon, 25 Jul 2011 16:25:33 +0000 (18:25 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Mon, 25 Jul 2011 16:25:33 +0000 (18:25 +0200)
Conflicts:
wcfsetup/install/files/js/WCF.js

1  2 
wcfsetup/install/files/acp/templates/header.tpl
wcfsetup/install/files/js/WCF.js

index c6a315a31c8c8b360356d0d905c12a2584654d10,be9bcf5e851b057e9d6a83130d03b53ae5096e4a..ff02fb7400283df10e94535bb5c4cdb13402961f
 -/**
 - * Class and function collection for WCF
 - * 
 - * @author    Alexander Ebert
 - * @copyright 2001-2011 WoltLab GmbH
 - * @license   GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 - */
 -
 -/**
 - * Initialize WCF namespace
 - */
 -var WCF = {};
 -
 -/**
 - * Extends jQuery with additional methods.
 - */
 -$.extend(true, {
 -      /**
 -       * 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;
 -      }
 -});
 -
 -/**
 - * Extends jQuery's chainable methods.
 - */
 -$.fn.extend({
 -      /**
 -       * Returns tag name of current jQuery element.
 -       * 
 -       * @returns     string
 -       */
 -      getTagName: function() {
 -              return 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 = {
 -                              display: this.css('display'),
 -                              visibility: this.css('visibility')
 -                      };
 -                      
 -                      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.innerWidth()
 -                              };
 -                      break;
 -                      
 -                      default:
 -                              dimensions = {
 -                                      height: this.height(),
 -                                      width: this.width()
 -                              };
 -                      break;
 -              }
 -              
 -              // restore previous settings
 -              if (wasHidden) {
 -                      this.css(css);
 -              }
 -              
 -              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 = {
 -                              display: this.css('display'),
 -                              visibility: this.css('visibility')
 -                      };
 -                      
 -                      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) {
 -                      this.css(css);
 -              }
 -              
 -              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');
 -      },
 -      
 -      /**
 -       * Applies a grow-effect by resizing element while moving the
 -       * element appropriately
 -       * 
 -       * @param       object          data
 -       * @param       object          options
 -       * @return      jQuery
 -       */
 -      wcfGrow: function(data, options) {
 -              // create temporarily element to determine dimensions
 -              var $tempElementID = WCF.getRandomID();
 -              $('body').append('<div id="' + $tempElementID + '" class="wcfDimensions">' + data.content + '</div>');
 -              var $tempElement = $('#' + $tempElementID);
 -              
 -              // get content dimensions
 -              var $dimensions = $tempElement.getDimensions();
 -              
 -              // remove temporarily element
 -              $tempElement.empty().remove();
 -              
 -              // move parent element, used if applying effect on dialogs
 -              if (!data.parent) {
 -                      data.parent = this;
 -              }
 -              
 -              // calculate values for grow-effect
 -              var $borderHeight = parseInt(data.parent.css('borderTopWidth')) + parseInt(data.parent.css('borderBottomWidth'));
 -              var $borderWidth = parseInt(data.parent.css('borderLeftWidth')) + parseInt(data.parent.css('borderRightWidth'));
 -              
 -              var $windowDimensions = $(window).getDimensions();
 -              var $leftOffset = Math.round(($windowDimensions.width - ($dimensions.width + $borderWidth)) / 2);
 -              var $topOffset = Math.round(($windowDimensions.height - ($dimensions.height + $borderHeight)) / 2);
 -              
 -              data.parent.makePositioned('fixed', false);
 -              data.parent.animate({
 -                      left: $leftOffset + 'px',
 -                      top: $topOffset + 'px'
 -              }, options);
 -              
 -              return this.animate({
 -                      height: $dimensions.height,
 -                      width: $dimensions.width
 -              }, options);
 -      },
 -      
 -      /**
 -       * Shows an element by sliding and fading it into viewport.
 -       * 
 -       * @param       string          direction
 -       * @param       object          callback
 -       * @returns     jQuery
 -       */
 -      wcfDropIn: function(direction, callback) {
 -              if (!direction) direction = 'up';
 -              
 -              return this.show(WCF.getEffect(this.getTagName(), 'drop'), { direction: direction }, 600, callback);
 -      },
 -      
 -      /**
 -       * Hides an element by sliding and fading it out the viewport.
 -       * 
 -       * @param       string          direction
 -       * @param       object          callback
 -       * @returns     jQuery
 -       */
 -      wcfDropOut: function(direction, callback) {
 -              if (!direction) direction = 'down';
 -              
 -              return this.hide(WCF.getEffect(this.getTagName(), 'drop'), { direction: direction }, 600, callback);
 -      },
 -      
 -      /**
 -       * Shows an element by blinding it up.
 -       * 
 -       * @param       string          direction
 -       * @param       object          callback
 -       * @returns     jQuery
 -       */
 -      wcfBlindIn: function(direction, callback) {
 -              if (!direction) direction = 'vertical';
 -              
 -              return this.show(WCF.getEffect(this.getTagName(), 'blind'), { direction: direction }, 200, callback);
 -      },
 -      
 -      /**
 -       * Hides an element by blinding it down.
 -       * 
 -       * @param       string          direction
 -       * @param       object          callback
 -       * @returns     jQuery
 -       */
 -      wcfBlindOut: function(direction, callback) {
 -              if (!direction) direction = 'vertical';
 -              
 -              return this.hide(WCF.getEffect(this.getTagName(), 'blind'), { direction: direction }, 200, callback);
 -      },
 -      
 -      /**
 -       * Highlights an element.
 -       * 
 -       * @param       object          options
 -       * @param       object          callback
 -       * @returns     jQuery
 -       */
 -      wcfHighlight: function(options, callback) {
 -              return this.effect('highlight', options, 600, callback);
 -      }
 -});
 -
 -/**
 - * WoltLab Community Framework core methods
 - */
 -$.extend(WCF, {
 -      /**
 -       * Counter for dynamic element id's
 -       *
 -       * @var integer
 -       */
 -      _idCounter: 0,
 -      
 -      /**
 -       * Shows a modal dialog with a built-in AJAX-loader.
 -       * 
 -       * @param       string          dialogID
 -       * @param       boolean         resetDialog
 -       */
 -      showAJAXDialog: function(dialogID, resetDialog) {
 -              if (!dialogID) {
 -                      dialogID = this.getRandomID();
 -              }
 -              
 -              if (!$.wcfIsset(dialogID)) {
 -                      $('body').append($('<div id="' + dialogID + '"></div>'));
 -              }
 -              
 -              var dialog = $('#' + $.wcfEscapeID(dialogID));
 -              
 -              if (resetDialog) {
 -                      dialog.empty();
 -              }
 -              
 -              dialog.addClass('overlayLoading');
 -              
 -              var dialogOptions = arguments[2] || {};
 -              dialog.wcfAJAXDialog(dialogOptions);
 -      },
 -      
 -      /**
 -       * Shows a modal dialog.
 -       * @param       string          dialogID
 -       */
 -      showDialog: function(dialogID) {
 -              // we cannot work with a non-existant dialog, if you wish to
 -              // load content via AJAX, see showAJAXDialog() instead
 -              if (!$.wcfIsset(dialogID)) return;
 -              
 -              var $dialog = $('#' + $.wcfEscapeID(dialogID));
 -              
 -              var dialogOptions = arguments[2] || {};
 -              $dialog.wcfDialog(dialogOptions);
 -      },
 -      
 -      /**
 -       * Returns a dynamically created id.
 -       * 
 -       * @see         https://github.com/sstephenson/prototype/blob/master/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       object          object
 -       * @param       string          effect
 -       * @return      string
 -       */
 -      getEffect: function(tagName, effect) {
 -              // most effects are not properly supported on table rows, use highlight instead
 -              if (tagName == 'tr') {
 -                      return 'highlight';
 -              }
 -              
 -              return effect;
 -      }
 -});
 -
 -/**
 - * 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 = function(callback, delay) { this.init(callback, delay); };
 -WCF.PeriodicalExecuter.prototype = {
 -      /**
 -       * Initializes a periodical executer.
 -       * 
 -       * @param       function                callback
 -       * @param       integer                 delay
 -       */
 -      init: function(callback, delay) {
 -              this.callback = callback;
 -              this.delay = delay;
 -              this.loop = true;
 -              
 -              this.intervalID = setInterval($.proxy(this._execute, this), this.delay);
 -      },
 -      
 -      /**
 -       * Executes callback.
 -       */
 -      _execute: function() {
 -              this.callback(this);
 -              
 -              if (!this.loop) {
 -                      clearInterval(this.intervalID);
 -              }
 -      },
 -      
 -      /**
 -       * Terminates loop.
 -       */
 -      stop: function() {
 -              this.loop = false;
 -      }
 -};
 -
 -/**
 - * Namespace for AJAXProxies
 - */
 -WCF.Action = {};
 -
 -/**
 - * Basic implementation for AJAX-based proxyies
 - * 
 - * @param     object          options
 - */
 -WCF.Action.Proxy = function(options) { this.init(options); };
 -WCF.Action.Proxy.prototype = {
 -      /**
 -       * Initializes AJAXProxy.
 -       * 
 -       * @param       object          options
 -       */
 -      init: function(options) {
 -              // initialize default values
 -              this.options = $.extend(true, {
 -                      autoSend: false,
 -                      data: { },
 -                      after: null,
 -                      init: null,
 -                      failure: null,
 -                      success: null,
 -                      type: 'POST',
 -                      url: 'index.php?action=AJAXProxy&t=' + SECURITY_TOKEN + SID_ARG_2ND
 -              }, options);
 -              
 -              this.confirmationDialog = null;
 -              this.loading = null;
 -              
 -              // send request immediately after initialization
 -              if (this.options.autoSend) {
 -                      this.sendRequest();
 -              }
 -      },
 -      
 -      /**
 -       * Sends an AJAX request.
 -       */
 -      sendRequest: function() {
 -              this._init();
 -              
 -              $.ajax({
 -                      data: this.options.data,
 -                      dataType: 'json',
 -                      type: this.options.type,
 -                      url: this.options.url,
 -                      success: $.proxy(this._success, this),
 -                      error: $.proxy(this._failure, this)
 -              });
 -      },
 -      
 -      /**
 -       * Fires before request is send, displays global loading status.
 -       */
 -      _init: function() {
 -              if ($.isFunction(this.options.init)) {
 -                      this.options.init();
 -              }
 -              
 -              $('<div id="actionProxyLoading" style="display: none;">Loading â€¦</div>').appendTo($('body'));
 -              this.loading = $('#actionProxyLoading');
 -              this.loading.wcfDropIn();
 -      },
 -      
 -      /**
 -       * Handles AJAX errors.
 -       * 
 -       * @param       object          jqXHR
 -       * @param       string          textStatus
 -       * @param       string          errorThrown
 -       */
 -      _failure: function(jqXHR, textStatus, errorThrown) {
 -              try {
 -                      var data = $.parseJSON(jqXHR.responseText);
 -                      
 -                      // call child method if applicable
 -                      if ($.isFunction(this.options.failure)) {
 -                              this.options.failure(jqXHR, textStatus, errorThrown, data);
 -                      }
 -                      
 -                      var $randomID = WCF.getRandomID();
 -                      $('<div id="' + $randomID + '" title="HTTP/1.0 ' + jqXHR.status + ' ' + errorThrown + '"><p>Der Server antwortete: ' + data.message + '.</p></div>').wcfDialog();
 -              }
 -              // failed to parse JSON
 -              catch (e) {
 -                      var $randomID = WCF.getRandomID();
 -                      $('<div id="' + $randomID + '" title="HTTP/1.0 ' + jqXHR.status + ' ' + errorThrown + '"><p>Der Server antwortete: ' + jqXHR.responseText + '.</p></div>').wcfDialog();
 -              }
 -              
 -              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)) {
 -                      this.options.success(data, textStatus, jqXHR);
 -              }
 -              
 -              this._after();
 -      },
 -      
 -      /**
 -       * Fires after an AJAX request, hides global loading status.
 -       */
 -      _after: function() {
 -              if ($.isFunction(this.options.after)) {
 -                      this.options.after();
 -              }
 -              
 -              this.loading.wcfDropOut('up');
 -      },
 -      
 -      /**
 -       * 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 = function(options, callbacks) { this.init(options, callbacks); };
 -WCF.Action.SimpleProxy.prototype = {
 -      /**
 -       * 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     jQuery          containerList
 - */
 -WCF.Action.Delete = function(className, containerList) { this.init(className, containerList); };
 -WCF.Action.Delete.prototype = {
 -      /**
 -       * Initializes 'delete'-Proxy.
 -       * 
 -       * @param       string          className
 -       * @param       jQuery          containerList
 -       */
 -      init: function(className, containerList) {
 -              if (!containerList.length) return;
 -              this.containerList = containerList;
 -              this.className = className;
 -              
 -              // initialize proxy
 -              var options = {
 -                      success: $.proxy(this._success, this)
 -              };
 -              this.proxy = new WCF.Action.Proxy(options);
 -              
 -              // bind event listener
 -              this.containerList.each($.proxy(function(index, container) {
 -                      $(container).find('.deleteButton').bind('click', $.proxy(this._click, this));
 -              }, this));
 -      },
 -      
 -      /**
 -       * Sends AJAX request.
 -       * 
 -       * @param       object          event
 -       */
 -      _click: function(event) {
 -              var $target = $(event.target);
 -              
 -              if ($target.data('confirmMessage')) {
 -                      if (confirm($target.data('confirmMessage'))) {
 -                              this._sendRequest($target);
 -                      }
 -              }
 -              else {
 -                      this._sendRequest($target);
 -              }
 -              
 -      },
 -      
 -      _sendRequest: function(object) {
 -              this.proxy.setOption('data', {
 -                      actionName: 'delete',
 -                      className: this.className,
 -                      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) {
 -              // remove items
 -              this.containerList.each(function(index, container) {
 -                      var $objectID = $(container).find('.deleteButton').data('objectID');
 -                      if (WCF.inArray($objectID, data.objectIDs)) {
 -                              $(container).wcfBlindOut('up', function() {
 -                                      $(container).empty().remove();
 -                              }, container);
 -                      }
 -              });
 -      }
 -};
 -
 -/**
 - * Basic implementation for AJAXProxy-based toggle actions.
 - * 
 - * @param     string          className
 - * @param     jQuery          containerList
 - */
 -WCF.Action.Toggle = function(className, containerList) { this.init(className, containerList); };
 -WCF.Action.Toggle.prototype = {
 -      /**
 -       * Initializes 'toggle'-Proxy
 -       * 
 -       * @param       string          className
 -       * @param       jQuery          containerList
 -       */
 -      init: function(className, containerList) {
 -              if (!containerList.length) return;
 -              this.containerList = containerList;
 -              this.className = className;
 -              
 -              // initialize proxy
 -              var options = {
 -                      success: $.proxy(this._success, this)
 -              };
 -              this.proxy = new WCF.Action.Proxy(options);
 -              
 -              // bind event listener
 -              this.containerList.each($.proxy(function(index, container) {
 -                      $(container).find('.toggleButton').bind('click', $.proxy(this._click, this));
 -              }, this));
 -      },
 -      
 -      /**
 -       * Sends AJAX request.
 -       * 
 -       * @param       object          event
 -       */
 -      _click: function(event) {
 -              this.proxy.setOption('data', {
 -                      actionName: 'toggle',
 -                      className: this.className,
 -                      objectIDs: [ $(event.target).data('objectID') ]
 -              });
 -              
 -              this.proxy.sendRequest();
 -      },
 -      
 -      /**
 -       * Toggles status icons.
 -       * 
 -       * @param       object          data
 -       * @param       string          textStatus
 -       * @param       object          jqXHR
 -       */
 -      _success: function(data, textStatus, jqXHR) {
 -              // remove items
 -              this.containerList.each(function(index, container) {
 -                      var $toggleButton = $(container).find('.toggleButton');
 -                      if (WCF.inArray($toggleButton.data('objectID'), data.objectIDs)) {
 -                              $(container).wcfHighlight();
 -                              
 -                              // toggle icon source
 -                              $toggleButton.attr('src', function() {
 -                                      if (this.src.match(/enabled(S|M|L)\.png$/)) {
 -                                              return this.src.replace(/enabled(S|M|L)\.png$/, 'disabled$1\.png');
 -                                      }
 -                                      else {
 -                                              return this.src.replace(/disabled(S|M|L)\.png$/, 'enabled$1\.png');
 -                                      }
 -                              });
 -                              // toogle icon title
 -                              $toggleButton.attr('title', function() {
 -                                      if (this.src.match(/enabled(S|M|L)\.png$/)) {
 -                                              return $(this).data('disableMessage');
 -                                      }
 -                                      else {
 -                                              return $(this).data('enableMessage');
 -                                      }
 -                              });
 -                      }
 -              });
 -      }
 -};
 -
 -/**
 - * Namespace for date-related functions.
 - */
 -WCF.Date = {};
 -
 -/**
 - * 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).
 -       * Parameter timestamp 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() * -1 * 60000;
 -              
 -              return new Date((timestamp - $localOffset - offset));
 -      }
 -};
 -
 -/**
 - * Handles relative time designations.
 - */
 -WCF.Date.Time = function() { this.init(); };
 -WCF.Date.Time.prototype = {
 -      /**
 -       * Initializes relative datetimes.
 -       */
 -      init: function() {
 -              // initialize variables
 -              this.elements = $('time.datetime');
 -              this.timestamp = 0;
 -              
 -              // calculate relative datetime on init
 -              this._refresh();
 -              
 -              // re-calculate relative datetime every minute
 -              new WCF.PeriodicalExecuter($.proxy(this._refresh, this), 60000);
 -      },
 -      
 -      /**
 -       * Refreshes relative datetime for each element.
 -       */
 -      _refresh: function() {
 -              // TESTING ONLY!
 -              var $date = new Date();
 -              this.timestamp = ($date.getTime() - $date.getMilliseconds()) / 1000;
 -              // TESTING ONLY!
 -              
 -              this.elements.each($.proxy(this._refreshElement, this));
 -      },
 -      
 -      /**
 -       * Refreshes relative datetime for current element.
 -       * 
 -       * @param       integer         index
 -       * @param       object          element
 -       */
 -      _refreshElement: function(index, element) {
 -              if (!$(element).attr('title')) {
 -                      $(element).attr('title', $(element).text());
 -              }
 -              
 -              var $timestamp = $(element).data('timestamp');
 -              var $date = $(element).data('date');
 -              var $time = $(element).data('time');
 -              var $offset = $(element).data('offset');
 -              
 -              // timestamp is less than 60 minutes ago (display 1 hour ago rather than 60 minutes ago)
 -              if (this.timestamp < ($timestamp + 3540)) {
 -                      var $minutes = Math.round((this.timestamp - $timestamp) / 60);
 -                      $(element).text(eval(WCF.Language.get('wcf.global.date.relative.minutes')));
 -              }
 -              // timestamp is less than 24 hours ago
 -              else if (this.timestamp < ($timestamp + 86400)) {
 -                      var $hours = Math.round((this.timestamp - $timestamp) / 3600);
 -                      $(element).text(eval(WCF.Language.get('wcf.global.date.relative.hours')));
 -              }
 -              // timestamp is less than a week ago
 -              else if (this.timestamp < ($timestamp + 604800)) {
 -                      var $days = Math.round((this.timestamp - $timestamp) / 86400);
 -                      var $string = eval(WCF.Language.get('wcf.global.date.relative.pastDays'));
 -              
 -                      // get day of week
 -                      var $dateObj = WCF.Date.Util.getTimezoneDate(($timestamp * 1000), $offset);
 -                      var $dow = $dateObj.getDay();
 -                      
 -                      $(element).text($string.replace(/\%day\%/, WCF.Language.get('__days')[$dow]).replace(/\%time\%/, $time));
 -              }
 -              // timestamp is between ~700 million years BC and last week
 -              else {
 -                      var $string = WCF.Language.get('wcf.global.date.dateTimeFormat');
 -                      $(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 = function() { this.init(); };
 -WCF.Dictionary.prototype = {
 -      /**
 -       * 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);
 -              }
 -      }
 -};
 -
 -/**
 - * Global language storage.
 - * 
 - * @see       WCF.Dictionary
 - */
 -WCF.Language = {
 -      _variables: new WCF.Dictionary(),
 -      
 -      /**
 -       * @see WCF.Dictionary.addObject()
 -       */
 -      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) {
 -              return this._variables.get(key);
 -      }
 -};
 -
 -/**
 - * String utilities.
 - */
 -WCF.String = {
 -      /**
 -       * Makes a string's first character uppercase
 -       * 
 -       * @param       string          string
 -       * @return      string
 -       */
 -      ucfirst: function(string) {
 -              return string.substring(0, 1).toUpperCase() + string.substring(1);
 -      },
 -      
 -      /**
 -       * Adds thousands separators to a given number.
 -       * 
 -       * @param       mixed           number
 -       * @return      string
 -       */
 -      addThousandsSeparator: function(number) {
 -              var $numberString = String(number);
 -              if (number >= 1000 || number <= -1000) {
 -                      var $negative = false;
 -                      if (number <= -1000) {
 -                              $negative = true;
 -                              $numberString = $numberString.substring(1);
 -                      }
 -                      var $separator = WCF.Language.get('wcf.global.thousandsSeparator');
 -                      
 -                      if ($separator != null && $separator != '') {
 -                              var $numElements = new Array();
 -                              var $firstPart = $numberString.length % 3
 -                              if ($firstPart == 0) $firstPart = 3;
 -                              for (var $i = 0; $i < Math.ceil($numberString.length / 3); $i++) {
 -                                      if ($i == 0) $numElements.push($numberString.substring(0, $firstPart));
 -                                      else {
 -                                              var $start = (($i - 1) * 3) + $firstPart
 -                                              $numElements.push($numberString.substring($start, $start + 3));
 -                                      }
 -                              }
 -                              $numberString = (($negative) ? ('-') : ('')) + $numElements.join($separator);
 -                      }
 -              }
 -              
 -              return $numberString;
 -      }
 -};
 -
 -/**
 - * 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 = {
 -      /**
 -       * Initializes all TabMenus
 -       */
 -      init: function() {
 -              $('.tabMenuContainer').each(function(index, tabMenu) {
 -                      if (!$(tabMenu).attr('id')) {
 -                              var $randomID = WCF.getRandomID();
 -                              $(tabMenu).attr('id', $randomID);
 -                      }
 -                      
 -                      // init jQuery UI TabMenu
 -                      $(tabMenu).wcfTabs({
 -                              select: function(event, ui) {
 -                                      var $panel = $(ui.panel);
 -                                      var $container = $panel.closest('.tabMenuContainer');
 -                                      
 -                                      // store currently selected item
 -                                      if ($container.data('store')) {
 -                                              if ($.wcfIsset($container.data('store'))) {
 -                                                      $('#' + $container.data('store')).attr('value', $panel.attr('id'));
 -                                              }
 -                                      }
 -                              }
 -                      });
 -                      
 -                      // display active item on init
 -                      if ($(tabMenu).data('active')) {
 -                              $(tabMenu).find('.tabMenuContent').each(function(index, tabMenuItem) {
 -                                      if ($(tabMenuItem).attr('id') == $(tabMenu).data('active')) {
 -                                              $(tabMenu).wcfTabs('select', index);
 -                                      }
 -                              });
 -                      }
 -              });
 -      }
 -};
 -
 -/**
 - * Toggles options.
 - * 
 - * @param     string          element
 - * @param     array           showItems
 - * @param     array           hideItems
 - */
 -WCF.ToggleOptions = function(element, showItems, hideItems) { this.init(element, showItems, hideItems); };
 -WCF.ToggleOptions.prototype = {
 -      /**
 -       * target item
 -       * 
 -       * @var jQuery
 -       */
 -      _element: null,
 -      
 -      /**
 -       * list of items to be shown
 -       * 
 -       * @var array
 -       */
 -      _showItems: [],
 -      
 -      /**
 -       * list of items to be hidden
 -       * 
 -       * @var array
 -       */
 -      _hideItems: [],
 -      
 -      /**
 -       * Initializes option toggle.
 -       * 
 -       * @param       string          element
 -       * @param       array           showItems
 -       * @param       array           hideItems
 -       */
 -      init: function(element, showItems, hideItems) {
 -              this._element = $('#' + element);
 -              this._showItems = showItems;
 -              this._hideItems = hideItems;
 -              
 -              // bind event
 -              this._element.click($.proxy(this._toggle, this));
 -              
 -              // execute toggle on init
 -              this._toggle();
 -      },
 -      
 -      /**
 -       * Toggles items.
 -       */
 -      _toggle: function() {
 -              if (!this._element.attr('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();
 -              }
 -      }
 -}
 -
 -/**
 - * Basic implementation for WCF dialogs.
 - */
 -$.widget('ui.wcfDialog', $.ui.dialog, {
 -      _init: function() {
 -              this.options.autoOpen = true;
 -              this.options.close = function(event, ui) {
 -                      $(this).parent('.ui-dialog').wcfDropOut('down', $.proxy(function() {
 -                              $(this).parent('.ui-dialog').empty().remove();
 -                      }, this));
 -              };
 -              this.options.height = 'auto';
 -              this.options.minHeight = 0;
 -              this.options.modal = true;
 -              this.options.width = 'auto';
 -              
 -              $.ui.dialog.prototype._init.apply(this, arguments);
 -      }
 -});
 -
 -/**
 - * Basic implementation for WCF dialogs loading content
 - * via AJAX before calling dialog itself.
 - */
 -$.widget('ui.wcfAJAXDialog', $.ui.dialog, {
 -      /**
 -       * Indicates wether callback was already executed
 -       * 
 -       * @var boolean
 -       */
 -      _callbackExecuted: false,
 -      
 -      /**
 -       * Initializes AJAX-request to fetch content.
 -       */
 -      _init: function() {
 -              if (this.options.ajax) {
 -                      this._loadContent();
 -              }
 -              
 -              // force dialog to be placed centered
 -              this.options.position = {
 -                      my: 'center center',
 -                      at: 'center center'
 -              };
 -              
 -              // dialog should display a spinner-like image, thus immediately fire up dialog
 -              this.options.autoOpen = true;
 -              this.options.width = 'auto';
 -              this.options.minHeight = 80;
 -              
 -              // disable ability to move dialog
 -              this.options.resizable = false;
 -              this.options.draggable = false;
 -              
 -              this.options.modal = true;
 -              this.options.hide = {
 -                      effect: 'drop',
 -                      direction: 'down'
 -              };
 -              
 -              this.options.close = function(event, ui) {
 -                      // loading ajax content seems to block properly closing
 -                      $(this).parent('.ui-dialog').empty().remove();
 -              };
 -              
 -              if (this.options.preventClose) {
 -                      this.options.closeOnEscape = false;
 -              }
 -              
 -              $.ui.dialog.prototype._init.apply(this, arguments);
 -              
 -              // remove complete node instead of removing node-by-node
 -              if (this.options.hideTitle && this.options.preventClose) {
 -                      this.element.parent('.ui-dialog').find('div.ui-dialog-titlebar').empty().remove();
 -              }
 -              else {
 -                      if (this.options.hideTitle) {
 -                              // remove title element
 -                              $('#ui-dialog-title-' + this.element.attr('id')).empty().remove();
 -                      }
 -                      
 -                      if (this.options.preventClose) {
 -                              // remove close-button
 -                              this.element.parent('.ui-dialog').find('a.ui-dialog-titlebar-close').empty().remove();
 -                      }
 -              }
 -      },
 -      
 -      /**
 -       * Loads content via AJAX.
 -       * 
 -       * @todo        Enforce JSON
 -       */
 -      _loadContent: function() {
 -              var $type = 'GET';
 -              if (this.options.ajax.type) {
 -                      $type = this.options.ajax.type;
 -                      
 -                      if (this.options.ajax.type != 'GET' && this.options.ajax.type != 'POST') {
 -                              $type = 'GET';
 -                      }
 -              }
 -              
 -              var $data = this.options.ajax.data || {};
 -              
 -              $.ajax({
 -                      url: this.options.ajax.url,
 -                      context: this,
 -                      dataType: 'json',
 -                      type: $type,
 -                      data: $data,
 -                      success: $.proxy(this._createDialog, this),
 -                      error: function(transport) {
 -                              alert(transport.responseText);
 -                      }
 -              });
 -      },
 -      
 -      /**
 -       * Inserts content.
 -       * 
 -       * @param       string          data
 -       */
 -      _createDialog: function(data) {
 -              data.ignoreTemplate = true;
 -              this.element.data('responseData', data);
 -              
 -              this.element.wcfGrow({
 -                      content: data.template,
 -                      parent: this.element.parent('.ui-dialog')
 -              }, {
 -                      duration: 600,
 -                      complete: $.proxy(function(data) {
 -                              this.element.css({
 -                                      height: 'auto'
 -                              });
 -                              
 -                              // prevent double execution due to two complete-calls (two times animate)
 -                              if (this._callbackExecuted) {
 -                                      return;
 -                              }
 -                              
 -                              this._callbackExecuted = true;
 -                              
 -                              this.element.removeClass('overlayLoading');
 -                              this.element.html(this.element.data('responseData').template);
 -                              
 -                              if (this.options.ajax.success) {
 -                                      this.options.ajax.success();
 -                              }
 -                      }, this)
 -              });
 -      },
 -      
 -      /**
 -       * Redraws dialog, should be executed everytime content is changed.
 -       */
 -      redraw: function() {
 -              var $dimensions = this.element.getDimensions();
 -              
 -              if ($dimensions.height > 200) {
 -                      this.element.wcfGrow({
 -                              content: this.element.html(),
 -                              parent: this.element.parent('.ui-dialog')
 -                      }, {
 -                              duration: 600,
 -                              complete: function() {
 -                                      $(this).css({ height: 'auto' });
 -                              }
 -                      });
 -              }
 -      }
 -});
 -
 -/**
 - * 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
 - */
 -$.widget('ui.wcfTabs', $.ui.tabs, {
 -      _init: function() {
 -              $.ui.dialog.prototype._init.apply(this, arguments);
 -      },
 -      
 -      _sanitizeSelector: function(hash) {
 -              return hash.replace(/([:\.])/g, '\\$1');
 -      }
 -});
 -
 -/**
 - * jQuery widget implementation of the wcf pagination.
 - */
 -$.widget('ui.wcfPages', {
 -      SHOW_LINKS: 11,
 -      SHOW_SUB_LINKS: 20,
 -      
 -      options: {
 -              // vars
 -              activePage: 1,
 -              maxPage: 1,
 -              
 -              // icons
 -              previousIcon: RELATIVE_WCF_DIR + 'icon/previousS.png',
 -              previousDisabledIcon: RELATIVE_WCF_DIR + 'icon/previousDisabledS.png',
 -              arrowDownIcon: RELATIVE_WCF_DIR + 'icon/arrowDown.png',
 -              nextIcon: RELATIVE_WCF_DIR + 'icon/nextS.png',
 -              nextDisabledIcon: RELATIVE_WCF_DIR + 'icon/nextDisabledS.png',
 -              
 -              // 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 th pages widget.
 -       */
 -      _render: function() {
 -              // only render if we have more than 1 page
 -              if (!this.options.disabled && this.options.maxPage > 1) {
 -                      // make sure pagination is visible
 -                      if (this.element.hasClass('hidden')) {
 -                              this.element.removeClass('hidden');
 -                      }
 -                      this.element.show();
 -                      
 -                      this.element.children().remove();
 -                      
 -                      var $pageList = $('<ul></ul>');
 -                      this.element.append($pageList);
 -                      
 -                      var $previousElement = $('<li></li>');
 -                      $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 = $('<img src="' + this.options.previousIcon + '" alt="" />');
 -                              $previousLink.append($previousImage);
 -                      }
 -                      else {
 -                              var $previousImage = $('<img src="' + this.options.previousDisabledIcon + '" alt="" />');
 -                              $previousElement.append($previousImage);
 -                      }
 -                      $previousElement.addClass('skip');
 -                      
 -                      // 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 {
 -                                      var $leftChildren = $('<li class="children"></li>');
 -                                      $pageList.append($leftChildren);
 -                                      
 -                                      var $leftChildrenLink = $('<a>&hellip;</a>');
 -                                      $leftChildren.append($leftChildrenLink);
 -                                      $leftChildrenLink.click($.proxy(this._startInput, this));
 -                                      
 -                                      var $leftChildrenImage = $('<img src="' + this.options.arrowDownIcon + '" alt="" />');
 -                                      $leftChildrenLink.append($leftChildrenImage);
 -                                      
 -                                      var $leftChildrenInput = $('<input type="text" class="inputText" name="pageNo" />');
 -                                      $leftChildren.append($leftChildrenInput);
 -                                      $leftChildrenInput.keydown($.proxy(this._handleInput, this));
 -                                      $leftChildrenInput.keyup($.proxy(this._handleInput, this));
 -                                      $leftChildrenInput.blur($.proxy(this._stopInput, this));
 -                                      
 -                                      var $leftChildrenContainer = $('<div></div>');
 -                                      $leftChildren.append($leftChildrenContainer);
 -                                      
 -                                      var $leftChildrenList = $('<ul></u>');
 -                                      $leftChildrenContainer.append($leftChildrenList);
 -                                      
 -                                      // render sublinks
 -                                      var $k = 0;
 -                                      var $step = Math.ceil(($left - 2) / this.SHOW_SUB_LINKS);
 -                                      for (var $i = 2; $i <= $left; $i += $step) {
 -                                              $leftChildrenList.append(this._renderLink($i, ($k != 0 && $k % 4 == 0)));
 -                                              $k++;
 -                                      }
 -                              }
 -                      }
 -                      
 -                      // 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 {
 -                                      var $rightChildren = $('<li class="children"></li>');
 -                                      $pageList.append($rightChildren);
 -                                      
 -                                      var $rightChildrenLink = $('<a>&hellip;</a>');
 -                                      $rightChildren.append($rightChildrenLink);
 -                                      $rightChildrenLink.click($.proxy(this._startInput, this));
 -                                      
 -                                      var $rightChildrenImage = $('<img src="' + this.options.arrowDownIcon + '" alt="" />');
 -                                      $rightChildrenLink.append($rightChildrenImage);
 -                                      
 -                                      var $rightChildrenInput = $('<input type="text" class="inputText" name="pageNo" />');
 -                                      $rightChildren.append($rightChildrenInput);
 -                                      $rightChildrenInput.keydown($.proxy(this._handleInput, this));
 -                                      $rightChildrenInput.keyup($.proxy(this._handleInput, this));
 -                                      $rightChildrenInput.blur($.proxy(this._stopInput, this));
 -                                      
 -                                      var $rightChildrenContainer = $('<div></div>');
 -                                      $rightChildren.append($rightChildrenContainer);
 -                                      
 -                                      var $rightChildrenList = $('<ul></ul>');
 -                                      $rightChildrenContainer.append($rightChildrenList);
 -                                      
 -                                      // render sublinks
 -                                      var $k = 0;
 -                                      var $step = Math.ceil((this.options.maxPage - $right) / this.SHOW_SUB_LINKS);
 -                                      for (var $i = $right; $i < this.options.maxPage; $i += $step) {
 -                                              $rightChildrenList.append(this._renderLink($i, ($k != 0 && $k % 4 == 0)));
 -                                              $k++;
 -                                      }
 -                              }
 -                      }
 -                      
 -                      // add last page
 -                      $pageList.append(this._renderLink(this.options.maxPage));
 -                      
 -                      // add next button
 -                      var $nextElement = $('<li></li>');
 -                      $pageList.append($nextElement);
 -                      
 -                      if (this.options.activePage < this.options.maxPage) {
 -                              var $nextLink = $('<a title="' + ((this.options.nextPage != null) ? (' title="' + this.options.nextPage + '"') : ('')) + '"></a>');
 -                              $nextElement.append($nextLink);
 -                              this._bindSwitchPage($nextLink, this.options.activePage + 1);
 -                              
 -                              var $nextImage = $('<img src="' + this.options.nextIcon + '" alt="" />');
 -                              $nextLink.append($nextImage);
 -                      }
 -                      else {
 -                              var $nextImage = $('<img src="' + this.options.nextDisabledIcon + '" alt="" />');
 -                              $nextElement.append($nextImage);
 -                      }
 -                      $nextElement.addClass('skip');
 -              }
 -              else {
 -                      // otherwise hide the paginator if not already hidden
 -                      this.element.hide();
 -              }
 -      },
 -      
 -      /**
 -       * Renders a page link
 -       * 
 -       * @parameter   integer         page
 -       * 
 -       * @return              $(element)
 -       */
 -      _renderLink: function(page, lineBreak) {
 -              var $pageElement = $('<li></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) {
 -                                      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();
 -                              }
 -                      }
 -              }
 -      }
 -});
 -
 -/**
 - * 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);
 -}
 +/**\r
 + * Class and function collection for WCF\r
 + * \r
 + * @author    Alexander Ebert\r
 + * @copyright 2001-2011 WoltLab GmbH\r
 + * @license   GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>\r
 + */\r
 +\r
 +/**\r
 + * Initialize WCF namespace\r
 + */\r
 +var WCF = {};\r
 +\r
 +/**\r
 + * Extends jQuery with additional methods.\r
 + */\r
 +$.extend(true, {\r
 +      /**\r
 +       * Escapes an ID to work with jQuery selectors.\r
 +       *\r
 +       * @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\r
 +       * @param       string          id\r
 +       * @return      string\r
 +       */\r
 +      wcfEscapeID: function(id) {\r
 +              return id.replace(/(:|\.)/g, '\\$1');\r
 +      },\r
 +      \r
 +      /**\r
 +       * Returns true if given ID exists within DOM.\r
 +       * \r
 +       * @param       string          id\r
 +       * @return      boolean\r
 +       */\r
 +      wcfIsset: function(id) {\r
 +              return !!$('#' + $.wcfEscapeID(id)).length;\r
 +      }\r
 +});\r
 +\r
 +/**\r
 + * Extends jQuery's chainable methods.\r
 + */\r
 +$.fn.extend({\r
 +      /**\r
 +       * Returns tag name of current jQuery element.\r
 +       * \r
 +       * @returns     string\r
 +       */\r
 +      getTagName: function() {\r
 +              return this.get(0).tagName.toLowerCase();\r
 +      },\r
 +      \r
 +      /**\r
 +       * Returns the dimensions for current element.\r
 +       * \r
 +       * @see         http://api.jquery.com/hidden-selector/\r
 +       * @param       string          type\r
 +       * @return      object\r
 +       */\r
 +      getDimensions: function(type) {\r
 +              var dimensions = css = {};\r
 +              var wasHidden = false;\r
 +              \r
 +              // show element to retrieve dimensions and restore them later\r
 +              if (this.is(':hidden')) {\r
 +                      css = {\r
 +                              display: this.css('display'),\r
 +                              visibility: this.css('visibility')\r
 +                      };\r
 +                      \r
 +                      wasHidden = true;\r
 +                      \r
 +                      this.css({\r
 +                              display: 'block',\r
 +                              visibility: 'hidden'\r
 +                      });\r
 +              }\r
 +              \r
 +              switch (type) {\r
 +                      case 'inner':\r
 +                              dimensions = {\r
 +                                      height: this.innerHeight(),\r
 +                                      width: this.innerWidth()\r
 +                              };\r
 +                      break;\r
 +                      \r
 +                      case 'outer':\r
 +                              dimensions = {\r
 +                                      height: this.outerHeight(),\r
 +                                      width: this.outerWidth()\r
 +                              };\r
 +                      break;\r
 +                      \r
 +                      default:\r
 +                              dimensions = {\r
 +                                      height: this.height(),\r
 +                                      width: this.width()\r
 +                              };\r
 +                      break;\r
 +              }\r
 +              \r
 +              // restore previous settings\r
 +              if (wasHidden) {\r
 +                      this.css(css);\r
 +              }\r
 +              \r
 +              return dimensions;\r
 +      },\r
 +      \r
 +      /**\r
 +       * Returns the offsets for current element, defaults to position\r
 +       * relative to document.\r
 +       * \r
 +       * @see         http://api.jquery.com/hidden-selector/\r
 +       * @param       string          type\r
 +       * @return      object\r
 +       */\r
 +      getOffsets: function(type) {\r
 +              var offsets = css = {};\r
 +              var wasHidden = false;\r
 +              \r
 +              // show element to retrieve dimensions and restore them later\r
 +              if (this.is(':hidden')) {\r
 +                      css = {\r
 +                              display: this.css('display'),\r
 +                              visibility: this.css('visibility')\r
 +                      };\r
 +                      \r
 +                      wasHidden = true;\r
 +                      \r
 +                      this.css({\r
 +                              display: 'block',\r
 +                              visibility: 'hidden'\r
 +                      });\r
 +              }\r
 +              \r
 +              switch (type) {\r
 +                      case 'offset':\r
 +                              offsets = this.offset();\r
 +                      break;\r
 +                      \r
 +                      case 'position':\r
 +                      default:\r
 +                              offsets = this.position();\r
 +                      break;\r
 +              }\r
 +              \r
 +              // restore previous settings\r
 +              if (wasHidden) {\r
 +                      this.css(css);\r
 +              }\r
 +              \r
 +              return offsets;\r
 +      },\r
 +      \r
 +      /**\r
 +       * Changes element's position to 'absolute' or 'fixed' while maintaining it's\r
 +       * current position relative to viewport. Optionally removes element from\r
 +       * current DOM-node and moving it into body-element (useful for drag & drop)\r
 +       * \r
 +       * @param       boolean         rebase\r
 +       * @return      object\r
 +       */\r
 +      makePositioned: function(position, rebase) {\r
 +              if (position != 'absolute' && position != 'fixed') {\r
 +                      position = 'absolute';\r
 +              }\r
 +              \r
 +              var $currentPosition = this.getOffsets('position');\r
 +              this.css({\r
 +                      position: position,\r
 +                      left: $currentPosition.left,\r
 +                      margin: 0,\r
 +                      top: $currentPosition.top\r
 +              });\r
 +              \r
 +              if (rebase) {\r
 +                      this.remove().appentTo('body');\r
 +              }\r
 +              \r
 +              return this;\r
 +      },\r
 +      \r
 +      /**\r
 +       * Disables a form element.\r
 +       * \r
 +       * @return jQuery\r
 +       */\r
 +      disable: function() {\r
 +              return this.attr('disabled', 'disabled');\r
 +      },\r
 +      \r
 +      /**\r
 +       * Enables a form element.\r
 +       * \r
 +       * @return      jQuery\r
 +       */\r
 +      enable: function() {\r
 +              return this.removeAttr('disabled');\r
 +      },\r
 +      \r
 +      /**\r
 +       * Applies a grow-effect by resizing element while moving the\r
 +       * element appropriately\r
 +       * \r
 +       * @param       object          data\r
 +       * @param       object          options\r
 +       * @return      jQuery\r
 +       */\r
 +      wcfGrow: function(data, options) {\r
 +              // create temporarily element to determine dimensions\r
 +              var $tempElementID = WCF.getRandomID();\r
 +              $('body').append('<div id="' + $tempElementID + '" class="wcfDimensions">' + data.content + '</div>');\r
 +              var $tempElement = $('#' + $tempElementID);\r
 +              \r
 +              // get content dimensions\r
 +              var $dimensions = $tempElement.getDimensions();\r
 +              \r
 +              // remove temporarily element\r
 +              $tempElement.empty().remove();\r
 +              \r
 +              // move parent element, used if applying effect on dialogs\r
 +              if (!data.parent) {\r
 +                      data.parent = this;\r
 +              }\r
 +              \r
 +              // calculate values for grow-effect\r
 +              var $borderHeight = parseInt(data.parent.css('borderTopWidth')) + parseInt(data.parent.css('borderBottomWidth'));\r
 +              var $borderWidth = parseInt(data.parent.css('borderLeftWidth')) + parseInt(data.parent.css('borderRightWidth'));\r
 +              \r
 +              var $windowDimensions = $(window).getDimensions();\r
 +              var $leftOffset = Math.round(($windowDimensions.width - ($dimensions.width + $borderWidth)) / 2);\r
 +              var $topOffset = Math.round(($windowDimensions.height - ($dimensions.height + $borderHeight)) / 2);\r
 +              \r
 +              data.parent.makePositioned('fixed', false);\r
 +              data.parent.animate({\r
 +                      left: $leftOffset + 'px',\r
 +                      top: $topOffset + 'px'\r
 +              }, options);\r
 +              \r
 +              return this.animate({\r
 +                      height: $dimensions.height,\r
 +                      width: $dimensions.width\r
 +              }, options);\r
 +      },\r
 +      \r
 +      /**\r
 +       * Shows an element by sliding and fading it into viewport.\r
 +       * \r
 +       * @param       string          direction\r
 +       * @param       object          callback\r
 +       * @returns     jQuery\r
 +       */\r
 +      wcfDropIn: function(direction, callback) {\r
 +              if (!direction) direction = 'up';\r
 +              \r
 +              return this.show(WCF.getEffect(this.getTagName(), 'drop'), { direction: direction }, 600, callback);\r
 +      },\r
 +      \r
 +      /**\r
 +       * Hides an element by sliding and fading it out the viewport.\r
 +       * \r
 +       * @param       string          direction\r
 +       * @param       object          callback\r
 +       * @returns     jQuery\r
 +       */\r
 +      wcfDropOut: function(direction, callback) {\r
 +              if (!direction) direction = 'down';\r
 +              \r
 +              return this.hide(WCF.getEffect(this.getTagName(), 'drop'), { direction: direction }, 600, callback);\r
 +      },\r
 +      \r
 +      /**\r
 +       * Shows an element by blinding it up.\r
 +       * \r
 +       * @param       string          direction\r
 +       * @param       object          callback\r
 +       * @returns     jQuery\r
 +       */\r
 +      wcfBlindIn: function(direction, callback) {\r
 +              if (!direction) direction = 'vertical';\r
 +              \r
 +              return this.show(WCF.getEffect(this.getTagName(), 'blind'), { direction: direction }, 200, callback);\r
 +      },\r
 +      \r
 +      /**\r
 +       * Hides an element by blinding it down.\r
 +       * \r
 +       * @param       string          direction\r
 +       * @param       object          callback\r
 +       * @returns     jQuery\r
 +       */\r
 +      wcfBlindOut: function(direction, callback) {\r
 +              if (!direction) direction = 'vertical';\r
 +              \r
 +              return this.hide(WCF.getEffect(this.getTagName(), 'blind'), { direction: direction }, 200, callback);\r
 +      },\r
 +      \r
 +      /**\r
 +       * Highlights an element.\r
 +       * \r
 +       * @param       object          options\r
 +       * @param       object          callback\r
 +       * @returns     jQuery\r
 +       */\r
 +      wcfHighlight: function(options, callback) {\r
 +              return this.effect('highlight', options, 600, callback);\r
 +      }\r
 +});\r
 +\r
 +/**\r
 + * WoltLab Community Framework core methods\r
 + */\r
 +$.extend(WCF, {\r
 +      /**\r
 +       * Counter for dynamic element id's\r
 +       *\r
 +       * @var integer\r
 +       */\r
 +      _idCounter: 0,\r
 +      \r
 +      /**\r
 +       * Shows a modal dialog with a built-in AJAX-loader.\r
 +       * \r
 +       * @param       string          dialogID\r
 +       * @param       boolean         resetDialog\r
 +       */\r
 +      showAJAXDialog: function(dialogID, resetDialog) {\r
 +              if (!dialogID) {\r
 +                      dialogID = this.getRandomID();\r
 +              }\r
 +              \r
 +              if (!$.wcfIsset(dialogID)) {\r
 +                      $('body').append($('<div id="' + dialogID + '"></div>'));\r
 +              }\r
 +              \r
 +              var dialog = $('#' + $.wcfEscapeID(dialogID));\r
 +              \r
 +              if (resetDialog) {\r
 +                      dialog.empty();\r
 +              }\r
 +              \r
 +              dialog.addClass('overlayLoading');\r
 +              \r
 +              var dialogOptions = arguments[2] || {};\r
 +              dialog.wcfAJAXDialog(dialogOptions);\r
 +      },\r
 +      \r
 +      /**\r
 +       * Shows a modal dialog.\r
 +       * @param       string          dialogID\r
 +       */\r
 +      showDialog: function(dialogID) {\r
 +              // we cannot work with a non-existant dialog, if you wish to\r
 +              // load content via AJAX, see showAJAXDialog() instead\r
 +              if (!$.wcfIsset(dialogID)) return;\r
 +              \r
 +              var $dialog = $('#' + $.wcfEscapeID(dialogID));\r
 +              \r
 +              var dialogOptions = arguments[2] || {};\r
 +              $dialog.wcfDialog(dialogOptions);\r
 +      },\r
 +      \r
 +      /**\r
 +       * Returns a dynamically created id.\r
 +       * \r
 +       * @see         https://github.com/sstephenson/prototype/blob/master/src/prototype/dom/dom.js#L1789\r
 +       * @return      string\r
 +       */\r
 +      getRandomID: function() {\r
 +              var $elementID = '';\r
 +              \r
 +              do {\r
 +                      $elementID = 'wcf' + this._idCounter++;\r
 +              }\r
 +              while ($.wcfIsset($elementID));\r
 +              \r
 +              return $elementID;\r
 +      },\r
 +      \r
 +      /**\r
 +       * Wrapper for $.inArray which returns boolean value instead of\r
 +       * index value, similar to PHP's in_array().\r
 +       * \r
 +       * @param       mixed           needle\r
 +       * @param       array           haystack\r
 +       * @return      boolean\r
 +       */\r
 +      inArray: function(needle, haystack) {\r
 +              return ($.inArray(needle, haystack) != -1);\r
 +      },\r
 +      \r
 +      /**\r
 +       * Adjusts effect for partially supported elements.\r
 +       * \r
 +       * @param       object          object\r
 +       * @param       string          effect\r
 +       * @return      string\r
 +       */\r
 +      getEffect: function(tagName, effect) {\r
 +              // most effects are not properly supported on table rows, use highlight instead\r
 +              if (tagName == 'tr') {\r
 +                      return 'highlight';\r
 +              }\r
 +              \r
 +              return effect;\r
 +      }\r
 +});\r
 +\r
 +/**\r
 + * Provides a simple call for periodical executed functions. Based upon\r
 + * ideas by Prototype's PeriodicalExecuter.\r
 + * \r
 + * @see               https://github.com/sstephenson/prototype/blob/master/src/prototype/lang/periodical_executer.js\r
 + * @param     function                callback\r
 + * @param     integer                 delay\r
 + */\r
 +WCF.PeriodicalExecuter = function(callback, delay) { this.init(callback, delay); };\r
 +WCF.PeriodicalExecuter.prototype = {\r
 +      /**\r
 +       * Initializes a periodical executer.\r
 +       * \r
 +       * @param       function                callback\r
 +       * @param       integer                 delay\r
 +       */\r
 +      init: function(callback, delay) {\r
 +              this.callback = callback;\r
 +              this.delay = delay;\r
 +              this.loop = true;\r
 +              \r
 +              this.intervalID = setInterval($.proxy(this._execute, this), this.delay);\r
 +      },\r
 +      \r
 +      /**\r
 +       * Executes callback.\r
 +       */\r
 +      _execute: function() {\r
 +              this.callback(this);\r
 +              \r
 +              if (!this.loop) {\r
 +                      clearInterval(this.intervalID);\r
 +              }\r
 +      },\r
 +      \r
 +      /**\r
 +       * Terminates loop.\r
 +       */\r
 +      stop: function() {\r
 +              this.loop = false;\r
 +      }\r
 +};\r
 +\r
 +/**\r
 + * Namespace for AJAXProxies\r
 + */\r
 +WCF.Action = {};\r
 +\r
 +/**\r
 + * Basic implementation for AJAX-based proxyies\r
 + * \r
 + * @param     object          options\r
 + */\r
 +WCF.Action.Proxy = function(options) { this.init(options); };\r
 +WCF.Action.Proxy.prototype = {\r
 +      /**\r
 +       * Initializes AJAXProxy.\r
 +       * \r
 +       * @param       object          options\r
 +       */\r
 +      init: function(options) {\r
 +              // initialize default values\r
 +              this.options = $.extend(true, {\r
 +                      autoSend: false,\r
 +                      data: { },\r
 +                      after: null,\r
 +                      init: null,\r
 +                      failure: null,\r
 +                      success: null,\r
 +                      type: 'POST',\r
 +                      url: 'index.php?action=AJAXProxy&t=' + SECURITY_TOKEN + SID_ARG_2ND\r
 +              }, options);\r
 +              \r
 +              this.confirmationDialog = null;\r
 +              this.loading = null;\r
 +              \r
 +              // send request immediately after initialization\r
 +              if (this.options.autoSend) {\r
 +                      this.sendRequest();\r
 +              }\r
 +      },\r
 +      \r
 +      /**\r
 +       * Sends an AJAX request.\r
 +       */\r
 +      sendRequest: function() {\r
 +              this._init();\r
 +              \r
 +              $.ajax({\r
 +                      data: this.options.data,\r
 +                      dataType: 'json',\r
 +                      type: this.options.type,\r
 +                      url: this.options.url,\r
 +                      success: $.proxy(this._success, this),\r
 +                      error: $.proxy(this._failure, this)\r
 +              });\r
 +      },\r
 +      \r
 +      /**\r
 +       * Fires before request is send, displays global loading status.\r
 +       */\r
 +      _init: function() {\r
 +              if ($.isFunction(this.options.init)) {\r
 +                      this.options.init();\r
 +              }\r
 +              \r
 +              $('<div id="actionProxyLoading" style="display: none;">Loading â€¦</div>').appendTo($('body'));\r
 +              this.loading = $('#actionProxyLoading');\r
 +              this.loading.wcfDropIn();\r
 +      },\r
 +      \r
 +      /**\r
 +       * Handles AJAX errors.\r
 +       * \r
 +       * @param       object          jqXHR\r
 +       * @param       string          textStatus\r
 +       * @param       string          errorThrown\r
 +       */\r
 +      _failure: function(jqXHR, textStatus, errorThrown) {\r
 +              try {\r
 +                      var data = $.parseJSON(jqXHR.responseText);\r
 +                      \r
 +                      // call child method if applicable\r
 +                      if ($.isFunction(this.options.failure)) {\r
 +                              this.options.failure(jqXHR, textStatus, errorThrown, data);\r
 +                      }\r
 +                      \r
 +                      var $randomID = WCF.getRandomID();\r
 +                      $('<div id="' + $randomID + '" title="HTTP/1.0 ' + jqXHR.status + ' ' + errorThrown + '"><p>Der Server antwortete: ' + data.message + '.</p></div>').wcfDialog();\r
 +              }\r
 +              // failed to parse JSON\r
 +              catch (e) {\r
 +                      var $randomID = WCF.getRandomID();\r
 +                      $('<div id="' + $randomID + '" title="HTTP/1.0 ' + jqXHR.status + ' ' + errorThrown + '"><p>Der Server antwortete: ' + jqXHR.responseText + '.</p></div>').wcfDialog();\r
 +              }\r
 +              \r
 +              this._after();\r
 +      },\r
 +      \r
 +      /**\r
 +       * Handles successful AJAX requests.\r
 +       * \r
 +       * @param       object          data\r
 +       * @param       string          textStatus\r
 +       * @param       object          jqXHR\r
 +       */\r
 +      _success: function(data, textStatus, jqXHR) {\r
 +              // call child method if applicable\r
 +              if ($.isFunction(this.options.success)) {\r
 +                      this.options.success(data, textStatus, jqXHR);\r
 +              }\r
 +              \r
 +              this._after();\r
 +      },\r
 +      \r
 +      /**\r
 +       * Fires after an AJAX request, hides global loading status.\r
 +       */\r
 +      _after: function() {\r
 +              if ($.isFunction(this.options.after)) {\r
 +                      this.options.after();\r
 +              }\r
 +              \r
 +              this.loading.wcfDropOut('up');\r
 +      },\r
 +      \r
 +      /**\r
 +       * Sets options, MUST be used to set parameters before sending request\r
 +       * if calling from child classes.\r
 +       * \r
 +       * @param       string          optionName\r
 +       * @param       mixed           optionData\r
 +       */\r
 +      setOption: function(optionName, optionData) {\r
 +              this.options[optionName] = optionData;\r
 +      }\r
 +};\r
 +\r
 +/**\r
 + * Basic implementation for simple proxy access using bound elements.\r
 + * \r
 + * @param     object          options\r
 + * @param     object          callbacks\r
 + */\r
 +WCF.Action.SimpleProxy = function(options, callbacks) { this.init(options, callbacks); };\r
 +WCF.Action.SimpleProxy.prototype = {\r
 +      /**\r
 +       * Initializes SimpleProxy.\r
 +       * \r
 +       * @param       object          options\r
 +       * @param       object          callbacks\r
 +       */\r
 +      init: function(options, callbacks) {\r
 +              /**\r
 +               * action-specific options\r
 +               */\r
 +              this.options = $.extend(true, {\r
 +                      action: '',\r
 +                      className: '',\r
 +                      elements: null,\r
 +                      eventName: 'click'\r
 +              }, options);\r
 +              \r
 +              /**\r
 +               * proxy-specific options\r
 +               */\r
 +              this.callbacks = $.extend(true, {\r
 +                      after: null,\r
 +                      failure: null,\r
 +                      init: null,\r
 +                      success: null\r
 +              }, callbacks);\r
 +              \r
 +              if (!this.options.elements) return;\r
 +              \r
 +              // initialize proxy\r
 +              this.proxy = new WCF.Action.Proxy(this.callbacks);\r
 +              \r
 +              // bind event listener\r
 +              this.options.elements.each($.proxy(function(index, element) {\r
 +                      $(element).bind(this.options.eventName, $.proxy(this._handleEvent, this));\r
 +              }, this));\r
 +      },\r
 +      \r
 +      /**\r
 +       * Handles event actions.\r
 +       * \r
 +       * @param       object          event\r
 +       */\r
 +      _handleEvent: function(event) {\r
 +              this.proxy.setOption('data', {\r
 +                      actionName: this.options.action,\r
 +                      className: this.options.className,\r
 +                      objectIDs: [ $(event.target).data('objectID') ]\r
 +              });\r
 +              \r
 +              this.proxy.sendRequest();\r
 +      }\r
 +};\r
 +\r
 +/**\r
 + * Basic implementation for AJAXProxy-based deletion.\r
 + * \r
 + * @param     string          className\r
 + * @param     jQuery          containerList\r
 + */\r
 +WCF.Action.Delete = function(className, containerList) { this.init(className, containerList); };\r
 +WCF.Action.Delete.prototype = {\r
 +      /**\r
 +       * Initializes 'delete'-Proxy.\r
 +       * \r
 +       * @param       string          className\r
 +       * @param       jQuery          containerList\r
 +       */\r
 +      init: function(className, containerList) {\r
 +              if (!containerList.length) return;\r
 +              this.containerList = containerList;\r
 +              this.className = className;\r
 +              \r
 +              // initialize proxy\r
 +              var options = {\r
 +                      success: $.proxy(this._success, this)\r
 +              };\r
 +              this.proxy = new WCF.Action.Proxy(options);\r
 +              \r
 +              // bind event listener\r
 +              this.containerList.each($.proxy(function(index, container) {\r
 +                      $(container).find('.deleteButton').bind('click', $.proxy(this._click, this));\r
 +              }, this));\r
 +      },\r
 +      \r
 +      /**\r
 +       * Sends AJAX request.\r
 +       * \r
 +       * @param       object          event\r
 +       */\r
 +      _click: function(event) {\r
 +              var $target = $(event.target);\r
 +              \r
 +              if ($target.data('confirmMessage')) {\r
 +                      if (confirm($target.data('confirmMessage'))) {\r
 +                              this._sendRequest($target);\r
 +                      }\r
 +              }\r
 +              else {\r
 +                      this._sendRequest($target);\r
 +              }\r
 +              \r
 +      },\r
 +      \r
 +      _sendRequest: function(object) {\r
 +              this.proxy.setOption('data', {\r
 +                      actionName: 'delete',\r
 +                      className: this.className,\r
 +                      objectIDs: [ $(object).data('objectID') ]\r
 +              });\r
 +              \r
 +              this.proxy.sendRequest();\r
 +      },\r
 +      \r
 +      /**\r
 +       * Deletes items from containers.\r
 +       * \r
 +       * @param       object          data\r
 +       * @param       string          textStatus\r
 +       * @param       object          jqXHR\r
 +       */\r
 +      _success: function(data, textStatus, jqXHR) {\r
 +              // remove items\r
 +              this.containerList.each(function(index, container) {\r
 +                      var $objectID = $(container).find('.deleteButton').data('objectID');\r
 +                      if (WCF.inArray($objectID, data.objectIDs)) {\r
 +                              $(container).wcfBlindOut('up', function() {\r
 +                                      $(container).empty().remove();\r
 +                              }, container);\r
 +                      }\r
 +              });\r
 +      }\r
 +};\r
 +\r
 +/**\r
 + * Basic implementation for AJAXProxy-based toggle actions.\r
 + * \r
 + * @param     string          className\r
 + * @param     jQuery          containerList\r
 + */\r
 +WCF.Action.Toggle = function(className, containerList) { this.init(className, containerList); };\r
 +WCF.Action.Toggle.prototype = {\r
 +      /**\r
 +       * Initializes 'toggle'-Proxy\r
 +       * \r
 +       * @param       string          className\r
 +       * @param       jQuery          containerList\r
 +       */\r
 +      init: function(className, containerList) {\r
 +              if (!containerList.length) return;\r
 +              this.containerList = containerList;\r
 +              this.className = className;\r
 +              \r
 +              // initialize proxy\r
 +              var options = {\r
 +                      success: $.proxy(this._success, this)\r
 +              };\r
 +              this.proxy = new WCF.Action.Proxy(options);\r
 +              \r
 +              // bind event listener\r
 +              this.containerList.each($.proxy(function(index, container) {\r
 +                      $(container).find('.toggleButton').bind('click', $.proxy(this._click, this));\r
 +              }, this));\r
 +      },\r
 +      \r
 +      /**\r
 +       * Sends AJAX request.\r
 +       * \r
 +       * @param       object          event\r
 +       */\r
 +      _click: function(event) {\r
 +              this.proxy.setOption('data', {\r
 +                      actionName: 'toggle',\r
 +                      className: this.className,\r
 +                      objectIDs: [ $(event.target).data('objectID') ]\r
 +              });\r
 +              \r
 +              this.proxy.sendRequest();\r
 +      },\r
 +      \r
 +      /**\r
 +       * Toggles status icons.\r
 +       * \r
 +       * @param       object          data\r
 +       * @param       string          textStatus\r
 +       * @param       object          jqXHR\r
 +       */\r
 +      _success: function(data, textStatus, jqXHR) {\r
 +              // remove items\r
 +              this.containerList.each(function(index, container) {\r
 +                      var $toggleButton = $(container).find('.toggleButton');\r
 +                      if (WCF.inArray($toggleButton.data('objectID'), data.objectIDs)) {\r
 +                              $(container).wcfHighlight();\r
 +                              \r
 +                              // toggle icon source\r
 +                              $toggleButton.attr('src', function() {\r
 +                                      if (this.src.match(/enabled(S|M|L)\.png$/)) {\r
 +                                              return this.src.replace(/enabled(S|M|L)\.png$/, 'disabled$1\.png');\r
 +                                      }\r
 +                                      else {\r
 +                                              return this.src.replace(/disabled(S|M|L)\.png$/, 'enabled$1\.png');\r
 +                                      }\r
 +                              });\r
 +                              // toogle icon title\r
 +                              $toggleButton.attr('title', function() {\r
 +                                      if (this.src.match(/enabled(S|M|L)\.png$/)) {\r
 +                                              return $(this).data('disableMessage');\r
 +                                      }\r
 +                                      else {\r
 +                                              return $(this).data('enableMessage');\r
 +                                      }\r
 +                              });\r
 +                      }\r
 +              });\r
 +      }\r
 +};\r
 +\r
 +/**\r
 + * Namespace for date-related functions.\r
 + */\r
 +WCF.Date = {};\r
 +\r
 +/**\r
 + * Provides utility functions for date operations.\r
 + */\r
 +WCF.Date.Util = {\r
 +      /**\r
 +       * Returns UTC timestamp, if date is not given, current time will be used.\r
 +       * \r
 +       * @param       Date            date\r
 +       * @return      integer\r
 +       */\r
 +      gmdate: function(date) {\r
 +              var $date = (date) ? date : new Date();\r
 +              \r
 +              return Math.round(Date.UTC(\r
 +                      $date.getUTCFullYear(),\r
 +                      $date.getUTCMonth(),\r
 +                      $date.getUTCDay(),\r
 +                      $date.getUTCHours(),\r
 +                      $date.getUTCMinutes(),\r
 +                      $date.getUTCSeconds()\r
 +              ) / 1000);\r
 +      },\r
 +      \r
 +      /**\r
 +       * Returns a Date object with precise offset (including timezone and local timezone).\r
 +       * Parameter timestamp must be in miliseconds!\r
 +       * \r
 +       * @param       integer         timestamp\r
 +       * @param       integer         offset\r
 +       * @return      Date\r
 +       */\r
 +      getTimezoneDate: function(timestamp, offset) {\r
 +              var $date = new Date(timestamp);\r
 +              var $localOffset = $date.getTimezoneOffset() * -1 * 60000;\r
 +              \r
 +              return new Date((timestamp - $localOffset - offset));\r
 +      }\r
 +};\r
 +\r
 +/**\r
 + * Handles relative time designations.\r
 + */\r
 +WCF.Date.Time = function() { this.init(); };\r
 +WCF.Date.Time.prototype = {\r
 +      /**\r
 +       * Initializes relative datetimes.\r
 +       */\r
 +      init: function() {\r
 +              // initialize variables\r
 +              this.elements = $('time.datetime');\r
 +              this.timestamp = 0;\r
 +              \r
 +              // calculate relative datetime on init\r
 +              this._refresh();\r
 +              \r
 +              // re-calculate relative datetime every minute\r
 +              new WCF.PeriodicalExecuter($.proxy(this._refresh, this), 60000);\r
 +      },\r
 +      \r
 +      /**\r
 +       * Refreshes relative datetime for each element.\r
 +       */\r
 +      _refresh: function() {\r
 +              // TESTING ONLY!\r
 +              var $date = new Date();\r
 +              this.timestamp = ($date.getTime() - $date.getMilliseconds()) / 1000;\r
 +              // TESTING ONLY!\r
 +              \r
 +              this.elements.each($.proxy(this._refreshElement, this));\r
 +      },\r
 +      \r
 +      /**\r
 +       * Refreshes relative datetime for current element.\r
 +       * \r
 +       * @param       integer         index\r
 +       * @param       object          element\r
 +       */\r
 +      _refreshElement: function(index, element) {\r
 +              if (!$(element).attr('title')) {\r
 +                      $(element).attr('title', $(element).text());\r
 +              }\r
 +              \r
 +              var $timestamp = $(element).data('timestamp');\r
 +              var $date = $(element).data('date');\r
 +              var $time = $(element).data('time');\r
 +              var $offset = $(element).data('offset');\r
 +              \r
 +              // timestamp is less than 60 minutes ago (display 1 hour ago rather than 60 minutes ago)\r
 +              if (this.timestamp < ($timestamp + 3540)) {\r
 +                      var $minutes = Math.round((this.timestamp - $timestamp) / 60);\r
 +                      $(element).text(eval(WCF.Language.get('wcf.global.date.relative.minutes')));\r
 +              }\r
 +              // timestamp is less than 24 hours ago\r
 +              else if (this.timestamp < ($timestamp + 86400)) {\r
 +                      var $hours = Math.round((this.timestamp - $timestamp) / 3600);\r
 +                      $(element).text(eval(WCF.Language.get('wcf.global.date.relative.hours')));\r
 +              }\r
 +              // timestamp is less than a week ago\r
 +              else if (this.timestamp < ($timestamp + 604800)) {\r
 +                      var $days = Math.round((this.timestamp - $timestamp) / 86400);\r
 +                      var $string = eval(WCF.Language.get('wcf.global.date.relative.pastDays'));\r
 +              \r
 +                      // get day of week\r
 +                      var $dateObj = WCF.Date.Util.getTimezoneDate(($timestamp * 1000), $offset);\r
 +                      var $dow = $dateObj.getDay();\r
 +                      \r
 +                      $(element).text($string.replace(/\%day\%/, WCF.Language.get('__days')[$dow]).replace(/\%time\%/, $time));\r
 +              }\r
 +              // timestamp is between ~700 million years BC and last week\r
 +              else {\r
 +                      var $string = WCF.Language.get('wcf.global.date.dateTimeFormat');\r
 +                      $(element).text($string.replace(/\%date\%/, $date).replace(/\%time\%/, $time));\r
 +              }\r
 +      }\r
 +};\r
 +\r
 +/**\r
 + * Hash-like dictionary. Based upon idead from Prototype's hash\r
 + * \r
 + * @see       https://github.com/sstephenson/prototype/blob/master/src/prototype/lang/hash.js\r
 + */\r
 +WCF.Dictionary = function() { this.init(); };\r
 +WCF.Dictionary.prototype = {\r
 +      /**\r
 +       * Initializes a new dictionary.\r
 +       */\r
 +      init: function() {\r
 +              this.variables = { };\r
 +      },\r
 +      \r
 +      /**\r
 +       * Adds an entry.\r
 +       * \r
 +       * @param       string          key\r
 +       * @param       mixed           value\r
 +       */\r
 +      add: function(key, value) {\r
 +              this.variables[key] = value;\r
 +      },\r
 +      \r
 +      /**\r
 +       * Adds a traditional object to current dataset.\r
 +       * \r
 +       * @param       object          object\r
 +       */\r
 +      addObject: function(object) {\r
 +              for (var $key in object) {\r
 +                      this.add($key, object[$key]);\r
 +              }\r
 +      },\r
 +      \r
 +      /**\r
 +       * Adds a dictionary to current dataset.\r
 +       * \r
 +       * @param       object          dictionary\r
 +       */\r
 +      addDictionary: function(dictionary) {\r
 +              dictionary.each($.proxy(function(pair) {\r
 +                      this.add(pair.key, pair.value);\r
 +              }, this));\r
 +      },\r
 +      \r
 +      /**\r
 +       * Retrieves the value of an entry or returns null if key is not found.\r
 +       * \r
 +       * @param       string          key\r
 +       * @returns     mixed\r
 +       */\r
 +      get: function(key) {\r
 +              if (this.isset(key)) {\r
 +                      return this.variables[key];\r
 +              }\r
 +              \r
 +              return null;\r
 +      },\r
 +      \r
 +      /**\r
 +       * Returns true if given key is a valid entry.\r
 +       * \r
 +       * @param       string          key\r
 +       */\r
 +      isset: function(key) {\r
 +              return this.variables.hasOwnProperty(key);\r
 +      },\r
 +      \r
 +      /**\r
 +       * Removes an entry.\r
 +       * \r
 +       * @param       string          key\r
 +       */\r
 +      remove: function(key) {\r
 +              delete this.variables[key];\r
 +      },\r
 +      \r
 +      /**\r
 +       * Iterates through dictionary.\r
 +       * \r
 +       * Usage:\r
 +       *      var $hash = new WCF.Dictionary();\r
 +       *      $hash.add('foo', 'bar');\r
 +       *      $hash.each(function(pair) {\r
 +       *              // alerts:      foo = bar\r
 +       *              alert(pair.key + ' = ' + pair.value);\r
 +       *      });\r
 +       * \r
 +       * @param       function        callback\r
 +       */\r
 +      each: function(callback) {\r
 +              if (!$.isFunction(callback)) {\r
 +                      return;\r
 +              }\r
 +              \r
 +              for (var $key in this.variables) {\r
 +                      var $value = this.variables[$key];\r
 +                      var $pair = {\r
 +                              key: $key,\r
 +                              value: $value\r
 +                      };\r
 +                      \r
 +                      callback($pair);\r
 +              }\r
 +      }\r
 +};\r
 +\r
 +/**\r
 + * Global language storage.\r
 + * \r
 + * @see       WCF.Dictionary\r
 + */\r
 +WCF.Language = {\r
 +      _variables: new WCF.Dictionary(),\r
 +      \r
 +      /**\r
 +       * @see WCF.Dictionary.addObject()\r
 +       */\r
 +      add: function(key, value) {\r
 +              this._variables.add(key, value);\r
 +      },\r
 +      \r
 +      /**\r
 +       * @see WCF.Dictionary.addObject()\r
 +       */\r
 +      addObject: function(object) {\r
 +              this._variables.addObject(object);\r
 +      },\r
 +      \r
 +      /**\r
 +       * Retrieves a variable.\r
 +       * \r
 +       * @param       string          key\r
 +       * @return      mixed\r
 +       */\r
 +      get: function(key) {\r
 +              return this._variables.get(key);\r
 +      }\r
 +};\r
 +\r
 +/**\r
 + * String utilities.\r
 + */\r
 +WCF.String = {\r
 +      /**\r
 +       * Makes a string's first character uppercase\r
 +       * \r
 +       * @param       string          string\r
 +       * @return      string\r
 +       */\r
 +      ucfirst: function(string) {\r
 +              return string.substring(0, 1).toUpperCase() + string.substring(1);\r
++      },\r
++      \r
++      /**\r
++       * Adds thousands separators to a given number.\r
++       * \r
++       * @param       mixed           number\r
++       * @return      string\r
++       */\r
++      addThousandsSeparator: function(number) {\r
++              var $numberString = String(number);\r
++              if (number >= 1000 || number <= -1000) {\r
++                      var $negative = false;\r
++                      if (number <= -1000) {\r
++                              $negative = true;\r
++                              $numberString = $numberString.substring(1);\r
++                      }\r
++                      var $separator = WCF.Language.get('wcf.global.thousandsSeparator');\r
++                      \r
++                      if ($separator != null && $separator != '') {\r
++                              var $numElements = new Array();\r
++                              var $firstPart = $numberString.length % 3\r
++                              if ($firstPart == 0) $firstPart = 3;\r
++                              for (var $i = 0; $i < Math.ceil($numberString.length / 3); $i++) {\r
++                                      if ($i == 0) $numElements.push($numberString.substring(0, $firstPart));\r
++                                      else {\r
++                                              var $start = (($i - 1) * 3) + $firstPart\r
++                                              $numElements.push($numberString.substring($start, $start + 3));\r
++                                      }\r
++                              }\r
++                              $numberString = (($negative) ? ('-') : ('')) + $numElements.join($separator);\r
++                      }\r
++              }\r
++              \r
++              return $numberString;\r
 +      }\r
 +};\r
 +\r
 +/**\r
 + * Basic implementation for WCF TabMenus. Use the data attributes 'active' to specify the\r
 + * tab which should be shown on init. Furthermore you may specify a 'store' data-attribute\r
 + * which will be filled with the currently selected tab.\r
 + */\r
 +WCF.TabMenu = {\r
 +      /**\r
 +       * Initializes all TabMenus\r
 +       */\r
 +      init: function() {\r
 +              $('.tabMenuContainer').each(function(index, tabMenu) {\r
 +                      if (!$(tabMenu).attr('id')) {\r
 +                              var $randomID = WCF.getRandomID();\r
 +                              $(tabMenu).attr('id', $randomID);\r
 +                      }\r
 +                      \r
 +                      // init jQuery UI TabMenu\r
 +                      $(tabMenu).wcfTabs({\r
 +                              select: function(event, ui) {\r
 +                                      var $panel = $(ui.panel);\r
 +                                      var $container = $panel.closest('.tabMenuContainer');\r
 +                                      \r
 +                                      // store currently selected item\r
 +                                      if ($container.data('store')) {\r
 +                                              if ($.wcfIsset($container.data('store'))) {\r
 +                                                      $('#' + $container.data('store')).attr('value', $panel.attr('id'));\r
 +                                              }\r
 +                                      }\r
 +                              }\r
 +                      });\r
 +                      \r
 +                      // display active item on init\r
 +                      if ($(tabMenu).data('active')) {\r
 +                              $(tabMenu).find('.tabMenuContent').each(function(index, tabMenuItem) {\r
 +                                      if ($(tabMenuItem).attr('id') == $(tabMenu).data('active')) {\r
 +                                              $(tabMenu).wcfTabs('select', index);\r
 +                                      }\r
 +                              });\r
 +                      }\r
 +              });\r
 +      }\r
 +};\r
 +\r
 +/**\r
 + * Toggles options.\r
 + * \r
 + * @param     string          element\r
 + * @param     array           showItems\r
 + * @param     array           hideItems\r
 + */\r
 +WCF.ToggleOptions = function(element, showItems, hideItems) { this.init(element, showItems, hideItems); };\r
 +WCF.ToggleOptions.prototype = {\r
 +      /**\r
 +       * target item\r
 +       * \r
 +       * @var jQuery\r
 +       */\r
 +      _element: null,\r
 +      \r
 +      /**\r
 +       * list of items to be shown\r
 +       * \r
 +       * @var array\r
 +       */\r
 +      _showItems: [],\r
 +      \r
 +      /**\r
 +       * list of items to be hidden\r
 +       * \r
 +       * @var array\r
 +       */\r
 +      _hideItems: [],\r
 +      \r
 +      /**\r
 +       * Initializes option toggle.\r
 +       * \r
 +       * @param       string          element\r
 +       * @param       array           showItems\r
 +       * @param       array           hideItems\r
 +       */\r
 +      init: function(element, showItems, hideItems) {\r
 +              this._element = $('#' + element);\r
 +              this._showItems = showItems;\r
 +              this._hideItems = hideItems;\r
 +              \r
 +              // bind event\r
 +              this._element.click($.proxy(this._toggle, this));\r
 +              \r
 +              // execute toggle on init\r
 +              this._toggle();\r
 +      },\r
 +      \r
 +      /**\r
 +       * Toggles items.\r
 +       */\r
 +      _toggle: function() {\r
 +              if (!this._element.attr('checked')) return;\r
 +              \r
 +              for (var $i = 0, $length = this._showItems.length; $i < $length; $i++) {\r
 +                      var $item = this._showItems[$i];\r
 +                      \r
 +                      $('#' + $item).show();\r
 +              }\r
 +              \r
 +              for (var $i = 0, $length = this._hideItems.length; $i < $length; $i++) {\r
 +                      var $item = this._hideItems[$i];\r
 +                      \r
 +                      $('#' + $item).hide();\r
 +              }\r
 +      }\r
 +};\r
 +\r
 +/**\r
 + * Basic implementation for WCF dialogs.\r
 + */\r
 +$.widget('ui.wcfDialog', $.ui.dialog, {\r
 +      _init: function() {\r
 +              this.options.autoOpen = true;\r
 +              this.options.close = function(event, ui) {\r
 +                      $(this).parent('.ui-dialog').wcfDropOut('down', $.proxy(function() {\r
 +                              $(this).parent('.ui-dialog').empty().remove();\r
 +                      }, this));\r
 +              };\r
 +              this.options.height = 'auto';\r
 +              this.options.minHeight = 0;\r
 +              this.options.modal = true;\r
 +              this.options.width = 'auto';\r
 +              \r
 +              $.ui.dialog.prototype._init.apply(this, arguments);\r
 +      }\r
 +});\r
 +\r
 +/**\r
 + * Basic implementation for WCF dialogs loading content\r
 + * via AJAX before calling dialog itself.\r
 + */\r
 +$.widget('ui.wcfAJAXDialog', $.ui.dialog, {\r
 +      /**\r
 +       * Indicates wether callback was already executed\r
 +       * \r
 +       * @var boolean\r
 +       */\r
 +      _callbackExecuted: false,\r
 +      \r
 +      /**\r
 +       * Initializes AJAX-request to fetch content.\r
 +       */\r
 +      _init: function() {\r
 +              if (this.options.ajax) {\r
 +                      this._loadContent();\r
 +              }\r
 +              \r
 +              // force dialog to be placed centered\r
 +              this.options.position = {\r
 +                      my: 'center center',\r
 +                      at: 'center center'\r
 +              };\r
 +              \r
 +              // dialog should display a spinner-like image, thus immediately fire up dialog\r
 +              this.options.autoOpen = true;\r
 +              this.options.width = 'auto';\r
 +              this.options.minHeight = 80;\r
 +              \r
 +              // disable ability to move dialog\r
 +              this.options.resizable = false;\r
 +              this.options.draggable = false;\r
 +              \r
 +              this.options.modal = true;\r
 +              this.options.hide = {\r
 +                      effect: 'drop',\r
 +                      direction: 'down'\r
 +              };\r
 +              \r
 +              this.options.close = function(event, ui) {\r
 +                      // loading ajax content seems to block properly closing\r
 +                      $(this).parent('.ui-dialog').empty().remove();\r
 +              };\r
 +              \r
 +              if (this.options.preventClose) {\r
 +                      this.options.closeOnEscape = false;\r
 +              }\r
 +              \r
 +              $.ui.dialog.prototype._init.apply(this, arguments);\r
 +              \r
 +              // remove complete node instead of removing node-by-node\r
 +              if (this.options.hideTitle && this.options.preventClose) {\r
 +                      this.element.parent('.ui-dialog').find('div.ui-dialog-titlebar').empty().remove();\r
 +              }\r
 +              else {\r
 +                      if (this.options.hideTitle) {\r
 +                              // remove title element\r
 +                              $('#ui-dialog-title-' + this.element.attr('id')).empty().remove();\r
 +                      }\r
 +                      \r
 +                      if (this.options.preventClose) {\r
 +                              // remove close-button\r
 +                              this.element.parent('.ui-dialog').find('a.ui-dialog-titlebar-close').empty().remove();\r
 +                      }\r
 +              }\r
 +      },\r
 +      \r
 +      /**\r
 +       * Loads content via AJAX.\r
 +       * \r
 +       * @todo        Enforce JSON\r
 +       */\r
 +      _loadContent: function() {\r
 +              var $type = 'GET';\r
 +              if (this.options.ajax.type) {\r
 +                      $type = this.options.ajax.type;\r
 +                      \r
 +                      if (this.options.ajax.type != 'GET' && this.options.ajax.type != 'POST') {\r
 +                              $type = 'GET';\r
 +                      }\r
 +              }\r
 +              \r
 +              var $data = this.options.ajax.data || {};\r
 +              \r
 +              $.ajax({\r
 +                      url: this.options.ajax.url,\r
 +                      context: this,\r
 +                      dataType: 'json',\r
 +                      type: $type,\r
 +                      data: $data,\r
 +                      success: $.proxy(this._createDialog, this),\r
 +                      error: function(transport) {\r
 +                              alert(transport.responseText);\r
 +                      }\r
 +              });\r
 +      },\r
 +      \r
 +      /**\r
 +       * Inserts content.\r
 +       * \r
 +       * @param       string          data\r
 +       */\r
 +      _createDialog: function(data) {\r
 +              data.ignoreTemplate = true;\r
 +              this.element.data('responseData', data);\r
 +              \r
 +              this.element.wcfGrow({\r
 +                      content: data.template,\r
 +                      parent: this.element.parent('.ui-dialog')\r
 +              }, {\r
 +                      duration: 600,\r
 +                      complete: $.proxy(function(data) {\r
 +                              this.element.css({\r
 +                                      height: 'auto'\r
 +                              });\r
 +                              \r
 +                              // prevent double execution due to two complete-calls (two times animate)\r
 +                              if (this._callbackExecuted) {\r
 +                                      return;\r
 +                              }\r
 +                              \r
 +                              this._callbackExecuted = true;\r
 +                              \r
 +                              this.element.removeClass('overlayLoading');\r
 +                              this.element.html(this.element.data('responseData').template);\r
 +                              \r
 +                              if (this.options.ajax.success) {\r
 +                                      this.options.ajax.success();\r
 +                              }\r
 +                      }, this)\r
 +              });\r
 +      },\r
 +      \r
 +      /**\r
 +       * Redraws dialog, should be executed everytime content is changed.\r
 +       */\r
 +      redraw: function() {\r
 +              var $dimensions = this.element.getDimensions();\r
 +              \r
 +              if ($dimensions.height > 200) {\r
 +                      this.element.wcfGrow({\r
 +                              content: this.element.html(),\r
 +                              parent: this.element.parent('.ui-dialog')\r
 +                      }, {\r
 +                              duration: 600,\r
 +                              complete: function() {\r
 +                                      $(this).css({ height: 'auto' });\r
 +                              }\r
 +                      });\r
 +              }\r
 +      }\r
 +});\r
 +\r
 +/**\r
 + * Workaround for ids containing a dot ".", until jQuery UI devs learn\r
 + * to properly escape ids ... (it took 18 months until they finally\r
 + * fixed it!)\r
 + * \r
 + * @see       http://bugs.jqueryui.com/ticket/4681\r
 + */\r
 +$.widget('ui.wcfTabs', $.ui.tabs, {\r
 +      _init: function() {\r
 +              $.ui.dialog.prototype._init.apply(this, arguments);\r
 +      },\r
 +      \r
 +      _sanitizeSelector: function(hash) {\r
 +              return hash.replace(/([:\.])/g, '\\$1');\r
 +      }\r
 +});\r
 +\r
++/**\r
++ * jQuery widget implementation of the wcf pagination.\r
++ */\r
++$.widget('ui.wcfPages', {\r
++      SHOW_LINKS: 11,\r
++      SHOW_SUB_LINKS: 20,\r
++      \r
++      options: {\r
++              // vars\r
++              activePage: 1,\r
++              maxPage: 1,\r
++              \r
++              // icons\r
++              previousIcon: RELATIVE_WCF_DIR + 'icon/previousS.png',\r
++              previousDisabledIcon: RELATIVE_WCF_DIR + 'icon/previousDisabledS.png',\r
++              arrowDownIcon: RELATIVE_WCF_DIR + 'icon/arrowDown.png',\r
++              nextIcon: RELATIVE_WCF_DIR + 'icon/nextS.png',\r
++              nextDisabledIcon: RELATIVE_WCF_DIR + 'icon/nextDisabledS.png',\r
++              \r
++              // language\r
++              // we use options here instead of language variables, because the paginator is not only usable with pages\r
++              nextPage: null,\r
++              previousPage: null,\r
++      },\r
++      \r
++      /**\r
++       * Creates the pages widget.\r
++       */\r
++      _create: function() {\r
++              if (this.options.nextPage === null) this.options.nextPage = WCF.Language.get('wcf.global.page.next');\r
++              if (this.options.previousPage === null) this.options.previousPage = WCF.Language.get('wcf.global.page.previous');\r
++              \r
++              this.element.addClass('pageNavigation');\r
++              \r
++              this._render();\r
++      },\r
++      \r
++      /**\r
++       * Destroys the pages widget.\r
++       */\r
++      destroy: function() {\r
++              $.Widget.prototype.destroy.apply(this, arguments);\r
++              \r
++              this.element.children().remove();\r
++      },\r
++      \r
++      /**\r
++       * Renders th pages widget.\r
++       */\r
++      _render: function() {\r
++              // only render if we have more than 1 page\r
++              if (!this.options.disabled && this.options.maxPage > 1) {\r
++                      // make sure pagination is visible\r
++                      if (this.element.hasClass('hidden')) {\r
++                              this.element.removeClass('hidden');\r
++                      }\r
++                      this.element.show();\r
++                      \r
++                      this.element.children().remove();\r
++                      \r
++                      var $pageList = $('<ul></ul>');\r
++                      this.element.append($pageList);\r
++                      \r
++                      var $previousElement = $('<li></li>');\r
++                      $pageList.append($previousElement);\r
++                      \r
++                      if (this.options.activePage > 1) {\r
++                              var $previousLink = $('<a' + ((this.options.previousPage != null) ? (' title="' + this.options.previousPage + '"') : ('')) + '></a>');\r
++                              $previousElement.append($previousLink);\r
++                              this._bindSwitchPage($previousLink, this.options.activePage - 1);\r
++                              \r
++                              var $previousImage = $('<img src="' + this.options.previousIcon + '" alt="" />');\r
++                              $previousLink.append($previousImage);\r
++                      }\r
++                      else {\r
++                              var $previousImage = $('<img src="' + this.options.previousDisabledIcon + '" alt="" />');\r
++                              $previousElement.append($previousImage);\r
++                      }\r
++                      $previousElement.addClass('skip');\r
++                      \r
++                      // add first page\r
++                      $pageList.append(this._renderLink(1));\r
++                      \r
++                      // calculate page links\r
++                      var $maxLinks = this.SHOW_LINKS - 4;\r
++                      var $linksBefore = this.options.activePage - 2;\r
++                      if ($linksBefore < 0) $linksBefore = 0;\r
++                      var $linksAfter = this.options.maxPage - (this.options.activePage + 1);\r
++                      if ($linksAfter < 0) $linksAfter = 0;\r
++                      if (this.options.activePage > 1 && this.options.activePage < this.options.maxPage) $maxLinks--;\r
++                      \r
++                      var $half = $maxLinks / 2;\r
++                      var $left = this.options.activePage;\r
++                      var $right = this.options.activePage;\r
++                      if ($left < 1) $left = 1;\r
++                      if ($right < 1) $right = 1;\r
++                      if ($right > this.options.maxPage - 1) $right = this.options.maxPage - 1;\r
++                      \r
++                      if ($linksBefore >= $half) {\r
++                              $left -= $half;\r
++                      }\r
++                      else {\r
++                              $left -= $linksBefore;\r
++                              $right += $half - $linksBefore;\r
++                      }\r
++                      \r
++                      if ($linksAfter >= $half) {\r
++                              $right += $half;\r
++                      }\r
++                      else {\r
++                              $right += $linksAfter;\r
++                              $left -= $half - $linksAfter;\r
++                      }\r
++                      \r
++                      $right = Math.ceil($right);\r
++                      $left = Math.ceil($left);\r
++                      if ($left < 1) $left = 1;\r
++                      if ($right > this.options.maxPage) $right = this.options.maxPage;\r
++                      \r
++                      // left ... links\r
++                      if ($left > 1) {\r
++                              if ($left - 1 < 2) {\r
++                                      $pageList.append(this._renderLink(2));\r
++                              }\r
++                              else {\r
++                                      var $leftChildren = $('<li class="children"></li>');\r
++                                      $pageList.append($leftChildren);\r
++                                      \r
++                                      var $leftChildrenLink = $('<a>&hellip;</a>');\r
++                                      $leftChildren.append($leftChildrenLink);\r
++                                      $leftChildrenLink.click($.proxy(this._startInput, this));\r
++                                      \r
++                                      var $leftChildrenImage = $('<img src="' + this.options.arrowDownIcon + '" alt="" />');\r
++                                      $leftChildrenLink.append($leftChildrenImage);\r
++                                      \r
++                                      var $leftChildrenInput = $('<input type="text" class="inputText" name="pageNo" />');\r
++                                      $leftChildren.append($leftChildrenInput);\r
++                                      $leftChildrenInput.keydown($.proxy(this._handleInput, this));\r
++                                      $leftChildrenInput.keyup($.proxy(this._handleInput, this));\r
++                                      $leftChildrenInput.blur($.proxy(this._stopInput, this));\r
++                                      \r
++                                      var $leftChildrenContainer = $('<div></div>');\r
++                                      $leftChildren.append($leftChildrenContainer);\r
++                                      \r
++                                      var $leftChildrenList = $('<ul></u>');\r
++                                      $leftChildrenContainer.append($leftChildrenList);\r
++                                      \r
++                                      // render sublinks\r
++                                      var $k = 0;\r
++                                      var $step = Math.ceil(($left - 2) / this.SHOW_SUB_LINKS);\r
++                                      for (var $i = 2; $i <= $left; $i += $step) {\r
++                                              $leftChildrenList.append(this._renderLink($i, ($k != 0 && $k % 4 == 0)));\r
++                                              $k++;\r
++                                      }\r
++                              }\r
++                      }\r
++                      \r
++                      // visible links\r
++                      for (var $i = $left + 1; $i < $right; $i++) {\r
++                              $pageList.append(this._renderLink($i));\r
++                      }\r
++                      \r
++                      // right ... links\r
++                      if ($right < this.options.maxPage) {\r
++                              if (this.options.maxPage - $right < 2) {\r
++                                      $pageList.append(this._renderLink(this.options.maxPage - 1));\r
++                              }\r
++                              else {\r
++                                      var $rightChildren = $('<li class="children"></li>');\r
++                                      $pageList.append($rightChildren);\r
++                                      \r
++                                      var $rightChildrenLink = $('<a>&hellip;</a>');\r
++                                      $rightChildren.append($rightChildrenLink);\r
++                                      $rightChildrenLink.click($.proxy(this._startInput, this));\r
++                                      \r
++                                      var $rightChildrenImage = $('<img src="' + this.options.arrowDownIcon + '" alt="" />');\r
++                                      $rightChildrenLink.append($rightChildrenImage);\r
++                                      \r
++                                      var $rightChildrenInput = $('<input type="text" class="inputText" name="pageNo" />');\r
++                                      $rightChildren.append($rightChildrenInput);\r
++                                      $rightChildrenInput.keydown($.proxy(this._handleInput, this));\r
++                                      $rightChildrenInput.keyup($.proxy(this._handleInput, this));\r
++                                      $rightChildrenInput.blur($.proxy(this._stopInput, this));\r
++                                      \r
++                                      var $rightChildrenContainer = $('<div></div>');\r
++                                      $rightChildren.append($rightChildrenContainer);\r
++                                      \r
++                                      var $rightChildrenList = $('<ul></ul>');\r
++                                      $rightChildrenContainer.append($rightChildrenList);\r
++                                      \r
++                                      // render sublinks\r
++                                      var $k = 0;\r
++                                      var $step = Math.ceil((this.options.maxPage - $right) / this.SHOW_SUB_LINKS);\r
++                                      for (var $i = $right; $i < this.options.maxPage; $i += $step) {\r
++                                              $rightChildrenList.append(this._renderLink($i, ($k != 0 && $k % 4 == 0)));\r
++                                              $k++;\r
++                                      }\r
++                              }\r
++                      }\r
++                      \r
++                      // add last page\r
++                      $pageList.append(this._renderLink(this.options.maxPage));\r
++                      \r
++                      // add next button\r
++                      var $nextElement = $('<li></li>');\r
++                      $pageList.append($nextElement);\r
++                      \r
++                      if (this.options.activePage < this.options.maxPage) {\r
++                              var $nextLink = $('<a title="' + ((this.options.nextPage != null) ? (' title="' + this.options.nextPage + '"') : ('')) + '"></a>');\r
++                              $nextElement.append($nextLink);\r
++                              this._bindSwitchPage($nextLink, this.options.activePage + 1);\r
++                              \r
++                              var $nextImage = $('<img src="' + this.options.nextIcon + '" alt="" />');\r
++                              $nextLink.append($nextImage);\r
++                      }\r
++                      else {\r
++                              var $nextImage = $('<img src="' + this.options.nextDisabledIcon + '" alt="" />');\r
++                              $nextElement.append($nextImage);\r
++                      }\r
++                      $nextElement.addClass('skip');\r
++              }\r
++              else {\r
++                      // otherwise hide the paginator if not already hidden\r
++                      this.element.hide();\r
++              }\r
++      },\r
++      \r
++      /**\r
++       * Renders a page link\r
++       * \r
++       * @parameter   integer         page\r
++       * \r
++       * @return              $(element)\r
++       */\r
++      _renderLink: function(page, lineBreak) {\r
++              var $pageElement = $('<li></li>');\r
++              if (lineBreak != undefined && lineBreak) {\r
++                      $pageElement.addClass('break');\r
++              }\r
++              if (page != this.options.activePage) {\r
++                      var $pageLink = $('<a>' + WCF.String.addThousandsSeparator(page) + '</a>'); \r
++                      $pageElement.append($pageLink);\r
++                      this._bindSwitchPage($pageLink, page);\r
++              }\r
++              else {\r
++                      $pageElement.addClass('active');\r
++                      var $pageSubElement = $('<span>' + WCF.String.addThousandsSeparator(page) + '</span>');\r
++                      $pageElement.append($pageSubElement);\r
++              }\r
++              \r
++              return $pageElement;\r
++      },\r
++      \r
++      /**\r
++       * Binds the 'click'-event for the page switching to the given element.\r
++       * \r
++       * @parameter   $(element)      element\r
++       * @paremeter   integer         page\r
++       */\r
++      _bindSwitchPage: function(element, page) {\r
++              var $self = this;\r
++              element.click(function() {\r
++                      $self.switchPage(page);\r
++              });\r
++      },\r
++      \r
++      /**\r
++       * Switches to the given page\r
++       * \r
++       * @parameter   Event           event\r
++       * @parameter   integer         page\r
++       */\r
++      switchPage: function(page) {\r
++              this._setOption('activePage', page);\r
++      },\r
++      \r
++      /**\r
++       * Sets the given option to the given value.\r
++       * See the jQuery UI widget documentation for more.\r
++       */\r
++      _setOption: function(key, value) {\r
++              if (key == 'activePage') {\r
++                      if (value != this.options[key] && value > 0 && value <= this.options.maxPage) {\r
++                              // you can prevent the page switching by returning false or by event.preventDefault()\r
++                              // in a shouldSwitch-callback. e.g. if an AJAX request is already running.\r
++                              var $result = this._trigger('shouldSwitch', undefined, {\r
++                                      nextPage: value,\r
++                              });\r
++                              \r
++                              if ($result) {\r
++                                      this.options[key] = value;\r
++                                      this._render();\r
++                                      this._trigger('switched', undefined, {\r
++                                              activePage: value,\r
++                                      });\r
++                              }\r
++                              else {\r
++                                      this._trigger('notSwitched', undefined, {\r
++                                              activePage: value,\r
++                                      });\r
++                              }\r
++                      }\r
++              }\r
++              else {\r
++                      this.options[key] = value;\r
++                      \r
++                      if (key == 'disabled') {\r
++                              if (value) {\r
++                                      this.element.children().remove();\r
++                              }\r
++                              else {\r
++                                      this._render()\r
++                              }\r
++                      }\r
++                      else if (key == 'maxPage') {\r
++                              this._render();\r
++                      }\r
++              }\r
++              \r
++              return this;\r
++      },\r
++      \r
++      /**\r
++       * Start input of pagenumber\r
++       * \r
++       * @parameter   Event           event\r
++       */\r
++      _startInput: function(event) {\r
++              // hide a-tag\r
++              var $childLink = $(event.currentTarget);\r
++              if (!$childLink.is('a')) $childLink = $childLink.parent('a');\r
++              \r
++              $childLink.hide();\r
++              \r
++              // show input-tag\r
++              var $childInput = $childLink.parent('li').children('input')\r
++                      .css('display', 'block')\r
++                      .val('');\r
++              \r
++              $childInput.focus();\r
++      },\r
++      \r
++      /**\r
++       * Stops input of pagenumber\r
++       * \r
++       * @parameter   Event           event\r
++       */\r
++      _stopInput: function(event) {\r
++              // hide input-tag\r
++              var $childInput = $(event.currentTarget);\r
++              $childInput.css('display', 'none');\r
++              \r
++              // show a-tag\r
++              var $childContainer = $childInput.parent('li')\r
++              if ($childContainer != undefined && $childContainer != null) {\r
++                      $childContainer.children('a').show();\r
++              }\r
++      },\r
++      \r
++      /**\r
++       * Handles input of pagenumber\r
++       * \r
++       * @parameter   Event           event\r
++       */\r
++      _handleInput: function(event) {\r
++              var $ie7 = ($.browser.msie && $.browser.version == '7.0');\r
++              if (event.type != 'keyup' || $ie7) {\r
++                      if (!$ie7 || ((event.which == 13 || event.which == 27) && event.type == 'keyup')) {\r
++                              if (event.which == 13) {\r
++                                      this.switchPage(parseInt($(event.currentTarget).val()));\r
++                              }\r
++                              \r
++                              if (event.which == 13 || event.which == 27) {\r
++                                      this._stopInput(event);\r
++                                      event.stopPropagation();\r
++                              }\r
++                      }\r
++              }\r
++      }\r
++});\r
++\r
 +/**\r
 + * Encapsulate eval() within an own function to prevent problems\r
 + * with optimizing and minifiny JS.\r
 + * \r
 + * @param     mixed           expression\r
 + * @returns   mixed\r
 + */\r
 +function wcfEval(expression) {\r
 +      return eval(expression);\r
- }
++}\r