Basic support for tiny JS mode
authorAlexander Ebert <ebert@woltlab.com>
Sun, 7 May 2017 14:08:21 +0000 (16:08 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Sun, 7 May 2017 14:08:21 +0000 (16:08 +0200)
12 files changed:
com.woltlab.wcf/templates/headIncludeJavaScript.tpl
wcfsetup/install/files/acp/templates/header.tpl
wcfsetup/install/files/js/WCF.ACL.js
wcfsetup/install/files/js/WCF.Attachment.js
wcfsetup/install/files/js/WCF.ColorPicker.js
wcfsetup/install/files/js/WCF.Label.js
wcfsetup/install/files/js/WCF.Location.js
wcfsetup/install/files/js/WCF.Message.js
wcfsetup/install/files/js/WCF.Moderation.js
wcfsetup/install/files/js/WCF.Poll.js
wcfsetup/install/files/js/WCF.User.js
wcfsetup/install/files/js/WCF.js

index 7480ccced1bb454e2e62be7cf6282645c80350e1..519d8acf39e9e836916cc7bebb50fcc3732f3d58 100644 (file)
        var TIME_NOW = {@TIME_NOW};
        var LAST_UPDATE_TIME = {@LAST_UPDATE_TIME};
        var URL_LEGACY_MODE = false;
+       
+       {if ENABLE_DEBUG_MODE}
+               {* This constant is a compiler option, it does not exist in production. *}
+               var COMPILER_TARGET_DEFAULT = {if $__wcf->user->userID}true{else}false{/if};
+       {/if}
 </script>
 
 {js application='wcf' lib='polyfill' file='promise' core='true'}
index 32aefd7bab11742a4cc28fba39145b5635d9a68f..385bfda8b15c4ca413f3173e4d3e22ae9aec961e 100644 (file)
                var TIME_NOW = {@TIME_NOW};
                var LAST_UPDATE_TIME = {@LAST_UPDATE_TIME};
                var URL_LEGACY_MODE = false;
+               
+               {if ENABLE_DEBUG_MODE}
+                       {* This constant is a compiler option, it does not exist in production. *}
+                       var COMPILER_TARGET_DEFAULT = {if $__wcf->user->userID}true{else}false{/if};
+               {/if}
        </script>
        
        {js application='wcf' file='require' bundle='WoltLabSuite.Core' core='true'}
index 09e4e34d0791a9e9ccd0e8db6030456a37c9fa3c..4ba7c2f3008daba8d2545df652c035ae0f24beb0 100644 (file)
  */
 WCF.ACL = { };
 
-/**
- * ACL support for WCF
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- */
-WCF.ACL.List = Class.extend({
-       /**
-        * name of the category the acl options belong to
-        * @var string
-        */
-       _categoryName: '',
-       
-       /**
-        * ACL container
-        * @var jQuery
-        */
-       _container: null,
-       
-       /**
-        * list of ACL container elements
-        * @var object
-        */
-       _containerElements: { },
-       
-       /**
-        * object id
-        * @var integer
-        */
-       _objectID: 0,
-       
-       /**
-        * object type id
-        * @var integer
-        */
-       _objectTypeID: null,
-       
-       /**
-        * list of available ACL options
-        * @var object
-        */
-       _options: { },
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * user search handler
-        * @var WCF.Search.User
-        */
-       _search: null,
-       
-       /**
-        * list of ACL settings
-        * @var object
-        */
-       _values: {
-               group: { },
-               user: { }
-       },
-       
-       /**
-        * Initializes the ACL configuration.
-        * 
-        * @param       string          containerSelector
-        * @param       integer         objectTypeID
-        * @param       string          categoryName
-        * @param       integer         objectID
-        * @param       boolean         includeUserGroups
-        */
-       init: function(containerSelector, objectTypeID, categoryName, objectID, includeUserGroups, initialPermissions) {
-               this._objectID = objectID || 0;
-               this._objectTypeID = objectTypeID;
-               this._categoryName = categoryName;
-               if (includeUserGroups === undefined) {
-                       includeUserGroups = true;
-               }
-               this._values = {
-                       group: { },
-                       user: { }
-               };
-               
-               this._proxy = new WCF.Action.Proxy({
-                       showLoadingOverlay: false,
-                       success: $.proxy(this._success, this)
-               });
-               
-               // bind hidden container
-               this._container = $(containerSelector).hide().addClass('aclContainer');
-               
-               // insert container elements
-               var $elementContainer = this._container.children('dd');
-               var $aclList = $('<ul class="aclList containerList" />').appendTo($elementContainer);
-               var $searchInput = $('<input type="text" class="long" placeholder="' + WCF.Language.get('wcf.acl.search.' + (!includeUserGroups ? 'user.' : '') + 'description') + '" />').appendTo($elementContainer);
-               var $permissionList = $('<ul class="aclPermissionList containerList" />').hide().appendTo($elementContainer);
-               elData($permissionList[0], 'grant', WCF.Language.get('wcf.acl.option.grant'));
-               elData($permissionList[0], 'deny', WCF.Language.get('wcf.acl.option.deny'));
-               
-               // set elements
-               this._containerElements = {
-                       aclList: $aclList,
-                       denyAll: null,
-                       grantAll: null,
-                       permissionList: $permissionList,
-                       searchInput: $searchInput
-               };
-               
-               // prepare search input
-               this._search = new WCF.Search.User($searchInput, $.proxy(this.addObject, this), includeUserGroups);
-               
-               // bind event listener for submit
-               var $form = this._container.parents('form:eq(0)');
-               $form.submit($.proxy(this.submit, this));
-               
-               // reset ACL on click
-               var $resetButton = $form.find('input[type=reset]:eq(0)');
-               if ($resetButton.length) {
-                       $resetButton.click($.proxy(this._reset, this));
-               }
-               
-               if (initialPermissions) {
-                       this._success(initialPermissions);
-               }
-               else {
-                       this._loadACL();
-               }
-       },
-       
-       /**
-        * Restores the original ACL state.
-        */
-       _reset: function() {
-               // reset stored values
-               this._values = {
-                       group: { },
-                       user: { }
-               };
-               
-               // remove entries
-               this._containerElements.aclList.empty();
-               this._containerElements.searchInput.val('');
-               
-               // deselect all input elements
-               this._containerElements.permissionList.hide().find('input[type=checkbox]').prop('checked', false);
-       },
-       
+if (COMPILER_TARGET_DEFAULT) {
        /**
-        * Loads current ACL configuration.
+        * ACL support for WCF
+        *
+        * @author        Alexander Ebert
+        * @copyright        2001-2017 WoltLab GmbH
+        * @license        GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
         */
-       _loadACL: function() {
-               this._proxy.setOption('data', {
-                       actionName: 'loadAll',
-                       className: 'wcf\\data\\acl\\option\\ACLOptionAction',
-                       parameters: {
-                               categoryName: this._categoryName,
-                               objectID: this._objectID,
-                               objectTypeID: this._objectTypeID
+       WCF.ACL.List = Class.extend({
+               /**
+                * name of the category the acl options belong to
+                * @var        string
+                */
+               _categoryName: '',
+               
+               /**
+                * ACL container
+                * @var        jQuery
+                */
+               _container: null,
+               
+               /**
+                * list of ACL container elements
+                * @var        object
+                */
+               _containerElements: {},
+               
+               /**
+                * object id
+                * @var        integer
+                */
+               _objectID: 0,
+               
+               /**
+                * object type id
+                * @var        integer
+                */
+               _objectTypeID: null,
+               
+               /**
+                * list of available ACL options
+                * @var        object
+                */
+               _options: {},
+               
+               /**
+                * action proxy
+                * @var        WCF.Action.Proxy
+                */
+               _proxy: null,
+               
+               /**
+                * user search handler
+                * @var        WCF.Search.User
+                */
+               _search: null,
+               
+               /**
+                * list of ACL settings
+                * @var        object
+                */
+               _values: {
+                       group: {},
+                       user: {}
+               },
+               
+               /**
+                * Initializes the ACL configuration.
+                *
+                * @param        string                containerSelector
+                * @param        integer                objectTypeID
+                * @param        string                categoryName
+                * @param        integer                objectID
+                * @param        boolean                includeUserGroups
+                */
+               init: function (containerSelector, objectTypeID, categoryName, objectID, includeUserGroups, initialPermissions) {
+                       this._objectID = objectID || 0;
+                       this._objectTypeID = objectTypeID;
+                       this._categoryName = categoryName;
+                       if (includeUserGroups === undefined) {
+                               includeUserGroups = true;
                        }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Adds a new object to acl list.
-        * 
-        * @param       object          data
-        */
-       addObject: function(data) {
-               var $listItem = this._createListItem(data.objectID, data.label, data.type);
-               
-               // toggle element
-               this._savePermissions();
-               this._containerElements.aclList.children('li').removeClass('active');
-               $listItem.addClass('active');
-               
-               this._search.addExcludedSearchValue(data.label);
-               
-               // uncheck all option values
-               this._containerElements.permissionList.find('input[type=checkbox]').prop('checked', false);
-               
-               // clear search input
-               this._containerElements.searchInput.val('');
-               
-               // show permissions
-               this._containerElements.permissionList.show();
-               
-               WCF.DOMNodeInsertedHandler.execute();
-       },
-       
-       /**
-        * Creates a list item with the given data and returns it.
-        * 
-        * @param       integer         objectID
-        * @param       string          label
-        * @param       string          type
-        * @return      jQuery
-        */
-       _createListItem: function(objectID, label, type) {
-               var $listItem = $('<li><span class="icon icon16 fa-user' + (type === 'group' ? 's' : '') + '" /> <span class="aclLabel">' + label + '</span></li>').appendTo(this._containerElements.aclList);
-               $listItem.data('objectID', objectID).data('type', type).data('label', label).click($.proxy(this._click, this));
-               $('<span class="icon icon16 fa-times jsTooltip pointer" title="' + WCF.Language.get('wcf.global.button.delete') + '" />').click($.proxy(this._removeItem, this)).appendTo($listItem);
-               
-               return $listItem;
-       },
-       
-       /**
-        * Removes an item from list.
-        * 
-        * @param       object          event
-        */
-       _removeItem: function(event) {
-               var $listItem = $(event.currentTarget).parent();
-               var $type = $listItem.data('type');
-               var $objectID = $listItem.data('objectID');
-               
-               this._search.removeExcludedSearchValue($listItem.data('label'));
-               $listItem.remove();
-               
-               // remove stored data
-               if (this._values[$type][$objectID]) {
-                       delete this._values[$type][$objectID];
-               }
-               
-               // try to select something else
-               this._selectFirstEntry();
-       },
-       
-       /**
-        * Selects the first available entry.
-        */
-       _selectFirstEntry: function() {
-               var $listItem = this._containerElements.aclList.children('li:eq(0)');
-               if ($listItem.length) {
-                       this._select($listItem, false);
-               }
-               else {
-                       this._reset();
-               }
-       },
-       
-       /**
-        * Parses current ACL configuration.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               if (!$.getLength(data.returnValues.options)) {
-                       return;
-               }
-               
-               // prepare options
-               var $count = 0;
-               var $structure = { };
-               for (var $optionID in data.returnValues.options) {
-                       var $option = data.returnValues.options[$optionID];
+                       this._values = {
+                               group: {},
+                               user: {}
+                       };
                        
-                       var $listItem = $('<li><span>' + $option.label + '</span></li>').data('optionID', $optionID).data('optionName', $option.optionName);
-                       var $grantPermission = $('<input type="checkbox" id="grant' + $optionID + '" />').appendTo($listItem).wrap('<label for="grant' + $optionID + '" class="jsTooltip" title="' + WCF.Language.get('wcf.acl.option.grant') + '" />');
-                       var $denyPermission = $('<input type="checkbox" id="deny' + $optionID + '" />').appendTo($listItem).wrap('<label for="deny' + $optionID + '" class="jsTooltip" title="' + WCF.Language.get('wcf.acl.option.deny') + '" />');
+                       this._proxy = new WCF.Action.Proxy({
+                               showLoadingOverlay: false,
+                               success: $.proxy(this._success, this)
+                       });
                        
-                       $grantPermission.data('type', 'grant').data('optionID', $optionID).change($.proxy(this._change, this));
-                       $denyPermission.data('type', 'deny').data('optionID', $optionID).change($.proxy(this._change, this));
+                       // bind hidden container
+                       this._container = $(containerSelector).hide().addClass('aclContainer');
                        
-                       if (!$structure[$option.categoryName]) {
-                               $structure[$option.categoryName] = [ ];
+                       // insert container elements
+                       var $elementContainer = this._container.children('dd');
+                       var $aclList = $('<ul class="aclList containerList" />').appendTo($elementContainer);
+                       var $searchInput = $('<input type="text" class="long" placeholder="' + WCF.Language.get('wcf.acl.search.' + (!includeUserGroups ? 'user.' : '') + 'description') + '" />').appendTo($elementContainer);
+                       var $permissionList = $('<ul class="aclPermissionList containerList" />').hide().appendTo($elementContainer);
+                       elData($permissionList[0], 'grant', WCF.Language.get('wcf.acl.option.grant'));
+                       elData($permissionList[0], 'deny', WCF.Language.get('wcf.acl.option.deny'));
+                       
+                       // set elements
+                       this._containerElements = {
+                               aclList: $aclList,
+                               denyAll: null,
+                               grantAll: null,
+                               permissionList: $permissionList,
+                               searchInput: $searchInput
+                       };
+                       
+                       // prepare search input
+                       this._search = new WCF.Search.User($searchInput, $.proxy(this.addObject, this), includeUserGroups);
+                       
+                       // bind event listener for submit
+                       var $form = this._container.parents('form:eq(0)');
+                       $form.submit($.proxy(this.submit, this));
+                       
+                       // reset ACL on click
+                       var $resetButton = $form.find('input[type=reset]:eq(0)');
+                       if ($resetButton.length) {
+                               $resetButton.click($.proxy(this._reset, this));
                        }
                        
-                       if ($option.categoryName === '') {
-                               $listItem.appendTo(this._containerElements.permissionList);
+                       if (initialPermissions) {
+                               this._success(initialPermissions);
                        }
                        else {
-                               $structure[$option.categoryName].push($listItem);
+                               this._loadACL();
                        }
+               },
+               
+               /**
+                * Restores the original ACL state.
+                */
+               _reset: function () {
+                       // reset stored values
+                       this._values = {
+                               group: {},
+                               user: {}
+                       };
                        
-                       $count++;
-               }
-               
-               // add a "full access" permission if there are more than one option
-               if ($count > 1) {
-                       var $listItem = $('<li class="aclFullAccess"><span>' + WCF.Language.get('wcf.acl.option.fullAccess') + '</span></li>').prependTo(this._containerElements.permissionList);
-                       this._containerElements.grantAll = $('<input type="checkbox" id="grantAll_' + this._container.attr('id') + '" />').appendTo($listItem).wrap('<label class="jsTooltip" title="' + WCF.Language.get('wcf.acl.option.grant') + '" />');
-                       this._containerElements.denyAll = $('<input type="checkbox" id="denyAll_' + this._container.attr('id') + '" />').appendTo($listItem).wrap('<label class="jsTooltip" title="' + WCF.Language.get('wcf.acl.option.deny') + '" />');
-                       
-                       // bind events
-                       this._containerElements.grantAll.data('type', 'grant').change($.proxy(this._changeAll, this));
-                       this._containerElements.denyAll.data('type', 'deny').change($.proxy(this._changeAll, this));
-               }
-               
-               if ($.getLength($structure)) {
-                       for (var $categoryName in $structure) {
-                               var $listItems = $structure[$categoryName];
+                       // remove entries
+                       this._containerElements.aclList.empty();
+                       this._containerElements.searchInput.val('');
+                       
+                       // deselect all input elements
+                       this._containerElements.permissionList.hide().find('input[type=checkbox]').prop('checked', false);
+               },
+               
+               /**
+                * Loads current ACL configuration.
+                */
+               _loadACL: function () {
+                       this._proxy.setOption('data', {
+                               actionName: 'loadAll',
+                               className: 'wcf\\data\\acl\\option\\ACLOptionAction',
+                               parameters: {
+                                       categoryName: this._categoryName,
+                                       objectID: this._objectID,
+                                       objectTypeID: this._objectTypeID
+                               }
+                       });
+                       this._proxy.sendRequest();
+               },
+               
+               /**
+                * Adds a new object to acl list.
+                *
+                * @param        object                data
+                */
+               addObject: function (data) {
+                       var $listItem = this._createListItem(data.objectID, data.label, data.type);
+                       
+                       // toggle element
+                       this._savePermissions();
+                       this._containerElements.aclList.children('li').removeClass('active');
+                       $listItem.addClass('active');
+                       
+                       this._search.addExcludedSearchValue(data.label);
+                       
+                       // uncheck all option values
+                       this._containerElements.permissionList.find('input[type=checkbox]').prop('checked', false);
+                       
+                       // clear search input
+                       this._containerElements.searchInput.val('');
+                       
+                       // show permissions
+                       this._containerElements.permissionList.show();
+                       
+                       WCF.DOMNodeInsertedHandler.execute();
+               },
+               
+               /**
+                * Creates a list item with the given data and returns it.
+                *
+                * @param        integer                objectID
+                * @param        string                label
+                * @param        string                type
+                * @return        jQuery
+                */
+               _createListItem: function (objectID, label, type) {
+                       var $listItem = $('<li><span class="icon icon16 fa-user' + (type === 'group' ? 's' : '') + '" /> <span class="aclLabel">' + label + '</span></li>').appendTo(this._containerElements.aclList);
+                       $listItem.data('objectID', objectID).data('type', type).data('label', label).click($.proxy(this._click, this));
+                       $('<span class="icon icon16 fa-times jsTooltip pointer" title="' + WCF.Language.get('wcf.global.button.delete') + '" />').click($.proxy(this._removeItem, this)).appendTo($listItem);
+                       
+                       return $listItem;
+               },
+               
+               /**
+                * Removes an item from list.
+                *
+                * @param        object                event
+                */
+               _removeItem: function (event) {
+                       var $listItem = $(event.currentTarget).parent();
+                       var $type = $listItem.data('type');
+                       var $objectID = $listItem.data('objectID');
+                       
+                       this._search.removeExcludedSearchValue($listItem.data('label'));
+                       $listItem.remove();
+                       
+                       // remove stored data
+                       if (this._values[$type][$objectID]) {
+                               delete this._values[$type][$objectID];
+                       }
+                       
+                       // try to select something else
+                       this._selectFirstEntry();
+               },
+               
+               /**
+                * Selects the first available entry.
+                */
+               _selectFirstEntry: function () {
+                       var $listItem = this._containerElements.aclList.children('li:eq(0)');
+                       if ($listItem.length) {
+                               this._select($listItem, false);
+                       }
+                       else {
+                               this._reset();
+                       }
+               },
+               
+               /**
+                * Parses current ACL configuration.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        jQuery                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       if (!$.getLength(data.returnValues.options)) {
+                               return;
+                       }
+                       
+                       // prepare options
+                       var $count = 0;
+                       var $structure = {};
+                       for (var $optionID in data.returnValues.options) {
+                               var $option = data.returnValues.options[$optionID];
                                
-                               if (data.returnValues.categories[$categoryName]) {
-                                       $('<li class="aclCategory">' + data.returnValues.categories[$categoryName] + '</li>').appendTo(this._containerElements.permissionList);
+                               var $listItem = $('<li><span>' + $option.label + '</span></li>').data('optionID', $optionID).data('optionName', $option.optionName);
+                               var $grantPermission = $('<input type="checkbox" id="grant' + $optionID + '" />').appendTo($listItem).wrap('<label for="grant' + $optionID + '" class="jsTooltip" title="' + WCF.Language.get('wcf.acl.option.grant') + '" />');
+                               var $denyPermission = $('<input type="checkbox" id="deny' + $optionID + '" />').appendTo($listItem).wrap('<label for="deny' + $optionID + '" class="jsTooltip" title="' + WCF.Language.get('wcf.acl.option.deny') + '" />');
+                               
+                               $grantPermission.data('type', 'grant').data('optionID', $optionID).change($.proxy(this._change, this));
+                               $denyPermission.data('type', 'deny').data('optionID', $optionID).change($.proxy(this._change, this));
+                               
+                               if (!$structure[$option.categoryName]) {
+                                       $structure[$option.categoryName] = [];
                                }
                                
-                               for (var $i = 0, $length = $listItems.length; $i < $length; $i++) {
-                                       $listItems[$i].appendTo(this._containerElements.permissionList);
+                               if ($option.categoryName === '') {
+                                       $listItem.appendTo(this._containerElements.permissionList);
+                               }
+                               else {
+                                       $structure[$option.categoryName].push($listItem);
                                }
+                               
+                               $count++;
                        }
-               }
-               
-               // set data
-               this._parseData(data, 'group');
-               this._parseData(data, 'user');
-               
-               // show container
-               this._container.show();
-               
-               // pre-select an entry
-               this._selectFirstEntry();
-       },
-       
-       /**
-        * Parses user and group data.
-        * 
-        * @param       object          data
-        * @param       string          type
-        */
-       _parseData: function(data, type) {
-               if (!$.getLength(data.returnValues[type].option)) {
-                       return;
-               }
-               
-               // add list items
-               for (var $typeID in data.returnValues[type].label) {
-                       this._createListItem($typeID, data.returnValues[type].label[$typeID], type);
                        
-                       this._search.addExcludedSearchValue(data.returnValues[type].label[$typeID]);
-               }
-               
-               // add options
-               this._values[type] = data.returnValues[type].option;
-               
-               WCF.DOMNodeInsertedHandler.execute();
-       },
-       
-       /**
-        * Prepares permission list for a specific object.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               var $listItem = $(event.currentTarget);
-               if ($listItem.hasClass('active')) {
-                       return;
-               }
-               
-               this._select($listItem, true);
-       },
-       
-       /**
-        * Selects the given item and marks it as active.
-        * 
-        * @param       jQuery          listItem
-        * @param       boolean         savePermissions
-        */
-       _select: function(listItem, savePermissions) {
-               // save previous permissions
-               if (savePermissions) {
-                       this._savePermissions();
-               }
-               
-               // switch active item
-               this._containerElements.aclList.children('li').removeClass('active');
-               listItem.addClass('active');
-               
-               // apply permissions for current item
-               this._setupPermissions(listItem.data('type'), listItem.data('objectID'));
-       },
-       
-       /**
-        * Toggles between deny and grant.
-        * 
-        * @param       object          event
-        */
-       _change: function(event) {
-               var $checkbox = $(event.currentTarget);
-               var $optionID = $checkbox.data('optionID');
-               var $type = $checkbox.data('type');
-               
-               if ($checkbox.is(':checked')) {
-                       if ($type === 'deny') {
-                               $('#grant' + $optionID).prop('checked', false);
+                       // add a "full access" permission if there are more than one option
+                       if ($count > 1) {
+                               var $listItem = $('<li class="aclFullAccess"><span>' + WCF.Language.get('wcf.acl.option.fullAccess') + '</span></li>').prependTo(this._containerElements.permissionList);
+                               this._containerElements.grantAll = $('<input type="checkbox" id="grantAll_' + this._container.attr('id') + '" />').appendTo($listItem).wrap('<label class="jsTooltip" title="' + WCF.Language.get('wcf.acl.option.grant') + '" />');
+                               this._containerElements.denyAll = $('<input type="checkbox" id="denyAll_' + this._container.attr('id') + '" />').appendTo($listItem).wrap('<label class="jsTooltip" title="' + WCF.Language.get('wcf.acl.option.deny') + '" />');
                                
-                               if (this._containerElements.grantAll !== null) {
-                                       this._containerElements.grantAll.prop('checked', false);
-                               }
+                               // bind events
+                               this._containerElements.grantAll.data('type', 'grant').change($.proxy(this._changeAll, this));
+                               this._containerElements.denyAll.data('type', 'deny').change($.proxy(this._changeAll, this));
                        }
-                       else {
-                               $('#deny' + $optionID).prop('checked', false);
-                               
-                               if (this._containerElements.denyAll !== null) {
-                                       this._containerElements.denyAll.prop('checked', false);
+                       
+                       if ($.getLength($structure)) {
+                               for (var $categoryName in $structure) {
+                                       var $listItems = $structure[$categoryName];
+                                       
+                                       if (data.returnValues.categories[$categoryName]) {
+                                               $('<li class="aclCategory">' + data.returnValues.categories[$categoryName] + '</li>').appendTo(this._containerElements.permissionList);
+                                       }
+                                       
+                                       for (var $i = 0, $length = $listItems.length; $i < $length; $i++) {
+                                               $listItems[$i].appendTo(this._containerElements.permissionList);
+                                       }
                                }
                        }
-               }
-               else {
-                       if ($type === 'deny' && this._containerElements.denyAll !== null) {
-                               this._containerElements.denyAll.prop('checked', false);
-                       }
-                       else if ($type === 'grant' && this._containerElements.grantAll !== null) {
-                               this._containerElements.grantAll.prop('checked', false);
+                       
+                       // set data
+                       this._parseData(data, 'group');
+                       this._parseData(data, 'user');
+                       
+                       // show container
+                       this._container.show();
+                       
+                       // pre-select an entry
+                       this._selectFirstEntry();
+               },
+               
+               /**
+                * Parses user and group data.
+                *
+                * @param        object                data
+                * @param        string                type
+                */
+               _parseData: function (data, type) {
+                       if (!$.getLength(data.returnValues[type].option)) {
+                               return;
                        }
-               }
-               
-               var $allChecked = true;
-               this._containerElements.permissionList.find('input[type=checkbox]').each($.proxy(function(index, item) {
-                       var $item = $(item);
-                       
-                       if ($item.data('type') === $type && $item.attr('id') !== $type + 'All_' + this._container.attr('id')) {
-                               if (!$item.is(':checked')) {
-                                       $allChecked = false;
-                                       return false;
-                               }
+                       
+                       // add list items
+                       for (var $typeID in data.returnValues[type].label) {
+                               this._createListItem($typeID, data.returnValues[type].label[$typeID], type);
+                               
+                               this._search.addExcludedSearchValue(data.returnValues[type].label[$typeID]);
                        }
-               }, this));
-               if ($type == 'deny') {
-                       if (this._containerElements.denyAll !== null) {
-                               if ($allChecked) this._containerElements.denyAll.prop('checked', true);
-                               else this._containerElements.denyAll.prop('checked', false);
+                       
+                       // add options
+                       this._values[type] = data.returnValues[type].option;
+                       
+                       WCF.DOMNodeInsertedHandler.execute();
+               },
+               
+               /**
+                * Prepares permission list for a specific object.
+                *
+                * @param        object                event
+                */
+               _click: function (event) {
+                       var $listItem = $(event.currentTarget);
+                       if ($listItem.hasClass('active')) {
+                               return;
                        }
-               }
-               else {
-                       if (this._containerElements.grantAll !== null) {
-                               if ($allChecked) this._containerElements.grantAll.prop('checked', true);
-                               else this._containerElements.grantAll.prop('checked', false);
+                       
+                       this._select($listItem, true);
+               },
+               
+               /**
+                * Selects the given item and marks it as active.
+                *
+                * @param        jQuery                listItem
+                * @param        boolean                savePermissions
+                */
+               _select: function (listItem, savePermissions) {
+                       // save previous permissions
+                       if (savePermissions) {
+                               this._savePermissions();
                        }
-               }
-       },
-       
-       /**
-        * Toggles all options between deny and grant.
-        * 
-        * @param       object          event
-        */
-       _changeAll: function(event) {
-               var $checkbox = $(event.currentTarget);
-               var $type = $checkbox.data('type');
-               
-               if ($checkbox.is(':checked')) {
-                       if ($type === 'deny') {
-                               this._containerElements.grantAll.prop('checked', false);
-                               
-                               this._containerElements.permissionList.find('input[type=checkbox]').each($.proxy(function(index, item) {
-                                       var $item = $(item);
+                       
+                       // switch active item
+                       this._containerElements.aclList.children('li').removeClass('active');
+                       listItem.addClass('active');
+                       
+                       // apply permissions for current item
+                       this._setupPermissions(listItem.data('type'), listItem.data('objectID'));
+               },
+               
+               /**
+                * Toggles between deny and grant.
+                *
+                * @param        object                event
+                */
+               _change: function (event) {
+                       var $checkbox = $(event.currentTarget);
+                       var $optionID = $checkbox.data('optionID');
+                       var $type = $checkbox.data('type');
+                       
+                       if ($checkbox.is(':checked')) {
+                               if ($type === 'deny') {
+                                       $('#grant' + $optionID).prop('checked', false);
                                        
-                                       if ($item.data('type') === 'deny' && $item.attr('id') !== 'denyAll_' + this._container.attr('id')) {
-                                               $item.prop('checked', true).trigger('change');
+                                       if (this._containerElements.grantAll !== null) {
+                                               this._containerElements.grantAll.prop('checked', false);
                                        }
-                               }, this));
-                       }
-                       else {
-                               this._containerElements.denyAll.prop('checked', false);
-                               
-                               this._containerElements.permissionList.find('input[type=checkbox]').each($.proxy(function(index, item) {
-                                       var $item = $(item);
+                               }
+                               else {
+                                       $('#deny' + $optionID).prop('checked', false);
                                        
-                                       if ($item.data('type') === 'grant' && $item.attr('id') !== 'grantAll_' + this._container.attr('id')) {
-                                               $item.prop('checked', true).trigger('change');
+                                       if (this._containerElements.denyAll !== null) {
+                                               this._containerElements.denyAll.prop('checked', false);
                                        }
-                               }, this));
+                               }
                        }
-               }
-               else {
-                       if ($type === 'deny') {
-                               this._containerElements.grantAll.prop('checked', false);
+                       else {
+                               if ($type === 'deny' && this._containerElements.denyAll !== null) {
+                                       this._containerElements.denyAll.prop('checked', false);
+                               }
+                               else if ($type === 'grant' && this._containerElements.grantAll !== null) {
+                                       this._containerElements.grantAll.prop('checked', false);
+                               }
+                       }
+                       
+                       var $allChecked = true;
+                       this._containerElements.permissionList.find('input[type=checkbox]').each($.proxy(function (index, item) {
+                               var $item = $(item);
                                
-                               this._containerElements.permissionList.find('input[type=checkbox]').each($.proxy(function(index, item) {
-                                       var $item = $(item);
-                                       
-                                       if ($item.data('type') === 'deny' && $item.attr('id') !== 'denyAll_' + this._container.attr('id')) {
-                                               $item.prop('checked', false).trigger('change');
+                               if ($item.data('type') === $type && $item.attr('id') !== $type + 'All_' + this._container.attr('id')) {
+                                       if (!$item.is(':checked')) {
+                                               $allChecked = false;
+                                               return false;
                                        }
-                               }, this));
+                               }
+                       }, this));
+                       if ($type == 'deny') {
+                               if (this._containerElements.denyAll !== null) {
+                                       if ($allChecked) this._containerElements.denyAll.prop('checked', true);
+                                       else this._containerElements.denyAll.prop('checked', false);
+                               }
                        }
                        else {
-                               this._containerElements.denyAll.prop('checked', false);
-                               
-                               this._containerElements.permissionList.find('input[type=checkbox]').each($.proxy(function(index, item) {
-                                       var $item = $(item);
-                                       
-                                       if ($item.data('type') === 'grant' && $item.attr('id') !== 'grantAll_' + this._container.attr('id')) {
-                                               $item.prop('checked', false).trigger('change');
-                                       }
-                               }, this));
+                               if (this._containerElements.grantAll !== null) {
+                                       if ($allChecked) this._containerElements.grantAll.prop('checked', true);
+                                       else this._containerElements.grantAll.prop('checked', false);
+                               }
                        }
-               }
-       },
-       
-       /**
-        * Setups permission input for given object.
-        * 
-        * @param       string          type
-        * @param       integer         objectID
-        */
-       _setupPermissions: function(type, objectID) {
-               // reset all checkboxes to unchecked
-               this._containerElements.permissionList.find("input[type='checkbox']").prop('checked', false);
-               
-               // use stored permissions if applicable
-               if (this._values[type] && this._values[type][objectID]) {
-                       for (var $optionID in this._values[type][objectID]) {
-                               if (this._values[type][objectID][$optionID] == 1) {
-                                       $('#grant' + $optionID).prop('checked', true).trigger('change');
+               },
+               
+               /**
+                * Toggles all options between deny and grant.
+                *
+                * @param        object                event
+                */
+               _changeAll: function (event) {
+                       var $checkbox = $(event.currentTarget);
+                       var $type = $checkbox.data('type');
+                       
+                       if ($checkbox.is(':checked')) {
+                               if ($type === 'deny') {
+                                       this._containerElements.grantAll.prop('checked', false);
+                                       
+                                       this._containerElements.permissionList.find('input[type=checkbox]').each($.proxy(function (index, item) {
+                                               var $item = $(item);
+                                               
+                                               if ($item.data('type') === 'deny' && $item.attr('id') !== 'denyAll_' + this._container.attr('id')) {
+                                                       $item.prop('checked', true).trigger('change');
+                                               }
+                                       }, this));
                                }
                                else {
-                                       $('#deny' + $optionID).prop('checked', true).trigger('change');
+                                       this._containerElements.denyAll.prop('checked', false);
+                                       
+                                       this._containerElements.permissionList.find('input[type=checkbox]').each($.proxy(function (index, item) {
+                                               var $item = $(item);
+                                               
+                                               if ($item.data('type') === 'grant' && $item.attr('id') !== 'grantAll_' + this._container.attr('id')) {
+                                                       $item.prop('checked', true).trigger('change');
+                                               }
+                                       }, this));
                                }
                        }
-               }
-               
-               // show permissions
-               this._containerElements.permissionList.show();
-       },
-       
-       /**
-        * Saves currently set permissions.
-        */
-       _savePermissions: function() {
-               // get active object
-               var $activeObject = this._containerElements.aclList.find('li.active');
-               if (!$activeObject.length) {
-                       return;
-               }
-               
-               var $objectID = $activeObject.data('objectID');
-               var $type = $activeObject.data('type');
-               
-               // clear old values
-               this._values[$type][$objectID] = { };
-               this._containerElements.permissionList.find("input[type='checkbox']").each((function(index, checkbox) {
-                       var $checkbox = $(checkbox);
-                       if ($checkbox.attr('id') != 'grantAll_' + this._container.attr('id') && $checkbox.attr('id') != 'denyAll_' + this._container.attr('id')) {
-                               var $optionValue = ($checkbox.data('type') === 'deny') ? 0 : 1;
-                               var $optionID = $checkbox.data('optionID');
-                               
-                               if ($checkbox.is(':checked')) {
-                                       // store value
-                                       this._values[$type][$objectID][$optionID] = $optionValue;
+                       else {
+                               if ($type === 'deny') {
+                                       this._containerElements.grantAll.prop('checked', false);
                                        
-                                       // reset value afterwards
-                                       $checkbox.prop('checked', false);
+                                       this._containerElements.permissionList.find('input[type=checkbox]').each($.proxy(function (index, item) {
+                                               var $item = $(item);
+                                               
+                                               if ($item.data('type') === 'deny' && $item.attr('id') !== 'denyAll_' + this._container.attr('id')) {
+                                                       $item.prop('checked', false).trigger('change');
+                                               }
+                                       }, this));
                                }
-                               else if (this._values[$type] && this._values[$type][$objectID] && this._values[$type][$objectID][$optionID] && this._values[$type][$objectID][$optionID] == $optionValue) {
-                                       delete this._values[$type][$objectID][$optionID];
+                               else {
+                                       this._containerElements.denyAll.prop('checked', false);
+                                       
+                                       this._containerElements.permissionList.find('input[type=checkbox]').each($.proxy(function (index, item) {
+                                               var $item = $(item);
+                                               
+                                               if ($item.data('type') === 'grant' && $item.attr('id') !== 'grantAll_' + this._container.attr('id')) {
+                                                       $item.prop('checked', false).trigger('change');
+                                               }
+                                       }, this));
                                }
                        }
-               }).bind(this));
-       },
-       
-       /**
-        * Prepares ACL values on submit.
-        * 
-        * @param       object          event
-        */
-       submit: function(event) {
-               this._savePermissions();
-               
-               this._save('group');
-               this._save('user');
-       },
-       
-       /**
-        * Inserts hidden form elements for each value.
-        * 
-        * @param       string          $type
-        */
-       _save: function($type) {
-               if ($.getLength(this._values[$type])) {
-                       var $form = this._container.parents('form:eq(0)');
+               },
+               
+               /**
+                * Setups permission input for given object.
+                *
+                * @param        string                type
+                * @param        integer                objectID
+                */
+               _setupPermissions: function (type, objectID) {
+                       // reset all checkboxes to unchecked
+                       this._containerElements.permissionList.find("input[type='checkbox']").prop('checked', false);
                        
-                       for (var $objectID in this._values[$type]) {
-                               var $object = this._values[$type][$objectID];
+                       // use stored permissions if applicable
+                       if (this._values[type] && this._values[type][objectID]) {
+                               for (var $optionID in this._values[type][objectID]) {
+                                       if (this._values[type][objectID][$optionID] == 1) {
+                                               $('#grant' + $optionID).prop('checked', true).trigger('change');
+                                       }
+                                       else {
+                                               $('#deny' + $optionID).prop('checked', true).trigger('change');
+                                       }
+                               }
+                       }
+                       
+                       // show permissions
+                       this._containerElements.permissionList.show();
+               },
+               
+               /**
+                * Saves currently set permissions.
+                */
+               _savePermissions: function () {
+                       // get active object
+                       var $activeObject = this._containerElements.aclList.find('li.active');
+                       if (!$activeObject.length) {
+                               return;
+                       }
+                       
+                       var $objectID = $activeObject.data('objectID');
+                       var $type = $activeObject.data('type');
+                       
+                       // clear old values
+                       this._values[$type][$objectID] = {};
+                       this._containerElements.permissionList.find("input[type='checkbox']").each((function (index, checkbox) {
+                               var $checkbox = $(checkbox);
+                               if ($checkbox.attr('id') != 'grantAll_' + this._container.attr('id') && $checkbox.attr('id') != 'denyAll_' + this._container.attr('id')) {
+                                       var $optionValue = ($checkbox.data('type') === 'deny') ? 0 : 1;
+                                       var $optionID = $checkbox.data('optionID');
+                                       
+                                       if ($checkbox.is(':checked')) {
+                                               // store value
+                                               this._values[$type][$objectID][$optionID] = $optionValue;
+                                               
+                                               // reset value afterwards
+                                               $checkbox.prop('checked', false);
+                                       }
+                                       else if (this._values[$type] && this._values[$type][$objectID] && this._values[$type][$objectID][$optionID] && this._values[$type][$objectID][$optionID] == $optionValue) {
+                                               delete this._values[$type][$objectID][$optionID];
+                                       }
+                               }
+                       }).bind(this));
+               },
+               
+               /**
+                * Prepares ACL values on submit.
+                *
+                * @param        object                event
+                */
+               submit: function (event) {
+                       this._savePermissions();
+                       
+                       this._save('group');
+                       this._save('user');
+               },
+               
+               /**
+                * Inserts hidden form elements for each value.
+                *
+                * @param        string                $type
+                */
+               _save: function ($type) {
+                       if ($.getLength(this._values[$type])) {
+                               var $form = this._container.parents('form:eq(0)');
                                
-                               for (var $optionID in $object) {
-                                       $('<input type="hidden" name="aclValues[' + $type + '][' + $objectID + '][' + $optionID + ']" value="' + $object[$optionID] + '" />').appendTo($form);
+                               for (var $objectID in this._values[$type]) {
+                                       var $object = this._values[$type][$objectID];
+                                       
+                                       for (var $optionID in $object) {
+                                               $('<input type="hidden" name="aclValues[' + $type + '][' + $objectID + '][' + $optionID + ']" value="' + $object[$optionID] + '" />').appendTo($form);
+                                       }
                                }
                        }
                }
-       }
-});
+       });
+}
+else {
+       WCF.ACL.List = Class.extend({
+               _categoryName: "",
+               _container: {},
+               _containerElements: {},
+               _objectID: 0,
+               _objectTypeID: {},
+               _options: {},
+               _proxy: {},
+               _search: {},
+               _values: {},
+               init: function() {},
+               _reset: function() {},
+               _loadACL: function() {},
+               addObject: function() {},
+               _createListItem: function() {},
+               _removeItem: function() {},
+               _selectFirstEntry: function() {},
+               _success: function() {},
+               _parseData: function() {},
+               _click: function() {},
+               _select: function() {},
+               _change: function() {},
+               _changeAll: function() {},
+               _setupPermissions: function() {},
+               _savePermissions: function() {},
+               submit: function() {},
+               _save: function() {}
+       });
+}
index 7cd17ff71b47a8c40554956d75eea517de8eace3..db3b2e852fccebacd69d3b18f26c2ce432008c98 100644 (file)
  */
 WCF.Attachment = {};
 
-/**
- * Attachment upload function
- * 
- * @see        WCF.Upload
- */
-WCF.Attachment.Upload = WCF.Upload.extend({
-       /**
-        * list of upload ids which should be automatically inserted
-        * @var array<integer>
-        */
-       _autoInsert: [ ],
-       
+if (COMPILER_TARGET_DEFAULT) {
        /**
-        * reference to 'Insert All' button
-        * @var jQuery
+        * Attachment upload function
+        *
+        * @see        WCF.Upload
         */
-       _insertAllButton: null,
-       
-       /**
-        * object type of the object the uploaded attachments belong to
-        * @var string
-        */
-       _objectType: '',
-       
-       /**
-        * id of the object the uploaded attachments belong to
-        * @var string
-        */
-       _objectID: 0,
-       
-       /**
-        * temporary hash to identify uploaded attachments
-        * @var string
-        */
-       _tmpHash: '',
-       
-       /**
-        * id of the parent object of the object the uploaded attachments belongs to
-        * @var string
-        */
-       _parentObjectID: 0,
-       
-       /**
-        * editor id
-        * @var string
-        */
-       _editorId: '',
-       
-       /**
-        * replace img element on load
-        * @var Object
-        */
-       _replaceOnLoad: {},
-       
-       /**
-        * @see WCF.Upload.init()
-        */
-       init: function(buttonSelector, fileListSelector, objectType, objectID, tmpHash, parentObjectID, maxUploads, editorId) {
-               this._super(buttonSelector, fileListSelector, 'wcf\\data\\attachment\\AttachmentAction', { multiple: true, maxUploads: maxUploads });
+       WCF.Attachment.Upload = WCF.Upload.extend({
+               /**
+                * list of upload ids which should be automatically inserted
+                * @var        array<integer>
+                */
+               _autoInsert: [],
                
-               this._autoInsert = [ ];
-               this._objectType = objectType;
-               this._objectID = parseInt(objectID);
-               this._tmpHash = tmpHash;
-               this._parentObjectID = parseInt(parentObjectID);
-               this._editorId = editorId;
+               /**
+                * reference to 'Insert All' button
+                * @var        jQuery
+                */
+               _insertAllButton: null,
                
-               this._buttonSelector.children('p.button').click($.proxy(this._validateLimit, this));
-               this._fileListSelector.find('.jsButtonInsertAttachment').click($.proxy(this._insert, this));
-               this._fileListSelector.find('.jsButtonAttachmentInsertThumbnail').click($.proxy(this._insert, this));
-               this._fileListSelector.find('.jsButtonAttachmentInsertFull').click($.proxy(this._insert, this));
+               /**
+                * object type of the object the uploaded attachments belong to
+                * @var        string
+                */
+               _objectType: '',
                
-               WCF.DOMNodeRemovedHandler.addCallback('WCF.Attachment.Upload', $.proxy(this._removeLimitError, this));
-               WCF.System.Event.addListener('com.woltlab.wcf.action.delete', 'attachment_' + this._editorId, $.proxy(this._removeLimitError, this));
+               /**
+                * id of the object the uploaded attachments belong to
+                * @var        string
+                */
+               _objectID: 0,
                
-               this._makeSortable();
+               /**
+                * temporary hash to identify uploaded attachments
+                * @var        string
+                */
+               _tmpHash: '',
                
-               this._insertAllButton = $('<p class="button jsButtonAttachmentInsertAll">' + WCF.Language.get('wcf.attachment.insertAll') + '</p>').hide().appendTo(this._buttonSelector);
-               this._insertAllButton.click($.proxy(this._insertAll, this));
+               /**
+                * id of the parent object of the object the uploaded attachments belongs to
+                * @var        string
+                */
+               _parentObjectID: 0,
                
-               if (this._fileListSelector.children('li:not(.uploadFailed)').length) {
-                       this._insertAllButton.show();
-               }
+               /**
+                * editor id
+                * @var        string
+                */
+               _editorId: '',
                
-               if (this._editorId) {
-                       WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'submit_' + this._editorId, this._submitInline.bind(this));
-                       WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'reset_' + this._editorId, this._reset.bind(this));
-                       WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'dragAndDrop_' + this._editorId, this._editorUpload.bind(this));
-                       WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'pasteFromClipboard_' + this._editorId, this._editorUpload.bind(this));
+               /**
+                * replace img element on load
+                * @var Object
+                */
+               _replaceOnLoad: {},
+               
+               /**
+                * @see        WCF.Upload.init()
+                */
+               init: function (buttonSelector, fileListSelector, objectType, objectID, tmpHash, parentObjectID, maxUploads, editorId) {
+                       this._super(buttonSelector, fileListSelector, 'wcf\\data\\attachment\\AttachmentAction', {
+                               multiple: true,
+                               maxUploads: maxUploads
+                       });
                        
-                       var metacodeAttachUuid = WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'metacode_attach_' + this._editorId, (function(data) {
-                               var images = this._getImageAttachments();
-                               var attachmentId = data.attributes[0] || 0;
-                               if (images.hasOwnProperty(attachmentId)) {
-                                       var thumbnail = data.attributes[2];
-                                       thumbnail = (thumbnail === true || thumbnail === 'true');
-                                       
-                                       var image = elCreate('img');
-                                       image.className = 'woltlabAttachment';
-                                       image.src = images[attachmentId][(thumbnail ? 'thumbnailUrl' : 'url')];
-                                       elData(image, 'attachment-id', attachmentId);
-                                       
-                                       var float = data.attributes[1] || 'none';
-                                       if (float === 'left') image.classList.add('messageFloatObjectLeft');
-                                       else if (float === 'right') image.classList.add('messageFloatObjectRight');
-                                       
-                                       var metacode = data.metacode;
-                                       metacode.parentNode.insertBefore(image, metacode);
-                                       elRemove(metacode);
-                                       
-                                       data.cancel = true;
-                               }
-                       }).bind(this));
+                       this._autoInsert = [];
+                       this._objectType = objectType;
+                       this._objectID = parseInt(objectID);
+                       this._tmpHash = tmpHash;
+                       this._parentObjectID = parseInt(parentObjectID);
+                       this._editorId = editorId;
+                       
+                       this._buttonSelector.children('p.button').click($.proxy(this._validateLimit, this));
+                       this._fileListSelector.find('.jsButtonInsertAttachment').click($.proxy(this._insert, this));
+                       this._fileListSelector.find('.jsButtonAttachmentInsertThumbnail').click($.proxy(this._insert, this));
+                       this._fileListSelector.find('.jsButtonAttachmentInsertFull').click($.proxy(this._insert, this));
+                       
+                       WCF.DOMNodeRemovedHandler.addCallback('WCF.Attachment.Upload', $.proxy(this._removeLimitError, this));
+                       WCF.System.Event.addListener('com.woltlab.wcf.action.delete', 'attachment_' + this._editorId, $.proxy(this._removeLimitError, this));
+                       
+                       this._makeSortable();
+                       
+                       this._insertAllButton = $('<p class="button jsButtonAttachmentInsertAll">' + WCF.Language.get('wcf.attachment.insertAll') + '</p>').hide().appendTo(this._buttonSelector);
+                       this._insertAllButton.click($.proxy(this._insertAll, this));
+                       
+                       if (this._fileListSelector.children('li:not(.uploadFailed)').length) {
+                               this._insertAllButton.show();
+                       }
                        
-                       WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'destroy_' + this._editorId, (function () {
-                               WCF.System.Event.removeAllListeners('com.woltlab.wcf.redactor2', 'submit_' + this._editorId);
-                               WCF.System.Event.removeAllListeners('com.woltlab.wcf.redactor2', 'reset_' + this._editorId);
-                               WCF.System.Event.removeAllListeners('com.woltlab.wcf.redactor2', 'insertAttachment_' + this._editorId);
-                               WCF.System.Event.removeAllListeners('com.woltlab.wcf.redactor2', 'dragAndDrop_' + this._editorId);
-                               WCF.System.Event.removeAllListeners('com.woltlab.wcf.redactor2', 'pasteFromClipboard_' + this._editorId);
+                       if (this._editorId) {
+                               WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'submit_' + this._editorId, this._submitInline.bind(this));
+                               WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'reset_' + this._editorId, this._reset.bind(this));
+                               WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'dragAndDrop_' + this._editorId, this._editorUpload.bind(this));
+                               WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'pasteFromClipboard_' + this._editorId, this._editorUpload.bind(this));
                                
-                               WCF.System.Event.removeListener('com.woltlab.wcf.redactor2', 'metacode_attach_' + this._editorId, metacodeAttachUuid);
-                       }).bind(this));
-               }
-       },
-       
-       /**
-        * Handles drag & drop uploads and clipboard paste.
-        * 
-        * @param       object          data
-        */
-       _editorUpload: function(data) {
-               var $uploadID, replace = null;
-               
-               // show tab
-               this._fileListSelector.closest('.messageTabMenu').messageTabMenu('showTab', 'attachments', true);
-               
-               if (data.file) {
-                       $uploadID = this._upload(undefined, data.file);
-               }
-               else {
-                       $uploadID = this._upload(undefined, undefined, data.blob);
-                       replace = data.replace || null;
-               }
-               
-               if (replace === null) {
-                       this._autoInsert.push($uploadID);
-               }
-               else {
-                       this._replaceOnLoad[$uploadID] = replace;
-               }
-               
-               data.uploadID = $uploadID;
-       },
-       
-       /**
-        * Sets the attachments representing an image.
-        * 
-        * @return      {Object}
-        */
-       _getImageAttachments: function() {
-               var images = {};
-               
-               this._fileListSelector.children('li').each(function(index, attachment) {
-                       var $attachment = $(attachment);
-                       if ($attachment.data('isImage')) {
-                               images[~~$attachment.data('objectID')] = {
-                                       thumbnailUrl: $attachment.find('.jsButtonAttachmentInsertThumbnail').data('url'),
-                                       url: $attachment.find('.jsButtonAttachmentInsertFull').data('url')
-                               };
+                               var metacodeAttachUuid = WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'metacode_attach_' + this._editorId, (function (data) {
+                                       var images = this._getImageAttachments();
+                                       var attachmentId = data.attributes[0] || 0;
+                                       if (images.hasOwnProperty(attachmentId)) {
+                                               var thumbnail = data.attributes[2];
+                                               thumbnail = (thumbnail === true || thumbnail === 'true');
+                                               
+                                               var image = elCreate('img');
+                                               image.className = 'woltlabAttachment';
+                                               image.src = images[attachmentId][(thumbnail ? 'thumbnailUrl' : 'url')];
+                                               elData(image, 'attachment-id', attachmentId);
+                                               
+                                               var float = data.attributes[1] || 'none';
+                                               if (float === 'left') image.classList.add('messageFloatObjectLeft');
+                                               else if (float === 'right') image.classList.add('messageFloatObjectRight');
+                                               
+                                               var metacode = data.metacode;
+                                               metacode.parentNode.insertBefore(image, metacode);
+                                               elRemove(metacode);
+                                               
+                                               data.cancel = true;
+                                       }
+                               }).bind(this));
+                               
+                               WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'destroy_' + this._editorId, (function () {
+                                       WCF.System.Event.removeAllListeners('com.woltlab.wcf.redactor2', 'submit_' + this._editorId);
+                                       WCF.System.Event.removeAllListeners('com.woltlab.wcf.redactor2', 'reset_' + this._editorId);
+                                       WCF.System.Event.removeAllListeners('com.woltlab.wcf.redactor2', 'insertAttachment_' + this._editorId);
+                                       WCF.System.Event.removeAllListeners('com.woltlab.wcf.redactor2', 'dragAndDrop_' + this._editorId);
+                                       WCF.System.Event.removeAllListeners('com.woltlab.wcf.redactor2', 'pasteFromClipboard_' + this._editorId);
+                                       
+                                       WCF.System.Event.removeListener('com.woltlab.wcf.redactor2', 'metacode_attach_' + this._editorId, metacodeAttachUuid);
+                               }).bind(this));
                        }
-               });
+               },
                
-               return images;
-       },
-       
-       /**
-        * Adds parameters for the inline editor.
-        * 
-        * @param       object          data
-        */
-       _submitInline: function(data) {
-               if (this._tmpHash) {
-                       data.tmpHash = this._tmpHash;
-               }
-       },
-       
-       /**
-        * Resets the attachment container.
-        */
-       _reset: function() {
-               this._fileListSelector.hide().empty();
-               this._insertAllButton.hide();
-               this._validateLimit();
-       },
-       
-       /**
-        * Validates upload limits.
-        * 
-        * @return      boolean
-        */
-       _validateLimit: function() {
-               var $innerError = this._buttonSelector.next('small.innerError');
-               
-               // check maximum uploads
-               var $max = this._options.maxUploads - this._fileListSelector.children('li:not(.uploadFailed)').length;
-               var $filesLength = (this._fileUpload) ? this._fileUpload.prop('files').length : 0;
-               if ($max <= 0 || $max < $filesLength) {
-                       // reached limit
-                       var $errorMessage = ($max <= 0) ? WCF.Language.get('wcf.attachment.upload.error.reachedLimit') : WCF.Language.get('wcf.attachment.upload.error.reachedRemainingLimit').replace(/#remaining#/, $max);
-                       if (!$innerError.length) {
-                               $innerError = $('<small class="innerError" />').insertAfter(this._buttonSelector);
+               /**
+                * Handles drag & drop uploads and clipboard paste.
+                *
+                * @param        object                data
+                */
+               _editorUpload: function (data) {
+                       var $uploadID, replace = null;
+                       
+                       // show tab
+                       this._fileListSelector.closest('.messageTabMenu').messageTabMenu('showTab', 'attachments', true);
+                       
+                       if (data.file) {
+                               $uploadID = this._upload(undefined, data.file);
+                       }
+                       else {
+                               $uploadID = this._upload(undefined, undefined, data.blob);
+                               replace = data.replace || null;
                        }
                        
-                       $innerError.html($errorMessage);
+                       if (replace === null) {
+                               this._autoInsert.push($uploadID);
+                       }
+                       else {
+                               this._replaceOnLoad[$uploadID] = replace;
+                       }
                        
-                       return false;
-               }
-               
-               // remove previous errors
-               $innerError.remove();
-               
-               return true;
-       },
-       
-       /**
-        * Removes the limit error message.
-        * 
-        * @param       object          data
-        */
-       _removeLimitError: function(data) {
-               var $listItems = this._fileListSelector.children('li');
-               if (!$listItems.filter(':not(.uploadFailed)').length) {
-                       this._insertAllButton.hide();
-               }
+                       data.uploadID = $uploadID;
+               },
                
-               if (!$listItems.length) {
-                       this._fileListSelector.hide();
-               }
-               
-               if (this._editorId && data.button) {
-                       WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'deleteAttachment_' + this._editorId, {
-                               attachmentId: data.button.data('objectID')
+               /**
+                * Sets the attachments representing an image.
+                *
+                * @return      {Object}
+                */
+               _getImageAttachments: function () {
+                       var images = {};
+                       
+                       this._fileListSelector.children('li').each(function (index, attachment) {
+                               var $attachment = $(attachment);
+                               if ($attachment.data('isImage')) {
+                                       images[~~$attachment.data('objectID')] = {
+                                               thumbnailUrl: $attachment.find('.jsButtonAttachmentInsertThumbnail').data('url'),
+                                               url: $attachment.find('.jsButtonAttachmentInsertFull').data('url')
+                                       };
+                               }
                        });
-               }
-       },
-       
-       /**
-        * @see WCF.Upload._upload()
-        */
-       _upload: function(event, file, blob) {
-               var $uploadID = undefined;
-               
-               if (this._validateLimit()) {
-                       $uploadID = this._super(event, file, blob);
-               }
+                       
+                       return images;
+               },
                
-               if (this._fileUpload) {
-                       // remove and re-create the upload button since the 'files' property
-                       // of the input field is readonly thus it can't be reset
-                       this._removeButton();
-                       this._createButton();
-               }
+               /**
+                * Adds parameters for the inline editor.
+                *
+                * @param        object                data
+                */
+               _submitInline: function (data) {
+                       if (this._tmpHash) {
+                               data.tmpHash = this._tmpHash;
+                       }
+               },
                
-               return $uploadID;
-       },
-       
-       /**
-        * @see WCF.Upload._createUploadMatrix()
-        */
-       _createUploadMatrix: function(files) {
-               // remove failed uploads
-               this._fileListSelector.children('li.uploadFailed').remove();
+               /**
+                * Resets the attachment container.
+                */
+               _reset: function () {
+                       this._fileListSelector.hide().empty();
+                       this._insertAllButton.hide();
+                       this._validateLimit();
+               },
                
-               return this._super(files);
-       },
-       
-       /**
-        * @see WCF.Upload._getParameters()
-        */
-       _getParameters: function() {
-               return {
-                       objectType: this._objectType,
-                       objectID: this._objectID,
-                       tmpHash: this._tmpHash,
-                       parentObjectID: this._parentObjectID
-               };
-       },
-       
-       /**
-        * @see WCF.Upload._initFile()
-        */
-       _initFile: function(file) {
-               var $li = $('<li class="box64"><span class="icon icon64 fa-spinner" /><div><div><p>'+file.name+'</p><small><progress max="100"></progress></small></div><ul></ul></div></li>').data('filename', file.name);
-               this._fileListSelector.append($li);
-               this._fileListSelector.show();
+               /**
+                * Validates upload limits.
+                *
+                * @return        boolean
+                */
+               _validateLimit: function () {
+                       var $innerError = this._buttonSelector.next('small.innerError');
+                       
+                       // check maximum uploads
+                       var $max = this._options.maxUploads - this._fileListSelector.children('li:not(.uploadFailed)').length;
+                       var $filesLength = (this._fileUpload) ? this._fileUpload.prop('files').length : 0;
+                       if ($max <= 0 || $max < $filesLength) {
+                               // reached limit
+                               var $errorMessage = ($max <= 0) ? WCF.Language.get('wcf.attachment.upload.error.reachedLimit') : WCF.Language.get('wcf.attachment.upload.error.reachedRemainingLimit').replace(/#remaining#/, $max);
+                               if (!$innerError.length) {
+                                       $innerError = $('<small class="innerError" />').insertAfter(this._buttonSelector);
+                               }
+                               
+                               $innerError.html($errorMessage);
+                               
+                               return false;
+                       }
+                       
+                       // remove previous errors
+                       $innerError.remove();
+                       
+                       return true;
+               },
                
-               // validate file size
-               if (this._buttonSelector.data('maxSize') < file.size) {
-                       // remove progress bar
-                       $li.find('progress').remove();
+               /**
+                * Removes the limit error message.
+                *
+                * @param        object                data
+                */
+               _removeLimitError: function (data) {
+                       var $listItems = this._fileListSelector.children('li');
+                       if (!$listItems.filter(':not(.uploadFailed)').length) {
+                               this._insertAllButton.hide();
+                       }
                        
-                       // upload icon
-                       $li.children('.fa-spinner').removeClass('fa-spinner').addClass('fa-ban');
+                       if (!$listItems.length) {
+                               this._fileListSelector.hide();
+                       }
                        
-                       // error message
-                       $li.find('div > div').append($('<small class="innerError">' + WCF.Language.get('wcf.attachment.upload.error.tooLarge') + '</small>'));
-                       $li.addClass('uploadFailed');
-               }
+                       if (this._editorId && data.button) {
+                               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'deleteAttachment_' + this._editorId, {
+                                       attachmentId: data.button.data('objectID')
+                               });
+                       }
+               },
                
-               return $li;
-       },
-       
-       /**
-        * @see WCF.Upload._success()
-        */
-       _success: function(uploadID, data) {
-               var attachmentData;
-               for (var $i in this._uploadMatrix[uploadID]) {
-                       if (!this._uploadMatrix[uploadID].hasOwnProperty($i)) {
-                               continue;
+               /**
+                * @see        WCF.Upload._upload()
+                */
+               _upload: function (event, file, blob) {
+                       var $uploadID = undefined;
+                       
+                       if (this._validateLimit()) {
+                               $uploadID = this._super(event, file, blob);
                        }
                        
-                       // get li
-                       var $li = this._uploadMatrix[uploadID][$i];
+                       if (this._fileUpload) {
+                               // remove and re-create the upload button since the 'files' property
+                               // of the input field is readonly thus it can't be reset
+                               this._removeButton();
+                               this._createButton();
+                       }
                        
-                       // remove progress bar
-                       $li.find('progress').remove();
+                       return $uploadID;
+               },
+               
+               /**
+                * @see        WCF.Upload._createUploadMatrix()
+                */
+               _createUploadMatrix: function (files) {
+                       // remove failed uploads
+                       this._fileListSelector.children('li.uploadFailed').remove();
                        
-                       // get filename and check result
-                       var $filename = $li.data('filename');
-                       var $internalFileID = $li.data('internalFileID');
-                       if (data.returnValues && data.returnValues.attachments[$internalFileID]) {
-                               attachmentData = data.returnValues.attachments[$internalFileID];
-                               
-                               // show thumbnail
-                               if (attachmentData.tinyURL) {
-                                       $li.children('.fa-spinner').replaceWith($('<img src="' + attachmentData.tinyURL + '" alt="" class="attachmentTinyThumbnail" />'));
-                                       
-                                       $li.data('height', attachmentData.height);
-                                       $li.data('width', attachmentData.width);
-                                       elData($li[0], 'is-image', attachmentData.isImage);
-                               }
-                               // show file icon
-                               else {
-                                       $li.children('.fa-spinner').removeClass('fa-spinner').addClass('fa-' + attachmentData.iconName);
-                               }
+                       return this._super(files);
+               },
+               
+               /**
+                * @see        WCF.Upload._getParameters()
+                */
+               _getParameters: function () {
+                       return {
+                               objectType: this._objectType,
+                               objectID: this._objectID,
+                               tmpHash: this._tmpHash,
+                               parentObjectID: this._parentObjectID
+                       };
+               },
+               
+               /**
+                * @see        WCF.Upload._initFile()
+                */
+               _initFile: function (file) {
+                       var $li = $('<li class="box64"><span class="icon icon64 fa-spinner" /><div><div><p>' + file.name + '</p><small><progress max="100"></progress></small></div><ul></ul></div></li>').data('filename', file.name);
+                       this._fileListSelector.append($li);
+                       this._fileListSelector.show();
+                       
+                       // validate file size
+                       if (this._buttonSelector.data('maxSize') < file.size) {
+                               // remove progress bar
+                               $li.find('progress').remove();
                                
-                               // update attachment link
-                               var $link = $('<a href=""></a>');
-                               $link.text($filename).attr('href', attachmentData.url);
+                               // upload icon
+                               $li.children('.fa-spinner').removeClass('fa-spinner').addClass('fa-ban');
                                
-                               if (attachmentData.isImage != 0) {
-                                       $link.addClass('jsImageViewer').attr('title', $filename);
+                               // error message
+                               $li.find('div > div').append($('<small class="innerError">' + WCF.Language.get('wcf.attachment.upload.error.tooLarge') + '</small>'));
+                               $li.addClass('uploadFailed');
+                       }
+                       
+                       return $li;
+               },
+               
+               /**
+                * @see        WCF.Upload._success()
+                */
+               _success: function (uploadID, data) {
+                       var attachmentData;
+                       for (var $i in this._uploadMatrix[uploadID]) {
+                               if (!this._uploadMatrix[uploadID].hasOwnProperty($i)) {
+                                       continue;
                                }
-                               $li.find('p').empty().append($link);
                                
-                               // update file size
-                               $li.find('small').append(attachmentData.formattedFilesize);
+                               // get li
+                               var $li = this._uploadMatrix[uploadID][$i];
                                
-                               // init buttons
-                               var $buttonList = $li.find('ul').addClass('buttonGroup');
-                               var $deleteButton = $('<li><span class="button small jsDeleteButton" data-object-id="'+attachmentData.attachmentID+'" data-confirm-message="'+WCF.Language.get('wcf.attachment.delete.sure')+'" data-event-name="attachment_' + this._editorId + '">' + WCF.Language.get('wcf.global.button.delete') + '</span></li>');
-                               $buttonList.append($deleteButton);
+                               // remove progress bar
+                               $li.find('progress').remove();
                                
-                               $li.data('objectID', attachmentData.attachmentID);
-                               
-                               if (this._editorId) {
+                               // get filename and check result
+                               var $filename = $li.data('filename');
+                               var $internalFileID = $li.data('internalFileID');
+                               if (data.returnValues && data.returnValues.attachments[$internalFileID]) {
+                                       attachmentData = data.returnValues.attachments[$internalFileID];
+                                       
+                                       // show thumbnail
                                        if (attachmentData.tinyURL) {
-                                               if (attachmentData.thumbnailURL) {
-                                                       var $insertThumbnail = $('<li><span class="button small jsButtonAttachmentInsertThumbnail" data-object-id="' + attachmentData.attachmentID + '" data-url="' + WCF.String.escapeHTML(attachmentData.thumbnailURL) + '">' + WCF.Language.get('wcf.attachment.insertThumbnail') + '</span></li>').appendTo($buttonList);
-                                                       $insertThumbnail.children('span.button').click($.proxy(this._insert, this));
-                                               }
+                                               $li.children('.fa-spinner').replaceWith($('<img src="' + attachmentData.tinyURL + '" alt="" class="attachmentTinyThumbnail" />'));
                                                
-                                               var $insertOriginal = $('<li><span class="button small jsButtonAttachmentInsertFull" data-object-id="' + attachmentData.attachmentID + '" data-url="' + WCF.String.escapeHTML(attachmentData.url) + '">' + WCF.Language.get('wcf.attachment.insertFull') + '</span></li>').appendTo($buttonList);
-                                               $insertOriginal.children('span.button').click($.proxy(this._insert, this));
+                                               $li.data('height', attachmentData.height);
+                                               $li.data('width', attachmentData.width);
+                                               elData($li[0], 'is-image', attachmentData.isImage);
                                        }
+                                       // show file icon
                                        else {
-                                               var $insertPlain = $('<li><span class="button small jsButtonAttachmentInsertPlain" data-object-id="' + attachmentData.attachmentID + '">' + WCF.Language.get('wcf.attachment.insert') + '</span></li>');
-                                               $insertPlain.appendTo($buttonList).children('span.button').click($.proxy(this._insert, this));
+                                               $li.children('.fa-spinner').removeClass('fa-spinner').addClass('fa-' + attachmentData.iconName);
                                        }
-                               }
-                               
-                               if (this._replaceOnLoad.hasOwnProperty(uploadID)) {
-                                       if (!$li.hasClass('uploadFailed')) {
-                                               var img = this._replaceOnLoad[uploadID];
-                                               if (img && img.parentNode) {
-                                                       WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'replaceAttachment_' + this._editorId, {
-                                                               attachmentId: attachmentData.attachmentID,
-                                                               img: img,
-                                                               src: (attachmentData.thumbnailURL) ? attachmentData.thumbnailURL : attachmentData.url
-                                                       });
+                                       
+                                       // update attachment link
+                                       var $link = $('<a href=""></a>');
+                                       $link.text($filename).attr('href', attachmentData.url);
+                                       
+                                       if (attachmentData.isImage != 0) {
+                                               $link.addClass('jsImageViewer').attr('title', $filename);
+                                       }
+                                       $li.find('p').empty().append($link);
+                                       
+                                       // update file size
+                                       $li.find('small').append(attachmentData.formattedFilesize);
+                                       
+                                       // init buttons
+                                       var $buttonList = $li.find('ul').addClass('buttonGroup');
+                                       var $deleteButton = $('<li><span class="button small jsDeleteButton" data-object-id="' + attachmentData.attachmentID + '" data-confirm-message="' + WCF.Language.get('wcf.attachment.delete.sure') + '" data-event-name="attachment_' + this._editorId + '">' + WCF.Language.get('wcf.global.button.delete') + '</span></li>');
+                                       $buttonList.append($deleteButton);
+                                       
+                                       $li.data('objectID', attachmentData.attachmentID);
+                                       
+                                       if (this._editorId) {
+                                               if (attachmentData.tinyURL) {
+                                                       if (attachmentData.thumbnailURL) {
+                                                               var $insertThumbnail = $('<li><span class="button small jsButtonAttachmentInsertThumbnail" data-object-id="' + attachmentData.attachmentID + '" data-url="' + WCF.String.escapeHTML(attachmentData.thumbnailURL) + '">' + WCF.Language.get('wcf.attachment.insertThumbnail') + '</span></li>').appendTo($buttonList);
+                                                               $insertThumbnail.children('span.button').click($.proxy(this._insert, this));
+                                                       }
+                                                       
+                                                       var $insertOriginal = $('<li><span class="button small jsButtonAttachmentInsertFull" data-object-id="' + attachmentData.attachmentID + '" data-url="' + WCF.String.escapeHTML(attachmentData.url) + '">' + WCF.Language.get('wcf.attachment.insertFull') + '</span></li>').appendTo($buttonList);
+                                                       $insertOriginal.children('span.button').click($.proxy(this._insert, this));
+                                               }
+                                               else {
+                                                       var $insertPlain = $('<li><span class="button small jsButtonAttachmentInsertPlain" data-object-id="' + attachmentData.attachmentID + '">' + WCF.Language.get('wcf.attachment.insert') + '</span></li>');
+                                                       $insertPlain.appendTo($buttonList).children('span.button').click($.proxy(this._insert, this));
                                                }
                                        }
                                        
-                                       this._replaceOnLoad[uploadID] = null;
-                               }
-                       }
-                       else {
-                               // upload icon
-                               $li.children('.fa-spinner').removeClass('fa-spinner').addClass('fa-ban');
-                               var $errorMessage = '';
-                               
-                               // error handling
-                               if (data.returnValues && data.returnValues.errors[$internalFileID]) {
-                                       $errorMessage = data.returnValues.errors[$internalFileID]['errorType'];
+                                       if (this._replaceOnLoad.hasOwnProperty(uploadID)) {
+                                               if (!$li.hasClass('uploadFailed')) {
+                                                       var img = this._replaceOnLoad[uploadID];
+                                                       if (img && img.parentNode) {
+                                                               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'replaceAttachment_' + this._editorId, {
+                                                                       attachmentId: attachmentData.attachmentID,
+                                                                       img: img,
+                                                                       src: (attachmentData.thumbnailURL) ? attachmentData.thumbnailURL : attachmentData.url
+                                                               });
+                                                       }
+                                               }
+                                               
+                                               this._replaceOnLoad[uploadID] = null;
+                                       }
                                }
                                else {
-                                       // unknown error
-                                       $errorMessage = 'uploadFailed';
+                                       // upload icon
+                                       $li.children('.fa-spinner').removeClass('fa-spinner').addClass('fa-ban');
+                                       var $errorMessage = '';
+                                       
+                                       // error handling
+                                       if (data.returnValues && data.returnValues.errors[$internalFileID]) {
+                                               $errorMessage = data.returnValues.errors[$internalFileID]['errorType'];
+                                       }
+                                       else {
+                                               // unknown error
+                                               $errorMessage = 'uploadFailed';
+                                       }
+                                       
+                                       $li.find('div > div').append($('<small class="innerError">' + WCF.Language.get('wcf.attachment.upload.error.' + $errorMessage) + '</small>'));
+                                       $li.addClass('uploadFailed');
                                }
                                
-                               $li.find('div > div').append($('<small class="innerError">'+WCF.Language.get('wcf.attachment.upload.error.'+$errorMessage)+'</small>'));
-                               $li.addClass('uploadFailed');
-                       }
-                       
-                       if (WCF.inArray(uploadID, this._autoInsert)) {
-                               this._autoInsert.splice(this._autoInsert.indexOf(uploadID), 1);
-                               
-                               if (!$li.hasClass('uploadFailed')) {
-                                       var btn = $li.find('.jsButtonAttachmentInsertThumbnail');
-                                       if (!btn.length) btn = $li.find('.jsButtonAttachmentInsertFull');
+                               if (WCF.inArray(uploadID, this._autoInsert)) {
+                                       this._autoInsert.splice(this._autoInsert.indexOf(uploadID), 1);
                                        
-                                       btn.trigger('click');
+                                       if (!$li.hasClass('uploadFailed')) {
+                                               var btn = $li.find('.jsButtonAttachmentInsertThumbnail');
+                                               if (!btn.length) btn = $li.find('.jsButtonAttachmentInsertFull');
+                                               
+                                               btn.trigger('click');
+                                       }
                                }
                        }
-               }
-               
-               this._makeSortable();
+                       
+                       this._makeSortable();
+                       
+                       if (this._fileListSelector.children('li:not(.uploadFailed)').length) {
+                               this._insertAllButton.show();
+                       }
+                       else {
+                               this._insertAllButton.hide();
+                       }
+                       
+                       WCF.DOMNodeInsertedHandler.execute();
+               },
                
-               if (this._fileListSelector.children('li:not(.uploadFailed)').length) {
-                       this._insertAllButton.show();
-               }
-               else {
-                       this._insertAllButton.hide();
-               }
+               /**
+                * Inserts an attachment into WYSIWYG editor contents.
+                *
+                * @param        {Event}                event
+                */
+               _insert: function (event) {
+                       WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'insertAttachment_' + this._editorId, {
+                               attachmentId: elData(event.currentTarget, 'object-id'),
+                               url: elData(event.currentTarget, 'url')
+                       });
+               },
                
-               WCF.DOMNodeInsertedHandler.execute();
-       },
-       
-       /**
-        * Inserts an attachment into WYSIWYG editor contents.
-        * 
-        * @param       {Event}         event
-        */
-       _insert: function(event) {
-               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'insertAttachment_' + this._editorId, {
-                       attachmentId: elData(event.currentTarget, 'object-id'),
-                       url: elData(event.currentTarget, 'url')
-               });
-       },
-       
-       /**
-        * Inserts all attachments at once.
-        */
-       _insertAll: function() {
-               this._fileListSelector.children('li:not(.uploadFailed)').find('.jsButtonAttachmentInsertThumbnail, .jsButtonAttachmentInsertPlain').trigger('click');
-       },
-       
-       /**
-        * @see WCF.Upload._error()
-        */
-       _error: function(data) {
-               // mark uploads as failed
-               this._fileListSelector.find('li').each(function(index, listItem) {
-                       var $listItem = $(listItem);
-                       if ($listItem.children('.fa-spinner').length) {
-                               // upload icon
-                               $listItem.addClass('uploadFailed').children('.fa-spinner').removeClass('fa-spinner').addClass('fa-ban');
-                               $listItem.find('div > div').append($('<small class="innerError">' + (data.responseJSON && data.responseJSON.message ? data.responseJSON.message : WCF.Language.get('wcf.attachment.upload.error.uploadFailed')) + '</small>'));
-                       }
-               });
-       },
-       
-       /**
-        * Initializes sorting for uploaded attachments.
-        */
-       _makeSortable: function() {
-               var $attachments = this._fileListSelector.children('li:not(.uploadFailed)');
-               if (!$attachments.length) {
-                       return;
-               }
+               /**
+                * Inserts all attachments at once.
+                */
+               _insertAll: function () {
+                       this._fileListSelector.children('li:not(.uploadFailed)').find('.jsButtonAttachmentInsertThumbnail, .jsButtonAttachmentInsertPlain').trigger('click');
+               },
                
-               $attachments.addClass('sortableAttachment').children('img').addClass('sortableNode');
+               /**
+                * @see        WCF.Upload._error()
+                */
+               _error: function (data) {
+                       // mark uploads as failed
+                       this._fileListSelector.find('li').each(function (index, listItem) {
+                               var $listItem = $(listItem);
+                               if ($listItem.children('.fa-spinner').length) {
+                                       // upload icon
+                                       $listItem.addClass('uploadFailed').children('.fa-spinner').removeClass('fa-spinner').addClass('fa-ban');
+                                       $listItem.find('div > div').append($('<small class="innerError">' + (data.responseJSON && data.responseJSON.message ? data.responseJSON.message : WCF.Language.get('wcf.attachment.upload.error.uploadFailed')) + '</small>'));
+                               }
+                       });
+               },
                
-               if (!this._fileListSelector.hasClass('sortableList')) {
-                       this._fileListSelector.addClass('sortableList');
+               /**
+                * Initializes sorting for uploaded attachments.
+                */
+               _makeSortable: function () {
+                       var $attachments = this._fileListSelector.children('li:not(.uploadFailed)');
+                       if (!$attachments.length) {
+                               return;
+                       }
                        
-                       require(['Environment'], (function (Environment) {
-                               if (Environment.platform() === 'desktop') {
-                                       new WCF.Sortable.List(this._fileListSelector.parent().wcfIdentify(), '', 0, {
-                                               axis: false,
-                                               items: 'li.sortableAttachment',
-                                               toleranceElement: null,
-                                               start: function (event, ui) {
-                                                       ui.placeholder[0].style.setProperty('height', ui.helper[0].offsetHeight + 'px', '');
-                                               },
-                                               update: (function() {
-                                                       var $attachmentIDs = [ ];
-                                                       this._fileListSelector.children('li:not(.uploadFailed)').each(function(index, listItem) {
-                                                               $attachmentIDs.push($(listItem).data('objectID'));
-                                                       });
-                                                       
-                                                       if ($attachmentIDs.length) {
-                                                               new WCF.Action.Proxy({
-                                                                       autoSend: true,
-                                                                       data: {
-                                                                               actionName: 'updatePosition',
-                                                                               className: 'wcf\\data\\attachment\\AttachmentAction',
-                                                                               parameters: {
-                                                                                       attachmentIDs: $attachmentIDs,
-                                                                                       objectID: this._objectID,
-                                                                                       objectType: this._objectType,
-                                                                                       tmpHash: this._tmpHash
-                                                                               }
-                                                                       }
+                       $attachments.addClass('sortableAttachment').children('img').addClass('sortableNode');
+                       
+                       if (!this._fileListSelector.hasClass('sortableList')) {
+                               this._fileListSelector.addClass('sortableList');
+                               
+                               require(['Environment'], (function (Environment) {
+                                       if (Environment.platform() === 'desktop') {
+                                               new WCF.Sortable.List(this._fileListSelector.parent().wcfIdentify(), '', 0, {
+                                                       axis: false,
+                                                       items: 'li.sortableAttachment',
+                                                       toleranceElement: null,
+                                                       start: function (event, ui) {
+                                                               ui.placeholder[0].style.setProperty('height', ui.helper[0].offsetHeight + 'px', '');
+                                                       },
+                                                       update: (function () {
+                                                               var $attachmentIDs = [];
+                                                               this._fileListSelector.children('li:not(.uploadFailed)').each(function (index, listItem) {
+                                                                       $attachmentIDs.push($(listItem).data('objectID'));
                                                                });
-                                                       }
-                                               }).bind(this)
-                                       }, true);
-                               }
-                       }).bind(this));
+                                                               
+                                                               if ($attachmentIDs.length) {
+                                                                       new WCF.Action.Proxy({
+                                                                               autoSend: true,
+                                                                               data: {
+                                                                                       actionName: 'updatePosition',
+                                                                                       className: 'wcf\\data\\attachment\\AttachmentAction',
+                                                                                       parameters: {
+                                                                                               attachmentIDs: $attachmentIDs,
+                                                                                               objectID: this._objectID,
+                                                                                               objectType: this._objectType,
+                                                                                               tmpHash: this._tmpHash
+                                                                                       }
+                                                                               }
+                                                                       });
+                                                               }
+                                                       }).bind(this)
+                                               }, true);
+                                       }
+                               }).bind(this));
+                       }
                }
-       }
-});
+       });
+}
+else {
+       WCF.Attachment.Upload = WCF.Upload.extend({
+               _autoInsert: {},
+               _insertAllButton: {},
+               _objectType: "",
+               _objectID: 0,
+               _tmpHash: "",
+               _parentObjectID: 0,
+               _editorId: "",
+               _replaceOnLoad: {},
+               init: function() {},
+               _editorUpload: function() {},
+               _getImageAttachments: function() {},
+               _submitInline: function() {},
+               _reset: function() {},
+               _validateLimit: function() {},
+               _removeLimitError: function() {},
+               _upload: function() {},
+               _createUploadMatrix: function() {},
+               _getParameters: function() {},
+               _initFile: function() {},
+               _success: function() {},
+               _insert: function() {},
+               _insertAll: function() {},
+               _error: function() {},
+               _makeSortable: function() {},
+               _name: "",
+               _buttonSelector: {},
+               _fileListSelector: {},
+               _fileUpload: {},
+               _className: "",
+               _iframe: {},
+               _internalFileID: 0,
+               _options: {},
+               _uploadMatrix: {},
+               _supportsAJAXUpload: true,
+               _overlay: {},
+               _createButton: function() {},
+               _insertButton: function() {},
+               _removeButton: function() {},
+               _progress: function() {},
+               _showOverlay: function() {},
+               _evaluateResponse: function() {},
+               _getFilename: function() {}
+       });
+}
index 4527a60e5be83591def6817fbb522d612a82c525..9b18167cd2de5799e6fe871703c4fffe0b005528 100644 (file)
 "use strict";
 
-/**
- * Color picker for WCF
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2017 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- */
-WCF.ColorPicker = Class.extend({
-       /**
-        * hue bar element
-        * @var jQuery
-        */
-       _bar: null,
-       
-       /**
-        * bar selector is being moved
-        * @var boolean
-        */
-       _barActive: false,
-       
-       /**
-        * bar selector element
-        * @var jQuery
-        */
-       _barSelector: null,
-       
-       /**
-        * optional submit callback
-        * @var Function
-        */
-       _callbackSubmit: null,
-       
-       /**
-        * dialog overlay
-        * @var jQuery
-        */
-       _dialog: null,
-       
-       /**
-        * initialization state
-        * @var boolean
-        */
-       _didInit: false,
-       
-       /**
-        * active element id
-        * @var string
-        */
-       _elementID: '',
-       
-       /**
-        * saturation and value gradient element
-        * @var jQuery
-        */
-       _gradient: null,
-       
-       /**
-        * gradient selector is being moved
-        * @var boolean
-        */
-       _gradientActive: false,
-       
-       /**
-        * gradient selector element
-        * @var jQuery
-        */
-       _gradientSelector: null,
-       
-       /**
-        * HEX input element
-        * @var jQuery
-        */
-       _hex: null,
-       
-       /**
-        * HSV representation
-        * @var object
-        */
-       _hsv: { },
-       
-       /**
-        * visual new color element
-        * @var jQuery
-        */
-       _newColor: null,
-       
-       /**
-        * visual previous color element
-        * @var jQuery
-        */
-       _oldColor: null,
-       
-       /**
-        * list of RGBa input elements
-        * @var object
-        */
-       _rgba: { },
-       
-       /**
-        * RegExp to parse rgba()
-        * @var RegExp
-        */
-       _rgbaRegExp: null,
-       
-       /**
-        * Initializes the WCF.ColorPicker class.
-        * 
-        * @param       string          selector
-        */
-       init: function(selector) {
-               this._callbackSubmit = null;
-               this._elementID = '';
-               this._hsv = { h: 0, s: 100, v: 100 };
-               this._position = { };
-               
-               var $elements = $(selector);
-               if (!$elements.length) {
-                       console.debug("[WCF.ColorPicker] Selector does not match any element, aborting.");
-                       return;
-               }
-               
-               $elements.click($.proxy(this._open, this));
-       },
-       
-       /**
-        * Sets an optional submit callback.
-        * 
-        * @param       {Function}      callback
-        */
-       setCallbackSubmit: function(callback) {
-               this._callbackSubmit = callback;
-       },
-       
-       /**
-        * Opens the color picker overlay.
-        * 
-        * @param       object          event
-        */
-       _open: function(event) {
-               if (!this._didInit) {
-                       // init color picker on first usage
-                       this._initColorPicker();
-                       this._didInit = true;
-               }
-               
-               // load values from element
-               var $element = $(event.currentTarget);
-               this._elementID = $element.wcfIdentify();
-               this._parseColor($element);
-               
-               // set 'current' color
-               var $rgb = this.hsvToRgb(this._hsv.h, this._hsv.s, this._hsv.v);
-               this._oldColor.css({ backgroundColor: 'rgba(' + $rgb.r + ', ' + $rgb.g + ', ' + $rgb.b + ', ' + (this._rgba.a.val() / 100) + ')' });
-               
-               this._dialog.wcfDialog({
-                       'title': WCF.Language.get('wcf.style.colorPicker')
-               });
-               
-               // set default focus
-               window.setTimeout((function () {
-                       this._hex.focus();
-               }).bind(this), 200);
-       },
-       
-       /**
-        * Parses the color of an element.
-        * 
-        * @param       jQuery          element
-        */
-       _parseColor: function(element) {
-               if (element.data('hsv') && element.data('rgb')) {
-                       // create an explicit copy here, otherwise it would be only a reference
-                       var $hsv = element.data('hsv');
-                       for (var $type in $hsv) {
-                               this._hsv[$type] = $hsv[$type];
-                       }
-                       this._updateValues(element.data('rgb'), true, true);
-                       this._rgba.a.val(parseInt(element.data('alpha')));
-               }
-               else {
-                       if (this._rgbaRegExp === null) {
-                               this._rgbaRegExp = new RegExp("^rgba\\((\\d{1,3}), ?(\\d{1,3}), ?(\\d{1,3}), ?(1|1\\.00?|0|0?\\.[0-9]{1,2})\\)$");
-                       }
+if (COMPILER_TARGET_DEFAULT) {
+       /**
+        * Color picker for WCF
+        *
+        * @author        Alexander Ebert
+        * @copyright        2001-2017 WoltLab GmbH
+        * @license        GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+        */
+       WCF.ColorPicker = Class.extend({
+               /**
+                * hue bar element
+                * @var        jQuery
+                */
+               _bar: null,
+               
+               /**
+                * bar selector is being moved
+                * @var        boolean
+                */
+               _barActive: false,
+               
+               /**
+                * bar selector element
+                * @var        jQuery
+                */
+               _barSelector: null,
+               
+               /**
+                * optional submit callback
+                * @var Function
+                */
+               _callbackSubmit: null,
+               
+               /**
+                * dialog overlay
+                * @var        jQuery
+                */
+               _dialog: null,
+               
+               /**
+                * initialization state
+                * @var        boolean
+                */
+               _didInit: false,
+               
+               /**
+                * active element id
+                * @var        string
+                */
+               _elementID: '',
+               
+               /**
+                * saturation and value gradient element
+                * @var        jQuery
+                */
+               _gradient: null,
+               
+               /**
+                * gradient selector is being moved
+                * @var        boolean
+                */
+               _gradientActive: false,
+               
+               /**
+                * gradient selector element
+                * @var        jQuery
+                */
+               _gradientSelector: null,
+               
+               /**
+                * HEX input element
+                * @var        jQuery
+                */
+               _hex: null,
+               
+               /**
+                * HSV representation
+                * @var        object
+                */
+               _hsv: {},
+               
+               /**
+                * visual new color element
+                * @var        jQuery
+                */
+               _newColor: null,
+               
+               /**
+                * visual previous color element
+                * @var        jQuery
+                */
+               _oldColor: null,
+               
+               /**
+                * list of RGBa input elements
+                * @var        object
+                */
+               _rgba: {},
+               
+               /**
+                * RegExp to parse rgba()
+                * @var        RegExp
+                */
+               _rgbaRegExp: null,
+               
+               /**
+                * Initializes the WCF.ColorPicker class.
+                *
+                * @param        string                selector
+                */
+               init: function (selector) {
+                       this._callbackSubmit = null;
+                       this._elementID = '';
+                       this._hsv = {h: 0, s: 100, v: 100};
+                       this._position = {};
                        
-                       // parse value
-                       this._rgbaRegExp.exec(element.data('color'));
-                       var $alpha = RegExp.$4;
-                       // convert into x.yz
-                       if ($alpha.indexOf('.') === 0) {
-                               $alpha = "0" + $alpha;
+                       var $elements = $(selector);
+                       if (!$elements.length) {
+                               console.debug("[WCF.ColorPicker] Selector does not match any element, aborting.");
+                               return;
                        }
-                       $alpha *= 100;
                        
-                       this._updateValues({
-                               r: RegExp.$1,
-                               g: RegExp.$2,
-                               b: RegExp.$3,
-                               a: Math.round($alpha)
-                       }, true, true);
-               }
-       },
-       
-       /**
-        * Initializes the color picker upon first usage.
-        */
-       _initColorPicker: function() {
-               this._dialog = $('<div id="colorPickerContainer" />').hide().appendTo(document.body);
-               
-               // create gradient
-               this._gradient = $('<div id="colorPickerGradient" />').appendTo(this._dialog);
-               this._gradientSelector = $('<span id="colorPickerGradientSelector"><span></span></span>').appendTo(this._gradient);
-               
-               // create bar
-               this._bar = $('<div id="colorPickerBar" />').appendTo(this._dialog);
-               this._barSelector = $('<span id="colorPickerBarSelector" />').appendTo(this._bar);
-               
-               // bind event listener
-               this._gradient.mousedown($.proxy(this._mouseDownGradient, this));
-               this._bar.mousedown($.proxy(this._mouseDownBar, this));
-               
-               var self = this;
-               $(document).mouseup(function(event) {
-                       if (self._barActive) {
-                               self._barActive = false;
-                               self._mouseBar(event);
-                       }
-                       else if (self._gradientActive) {
-                               self._gradientActive = false;
-                               self._mouseGradient(event);
+                       $elements.click($.proxy(this._open, this));
+               },
+               
+               /**
+                * Sets an optional submit callback.
+                *
+                * @param        {Function}        callback
+                */
+               setCallbackSubmit: function (callback) {
+                       this._callbackSubmit = callback;
+               },
+               
+               /**
+                * Opens the color picker overlay.
+                *
+                * @param        object                event
+                */
+               _open: function (event) {
+                       if (!this._didInit) {
+                               // init color picker on first usage
+                               this._initColorPicker();
+                               this._didInit = true;
                        }
-               }).mousemove(function(event) {
-                       if (self._barActive) {
-                               self._mouseBar(event);
-                       }
-                       else if (self._gradientActive) {
-                               self._mouseGradient(event);
+                       
+                       // load values from element
+                       var $element = $(event.currentTarget);
+                       this._elementID = $element.wcfIdentify();
+                       this._parseColor($element);
+                       
+                       // set 'current' color
+                       var $rgb = this.hsvToRgb(this._hsv.h, this._hsv.s, this._hsv.v);
+                       this._oldColor.css({backgroundColor: 'rgba(' + $rgb.r + ', ' + $rgb.g + ', ' + $rgb.b + ', ' + (this._rgba.a.val() / 100) + ')'});
+                       
+                       this._dialog.wcfDialog({
+                               'title': WCF.Language.get('wcf.style.colorPicker')
+                       });
+                       
+                       // set default focus
+                       window.setTimeout((function () {
+                               this._hex.focus();
+                       }).bind(this), 200);
+               },
+               
+               /**
+                * Parses the color of an element.
+                *
+                * @param        jQuery                element
+                */
+               _parseColor: function (element) {
+                       if (element.data('hsv') && element.data('rgb')) {
+                               // create an explicit copy here, otherwise it would be only a reference
+                               var $hsv = element.data('hsv');
+                               for (var $type in $hsv) {
+                                       this._hsv[$type] = $hsv[$type];
+                               }
+                               this._updateValues(element.data('rgb'), true, true);
+                               this._rgba.a.val(parseInt(element.data('alpha')));
                        }
-               });
-               
-               this._initColorPickerForm();
-       },
-       
-       /**
-        * Initializes the color picker input elements upon first usage.
-        */
-       _initColorPickerForm: function() {
-               var $form = $('<div id="colorPickerForm" />').appendTo(this._dialog);
-               
-               // new and current color
-               $('<small>' + WCF.Language.get('wcf.style.colorPicker.new') + '</small>').appendTo($form);
-               var $colors = $('<ul class="colors" />').appendTo($form);
-               this._newColor = $('<li class="new"><span /></li>').appendTo($colors).children('span');
-               this._oldColor = $('<li class="old"><span /></li>').appendTo($colors).children('span');
-               $('<small>' + WCF.Language.get('wcf.style.colorPicker.current') + '</small>').appendTo($form);
-               
-               // RGBa input
-               var $rgba = $('<ul class="rgba" />').appendTo($form);
-               this._createInputElement('r', 'R', 0, 255).appendTo($rgba);
-               this._createInputElement('g', 'G', 0, 255).appendTo($rgba);
-               this._createInputElement('b', 'B', 0, 255).appendTo($rgba);
-               this._createInputElement('a', 'a', 0, 100).appendTo($rgba);
-               
-               // HEX input
-               var $hex = $('<ul class="hex"><li><label><span>#</span></label></li></ul>').appendTo($form);
-               this._hex = $('<input type="text" maxlength="6" />').appendTo($hex.find('label'));
-               
-               // bind event listener
-               this._rgba.r.blur($.proxy(this._blurRgba, this)).keyup($.proxy(this._keyUpRGBA, this));
-               this._rgba.g.blur($.proxy(this._blurRgba, this)).keyup($.proxy(this._keyUpRGBA, this));
-               this._rgba.b.blur($.proxy(this._blurRgba, this)).keyup($.proxy(this._keyUpRGBA, this));
-               this._rgba.a.blur($.proxy(this._blurRgba, this)).keyup($.proxy(this._keyUpRGBA, this));
-               this._hex.blur($.proxy(this._blurHex, this)).keyup($.proxy(this._keyUpHex, this));
-               
-               // submit button
-               var $submitForm = $('<div class="formSubmit" />').appendTo(this._dialog);
-               $('<button class="buttonPrimary">' + WCF.Language.get('wcf.style.colorPicker.button.apply') + '</button>').appendTo($submitForm).click($.proxy(this._submit, this));
-               
-               // allow pasting of colors like '#888888'
-               var self = this;
-               this._hex.on('paste', function() {
-                       self._hex.attr('maxlength', '7');
-                       
-                       setTimeout(function() {
-                               var $value = self._hex.val();
-                               if ($value.substring(0, 1) == '#') {
-                                       $value = $value.substr(1);
+                       else {
+                               if (this._rgbaRegExp === null) {
+                                       this._rgbaRegExp = new RegExp("^rgba\\((\\d{1,3}), ?(\\d{1,3}), ?(\\d{1,3}), ?(1|1\\.00?|0|0?\\.[0-9]{1,2})\\)$");
                                }
                                
-                               if ($value.length > 6) {
-                                       $value = $value.substring(0, 6);
+                               // parse value
+                               this._rgbaRegExp.exec(element.data('color'));
+                               var $alpha = RegExp.$4;
+                               // convert into x.yz
+                               if ($alpha.indexOf('.') === 0) {
+                                       $alpha = "0" + $alpha;
                                }
+                               $alpha *= 100;
                                
-                               self._hex.attr('maxlength', '6').val($value);
-                       }, 50);
-               });
-               
-               // select text in input boxes on user focus
-               $form.find('input').focus(function(){
-                       this.select();
-               });
-       },
-       
-       /**
-        * Submits form on enter.
-        */
-       _keyUpRGBA: function(event) {
-               if (event.which == 13) {
-                       this._blurRgba();
-                       this._submit();
-               }
-       },
-       
-       /**
-        * Submits form on enter.
-        */
-       _keyUpHex: function(event) {
-               if (event.which == 13) {
-                       this._blurHex();
-                       this._submit();
-               }
-       },
-       
-       /**
-        * Assigns the new color for active element.
-        */
-       _submit: function() {
-               var $rgb = this.hsvToRgb(this._hsv.h, this._hsv.s, this._hsv.v);
-               
-               // create an explicit copy here, otherwise it would be only a reference
-               var $hsv = { };
-               for (var $type in this._hsv) {
-                       $hsv[$type] = this._hsv[$type];
-               }
-               
-               var $element = $('#' + this._elementID);
-               $element.data('hsv', $hsv).css({ backgroundColor: 'rgba(' + $rgb.r + ', ' + $rgb.g + ', ' + $rgb.b + ', ' + (this._rgba.a.val() / 100) + ')' }).data('alpha', parseInt(this._rgba.a.val()));
-               $element.data('rgb', {
-                       r: this._rgba.r.val(),
-                       g: this._rgba.g.val(),
-                       b: this._rgba.b.val()
-               });
-               $('#' + $element.data('store')).val('rgba(' + this._rgba.r.val() + ', ' + this._rgba.g.val() + ', ' + this._rgba.b.val() + ', ' + (this._rgba.a.val() / 100) + ')').trigger('change');
+                               this._updateValues({
+                                       r: RegExp.$1,
+                                       g: RegExp.$2,
+                                       b: RegExp.$3,
+                                       a: Math.round($alpha)
+                               }, true, true);
+                       }
+               },
                
-               this._dialog.wcfDialog('close');
+               /**
+                * Initializes the color picker upon first usage.
+                */
+               _initColorPicker: function () {
+                       this._dialog = $('<div id="colorPickerContainer" />').hide().appendTo(document.body);
+                       
+                       // create gradient
+                       this._gradient = $('<div id="colorPickerGradient" />').appendTo(this._dialog);
+                       this._gradientSelector = $('<span id="colorPickerGradientSelector"><span></span></span>').appendTo(this._gradient);
+                       
+                       // create bar
+                       this._bar = $('<div id="colorPickerBar" />').appendTo(this._dialog);
+                       this._barSelector = $('<span id="colorPickerBarSelector" />').appendTo(this._bar);
+                       
+                       // bind event listener
+                       this._gradient.mousedown($.proxy(this._mouseDownGradient, this));
+                       this._bar.mousedown($.proxy(this._mouseDownBar, this));
+                       
+                       var self = this;
+                       $(document).mouseup(function (event) {
+                               if (self._barActive) {
+                                       self._barActive = false;
+                                       self._mouseBar(event);
+                               }
+                               else if (self._gradientActive) {
+                                       self._gradientActive = false;
+                                       self._mouseGradient(event);
+                               }
+                       }).mousemove(function (event) {
+                               if (self._barActive) {
+                                       self._mouseBar(event);
+                               }
+                               else if (self._gradientActive) {
+                                       self._mouseGradient(event);
+                               }
+                       });
+                       
+                       this._initColorPickerForm();
+               },
+               
+               /**
+                * Initializes the color picker input elements upon first usage.
+                */
+               _initColorPickerForm: function () {
+                       var $form = $('<div id="colorPickerForm" />').appendTo(this._dialog);
+                       
+                       // new and current color
+                       $('<small>' + WCF.Language.get('wcf.style.colorPicker.new') + '</small>').appendTo($form);
+                       var $colors = $('<ul class="colors" />').appendTo($form);
+                       this._newColor = $('<li class="new"><span /></li>').appendTo($colors).children('span');
+                       this._oldColor = $('<li class="old"><span /></li>').appendTo($colors).children('span');
+                       $('<small>' + WCF.Language.get('wcf.style.colorPicker.current') + '</small>').appendTo($form);
+                       
+                       // RGBa input
+                       var $rgba = $('<ul class="rgba" />').appendTo($form);
+                       this._createInputElement('r', 'R', 0, 255).appendTo($rgba);
+                       this._createInputElement('g', 'G', 0, 255).appendTo($rgba);
+                       this._createInputElement('b', 'B', 0, 255).appendTo($rgba);
+                       this._createInputElement('a', 'a', 0, 100).appendTo($rgba);
+                       
+                       // HEX input
+                       var $hex = $('<ul class="hex"><li><label><span>#</span></label></li></ul>').appendTo($form);
+                       this._hex = $('<input type="text" maxlength="6" />').appendTo($hex.find('label'));
+                       
+                       // bind event listener
+                       this._rgba.r.blur($.proxy(this._blurRgba, this)).keyup($.proxy(this._keyUpRGBA, this));
+                       this._rgba.g.blur($.proxy(this._blurRgba, this)).keyup($.proxy(this._keyUpRGBA, this));
+                       this._rgba.b.blur($.proxy(this._blurRgba, this)).keyup($.proxy(this._keyUpRGBA, this));
+                       this._rgba.a.blur($.proxy(this._blurRgba, this)).keyup($.proxy(this._keyUpRGBA, this));
+                       this._hex.blur($.proxy(this._blurHex, this)).keyup($.proxy(this._keyUpHex, this));
+                       
+                       // submit button
+                       var $submitForm = $('<div class="formSubmit" />').appendTo(this._dialog);
+                       $('<button class="buttonPrimary">' + WCF.Language.get('wcf.style.colorPicker.button.apply') + '</button>').appendTo($submitForm).click($.proxy(this._submit, this));
+                       
+                       // allow pasting of colors like '#888888'
+                       var self = this;
+                       this._hex.on('paste', function () {
+                               self._hex.attr('maxlength', '7');
+                               
+                               setTimeout(function () {
+                                       var $value = self._hex.val();
+                                       if ($value.substring(0, 1) == '#') {
+                                               $value = $value.substr(1);
+                                       }
+                                       
+                                       if ($value.length > 6) {
+                                               $value = $value.substring(0, 6);
+                                       }
+                                       
+                                       self._hex.attr('maxlength', '6').val($value);
+                               }, 50);
+                       });
+                       
+                       // select text in input boxes on user focus
+                       $form.find('input').focus(function () {
+                               this.select();
+                       });
+               },
+               
+               /**
+                * Submits form on enter.
+                */
+               _keyUpRGBA: function (event) {
+                       if (event.which == 13) {
+                               this._blurRgba();
+                               this._submit();
+                       }
+               },
+               
+               /**
+                * Submits form on enter.
+                */
+               _keyUpHex: function (event) {
+                       if (event.which == 13) {
+                               this._blurHex();
+                               this._submit();
+                       }
+               },
                
-               if (typeof this._callbackSubmit === 'function') {
-                       this._callbackSubmit({
+               /**
+                * Assigns the new color for active element.
+                */
+               _submit: function () {
+                       var $rgb = this.hsvToRgb(this._hsv.h, this._hsv.s, this._hsv.v);
+                       
+                       // create an explicit copy here, otherwise it would be only a reference
+                       var $hsv = {};
+                       for (var $type in this._hsv) {
+                               $hsv[$type] = this._hsv[$type];
+                       }
+                       
+                       var $element = $('#' + this._elementID);
+                       $element.data('hsv', $hsv).css({backgroundColor: 'rgba(' + $rgb.r + ', ' + $rgb.g + ', ' + $rgb.b + ', ' + (this._rgba.a.val() / 100) + ')'}).data('alpha', parseInt(this._rgba.a.val()));
+                       $element.data('rgb', {
                                r: this._rgba.r.val(),
                                g: this._rgba.g.val(),
-                               b: this._rgba.b.val(),
-                               a: (this._rgba.a.val() / 100)
+                               b: this._rgba.b.val()
                        });
-               }
-       },
-       
-       /**
-        * Creates an input element.
-        * 
-        * @param       string          type
-        * @param       string          label
-        * @param       integer         min
-        * @param       integer         max
-        * @return      jQuery
-        */
-       _createInputElement: function(type, label, min, max) {
-               // create elements
-               var $listItem = $('<li class="' + type + '" />');
-               var $label = $('<label />').appendTo($listItem);
-               $('<span>' + label + '</span>').appendTo($label);
-               this._rgba[type] = $('<input type="number" value="0" min="' + min + '" max="' + max + '" step="1" />').appendTo($label);
-               
-               return $listItem;
-       },
-       
-       /**
-        * Handles the mouse down event on the gradient.
-        * 
-        * @param       object          event
-        */
-       _mouseDownGradient: function(event) {
-               this._gradientActive = true;
-               this._mouseGradient(event);
-       },
-       
-       /**
-        * Handles updates of gradient selector position.
-        * 
-        * @param       object          event
-        */
-       _mouseGradient: function(event) {
-               var $position = this._gradient.getOffsets('offset');
-               
-               var $left = Math.max(Math.min(event.pageX - $position.left, 255), 0);
-               var $top = Math.max(Math.min(event.pageY - $position.top, 255), 0);
-               
-               // calculate saturation and value
-               this._hsv.s = Math.max(0, Math.min(1, $left / 255)) * 100;
-               this._hsv.v = Math.max(0, Math.min(1, (255 - $top) / 255)) * 100;
-               
-               // update color
-               this._updateValues(null);
-       },
-       
-       /**
-        * Handles the mouse down event on the bar.
-        * 
-        * @param       object          event
-        */
-       _mouseDownBar: function(event) {
-               this._barActive = true;
-               this._mouseBar(event);
-       },
-       
-       /**
-        * Handles updates of the bar selector position.
-        * 
-        * @param       object          event
-        */
-       _mouseBar: function(event) {
-               var $position = this._bar.getOffsets('offset');
-               var $top = Math.max(Math.min(event.pageY - $position.top, 255), 0);
-               this._barSelector.css({ top: $top + 'px' });
-               
-               // calculate hue
-               this._hsv.h = Math.max(0, Math.min(359, Math.round((255 - $top) / 255 * 360)));
-               
-               // update color
-               this._updateValues(null);
-       },
-       
-       /**
-        * Handles changes of RGBa input fields.
-        */
-       _blurRgba: function() {
-               for (var $type in this._rgba) {
-                       var $value = parseInt(this._rgba[$type].val()) || 0;
+                       $('#' + $element.data('store')).val('rgba(' + this._rgba.r.val() + ', ' + this._rgba.g.val() + ', ' + this._rgba.b.val() + ', ' + (this._rgba.a.val() / 100) + ')').trigger('change');
+                       
+                       this._dialog.wcfDialog('close');
                        
-                       // alpha
-                       if ($type === 'a') {
-                               this._rgba[$type].val(Math.max(0, Math.min(100, $value)));
+                       if (typeof this._callbackSubmit === 'function') {
+                               this._callbackSubmit({
+                                       r: this._rgba.r.val(),
+                                       g: this._rgba.g.val(),
+                                       b: this._rgba.b.val(),
+                                       a: (this._rgba.a.val() / 100)
+                               });
                        }
-                       else {
-                               // rgb
-                               this._rgba[$type].val(Math.max(0, Math.min(255, $value)));
+               },
+               
+               /**
+                * Creates an input element.
+                *
+                * @param        string                type
+                * @param        string                label
+                * @param        integer                min
+                * @param        integer                max
+                * @return        jQuery
+                */
+               _createInputElement: function (type, label, min, max) {
+                       // create elements
+                       var $listItem = $('<li class="' + type + '" />');
+                       var $label = $('<label />').appendTo($listItem);
+                       $('<span>' + label + '</span>').appendTo($label);
+                       this._rgba[type] = $('<input type="number" value="0" min="' + min + '" max="' + max + '" step="1" />').appendTo($label);
+                       
+                       return $listItem;
+               },
+               
+               /**
+                * Handles the mouse down event on the gradient.
+                *
+                * @param        object                event
+                */
+               _mouseDownGradient: function (event) {
+                       this._gradientActive = true;
+                       this._mouseGradient(event);
+               },
+               
+               /**
+                * Handles updates of gradient selector position.
+                *
+                * @param        object                event
+                */
+               _mouseGradient: function (event) {
+                       var $position = this._gradient.getOffsets('offset');
+                       
+                       var $left = Math.max(Math.min(event.pageX - $position.left, 255), 0);
+                       var $top = Math.max(Math.min(event.pageY - $position.top, 255), 0);
+                       
+                       // calculate saturation and value
+                       this._hsv.s = Math.max(0, Math.min(1, $left / 255)) * 100;
+                       this._hsv.v = Math.max(0, Math.min(1, (255 - $top) / 255)) * 100;
+                       
+                       // update color
+                       this._updateValues(null);
+               },
+               
+               /**
+                * Handles the mouse down event on the bar.
+                *
+                * @param        object                event
+                */
+               _mouseDownBar: function (event) {
+                       this._barActive = true;
+                       this._mouseBar(event);
+               },
+               
+               /**
+                * Handles updates of the bar selector position.
+                *
+                * @param        object                event
+                */
+               _mouseBar: function (event) {
+                       var $position = this._bar.getOffsets('offset');
+                       var $top = Math.max(Math.min(event.pageY - $position.top, 255), 0);
+                       this._barSelector.css({top: $top + 'px'});
+                       
+                       // calculate hue
+                       this._hsv.h = Math.max(0, Math.min(359, Math.round((255 - $top) / 255 * 360)));
+                       
+                       // update color
+                       this._updateValues(null);
+               },
+               
+               /**
+                * Handles changes of RGBa input fields.
+                */
+               _blurRgba: function () {
+                       for (var $type in this._rgba) {
+                               var $value = parseInt(this._rgba[$type].val()) || 0;
+                               
+                               // alpha
+                               if ($type === 'a') {
+                                       this._rgba[$type].val(Math.max(0, Math.min(100, $value)));
+                               }
+                               else {
+                                       // rgb
+                                       this._rgba[$type].val(Math.max(0, Math.min(255, $value)));
+                               }
                        }
-               }
-               
-               this._updateValues({
-                       r: this._rgba.r.val(),
-                       g: this._rgba.g.val(),
-                       b: this._rgba.b.val()
-               }, true, true);
-       },
-       
-       /**
-        * Handles change of HEX value.
-        */
-       _blurHex: function() {
-               var $value = this.hexToRgb(this._hex.val());
-               if ($value !== Number.NaN) {
-                       this._updateValues($value, true, true);
-               }
-       },
-       
-       /**
-        * Updates the values of all elements, including color picker and
-        * input elements. Argument 'rgb' may be null.
-        * 
-        * @param       object          rgb
-        * @param       boolean         changeH
-        * @param       boolean         changeSV
-        */
-       _updateValues: function(rgb, changeH, changeSV) {
-               changeH = (changeH === true) ? true : false;
-               changeSV = (changeSV === true) ? true : false;
-               
-               // calculate RGB values from HSV
-               if (rgb === null) {
-                       rgb = this.hsvToRgb(this._hsv.h, this._hsv.s, this._hsv.v);
                        
-                       if (this._rgba.a.val() == 0) {
-                               rgb.a = 100;
+                       this._updateValues({
+                               r: this._rgba.r.val(),
+                               g: this._rgba.g.val(),
+                               b: this._rgba.b.val()
+                       }, true, true);
+               },
+               
+               /**
+                * Handles change of HEX value.
+                */
+               _blurHex: function () {
+                       var $value = this.hexToRgb(this._hex.val());
+                       if ($value !== Number.NaN) {
+                               this._updateValues($value, true, true);
+                       }
+               },
+               
+               /**
+                * Updates the values of all elements, including color picker and
+                * input elements. Argument 'rgb' may be null.
+                *
+                * @param        object                rgb
+                * @param        boolean                changeH
+                * @param        boolean                changeSV
+                */
+               _updateValues: function (rgb, changeH, changeSV) {
+                       changeH = (changeH === true) ? true : false;
+                       changeSV = (changeSV === true) ? true : false;
+                       
+                       // calculate RGB values from HSV
+                       if (rgb === null) {
+                               rgb = this.hsvToRgb(this._hsv.h, this._hsv.s, this._hsv.v);
+                               
+                               if (this._rgba.a.val() == 0) {
+                                       rgb.a = 100;
+                               }
                        }
-               }
-               
-               // add alpha channel
-               if (rgb.a === undefined) {
-                       rgb.a = this._rgba.a.val();
-               }
-               
-               // adjust RGBa input
-               for (var $type in rgb) {
-                       this._rgba[$type].val(rgb[$type]);
-               }
-               
-               // set hex input
-               this._hex.val(this.rgbToHex(rgb.r, rgb.g, rgb.b));
-               
-               // calculate HSV to adjust selectors
-               if (changeH || changeSV) {
-                       var $hsv = this.rgbToHsv(rgb.r, rgb.g, rgb.b);
                        
-                       // adjust hue
-                       if (changeH) {
-                               this._hsv.h = $hsv.h;
+                       // add alpha channel
+                       if (rgb.a === undefined) {
+                               rgb.a = this._rgba.a.val();
                        }
                        
-                       // adjust saturation and value
-                       if (changeSV) {
-                               this._hsv.s = $hsv.s;
-                               this._hsv.v = $hsv.v;
+                       // adjust RGBa input
+                       for (var $type in rgb) {
+                               this._rgba[$type].val(rgb[$type]);
                        }
-               }
-               
-               // adjust bar selector
-               var $top = Math.max(0, Math.min(255, 255 - (this._hsv.h / 360) * 255));
-               this._barSelector.css({ top: $top + 'px' });
-               
-               // adjust gradient selector
-               var $left = Math.max(0, Math.min(255, (this._hsv.s / 100) * 255));
-               var $top = Math.max(0, Math.min(255, 255 - ((this._hsv.v / 100) * 255)));
-               this._gradientSelector.css({
-                       left: ($left - 6) + 'px',
-                       top: ($top - 6) + 'px'
-               });
+                       
+                       // set hex input
+                       this._hex.val(this.rgbToHex(rgb.r, rgb.g, rgb.b));
+                       
+                       // calculate HSV to adjust selectors
+                       if (changeH || changeSV) {
+                               var $hsv = this.rgbToHsv(rgb.r, rgb.g, rgb.b);
                                
-               // update 'new' color
-               this._newColor.css({ backgroundColor: 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + (rgb.a / 100) + ')' });
-               
-               // adjust gradient color
-               var $rgb = this.hsvToRgb(this._hsv.h, 100, 100);
-               this._gradient.css({ backgroundColor: 'rgb(' + $rgb.r + ', ' + $rgb.g + ', ' + $rgb.b + ')' });
-       },
-       
-       /**
-        * Converts a HSV color into RGB.
-        * 
-        * @see https://secure.wikimedia.org/wikipedia/de/wiki/HSV-Farbraum#Transformation_von_RGB_und_HSV
-        * 
-        * @param       integer         h
-        * @param       integer         s
-        * @param       integer         v
-        * @return      object
-        */
-       hsvToRgb: function(h, s, v) {
-               return window.__wcf_bc_colorUtil.hsvToRgb(h, s, v);
-       },
-       
-       /**
-        * Converts a RGB color into HSV.
-        * 
-        * @see https://secure.wikimedia.org/wikipedia/de/wiki/HSV-Farbraum#Transformation_von_RGB_und_HSV
-        * 
-        * @param       integer         r
-        * @param       integer         g
-        * @param       integer         b
-        * @return      object
-        */
-       rgbToHsv: function(r, g, b) {
-               return window.__wcf_bc_colorUtil.rgbToHsv(r, g, b);
-       },
-       
-       /**
-        * Converts HEX into RGB.
-        * 
-        * @param       string          hex
-        * @return      object
-        */
-       hexToRgb: function(hex) {
-               return window.__wcf_bc_colorUtil.hexToRgb(hex);
-       },
-       
-       /**
-        * Converts a RGB into HEX.
-        * 
-        * @see http://www.linuxtopia.org/online_books/javascript_guides/javascript_faq/rgbtohex.htm
-        * 
-        * @param       integer         r
-        * @param       integer         g
-        * @param       integer         b
-        * @return      string
-        */
-       rgbToHex: function(r, g, b) {
-               return window.__wcf_bc_colorUtil.rgbToHex(r, g, b);
-       }
-});
-
-(function() {
-       if (window.__wcf_bc_colorUtil === undefined) {
-               require(['ColorUtil'], function (ColorUtil) {
-                       // void call to force module evaluation
-               });
-       }
+                               // adjust hue
+                               if (changeH) {
+                                       this._hsv.h = $hsv.h;
+                               }
+                               
+                               // adjust saturation and value
+                               if (changeSV) {
+                                       this._hsv.s = $hsv.s;
+                                       this._hsv.v = $hsv.v;
+                               }
+                       }
+                       
+                       // adjust bar selector
+                       var $top = Math.max(0, Math.min(255, 255 - (this._hsv.h / 360) * 255));
+                       this._barSelector.css({top: $top + 'px'});
+                       
+                       // adjust gradient selector
+                       var $left = Math.max(0, Math.min(255, (this._hsv.s / 100) * 255));
+                       var $top = Math.max(0, Math.min(255, 255 - ((this._hsv.v / 100) * 255)));
+                       this._gradientSelector.css({
+                               left: ($left - 6) + 'px',
+                               top: ($top - 6) + 'px'
+                       });
+                       
+                       // update 'new' color
+                       this._newColor.css({backgroundColor: 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + (rgb.a / 100) + ')'});
+                       
+                       // adjust gradient color
+                       var $rgb = this.hsvToRgb(this._hsv.h, 100, 100);
+                       this._gradient.css({backgroundColor: 'rgb(' + $rgb.r + ', ' + $rgb.g + ', ' + $rgb.b + ')'});
+               },
+               
+               /**
+                * Converts a HSV color into RGB.
+                *
+                * @see        https://secure.wikimedia.org/wikipedia/de/wiki/HSV-Farbraum#Transformation_von_RGB_und_HSV
+                *
+                * @param        integer                h
+                * @param        integer                s
+                * @param        integer                v
+                * @return        object
+                */
+               hsvToRgb: function (h, s, v) {
+                       return window.__wcf_bc_colorUtil.hsvToRgb(h, s, v);
+               },
+               
+               /**
+                * Converts a RGB color into HSV.
+                *
+                * @see        https://secure.wikimedia.org/wikipedia/de/wiki/HSV-Farbraum#Transformation_von_RGB_und_HSV
+                *
+                * @param        integer                r
+                * @param        integer                g
+                * @param        integer                b
+                * @return        object
+                */
+               rgbToHsv: function (r, g, b) {
+                       return window.__wcf_bc_colorUtil.rgbToHsv(r, g, b);
+               },
+               
+               /**
+                * Converts HEX into RGB.
+                *
+                * @param        string                hex
+                * @return        object
+                */
+               hexToRgb: function (hex) {
+                       return window.__wcf_bc_colorUtil.hexToRgb(hex);
+               },
+               
+               /**
+                * Converts a RGB into HEX.
+                *
+                * @see        http://www.linuxtopia.org/online_books/javascript_guides/javascript_faq/rgbtohex.htm
+                *
+                * @param        integer                r
+                * @param        integer                g
+                * @param        integer                b
+                * @return        string
+                */
+               rgbToHex: function (r, g, b) {
+                       return window.__wcf_bc_colorUtil.rgbToHex(r, g, b);
+               }
+       });
        
-       if (typeof window.__wcf_bc_colorPickerInit === 'function') {
-               window.__wcf_bc_colorPickerInit();
-       }
-})();
\ No newline at end of file
+       (function () {
+               if (window.__wcf_bc_colorUtil === undefined) {
+                       require(['ColorUtil'], function (ColorUtil) {
+                               // void call to force module evaluation
+                       });
+               }
+               
+               if (typeof window.__wcf_bc_colorPickerInit === 'function') {
+                       window.__wcf_bc_colorPickerInit();
+               }
+       })();
+}
+else {
+       WCF.ColorPicker = Class.extend({
+               _bar: {},
+               _barActive: false,
+               _barSelector: {},
+               _dialog: {},
+               _didInit: false,
+               _elementID: "",
+               _gradient: {},
+               _gradientActive: false,
+               _gradientSelector: {},
+               _hex: {},
+               _hsv: {},
+               _newColor: {},
+               _oldColor: {},
+               _rgba: {},
+               _rgbaRegExp: {},
+               init: function() {},
+               _open: function() {},
+               _parseColor: function() {},
+               _initColorPicker: function() {},
+               _initColorPickerForm: function() {},
+               _keyUpRGBA: function() {},
+               _keyUpHex: function() {},
+               _submit: function() {},
+               _createInputElement: function() {},
+               _mouseDownGradient: function() {},
+               _mouseGradient: function() {},
+               _mouseDownBar: function() {},
+               _mouseBar: function() {},
+               _blurRgba: function() {},
+               _blurHex: function() {},
+               _updateValues: function() {},
+               hsvToRgb: function (h, s, v) { return window.__wcf_bc_colorUtil.hsvToRgb(h, s, v); },
+               rgbToHsv: function (r, g, b) { return window.__wcf_bc_colorUtil.rgbToHsv(r, g, b); },
+               hexToRgb: function (hex) { return window.__wcf_bc_colorUtil.hexToRgb(hex); },
+               rgbToHex: function (r, g, b) { return window.__wcf_bc_colorUtil.rgbToHex(r, g, b); }
+       });
+}
\ No newline at end of file
index a9fc4c5d9a4393f1e7f441e57ced28b04a5e89e0..1450cd9f00d36b6dedfad8d76cb0d6ec394d0753 100644 (file)
  */
 WCF.Label = {};
 
-/**
- * Provides enhancements for ACP label management.
- */
-WCF.Label.ACPList = Class.extend({
-       /**
-        * input element
-        * @var jQuery
-        */
-       _labelInput: null,
-       
-       /**
-        * list of pre-defined label items
-        * @var array<jQuery>
-        */
-       _labelList: [ ],
-       
+if (COMPILER_TARGET_DEFAULT) {
        /**
-        * Initializes the ACP label list.
+        * Provides enhancements for ACP label management.
         */
-       init: function() {
-               this._labelInput = $('#label').keydown($.proxy(this._keyPressed, this)).keyup($.proxy(this._keyPressed, this)).blur($.proxy(this._keyPressed, this));
+       WCF.Label.ACPList = Class.extend({
+               /**
+                * input element
+                * @var        jQuery
+                */
+               _labelInput: null,
                
-               if ($.browser.mozilla && $.browser.touch) {
-                       this._labelInput.on('input', $.proxy(this._keyPressed, this));
-               }
+               /**
+                * list of pre-defined label items
+                * @var        array<jQuery>
+                */
+               _labelList: [],
                
-               $('#labelList').find('input[type="radio"]').each($.proxy(function(index, input) {
-                       var $input = $(input);
+               /**
+                * Initializes the ACP label list.
+                */
+               init: function () {
+                       this._labelInput = $('#label').keydown($.proxy(this._keyPressed, this)).keyup($.proxy(this._keyPressed, this)).blur($.proxy(this._keyPressed, this));
                        
-                       // ignore custom values
-                       if ($input.prop('value') !== 'custom') {
-                               this._labelList.push($($input.next('span')));
+                       if ($.browser.mozilla && $.browser.touch) {
+                               this._labelInput.on('input', $.proxy(this._keyPressed, this));
                        }
-               }, this));
-       },
-       
-       /**
-        * Renders label name as label or falls back to a default value if label is empty.
-        */
-       _keyPressed: function() {
-               var $text = this._labelInput.prop('value');
-               if ($text === '') $text = WCF.Language.get('wcf.acp.label.defaultValue');
+                       
+                       $('#labelList').find('input[type="radio"]').each($.proxy(function (index, input) {
+                               var $input = $(input);
+                               
+                               // ignore custom values
+                               if ($input.prop('value') !== 'custom') {
+                                       this._labelList.push($($input.next('span')));
+                               }
+                       }, this));
+               },
                
-               for (var $i = 0, $length = this._labelList.length; $i < $length; $i++) {
-                       this._labelList[$i].text($text);
+               /**
+                * Renders label name as label or falls back to a default value if label is empty.
+                */
+               _keyPressed: function () {
+                       var $text = this._labelInput.prop('value');
+                       if ($text === '') $text = WCF.Language.get('wcf.acp.label.defaultValue');
+                       
+                       for (var $i = 0, $length = this._labelList.length; $i < $length; $i++) {
+                               this._labelList[$i].text($text);
+                       }
                }
-       }
-});
-
-/**
- * Provides simple logic to inherit associations within structured lists.
- */
-WCF.Label.ACPList.Connect = Class.extend({
-       /**
-        * Initializes inheritation for structured lists.
-        */
-       init: function() {
-               var $listItems = $('#connect .structuredList li');
-               if (!$listItems.length) return;
-               
-               $listItems.each($.proxy(function(index, item) {
-                       $(item).find('input[type="checkbox"]').click($.proxy(this._click, this));
-               }, this));
-       },
+       });
        
        /**
-        * Marks items as checked if they're logically below current item.
-        * 
-        * @param       object          event
+        * Provides simple logic to inherit associations within structured lists.
         */
-       _click: function(event) {
-               var $listItem = $(event.currentTarget);
-               if ($listItem.is(':checked')) {
-                       $listItem = $listItem.parents('li');
-                       var $depth = $listItem.data('depth');
+       WCF.Label.ACPList.Connect = Class.extend({
+               /**
+                * Initializes inheritation for structured lists.
+                */
+               init: function () {
+                       var $listItems = $('#connect .structuredList li');
+                       if (!$listItems.length) return;
                        
-                       while (true) {
-                               $listItem = $listItem.next();
-                               if (!$listItem.length) {
-                                       // no more siblings
-                                       return true;
-                               }
+                       $listItems.each($.proxy(function (index, item) {
+                               $(item).find('input[type="checkbox"]').click($.proxy(this._click, this));
+                       }, this));
+               },
+               
+               /**
+                * Marks items as checked if they're logically below current item.
+                *
+                * @param        object                event
+                */
+               _click: function (event) {
+                       var $listItem = $(event.currentTarget);
+                       if ($listItem.is(':checked')) {
+                               $listItem = $listItem.parents('li');
+                               var $depth = $listItem.data('depth');
                                
-                               // element is on the same or higher level (= lower depth)
-                               if ($listItem.data('depth') <= $depth) {
-                                       return true;
+                               while (true) {
+                                       $listItem = $listItem.next();
+                                       if (!$listItem.length) {
+                                               // no more siblings
+                                               return true;
+                                       }
+                                       
+                                       // element is on the same or higher level (= lower depth)
+                                       if ($listItem.data('depth') <= $depth) {
+                                               return true;
+                                       }
+                                       
+                                       $listItem.find('input[type="checkbox"]').prop('checked', 'checked');
                                }
-                               
-                               $listItem.find('input[type="checkbox"]').prop('checked', 'checked');
                        }
                }
-       }
-});
+       });
+}
+else {
+       WCF.Label.ACPList = Class.extend({
+               _labelInput: {},
+               _labelList: {},
+               init: function() {},
+               _keyPressed: function() {}
+       });
+       
+       WCF.Label.ACPList.Connect = Class.extend({
+               init: function() {},
+               _click: function() {}
+       });
+}
 
 /**
  * Provides a flexible label chooser.
@@ -280,64 +295,74 @@ WCF.Label.Chooser = Class.extend({
        }
 });
 
-/**
- * Handles displaying label groups based on the selected categories.
- */
-WCF.Label.ArticleLabelChooser = WCF.Label.Chooser.extend({
-       /**
-        * maps the available label group ids to the categories
-        * @var object
-        */
-       _labelGroupsToCategories: null,
-       
-       /**
-        * Initializes a new WCF.Label.ArticleLabelChooser object.
-        *
-        * @param       object          labelGroupsToCategories
-        * @param       object          selectedLabelIDs
-        * @param       string          containerSelector
-        * @param       string          submitButtonSelector
-        * @param       boolean         showWithoutSelection
-        */
-       init: function(labelGroupsToCategories, selectedLabelIDs, containerSelector, submitButtonSelector, showWithoutSelection) {
-               this._super(selectedLabelIDs, containerSelector, submitButtonSelector, showWithoutSelection);
-               this._labelGroupsToCategories = labelGroupsToCategories;
-               
-               this._updateLabelGroups();
-               
-               $('#categoryID').change($.proxy(this._updateLabelGroups, this));
-       },
-       
+if (COMPILER_TARGET_DEFAULT) {
        /**
-        * Updates the visible label groups based on the selected categories.
+        * Handles displaying label groups based on the selected categories.
         */
-       _updateLabelGroups: function() {
-               // hide all label choosers first
-               $('.labelChooser').each(function(index, element) {
-                       $(element).parents('dl:eq(0)').hide();
-               })
+       WCF.Label.ArticleLabelChooser = WCF.Label.Chooser.extend({
+               /**
+                * maps the available label group ids to the categories
+                * @var        object
+                */
+               _labelGroupsToCategories: null,
                
-               var visibleGroupIDs = [];
-               var categoryID = parseInt($('#categoryID').val());
+               /**
+                * Initializes a new WCF.Label.ArticleLabelChooser object.
+                *
+                * @param        object                labelGroupsToCategories
+                * @param        object                selectedLabelIDs
+                * @param        string                containerSelector
+                * @param        string                submitButtonSelector
+                * @param        boolean                showWithoutSelection
+                */
+               init: function (labelGroupsToCategories, selectedLabelIDs, containerSelector, submitButtonSelector, showWithoutSelection) {
+                       this._super(selectedLabelIDs, containerSelector, submitButtonSelector, showWithoutSelection);
+                       this._labelGroupsToCategories = labelGroupsToCategories;
+                       
+                       this._updateLabelGroups();
+                       
+                       $('#categoryID').change($.proxy(this._updateLabelGroups, this));
+               },
                
-               if (this._labelGroupsToCategories[categoryID]) {
-                       for (var i = 0, length = this._labelGroupsToCategories[categoryID].length; i < length; i++) {
-                               $('#labelGroup' + this._labelGroupsToCategories[categoryID][i]).parents('dl:eq(0)').show();
+               /**
+                * Updates the visible label groups based on the selected categories.
+                */
+               _updateLabelGroups: function () {
+                       // hide all label choosers first
+                       $('.labelChooser').each(function (index, element) {
+                               $(element).parents('dl:eq(0)').hide();
+                       })
+                       
+                       var visibleGroupIDs = [];
+                       var categoryID = parseInt($('#categoryID').val());
+                       
+                       if (this._labelGroupsToCategories[categoryID]) {
+                               for (var i = 0, length = this._labelGroupsToCategories[categoryID].length; i < length; i++) {
+                                       $('#labelGroup' + this._labelGroupsToCategories[categoryID][i]).parents('dl:eq(0)').show();
+                               }
                        }
-               }
-       },
-       
-       /**
-        * @see WCF.Label.Chooser._submit()
-        */
-       _submit: function() {
-               // delete non-selected groups to avoid sumitting these labels
-               for (var groupID in this._groups) {
-                       if (!this._groups[groupID].is(':visible')) {
-                               delete this._groups[groupID];
+               },
+               
+               /**
+                * @see        WCF.Label.Chooser._submit()
+                */
+               _submit: function () {
+                       // delete non-selected groups to avoid sumitting these labels
+                       for (var groupID in this._groups) {
+                               if (!this._groups[groupID].is(':visible')) {
+                                       delete this._groups[groupID];
+                               }
                        }
+                       
+                       this._super();
                }
-               
-               this._super();
-       }
-});
\ No newline at end of file
+       });
+}
+else {
+       WCF.Label.ArticleLabelChooser = WCF.Label.Chooser.extend({
+               _labelGroupsToCategories: {},
+               init: function() {},
+               _updateLabelGroups: function () {},
+               _submit: function() {}
+       });
+}
index ae43b40c22fb18852b0ce4034fad32b37b4fa39d..7f9f566ae44ab0bb6153b91078e3ed4437d5f07c 100644 (file)
@@ -737,21 +737,17 @@ WCF.Location.GoogleMaps.LocationSearch = WCF.Search.Base.extend({
                        case $.ui.keyCode.LEFT:
                        case $.ui.keyCode.RIGHT:
                                return;
-                       break;
                        
                        case $.ui.keyCode.UP:
                                this._selectPreviousItem();
                                return;
-                       break;
                        
                        case $.ui.keyCode.DOWN:
                                this._selectNextItem();
                                return;
-                       break;
                        
                        case $.ui.keyCode.ENTER:
                                return this._selectElement(event);
-                       break;
                }
                
                var $content = this._getSearchString(event);
index cb9185363a9549cf9ec2f244586ce8d701ec82d1..dab7720740c620c759d8f2aa9e2f247170fb48eb 100644 (file)
@@ -86,190 +86,206 @@ WCF.Message.BBCode.CodeViewer = Class.extend({
        }
 });
 
-/**
- * Provides the dynamic parts of the edit history interface.
- */
-WCF.Message.EditHistory = Class.extend({
-       /**
-        * jQuery object containing the radio buttons for the oldID
-        * @var object
-        */
-       _oldIDInputs: null,
-       
-       /**
-        * jQuery object containing the radio buttons for the oldID
-        * @var object
-        */
-       _newIDInputs: null,
-       
-       /**
-        * selector for the version rows
-        * @var string
-        */
-       _containerSelector: '',
-       
-       /**
-        * selector for the revert button
-        * @var string
-        */
-       _buttonSelector: '.jsRevertButton',
-       
-       /**
-        * Initializes the edit history interface.
-        * 
-        * @param       object  oldIDInputs
-        * @param       object  newIDInputs
-        * @param       string  containerSelector
-        * @param       string  buttonSelector
-        * @param       {Object}        options
-        */
-       init: function(oldIDInputs, newIDInputs, containerSelector, buttonSelector, options) {
-               this._oldIDInputs = oldIDInputs;
-               this._newIDInputs = newIDInputs;
-               this._containerSelector = containerSelector;
-               this._buttonSelector = (buttonSelector) ? buttonSelector : '.jsRevertButton';
-               this._options = $.extend({
-                       isVersionTracker: false,
-                       versionTrackerObjectType: '',
-                       versionTrackerObjectId: 0,
-                       redirectUrl: ''
-               }, options);
-               
-               this.proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               this._initInputs();
-               this._initElements();
-       },
-       
-       /**
-        * Initializes the radio buttons.
-        * Force the "oldID" to be lower than the "newID"
-        * 'current' is interpreted as Infinity.
-        */
-       _initInputs: function() {
-               var self = this;
-               this._newIDInputs.change(function(event) {
-                       var newID = parseInt($(this).val());
-                       if ($(this).val() === 'current') newID = Infinity;
+if (COMPILER_TARGET_DEFAULT) {
+       /**
+        * Provides the dynamic parts of the edit history interface.
+        */
+       WCF.Message.EditHistory = Class.extend({
+               /**
+                * jQuery object containing the radio buttons for the oldID
+                * @var        object
+                */
+               _oldIDInputs: null,
+               
+               /**
+                * jQuery object containing the radio buttons for the oldID
+                * @var        object
+                */
+               _newIDInputs: null,
+               
+               /**
+                * selector for the version rows
+                * @var        string
+                */
+               _containerSelector: '',
+               
+               /**
+                * selector for the revert button
+                * @var        string
+                */
+               _buttonSelector: '.jsRevertButton',
+               
+               /**
+                * Initializes the edit history interface.
+                *
+                * @param        object        oldIDInputs
+                * @param        object        newIDInputs
+                * @param        string        containerSelector
+                * @param        string        buttonSelector
+                * @param       {Object}        options
+                */
+               init: function (oldIDInputs, newIDInputs, containerSelector, buttonSelector, options) {
+                       this._oldIDInputs = oldIDInputs;
+                       this._newIDInputs = newIDInputs;
+                       this._containerSelector = containerSelector;
+                       this._buttonSelector = (buttonSelector) ? buttonSelector : '.jsRevertButton';
+                       this._options = $.extend({
+                               isVersionTracker: false,
+                               versionTrackerObjectType: '',
+                               versionTrackerObjectId: 0,
+                               redirectUrl: ''
+                       }, options);
                        
-                       self._oldIDInputs.each(function(event) {
-                               var oldID = parseInt($(this).val());
-                               if ($(this).val() === 'current') oldID = Infinity;
-                               
-                               if (oldID >= newID) {
-                                       $(this).disable();
-                               }
-                               else {
-                                       $(this).enable();
-                               }
+                       this.proxy = new WCF.Action.Proxy({
+                               success: $.proxy(this._success, this)
                        });
-               });
-               
-               this._oldIDInputs.change(function(event) {
-                       var oldID = parseInt($(this).val());
-                       if ($(this).val() === 'current') oldID = Infinity;
                        
-                       self._newIDInputs.each(function(event) {
+                       this._initInputs();
+                       this._initElements();
+               },
+               
+               /**
+                * Initializes the radio buttons.
+                * Force the "oldID" to be lower than the "newID"
+                * 'current' is interpreted as Infinity.
+                */
+               _initInputs: function () {
+                       var self = this;
+                       this._newIDInputs.change(function (event) {
                                var newID = parseInt($(this).val());
                                if ($(this).val() === 'current') newID = Infinity;
                                
-                               if (newID <= oldID) {
-                                       $(this).disable();
-                               }
-                               else {
-                                       $(this).enable();
-                               }
+                               self._oldIDInputs.each(function (event) {
+                                       var oldID = parseInt($(this).val());
+                                       if ($(this).val() === 'current') oldID = Infinity;
+                                       
+                                       if (oldID >= newID) {
+                                               $(this).disable();
+                                       }
+                                       else {
+                                               $(this).enable();
+                                       }
+                               });
                        });
-               });
-               this._oldIDInputs.filter(':checked').change();
-               this._newIDInputs.filter(':checked').change();
-       },
-       
-       /**
-        * Initializes available element containers.
-        */
-       _initElements: function() {
-               var self = this;
-               $(this._containerSelector).each(function(index, container) {
-                       var $container = $(container);
-                       $container.find(self._buttonSelector).click($.proxy(self._click, self));
-               });
-       },
-       
-       /**
-        * Sends AJAX request.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               var $target = $(event.currentTarget);
-               event.preventDefault();
-               
-               if ($target.data('confirmMessage')) {
-                       var self = this;
                        
-                       WCF.System.Confirmation.show($target.data('confirmMessage'), function(action) {
-                               if (action === 'cancel') return;
+                       this._oldIDInputs.change(function (event) {
+                               var oldID = parseInt($(this).val());
+                               if ($(this).val() === 'current') oldID = Infinity;
                                
-                               self._sendRequest($target);
-                       }, undefined, undefined, true);
-               }
-               else {
-                       this._sendRequest($target);
-               }
-       },
-       
-       
-       /**
-        * Sends the request
-        * 
-        * @param       jQuery  object
-        */
-       _sendRequest: function(object) {
-               if (this._options.isVersionTracker) {
-                       //noinspection JSUnresolvedVariable
-                       this.proxy.setOption('url', window.WSC_API_URL + 'index.php?ajax-invoke/&t=' + window.SECURITY_TOKEN);
-                       this.proxy.setOption('data', {
-                               actionName: 'revert',
-                               className: 'wcf\\system\\version\\VersionTracker',
-                               parameters: {
-                                       objectType: this._options.versionTrackerObjectType,
-                                       objectID: this._options.versionTrackerObjectId,
-                                       versionID: $(object).data('objectID')
-                               }
+                               self._newIDInputs.each(function (event) {
+                                       var newID = parseInt($(this).val());
+                                       if ($(this).val() === 'current') newID = Infinity;
+                                       
+                                       if (newID <= oldID) {
+                                               $(this).disable();
+                                       }
+                                       else {
+                                               $(this).enable();
+                                       }
+                               });
                        });
-               }
-               else {
-                       this.proxy.setOption('data', {
-                               actionName: 'revert',
-                               className: 'wcf\\data\\edit\\history\\entry\\EditHistoryEntryAction',
-                               objectIDs: [$(object).data('objectID')]
+                       this._oldIDInputs.filter(':checked').change();
+                       this._newIDInputs.filter(':checked').change();
+               },
+               
+               /**
+                * Initializes available element containers.
+                */
+               _initElements: function () {
+                       var self = this;
+                       $(this._containerSelector).each(function (index, container) {
+                               var $container = $(container);
+                               $container.find(self._buttonSelector).click($.proxy(self._click, self));
                        });
+               },
+               
+               /**
+                * Sends AJAX request.
+                *
+                * @param        object                event
+                */
+               _click: function (event) {
+                       var $target = $(event.currentTarget);
+                       event.preventDefault();
+                       
+                       if ($target.data('confirmMessage')) {
+                               var self = this;
+                               
+                               WCF.System.Confirmation.show($target.data('confirmMessage'), function (action) {
+                                       if (action === 'cancel') return;
+                                       
+                                       self._sendRequest($target);
+                               }, undefined, undefined, true);
+                       }
+                       else {
+                               this._sendRequest($target);
+                       }
+               },
+               
+               
+               /**
+                * Sends the request
+                *
+                * @param        jQuery        object
+                */
+               _sendRequest: function (object) {
+                       if (this._options.isVersionTracker) {
+                               //noinspection JSUnresolvedVariable
+                               this.proxy.setOption('url', window.WSC_API_URL + 'index.php?ajax-invoke/&t=' + window.SECURITY_TOKEN);
+                               this.proxy.setOption('data', {
+                                       actionName: 'revert',
+                                       className: 'wcf\\system\\version\\VersionTracker',
+                                       parameters: {
+                                               objectType: this._options.versionTrackerObjectType,
+                                               objectID: this._options.versionTrackerObjectId,
+                                               versionID: $(object).data('objectID')
+                                       }
+                               });
+                       }
+                       else {
+                               this.proxy.setOption('data', {
+                                       actionName: 'revert',
+                                       className: 'wcf\\data\\edit\\history\\entry\\EditHistoryEntryAction',
+                                       objectIDs: [$(object).data('objectID')]
+                               });
+                       }
+                       
+                       this.proxy.sendRequest();
+               },
+               
+               /**
+                * Reloads the page to show the new versions.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        object                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       if (this._options.redirectUrl) {
+                               new WCF.System.Notification().show((function () {
+                                       window.location = this._options.redirectUrl;
+                               }).bind(this));
+                       }
+                       else {
+                               window.location.reload(true);
+                       }
                }
-               
-               this.proxy.sendRequest();
-       },
-       
-       /**
-        * Reloads the page to show the new versions.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       object          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               if (this._options.redirectUrl) {
-                       new WCF.System.Notification().show((function () {
-                               window.location = this._options.redirectUrl;
-                       }).bind(this));
-               }
-               else {
-                       window.location.reload(true);
-               }
-       }
-});
+       });
+}
+else {
+       WCF.Message.EditHistory = Class.extend({
+               _oldIDInputs: {},
+               _newIDInputs: {},
+               _containerSelector: "",
+               _buttonSelector: "",
+               init: function() {},
+               _initInputs: function() {},
+               _initElements: function() {},
+               _click: function() {},
+               _sendRequest: function() {},
+               _success: function() {}
+       });
+}
 
 /**
  * Prevents multiple submits of the same form by disabling the submit button.
@@ -290,1540 +306,1695 @@ WCF.Message.FormGuard = Class.extend({
        }
 });
 
-/**
- * Provides previews for Redactor message fields.
- * 
- * @param      string          className
- * @param      string          messageFieldID
- * @param      string          previewButtonID
- */
-WCF.Message.Preview = Class.extend({
-       /**
-        * class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * message field id
-        * @var string
-        */
-       _messageFieldID: '',
-       
-       /**
-        * message field
-        * @var jQuery
-        */
-       _messageField: null,
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * preview button
-        * @var jQuery
-        */
-       _previewButton: null,
-       
-       /**
-        * previous button label
-        * @var string
-        */
-       _previewButtonLabel: '',
-       
-       /**
-        * Initializes a new WCF.Message.Preview object.
-        * 
-        * @param       string          className
-        * @param       string          messageFieldID
-        * @param       string          previewButtonID
-        */
-       init: function(className, messageFieldID, previewButtonID) {
-               this._className = className;
-               
-               // validate message field
-               this._messageFieldID = $.wcfEscapeID(messageFieldID);
-               this._textarea = $('#' + this._messageFieldID);
-               if (!this._textarea.length) {
-                       console.debug("[WCF.Message.Preview] Unable to find message field identified by '" + this._messageFieldID + "'");
-                       return;
-               }
-               
-               // validate preview button
-               previewButtonID = $.wcfEscapeID(previewButtonID);
-               this._previewButton = $('#' + previewButtonID);
-               if (!this._previewButton.length) {
-                       console.debug("[WCF.Message.Preview] Unable to find preview button identified by '" + previewButtonID + "'");
-                       return;
+if (COMPILER_TARGET_DEFAULT) {
+       /**
+        * Provides previews for Redactor message fields.
+        *
+        * @param        string                className
+        * @param        string                messageFieldID
+        * @param        string                previewButtonID
+        */
+       WCF.Message.Preview = Class.extend({
+               /**
+                * class name
+                * @var        string
+                */
+               _className: '',
+               
+               /**
+                * message field id
+                * @var        string
+                */
+               _messageFieldID: '',
+               
+               /**
+                * message field
+                * @var        jQuery
+                */
+               _messageField: null,
+               
+               /**
+                * action proxy
+                * @var        WCF.Action.Proxy
+                */
+               _proxy: null,
+               
+               /**
+                * preview button
+                * @var        jQuery
+                */
+               _previewButton: null,
+               
+               /**
+                * previous button label
+                * @var        string
+                */
+               _previewButtonLabel: '',
+               
+               /**
+                * Initializes a new WCF.Message.Preview object.
+                *
+                * @param        string                className
+                * @param        string                messageFieldID
+                * @param        string                previewButtonID
+                */
+               init: function (className, messageFieldID, previewButtonID) {
+                       this._className = className;
+                       
+                       // validate message field
+                       this._messageFieldID = $.wcfEscapeID(messageFieldID);
+                       this._textarea = $('#' + this._messageFieldID);
+                       if (!this._textarea.length) {
+                               console.debug("[WCF.Message.Preview] Unable to find message field identified by '" + this._messageFieldID + "'");
+                               return;
+                       }
+                       
+                       // validate preview button
+                       previewButtonID = $.wcfEscapeID(previewButtonID);
+                       this._previewButton = $('#' + previewButtonID);
+                       if (!this._previewButton.length) {
+                               console.debug("[WCF.Message.Preview] Unable to find preview button identified by '" + previewButtonID + "'");
+                               return;
+                       }
+                       
+                       this._previewButton.click($.proxy(this._click, this));
+                       this._proxy = new WCF.Action.Proxy({
+                               failure: $.proxy(this._failure, this),
+                               success: $.proxy(this._success, this)
+                       });
+               },
+               
+               /**
+                * Reads message field input and triggers an AJAX request.
+                */
+               _click: function (event) {
+                       var $message = this._getMessage();
+                       if ($message === null) {
+                               console.debug("[WCF.Message.Preview] Unable to access Redactor instance of '" + this._messageFieldID + "'");
+                               return;
+                       }
+                       
+                       this._proxy.setOption('data', {
+                               actionName: 'getMessagePreview',
+                               className: this._className,
+                               parameters: this._getParameters($message)
+                       });
+                       this._proxy.sendRequest();
+                       
+                       // update button label
+                       this._previewButtonLabel = this._previewButton.html();
+                       this._previewButton.html(WCF.Language.get('wcf.global.loading')).disable();
+                       
+                       // poke event
+                       event.stopPropagation();
+                       return false;
+               },
+               
+               /**
+                * Returns request parameters.
+                *
+                * @param        string                message
+                * @return        object
+                */
+               _getParameters: function (message) {
+                       // collect message form options
+                       var $options = {};
+                       $('#settings_' + this._messageFieldID).find('input[type=checkbox]').each(function (index, checkbox) {
+                               var $checkbox = $(checkbox);
+                               if ($checkbox.is(':checked')) {
+                                       $options[$checkbox.prop('name')] = $checkbox.prop('value');
+                               }
+                       });
+                       
+                       // build parameters
+                       return {
+                               data: {
+                                       message: message
+                               },
+                               options: $options
+                       };
+               },
+               
+               /**
+                * Returns parsed message from Redactor or null if editor was not accessible.
+                *
+                * @return        string
+                */
+               _getMessage: function () {
+                       return this._textarea.redactor('code.get');
+               },
+               
+               /**
+                * Handles successful AJAX requests.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        jQuery                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       // restore preview button
+                       this._previewButton.html(this._previewButtonLabel).enable();
+                       
+                       // remove error message
+                       this._textarea.parent().children('small.innerError').remove();
+                       
+                       // evaluate message
+                       this._handleResponse(data);
+               },
+               
+               /**
+                * Evaluates response data.
+                *
+                * @param        object                data
+                */
+               _handleResponse: function (data) {
+               },
+               
+               /**
+                * Handles errors during preview requests.
+                *
+                * The return values indicates if the default error overlay is shown.
+                *
+                * @param        object                data
+                * @return        boolean
+                */
+               _failure: function (data) {
+                       if (data === null || data.returnValues === undefined || data.returnValues.errorType === undefined) {
+                               return true;
+                       }
+                       
+                       // restore preview button
+                       this._previewButton.html(this._previewButtonLabel).enable();
+                       
+                       var $innerError = this._textarea.parent().children('small.innerError').empty();
+                       if (!$innerError.length) {
+                               $innerError = $('<small class="innerError" />').appendTo(this._textarea.parent());
+                       }
+                       
+                       $innerError.html((data.returnValues.errorType === 'empty' ? WCF.Language.get('wcf.global.form.error.empty') : data.returnValues.errorMessage));
+                       
+                       return false;
                }
-               
-               this._previewButton.click($.proxy(this._click, this));
-               this._proxy = new WCF.Action.Proxy({
-                       failure: $.proxy(this._failure, this),
-                       success: $.proxy(this._success, this)
-               });
-       },
+       });
        
        /**
-        * Reads message field input and triggers an AJAX request.
+        * Default implementation for message previews.
+        *
+        * @see        WCF.Message.Preview
         */
-       _click: function(event) {
-               var $message = this._getMessage();
-               if ($message === null) {
-                       console.debug("[WCF.Message.Preview] Unable to access Redactor instance of '" + this._messageFieldID + "'");
-                       return;
-               }
-               
-               this._proxy.setOption('data', {
-                       actionName: 'getMessagePreview',
-                       className: this._className,
-                       parameters: this._getParameters($message)
-               });
-               this._proxy.sendRequest();
-               
-               // update button label
-               this._previewButtonLabel = this._previewButton.html();
-               this._previewButton.html(WCF.Language.get('wcf.global.loading')).disable();
+       WCF.Message.DefaultPreview = WCF.Message.Preview.extend({
+               _dialog: null,
+               _options: {},
                
-               // poke event
-               event.stopPropagation();
-               return false;
-       },
-       
-       /**
-        * Returns request parameters.
-        * 
-        * @param       string          message
-        * @return      object
-        */
-       _getParameters: function(message) {
-               // collect message form options
-               var $options = { };
-               $('#settings_' + this._messageFieldID).find('input[type=checkbox]').each(function(index, checkbox) {
-                       var $checkbox = $(checkbox);
-                       if ($checkbox.is(':checked')) {
-                               $options[$checkbox.prop('name')] = $checkbox.prop('value');
+               /**
+                * @see        WCF.Message.Preview.init()
+                */
+               init: function (options) {
+                       if (arguments.length > 1 && typeof options === 'string') {
+                               throw new Error("Outdated API call, please update your implementation.");
                        }
-               });
-               
-               // build parameters
-               return {
-                       data: {
-                               message: message
-                       },
-                       options: $options
-               };
-       },
-       
-       /**
-        * Returns parsed message from Redactor or null if editor was not accessible.
-        * 
-        * @return      string
-        */
-       _getMessage: function() {
-               return this._textarea.redactor('code.get');
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               // restore preview button
-               this._previewButton.html(this._previewButtonLabel).enable();
-               
-               // remove error message
-               this._textarea.parent().children('small.innerError').remove();
+                       
+                       this._options = $.extend({
+                               disallowedBBCodesPermission: 'user.message.disallowedBBCodes',
+                               messageFieldID: '',
+                               previewButtonID: '',
+                               messageObjectType: '',
+                               messageObjectID: 0
+                       }, options);
+                       
+                       if (!this._options.messageObjectType) {
+                               throw new Error("Field 'messageObjectType' cannot be empty.");
+                       }
+                       
+                       this._super('wcf\\data\\bbcode\\MessagePreviewAction', this._options.messageFieldID, this._options.previewButtonID);
+               },
+               
+               /**
+                * @see        WCF.Message.Preview._handleResponse()
+                */
+               _handleResponse: function (data) {
+                       require(['WoltLabSuite/Core/Ui/Dialog'], (function (UiDialog) {
+                               UiDialog.open(this, '<div class="htmlContent">' + data.returnValues.message + '</div>');
+                       }).bind(this));
+               },
                
-               // evaluate message
-               this._handleResponse(data);
-       },
-       
-       /**
-        * Evaluates response data.
-        * 
-        * @param       object          data
-        */
-       _handleResponse: function(data) { },
-       
-       /**
-        * Handles errors during preview requests.
-        * 
-        * The return values indicates if the default error overlay is shown.
-        * 
-        * @param       object          data
-        * @return      boolean
-        */
-       _failure: function(data) {
-               if (data === null || data.returnValues === undefined || data.returnValues.errorType === undefined) {
-                       return true;
+               /**
+                * @see        WCF.Message.Preview._getParameters()
+                */
+               _getParameters: function (message) {
+                       var $parameters = this._super(message);
+                       
+                       for (var key in this._options) {
+                               if (this._options.hasOwnProperty(key) && key !== 'messageFieldID' && key !== 'previewButtonID') {
+                                       $parameters[key] = this._options[key];
+                               }
+                       }
+                       
+                       return $parameters;
+               },
+               
+               _dialogSetup: function () {
+                       return {
+                               id: 'messagePreview',
+                               options: {
+                                       title: WCF.Language.get('wcf.global.preview')
+                               },
+                               source: null
+                       }
                }
-               
-               // restore preview button
-               this._previewButton.html(this._previewButtonLabel).enable();
-               
-               var $innerError = this._textarea.parent().children('small.innerError').empty();
-               if (!$innerError.length) {
-                       $innerError = $('<small class="innerError" />').appendTo(this._textarea.parent());
+       });
+       
+       /**
+        * Handles multilingualism for messages.
+        *
+        * @param        integer                languageID
+        * @param        object                availableLanguages
+        * @param        boolean                forceSelection
+        */
+       WCF.Message.Multilingualism = Class.extend({
+               /**
+                * list of available languages
+                * @var        object
+                */
+               _availableLanguages: {},
+               
+               /**
+                * language id
+                * @var        integer
+                */
+               _languageID: 0,
+               
+               /**
+                * language input element
+                * @var        jQuery
+                */
+               _languageInput: null,
+               
+               /**
+                * Initializes WCF.Message.Multilingualism
+                *
+                * @param        integer                languageID
+                * @param        object                availableLanguages
+                * @param        boolean                forceSelection
+                */
+               init: function (languageID, availableLanguages, forceSelection) {
+                       this._availableLanguages = availableLanguages;
+                       this._languageID = languageID || 0;
+                       
+                       this._languageInput = $('#languageID');
+                       
+                       // preselect current language id
+                       this._updateLabel();
+                       
+                       // register event listener
+                       this._languageInput.find('.dropdownMenu > li').click($.proxy(this._click, this));
+                       
+                       // add element to disable multilingualism
+                       if (!forceSelection) {
+                               var $dropdownMenu = this._languageInput.find('.dropdownMenu');
+                               $('<li class="dropdownDivider" />').appendTo($dropdownMenu);
+                               $('<li><span><span class="badge">' + this._availableLanguages[0] + '</span></span></li>').click($.proxy(this._disable, this)).appendTo($dropdownMenu);
+                       }
+                       
+                       // bind submit event
+                       this._languageInput.parents('form').submit($.proxy(this._submit, this));
+               },
+               
+               /**
+                * Handles language selections.
+                *
+                * @param        object                event
+                */
+               _click: function (event) {
+                       this._languageID = $(event.currentTarget).data('languageID');
+                       this._updateLabel();
+               },
+               
+               /**
+                * Disables language selection.
+                */
+               _disable: function () {
+                       this._languageID = 0;
+                       this._updateLabel();
+               },
+               
+               /**
+                * Updates selected language.
+                */
+               _updateLabel: function () {
+                       this._languageInput.find('.dropdownToggle > span').text(this._availableLanguages[this._languageID]);
+               },
+               
+               /**
+                * Sets language id upon submit.
+                */
+               _submit: function () {
+                       this._languageInput.next('input[name=languageID]').prop('value', this._languageID);
+               }
+       });
+       
+       /**
+        * Loads smiley categories upon user request.
+        */
+       WCF.Message.SmileyCategories = Class.extend({
+               /**
+                * list of already loaded category ids
+                * @var        array<integer>
+                */
+               _cache: [],
+               
+               /**
+                * action proxy
+                * @var        WCF.Action.Proxy
+                */
+               _proxy: null,
+               
+               /**
+                * wysiwyg editor selector
+                * @var        string
+                */
+               _wysiwygSelector: '',
+               
+               /**
+                * Initializes the smiley loader.
+                *
+                * @param        string                wysiwygSelector
+                */
+               init: function (wysiwygSelector) {
+                       this._proxy = new WCF.Action.Proxy({
+                               success: $.proxy(this._success, this)
+                       });
+                       this._wysiwygSelector = wysiwygSelector;
+                       
+                       $('#smilies-' + this._wysiwygSelector).on('messagetabmenushow', $.proxy(this._click, this));
+               },
+               
+               /**
+                * Handles tab menu clicks.
+                *
+                * @param        {Event}        event
+                * @param        {Object}        data
+                */
+               _click: function (event, data) {
+                       event.preventDefault();
+                       
+                       var $categoryID = parseInt(data.activeTab.tab.data('smileyCategoryID'));
+                       
+                       // ignore global category, will always be pre-loaded
+                       if (!$categoryID) {
+                               return;
+                       }
+                       
+                       // smilies have already been loaded for this tab, ignore
+                       if (data.activeTab.container.children('ul.smileyList').length) {
+                               return;
+                       }
+                       
+                       // cache exists
+                       if (this._cache[$categoryID] !== undefined) {
+                               data.activeTab.container.html(this._cache[$categoryID]);
+                               return;
+                       }
+                       
+                       // load content
+                       this._proxy.setOption('data', {
+                               actionName: 'getSmilies',
+                               className: 'wcf\\data\\smiley\\category\\SmileyCategoryAction',
+                               objectIDs: [$categoryID]
+                       });
+                       this._proxy.sendRequest();
+               },
+               
+               /**
+                * Handles successful AJAX requests.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        jQuery                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       var $categoryID = parseInt(data.returnValues.smileyCategoryID);
+                       this._cache[$categoryID] = data.returnValues.template;
+                       
+                       $('#smilies-' + this._wysiwygSelector + '-' + $categoryID).html(data.returnValues.template);
                }
-               
-               $innerError.html((data.returnValues.errorType === 'empty' ? WCF.Language.get('wcf.global.form.error.empty') : data.returnValues.errorMessage));
-               
-               return false;
-       }
-});
-
-/**
- * Default implementation for message previews.
- * 
- * @see        WCF.Message.Preview
- */
-WCF.Message.DefaultPreview = WCF.Message.Preview.extend({
-       _dialog: null,
-       _options: {},
+       });
        
        /**
-        * @see WCF.Message.Preview.init()
+        * Handles smiley clicks.
+        *
+        * @param        string                wysiwygSelector
         */
-       init: function(options) {
-               if (arguments.length > 1 && typeof options === 'string') {
-                       throw new Error("Outdated API call, please update your implementation.");
-               }
+       WCF.Message.Smilies = Class.extend({
+               /**
+                * wysiwyg editor id
+                * @var        string
+                */
+               _editorId: '',
                
-               this._options = $.extend({
-                       disallowedBBCodesPermission: 'user.message.disallowedBBCodes',
-                       messageFieldID: '',
-                       previewButtonID: '',
-                       messageObjectType: '',
-                       messageObjectID: 0
-               }, options);
-               
-               if (!this._options.messageObjectType) {
-                       throw new Error("Field 'messageObjectType' cannot be empty.");
+               /**
+                * Initializes the smiley handler.
+                *
+                * @param        {string}        editorId
+                */
+               init: function (editorId) {
+                       this._editorId = editorId;
+                       
+                       $('.messageTabMenu[data-wysiwyg-container-id=' + this._editorId + ']').on('mousedown', '.jsSmiley', this._smileyClick.bind(this));
+               },
+               
+               /**
+                * Handles tab smiley clicks.
+                *
+                * @param        {Event}                event
+                */
+               _smileyClick: function (event) {
+                       event.preventDefault();
+                       
+                       require(['EventHandler'], (function (EventHandler) {
+                               EventHandler.fire('com.woltlab.wcf.redactor2', 'insertSmiley_' + this._editorId, {
+                                       img: event.currentTarget.children[0]
+                               });
+                       }).bind(this));
                }
-               
-               this._super('wcf\\data\\bbcode\\MessagePreviewAction', this._options.messageFieldID, this._options.previewButtonID);
-       },
-       
-       /**
-        * @see WCF.Message.Preview._handleResponse()
-        */
-       _handleResponse: function(data) {
-               require(['WoltLabSuite/Core/Ui/Dialog'], (function(UiDialog) {
-                       UiDialog.open(this, '<div class="htmlContent">' + data.returnValues.message + '</div>');
-               }).bind(this));
-       },
-       
-       /**
-        * @see WCF.Message.Preview._getParameters()
-        */
-       _getParameters: function(message) {
-               var $parameters = this._super(message);
-               
-               for (var key in this._options) {
-                       if (this._options.hasOwnProperty(key) && key !== 'messageFieldID' && key !== 'previewButtonID') {
-                               $parameters[key] = this._options[key];
+       });
+       
+       /**
+        * Provides an inline message editor.
+        *
+        * @deprecated        3.0 - please use `WoltLabSuite/Core/Ui/Message/InlineEditor` instead
+        *
+        * @param        integer                containerID
+        */
+       WCF.Message.InlineEditor = Class.extend({
+               /**
+                * list of messages
+                * @var        object
+                */
+               _container: {},
+               
+               /**
+                * container id
+                * @var        int
+                */
+               _containerID: 0,
+               
+               /**
+                * list of dropdowns
+                * @var        object
+                */
+               _dropdowns: {},
+               
+               /**
+                * CSS selector for the message container
+                * @var        string
+                */
+               _messageContainerSelector: '.jsMessage',
+               
+               /**
+                * prefix of the message editor CSS id
+                * @var        string
+                */
+               _messageEditorIDPrefix: 'messageEditor',
+               
+               /**
+                * Initializes a new WCF.Message.InlineEditor object.
+                *
+                * @param        integer                                containerID
+                * @param        boolean                                supportExtendedForm
+                * @param        WCF.Message.Quote.Manager        quoteManager
+                */
+               init: function (containerID, supportExtendedForm, quoteManager) {
+                       require(['WoltLabSuite/Core/Ui/Message/InlineEditor'], (function (UiMessageInlineEditor) {
+                               new UiMessageInlineEditor({
+                                       className: this._getClassName(),
+                                       containerId: containerID,
+                                       editorPrefix: this._messageEditorIDPrefix,
+                                       
+                                       messageSelector: this._messageContainerSelector,
+                                       
+                                       callbackDropdownInit: this._callbackDropdownInit.bind(this)
+                               });
+                       }).bind(this));
+               },
+               
+               /**
+                * Loads WYSIWYG editor for selected message.
+                *
+                * @param        object                event
+                * @param        integer                containerID
+                * @return        boolean
+                */
+               _click: function (event, containerID) {
+                       containerID = (event === null) ? ~~containerID : ~~elData(event.currentTarget, 'container-id');
+                       
+                       require(['WoltLabSuite/Core/Ui/Message/InlineEditor'], (function (UiMessageInlineEditor) {
+                               UiMessageInlineEditor.legacyEdit(containerID);
+                       }).bind(this));
+                       
+                       if (event) {
+                               event.preventDefault();
                        }
-               }
-               
-               return $parameters;
-       },
-       
-       _dialogSetup: function() {
-               return {
-                       id: 'messagePreview',
-                       options: {
-                               title: WCF.Language.get('wcf.global.preview')
-                       },
-                       source: null
-               }
-       }
-});
-
-/**
- * Handles multilingualism for messages.
- * 
- * @param      integer         languageID
- * @param      object          availableLanguages
- * @param      boolean         forceSelection
- */
-WCF.Message.Multilingualism = Class.extend({
-       /**
-        * list of available languages
-        * @var object
-        */
-       _availableLanguages: { },
-       
-       /**
-        * language id
-        * @var integer
-        */
-       _languageID: 0,
-       
-       /**
-        * language input element
-        * @var jQuery
-        */
-       _languageInput: null,
-       
-       /**
-        * Initializes WCF.Message.Multilingualism
-        * 
-        * @param       integer         languageID
-        * @param       object          availableLanguages
-        * @param       boolean         forceSelection
-        */
-       init: function(languageID, availableLanguages, forceSelection) {
-               this._availableLanguages = availableLanguages;
-               this._languageID = languageID || 0;
-               
-               this._languageInput = $('#languageID');
-               
-               // preselect current language id
-               this._updateLabel();
-               
-               // register event listener
-               this._languageInput.find('.dropdownMenu > li').click($.proxy(this._click, this));
-               
-               // add element to disable multilingualism
-               if (!forceSelection) {
-                       var $dropdownMenu = this._languageInput.find('.dropdownMenu');
-                       $('<li class="dropdownDivider" />').appendTo($dropdownMenu);
-                       $('<li><span><span class="badge">' + this._availableLanguages[0] + '</span></span></li>').click($.proxy(this._disable, this)).appendTo($dropdownMenu);
-               }
-               
-               // bind submit event
-               this._languageInput.parents('form').submit($.proxy(this._submit, this));
-       },
-       
-       /**
-        * Handles language selections.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               this._languageID = $(event.currentTarget).data('languageID');
-               this._updateLabel();
-       },
-       
-       /**
-        * Disables language selection.
-        */
-       _disable: function() {
-               this._languageID = 0;
-               this._updateLabel();
-       },
-       
-       /**
-        * Updates selected language.
-        */
-       _updateLabel: function() {
-               this._languageInput.find('.dropdownToggle > span').text(this._availableLanguages[this._languageID]);
-       },
-       
-       /**
-        * Sets language id upon submit.
-        */
-       _submit: function() {
-               this._languageInput.next('input[name=languageID]').prop('value', this._languageID);
-       }
-});
-
-/**
- * Loads smiley categories upon user request.
- */
-WCF.Message.SmileyCategories = Class.extend({
-       /**
-        * list of already loaded category ids
-        * @var array<integer>
-        */
-       _cache: [ ],
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * wysiwyg editor selector
-        * @var string
-        */
-       _wysiwygSelector: '',
-       
-       /**
-        * Initializes the smiley loader.
-        * 
-        * @param       string          wysiwygSelector
-        */
-       init: function(wysiwygSelector) {
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               this._wysiwygSelector = wysiwygSelector;
-               
-               $('#smilies-' + this._wysiwygSelector).on('messagetabmenushow', $.proxy(this._click, this));
-       },
-       
-       /**
-        * Handles tab menu clicks.
-        * 
-        * @param       {Event}         event
-        * @param       {Object}        data
-        */
-       _click: function(event, data) {
-               event.preventDefault();
-               
-               var $categoryID = parseInt(data.activeTab.tab.data('smileyCategoryID'));
-               
-               // ignore global category, will always be pre-loaded
-               if (!$categoryID) {
-                       return;
-               }
-               
-               // smilies have already been loaded for this tab, ignore
-               if (data.activeTab.container.children('ul.smileyList').length) {
-                       return;
-               }
-               
-               // cache exists
-               if (this._cache[$categoryID] !== undefined) {
-                       data.activeTab.container.html(this._cache[$categoryID]);
-                       return;
-               }
-               
-               // load content
-               this._proxy.setOption('data', {
-                       actionName: 'getSmilies',
-                       className: 'wcf\\data\\smiley\\category\\SmileyCategoryAction',
-                       objectIDs: [ $categoryID ]
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               var $categoryID = parseInt(data.returnValues.smileyCategoryID);
-               this._cache[$categoryID] = data.returnValues.template;
-               
-               $('#smilies-' + this._wysiwygSelector + '-' + $categoryID).html(data.returnValues.template);
-       }
-});
-
-/**
- * Handles smiley clicks.
- * 
- * @param      string          wysiwygSelector
- */
-WCF.Message.Smilies = Class.extend({
-       /**
-        * wysiwyg editor id
-        * @var string
-        */
-       _editorId: '',
-       
-       /**
-        * Initializes the smiley handler.
-        * 
-        * @param       {string}        editorId
-        */
-       init: function(editorId) {
-               this._editorId = editorId;
-               
-               $('.messageTabMenu[data-wysiwyg-container-id=' + this._editorId + ']').on('mousedown', '.jsSmiley', this._smileyClick.bind(this));
-       },
-       
-       /**
-        * Handles tab smiley clicks.
-        * 
-        * @param       {Event}         event
-        */
-       _smileyClick: function(event) {
-               event.preventDefault();
-               
-               require(['EventHandler'], (function(EventHandler) {
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'insertSmiley_' + this._editorId, {
-                               img: event.currentTarget.children[0]
-                       });
-               }).bind(this));
-       }
-});
-
-/**
- * Provides an inline message editor.
- * 
- * @deprecated 3.0 - please use `WoltLabSuite/Core/Ui/Message/InlineEditor` instead
- * 
- * @param      integer         containerID
- */
-WCF.Message.InlineEditor = Class.extend({
-       /**
-        * list of messages
-        * @var object
-        */
-       _container: { },
-       
-       /**
-        * container id
-        * @var int
-        */
-       _containerID: 0,
-       
-       /**
-        * list of dropdowns
-        * @var object
-        */
-       _dropdowns: { },
-       
-       /**
-        * CSS selector for the message container
-        * @var string
-        */
-       _messageContainerSelector: '.jsMessage',
-       
-       /**
-        * prefix of the message editor CSS id
-        * @var string
-        */
-       _messageEditorIDPrefix: 'messageEditor',
-       
-       /**
-        * Initializes a new WCF.Message.InlineEditor object.
-        * 
-        * @param       integer                         containerID
-        * @param       boolean                         supportExtendedForm
-        * @param       WCF.Message.Quote.Manager       quoteManager
-        */
-       init: function(containerID, supportExtendedForm, quoteManager) {
-               require(['WoltLabSuite/Core/Ui/Message/InlineEditor'], (function(UiMessageInlineEditor) {
-                       new UiMessageInlineEditor({
-                               className: this._getClassName(),
-                               containerId: containerID,
-                               editorPrefix: this._messageEditorIDPrefix,
-                               
-                               messageSelector: this._messageContainerSelector,
-                               
-                               callbackDropdownInit: this._callbackDropdownInit.bind(this)
-                       });
-               }).bind(this));
-       },
-       
-       /**
-        * Loads WYSIWYG editor for selected message.
-        * 
-        * @param       object          event
-        * @param       integer         containerID
-        * @return      boolean
-        */
-       _click: function(event, containerID) {
-               containerID = (event === null) ? ~~containerID : ~~elData(event.currentTarget, 'container-id');
-               
-               require(['WoltLabSuite/Core/Ui/Message/InlineEditor'], (function(UiMessageInlineEditor) {
-                       UiMessageInlineEditor.legacyEdit(containerID);
-               }).bind(this));
-               
-               if (event) {
-                       event.preventDefault();
-               }
-       },
-       
-       /**
-        * Initializes the inline edit dropdown menu.
-        * 
-        * @param       integer         containerID
-        * @param       jQuery          dropdownMenu
-        */
-       _initDropdownMenu: function(containerID, dropdownMenu) { },
-       
-       _callbackDropdownInit: function(element, dropdownMenu) {
-               this._initDropdownMenu($(element).wcfIdentify(), $(dropdownMenu));
-               
-               return null;
-       },
-       
-       /**
-        * Returns message action class name.
-        * 
-        * @return      string
-        */
-       _getClassName: function() {
-               return '';
-       }
-});
-
-/**
- * Handles submit buttons for forms with an embedded WYSIWYG editor.
- */
-WCF.Message.Submit = {
-       /**
-        * list of registered buttons
-        * @var object
-        */
-       _buttons: { },
-       
-       /**
-        * Registers submit button for specified wysiwyg container id.
-        * 
-        * @param       string          wysiwygContainerID
-        * @param       string          selector
-        */
-       registerButton: function(wysiwygContainerID, selector) {
-               if (!WCF.Browser.isChrome()) {
-                       return;
-               }
-               
-               this._buttons[wysiwygContainerID] = $(selector);
-       },
-       
-       /**
-        * Triggers 'click' event for registered buttons.
-        */
-       execute: function(wysiwygContainerID) {
-               if (!this._buttons[wysiwygContainerID]) {
-                       return;
-               }
-               
-               this._buttons[wysiwygContainerID].trigger('click');
-       }
-};
+               },
+               
+               /**
+                * Initializes the inline edit dropdown menu.
+                *
+                * @param        integer                containerID
+                * @param        jQuery                dropdownMenu
+                */
+               _initDropdownMenu: function (containerID, dropdownMenu) {
+               },
+               
+               _callbackDropdownInit: function (element, dropdownMenu) {
+                       this._initDropdownMenu($(element).wcfIdentify(), $(dropdownMenu));
+                       
+                       return null;
+               },
+               
+               /**
+                * Returns message action class name.
+                *
+                * @return        string
+                */
+               _getClassName: function () {
+                       return '';
+               }
+       });
+       
+       /**
+        * Handles submit buttons for forms with an embedded WYSIWYG editor.
+        */
+       WCF.Message.Submit = {
+               /**
+                * list of registered buttons
+                * @var        object
+                */
+               _buttons: {},
+               
+               /**
+                * Registers submit button for specified wysiwyg container id.
+                *
+                * @param        string                wysiwygContainerID
+                * @param        string                selector
+                */
+               registerButton: function (wysiwygContainerID, selector) {
+                       if (!WCF.Browser.isChrome()) {
+                               return;
+                       }
+                       
+                       this._buttons[wysiwygContainerID] = $(selector);
+               },
+               
+               /**
+                * Triggers 'click' event for registered buttons.
+                */
+               execute: function (wysiwygContainerID) {
+                       if (!this._buttons[wysiwygContainerID]) {
+                               return;
+                       }
+                       
+                       this._buttons[wysiwygContainerID].trigger('click');
+               }
+       };
+}
+else {
+       WCF.Message.Preview = Class.extend({
+               _className: "",
+               _messageFieldID: "",
+               _messageField: {},
+               _proxy: {},
+               _previewButton: {},
+               _previewButtonLabel: "",
+               init: function() {},
+               _click: function() {},
+               _getParameters: function() {},
+               _getMessage: function() {},
+               _success: function() {},
+               _handleResponse: function() {},
+               _failure: function() {}
+       });
+       
+       WCF.Message.DefaultPreview = WCF.Message.Preview.extend({
+               _dialog: {},
+               _options: {},
+               init: function() {},
+               _handleResponse: function() {},
+               _getParameters: function() {},
+               _dialogSetup: function() {},
+               _className: "",
+               _messageFieldID: "",
+               _messageField: {},
+               _proxy: {},
+               _previewButton: {},
+               _previewButtonLabel: "",
+               _click: function() {},
+               _getMessage: function() {},
+               _success: function() {},
+               _failure: function() {}
+       });
+       
+       WCF.Message.Multilingualism = Class.extend({
+               _availableLanguages: {},
+               _languageID: 0,
+               _languageInput: {},
+               init: function() {},
+               _click: function() {},
+               _disable: function() {},
+               _updateLabel: function() {},
+               _submit: function() {}
+       });
+       
+       WCF.Message.SmileyCategories = Class.extend({
+               _cache: {},
+               _proxy: {},
+               _wysiwygSelector: "",
+               init: function() {},
+               _click: function() {},
+               _success: function() {}
+       });
+       
+       WCF.Message.Smilies = Class.extend({
+               _editorId: "",
+               init: function() {},
+               _smileyClick: function() {}
+       });
+       
+       WCF.Message.InlineEditor = Class.extend({
+               _container: {},
+               _containerID: 0,
+               _dropdowns: {},
+               _messageContainerSelector: "",
+               _messageEditorIDPrefix: "",
+               init: function() {},
+               _click: function() {},
+               _initDropdownMenu: function() {},
+               _callbackDropdownInit: function() {},
+               _getClassName: function() {}
+       });
+       
+       WCF.Message.Submit = {
+               _buttons: {},
+               registerButton: function() {},
+               execute: function() {}
+       };
+}
 
 /**
  * Namespace for message quotes.
  */
 WCF.Message.Quote = { };
 
-/**
- * Handles message quotes.
- */
-WCF.Message.Quote.Handler = Class.extend({
-       /**
-        * active container id
-        * @var string
-        */
-       _activeContainerID: '',
-       
-       /**
-        * action class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * list of message containers
-        * @var object
-        */
-       _containers: { },
-       
-       /**
-        * container selector
-        * @var string
-        */
-       _containerSelector: '',
-       
-       /**
-        * 'copy quote' overlay
-        * @var jQuery
-        */
-       _copyQuote: null,
-       
-       /**
-        * marked message
-        * @var string
-        */
-       _message: '',
-       
-       /**
-        * message body selector
-        * @var string
-        */
-       _messageBodySelector: '',
-       
-       /**
-        * object id
-        * @var {int}
-        */
-       _objectID: 0,
-       
-       /**
-        * object type name
-        * @var string
-        */
-       _objectType: '',
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * quote manager
-        * @var WCF.Message.Quote.Manager
-        */
-       _quoteManager: null,
-       
-       /**
-        * Initializes the quote handler for given object type.
-        * 
-        * @param       {WCF.Message.Quote.Manager}     quoteManager
-        * @param       {string}                        className
-        * @param       {string}                        objectType
-        * @param       {string}                        containerSelector
-        * @param       {string}                        messageBodySelector
-        * @param       {string}                        messageContentSelector
-        * @param       {boolean}                       supportDirectInsert
-        */
-       init: function(quoteManager, className, objectType, containerSelector, messageBodySelector, messageContentSelector, supportDirectInsert) {
-               this._className = className;
-               if (this._className === '') {
-                       console.debug("[WCF.Message.QuoteManager] Empty class name given, aborting.");
-                       return;
-               }
-               
-               this._objectType = objectType;
-               if (this._objectType === '') {
-                       console.debug("[WCF.Message.QuoteManager] Empty object type name given, aborting.");
-                       return;
-               }
-               
-               this._containerSelector = containerSelector;
-               this._message = '';
-               this._messageBodySelector = messageBodySelector;
-               this._objectID = 0;
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               this._initContainers();
-               
-               supportDirectInsert = (supportDirectInsert && quoteManager.supportPaste());
-               this._initCopyQuote(supportDirectInsert);
-               
-               $(document).mouseup($.proxy(this._mouseUp, this));
-               
-               // register with quote manager
-               this._quoteManager = quoteManager;
-               this._quoteManager.register(this._objectType, this);
-               
-               // register with DOMNodeInsertedHandler
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.Message.Quote.Handler' + objectType.hashCode(), $.proxy(this._initContainers, this));
-       },
-       
-       /**
-        * Initializes message containers.
-        */
-       _initContainers: function() {
-               var self = this;
-               $(this._containerSelector).each(function(index, container) {
-                       var $container = $(container);
-                       var $containerID = $container.wcfIdentify();
-                       
-                       if (!self._containers[$containerID]) {
-                               self._containers[$containerID] = $container;
-                               if ($container.hasClass('jsInvalidQuoteTarget')) {
-                                       return true;
-                               }
+if (COMPILER_TARGET_DEFAULT) {
+       /**
+        * Handles message quotes.
+        */
+       WCF.Message.Quote.Handler = Class.extend({
+               /**
+                * active container id
+                * @var        string
+                */
+               _activeContainerID: '',
+               
+               /**
+                * action class name
+                * @var        string
+                */
+               _className: '',
+               
+               /**
+                * list of message containers
+                * @var        object
+                */
+               _containers: {},
+               
+               /**
+                * container selector
+                * @var        string
+                */
+               _containerSelector: '',
+               
+               /**
+                * 'copy quote' overlay
+                * @var        jQuery
+                */
+               _copyQuote: null,
+               
+               /**
+                * marked message
+                * @var        string
+                */
+               _message: '',
+               
+               /**
+                * message body selector
+                * @var        string
+                */
+               _messageBodySelector: '',
+               
+               /**
+                * object id
+                * @var        {int}
+                */
+               _objectID: 0,
+               
+               /**
+                * object type name
+                * @var        string
+                */
+               _objectType: '',
+               
+               /**
+                * action proxy
+                * @var        WCF.Action.Proxy
+                */
+               _proxy: null,
+               
+               /**
+                * quote manager
+                * @var        WCF.Message.Quote.Manager
+                */
+               _quoteManager: null,
+               
+               /**
+                * Initializes the quote handler for given object type.
+                *
+                * @param        {WCF.Message.Quote.Manager}        quoteManager
+                * @param        {string}                        className
+                * @param        {string}                        objectType
+                * @param        {string}                        containerSelector
+                * @param        {string}                        messageBodySelector
+                * @param        {string}                        messageContentSelector
+                * @param        {boolean}                        supportDirectInsert
+                */
+               init: function (quoteManager, className, objectType, containerSelector, messageBodySelector, messageContentSelector, supportDirectInsert) {
+                       this._className = className;
+                       if (this._className === '') {
+                               console.debug("[WCF.Message.QuoteManager] Empty class name given, aborting.");
+                               return;
+                       }
+                       
+                       this._objectType = objectType;
+                       if (this._objectType === '') {
+                               console.debug("[WCF.Message.QuoteManager] Empty object type name given, aborting.");
+                               return;
+                       }
+                       
+                       this._containerSelector = containerSelector;
+                       this._message = '';
+                       this._messageBodySelector = messageBodySelector;
+                       this._objectID = 0;
+                       this._proxy = new WCF.Action.Proxy({
+                               success: $.proxy(this._success, this)
+                       });
+                       
+                       this._initContainers();
+                       
+                       supportDirectInsert = (supportDirectInsert && quoteManager.supportPaste());
+                       this._initCopyQuote(supportDirectInsert);
+                       
+                       $(document).mouseup($.proxy(this._mouseUp, this));
+                       
+                       // register with quote manager
+                       this._quoteManager = quoteManager;
+                       this._quoteManager.register(this._objectType, this);
+                       
+                       // register with DOMNodeInsertedHandler
+                       WCF.DOMNodeInsertedHandler.addCallback('WCF.Message.Quote.Handler' + objectType.hashCode(), $.proxy(this._initContainers, this));
+               },
+               
+               /**
+                * Initializes message containers.
+                */
+               _initContainers: function () {
+                       var self = this;
+                       $(this._containerSelector).each(function (index, container) {
+                               var $container = $(container);
+                               var $containerID = $container.wcfIdentify();
                                
-                               if (self._messageBodySelector) {
-                                       $container.data('body', $container.find(self._messageBodySelector).data('containerID', $containerID));
+                               if (!self._containers[$containerID]) {
+                                       self._containers[$containerID] = $container;
+                                       if ($container.hasClass('jsInvalidQuoteTarget')) {
+                                               return true;
+                                       }
+                                       
+                                       if (self._messageBodySelector) {
+                                               $container.data('body', $container.find(self._messageBodySelector).data('containerID', $containerID));
+                                       }
+                                       
+                                       $container.mousedown($.proxy(self._mouseDown, self));
+                                       
+                                       // bind event to quote whole message
+                                       self._containers[$containerID].find('.jsQuoteMessage').click($.proxy(self._saveFullQuote, self));
                                }
+                       });
+               },
+               
+               /**
+                * Handles mouse down event.
+                *
+                * @param        {Event}                event
+                */
+               _mouseDown: function (event) {
+                       // hide copy quote
+                       this._copyQuote.removeClass('active');
+                       
+                       this._activeContainerID = (event.currentTarget.classList.contains('jsInvalidQuoteTarget')) ? '' : event.currentTarget.id;
+               },
+               
+               /**
+                * Returns the text of a node and its children.
+                *
+                * @param        {Node}                node
+                * @return        {string}
+                */
+               _getNodeText: function (node) {
+                       // work-around for IE, see http://stackoverflow.com/a/5983176
+                       var $nodeFilter = function (node) {
+                               switch (node.tagName) {
+                                       case 'BLOCKQUOTE':
+                                       case 'IMG':
+                                       case 'SCRIPT':
+                                               return NodeFilter.FILTER_REJECT;
+                                       
+                                       default:
+                                               return NodeFilter.FILTER_ACCEPT;
+                               }
+                       };
+                       $nodeFilter.acceptNode = $nodeFilter;
+                       
+                       var $walker = document.createTreeWalker(
+                               node,
+                               NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,
+                               $nodeFilter,
+                               true
+                       );
+                       
+                       var $text = '', ignoreLinks = [], value;
+                       while ($walker.nextNode()) {
+                               var $node = $walker.currentNode;
                                
-                               $container.mousedown($.proxy(self._mouseDown, self));
-                               
-                               // bind event to quote whole message
-                               self._containers[$containerID].find('.jsQuoteMessage').click($.proxy(self._saveFullQuote, self));
-                       }
-               });
-       },
-       
-       /**
-        * Handles mouse down event.
-        * 
-        * @param       {Event}         event
-        */
-       _mouseDown: function(event) {
-               // hide copy quote
-               this._copyQuote.removeClass('active');
-               
-               this._activeContainerID = (event.currentTarget.classList.contains('jsInvalidQuoteTarget')) ? '' : event.currentTarget.id;
-       },
-       
-       /**
-        * Returns the text of a node and its children.
-        * 
-        * @param       {Node}          node
-        * @return      {string}
-        */
-       _getNodeText: function(node) {
-               // work-around for IE, see http://stackoverflow.com/a/5983176
-               var $nodeFilter = function(node) {
-                       switch (node.tagName) {
-                               case 'BLOCKQUOTE':
-                               case 'IMG':
-                               case 'SCRIPT':
-                                       return NodeFilter.FILTER_REJECT;
-                               break;
-                               
-                               default:
-                                       return NodeFilter.FILTER_ACCEPT;
-                               break;
-                       }
-               };
-               $nodeFilter.acceptNode = $nodeFilter;
-               
-               var $walker = document.createTreeWalker(
-                       node,
-                       NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,
-                       $nodeFilter,
-                       true
-               );
-               
-               var $text = '', ignoreLinks = [], value;
-               while ($walker.nextNode()) {
-                       var $node = $walker.currentNode;
-                       
-                       if ($node.nodeType === Node.ELEMENT_NODE) {
-                               switch ($node.tagName) {
-                                       case 'A':
-                                               // \u2026 === &hellip;
-                                               value = $node.textContent;
-                                               if (value.indexOf('\u2026') > 0) {
-                                                       var tmp = value.split(/\u2026/);
-                                                       if (tmp.length === 2) {
-                                                               var href = $node.href;
-                                                               if (href.indexOf(tmp[0]) === 0 && href.substr(tmp[1].length * -1) === tmp[1]) {
-                                                                       // truncated url, use original href to preserve link
-                                                                       $text += href;
-                                                                       ignoreLinks.push($node);
+                               if ($node.nodeType === Node.ELEMENT_NODE) {
+                                       switch ($node.tagName) {
+                                               case 'A':
+                                                       // \u2026 === &hellip;
+                                                       value = $node.textContent;
+                                                       if (value.indexOf('\u2026') > 0) {
+                                                               var tmp = value.split(/\u2026/);
+                                                               if (tmp.length === 2) {
+                                                                       var href = $node.href;
+                                                                       if (href.indexOf(tmp[0]) === 0 && href.substr(tmp[1].length * -1) === tmp[1]) {
+                                                                               // truncated url, use original href to preserve link
+                                                                               $text += href;
+                                                                               ignoreLinks.push($node);
+                                                                       }
                                                                }
                                                        }
-                                               }
-                                               break;
+                                                       break;
                                                
-                                       case 'BR':
-                                       case 'LI':
-                                       case 'UL':
-                                               $text += "\n";
-                                               break;
-                                       
-                                       case 'TD':
-                                               if (!$.browser.msie) {
+                                               case 'BR':
+                                               case 'LI':
+                                               case 'UL':
                                                        $text += "\n";
-                                               }
-                                               break;
-                                       
-                                       case 'P':
-                                               $text += "\n\n";
-                                               break;
+                                                       break;
+                                               
+                                               case 'TD':
+                                                       if (!$.browser.msie) {
+                                                               $text += "\n";
+                                                       }
+                                                       break;
+                                               
+                                               case 'P':
+                                                       $text += "\n\n";
+                                                       break;
+                                       }
                                }
-                       }
-                       else {
-                               if ($node.parentNode.nodeName === 'A' && ignoreLinks.indexOf($node.parentNode) !== -1) {
-                                       // ignore text content of links that have already been captured
-                                       continue;
+                               else {
+                                       if ($node.parentNode.nodeName === 'A' && ignoreLinks.indexOf($node.parentNode) !== -1) {
+                                               // ignore text content of links that have already been captured
+                                               continue;
+                                       }
+                                       
+                                       $text += $node.nodeValue.replace(/\n/g, '');
                                }
                                
-                               $text += $node.nodeValue.replace(/\n/g, '');
                        }
                        
-               }
-               
-               return $text;
-       },
-       
-       /**
-        * Handles the mouse up event.
-        */
-       _mouseUp: function() {
-               // ignore event
-               if (this._activeContainerID === '') {
-                       this._copyQuote.removeClass('active');
-                       return;
-               }
-               
-               var selection = window.getSelection();
-               if (selection.rangeCount !== 1 || selection.isCollapsed) {
-                       this._copyQuote.removeClass('active');
-                       return;
-               }
-               
-               var $container = this._containers[this._activeContainerID];
-               var $objectID = $container.data('objectID');
-               $container = $container.data('body') || $container;
-               
-               var anchorNode = selection.anchorNode;
-               while (anchorNode) {
-                       if (anchorNode === $container[0]) {
-                               break;
+                       return $text;
+               },
+               
+               /**
+                * Handles the mouse up event.
+                */
+               _mouseUp: function () {
+                       // ignore event
+                       if (this._activeContainerID === '') {
+                               this._copyQuote.removeClass('active');
+                               return;
                        }
                        
-                       anchorNode = anchorNode.parentNode;
-               }
-               
-               // selection spans unrelated nodes
-               if (anchorNode !== $container[0]) {
-                       this._copyQuote.removeClass('active');
-                       return;
-               }
-               
-               var $selection = this._getSelectedText();
-               var $text = $.trim($selection);
-               if ($text == '') {
-                       this._copyQuote.removeClass('active');
+                       var selection = window.getSelection();
+                       if (selection.rangeCount !== 1 || selection.isCollapsed) {
+                               this._copyQuote.removeClass('active');
+                               return;
+                       }
                        
-                       return;
-               }
-               
-               // check if mousedown/mouseup took place inside a blockquote
-               var range = selection.getRangeAt(0);
-               var startContainer = (range.startContainer.nodeType === Node.TEXT_NODE) ? range.startContainer.parentNode : range.startContainer;
-               var endContainer = (range.endContainer.nodeType === Node.TEXT_NODE) ? range.endContainer.parentNode : range.endContainer;
-               if (startContainer.closest('blockquote') || endContainer.closest('blockquote')) {
-                       this._copyQuote.removeClass('active');
+                       var $container = this._containers[this._activeContainerID];
+                       var $objectID = $container.data('objectID');
+                       $container = $container.data('body') || $container;
                        
-                       return;
-               }
-               
-               // compare selection with message text of given container
-               var $messageText = this._getNodeText($container[0]);
-               
-               // selected text is not part of $messageText or contains text from unrelated nodes
-               if (this._normalize($messageText).indexOf(this._normalize($text)) === -1) {
-                       return;
-               }
-               this._copyQuote.addClass('active');
-               
-               var $coordinates = this._getBoundingRectangle($container, window.getSelection());
-               var $dimensions = this._copyQuote.getDimensions('outer');
-               var $left = ($coordinates.right - $coordinates.left) / 2 - ($dimensions.width / 2) + $coordinates.left;
-               
-               this._copyQuote.css({
-                       top: $coordinates.top - $dimensions.height - 7 + 'px',
-                       left: $left + 'px'
-               });
-               this._copyQuote.removeClass('active');
-               
-               // reset containerID
-               this._activeContainerID = '';
-               
-               // show element after a delay, to prevent display if text was unmarked again (clicking into marked text)
-               var self = this;
-               window.setTimeout(function() {
-                       var $text = $.trim(self._getSelectedText());
-                       if ($text != '') {
-                               self._copyQuote.addClass('active');
-                               self._message = $text;
-                               self._objectID = $objectID;
+                       var anchorNode = selection.anchorNode;
+                       while (anchorNode) {
+                               if (anchorNode === $container[0]) {
+                                       break;
+                               }
+                               
+                               anchorNode = anchorNode.parentNode;
                        }
-               }, 10);
-       },
-       
-       /**
-        * Normalizes a text for comparison.
-        * 
-        * @param       {string}        text
-        * @return      {string}
-        */
-       _normalize: function(text) {
-               return text.replace(/\r?\n|\r/g, "\n").replace(/\s/g, ' ').replace(/\s{2,}/g, ' ');
-       },
-       
-       /**
-        * Returns the offsets of the selection's bounding rectangle.
-        * 
-        * @return      {Object}
-        */
-       _getBoundingRectangle: function(container, selection) {
-               var $coordinates = null;
-               
-               if (selection.rangeCount > 0) {
-                       // the coordinates returned by getBoundingClientRect() are relative to the viewport, not the document!
-                       var $rect = selection.getRangeAt(0).getBoundingClientRect();
                        
-                       $coordinates = {
-                               left: $rect.left,
-                               right: $rect.right,
-                               top: $rect.top + $(document).scrollTop()
-                       };
-               }
-               
-               return $coordinates;
-       },
-       
-       /**
-        * Initializes the 'copy quote' element.
-        * 
-        * @param       {boolean}       supportDirectInsert
-        */
-       _initCopyQuote: function(supportDirectInsert) {
-               this._copyQuote = $('#quoteManagerCopy');
-               if (!this._copyQuote.length) {
-                       this._copyQuote = $('<div id="quoteManagerCopy" class="balloonTooltip interactive"><span class="jsQuoteManagerStore">' + WCF.Language.get('wcf.message.quote.quoteSelected') + '</span></div>').appendTo(document.body);
-                       var $storeQuote = this._copyQuote.children('span.jsQuoteManagerStore').click($.proxy(this._saveQuote, this));
-                       if (supportDirectInsert) {
-                               $('<span class="jsQuoteManagerQuoteAndInsert">' + WCF.Language.get('wcf.message.quote.quoteAndReply') + '</span>').click($.proxy(this._saveAndInsertQuote, this)).insertAfter($storeQuote);
-                       }
-               }
-       },
-       
-       /**
-        * Returns the text selection.
-        * 
-        * @return      string
-        */
-       _getSelectedText: function() {
-               var $selection = window.getSelection();
-               if ($selection.rangeCount) {
-                       return this._getNodeText($selection.getRangeAt(0).cloneContents());
-               }
-               
-               return '';
-       },
-       
-       /**
-        * Saves a full quote.
-        * 
-        * @param       {Event}         event
-        */
-       _saveFullQuote: function(event) {
-               event.preventDefault();
-               
-               var $listItem = $(event.currentTarget);
-               
-               this._proxy.setOption('data', {
-                       actionName: 'saveFullQuote',
-                       className: this._className,
-                       interfaceName: 'wcf\\data\\IMessageQuoteAction',
-                       objectIDs: [ $listItem.data('objectID') ]
-               });
-               this._proxy.sendRequest();
-               
-               // mark element as quoted
-               if ($listItem.data('isQuoted')) {
-                       $listItem.data('isQuoted', false).children('a').removeClass('active');
-               }
-               else {
-                       $listItem.data('isQuoted', true).children('a').addClass('active');
-               }
-               
-               // close navigation on mobile
-               var $navigationList = $listItem.parents('.buttonGroupNavigation');
-               if ($navigationList.hasClass('jsMobileButtonGroupNavigation')) {
-                       $navigationList.children('.dropdownLabel').trigger('click');
-               }
-       },
-       
-       /**
-        * Saves a quote.
-        * 
-        * @param       {boolean}       renderQuote
-        */
-       _saveQuote: function(renderQuote) {
-               this._proxy.setOption('data', {
-                       actionName: 'saveQuote',
-                       className: this._className,
-                       interfaceName: 'wcf\\data\\IMessageQuoteAction',
-                       objectIDs: [ this._objectID ],
-                       parameters: {
-                               message: this._message,
-                               renderQuote: (renderQuote === true)
+                       // selection spans unrelated nodes
+                       if (anchorNode !== $container[0]) {
+                               this._copyQuote.removeClass('active');
+                               return;
                        }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Saves a quote and directly inserts it.
-        */
-       _saveAndInsertQuote: function() {
-               this._saveQuote(true);
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       {Object}        data
-        */
-       _success: function(data) {
-               if (data.returnValues.count !== undefined) {
-                       if (data.returnValues.fullQuoteMessageIDs !== undefined) {
-                               data.returnValues.fullQuoteObjectIDs = data.returnValues.fullQuoteMessageIDs;
+                       
+                       var $selection = this._getSelectedText();
+                       var $text = $.trim($selection);
+                       if ($text == '') {
+                               this._copyQuote.removeClass('active');
+                               
+                               return;
                        }
                        
-                       var $fullQuoteObjectIDs = (data.returnValues.fullQuoteObjectIDs !== undefined) ? data.returnValues.fullQuoteObjectIDs : { };
-                       this._quoteManager.updateCount(data.returnValues.count, $fullQuoteObjectIDs);
-               }
-               
-               switch (data.actionName) {
-                       case 'saveQuote':
-                       case 'saveFullQuote':
-                               if (data.returnValues.renderedQuote) {
-                                       WCF.System.Event.fireEvent('com.woltlab.wcf.message.quote', 'insert', {
-                                               forceInsert: (data.actionName === 'saveQuote'),
-                                               quote: data.returnValues.renderedQuote
-                                       });
-                               }
-                       break;
-               }
-       },
-       
-       /**
-        * Updates the full quote data for all matching objects.
-        * 
-        * @param       array<integer>          $objectIDs
-        */
-       updateFullQuoteObjectIDs: function(objectIDs) {
-               for (var $containerID in this._containers) {
-                       this._containers[$containerID].find('.jsQuoteMessage').each(function(index, button) {
-                               // reset all markings
-                               var $button = $(button).data('isQuoted', 0);
-                               $button.children('a').removeClass('active');
+                       // check if mousedown/mouseup took place inside a blockquote
+                       var range = selection.getRangeAt(0);
+                       var startContainer = (range.startContainer.nodeType === Node.TEXT_NODE) ? range.startContainer.parentNode : range.startContainer;
+                       var endContainer = (range.endContainer.nodeType === Node.TEXT_NODE) ? range.endContainer.parentNode : range.endContainer;
+                       if (startContainer.closest('blockquote') || endContainer.closest('blockquote')) {
+                               this._copyQuote.removeClass('active');
                                
-                               // mark as active
-                               if (WCF.inArray($button.data('objectID'), objectIDs)) {
-                                       $button.data('isQuoted', 1).children('a').addClass('active');
-                               }
+                               return;
+                       }
+                       
+                       // compare selection with message text of given container
+                       var $messageText = this._getNodeText($container[0]);
+                       
+                       // selected text is not part of $messageText or contains text from unrelated nodes
+                       if (this._normalize($messageText).indexOf(this._normalize($text)) === -1) {
+                               return;
+                       }
+                       this._copyQuote.addClass('active');
+                       
+                       var $coordinates = this._getBoundingRectangle($container, window.getSelection());
+                       var $dimensions = this._copyQuote.getDimensions('outer');
+                       var $left = ($coordinates.right - $coordinates.left) / 2 - ($dimensions.width / 2) + $coordinates.left;
+                       
+                       this._copyQuote.css({
+                               top: $coordinates.top - $dimensions.height - 7 + 'px',
+                               left: $left + 'px'
                        });
-               }
-       }
-});
-
-/**
- * Manages stored quotes.
- * 
- * @param      integer         count
- */
-WCF.Message.Quote.Manager = Class.extend({
-       /**
-        * list of form buttons
-        * @var {Object}
-        */
-       _buttons: {},
-       
-       /**
-        * number of stored quotes
-        * @var {int}
-        */
-       _count: 0,
-       
-       /**
-        * dialog overlay
-        * @var {jQuery}
-        */
-       _dialog: null,
-       
-       /**
-        * editor element id
-        * @var {string}
-        */
-       _editorId: '',
-       
-       /**
-        * alternative editor element id
-        * @var {string}
-        */
-       _editorIdAlternative: '',
-       
-       /**
-        * form element
-        * @var {jQuery}
-        */
-       _form: null,
-       
-       /**
-        * list of quote handlers
-        * @var {Object}
-        */
-       _handlers: {},
-       
-       /**
-        * true, if an up-to-date template exists
-        * @var {boolean}
-        */
-       _hasTemplate: false,
-       
-       /**
-        * true, if related quotes should be inserted
-        * @var {boolean}
-        */
-       _insertQuotes: true,
-       
-       /**
-        * action proxy
-        * @var {WCF.Action.Proxy}
-        */
-       _proxy: null,
-       
-       /**
-        * list of quotes to remove upon submit
-        * @var {Array}
-        */
-       _removeOnSubmit: [ ],
-       
-       /**
-        * allow pasting
-        * @var {boolean}
-        */
-       _supportPaste: false,
-       
-       /**
-        * Initializes the quote manager.
-        * 
-        * @param       {int}           count
-        * @param       {string}        elementID
-        * @param       {boolean}       supportPaste
-        * @param       {Array}         removeOnSubmit
-        */
-       init: function(count, elementID, supportPaste, removeOnSubmit) {
-               this._buttons = {
-                       insert: null,
-                       remove: null
-               };
-               this._count = parseInt(count) || 0;
-               this._dialog = null;
-               this._editorId = '';
-               this._editorIdAlternative = '';
-               this._form = null;
-               this._handlers = { };
-               this._hasTemplate = false;
-               this._insertQuotes = true;
-               this._removeOnSubmit = [];
-               this._supportPaste = false;
-               
-               if (elementID) {
-                       var element = $('#' + elementID);
-                       if (element.length) {
-                               this._editorId = elementID;
-                               this._supportPaste = true;
-                               
-                               // get surrounding form-tag
-                               this._form = element.parents('form:eq(0)');
-                               if (this._form.length) {
-                                       this._form.submit(this._submit.bind(this));
-                                       this._removeOnSubmit = removeOnSubmit || [];
+                       this._copyQuote.removeClass('active');
+                       
+                       // reset containerID
+                       this._activeContainerID = '';
+                       
+                       // show element after a delay, to prevent display if text was unmarked again (clicking into marked text)
+                       var self = this;
+                       window.setTimeout(function () {
+                               var $text = $.trim(self._getSelectedText());
+                               if ($text != '') {
+                                       self._copyQuote.addClass('active');
+                                       self._message = $text;
+                                       self._objectID = $objectID;
                                }
-                               else {
-                                       this._form = null;
-                                       
-                                       // allow override
-                                       this._supportPaste = (supportPaste === true);
+                       }, 10);
+               },
+               
+               /**
+                * Normalizes a text for comparison.
+                *
+                * @param        {string}        text
+                * @return        {string}
+                */
+               _normalize: function (text) {
+                       return text.replace(/\r?\n|\r/g, "\n").replace(/\s/g, ' ').replace(/\s{2,}/g, ' ');
+               },
+               
+               /**
+                * Returns the offsets of the selection's bounding rectangle.
+                *
+                * @return        {Object}
+                */
+               _getBoundingRectangle: function (container, selection) {
+                       var $coordinates = null;
+                       
+                       if (selection.rangeCount > 0) {
+                               // the coordinates returned by getBoundingClientRect() are relative to the viewport, not the document!
+                               var $rect = selection.getRangeAt(0).getBoundingClientRect();
+                               
+                               $coordinates = {
+                                       left: $rect.left,
+                                       right: $rect.right,
+                                       top: $rect.top + $(document).scrollTop()
+                               };
+                       }
+                       
+                       return $coordinates;
+               },
+               
+               /**
+                * Initializes the 'copy quote' element.
+                *
+                * @param        {boolean}        supportDirectInsert
+                */
+               _initCopyQuote: function (supportDirectInsert) {
+                       this._copyQuote = $('#quoteManagerCopy');
+                       if (!this._copyQuote.length) {
+                               this._copyQuote = $('<div id="quoteManagerCopy" class="balloonTooltip interactive"><span class="jsQuoteManagerStore">' + WCF.Language.get('wcf.message.quote.quoteSelected') + '</span></div>').appendTo(document.body);
+                               var $storeQuote = this._copyQuote.children('span.jsQuoteManagerStore').click($.proxy(this._saveQuote, this));
+                               if (supportDirectInsert) {
+                                       $('<span class="jsQuoteManagerQuoteAndInsert">' + WCF.Language.get('wcf.message.quote.quoteAndReply') + '</span>').click($.proxy(this._saveAndInsertQuote, this)).insertAfter($storeQuote);
                                }
                        }
-               }
-               
-               this._proxy = new WCF.Action.Proxy({
-                       showLoadingOverlay: false,
-                       success: $.proxy(this._success, this),
-                       url: 'index.php?message-quote/&t=' + SECURITY_TOKEN
-               });
-               
-               this._toggleShowQuotes();
-               
-               WCF.System.Event.addListener('com.woltlab.wcf.quote', 'reload', this.countQuotes.bind(this));
-               
-               // event forwarding
-               WCF.System.Event.addListener('com.woltlab.wcf.message.quote', 'insert', (function(data) {
-                       //noinspection JSUnresolvedVariable
-                       WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'insertQuote_' + (this._editorIdAlternative ? this._editorIdAlternative : this._editorId), {
-                               author: data.quote.username,
-                               content: data.quote.text,
-                               isText: !data.quote.isFullQuote,
-                               link: data.quote.link
-                       });
-               }).bind(this));
-       },
-       
-       /**
-        * Sets an alternative editor element id on runtime.
-        * 
-        * @param       {(string|jQuery)}       elementId       element id or jQuery element
-        */
-       setAlternativeEditor: function(elementId) {
-               if (typeof elementId === 'object') elementId = elementId[0].id;
-               this._editorIdAlternative = elementId;
-       },
-       
-       /**
-        * Clears alternative editor element id.
-        */
-       clearAlternativeEditor: function() {
-               this._editorIdAlternative = '';
-       },
-       
-       /**
-        * Registers a quote handler.
-        * 
-        * @param       {string}                        objectType
-        * @param       {WCF.Message.Quote.Handler}     handler
-        */
-       register: function(objectType, handler) {
-               this._handlers[objectType] = handler;
-       },
-       
-       /**
-        * Updates number of stored quotes.
-        * 
-        * @param       {int}           count
-        * @param       {Object}        fullQuoteObjectIDs
-        */
-       updateCount: function(count, fullQuoteObjectIDs) {
-               this._count = parseInt(count) || 0;
-               
-               this._toggleShowQuotes();
-               
-               // update full quote ids of handlers
-               for (var $objectType in this._handlers) {
-                       if (this._handlers.hasOwnProperty($objectType)) {
-                               var $objectIDs = fullQuoteObjectIDs[$objectType] || [];
-                               this._handlers[$objectType].updateFullQuoteObjectIDs($objectIDs);
+               },
+               
+               /**
+                * Returns the text selection.
+                *
+                * @return        string
+                */
+               _getSelectedText: function () {
+                       var $selection = window.getSelection();
+                       if ($selection.rangeCount) {
+                               return this._getNodeText($selection.getRangeAt(0).cloneContents());
+                       }
+                       
+                       return '';
+               },
+               
+               /**
+                * Saves a full quote.
+                *
+                * @param        {Event}                event
+                */
+               _saveFullQuote: function (event) {
+                       event.preventDefault();
+                       
+                       var $listItem = $(event.currentTarget);
+                       
+                       this._proxy.setOption('data', {
+                               actionName: 'saveFullQuote',
+                               className: this._className,
+                               interfaceName: 'wcf\\data\\IMessageQuoteAction',
+                               objectIDs: [$listItem.data('objectID')]
+                       });
+                       this._proxy.sendRequest();
+                       
+                       // mark element as quoted
+                       if ($listItem.data('isQuoted')) {
+                               $listItem.data('isQuoted', false).children('a').removeClass('active');
+                       }
+                       else {
+                               $listItem.data('isQuoted', true).children('a').addClass('active');
                        }
-               }
-       },
-       
-       /**
-        * Inserts all associated quotes upon first time using quick reply.
-        * 
-        * @param       {string}        className
-        * @param       {int}           parentObjectID
-        * @param       {Object}        callback
-        */
-       insertQuotes: function(className, parentObjectID, callback) {
-               if (!this._insertQuotes) {
-                       this._insertQuotes = true;
                        
-                       return;
-               }
-               
-               new WCF.Action.Proxy({
-                       autoSend: true,
-                       data: {
-                               actionName: 'getRenderedQuotes',
-                               className: className,
+                       // close navigation on mobile
+                       var $navigationList = $listItem.parents('.buttonGroupNavigation');
+                       if ($navigationList.hasClass('jsMobileButtonGroupNavigation')) {
+                               $navigationList.children('.dropdownLabel').trigger('click');
+                       }
+               },
+               
+               /**
+                * Saves a quote.
+                *
+                * @param        {boolean}        renderQuote
+                */
+               _saveQuote: function (renderQuote) {
+                       this._proxy.setOption('data', {
+                               actionName: 'saveQuote',
+                               className: this._className,
                                interfaceName: 'wcf\\data\\IMessageQuoteAction',
+                               objectIDs: [this._objectID],
                                parameters: {
-                                       parentObjectID: parentObjectID
+                                       message: this._message,
+                                       renderQuote: (renderQuote === true)
                                }
-                       },
-                       success: callback
-               });
-       },
-       
-       /**
-        * Toggles the display of the 'Show quotes' button
-        */
-       _toggleShowQuotes: function() {
-               require(['WoltLabSuite/Core/Ui/Page/Action'], (function(UiPageAction) {
-                       var buttonName = 'showQuotes';
-                       
-                       if (this._count) {
-                               var button = UiPageAction.get(buttonName);
-                               if (button === undefined) {
-                                       button = elCreate('a');
-                                       button.addEventListener('mousedown', this._click.bind(this));
+                       });
+                       this._proxy.sendRequest();
+               },
+               
+               /**
+                * Saves a quote and directly inserts it.
+                */
+               _saveAndInsertQuote: function () {
+                       this._saveQuote(true);
+               },
+               
+               /**
+                * Handles successful AJAX requests.
+                *
+                * @param        {Object}        data
+                */
+               _success: function (data) {
+                       if (data.returnValues.count !== undefined) {
+                               if (data.returnValues.fullQuoteMessageIDs !== undefined) {
+                                       data.returnValues.fullQuoteObjectIDs = data.returnValues.fullQuoteMessageIDs;
+                               }
+                               
+                               var $fullQuoteObjectIDs = (data.returnValues.fullQuoteObjectIDs !== undefined) ? data.returnValues.fullQuoteObjectIDs : {};
+                               this._quoteManager.updateCount(data.returnValues.count, $fullQuoteObjectIDs);
+                       }
+                       
+                       switch (data.actionName) {
+                               case 'saveQuote':
+                               case 'saveFullQuote':
+                                       if (data.returnValues.renderedQuote) {
+                                               WCF.System.Event.fireEvent('com.woltlab.wcf.message.quote', 'insert', {
+                                                       forceInsert: (data.actionName === 'saveQuote'),
+                                                       quote: data.returnValues.renderedQuote
+                                               });
+                                       }
+                                       break;
+                       }
+               },
+               
+               /**
+                * Updates the full quote data for all matching objects.
+                *
+                * @param        array<integer>                $objectIDs
+                */
+               updateFullQuoteObjectIDs: function (objectIDs) {
+                       for (var $containerID in this._containers) {
+                               this._containers[$containerID].find('.jsQuoteMessage').each(function (index, button) {
+                                       // reset all markings
+                                       var $button = $(button).data('isQuoted', 0);
+                                       $button.children('a').removeClass('active');
+                                       
+                                       // mark as active
+                                       if (WCF.inArray($button.data('objectID'), objectIDs)) {
+                                               $button.data('isQuoted', 1).children('a').addClass('active');
+                                       }
+                               });
+                       }
+               }
+       });
+       
+       /**
+        * Manages stored quotes.
+        *
+        * @param        integer                count
+        */
+       WCF.Message.Quote.Manager = Class.extend({
+               /**
+                * list of form buttons
+                * @var        {Object}
+                */
+               _buttons: {},
+               
+               /**
+                * number of stored quotes
+                * @var        {int}
+                */
+               _count: 0,
+               
+               /**
+                * dialog overlay
+                * @var        {jQuery}
+                */
+               _dialog: null,
+               
+               /**
+                * editor element id
+                * @var        {string}
+                */
+               _editorId: '',
+               
+               /**
+                * alternative editor element id
+                * @var        {string}
+                */
+               _editorIdAlternative: '',
+               
+               /**
+                * form element
+                * @var        {jQuery}
+                */
+               _form: null,
+               
+               /**
+                * list of quote handlers
+                * @var        {Object}
+                */
+               _handlers: {},
+               
+               /**
+                * true, if an up-to-date template exists
+                * @var        {boolean}
+                */
+               _hasTemplate: false,
+               
+               /**
+                * true, if related quotes should be inserted
+                * @var        {boolean}
+                */
+               _insertQuotes: true,
+               
+               /**
+                * action proxy
+                * @var        {WCF.Action.Proxy}
+                */
+               _proxy: null,
+               
+               /**
+                * list of quotes to remove upon submit
+                * @var        {Array}
+                */
+               _removeOnSubmit: [],
+               
+               /**
+                * allow pasting
+                * @var        {boolean}
+                */
+               _supportPaste: false,
+               
+               /**
+                * Initializes the quote manager.
+                *
+                * @param        {int}                count
+                * @param        {string}        elementID
+                * @param        {boolean}        supportPaste
+                * @param        {Array}        removeOnSubmit
+                */
+               init: function (count, elementID, supportPaste, removeOnSubmit) {
+                       this._buttons = {
+                               insert: null,
+                               remove: null
+                       };
+                       this._count = parseInt(count) || 0;
+                       this._dialog = null;
+                       this._editorId = '';
+                       this._editorIdAlternative = '';
+                       this._form = null;
+                       this._handlers = {};
+                       this._hasTemplate = false;
+                       this._insertQuotes = true;
+                       this._removeOnSubmit = [];
+                       this._supportPaste = false;
+                       
+                       if (elementID) {
+                               var element = $('#' + elementID);
+                               if (element.length) {
+                                       this._editorId = elementID;
+                                       this._supportPaste = true;
                                        
-                                       UiPageAction.add(buttonName, button);
+                                       // get surrounding form-tag
+                                       this._form = element.parents('form:eq(0)');
+                                       if (this._form.length) {
+                                               this._form.submit(this._submit.bind(this));
+                                               this._removeOnSubmit = removeOnSubmit || [];
+                                       }
+                                       else {
+                                               this._form = null;
+                                               
+                                               // allow override
+                                               this._supportPaste = (supportPaste === true);
+                                       }
+                               }
+                       }
+                       
+                       this._proxy = new WCF.Action.Proxy({
+                               showLoadingOverlay: false,
+                               success: $.proxy(this._success, this),
+                               url: 'index.php?message-quote/&t=' + SECURITY_TOKEN
+                       });
+                       
+                       this._toggleShowQuotes();
+                       
+                       WCF.System.Event.addListener('com.woltlab.wcf.quote', 'reload', this.countQuotes.bind(this));
+                       
+                       // event forwarding
+                       WCF.System.Event.addListener('com.woltlab.wcf.message.quote', 'insert', (function (data) {
+                               //noinspection JSUnresolvedVariable
+                               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'insertQuote_' + (this._editorIdAlternative ? this._editorIdAlternative : this._editorId), {
+                                       author: data.quote.username,
+                                       content: data.quote.text,
+                                       isText: !data.quote.isFullQuote,
+                                       link: data.quote.link
+                               });
+                       }).bind(this));
+               },
+               
+               /**
+                * Sets an alternative editor element id on runtime.
+                *
+                * @param        {(string|jQuery)}       elementId       element id or jQuery element
+                */
+               setAlternativeEditor: function (elementId) {
+                       if (typeof elementId === 'object') elementId = elementId[0].id;
+                       this._editorIdAlternative = elementId;
+               },
+               
+               /**
+                * Clears alternative editor element id.
+                */
+               clearAlternativeEditor: function () {
+                       this._editorIdAlternative = '';
+               },
+               
+               /**
+                * Registers a quote handler.
+                *
+                * @param        {string}                        objectType
+                * @param        {WCF.Message.Quote.Handler}        handler
+                */
+               register: function (objectType, handler) {
+                       this._handlers[objectType] = handler;
+               },
+               
+               /**
+                * Updates number of stored quotes.
+                *
+                * @param        {int}                count
+                * @param        {Object}        fullQuoteObjectIDs
+                */
+               updateCount: function (count, fullQuoteObjectIDs) {
+                       this._count = parseInt(count) || 0;
+                       
+                       this._toggleShowQuotes();
+                       
+                       // update full quote ids of handlers
+                       for (var $objectType in this._handlers) {
+                               if (this._handlers.hasOwnProperty($objectType)) {
+                                       var $objectIDs = fullQuoteObjectIDs[$objectType] || [];
+                                       this._handlers[$objectType].updateFullQuoteObjectIDs($objectIDs);
                                }
+                       }
+               },
+               
+               /**
+                * Inserts all associated quotes upon first time using quick reply.
+                *
+                * @param        {string}        className
+                * @param        {int}                parentObjectID
+                * @param        {Object}        callback
+                */
+               insertQuotes: function (className, parentObjectID, callback) {
+                       if (!this._insertQuotes) {
+                               this._insertQuotes = true;
                                
-                               button.textContent = WCF.Language.get('wcf.message.quote.showQuotes').replace(/#count#/, this._count);
+                               return;
+                       }
+                       
+                       new WCF.Action.Proxy({
+                               autoSend: true,
+                               data: {
+                                       actionName: 'getRenderedQuotes',
+                                       className: className,
+                                       interfaceName: 'wcf\\data\\IMessageQuoteAction',
+                                       parameters: {
+                                               parentObjectID: parentObjectID
+                                       }
+                               },
+                               success: callback
+                       });
+               },
+               
+               /**
+                * Toggles the display of the 'Show quotes' button
+                */
+               _toggleShowQuotes: function () {
+                       require(['WoltLabSuite/Core/Ui/Page/Action'], (function (UiPageAction) {
+                               var buttonName = 'showQuotes';
+                               
+                               if (this._count) {
+                                       var button = UiPageAction.get(buttonName);
+                                       if (button === undefined) {
+                                               button = elCreate('a');
+                                               button.addEventListener('mousedown', this._click.bind(this));
+                                               
+                                               UiPageAction.add(buttonName, button);
+                                       }
+                                       
+                                       button.textContent = WCF.Language.get('wcf.message.quote.showQuotes').replace(/#count#/, this._count);
+                                       
+                                       UiPageAction.show(buttonName);
+                               }
+                               else {
+                                       UiPageAction.hide(buttonName);
+                               }
                                
-                               UiPageAction.show(buttonName);
+                               this._hasTemplate = false;
+                       }).bind(this));
+               },
+               
+               /**
+                * Handles clicks on 'Show quotes'.
+                */
+               _click: function () {
+                       var editor = document.activeElement;
+                       if (editor.classList.contains('redactor-layer')) {
+                               $('#' + elData(editor, 'element-id')).redactor('selection.save');
+                       }
+                       
+                       if (this._hasTemplate) {
+                               this._dialog.wcfDialog('open');
                        }
                        else {
-                               UiPageAction.hide(buttonName);
+                               this._proxy.showLoadingOverlayOnce();
+                               
+                               this._proxy.setOption('data', {
+                                       actionName: 'getQuotes',
+                                       supportPaste: this._supportPaste
+                               });
+                               this._proxy.sendRequest();
+                       }
+               },
+               
+               /**
+                * Renders the dialog.
+                *
+                * @param        {string}        template
+                */
+               renderDialog: function (template) {
+                       // create dialog if not exists
+                       if (this._dialog === null) {
+                               this._dialog = $('#messageQuoteList');
+                               if (!this._dialog.length) {
+                                       this._dialog = $('<div id="messageQuoteList" />').hide().appendTo(document.body);
+                               }
                        }
                        
-                       this._hasTemplate = false;
-               }).bind(this));
-       },
-       
-       /**
-        * Handles clicks on 'Show quotes'.
-        */
-       _click: function() {
-               var editor = document.activeElement;
-               if (editor.classList.contains('redactor-layer')) {
-                       $('#' + elData(editor, 'element-id')).redactor('selection.save');
-               }
-               
-               if (this._hasTemplate) {
-                       this._dialog.wcfDialog('open');
-               }
-               else {
-                       this._proxy.showLoadingOverlayOnce();
+                       // add template
+                       this._dialog.html(template);
                        
-                       this._proxy.setOption('data', {
-                               actionName: 'getQuotes',
-                               supportPaste: this._supportPaste
+                       // add 'insert' and 'delete' buttons
+                       var $formSubmit = $('<div class="formSubmit" />').appendTo(this._dialog);
+                       if (this._supportPaste) this._buttons.insert = $('<button class="buttonPrimary">' + WCF.Language.get('wcf.message.quote.insertAllQuotes') + '</button>').click($.proxy(this._insertSelected, this)).appendTo($formSubmit);
+                       this._buttons.remove = $('<button>' + WCF.Language.get('wcf.message.quote.removeAllQuotes') + '</button>').click($.proxy(this._removeSelected, this)).appendTo($formSubmit);
+                       
+                       // show dialog
+                       this._dialog.wcfDialog({
+                               title: WCF.Language.get('wcf.message.quote.manageQuotes')
                        });
-                       this._proxy.sendRequest();
-               }
-       },
-       
-       /**
-        * Renders the dialog.
-        * 
-        * @param       {string}        template
-        */
-       renderDialog: function(template) {
-               // create dialog if not exists
-               if (this._dialog === null) {
-                       this._dialog = $('#messageQuoteList');
-                       if (!this._dialog.length) {
-                               this._dialog = $('<div id="messageQuoteList" />').hide().appendTo(document.body);
+                       this._dialog.wcfDialog('render');
+                       this._hasTemplate = true;
+                       
+                       // bind event listener
+                       var $insertQuoteButtons = this._dialog.find('.jsInsertQuote');
+                       if (this._supportPaste) {
+                               $insertQuoteButtons.click($.proxy(this._insertQuote, this));
                        }
-               }
-               
-               // add template
-               this._dialog.html(template);
-               
-               // add 'insert' and 'delete' buttons
-               var $formSubmit = $('<div class="formSubmit" />').appendTo(this._dialog);
-               if (this._supportPaste) this._buttons.insert = $('<button class="buttonPrimary">' + WCF.Language.get('wcf.message.quote.insertAllQuotes') + '</button>').click($.proxy(this._insertSelected, this)).appendTo($formSubmit);
-               this._buttons.remove = $('<button>' + WCF.Language.get('wcf.message.quote.removeAllQuotes') + '</button>').click($.proxy(this._removeSelected, this)).appendTo($formSubmit);
-               
-               // show dialog
-               this._dialog.wcfDialog({
-                       title: WCF.Language.get('wcf.message.quote.manageQuotes')
-               });
-               this._dialog.wcfDialog('render');
-               this._hasTemplate = true;
-               
-               // bind event listener
-               var $insertQuoteButtons = this._dialog.find('.jsInsertQuote');
-               if (this._supportPaste) {
-                       $insertQuoteButtons.click($.proxy(this._insertQuote, this));
-               }
-               else {
-                       $insertQuoteButtons.hide();
-               }
-               
-               this._dialog.find('input.jsCheckbox').change($.proxy(this._changeButtons, this));
-               
-               // mark quotes for removal
-               if (this._removeOnSubmit.length) {
-                       var self = this;
-                       this._dialog.find('input.jsRemoveQuote').each(function(index, input) {
-                               var $input = $(input).change($.proxy(this._change, this));
-                               
-                               // mark for deletion
-                               if (WCF.inArray($input.parent('li').attr('data-quote-id'), self._removeOnSubmit)) {
-                                       $input.attr('checked', 'checked');
+                       else {
+                               $insertQuoteButtons.hide();
+                       }
+                       
+                       this._dialog.find('input.jsCheckbox').change($.proxy(this._changeButtons, this));
+                       
+                       // mark quotes for removal
+                       if (this._removeOnSubmit.length) {
+                               var self = this;
+                               this._dialog.find('input.jsRemoveQuote').each(function (index, input) {
+                                       var $input = $(input).change($.proxy(this._change, this));
+                                       
+                                       // mark for deletion
+                                       if (WCF.inArray($input.parent('li').attr('data-quote-id'), self._removeOnSubmit)) {
+                                               $input.attr('checked', 'checked');
+                                       }
+                               });
+                       }
+               },
+               
+               /**
+                * Updates button labels if a checkbox is checked or unchecked.
+                */
+               _changeButtons: function () {
+                       // selection
+                       if (this._dialog.find('input.jsCheckbox:checked').length) {
+                               if (this._supportPaste) this._buttons.insert.html(WCF.Language.get('wcf.message.quote.insertSelectedQuotes'));
+                               this._buttons.remove.html(WCF.Language.get('wcf.message.quote.removeSelectedQuotes'));
+                       }
+                       else {
+                               // no selection, pick all
+                               if (this._supportPaste) this._buttons.insert.html(WCF.Language.get('wcf.message.quote.insertAllQuotes'));
+                               this._buttons.remove.html(WCF.Language.get('wcf.message.quote.removeAllQuotes'));
+                       }
+               },
+               
+               /**
+                * Checks for change event on delete-checkboxes.
+                *
+                * @param        {Object}        event
+                */
+               _change: function (event) {
+                       var $input = $(event.currentTarget);
+                       var $quoteID = $input.parent('li').attr('data-quote-id');
+                       
+                       if ($input.prop('checked')) {
+                               this._removeOnSubmit.push($quoteID);
+                       }
+                       else {
+                               var index = this._removeOnSubmit.indexOf($quoteID);
+                               if (index !== -1) {
+                                       this._removeOnSubmit.splice(index, 1);
                                }
-                       });
-               }
-       },
-       
-       /**
-        * Updates button labels if a checkbox is checked or unchecked.
-        */
-       _changeButtons: function() {
-               // selection
-               if (this._dialog.find('input.jsCheckbox:checked').length) {
-                       if (this._supportPaste) this._buttons.insert.html(WCF.Language.get('wcf.message.quote.insertSelectedQuotes'));
-                       this._buttons.remove.html(WCF.Language.get('wcf.message.quote.removeSelectedQuotes'));
-               }
-               else {
-                       // no selection, pick all
-                       if (this._supportPaste) this._buttons.insert.html(WCF.Language.get('wcf.message.quote.insertAllQuotes'));
-                       this._buttons.remove.html(WCF.Language.get('wcf.message.quote.removeAllQuotes'));
-               }
-       },
-       
-       /**
-        * Checks for change event on delete-checkboxes.
-        * 
-        * @param       {Object}        event
-        */
-       _change: function(event) {
-               var $input = $(event.currentTarget);
-               var $quoteID = $input.parent('li').attr('data-quote-id');
-               
-               if ($input.prop('checked')) {
-                       this._removeOnSubmit.push($quoteID);
-               }
-               else {
-                       var index = this._removeOnSubmit.indexOf($quoteID);
-                       if (index !== -1) {
-                               this._removeOnSubmit.splice(index, 1);
                        }
-               }
-       },
-       
-       /**
-        * Inserts the selected quotes.
-        */
-       _insertSelected: function() {
-               if (!this._dialog.find('input.jsCheckbox:checked').length) {
-                       this._dialog.find('input.jsCheckbox').prop('checked', 'checked');
-               }
-               
-               // insert all quotes
-               this._dialog.find('input.jsCheckbox:checked').each($.proxy(function(index, input) {
-                       this._insertQuote(null, input);
-               }, this));
-               
-               // close dialog
-               this._dialog.wcfDialog('close');
-       },
-       
-       /**
-        * Inserts a quote.
-        * 
-        * @param       {Event}         event
-        * @param       {Object}        inputElement
-        */
-       _insertQuote: function(event, inputElement) {
-               var listItem = $(event ? event.currentTarget : inputElement).parents('li:eq(0)');
-               var text = listItem.children('.jsFullQuote')[0].textContent.trim();
-               
-               var message = listItem.parents('.message:eq(0)');
-               var author = message.data('username');
-               var link = message.data('link');
-               var isText = !elDataBool(listItem[0], 'is-full-quote');
-               
-               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'insertQuote_' + (this._editorIdAlternative ? this._editorIdAlternative : this._editorId), {
-                       author: author,
-                       content: text,
-                       isText: isText,
-                       link: link
-               });
-               
-               // remove quote upon submit or upon request
-               this._removeOnSubmit.push(listItem.data('quote-id'));
-               
-               // close dialog
-               if (event !== null) {
+               },
+               
+               /**
+                * Inserts the selected quotes.
+                */
+               _insertSelected: function () {
+                       if (!this._dialog.find('input.jsCheckbox:checked').length) {
+                               this._dialog.find('input.jsCheckbox').prop('checked', 'checked');
+                       }
+                       
+                       // insert all quotes
+                       this._dialog.find('input.jsCheckbox:checked').each($.proxy(function (index, input) {
+                               this._insertQuote(null, input);
+                       }, this));
+                       
+                       // close dialog
                        this._dialog.wcfDialog('close');
-               }
-       },
-       
-       /**
-        * Removes selected quotes.
-        */
-       _removeSelected: function() {
-               if (!this._dialog.find('input.jsCheckbox:checked').length) {
-                       this._dialog.find('input.jsCheckbox').prop('checked', 'checked');
-               }
-               
-               var $quoteIDs = [ ];
-               this._dialog.find('input.jsCheckbox:checked').each(function(index, input) {
-                       $quoteIDs.push($(input).parents('li').attr('data-quote-id'));
-               });
+               },
+               
+               /**
+                * Inserts a quote.
+                *
+                * @param        {Event}                event
+                * @param        {Object}        inputElement
+                */
+               _insertQuote: function (event, inputElement) {
+                       var listItem = $(event ? event.currentTarget : inputElement).parents('li:eq(0)');
+                       var text = listItem.children('.jsFullQuote')[0].textContent.trim();
+                       
+                       var message = listItem.parents('.message:eq(0)');
+                       var author = message.data('username');
+                       var link = message.data('link');
+                       var isText = !elDataBool(listItem[0], 'is-full-quote');
+                       
+                       WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'insertQuote_' + (this._editorIdAlternative ? this._editorIdAlternative : this._editorId), {
+                               author: author,
+                               content: text,
+                               isText: isText,
+                               link: link
+                       });
+                       
+                       // remove quote upon submit or upon request
+                       this._removeOnSubmit.push(listItem.data('quote-id'));
+                       
+                       // close dialog
+                       if (event !== null) {
+                               this._dialog.wcfDialog('close');
+                       }
+               },
+               
+               /**
+                * Removes selected quotes.
+                */
+               _removeSelected: function () {
+                       if (!this._dialog.find('input.jsCheckbox:checked').length) {
+                               this._dialog.find('input.jsCheckbox').prop('checked', 'checked');
+                       }
+                       
+                       var $quoteIDs = [];
+                       this._dialog.find('input.jsCheckbox:checked').each(function (index, input) {
+                               $quoteIDs.push($(input).parents('li').attr('data-quote-id'));
+                       });
+                       
+                       if ($quoteIDs.length) {
+                               // get object types
+                               var $objectTypes = [];
+                               for (var $objectType in this._handlers) {
+                                       if (this._handlers.hasOwnProperty($objectType)) {
+                                               $objectTypes.push($objectType);
+                                       }
+                               }
+                               
+                               this._proxy.setOption('data', {
+                                       actionName: 'remove',
+                                       getFullQuoteObjectIDs: this._handlers.length > 0,
+                                       objectTypes: $objectTypes,
+                                       quoteIDs: $quoteIDs
+                               });
+                               this._proxy.sendRequest();
+                               
+                               this._dialog.wcfDialog('close');
+                       }
+               },
+               
+               /**
+                * Appends list of quote ids to remove after successful submit.
+                */
+               _submit: function () {
+                       if (this._supportPaste && this._removeOnSubmit.length > 0) {
+                               var $formSubmit = this._form.find('.formSubmit');
+                               for (var i = 0, length = this._removeOnSubmit.length; i < length; i++) {
+                                       $('<input type="hidden" name="__removeQuoteIDs[]" value="' + this._removeOnSubmit[i] + '" />').appendTo($formSubmit);
+                               }
+                       }
+               },
+               
+               /**
+                * Returns a list of quote ids marked for removal.
+                *
+                * @return        {Array}
+                */
+               getQuotesMarkedForRemoval: function () {
+                       return this._removeOnSubmit;
+               },
+               
+               /**
+                * Marks quote ids for removal.
+                */
+               markQuotesForRemoval: function () {
+                       if (this._removeOnSubmit.length) {
+                               this._proxy.setOption('data', {
+                                       actionName: 'markForRemoval',
+                                       quoteIDs: this._removeOnSubmit
+                               });
+                               this._proxy.suppressErrors();
+                               this._proxy.sendRequest();
+                       }
+               },
+               
+               /**
+                * Removes all marked quote ids.
+                */
+               removeMarkedQuotes: function () {
+                       if (this._removeOnSubmit.length) {
+                               this._proxy.setOption('data', {
+                                       actionName: 'removeMarkedQuotes',
+                                       getFullQuoteObjectIDs: this._handlers.length > 0
+                               });
+                               this._proxy.sendRequest();
+                       }
+               },
                
-               if ($quoteIDs.length) {
-                       // get object types
+               /**
+                * Counts stored quotes.
+                */
+               countQuotes: function () {
                        var $objectTypes = [];
                        for (var $objectType in this._handlers) {
                                if (this._handlers.hasOwnProperty($objectType)) {
@@ -1832,118 +2003,113 @@ WCF.Message.Quote.Manager = Class.extend({
                        }
                        
                        this._proxy.setOption('data', {
-                               actionName: 'remove',
-                               getFullQuoteObjectIDs: this._handlers.length > 0,
-                               objectTypes: $objectTypes,
-                               quoteIDs: $quoteIDs
-                       });
-                       this._proxy.sendRequest();
-                       
-                       this._dialog.wcfDialog('close');
-               }
-       },
-       
-       /**
-        * Appends list of quote ids to remove after successful submit.
-        */
-       _submit: function() {
-               if (this._supportPaste && this._removeOnSubmit.length > 0) {
-                       var $formSubmit = this._form.find('.formSubmit');
-                       for (var i = 0, length = this._removeOnSubmit.length; i < length; i++) {
-                               $('<input type="hidden" name="__removeQuoteIDs[]" value="' + this._removeOnSubmit[i] + '" />').appendTo($formSubmit);
-                       }
-               }
-       },
-       
-       /**
-        * Returns a list of quote ids marked for removal.
-        * 
-        * @return      {Array}
-        */
-       getQuotesMarkedForRemoval: function() {
-               return this._removeOnSubmit;
-       },
-       
-       /**
-        * Marks quote ids for removal.
-        */
-       markQuotesForRemoval: function() {
-               if (this._removeOnSubmit.length) {
-                       this._proxy.setOption('data', {
-                               actionName: 'markForRemoval',
-                               quoteIDs: this._removeOnSubmit
-                       });
-                       this._proxy.suppressErrors();
-                       this._proxy.sendRequest();
-               }
-       },
-       
-       /**
-        * Removes all marked quote ids.
-        */
-       removeMarkedQuotes: function() {
-               if (this._removeOnSubmit.length) {
-                       this._proxy.setOption('data', {
-                               actionName: 'removeMarkedQuotes',
-                               getFullQuoteObjectIDs: this._handlers.length > 0
+                               actionName: 'count',
+                               getFullQuoteObjectIDs: ($objectTypes.length > 0),
+                               objectTypes: $objectTypes
                        });
                        this._proxy.sendRequest();
-               }
-       },
-       
-       /**
-        * Counts stored quotes.
-        */
-       countQuotes: function() {
-               var $objectTypes = [ ];
-               for (var $objectType in this._handlers) {
-                       if (this._handlers.hasOwnProperty($objectType)) {
-                               $objectTypes.push($objectType);
+               },
+               
+               /**
+                * Handles successful AJAX requests.
+                *
+                * @param        {Object}        data
+                */
+               _success: function (data) {
+                       if (data === null) {
+                               return;
                        }
-               }
-               
-               this._proxy.setOption('data', {
-                       actionName: 'count',
-                       getFullQuoteObjectIDs: ($objectTypes.length > 0),
-                       objectTypes: $objectTypes
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       {Object}        data
-        */
-       _success: function(data) {
-               if (data === null) {
-                       return;
-               }
-               
-               if (data.count !== undefined) {
-                       var $fullQuoteObjectIDs = (data.fullQuoteObjectIDs !== undefined) ? data.fullQuoteObjectIDs : { };
-                       this.updateCount(data.count, $fullQuoteObjectIDs);
-               }
-               
-               if (data.template !== undefined) {
-                       if ($.trim(data.template) == '') {
-                               this.updateCount(0, { });
+                       
+                       if (data.count !== undefined) {
+                               var $fullQuoteObjectIDs = (data.fullQuoteObjectIDs !== undefined) ? data.fullQuoteObjectIDs : {};
+                               this.updateCount(data.count, $fullQuoteObjectIDs);
                        }
-                       else {
-                               this.renderDialog(data.template);
+                       
+                       if (data.template !== undefined) {
+                               if ($.trim(data.template) == '') {
+                                       this.updateCount(0, {});
+                               }
+                               else {
+                                       this.renderDialog(data.template);
+                               }
                        }
-               }
-       },
-       
-       /**
-        * Returns true if pasting is supported.
-        * 
-        * @return      boolean
-        */
-       supportPaste: function() {
-               return this._supportPaste;
-       }
-});
+               },
+               
+               /**
+                * Returns true if pasting is supported.
+                *
+                * @return        boolean
+                */
+               supportPaste: function () {
+                       return this._supportPaste;
+               }
+       });
+}
+else {
+       WCF.Message.Quote.Handler = Class.extend({
+               _activeContainerID: "",
+               _className: "",
+               _containers: {},
+               _containerSelector: "",
+               _copyQuote: {},
+               _message: "",
+               _messageBodySelector: "",
+               _objectID: 0,
+               _objectType: "",
+               _proxy: {},
+               _quoteManager: {},
+               init: function() {},
+               _initContainers: function() {},
+               _mouseDown: function() {},
+               _getNodeText: function() {},
+               _mouseUp: function() {},
+               _normalize: function() {},
+               _getBoundingRectangle: function() {},
+               _initCopyQuote: function() {},
+               _getSelectedText: function() {},
+               _saveFullQuote: function() {},
+               _saveQuote: function() {},
+               _saveAndInsertQuote: function() {},
+               _success: function() {},
+               updateFullQuoteObjectIDs: function() {}
+       });
+       
+       WCF.Message.Quote.Manager = Class.extend({
+               _buttons: {},
+               _count: 0,
+               _dialog: {},
+               _editorId: "",
+               _editorIdAlternative: "",
+               _form: {},
+               _handlers: {},
+               _hasTemplate: false,
+               _insertQuotes: true,
+               _proxy: {},
+               _removeOnSubmit: {},
+               _supportPaste: false,
+               init: function() {},
+               setAlternativeEditor: function() {},
+               clearAlternativeEditor: function() {},
+               register: function() {},
+               updateCount: function() {},
+               insertQuotes: function() {},
+               _toggleShowQuotes: function() {},
+               _click: function() {},
+               renderDialog: function() {},
+               _changeButtons: function() {},
+               _change: function() {},
+               _insertSelected: function() {},
+               _insertQuote: function() {},
+               _removeSelected: function() {},
+               _submit: function() {},
+               getQuotesMarkedForRemoval: function() {},
+               markQuotesForRemoval: function() {},
+               removeMarkedQuotes: function() {},
+               countQuotes: function() {},
+               _success: function() {},
+               supportPaste: function() {}
+       });
+}
 
 /**
  * Namespace for message sharing related classes.
index d79714f1774b28bc74d78c0e59b8089493277a0d..2b3415093e30d1b9779041fddbb8f03d81c952c6 100644 (file)
  */
 WCF.Moderation = { };
 
-/**
- * Moderation queue management.
- * 
- * @param      integer         queueID
- * @param      string          redirectURL
- */
-WCF.Moderation.Management = Class.extend({
-       /**
-        * button selector
-        * @var string
-        */
-       _buttonSelector: '',
-       
-       /**
-        * action class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * list of templates for confirmation message by action name
-        * @var object
-        */
-       _confirmationTemplate: { },
-       
-       /**
-        * dialog overlay
-        * @var jQuery
-        */
-       _dialog: null,
-       
-       /**
-        * language item pattern
-        * @var string
-        */
-       _languageItem: '',
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * queue id
-        * @var integer
-        */
-       _queueID: 0,
-       
-       /**
-        * redirect URL
-        * @var string
-        */
-       _redirectURL: '',
-       
-       /**
-        * Initializes the moderation report management.
-        * 
-        * @param       integer         queueID
-        * @param       string          redirectURL
-        * @param       string          languageItem
-        */
-       init: function(queueID, redirectURL, languageItem) {
-               if (!this._buttonSelector) {
-                       console.debug("[WCF.Moderation.Management] Missing button selector, aborting.");
-                       return;
-               }
-               else if (!this._className) {
-                       console.debug("[WCF.Moderation.Management] Missing class name, aborting.");
-                       return;
-               }
+if (COMPILER_TARGET_DEFAULT) {
+       /**
+        * Moderation queue management.
+        *
+        * @param        integer                queueID
+        * @param        string                redirectURL
+        */
+       WCF.Moderation.Management = Class.extend({
+               /**
+                * button selector
+                * @var        string
+                */
+               _buttonSelector: '',
                
-               this._dialog = null;
-               this._queueID = queueID;
-               this._redirectURL = redirectURL;
-               this._languageItem = languageItem;
+               /**
+                * action class name
+                * @var        string
+                */
+               _className: '',
                
-               this._proxy = new WCF.Action.Proxy({
-                       failure: $.proxy(this._failure, this),
-                       success: $.proxy(this._success, this)
-               });
+               /**
+                * list of templates for confirmation message by action name
+                * @var        object
+                */
+               _confirmationTemplate: {},
                
-               $(this._buttonSelector).click($.proxy(this._click, this));
+               /**
+                * dialog overlay
+                * @var        jQuery
+                */
+               _dialog: null,
                
-               $('#moderationAssignUser').click($.proxy(this._clickAssignedUser, this));
-       },
-       
-       /**
-        * Handles clicks on the action buttons.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               var $actionName = $(event.currentTarget).wcfIdentify();
-               var $innerTemplate = '';
-               if (this._confirmationTemplate[$actionName]) {
-                       $innerTemplate = this._confirmationTemplate[$actionName];
-               }
+               /**
+                * language item pattern
+                * @var        string
+                */
+               _languageItem: '',
                
-               WCF.System.Confirmation.show(WCF.Language.get(this._languageItem.replace(/{actionName}/, $actionName)), $.proxy(function(action, parameters, content) {
-                       if (action === 'confirm') {
-                               var $parameters = {
-                                       actionName: $actionName,
-                                       className: this._className,
-                                       objectIDs: [ this._queueID ]
-                               };
-                               if (this._confirmationTemplate[$actionName]) {
-                                       $parameters.parameters = { };
-                                       $(content).find('input, textarea').each(function(index, element) {
-                                               var $element = $(element);
-                                               var $value = $element.val();
-                                               if ($element.getTagName() === 'input' && $element.attr('type') === 'checkbox') {
-                                                       if (!$element.is(':checked')) {
-                                                               $value = null;
+               /**
+                * action proxy
+                * @var        WCF.Action.Proxy
+                */
+               _proxy: null,
+               
+               /**
+                * queue id
+                * @var        integer
+                */
+               _queueID: 0,
+               
+               /**
+                * redirect URL
+                * @var        string
+                */
+               _redirectURL: '',
+               
+               /**
+                * Initializes the moderation report management.
+                *
+                * @param        integer                queueID
+                * @param        string                redirectURL
+                * @param        string                languageItem
+                */
+               init: function (queueID, redirectURL, languageItem) {
+                       if (!this._buttonSelector) {
+                               console.debug("[WCF.Moderation.Management] Missing button selector, aborting.");
+                               return;
+                       }
+                       else if (!this._className) {
+                               console.debug("[WCF.Moderation.Management] Missing class name, aborting.");
+                               return;
+                       }
+                       
+                       this._dialog = null;
+                       this._queueID = queueID;
+                       this._redirectURL = redirectURL;
+                       this._languageItem = languageItem;
+                       
+                       this._proxy = new WCF.Action.Proxy({
+                               failure: $.proxy(this._failure, this),
+                               success: $.proxy(this._success, this)
+                       });
+                       
+                       $(this._buttonSelector).click($.proxy(this._click, this));
+                       
+                       $('#moderationAssignUser').click($.proxy(this._clickAssignedUser, this));
+               },
+               
+               /**
+                * Handles clicks on the action buttons.
+                *
+                * @param        object                event
+                */
+               _click: function (event) {
+                       var $actionName = $(event.currentTarget).wcfIdentify();
+                       var $innerTemplate = '';
+                       if (this._confirmationTemplate[$actionName]) {
+                               $innerTemplate = this._confirmationTemplate[$actionName];
+                       }
+                       
+                       WCF.System.Confirmation.show(WCF.Language.get(this._languageItem.replace(/{actionName}/, $actionName)), $.proxy(function (action, parameters, content) {
+                               if (action === 'confirm') {
+                                       var $parameters = {
+                                               actionName: $actionName,
+                                               className: this._className,
+                                               objectIDs: [this._queueID]
+                                       };
+                                       if (this._confirmationTemplate[$actionName]) {
+                                               $parameters.parameters = {};
+                                               $(content).find('input, textarea').each(function (index, element) {
+                                                       var $element = $(element);
+                                                       var $value = $element.val();
+                                                       if ($element.getTagName() === 'input' && $element.attr('type') === 'checkbox') {
+                                                               if (!$element.is(':checked')) {
+                                                                       $value = null;
+                                                               }
                                                        }
-                                               }
-                                               
-                                               if ($value !== null) {
-                                                       $parameters.parameters[$element.attr('name')] = $value;
-                                               }
-                                       });
+                                                       
+                                                       if ($value !== null) {
+                                                               $parameters.parameters[$element.attr('name')] = $value;
+                                                       }
+                                               });
+                                       }
+                                       
+                                       this._proxy.setOption('data', $parameters);
+                                       this._proxy.sendRequest();
+                                       
+                                       $(this._buttonSelector).disable();
                                }
+                       }, this), {}, $innerTemplate);
+               },
+               
+               /**
+                * Handles clicks on the assign user link.
+                */
+               _clickAssignedUser: function () {
+                       this._proxy.setOption('data', {
+                               actionName: 'getAssignUserForm',
+                               className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction',
+                               objectIDs: [this._queueID]
+                       });
+                       this._proxy.sendRequest();
+               },
+               
+               /**
+                * Handles successful AJAX requests.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        jQuery                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       switch (data.actionName) {
+                               case 'getAssignUserForm':
+                                       if (this._dialog === null) {
+                                               this._dialog = $('<div />').hide().appendTo(document.body);
+                                               this._dialog.html(data.returnValues.template).wcfDialog({
+                                                       title: WCF.Language.get('wcf.moderation.assignedUser')
+                                               });
+                                       }
+                                       else {
+                                               this._dialog.html(data.returnValues.template).wcfDialog('open');
+                                       }
+                                       
+                                       this._dialog.find('button[data-type=submit]').click($.proxy(this._assignUser, this));
+                                       break;
                                
-                               this._proxy.setOption('data', $parameters);
-                               this._proxy.sendRequest();
+                               case 'assignUser':
+                                       var $span = $('#moderationAssignedUserContainer > dd > span').empty();
+                                       if (data.returnValues.userID) {
+                                               $('<a href="' + data.returnValues.link + '" data-user-id="' + data.returnValues.userID + '" class="userLink">' + WCF.String.escapeHTML(data.returnValues.username) + '</a>').appendTo($span);
+                                       }
+                                       else {
+                                               $span.append(data.returnValues.username);
+                                       }
+                                       
+                                       $span.append(' ');
+                                       
+                                       if (data.returnValues.newStatus) {
+                                               $('#moderationStatusContainer > dd').text(WCF.Language.get('wcf.moderation.status.' + data.returnValues.newStatus));
+                                       }
+                                       
+                                       this._dialog.wcfDialog('close');
+                                       
+                                       new WCF.System.Notification().show();
+                                       break;
                                
-                               $(this._buttonSelector).disable();
-                       }
-               }, this), { }, $innerTemplate);
-       },
-       
-       /**
-        * Handles clicks on the assign user link.
-        */
-       _clickAssignedUser: function() {
-               this._proxy.setOption('data', {
-                       actionName: 'getAssignUserForm',
-                       className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction',
-                       objectIDs: [ this._queueID ]
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               switch (data.actionName) {
-                       case 'getAssignUserForm':
-                               if (this._dialog === null) {
-                                       this._dialog = $('<div />').hide().appendTo(document.body);
-                                       this._dialog.html(data.returnValues.template).wcfDialog({
-                                               title: WCF.Language.get('wcf.moderation.assignedUser')
+                               default:
+                                       var $notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success'));
+                                       var self = this;
+                                       $notification.show(function () {
+                                               window.location = self._redirectURL;
                                        });
-                               }
-                               else {
-                                       this._dialog.html(data.returnValues.template).wcfDialog('open');
-                               }
-                               
-                               this._dialog.find('button[data-type=submit]').click($.proxy(this._assignUser, this));
-                       break;
-                       
-                       case 'assignUser':
-                               var $span = $('#moderationAssignedUserContainer > dd > span').empty();
-                               if (data.returnValues.userID) {
-                                       $('<a href="' + data.returnValues.link + '" data-user-id="' + data.returnValues.userID + '" class="userLink">' + WCF.String.escapeHTML(data.returnValues.username) + '</a>').appendTo($span);
-                               }
-                               else {
-                                       $span.append(data.returnValues.username);
-                               }
-                               
-                               $span.append(' ');
+                                       break;
+                       }
+               },
+               
+               /**
+                * Handles errorneus AJAX requests.
+                *
+                * @param        object                data
+                * @param        jQuery                jqXHR
+                * @param        string                textStatus
+                * @param        string                errorThrown
+                */
+               _failure: function (data, jqXHR, textStatus, errorThrown) {
+                       if (data.returnValues && data.returnValues.fieldName && data.returnValues.fieldName == 'assignedUsername') {
+                               this._dialog.find('small.innerError').remove();
                                
-                               if (data.returnValues.newStatus) {
-                                       $('#moderationStatusContainer > dd').text(WCF.Language.get('wcf.moderation.status.' + data.returnValues.newStatus));
+                               var $errorString = '';
+                               switch (data.returnValues.errorType) {
+                                       case 'empty':
+                                               $errorString = WCF.Language.get('wcf.global.form.error.empty');
+                                               break;
+                                       
+                                       case 'notAffected':
+                                               $errorString = WCF.Language.get('wcf.moderation.assignedUser.error.notAffected');
+                                               break;
+                                       
+                                       default:
+                                               $errorString = WCF.Language.get('wcf.user.username.error.' + data.returnValues.errorType, {username: this._dialog.find('#assignedUsername').val()});
+                                               break;
                                }
                                
-                               this._dialog.wcfDialog('close');
+                               $('<small class="innerError">' + $errorString + '</small>').insertAfter(this._dialog.find('#assignedUsername'));
                                
-                               new WCF.System.Notification().show();
-                       break;
-                       
-                       default:
-                               var $notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success'));
-                               var self = this;
-                               $notification.show(function() {
-                                       window.location = self._redirectURL;
-                               });
-                       break;
-               }
-       },
-       
-       /**
-        * Handles errorneus AJAX requests.
-        * 
-        * @param       object          data
-        * @param       jQuery          jqXHR
-        * @param       string          textStatus
-        * @param       string          errorThrown
-        */
-       _failure: function(data, jqXHR, textStatus, errorThrown) {
-               if (data.returnValues && data.returnValues.fieldName && data.returnValues.fieldName == 'assignedUsername') {
-                       this._dialog.find('small.innerError').remove();
+                               return false;
+                       }
                        
-                       var $errorString = '';
-                       switch (data.returnValues.errorType) {
-                               case 'empty':
-                                       $errorString = WCF.Language.get('wcf.global.form.error.empty');
-                               break;
-                               
-                               case 'notAffected':
-                                       $errorString = WCF.Language.get('wcf.moderation.assignedUser.error.notAffected');
-                               break;
-                               
-                               default:
-                                       $errorString = WCF.Language.get('wcf.user.username.error.' + data.returnValues.errorType, { username: this._dialog.find('#assignedUsername').val() });
-                               break;
+                       return true;
+               },
+               
+               /**
+                * Submits the assign user form.
+                */
+               _assignUser: function () {
+                       var $assignedUserID = this._dialog.find('input[name=assignedUserID]:checked').val();
+                       var $assignedUsername = '';
+                       if ($assignedUserID == -1) {
+                               $assignedUsername = $.trim(this._dialog.find('#assignedUsername').val());
                        }
                        
-                       $('<small class="innerError">' + $errorString + '</small>').insertAfter(this._dialog.find('#assignedUsername'));
+                       if ($assignedUserID == -1 && $assignedUsername.length == 0) {
+                               this._dialog.find('small.innerError').remove();
+                               $('<small class="innerError">' + WCF.Language.get('wcf.global.form.error.empty') + '</small>').insertAfter(this._dialog.find('#assignedUsername'));
+                               return;
+                       }
                        
-                       return false;
-               }
-               
-               return true;
-       },
-       
-       /**
-        * Submits the assign user form.
-        */
-       _assignUser: function() {
-               var $assignedUserID = this._dialog.find('input[name=assignedUserID]:checked').val();
-               var $assignedUsername = '';
-               if ($assignedUserID == -1) {
-                       $assignedUsername = $.trim(this._dialog.find('#assignedUsername').val());
-               }
-               
-               if ($assignedUserID == -1 && $assignedUsername.length == 0) {
-                       this._dialog.find('small.innerError').remove();
-                       $('<small class="innerError">' + WCF.Language.get('wcf.global.form.error.empty') + '</small>').insertAfter(this._dialog.find('#assignedUsername'));
-                       return;
+                       this._proxy.setOption('data', {
+                               actionName: 'assignUser',
+                               className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction',
+                               objectIDs: [this._queueID],
+                               parameters: {
+                                       assignedUserID: $assignedUserID,
+                                       assignedUsername: $assignedUsername
+                               }
+                       });
+                       this._proxy.sendRequest();
                }
-               
-               this._proxy.setOption('data', {
-                       actionName: 'assignUser',
-                       className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction',
-                       objectIDs: [ this._queueID ],
-                       parameters: {
-                               assignedUserID: $assignedUserID,
-                               assignedUsername: $assignedUsername
-                       }
-               });
-               this._proxy.sendRequest();
-       }
-});
+       });
+}
+else {
+       WCF.Moderation.Management = Class.extend({
+               _buttonSelector: "",
+               _className: "",
+               _confirmationTemplate: {},
+               _dialog: {},
+               _languageItem: "",
+               _proxy: {},
+               _queueID: 0,
+               _redirectURL: "",
+               init: function() {},
+               _click: function() {},
+               _clickAssignedUser: function() {},
+               _success: function() {},
+               _failure: function() {},
+               _assignUser: function() {}
+       });
+}
 
 /**
  * Namespace for moderation queue related classes.
  */
 WCF.Moderation.Queue = { };
 
-/**
- * Marks one moderation queue entry as read.
- */
-WCF.Moderation.Queue.MarkAsRead = Class.extend({
+if (COMPILER_TARGET_DEFAULT) {
        /**
-        * action proxy
-        * @var WCF.Action.Proxy
+        * Marks one moderation queue entry as read.
         */
-       _proxy: null,
-       
-       /**
-        * Initializes the mark as read for queue entries.
-        */
-       init: function() {
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
+       WCF.Moderation.Queue.MarkAsRead = Class.extend({
+               /**
+                * action proxy
+                * @var        WCF.Action.Proxy
+                */
+               _proxy: null,
                
-               $(document).on('dblclick', '.moderationList .new .columnAvatar', $.proxy(this._dblclick, this));
-       },
-       
-       /**
-        * Handles double clicks on avatar.
-        * 
-        * @param       object          event
-        */
-       _dblclick: function(event) {
-               this._proxy.setOption('data', {
-                       actionName: 'markAsRead',
-                       className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction',
-                       objectIDs: [ $(event.currentTarget).parents('.moderationQueueEntry:eq(0)').data('queueID') ]
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               $('.moderationList .new').each(function(index, element) {
-                       var $element = $(element);
-                       if (WCF.inArray($element.data('queueID'), data.objectIDs)) {
-                               // remove new class
-                               $element.removeClass('new');
-                               
-                               // remove event
-                               $element.find('.columnAvatar').off('dblclick');
-                       }
-               });
-       }
-});
-
-/**
- * Marks all moderation queue entries as read.
- */
-WCF.Moderation.Queue.MarkAllAsRead = Class.extend({
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * Initializes the WCF.Moderation.Queue.MarkAllAsRead class.
-        */
-       init: function() {
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
+               /**
+                * Initializes the mark as read for queue entries.
+                */
+               init: function () {
+                       this._proxy = new WCF.Action.Proxy({
+                               success: $.proxy(this._success, this)
+                       });
+                       
+                       $(document).on('dblclick', '.moderationList .new .columnAvatar', $.proxy(this._dblclick, this));
+               },
                
-               $('.markAllAsReadButton').click($.proxy(this._click, this));
-       },
-       
-       /**
-        * Handles clicks.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               event.preventDefault();
+               /**
+                * Handles double clicks on avatar.
+                *
+                * @param        object                event
+                */
+               _dblclick: function (event) {
+                       this._proxy.setOption('data', {
+                               actionName: 'markAsRead',
+                               className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction',
+                               objectIDs: [$(event.currentTarget).parents('.moderationQueueEntry:eq(0)').data('queueID')]
+                       });
+                       this._proxy.sendRequest();
+               },
                
-               this._proxy.setOption('data', {
-                       actionName: 'markAllAsRead',
-                       className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction'
-               });
-               this._proxy.sendRequest();
-       },
+               /**
+                * Handles successful AJAX requests.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        jQuery                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       $('.moderationList .new').each(function (index, element) {
+                               var $element = $(element);
+                               if (WCF.inArray($element.data('queueID'), data.objectIDs)) {
+                                       // remove new class
+                                       $element.removeClass('new');
+                                       
+                                       // remove event
+                                       $element.find('.columnAvatar').off('dblclick');
+                               }
+                       });
+               }
+       });
        
        /**
-        * Marks all queue entries as read.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
+        * Marks all moderation queue entries as read.
         */
-       _success: function(data, textStatus, jqXHR) {
-               // update dropdown
-               var dropdown = WCF.Dropdown.Interactive.Handler.getDropdown('outstandingModeration');
-               if (dropdown) {
-                       dropdown.getLinkList().find('.interactiveDropdownItemMarkAllAsRead').remove();
-                       dropdown.getItemList().find('.interactiveDropdownItemMarkAsRead').remove();
-               }
+       WCF.Moderation.Queue.MarkAllAsRead = Class.extend({
+               /**
+                * action proxy
+                * @var        WCF.Action.Proxy
+                */
+               _proxy: null,
                
-               // remove badge in userpanel
-               $('#outstandingModeration .badgeUpdate').remove();
+               /**
+                * Initializes the WCF.Moderation.Queue.MarkAllAsRead class.
+                */
+               init: function () {
+                       this._proxy = new WCF.Action.Proxy({
+                               success: $.proxy(this._success, this)
+                       });
+                       
+                       $('.markAllAsReadButton').click($.proxy(this._click, this));
+               },
                
-               // fix moderation list
-               var $moderationList = $('.moderationList');
-               $moderationList.find('.new').removeClass('new');
-               $moderationList.find('.columnAvatar').off('dblclick');
-       }
-});
+               /**
+                * Handles clicks.
+                *
+                * @param        object                event
+                */
+               _click: function (event) {
+                       event.preventDefault();
+                       
+                       this._proxy.setOption('data', {
+                               actionName: 'markAllAsRead',
+                               className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction'
+                       });
+                       this._proxy.sendRequest();
+               },
+               
+               /**
+                * Marks all queue entries as read.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        jQuery                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       // update dropdown
+                       var dropdown = WCF.Dropdown.Interactive.Handler.getDropdown('outstandingModeration');
+                       if (dropdown) {
+                               dropdown.getLinkList().find('.interactiveDropdownItemMarkAllAsRead').remove();
+                               dropdown.getItemList().find('.interactiveDropdownItemMarkAsRead').remove();
+                       }
+                       
+                       // remove badge in userpanel
+                       $('#outstandingModeration .badgeUpdate').remove();
+                       
+                       // fix moderation list
+                       var $moderationList = $('.moderationList');
+                       $moderationList.find('.new').removeClass('new');
+                       $moderationList.find('.columnAvatar').off('dblclick');
+               }
+       });
+}
+else {
+       WCF.Moderation.Queue.MarkAsRead = Class.extend({
+               _proxy: {},
+               init: function() {},
+               _dblclick: function() {},
+               _success: function() {}
+       });
+       
+       WCF.Moderation.Queue.MarkAllAsRead = Class.extend({
+               _proxy: {},
+               init: function() {},
+               _click: function() {},
+               _success: function() {}
+       });
+}
 
 /**
  * Namespace for activation related classes.
  */
 WCF.Moderation.Activation = { };
 
-/**
- * Manages disabled content within moderation.
- * 
- * @see        WCF.Moderation.Management
- */
-WCF.Moderation.Activation.Management = WCF.Moderation.Management.extend({
-       /**
-        * @see WCF.Moderation.Management.init()
-        */
-       init: function(queueID, redirectURL) {
-               this._buttonSelector = '#enableContent, #removeContent';
-               this._className = 'wcf\\data\\moderation\\queue\\ModerationQueueActivationAction';
-               
-               this._super(queueID, redirectURL, 'wcf.moderation.activation.{actionName}.confirmMessage');
-       }
-});
+if (COMPILER_TARGET_DEFAULT) {
+       /**
+        * Manages disabled content within moderation.
+        *
+        * @see        WCF.Moderation.Management
+        */
+       WCF.Moderation.Activation.Management = WCF.Moderation.Management.extend({
+               /**
+                * @see        WCF.Moderation.Management.init()
+                */
+               init: function (queueID, redirectURL) {
+                       this._buttonSelector = '#enableContent, #removeContent';
+                       this._className = 'wcf\\data\\moderation\\queue\\ModerationQueueActivationAction';
+                       
+                       this._super(queueID, redirectURL, 'wcf.moderation.activation.{actionName}.confirmMessage');
+               }
+       });
+}
+else {
+       WCF.Moderation.Activation.Management = WCF.Moderation.Management.extend({
+               init: function() {},
+               _buttonSelector: "",
+               _className: "",
+               _confirmationTemplate: {},
+               _dialog: {},
+               _languageItem: "",
+               _proxy: {},
+               _queueID: 0,
+               _redirectURL: "",
+               _click: function() {},
+               _clickAssignedUser: function() {},
+               _success: function() {},
+               _failure: function() {},
+               _assignUser: function() {}
+       });
+}
 
 /**
  * Namespace for report related classes.
@@ -600,99 +657,140 @@ WCF.Moderation.Report.Content = Class.extend({
        }
 });
 
-/**
- * Manages reported content within moderation.
- * 
- * @see        WCF.Moderation.Management
- */
-WCF.Moderation.Report.Management = WCF.Moderation.Management.extend({
-       /**
-        * @see WCF.Moderation.Management.init()
-        */
-       init: function(queueID, redirectURL) {
-               this._buttonSelector = '#removeContent, #removeReport';
-               this._className = 'wcf\\data\\moderation\\queue\\ModerationQueueReportAction';
-               
-               this._super(queueID, redirectURL, 'wcf.moderation.report.{actionName}.confirmMessage');
-               
-               this._confirmationTemplate.removeContent = $('<div class="section"><dl><dt><label for="message">' + WCF.Language.get('wcf.moderation.report.removeContent.reason') + '</label></dt><dd><textarea name="message" id="message" cols="40" rows="3" /></dd></dl></div>');
-       }
-});
-
-/**
- * User Panel implementation for moderation queues.
- * 
- * @see        WCF.User.Panel.Abstract
- */
-WCF.User.Panel.Moderation = WCF.User.Panel.Abstract.extend({
+if (COMPILER_TARGET_DEFAULT) {
+       /**
+        * Manages reported content within moderation.
+        *
+        * @see        WCF.Moderation.Management
+        */
+       WCF.Moderation.Report.Management = WCF.Moderation.Management.extend({
+               /**
+                * @see        WCF.Moderation.Management.init()
+                */
+               init: function (queueID, redirectURL) {
+                       this._buttonSelector = '#removeContent, #removeReport';
+                       this._className = 'wcf\\data\\moderation\\queue\\ModerationQueueReportAction';
+                       
+                       this._super(queueID, redirectURL, 'wcf.moderation.report.{actionName}.confirmMessage');
+                       
+                       this._confirmationTemplate.removeContent = $('<div class="section"><dl><dt><label for="message">' + WCF.Language.get('wcf.moderation.report.removeContent.reason') + '</label></dt><dd><textarea name="message" id="message" cols="40" rows="3" /></dd></dl></div>');
+               }
+       });
+       
        /**
-        * @see WCF.User.Panel.Abstract.init()
+        * User Panel implementation for moderation queues.
+        *
+        * @see        WCF.User.Panel.Abstract
         */
-       init: function(options) {
-               options.enableMarkAsRead = true;
+       WCF.User.Panel.Moderation = WCF.User.Panel.Abstract.extend({
+               /**
+                * @see        WCF.User.Panel.Abstract.init()
+                */
+               init: function (options) {
+                       options.enableMarkAsRead = true;
+                       
+                       this._super($('#outstandingModeration'), 'outstandingModeration', options);
+                       
+                       require(['EventHandler'], (function (EventHandler) {
+                               EventHandler.add('com.woltlab.wcf.UserMenuMobile', 'more', (function (data) {
+                                       if (data.identifier === 'com.woltlab.wcf.moderation') {
+                                               this.toggle();
+                                       }
+                               }).bind(this));
+                       }).bind(this));
+               },
                
-               this._super($('#outstandingModeration'), 'outstandingModeration', options);
+               /**
+                * @see        WCF.User.Panel.Abstract._initDropdown()
+                */
+               _initDropdown: function () {
+                       var $dropdown = this._super();
+                       
+                       $('<li><a href="' + this._options.deletedContentLink + '" title="' + this._options.deletedContent + '" class="jsTooltip"><span class="icon icon24 fa-trash-o" /></a></li>').appendTo($dropdown.getLinkList());
+                       
+                       return $dropdown;
+               },
                
-               require(['EventHandler'], (function(EventHandler) {
-                       EventHandler.add('com.woltlab.wcf.UserMenuMobile', 'more', (function(data) {
-                               if (data.identifier === 'com.woltlab.wcf.moderation') {
-                                       this.toggle();
-                               }
-                       }).bind(this));
-               }).bind(this));
-       },
-       
-       /**
-        * @see WCF.User.Panel.Abstract._initDropdown()
-        */
-       _initDropdown: function() {
-               var $dropdown = this._super();
+               /**
+                * @see        WCF.User.Panel.Abstract._load()
+                */
+               _load: function () {
+                       this._proxy.setOption('data', {
+                               actionName: 'getOutstandingQueues',
+                               className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction'
+                       });
+                       this._proxy.sendRequest();
+               },
                
-               $('<li><a href="' + this._options.deletedContentLink + '" title="' + this._options.deletedContent + '" class="jsTooltip"><span class="icon icon24 fa-trash-o" /></a></li>').appendTo($dropdown.getLinkList());
+               /**
+                * @see        WCF.User.Panel.Abstract._markAsRead()
+                */
+               _markAsRead: function (event, objectID) {
+                       this._proxy.setOption('data', {
+                               actionName: 'markAsRead',
+                               className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction',
+                               objectIDs: [objectID]
+                       });
+                       this._proxy.sendRequest();
+               },
                
-               return $dropdown;
-       },
-       
-       /**
-        * @see WCF.User.Panel.Abstract._load()
-        */
-       _load: function() {
-               this._proxy.setOption('data', {
-                       actionName: 'getOutstandingQueues',
-                       className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction'
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * @see WCF.User.Panel.Abstract._markAsRead()
-        */
-       _markAsRead: function(event, objectID) {
-               this._proxy.setOption('data', {
-                       actionName: 'markAsRead',
-                       className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction',
-                       objectIDs: [ objectID ]
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * @see WCF.User.Panel.Abstract._markAllAsRead()
-        */
-       _markAllAsRead: function(event) {
-               this._proxy.setOption('data', {
-                       actionName: 'markAllAsRead',
-                       className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction'
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * @see WCF.User.Panel.Abstract.resetItems()
-        */
-       resetItems: function() {
-               this._super();
+               /**
+                * @see        WCF.User.Panel.Abstract._markAllAsRead()
+                */
+               _markAllAsRead: function (event) {
+                       this._proxy.setOption('data', {
+                               actionName: 'markAllAsRead',
+                               className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction'
+                       });
+                       this._proxy.sendRequest();
+               },
                
-               this._loadData = true;
-       }
-});
+               /**
+                * @see        WCF.User.Panel.Abstract.resetItems()
+                */
+               resetItems: function () {
+                       this._super();
+                       
+                       this._loadData = true;
+               }
+       });
+}
+else {
+       WCF.Moderation.Report.Management = WCF.Moderation.Management.extend({
+               init: function() {},
+               _buttonSelector: "",
+               _className: "",
+               _confirmationTemplate: {},
+               _dialog: {},
+               _languageItem: "",
+               _proxy: {},
+               _queueID: 0,
+               _redirectURL: "",
+               _click: function() {},
+               _clickAssignedUser: function() {},
+               _success: function() {},
+               _failure: function() {},
+               _assignUser: function() {}
+       });
+       
+       WCF.User.Panel.Moderation = WCF.User.Panel.Abstract.extend({
+               init: function() {},
+               _initDropdown: function() {},
+               _load: function() {},
+               _markAsRead: function() {},
+               _markAllAsRead: function() {},
+               resetItems: function() {},
+               _badge: {},
+               _dropdown: {},
+               _identifier: "",
+               _loadData: true,
+               _markAllAsReadLink: {},
+               _options: {},
+               _proxy: {},
+               _triggerElement: {},
+               toggle: function() {},
+               _dblClick: function() {},
+               _success: function() {},
+               updateBadge: function() {}
+       });
+}
index cfe32c0f3c2c48901a2d2fd3e8829bdcb511b1da..ba2a80b0fe0e689e2d868ad7391b9bfb407c81f9 100644 (file)
  */
 WCF.Poll = { };
 
-/**
- * Handles poll option management.
- * 
- * @param      string          containerID
- * @param      array<object>   optionList
- */
-WCF.Poll.Management = Class.extend({
-       /**
-        * container object
-        * @var jQuery
-        */
-       _container: null,
-       
-       /**
-        * number of options
-        * @var int
-        */
-       _count: 0,
-       
-       /**
-        * editor element id
-        * @var string
-        */
-       _editorId: '',
-       
-       /**
-        * maximum allowed number of options
-        * @var int
-        */
-       _maxOptions: 0,
-       
+if (COMPILER_TARGET_DEFAULT) {
        /**
-        * Initializes the WCF.Poll.Management class.
-        * 
-        * @param       {string}        containerID
-        * @param       {Object[]}      optionList
-        * @param       {int}           maxOptions
-        * @param       {string}        editorId
+        * Handles poll option management.
+        *
+        * @param        string                containerID
+        * @param        array<object>        optionList
         */
-       init: function(containerID, optionList, maxOptions, editorId) {
-               this._count = 0;
-               this._maxOptions = maxOptions || -1;
-               this._container = $('#' + containerID).children('ol:eq(0)');
-               if (!this._container.length) {
-                       console.debug("[WCF.Poll.Management] Invalid container id given, aborting.");
-                       return;
-               }
-               
-               optionList = optionList || [];
-               this._createOptionList(optionList);
-               
-               // bind event listener
-               if (editorId) {
-                       this._editorId = editorId;
+       WCF.Poll.Management = Class.extend({
+               /**
+                * container object
+                * @var        jQuery
+                */
+               _container: null,
+               
+               /**
+                * number of options
+                * @var        int
+                */
+               _count: 0,
+               
+               /**
+                * editor element id
+                * @var string
+                */
+               _editorId: '',
+               
+               /**
+                * maximum allowed number of options
+                * @var        int
+                */
+               _maxOptions: 0,
+               
+               /**
+                * Initializes the WCF.Poll.Management class.
+                *
+                * @param       {string}        containerID
+                * @param       {Object[]}      optionList
+                * @param       {int}           maxOptions
+                * @param       {string}        editorId
+                */
+               init: function (containerID, optionList, maxOptions, editorId) {
+                       this._count = 0;
+                       this._maxOptions = maxOptions || -1;
+                       this._container = $('#' + containerID).children('ol:eq(0)');
+                       if (!this._container.length) {
+                               console.debug("[WCF.Poll.Management] Invalid container id given, aborting.");
+                               return;
+                       }
                        
-                       WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'reset_' + editorId, this._reset.bind(this));
-                       WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'submit_' + editorId, this._submit.bind(this));
-                       WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'validate_' + editorId, this._validate.bind(this));
-               }
-               else {
-                       this._container.closest('form').submit($.proxy(this._submit, this));
-               }
-               
-               // init sorting
-               require(['WoltLabSuite/Core/Ui/Sortable/List'], function (UiSortableList) {
-                       new UiSortableList({
-                               containerId: containerID,
-                               options: {
-                                       toleranceElement: '> div'
-                               }
+                       optionList = optionList || [];
+                       this._createOptionList(optionList);
+                       
+                       // bind event listener
+                       if (editorId) {
+                               this._editorId = editorId;
+                               
+                               WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'reset_' + editorId, this._reset.bind(this));
+                               WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'submit_' + editorId, this._submit.bind(this));
+                               WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'validate_' + editorId, this._validate.bind(this));
+                       }
+                       else {
+                               this._container.closest('form').submit($.proxy(this._submit, this));
+                       }
+                       
+                       // init sorting
+                       require(['WoltLabSuite/Core/Ui/Sortable/List'], function (UiSortableList) {
+                               new UiSortableList({
+                                       containerId: containerID,
+                                       options: {
+                                               toleranceElement: '> div'
+                                       }
+                               });
                        });
-               });
-       },
-       
-       /**
-        * Creates the option list on init.
-        * 
-        * @param       array<object>           optionList
-        */
-       _createOptionList: function(optionList) {
-               for (var $i = 0, $length = optionList.length; $i < $length; $i++) {
-                       var $option = optionList[$i];
-                       this._createOption($option.optionValue, $option.optionID);
-               }
-               
-               // add empty option
-               this._createOption();
-       },
-       
-       /**
-        * Creates a new option element.
-        * 
-        * @param       string          optionValue
-        * @param       integer         optionID
-        * @param       jQuery          insertAfter
-        */
-       _createOption: function(optionValue, optionID, insertAfter) {
-               optionValue = optionValue || '';
-               optionID = parseInt(optionID) || 0;
-               insertAfter = insertAfter || null;
-               
-               var $listItem = $('<li class="sortableNode" />').data('optionID', optionID);
-               if (insertAfter === null) {
-                       $listItem.appendTo(this._container);
-               }
-               else {
-                       $listItem.insertAfter(insertAfter);
-               }
-               
-               // insert buttons
-               var $container = $('<div class="pollOptionInput" />').appendTo($listItem);
-               $('<span class="icon icon16 fa-arrows sortableNodeHandle" />').appendTo($container);
-               $('<span class="icon icon16 fa-plus jsTooltip jsAddOption pointer" title="' + WCF.Language.get('wcf.poll.button.addOption') + '" />').click($.proxy(this._addOption, this)).appendTo($container);
-               $('<span class="icon icon16 fa-times jsTooltip jsDeleteOption pointer" title="' + WCF.Language.get('wcf.poll.button.removeOption') + '" />').click($.proxy(this._removeOption, this)).appendTo($container);
-               
-               // insert input field
-               var $input = $('<input type="text" value="' + optionValue + '" maxlength="255" />').keydown($.proxy(this._keyDown, this)).appendTo($container);
-               $input.click(function() {
-                       // work-around for some weird focus issue on iOS/Android
-                       if (document.activeElement !== this) {
-                               this.focus();
+               },
+               
+               /**
+                * Creates the option list on init.
+                *
+                * @param        array<object>                optionList
+                */
+               _createOptionList: function (optionList) {
+                       for (var $i = 0, $length = optionList.length; $i < $length; $i++) {
+                               var $option = optionList[$i];
+                               this._createOption($option.optionValue, $option.optionID);
                        }
-               });
-               
-               if (insertAfter !== null) {
-                       $input.focus();
-               }
-               
-               WCF.DOMNodeInsertedHandler.execute();
-               
-               this._count++;
-               if (this._count === this._maxOptions) {
-                       this._container.find('span.jsAddOption').removeClass('pointer').addClass('disabled');
-               }
-       },
-       
-       /**
-        * Handles key down events for option input field.
-        * 
-        * @param       object          event
-        */
-       _keyDown: function(event) {
-               // ignore every key except for [Enter]
-               if (event.which !== 13) {
-                       return;
-               }
-               
-               $(event.currentTarget).parent().children('.jsAddOption').trigger('click');
-               
-               event.preventDefault();
-       },
-       
-       /**
-        * Adds a new option after current one.
-        * 
-        * @param       object          event
-        */
-       _addOption: function(event) {
-               if (this._count === this._maxOptions) {
-                       return false;
-               }
-               
-               var $listItem = $(event.currentTarget).closest('li', this._container[0]);
-               
-               this._createOption(undefined, undefined, $listItem);
-       },
-       
-       /**
-        * Removes an option.
-        * 
-        * @param       object          event
-        */
-       _removeOption: function(event) {
-               $(event.currentTarget).closest('li', this._container[0]).remove();
-               
-               this._count--;
-               this._container.find('span.jsAddOption').addClass('pointer').removeClass('disabled');
-               
-               if (this._container.children('li').length == 0) {
+                       
+                       // add empty option
                        this._createOption();
-               }
-       },
-       
-       /**
-        * Inserts hidden input elements storing the option values.
-        * 
-        * @param       {(Event|Object)}        event
-        */
-       _submit: function(event) {
-               var $options = [];
-               this._container.children('li').each(function (index, listItem) {
-                       var $listItem = $(listItem);
-                       var $optionValue = $.trim($listItem.find('input').val());
+               },
+               
+               /**
+                * Creates a new option element.
+                *
+                * @param        string                optionValue
+                * @param        integer                optionID
+                * @param        jQuery                insertAfter
+                */
+               _createOption: function (optionValue, optionID, insertAfter) {
+                       optionValue = optionValue || '';
+                       optionID = parseInt(optionID) || 0;
+                       insertAfter = insertAfter || null;
                        
-                       // ignore empty values
-                       if ($optionValue != '') {
-                               $options.push($listItem.data('optionID') + '_' + $optionValue);
+                       var $listItem = $('<li class="sortableNode" />').data('optionID', optionID);
+                       if (insertAfter === null) {
+                               $listItem.appendTo(this._container);
                        }
-               });
-               
-               if (typeof event.originalEvent === 'object' && event.originalEvent instanceof Event) {
-                       // create hidden input fields
-                       if ($options.length) {
-                               var $formSubmit = this._container.parents('form').find('.formSubmit');
+                       else {
+                               $listItem.insertAfter(insertAfter);
+                       }
+                       
+                       // insert buttons
+                       var $container = $('<div class="pollOptionInput" />').appendTo($listItem);
+                       $('<span class="icon icon16 fa-arrows sortableNodeHandle" />').appendTo($container);
+                       $('<span class="icon icon16 fa-plus jsTooltip jsAddOption pointer" title="' + WCF.Language.get('wcf.poll.button.addOption') + '" />').click($.proxy(this._addOption, this)).appendTo($container);
+                       $('<span class="icon icon16 fa-times jsTooltip jsDeleteOption pointer" title="' + WCF.Language.get('wcf.poll.button.removeOption') + '" />').click($.proxy(this._removeOption, this)).appendTo($container);
+                       
+                       // insert input field
+                       var $input = $('<input type="text" value="' + optionValue + '" maxlength="255" />').keydown($.proxy(this._keyDown, this)).appendTo($container);
+                       $input.click(function () {
+                               // work-around for some weird focus issue on iOS/Android
+                               if (document.activeElement !== this) {
+                                       this.focus();
+                               }
+                       });
+                       
+                       if (insertAfter !== null) {
+                               $input.focus();
+                       }
+                       
+                       WCF.DOMNodeInsertedHandler.execute();
+                       
+                       this._count++;
+                       if (this._count === this._maxOptions) {
+                               this._container.find('span.jsAddOption').removeClass('pointer').addClass('disabled');
+                       }
+               },
+               
+               /**
+                * Handles key down events for option input field.
+                *
+                * @param        object                event
+                */
+               _keyDown: function (event) {
+                       // ignore every key except for [Enter]
+                       if (event.which !== 13) {
+                               return;
+                       }
+                       
+                       $(event.currentTarget).parent().children('.jsAddOption').trigger('click');
+                       
+                       event.preventDefault();
+               },
+               
+               /**
+                * Adds a new option after current one.
+                *
+                * @param        object                event
+                */
+               _addOption: function (event) {
+                       if (this._count === this._maxOptions) {
+                               return false;
+                       }
+                       
+                       var $listItem = $(event.currentTarget).closest('li', this._container[0]);
+                       
+                       this._createOption(undefined, undefined, $listItem);
+               },
+               
+               /**
+                * Removes an option.
+                *
+                * @param        object                event
+                */
+               _removeOption: function (event) {
+                       $(event.currentTarget).closest('li', this._container[0]).remove();
+                       
+                       this._count--;
+                       this._container.find('span.jsAddOption').addClass('pointer').removeClass('disabled');
+                       
+                       if (this._container.children('li').length == 0) {
+                               this._createOption();
+                       }
+               },
+               
+               /**
+                * Inserts hidden input elements storing the option values.
+                *
+                * @param       {(Event|Object)}        event
+                */
+               _submit: function (event) {
+                       var $options = [];
+                       this._container.children('li').each(function (index, listItem) {
+                               var $listItem = $(listItem);
+                               var $optionValue = $.trim($listItem.find('input').val());
                                
-                               for (var $i = 0, $length = $options.length; $i < $length; $i++) {
-                                       $('<input type="hidden" name="pollOptions[' + $i + ']">').val($options[$i]).appendTo($formSubmit);
+                               // ignore empty values
+                               if ($optionValue != '') {
+                                       $options.push($listItem.data('optionID') + '_' + $optionValue);
+                               }
+                       });
+                       
+                       if (typeof event.originalEvent === 'object' && event.originalEvent instanceof Event) {
+                               // create hidden input fields
+                               if ($options.length) {
+                                       var $formSubmit = this._container.parents('form').find('.formSubmit');
+                                       
+                                       for (var $i = 0, $length = $options.length; $i < $length; $i++) {
+                                               $('<input type="hidden" name="pollOptions[' + $i + ']">').val($options[$i]).appendTo($formSubmit);
+                                       }
                                }
                        }
-               }
-               else {
-                       event.poll = { pollOptions: $options };
+                       else {
+                               event.poll = {pollOptions: $options};
+                               
+                               // get form input fields
+                               var parentContainer = this._container.parents('.messageTabMenuContent:eq(0)');
+                               parentContainer.find('input').each(function (index, input) {
+                                       if (input.name) {
+                                               if (input.type !== 'checkbox' || input.checked) {
+                                                       event.poll[input.name] = input.value;
+                                               }
+                                       }
+                               });
+                       }
+               },
+               
+               /**
+                * Resets the poll option form.
+                *
+                * @private
+                */
+               _reset: function () {
+                       // reset options
+                       /** @type Element */
+                       var container = this._container[0];
+                       while (container.childElementCount > 1) {
+                               container.removeChild(container.children[1]);
+                       }
                        
-                       // get form input fields
+                       elBySel('input', container.children[0]).value = '';
+                       
+                       // reset input fields and checkboxes
                        var parentContainer = this._container.parents('.messageTabMenuContent:eq(0)');
-                       parentContainer.find('input').each(function(index, input) {
+                       parentContainer.find('input').each(function (index, input) {
                                if (input.name) {
-                                       if (input.type !== 'checkbox' || input.checked) {
-                                               event.poll[input.name] = input.value;
+                                       if (input.type === 'checkbox') {
+                                               input.checked = false;
+                                       }
+                                       else if (input.type === 'text') {
+                                               input.value = '';
+                                       }
+                                       else if (input.type === 'number') {
+                                               input.value = input.min;
                                        }
                                }
                        });
-               }
-       },
-       
-       /**
-        * Resets the poll option form.
-        * 
-        * @private
-        */
-       _reset: function() {
-               // reset options
-               /** @type Element */
-               var container = this._container[0];
-               while (container.childElementCount > 1) {
-                       container.removeChild(container.children[1]);
-               }
-               
-               elBySel('input', container.children[0]).value = '';
-               
-               // reset input fields and checkboxes
-               var parentContainer = this._container.parents('.messageTabMenuContent:eq(0)');
-               parentContainer.find('input').each(function(index, input) {
-                       if (input.name) {
-                               if (input.type === 'checkbox') {
-                                       input.checked = false;
-                               }
-                               else if (input.type === 'text') {
-                                       input.value = '';
-                               }
-                               else if (input.type === 'number') {
-                                       input.value = input.min;
-                               }
-                       }
-               });
-               
-               // reset date picker
-               require(['WoltLabSuite/Core/Date/Picker'], (function(UiDatePicker) {
-                       UiDatePicker.clear('pollEndTime_' + this._editorId);
-               }).bind(this));
-       },
-       
-       _validate: function(data) {
-               var question = elById('pollQuestion_' + this._editorId);
-               if (question.value.trim() === '') {
-                       // no question provided, ignore
-                       return;
-               }
-               
-               // get options
-               var count = 0;
-               elBySelAll('li input[type="text"]', this._container[0], function(input) {
-                       if (input.value.trim() !== '') {
-                               count++;
+                       
+                       // reset date picker
+                       require(['WoltLabSuite/Core/Date/Picker'], (function (UiDatePicker) {
+                               UiDatePicker.clear('pollEndTime_' + this._editorId);
+                       }).bind(this));
+               },
+               
+               _validate: function (data) {
+                       var question = elById('pollQuestion_' + this._editorId);
+                       if (question.value.trim() === '') {
+                               // no question provided, ignore
+                               return;
                        }
-               });
-               
-               if (count === 0) {
-                       data.api.throwError(this._container[0], WCF.Language.get('wcf.global.form.error.empty'));
-                       data.valid = false;
-               }
-               else {
-                       var pollMaxVotes = elById('pollMaxVotes_' + this._editorId);
-                       var num = ~~pollMaxVotes.value;
-                       if (num && num > count) {
-                               data.api.throwError(pollMaxVotes, WCF.Language.get('wcf.poll.maxVotes.error.invalid'));
+                       
+                       // get options
+                       var count = 0;
+                       elBySelAll('li input[type="text"]', this._container[0], function (input) {
+                               if (input.value.trim() !== '') {
+                                       count++;
+                               }
+                       });
+                       
+                       if (count === 0) {
+                               data.api.throwError(this._container[0], WCF.Language.get('wcf.global.form.error.empty'));
                                data.valid = false;
                        }
+                       else {
+                               var pollMaxVotes = elById('pollMaxVotes_' + this._editorId);
+                               var num = ~~pollMaxVotes.value;
+                               if (num && num > count) {
+                                       data.api.throwError(pollMaxVotes, WCF.Language.get('wcf.poll.maxVotes.error.invalid'));
+                                       data.valid = false;
+                               }
+                       }
                }
-       }
-});
+       });
+}
+else {
+       WCF.Poll.Management = Class.extend({
+               _container: {},
+               _count: 0,
+               _editorId: "",
+               _maxOptions: 0,
+               init: function() {},
+               _createOptionList: function() {},
+               _createOption: function() {},
+               _keyDown: function() {},
+               _addOption: function() {},
+               _removeOption: function() {},
+               _submit: function() {},
+               _reset: function() {},
+               _validate: function() {}
+       });
+}
 
 /**
  * Manages poll voting and result display.
index 4bd95cbfc7b1d99a51e22f078bda0150324ea62c..06ed74388485c9735b47a706340d7b009167232c 100644 (file)
@@ -107,469 +107,537 @@ WCF.User.Login = Class.extend({
  */
 WCF.User.Panel = { };
 
-/**
- * Abstract implementation for user panel items providing an interactive dropdown.
- * 
- * @param      jQuery          triggerElement
- * @param      string          identifier
- * @param      object          options
- */
-WCF.User.Panel.Abstract = Class.extend({
-       /**
-        * counter badge
-        * @var jQuery
-        */
-       _badge: null,
-       
-       /**
-        * interactive dropdown instance
-        * @var WCF.Dropdown.Interactive.Instance
-        */
-       _dropdown: null,
-       
-       /**
-        * dropdown identifier
-        * @var string
-        */
-       _identifier: '',
-       
-       /**
-        * true if data should be loaded using an AJAX request
-        * @var boolean
-        */
-       _loadData: true,
-       
-       /**
-        * header icon to mark all items belonging to this user panel item as read
-        * @var jQuery
-        */
-       _markAllAsReadLink: null,
-       
-       /**
-        * list of options required to dropdown initialization and UI features
-        * @var object
-        */
-       _options: { },
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * trigger element
-        * @var jQuery
-        */
-       _triggerElement: null,
-       
-       /**
-        * Initializes the WCF.User.Panel.Abstract class.
-        * 
-        * @param       jQuery          triggerElement
-        * @param       string          identifier
-        * @param       object          options
-        */
-       init: function(triggerElement, identifier, options) {
-               this._dropdown = null;
-               this._loadData = true;
-               this._identifier = identifier;
-               this._triggerElement = triggerElement;
-               this._options = options;
-               
-               this._proxy = new WCF.Action.Proxy({
-                       showLoadingOverlay: false,
-                       success: $.proxy(this._success, this)
-               });
-               
-               this._triggerElement.click($.proxy(this.toggle, this));
-               
-               if (this._options.showAllLink) {
-                       this._triggerElement.dblclick($.proxy(this._dblClick, this));
-               }
-               
-               if (this._options.staticDropdown === true) {
-                       this._loadData = false;
-               }
-               else {
-                       var $badge = this._triggerElement.find('span.badge');
-                       if ($badge.length) {
-                               this._badge = $badge;
+if (COMPILER_TARGET_DEFAULT) {
+       /**
+        * Abstract implementation for user panel items providing an interactive dropdown.
+        *
+        * @param        jQuery                triggerElement
+        * @param        string                identifier
+        * @param        object                options
+        */
+       WCF.User.Panel.Abstract = Class.extend({
+               /**
+                * counter badge
+                * @var        jQuery
+                */
+               _badge: null,
+               
+               /**
+                * interactive dropdown instance
+                * @var        WCF.Dropdown.Interactive.Instance
+                */
+               _dropdown: null,
+               
+               /**
+                * dropdown identifier
+                * @var        string
+                */
+               _identifier: '',
+               
+               /**
+                * true if data should be loaded using an AJAX request
+                * @var        boolean
+                */
+               _loadData: true,
+               
+               /**
+                * header icon to mark all items belonging to this user panel item as read
+                * @var        jQuery
+                */
+               _markAllAsReadLink: null,
+               
+               /**
+                * list of options required to dropdown initialization and UI features
+                * @var        object
+                */
+               _options: {},
+               
+               /**
+                * action proxy
+                * @var        WCF.Action.Proxy
+                */
+               _proxy: null,
+               
+               /**
+                * trigger element
+                * @var        jQuery
+                */
+               _triggerElement: null,
+               
+               /**
+                * Initializes the WCF.User.Panel.Abstract class.
+                *
+                * @param        jQuery                triggerElement
+                * @param        string                identifier
+                * @param        object                options
+                */
+               init: function (triggerElement, identifier, options) {
+                       this._dropdown = null;
+                       this._loadData = true;
+                       this._identifier = identifier;
+                       this._triggerElement = triggerElement;
+                       this._options = options;
+                       
+                       this._proxy = new WCF.Action.Proxy({
+                               showLoadingOverlay: false,
+                               success: $.proxy(this._success, this)
+                       });
+                       
+                       this._triggerElement.click($.proxy(this.toggle, this));
+                       
+                       if (this._options.showAllLink) {
+                               this._triggerElement.dblclick($.proxy(this._dblClick, this));
                        }
-               }
-       },
-       
-       /**
-        * Toggles the interactive dropdown.
-        * 
-        * @param       {Event?}        event
-        * @return      {boolean}
-        */
-       toggle: function(event) {
-               if (event instanceof Event) {
-                       event.preventDefault();
-               }
-               
-               if (this._dropdown === null) {
-                       this._dropdown = this._initDropdown();
-               }
-               
-               if (this._dropdown.toggle()) {
-                       if (!this._loadData) {
-                               // check if there are outstanding items but there are no outstanding ones in the current list
-                               if (this._badge !== null) {
-                                       var $count = parseInt(this._badge.text()) || 0;
-                                       if ($count && !this._dropdown.getItemList().children('.interactiveDropdownItemOutstanding').length) {
-                                               this._loadData = true;
-                                       }
+                       
+                       if (this._options.staticDropdown === true) {
+                               this._loadData = false;
+                       }
+                       else {
+                               var $badge = this._triggerElement.find('span.badge');
+                               if ($badge.length) {
+                                       this._badge = $badge;
                                }
                        }
+               },
+               
+               /**
+                * Toggles the interactive dropdown.
+                *
+                * @param        {Event?}        event
+                * @return        {boolean}
+                */
+               toggle: function (event) {
+                       if (event instanceof Event) {
+                               event.preventDefault();
+                       }
                        
-                       if (this._loadData) {
-                               this._loadData = false;
-                               this._load();
+                       if (this._dropdown === null) {
+                               this._dropdown = this._initDropdown();
                        }
-               }
-               
-               return false;
-       },
-       
-       /**
-        * Forward to original URL by double clicking the trigger element.
-        * 
-        * @param       object          event
-        * @return      boolean
-        */
-       _dblClick: function(event) {
-               event.preventDefault();
-               
-               window.location = this._options.showAllLink;
-               
-               return false;
-       },
-       
-       /**
-        * Initializes the dropdown on first usage.
-        * 
-        * @return      WCF.Dropdown.Interactive.Instance
-        */
-       _initDropdown: function() {
-               var $dropdown = WCF.Dropdown.Interactive.Handler.create(this._triggerElement, this._identifier, this._options);
-               $('<li class="loading"><span class="icon icon24 fa-spinner" /> <span>' + WCF.Language.get('wcf.global.loading') + '</span></li>').appendTo($dropdown.getItemList());
-               
-               return $dropdown;
-       },
-       
-       /**
-        * Loads item list data via AJAX.
-        */
-       _load: function() {
-               // override this in your own implementation to fetch display data
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        */
-       _success: function(data) {
-               if (data.returnValues.template !== undefined) {
-                       var $itemList = this._dropdown.getItemList().empty();
-                       $(data.returnValues.template).appendTo($itemList);
                        
-                       if (!$itemList.children().length) {
-                               $('<li class="noItems">' + this._options.noItems + '</li>').appendTo($itemList);
+                       if (this._dropdown.toggle()) {
+                               if (!this._loadData) {
+                                       // check if there are outstanding items but there are no outstanding ones in the current list
+                                       if (this._badge !== null) {
+                                               var $count = parseInt(this._badge.text()) || 0;
+                                               if ($count && !this._dropdown.getItemList().children('.interactiveDropdownItemOutstanding').length) {
+                                                       this._loadData = true;
+                                               }
+                                       }
+                               }
+                               
+                               if (this._loadData) {
+                                       this._loadData = false;
+                                       this._load();
+                               }
                        }
                        
-                       if (this._options.enableMarkAsRead) {
-                               var $outstandingItems = this._dropdown.getItemList().children('.interactiveDropdownItemOutstanding');
-                               if (this._markAllAsReadLink === null && $outstandingItems.length && this._options.markAllAsReadConfirmMessage) {
-                                       var $button = this._markAllAsReadLink = $('<li class="interactiveDropdownItemMarkAllAsRead"><a href="#" title="' + WCF.Language.get('wcf.user.panel.markAllAsRead') + '" class="jsTooltip"><span class="icon icon24 fa-check" /></a></li>').appendTo(this._dropdown.getLinkList());
-                                       $button.click((function(event) {
-                                               this._dropdown.close();
-                                               
-                                               WCF.System.Confirmation.show(this._options.markAllAsReadConfirmMessage, (function(action) {
-                                                       if (action === 'confirm') {
-                                                               this._markAllAsRead();
-                                                       }
+                       return false;
+               },
+               
+               /**
+                * Forward to original URL by double clicking the trigger element.
+                *
+                * @param        object                event
+                * @return        boolean
+                */
+               _dblClick: function (event) {
+                       event.preventDefault();
+                       
+                       window.location = this._options.showAllLink;
+                       
+                       return false;
+               },
+               
+               /**
+                * Initializes the dropdown on first usage.
+                *
+                * @return        WCF.Dropdown.Interactive.Instance
+                */
+               _initDropdown: function () {
+                       var $dropdown = WCF.Dropdown.Interactive.Handler.create(this._triggerElement, this._identifier, this._options);
+                       $('<li class="loading"><span class="icon icon24 fa-spinner" /> <span>' + WCF.Language.get('wcf.global.loading') + '</span></li>').appendTo($dropdown.getItemList());
+                       
+                       return $dropdown;
+               },
+               
+               /**
+                * Loads item list data via AJAX.
+                */
+               _load: function () {
+                       // override this in your own implementation to fetch display data
+               },
+               
+               /**
+                * Handles successful AJAX requests.
+                *
+                * @param        object                data
+                */
+               _success: function (data) {
+                       if (data.returnValues.template !== undefined) {
+                               var $itemList = this._dropdown.getItemList().empty();
+                               $(data.returnValues.template).appendTo($itemList);
+                               
+                               if (!$itemList.children().length) {
+                                       $('<li class="noItems">' + this._options.noItems + '</li>').appendTo($itemList);
+                               }
+                               
+                               if (this._options.enableMarkAsRead) {
+                                       var $outstandingItems = this._dropdown.getItemList().children('.interactiveDropdownItemOutstanding');
+                                       if (this._markAllAsReadLink === null && $outstandingItems.length && this._options.markAllAsReadConfirmMessage) {
+                                               var $button = this._markAllAsReadLink = $('<li class="interactiveDropdownItemMarkAllAsRead"><a href="#" title="' + WCF.Language.get('wcf.user.panel.markAllAsRead') + '" class="jsTooltip"><span class="icon icon24 fa-check" /></a></li>').appendTo(this._dropdown.getLinkList());
+                                               $button.click((function (event) {
+                                                       this._dropdown.close();
+                                                       
+                                                       WCF.System.Confirmation.show(this._options.markAllAsReadConfirmMessage, (function (action) {
+                                                               if (action === 'confirm') {
+                                                                       this._markAllAsRead();
+                                                               }
+                                                       }).bind(this));
+                                                       
+                                                       return false;
                                                }).bind(this));
+                                       }
+                                       
+                                       $outstandingItems.each((function (index, item) {
+                                               var $item = $(item).addClass('interactiveDropdownItemOutstandingIcon');
+                                               var $objectID = $item.data('objectID');
                                                
-                                               return false;
+                                               var $button = $('<div class="interactiveDropdownItemMarkAsRead"><a href="#" title="' + WCF.Language.get('wcf.user.panel.markAsRead') + '" class="jsTooltip"><span class="icon icon16 fa-check" /></a></div>').appendTo($item);
+                                               $button.click((function (event) {
+                                                       this._markAsRead(event, $objectID);
+                                                       
+                                                       return false;
+                                               }).bind(this));
                                        }).bind(this));
                                }
                                
-                               $outstandingItems.each((function(index, item) {
-                                       var $item = $(item).addClass('interactiveDropdownItemOutstandingIcon');
-                                       var $objectID = $item.data('objectID');
+                               this._dropdown.getItemList().children().each(function (index, item) {
+                                       var $item = $(item);
+                                       var $link = $item.data('link');
                                        
-                                       var $button = $('<div class="interactiveDropdownItemMarkAsRead"><a href="#" title="' + WCF.Language.get('wcf.user.panel.markAsRead') + '" class="jsTooltip"><span class="icon icon16 fa-check" /></a></div>').appendTo($item);
-                                       $button.click((function(event) {
-                                               this._markAsRead(event, $objectID);
+                                       if ($link) {
+                                               if ($.browser.msie) {
+                                                       $item.click(function (event) {
+                                                               if (event.target.tagName !== 'A') {
+                                                                       window.location = $link;
+                                                                       
+                                                                       return false;
+                                                               }
+                                                       });
+                                               }
+                                               else {
+                                                       $item.addClass('interactiveDropdownItemShadow');
+                                                       $('<a href="' + $link + '" class="interactiveDropdownItemShadowLink" />').appendTo($item);
+                                               }
                                                
-                                               return false;
-                                       }).bind(this));
-                               }).bind(this));
+                                               if ($item.data('linkReplaceAll')) {
+                                                       $item.find('> .box48 a:not(.userLink)').prop('href', $link);
+                                               }
+                                       }
+                               });
+                               
+                               this._dropdown.rebuildScrollbar();
                        }
                        
-                       this._dropdown.getItemList().children().each(function(index, item) {
-                               var $item = $(item);
-                               var $link = $item.data('link');
-                               
-                               if ($link) {
-                                       if ($.browser.msie) {
-                                               $item.click(function(event) {
-                                                       if (event.target.tagName !== 'A') {
-                                                               window.location = $link;
-                                                               
-                                                               return false;
-                                                       }
-                                               });
-                                       }
-                                       else {
-                                               $item.addClass('interactiveDropdownItemShadow');
-                                               $('<a href="' + $link + '" class="interactiveDropdownItemShadowLink" />').appendTo($item);
-                                       }
-                                       
-                                       if ($item.data('linkReplaceAll')) {
-                                               $item.find('> .box48 a:not(.userLink)').prop('href', $link);
+                       if (data.returnValues.totalCount !== undefined) {
+                               this.updateBadge(data.returnValues.totalCount);
+                       }
+                       
+                       if (this._options.enableMarkAsRead) {
+                               if (data.returnValues.markAsRead) {
+                                       var $item = this._dropdown.getItemList().children('li[data-object-id=' + data.returnValues.markAsRead + ']');
+                                       if ($item.length) {
+                                               $item.removeClass('interactiveDropdownItemOutstanding').data('isRead', true);
+                                               $item.children('.interactiveDropdownItemMarkAsRead').remove();
                                        }
                                }
-                       });
+                               else if (data.returnValues.markAllAsRead) {
+                                       this.resetItems();
+                                       this.updateBadge(0);
+                               }
+                       }
+               },
+               
+               /**
+                * Marks an item as read.
+                *
+                * @param        object                event
+                * @param        integer                objectID
+                */
+               _markAsRead: function (event, objectID) {
+                       // override this in your own implementation to mark an item as read
+               },
+               
+               /**
+                * Marks all items as read.
+                */
+               _markAllAsRead: function () {
+                       // override this in your own implementation to mark all items as read
+               },
+               
+               /**
+                * Updates the badge's count or removes it if count reaches zero. Passing a negative number is undefined.
+                *
+                * @param        integer                count
+                */
+               updateBadge: function (count) {
+                       count = parseInt(count) || 0;
                        
-                       this._dropdown.rebuildScrollbar();
-               }
-               
-               if (data.returnValues.totalCount !== undefined) {
-                       this.updateBadge(data.returnValues.totalCount);
-               }
-               
-               if (this._options.enableMarkAsRead) {
-                       if (data.returnValues.markAsRead) {
-                               var $item = this._dropdown.getItemList().children('li[data-object-id=' + data.returnValues.markAsRead + ']');
-                               if ($item.length) {
-                                       $item.removeClass('interactiveDropdownItemOutstanding').data('isRead', true);
-                                       $item.children('.interactiveDropdownItemMarkAsRead').remove();
+                       if (count) {
+                               if (this._badge === null) {
+                                       this._badge = $('<span class="badge badgeUpdate" />').appendTo(this._triggerElement.children('a'));
+                                       this._badge.before(' ');
                                }
+                               
+                               this._badge.text(count);
                        }
-                       else if (data.returnValues.markAllAsRead) {
-                               this.resetItems();
-                               this.updateBadge(0);
+                       else if (this._badge !== null) {
+                               this._badge.remove();
+                               this._badge = null;
                        }
-               }
-       },
-       
-       /**
-        * Marks an item as read.
-        * 
-        * @param       object          event
-        * @param       integer         objectID
-        */
-       _markAsRead: function(event, objectID) {
-               // override this in your own implementation to mark an item as read
-       },
-       
-       /**
-        * Marks all items as read.
-        */
-       _markAllAsRead: function() {
-               // override this in your own implementation to mark all items as read
-       },
-       
-       /**
-        * Updates the badge's count or removes it if count reaches zero. Passing a negative number is undefined.
-        * 
-        * @param       integer         count
-        */
-       updateBadge: function(count) {
-               count = parseInt(count) || 0;
-               
-               if (count) {
-                       if (this._badge === null) {
-                               this._badge = $('<span class="badge badgeUpdate" />').appendTo(this._triggerElement.children('a'));
-                               this._badge.before(' ');
+                       
+                       if (this._options.enableMarkAsRead) {
+                               if (!count && this._markAllAsReadLink !== null) {
+                                       this._markAllAsReadLink.remove();
+                                       this._markAllAsReadLink = null;
+                               }
                        }
                        
-                       this._badge.text(count);
-               }
-               else if (this._badge !== null) {
-                       this._badge.remove();
-                       this._badge = null;
-               }
-               
-               if (this._options.enableMarkAsRead) {
-                       if (!count && this._markAllAsReadLink !== null) {
-                               this._markAllAsReadLink.remove();
-                               this._markAllAsReadLink = null;
+                       WCF.System.Event.fireEvent('com.woltlab.wcf.userMenu', 'updateBadge', {
+                               count: count,
+                               identifier: this._identifier
+                       });
+               },
+               
+               /**
+                * Resets the dropdown's inner item list.
+                */
+               resetItems: function () {
+                       // this method could be called from outside, but the dropdown was never
+                       // toggled and thus never initialized
+                       if (this._dropdown !== null) {
+                               this._dropdown.resetItems();
                        }
                }
-               
-               WCF.System.Event.fireEvent('com.woltlab.wcf.userMenu', 'updateBadge', {
-                       count: count,
-                       identifier: this._identifier
-               });
-       },
-       
-       /**
-        * Resets the dropdown's inner item list.
-        */
-       resetItems: function() {
-               // this method could be called from outside, but the dropdown was never
-               // toggled and thus never initialized
-               if (this._dropdown !== null) {
-                       this._dropdown.resetItems();
-               }
-       }
-});
-
-/**
- * User Panel implementation for user notifications.
- * 
- * @see        WCF.User.Panel.Abstract
- */
-WCF.User.Panel.Notification = WCF.User.Panel.Abstract.extend({
-       /**
-        * favico instance
-        * @var Favico
-        */
-       _favico: null,
+       });
        
        /**
-        * @see WCF.User.Panel.Abstract.init()
+        * User Panel implementation for user notifications.
+        *
+        * @see        WCF.User.Panel.Abstract
         */
-       init: function(options) {
-               options.enableMarkAsRead = true;
+       WCF.User.Panel.Notification = WCF.User.Panel.Abstract.extend({
+               /**
+                * favico instance
+                * @var        Favico
+                */
+               _favico: null,
                
-               this._super($('#userNotifications'), 'userNotifications', options);
-               
-               try {
-                       this._favico = new Favico({
-                               animation: 'none',
-                               type: 'circle'
-                       });
+               /**
+                * @see        WCF.User.Panel.Abstract.init()
+                */
+               init: function (options) {
+                       options.enableMarkAsRead = true;
                        
-                       if (this._badge !== null) {
-                               var $count = parseInt(this._badge.text()) || 0;
-                               this._favico.badge($count);
-                       }
-               }
-               catch (e) {
-                       console.debug("[WCF.User.Panel.Notification] Failed to initialized Favico: " + e.message);
-               }
-               
-               WCF.System.PushNotification.addCallback('userNotificationCount', $.proxy(this.updateUserNotificationCount, this));
-               
-               require(['EventHandler'], (function(EventHandler) {
-                       EventHandler.add('com.woltlab.wcf.UserMenuMobile', 'more', (function(data) {
-                               if (data.identifier === 'com.woltlab.wcf.notifications') {
-                                       this.toggle();
+                       this._super($('#userNotifications'), 'userNotifications', options);
+                       
+                       try {
+                               this._favico = new Favico({
+                                       animation: 'none',
+                                       type: 'circle'
+                               });
+                               
+                               if (this._badge !== null) {
+                                       var $count = parseInt(this._badge.text()) || 0;
+                                       this._favico.badge($count);
                                }
+                       }
+                       catch (e) {
+                               console.debug("[WCF.User.Panel.Notification] Failed to initialized Favico: " + e.message);
+                       }
+                       
+                       WCF.System.PushNotification.addCallback('userNotificationCount', $.proxy(this.updateUserNotificationCount, this));
+                       
+                       require(['EventHandler'], (function (EventHandler) {
+                               EventHandler.add('com.woltlab.wcf.UserMenuMobile', 'more', (function (data) {
+                                       if (data.identifier === 'com.woltlab.wcf.notifications') {
+                                               this.toggle();
+                                       }
+                               }).bind(this));
                        }).bind(this));
-               }).bind(this));
-       },
-       
-       /**
-        * @see WCF.User.Panel.Abstract._initDropdown()
-        */
-       _initDropdown: function() {
-               var $dropdown = this._super();
+               },
                
-               $('<li><a href="' + this._options.settingsLink + '" title="' + WCF.Language.get('wcf.user.panel.settings') + '" class="jsTooltip"><span class="icon icon24 fa-cog" /></a></li>').appendTo($dropdown.getLinkList());
+               /**
+                * @see        WCF.User.Panel.Abstract._initDropdown()
+                */
+               _initDropdown: function () {
+                       var $dropdown = this._super();
+                       
+                       $('<li><a href="' + this._options.settingsLink + '" title="' + WCF.Language.get('wcf.user.panel.settings') + '" class="jsTooltip"><span class="icon icon24 fa-cog" /></a></li>').appendTo($dropdown.getLinkList());
+                       
+                       return $dropdown;
+               },
                
-               return $dropdown;
-       },
-       
-       /**
-        * @see WCF.User.Panel.Abstract._load()
-        */
-       _load: function() {
-               this._proxy.setOption('data', {
-                       actionName: 'getOutstandingNotifications',
-                       className: 'wcf\\data\\user\\notification\\UserNotificationAction'
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * @see WCF.User.Panel.Abstract._markAsRead()
-        */
-       _markAsRead: function(event, objectID) {
-               this._proxy.setOption('data', {
-                       actionName: 'markAsConfirmed',
-                       className: 'wcf\\data\\user\\notification\\UserNotificationAction',
-                       objectIDs: [ objectID ]
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * @see WCF.User.Panel.Abstract._markAllAsRead()
-        */
-       _markAllAsRead: function(event) {
-               this._proxy.setOption('data', {
-                       actionName: 'markAllAsConfirmed',
-                       className: 'wcf\\data\\user\\notification\\UserNotificationAction'
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * @see WCF.User.Panel.Abstract.resetItems()
-        */
-       resetItems: function() {
-               this._super();
+               /**
+                * @see        WCF.User.Panel.Abstract._load()
+                */
+               _load: function () {
+                       this._proxy.setOption('data', {
+                               actionName: 'getOutstandingNotifications',
+                               className: 'wcf\\data\\user\\notification\\UserNotificationAction'
+                       });
+                       this._proxy.sendRequest();
+               },
                
-               if (this._markAllAsReadLink) {
-                       this._markAllAsReadLink.remove();
-                       this._markAllAsReadLink = null;
-               }
-       },
-       
-       /**
-        * @see WCF.User.Panel.Abstract.updateBadge()
-        */
-       updateBadge: function(count) {
-               count = parseInt(count) || 0;
+               /**
+                * @see        WCF.User.Panel.Abstract._markAsRead()
+                */
+               _markAsRead: function (event, objectID) {
+                       this._proxy.setOption('data', {
+                               actionName: 'markAsConfirmed',
+                               className: 'wcf\\data\\user\\notification\\UserNotificationAction',
+                               objectIDs: [objectID]
+                       });
+                       this._proxy.sendRequest();
+               },
                
-               // update data attribute
-               $('#userNotifications').attr('data-count', count);
+               /**
+                * @see        WCF.User.Panel.Abstract._markAllAsRead()
+                */
+               _markAllAsRead: function (event) {
+                       this._proxy.setOption('data', {
+                               actionName: 'markAllAsConfirmed',
+                               className: 'wcf\\data\\user\\notification\\UserNotificationAction'
+                       });
+                       this._proxy.sendRequest();
+               },
                
-               if (this._favico !== null) {
-                       this._favico.badge(count);
-               }
+               /**
+                * @see        WCF.User.Panel.Abstract.resetItems()
+                */
+               resetItems: function () {
+                       this._super();
+                       
+                       if (this._markAllAsReadLink) {
+                               this._markAllAsReadLink.remove();
+                               this._markAllAsReadLink = null;
+                       }
+               },
                
-               this._super(count);
-       },
+               /**
+                * @see        WCF.User.Panel.Abstract.updateBadge()
+                */
+               updateBadge: function (count) {
+                       count = parseInt(count) || 0;
+                       
+                       // update data attribute
+                       $('#userNotifications').attr('data-count', count);
+                       
+                       if (this._favico !== null) {
+                               this._favico.badge(count);
+                       }
+                       
+                       this._super(count);
+               },
+               
+               /**
+                * Updates the badge counter and resets the dropdown's item list.
+                *
+                * @param        integer                count
+                */
+               updateUserNotificationCount: function (count) {
+                       if (this._dropdown !== null) {
+                               this._dropdown.resetItems();
+                       }
+                       
+                       this.updateBadge(count);
+               }
+       });
        
        /**
-        * Updates the badge counter and resets the dropdown's item list.
-        * 
-        * @param       integer         count
+        * User Panel implementation for user menu dropdown.
+        *
+        * @see        WCF.User.Panel.Abstract
         */
-       updateUserNotificationCount: function(count) {
-               if (this._dropdown !== null) {
-                       this._dropdown.resetItems();
+       WCF.User.Panel.UserMenu = WCF.User.Panel.Abstract.extend({
+               /**
+                * @see        WCF.User.Panel.Abstract.init()
+                */
+               init: function () {
+                       this._super($('#userMenu'), 'userMenu', {
+                               pointerOffset: '13px',
+                               staticDropdown: true
+                       });
                }
-               
-               this.updateBadge(count);
-       }
-});
-
-/**
- * User Panel implementation for user menu dropdown.
- * 
- * @see        WCF.User.Panel.Abstract
- */
-WCF.User.Panel.UserMenu = WCF.User.Panel.Abstract.extend({
-       /**
-        * @see WCF.User.Panel.Abstract.init()
-        */
-       init: function() {
-               this._super($('#userMenu'), 'userMenu', {
-                       pointerOffset: '13px',
-                       staticDropdown: true
-               });
-       }
-});
+       });
+}
+else {
+       WCF.User.Panel.Abstract = Class.extend({
+               _badge: {},
+               _dropdown: {},
+               _identifier: "",
+               _loadData: true,
+               _markAllAsReadLink: {},
+               _options: {},
+               _proxy: {},
+               _triggerElement: {},
+               init: function() {},
+               toggle: function() {},
+               _dblClick: function() {},
+               _initDropdown: function() {},
+               _load: function() {},
+               _success: function() {},
+               _markAsRead: function() {},
+               _markAllAsRead: function() {},
+               updateBadge: function() {},
+               resetItems: function() {}
+       });
+       
+       WCF.User.Panel.Notification = WCF.User.Panel.Abstract.extend({
+               _favico: {},
+               init: function() {},
+               _initDropdown: function() {},
+               _load: function() {},
+               _markAsRead: function() {},
+               _markAllAsRead: function() {},
+               resetItems: function() {},
+               updateBadge: function() {},
+               updateUserNotificationCount: function() {},
+               _badge: {},
+               _dropdown: {},
+               _identifier: "",
+               _loadData: true,
+               _markAllAsReadLink: {},
+               _options: {},
+               _proxy: {},
+               _triggerElement: {},
+               toggle: function() {},
+               _dblClick: function() {},
+               _success: function() {}
+       });
+       
+       WCF.User.Panel.UserMenu = WCF.User.Panel.Abstract.extend({
+               init: function() {},
+               _badge: {},
+               _dropdown: {},
+               _identifier: "",
+               _loadData: true,
+               _markAllAsReadLink: {},
+               _options: {},
+               _proxy: {},
+               _triggerElement: {},
+               toggle: function() {},
+               _dblClick: function() {},
+               _initDropdown: function() {},
+               _load: function() {},
+               _success: function() {},
+               _markAsRead: function() {},
+               _markAllAsRead: function() {},
+               updateBadge: function() {},
+               resetItems: function() {}
+       });
+}
 
 /**
  * Quick login box
@@ -884,255 +952,276 @@ WCF.User.Profile.TabMenu = Class.extend({
        }
 });
 
-/**
- * User profile inline editor.
- * 
- * @param      integer         userID
- * @param      boolean         editOnInit
- */
-WCF.User.Profile.Editor = Class.extend({
-       /**
-        * current action
-        * @var string
-        */
-       _actionName: '',
-       
-       _active: false,
-       
-       /**
-        * list of interface buttons
-        * @var object
-        */
-       _buttons: { },
-       
-       /**
-        * cached tab content
-        * @var string
-        */
-       _cachedTemplate: '',
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * tab object
-        * @var jQuery
-        */
-       _tab: null,
-       
-       /**
-        * target user id
-        * @var integer
-        */
-       _userID: 0,
-       
-       /**
-        * Initializes the WCF.User.Profile.Editor object.
-        * 
-        * @param       integer         userID
-        * @param       boolean         editOnInit
-        */
-       init: function(userID, editOnInit) {
-               this._actionName = '';
-               this._active = false;
-               this._cachedTemplate = '';
-               this._tab = $('#about');
-               this._userID = userID;
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               this._initButtons();
-               
-               // begin editing on page load
-               if (editOnInit) {
-                       this._beginEdit();
-               }
-       },
-       
-       /**
-        * Initializes interface buttons.
-        */
-       _initButtons: function() {
-               // create buttons
-               this._buttons = {
-                       beginEdit: $('.jsButtonEditProfile:eq(0)').click(this._beginEdit.bind(this))
-               };
-       },
-       
-       /**
-        * Begins editing.
-        * 
-        * @param       {Event?}         event   event object
-        */
-       _beginEdit: function(event) {
-               if (event) event.preventDefault();
-               
-               if (this._active) return;
-               this._active = true;
-               
-               this._actionName = 'beginEdit';
-               this._buttons.beginEdit.parent().addClass('active');
-               $('#profileContent').wcfTabs('select', 'about');
-               
-               // load form
-               this._proxy.setOption('data', {
-                       actionName: 'beginEdit',
-                       className: 'wcf\\data\\user\\UserProfileAction',
-                       objectIDs: [ this._userID ]
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Saves input values.
-        */
-       _save: function() {
-               this._actionName = 'save';
-               
-               // collect values
-               var $regExp = /values\[([a-zA-Z0-9._-]+)\]/;
-               var $values = { };
-               this._tab.find('input, textarea, select').each(function(index, element) {
-                       var $element = $(element);
-                       var $value = null;
+if (COMPILER_TARGET_DEFAULT) {
+       /**
+        * User profile inline editor.
+        *
+        * @param        integer                userID
+        * @param        boolean                editOnInit
+        */
+       WCF.User.Profile.Editor = Class.extend({
+               /**
+                * current action
+                * @var        string
+                */
+               _actionName: '',
+               
+               _active: false,
+               
+               /**
+                * list of interface buttons
+                * @var        object
+                */
+               _buttons: {},
+               
+               /**
+                * cached tab content
+                * @var        string
+                */
+               _cachedTemplate: '',
+               
+               /**
+                * action proxy
+                * @var        WCF.Action.Proxy
+                */
+               _proxy: null,
+               
+               /**
+                * tab object
+                * @var        jQuery
+                */
+               _tab: null,
+               
+               /**
+                * target user id
+                * @var        integer
+                */
+               _userID: 0,
+               
+               /**
+                * Initializes the WCF.User.Profile.Editor object.
+                *
+                * @param        integer                userID
+                * @param        boolean                editOnInit
+                */
+               init: function (userID, editOnInit) {
+                       this._actionName = '';
+                       this._active = false;
+                       this._cachedTemplate = '';
+                       this._tab = $('#about');
+                       this._userID = userID;
+                       this._proxy = new WCF.Action.Proxy({
+                               success: $.proxy(this._success, this)
+                       });
                        
-                       switch ($element.getTagName()) {
-                               case 'input':
-                                       var $type = $element.attr('type');
-                                       
-                                       if (($type === 'radio' || $type === 'checkbox') && !$element.prop('checked')) {
-                                               return;
-                                       }
-                               break;
-                               
-                               case 'textarea':
-                                       if ($element.data('redactor')) {
-                                               $value = $element.redactor('code.get');
-                                       }
-                               break;
+                       this._initButtons();
+                       
+                       // begin editing on page load
+                       if (editOnInit) {
+                               this._beginEdit();
                        }
+               },
+               
+               /**
+                * Initializes interface buttons.
+                */
+               _initButtons: function () {
+                       // create buttons
+                       this._buttons = {
+                               beginEdit: $('.jsButtonEditProfile:eq(0)').click(this._beginEdit.bind(this))
+                       };
+               },
+               
+               /**
+                * Begins editing.
+                *
+                * @param       {Event?}         event   event object
+                */
+               _beginEdit: function (event) {
+                       if (event) event.preventDefault();
+                       
+                       if (this._active) return;
+                       this._active = true;
+                       
+                       this._actionName = 'beginEdit';
+                       this._buttons.beginEdit.parent().addClass('active');
+                       $('#profileContent').wcfTabs('select', 'about');
+                       
+                       // load form
+                       this._proxy.setOption('data', {
+                               actionName: 'beginEdit',
+                               className: 'wcf\\data\\user\\UserProfileAction',
+                               objectIDs: [this._userID]
+                       });
+                       this._proxy.sendRequest();
+               },
+               
+               /**
+                * Saves input values.
+                */
+               _save: function () {
+                       this._actionName = 'save';
                        
-                       var $name = $element.attr('name');
-                       if ($regExp.test($name)) {
-                               var $fieldName = RegExp.$1;
-                               if ($value === null) $value = $element.val();
+                       // collect values
+                       var $regExp = /values\[([a-zA-Z0-9._-]+)\]/;
+                       var $values = {};
+                       this._tab.find('input, textarea, select').each(function (index, element) {
+                               var $element = $(element);
+                               var $value = null;
                                
-                               // check for checkboxes
-                               if ($element.attr('type') === 'checkbox' && /\[\]$/.test($name)) {
-                                       if (!Array.isArray($values[$fieldName])) {
-                                               $values[$fieldName] = [];
-                                       }
+                               switch ($element.getTagName()) {
+                                       case 'input':
+                                               var $type = $element.attr('type');
+                                               
+                                               if (($type === 'radio' || $type === 'checkbox') && !$element.prop('checked')) {
+                                                       return;
+                                               }
+                                               break;
                                        
-                                       $values[$fieldName].push($value);
-                               }
-                               else {
-                                       $values[$fieldName] = $value;
-                               }
-                       }
-               });
-               
-               this._proxy.setOption('data', {
-                       actionName: 'save',
-                       className: 'wcf\\data\\user\\UserProfileAction',
-                       objectIDs: [ this._userID ],
-                       parameters: {
-                               values: $values
-                       }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Restores back to default view.
-        */
-       _restore: function() {
-               this._actionName = 'restore';
-               this._active = false;
-               this._buttons.beginEdit.parent().removeClass('active');
-               
-               this._destroyEditor();
-               
-               this._tab.html(this._cachedTemplate).children().css({ height: 'auto' });
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               switch (this._actionName) {
-                       case 'beginEdit':
-                               this._prepareEdit(data);
-                       break;
-                       
-                       case 'save':
-                               // save was successful, show parsed template
-                               if (data.returnValues.success) {
-                                       this._cachedTemplate = data.returnValues.template;
-                                       this._restore();
+                                       case 'textarea':
+                                               if ($element.data('redactor')) {
+                                                       $value = $element.redactor('code.get');
+                                               }
+                                               break;
                                }
-                               else {
-                                       this._prepareEdit(data, true);
+                               
+                               var $name = $element.attr('name');
+                               if ($regExp.test($name)) {
+                                       var $fieldName = RegExp.$1;
+                                       if ($value === null) $value = $element.val();
+                                       
+                                       // check for checkboxes
+                                       if ($element.attr('type') === 'checkbox' && /\[\]$/.test($name)) {
+                                               if (!Array.isArray($values[$fieldName])) {
+                                                       $values[$fieldName] = [];
+                                               }
+                                               
+                                               $values[$fieldName].push($value);
+                                       }
+                                       else {
+                                               $values[$fieldName] = $value;
+                                       }
                                }
-                       break;
-               }
-       },
-       
-       /**
-        * Prepares editing mode.
-        * 
-        * @param       object          data
-        * @param       boolean         disableCache
-        */
-       _prepareEdit: function(data, disableCache) {
-               this._destroyEditor();
-               
-               // update template
-               var self = this;
-               this._tab.html(function(index, oldHTML) {
-                       if (disableCache !== true) {
-                               self._cachedTemplate = oldHTML;
-                       }
+                       });
                        
-                       return data.returnValues.template;
-               });
-               
-               // block autocomplete
-               this._tab.find('input[type=text]').attr('autocomplete', 'off');
-               
-               // bind event listener
-               this._tab.find('.formSubmit > button[data-type=save]').click($.proxy(this._save, this));
-               this._tab.find('.formSubmit > button[data-type=restore]').click($.proxy(this._restore, this));
-               this._tab.find('input').keyup(function(event) {
-                       if (event.which === $.ui.keyCode.ENTER) {
-                               self._save();
+                       this._proxy.setOption('data', {
+                               actionName: 'save',
+                               className: 'wcf\\data\\user\\UserProfileAction',
+                               objectIDs: [this._userID],
+                               parameters: {
+                                       values: $values
+                               }
+                       });
+                       this._proxy.sendRequest();
+               },
+               
+               /**
+                * Restores back to default view.
+                */
+               _restore: function () {
+                       this._actionName = 'restore';
+                       this._active = false;
+                       this._buttons.beginEdit.parent().removeClass('active');
+                       
+                       this._destroyEditor();
+                       
+                       this._tab.html(this._cachedTemplate).children().css({height: 'auto'});
+               },
+               
+               /**
+                * Handles successful AJAX requests.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        jQuery                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       switch (this._actionName) {
+                               case 'beginEdit':
+                                       this._prepareEdit(data);
+                                       break;
                                
-                               event.preventDefault();
-                               return false;
-                       }
-               });
-       },
-       
-       /**
-        * Destroys all editor instances within current tab.
-        */
-       _destroyEditor: function() {
-               // destroy all editor instances
-               this._tab.find('textarea').each(function(index, container) {
-                       var $container = $(container);
-                       if ($container.data('redactor')) {
-                               $container.redactor('core.destroy');
+                               case 'save':
+                                       // save was successful, show parsed template
+                                       if (data.returnValues.success) {
+                                               this._cachedTemplate = data.returnValues.template;
+                                               this._restore();
+                                       }
+                                       else {
+                                               this._prepareEdit(data, true);
+                                       }
+                                       break;
                        }
-               });
-       }
-});
+               },
+               
+               /**
+                * Prepares editing mode.
+                *
+                * @param        object                data
+                * @param        boolean                disableCache
+                */
+               _prepareEdit: function (data, disableCache) {
+                       this._destroyEditor();
+                       
+                       // update template
+                       var self = this;
+                       this._tab.html(function (index, oldHTML) {
+                               if (disableCache !== true) {
+                                       self._cachedTemplate = oldHTML;
+                               }
+                               
+                               return data.returnValues.template;
+                       });
+                       
+                       // block autocomplete
+                       this._tab.find('input[type=text]').attr('autocomplete', 'off');
+                       
+                       // bind event listener
+                       this._tab.find('.formSubmit > button[data-type=save]').click($.proxy(this._save, this));
+                       this._tab.find('.formSubmit > button[data-type=restore]').click($.proxy(this._restore, this));
+                       this._tab.find('input').keyup(function (event) {
+                               if (event.which === $.ui.keyCode.ENTER) {
+                                       self._save();
+                                       
+                                       event.preventDefault();
+                                       return false;
+                               }
+                       });
+               },
+               
+               /**
+                * Destroys all editor instances within current tab.
+                */
+               _destroyEditor: function () {
+                       // destroy all editor instances
+                       this._tab.find('textarea').each(function (index, container) {
+                               var $container = $(container);
+                               if ($container.data('redactor')) {
+                                       $container.redactor('core.destroy');
+                               }
+                       });
+               }
+       });
+}
+else {
+       WCF.User.Profile.Editor = Class.extend({
+               _actionName: "",
+               _active: false,
+               _buttons: {},
+               _cachedTemplate: "",
+               _proxy: {},
+               _tab: {},
+               _userID: 0,
+               init: function() {},
+               _initButtons: function() {},
+               _beginEdit: function() {},
+               _save: function() {},
+               _restore: function() {},
+               _success: function() {},
+               _prepareEdit: function() {},
+               _destroyEditor: function() {}
+       });
+}
 
 /**
  * Namespace for registration functions.
@@ -1548,125 +1637,154 @@ WCF.User.Registration.LostPassword = Class.extend({
  */
 WCF.Notification = { };
 
-/**
- * Handles the notification list.
- */
-WCF.Notification.List = Class.extend({
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
+if (COMPILER_TARGET_DEFAULT) {
        /**
-        * Initializes the WCF.Notification.List object.
+        * Handles the notification list.
         */
-       init: function() {
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
+       WCF.Notification.List = Class.extend({
+               /**
+                * action proxy
+                * @var        WCF.Action.Proxy
+                */
+               _proxy: null,
                
-               // handle 'mark all as confirmed' buttons
-               $('.contentHeaderNavigation .jsMarkAllAsConfirmed').click(function() {
-                       WCF.System.Confirmation.show(WCF.Language.get('wcf.user.notification.markAllAsConfirmed.confirmMessage'), function(action) {
-                               if (action === 'confirm') {
-                                       new WCF.Action.Proxy({
-                                               autoSend: true,
-                                               data: {
-                                                       actionName: 'markAllAsConfirmed',
-                                                       className: 'wcf\\data\\user\\notification\\UserNotificationAction'
-                                               },
-                                               success: function() { window.location.reload(); }
+               /**
+                * Initializes the WCF.Notification.List object.
+                */
+               init: function () {
+                       this._proxy = new WCF.Action.Proxy({
+                               success: $.proxy(this._success, this)
+                       });
+                       
+                       // handle 'mark all as confirmed' buttons
+                       $('.contentHeaderNavigation .jsMarkAllAsConfirmed').click(function () {
+                               WCF.System.Confirmation.show(WCF.Language.get('wcf.user.notification.markAllAsConfirmed.confirmMessage'), function (action) {
+                                       if (action === 'confirm') {
+                                               new WCF.Action.Proxy({
+                                                       autoSend: true,
+                                                       data: {
+                                                               actionName: 'markAllAsConfirmed',
+                                                               className: 'wcf\\data\\user\\notification\\UserNotificationAction'
+                                                       },
+                                                       success: function () {
+                                                               window.location.reload();
+                                                       }
+                                               });
+                                       }
+                               });
+                       });
+                       
+                       // handle regular items
+                       this._convertList();
+               },
+               
+               /**
+                * Converts the notification item list to be in sync with the notification dropdown.
+                */
+               _convertList: function () {
+                       $('.userNotificationItemList > .notificationItem').each((function (index, item) {
+                               var $item = $(item);
+                               
+                               if (!$item.data('isRead')) {
+                                       $item.find('a:not(.userLink)').prop('href', $item.data('link'));
+                                       
+                                       var $markAsConfirmed = $('<a href="#" class="icon icon24 fa-check notificationItemMarkAsConfirmed jsTooltip" title="' + WCF.Language.get('wcf.user.notification.markAsConfirmed') + '" />').appendTo($item);
+                                       $markAsConfirmed.click($.proxy(this._markAsConfirmed, this));
+                               }
+                               
+                               // work-around for legacy notifications
+                               if (!$item.find('a:not(.notificationItemMarkAsConfirmed)').length) {
+                                       $item.find('.details > p:eq(0)').html(function (index, oldHTML) {
+                                               return '<a href="' + $item.data('link') + '">' + oldHTML + '</a>';
                                        });
                                }
+                       }).bind(this));
+                       
+                       WCF.DOMNodeInsertedHandler.execute();
+               },
+               
+               /**
+                * Marks a single notification as confirmed.
+                *
+                * @param        object                event
+                */
+               _markAsConfirmed: function (event) {
+                       event.preventDefault();
+                       
+                       var $notificationID = $(event.currentTarget).parents('.notificationItem:eq(0)').data('objectID');
+                       
+                       this._proxy.setOption('data', {
+                               actionName: 'markAsConfirmed',
+                               className: 'wcf\\data\\user\\notification\\UserNotificationAction',
+                               objectIDs: [$notificationID]
                        });
-               });
-               
-               // handle regular items
-               this._convertList();
-       },
-       
-       /**
-        * Converts the notification item list to be in sync with the notification dropdown.
-        */
-       _convertList: function() {
-               $('.userNotificationItemList > .notificationItem').each((function(index, item) {
-                       var $item = $(item);
+                       this._proxy.sendRequest();
                        
-                       if (!$item.data('isRead')) {
-                               $item.find('a:not(.userLink)').prop('href', $item.data('link'));
-                               
-                               var $markAsConfirmed = $('<a href="#" class="icon icon24 fa-check notificationItemMarkAsConfirmed jsTooltip" title="' + WCF.Language.get('wcf.user.notification.markAsConfirmed') + '" />').appendTo($item);
-                               $markAsConfirmed.click($.proxy(this._markAsConfirmed, this));
-                       }
+                       return false;
+               },
+               
+               /**
+                * Handles successful AJAX requests.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        jQuery                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       var $item = $('.userNotificationItemList > .notificationItem[data-object-id=' + data.returnValues.markAsRead + ']');
                        
-                       // work-around for legacy notifications
-                       if (!$item.find('a:not(.notificationItemMarkAsConfirmed)').length) {
-                               $item.find('.details > p:eq(0)').html(function(index, oldHTML) {
-                                       return '<a href="' + $item.data('link') + '">' + oldHTML + '</a>';
-                               });
+                       $item.data('isRead', true);
+                       $item.find('.newContentBadge').remove();
+                       $item.find('.notificationItemMarkAsConfirmed').remove();
+                       $item.removeClass('notificationUnconfirmed');
+               }
+       });
+       
+       /**
+        * Signature preview.
+        *
+        * @see        WCF.Message.Preview
+        */
+       WCF.User.SignaturePreview = WCF.Message.Preview.extend({
+               /**
+                * @see        WCF.Message.Preview._handleResponse()
+                */
+               _handleResponse: function (data) {
+                       // get preview container
+                       var $preview = $('#previewContainer');
+                       if (!$preview.length) {
+                               $preview = $('<section class="section" id="previewContainer"><h2 class="sectionTitle">' + WCF.Language.get('wcf.global.preview') + '</h2><div class="htmlContent"></div></section>').insertBefore($('#signatureContainer')).wcfFadeIn();
                        }
-               }).bind(this));
-               
-               WCF.DOMNodeInsertedHandler.execute();
-       },
-       
-       /**
-        * Marks a single notification as confirmed.
-        * 
-        * @param       object          event
-        */
-       _markAsConfirmed: function(event) {
-               event.preventDefault();
-               
-               var $notificationID = $(event.currentTarget).parents('.notificationItem:eq(0)').data('objectID');
-               
-               this._proxy.setOption('data', {
-                       actionName: 'markAsConfirmed',
-                       className: 'wcf\\data\\user\\notification\\UserNotificationAction',
-                       objectIDs: [ $notificationID ]
-               });
-               this._proxy.sendRequest();
-               
-               return false;
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               var $item = $('.userNotificationItemList > .notificationItem[data-object-id=' + data.returnValues.markAsRead + ']');
-               
-               $item.data('isRead', true);
-               $item.find('.newContentBadge').remove();
-               $item.find('.notificationItemMarkAsConfirmed').remove();
-               $item.removeClass('notificationUnconfirmed');
-       }
-});
-
-/**
- * Signature preview.
- * 
- * @see        WCF.Message.Preview
- */
-WCF.User.SignaturePreview = WCF.Message.Preview.extend({
-       /**
-        * @see WCF.Message.Preview._handleResponse()
-        */
-       _handleResponse: function(data) {
-               // get preview container
-               var $preview = $('#previewContainer');
-               if (!$preview.length) {
-                       $preview = $('<section class="section" id="previewContainer"><h2 class="sectionTitle">' + WCF.Language.get('wcf.global.preview') + '</h2><div class="htmlContent"></div></section>').insertBefore($('#signatureContainer')).wcfFadeIn();
-               }
-               
-               $preview.children('div').first().html(data.returnValues.message);
-       }
-});
+                       
+                       $preview.children('div').first().html(data.returnValues.message);
+               }
+       });
+}
+else {
+       WCF.Notification.List = Class.extend({
+               _proxy: {},
+               init: function() {},
+               _convertList: function() {},
+               _markAsConfirmed: function() {},
+               _success: function() {}
+       });
+       
+       WCF.User.SignaturePreview = WCF.Message.Preview.extend({
+               _handleResponse: function() {},
+               _className: "",
+               _messageFieldID: "",
+               _messageField: {},
+               _proxy: {},
+               _previewButton: {},
+               _previewButtonLabel: "",
+               init: function() {},
+               _click: function() {},
+               _getParameters: function() {},
+               _getMessage: function() {},
+               _success: function() {},
+               _failure: function() {}
+       });
+}
 
 /**
  * Loads recent activity events once the user scrolls to the very bottom.
@@ -2061,339 +2179,394 @@ WCF.User.ProfilePreview = WCF.Popover.extend({
  */
 WCF.User.Action = {};
 
-/**
- * Handles user follow and unfollow links.
- */
-WCF.User.Action.Follow = Class.extend({
-       /**
-        * list with elements containing follow and unfollow buttons
-        * @var array
-        */
-       _containerList: null,
-       
-       /**
-        * CSS selector for follow buttons
-        * @var string
-        */
-       _followButtonSelector: '.jsFollowButton',
-       
-       /**
-        * id of the user that is currently being followed/unfollowed
-        * @var integer
-        */
-       _userID: 0,
-       
-       /**
-        * Initializes new WCF.User.Action.Follow object.
-        * 
-        * @param       array           containerList
-        * @param       string          followButtonSelector
-        */
-       init: function(containerList, followButtonSelector) {
-               if (!containerList.length) {
-                       return;
-               }
-               this._containerList = containerList;
-               
-               if (followButtonSelector) {
-                       this._followButtonSelector = followButtonSelector;
-               }
-               
-               // initialize proxy
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               // bind event listeners
-               this._containerList.each($.proxy(function(index, container) {
-                       $(container).find(this._followButtonSelector).click($.proxy(this._click, this));
-               }, this));
-       },
-       
-       /**
-        * Handles a click on a follow or unfollow button.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               event.preventDefault();
-               var link = $(event.target);
-               if (!link.is('a')) {
-                       link = link.closest('a');
-               }
-               this._userID = link.data('objectID');
-               
-               this._proxy.setOption('data', {
-                       'actionName': link.data('following') ? 'unfollow' : 'follow',
-                       'className': 'wcf\\data\\user\\follow\\UserFollowAction',
-                       'parameters': {
-                               data: {
-                                       userID: this._userID
-                               }
+if (COMPILER_TARGET_DEFAULT) {
+       /**
+        * Handles user follow and unfollow links.
+        */
+       WCF.User.Action.Follow = Class.extend({
+               /**
+                * list with elements containing follow and unfollow buttons
+                * @var        array
+                */
+               _containerList: null,
+               
+               /**
+                * CSS selector for follow buttons
+                * @var        string
+                */
+               _followButtonSelector: '.jsFollowButton',
+               
+               /**
+                * id of the user that is currently being followed/unfollowed
+                * @var        integer
+                */
+               _userID: 0,
+               
+               /**
+                * Initializes new WCF.User.Action.Follow object.
+                *
+                * @param        array                containerList
+                * @param        string                followButtonSelector
+                */
+               init: function (containerList, followButtonSelector) {
+                       if (!containerList.length) {
+                               return;
                        }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Handles the successful (un)following of a user.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               this._containerList.each($.proxy(function(index, container) {
-                       var button = $(container).find(this._followButtonSelector).get(0);
+                       this._containerList = containerList;
                        
-                       if (button && $(button).data('objectID') == this._userID) {
-                               button = $(button);
-                               
-                               // toogle icon title
-                               if (data.returnValues.following) {
-                                       button.attr('data-tooltip', WCF.Language.get('wcf.user.button.unfollow')).children('.icon').removeClass('fa-plus').addClass('fa-minus');
-                                       button.children('.invisible').text(WCF.Language.get('wcf.user.button.unfollow'));
-                               }
-                               else {
-                                       button.attr('data-tooltip', WCF.Language.get('wcf.user.button.follow')).children('.icon').removeClass('fa-minus').addClass('fa-plus');
-                                       button.children('.invisible').text(WCF.Language.get('wcf.user.button.follow'));
+                       if (followButtonSelector) {
+                               this._followButtonSelector = followButtonSelector;
+                       }
+                       
+                       // initialize proxy
+                       this._proxy = new WCF.Action.Proxy({
+                               success: $.proxy(this._success, this)
+                       });
+                       
+                       // bind event listeners
+                       this._containerList.each($.proxy(function (index, container) {
+                               $(container).find(this._followButtonSelector).click($.proxy(this._click, this));
+                       }, this));
+               },
+               
+               /**
+                * Handles a click on a follow or unfollow button.
+                *
+                * @param        object                event
+                */
+               _click: function (event) {
+                       event.preventDefault();
+                       var link = $(event.target);
+                       if (!link.is('a')) {
+                               link = link.closest('a');
+                       }
+                       this._userID = link.data('objectID');
+                       
+                       this._proxy.setOption('data', {
+                               'actionName': link.data('following') ? 'unfollow' : 'follow',
+                               'className': 'wcf\\data\\user\\follow\\UserFollowAction',
+                               'parameters': {
+                                       data: {
+                                               userID: this._userID
+                                       }
                                }
+                       });
+                       this._proxy.sendRequest();
+               },
+               
+               /**
+                * Handles the successful (un)following of a user.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        jQuery                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       this._containerList.each($.proxy(function (index, container) {
+                               var button = $(container).find(this._followButtonSelector).get(0);
                                
-                               button.data('following', data.returnValues.following);
-                               
-                               return false;
-                       }
-               }, this));
-               
-               var $notification = new WCF.System.Notification();
-               $notification.show();
-       }
-});
-
-/**
- * Handles user ignore and unignore links.
- */
-WCF.User.Action.Ignore = Class.extend({
-       /**
-        * list with elements containing ignore and unignore buttons
-        * @var array
-        */
-       _containerList: null,
-       
-       /**
-        * CSS selector for ignore buttons
-        * @var string
-        */
-       _ignoreButtonSelector: '.jsIgnoreButton',
-       
-       /**
-        * id of the user that is currently being ignored/unignored
-        * @var integer
-        */
-       _userID: 0,
-       
-       /**
-        * Initializes new WCF.User.Action.Ignore object.
-        * 
-        * @param       array           containerList
-        * @param       string          ignoreButtonSelector
-        */
-       init: function(containerList, ignoreButtonSelector) {
-               if (!containerList.length) {
-                       return;
-               }
-               this._containerList = containerList;
-               
-               if (ignoreButtonSelector) {
-                       this._ignoreButtonSelector = ignoreButtonSelector;
-               }
-               
-               // initialize proxy
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               // bind event listeners
-               this._containerList.each($.proxy(function(index, container) {
-                       $(container).find(this._ignoreButtonSelector).click($.proxy(this._click, this));
-               }, this));
-       },
-       
-       /**
-        * Handles a click on a ignore or unignore button.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               event.preventDefault();
-               var link = $(event.target);
-               if (!link.is('a')) {
-                       link = link.closest('a');
-               }
-               this._userID = link.data('objectID');
-               
-               this._proxy.setOption('data', {
-                       'actionName': link.data('ignored') ? 'unignore' : 'ignore',
-                       'className': 'wcf\\data\\user\\ignore\\UserIgnoreAction',
-                       'parameters': {
-                               data: {
-                                       userID: this._userID
+                               if (button && $(button).data('objectID') == this._userID) {
+                                       button = $(button);
+                                       
+                                       // toogle icon title
+                                       if (data.returnValues.following) {
+                                               button.attr('data-tooltip', WCF.Language.get('wcf.user.button.unfollow')).children('.icon').removeClass('fa-plus').addClass('fa-minus');
+                                               button.children('.invisible').text(WCF.Language.get('wcf.user.button.unfollow'));
+                                       }
+                                       else {
+                                               button.attr('data-tooltip', WCF.Language.get('wcf.user.button.follow')).children('.icon').removeClass('fa-minus').addClass('fa-plus');
+                                               button.children('.invisible').text(WCF.Language.get('wcf.user.button.follow'));
+                                       }
+                                       
+                                       button.data('following', data.returnValues.following);
+                                       
+                                       return false;
                                }
+                       }, this));
+                       
+                       var $notification = new WCF.System.Notification();
+                       $notification.show();
+               }
+       });
+       
+       /**
+        * Handles user ignore and unignore links.
+        */
+       WCF.User.Action.Ignore = Class.extend({
+               /**
+                * list with elements containing ignore and unignore buttons
+                * @var        array
+                */
+               _containerList: null,
+               
+               /**
+                * CSS selector for ignore buttons
+                * @var        string
+                */
+               _ignoreButtonSelector: '.jsIgnoreButton',
+               
+               /**
+                * id of the user that is currently being ignored/unignored
+                * @var        integer
+                */
+               _userID: 0,
+               
+               /**
+                * Initializes new WCF.User.Action.Ignore object.
+                *
+                * @param        array                containerList
+                * @param        string                ignoreButtonSelector
+                */
+               init: function (containerList, ignoreButtonSelector) {
+                       if (!containerList.length) {
+                               return;
+                       }
+                       this._containerList = containerList;
+                       
+                       if (ignoreButtonSelector) {
+                               this._ignoreButtonSelector = ignoreButtonSelector;
                        }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Handles the successful (un)ignoring of a user.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               this._containerList.each($.proxy(function(index, container) {
-                       var button = $(container).find(this._ignoreButtonSelector).get(0);
                        
-                       if (button && $(button).data('objectID') == this._userID) {
-                               button = $(button);
-                               
-                               // toogle icon title
-                               if (data.returnValues.isIgnoredUser) {
-                                       button.attr('data-tooltip', WCF.Language.get('wcf.user.button.unignore')).children('.icon').removeClass('fa-ban').addClass('fa-circle-o');
-                                       button.children('.invisible').text(WCF.Language.get('wcf.user.button.unignore'));
-                               }
-                               else {
-                                       button.attr('data-tooltip', WCF.Language.get('wcf.user.button.ignore')).children('.icon').removeClass('fa-circle-o').addClass('fa-ban');
-                                       button.children('.invisible').text(WCF.Language.get('wcf.user.button.ignore'));
+                       // initialize proxy
+                       this._proxy = new WCF.Action.Proxy({
+                               success: $.proxy(this._success, this)
+                       });
+                       
+                       // bind event listeners
+                       this._containerList.each($.proxy(function (index, container) {
+                               $(container).find(this._ignoreButtonSelector).click($.proxy(this._click, this));
+                       }, this));
+               },
+               
+               /**
+                * Handles a click on a ignore or unignore button.
+                *
+                * @param        object                event
+                */
+               _click: function (event) {
+                       event.preventDefault();
+                       var link = $(event.target);
+                       if (!link.is('a')) {
+                               link = link.closest('a');
+                       }
+                       this._userID = link.data('objectID');
+                       
+                       this._proxy.setOption('data', {
+                               'actionName': link.data('ignored') ? 'unignore' : 'ignore',
+                               'className': 'wcf\\data\\user\\ignore\\UserIgnoreAction',
+                               'parameters': {
+                                       data: {
+                                               userID: this._userID
+                                       }
                                }
+                       });
+                       this._proxy.sendRequest();
+               },
+               
+               /**
+                * Handles the successful (un)ignoring of a user.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        jQuery                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       this._containerList.each($.proxy(function (index, container) {
+                               var button = $(container).find(this._ignoreButtonSelector).get(0);
                                
-                               button.data('ignored', data.returnValues.isIgnoredUser);
-                               
-                               return false;
-                       }
-               }, this));
-               
-               var $notification = new WCF.System.Notification();
-               $notification.show();
-               
-               // force rebuilding of popover cache
-               var self = this;
-               WCF.System.ObjectStore.invoke('WCF.User.ProfilePreview', function(profilePreview) {
-                       profilePreview.purge(self._userID);
-               });
-       }
-});
+                               if (button && $(button).data('objectID') == this._userID) {
+                                       button = $(button);
+                                       
+                                       // toogle icon title
+                                       if (data.returnValues.isIgnoredUser) {
+                                               button.attr('data-tooltip', WCF.Language.get('wcf.user.button.unignore')).children('.icon').removeClass('fa-ban').addClass('fa-circle-o');
+                                               button.children('.invisible').text(WCF.Language.get('wcf.user.button.unignore'));
+                                       }
+                                       else {
+                                               button.attr('data-tooltip', WCF.Language.get('wcf.user.button.ignore')).children('.icon').removeClass('fa-circle-o').addClass('fa-ban');
+                                               button.children('.invisible').text(WCF.Language.get('wcf.user.button.ignore'));
+                                       }
+                                       
+                                       button.data('ignored', data.returnValues.isIgnoredUser);
+                                       
+                                       return false;
+                               }
+                       }, this));
+                       
+                       var $notification = new WCF.System.Notification();
+                       $notification.show();
+                       
+                       // force rebuilding of popover cache
+                       var self = this;
+                       WCF.System.ObjectStore.invoke('WCF.User.ProfilePreview', function (profilePreview) {
+                               profilePreview.purge(self._userID);
+                       });
+               }
+       });
+}
+else {
+       WCF.User.Action.Follow = Class.extend({
+               _containerList: {},
+               _followButtonSelector: "",
+               _userID: 0,
+               init: function() {},
+               _click: function() {},
+               _success: function() {}
+       });
+       
+       WCF.User.Action.Ignore = Class.extend({
+               _containerList: {},
+               _ignoreButtonSelector: "",
+               _userID: 0,
+               init: function() {},
+               _click: function() {},
+               _success: function() {}
+       });
+}
 
 /**
  * Namespace for avatar functions.
  */
 WCF.User.Avatar = {};
 
-/**
- * Avatar upload function
- * 
- * @see        WCF.Upload
- */
-WCF.User.Avatar.Upload = WCF.Upload.extend({
-       /**
-        * user id of avatar owner
-        * @var integer
-        */
-       _userID: 0,
-       
-       /**
-        * Initalizes a new WCF.User.Avatar.Upload object.
-        * 
-        * @param       integer                 userID
-        */
-       init: function(userID) {
-               this._super($('#avatarUpload > dd > div'), undefined, 'wcf\\data\\user\\avatar\\UserAvatarAction');
-               this._userID = userID || 0;
-               
-               $('#avatarForm input[type=radio]').change(function() {
-                       if ($(this).val() == 'custom') {
-                               $('#avatarUpload > dd > div').show();
-                       }
-                       else {
+if (COMPILER_TARGET_DEFAULT) {
+       /**
+        * Avatar upload function
+        *
+        * @see        WCF.Upload
+        */
+       WCF.User.Avatar.Upload = WCF.Upload.extend({
+               /**
+                * user id of avatar owner
+                * @var        integer
+                */
+               _userID: 0,
+               
+               /**
+                * Initalizes a new WCF.User.Avatar.Upload object.
+                *
+                * @param        integer                        userID
+                */
+               init: function (userID) {
+                       this._super($('#avatarUpload > dd > div'), undefined, 'wcf\\data\\user\\avatar\\UserAvatarAction');
+                       this._userID = userID || 0;
+                       
+                       $('#avatarForm input[type=radio]').change(function () {
+                               if ($(this).val() == 'custom') {
+                                       $('#avatarUpload > dd > div').show();
+                               }
+                               else {
+                                       $('#avatarUpload > dd > div').hide();
+                               }
+                       });
+                       if (!$('#avatarForm input[type=radio][value=custom]:checked').length) {
                                $('#avatarUpload > dd > div').hide();
                        }
-               });
-               if (!$('#avatarForm input[type=radio][value=custom]:checked').length) {
-                       $('#avatarUpload > dd > div').hide();
-               }
-       },
-       
-       /**
-        * @see WCF.Upload._initFile()
-        */
-       _initFile: function(file) {
-               return $('#avatarUpload > dt > img');
-       },
-       
-       /**
-        * @see WCF.Upload._success()
-        */
-       _success: function(uploadID, data) {
-               if (data.returnValues.url) {
-                       this._updateImage(data.returnValues.url);
+               },
+               
+               /**
+                * @see        WCF.Upload._initFile()
+                */
+               _initFile: function (file) {
+                       return $('#avatarUpload > dt > img');
+               },
+               
+               /**
+                * @see        WCF.Upload._success()
+                */
+               _success: function (uploadID, data) {
+                       if (data.returnValues.url) {
+                               this._updateImage(data.returnValues.url);
+                               
+                               // hide error
+                               $('#avatarUpload > dd > .innerError').remove();
+                               
+                               // show success message
+                               var $notification = new WCF.System.Notification(WCF.Language.get('wcf.user.avatar.upload.success'));
+                               $notification.show();
+                       }
+                       else if (data.returnValues.errorType) {
+                               // show error
+                               this._getInnerErrorElement().text(WCF.Language.get('wcf.user.avatar.upload.error.' + data.returnValues.errorType));
+                       }
+               },
+               
+               /**
+                * Updates the displayed avatar image.
+                *
+                * @param        string                url
+                */
+               _updateImage: function (url) {
+                       $('#avatarUpload > dt > img').remove();
+                       var $image = $('<img src="' + url + '" class="userAvatarImage" alt="" />').css({
+                               'height': 'auto',
+                               'max-height': '96px',
+                               'max-width': '96px',
+                               'width': 'auto'
+                       });
                        
-                       // hide error
-                       $('#avatarUpload > dd > .innerError').remove();
+                       $('#avatarUpload > dt').prepend($image);
                        
-                       // show success message
-                       var $notification = new WCF.System.Notification(WCF.Language.get('wcf.user.avatar.upload.success'));
-                       $notification.show();
-               }
-               else if (data.returnValues.errorType) {
-                       // show error
-                       this._getInnerErrorElement().text(WCF.Language.get('wcf.user.avatar.upload.error.' + data.returnValues.errorType));
-               }
-       },
-       
-       /**
-        * Updates the displayed avatar image.
-        * 
-        * @param       string          url
-        */
-       _updateImage: function(url) {
-               $('#avatarUpload > dt > img').remove();
-               var $image = $('<img src="' + url + '" class="userAvatarImage" alt="" />').css({
-                       'height': 'auto',
-                       'max-height': '96px',
-                       'max-width': '96px',
-                       'width': 'auto'
-               });
-               
-               $('#avatarUpload > dt').prepend($image);
-               
-               WCF.DOMNodeInsertedHandler.execute();
-       },
-       
-       /**
-        * Returns the inner error element.
-        * 
-        * @return      jQuery
-        */
-       _getInnerErrorElement: function() {
-               var $span = $('#avatarUpload > dd > .innerError');
-               if (!$span.length) {
-                       $span = $('<small class="innerError"></span>');
-                       $('#avatarUpload > dd').append($span);
+                       WCF.DOMNodeInsertedHandler.execute();
+               },
+               
+               /**
+                * Returns the inner error element.
+                *
+                * @return        jQuery
+                */
+               _getInnerErrorElement: function () {
+                       var $span = $('#avatarUpload > dd > .innerError');
+                       if (!$span.length) {
+                               $span = $('<small class="innerError"></span>');
+                               $('#avatarUpload > dd').append($span);
+                       }
+                       
+                       return $span;
+               },
+               
+               /**
+                * @see        WCF.Upload._getParameters()
+                */
+               _getParameters: function () {
+                       return {
+                               userID: this._userID
+                       };
                }
-               
-               return $span;
-       },
-       
-       /**
-        * @see WCF.Upload._getParameters()
-        */
-       _getParameters: function() {
-               return {
-                       userID: this._userID
-               };
-       }
-});
+       });
+}
+else {
+       WCF.User.Avatar.Upload = WCF.Upload.extend({
+               _userID: 0,
+               init: function() {},
+               _initFile: function() {},
+               _success: function() {},
+               _updateImage: function() {},
+               _getInnerErrorElement: function() {},
+               _getParameters: function() {},
+               _name: "",
+               _buttonSelector: {},
+               _fileListSelector: {},
+               _fileUpload: {},
+               _className: "",
+               _iframe: {},
+               _internalFileID: 0,
+               _options: {},
+               _uploadMatrix: {},
+               _supportsAJAXUpload: true,
+               _overlay: {},
+               _createButton: function() {},
+               _insertButton: function() {},
+               _removeButton: function() {},
+               _upload: function() {},
+               _createUploadMatrix: function() {},
+               _error: function() {},
+               _progress: function() {},
+               _showOverlay: function() {},
+               _evaluateResponse: function() {},
+               _getFilename: function() {}
+       });
+}
 
 /**
  * Generic implementation for grouped user lists.
@@ -2571,201 +2744,217 @@ WCF.User.List = Class.extend({
  */
 WCF.User.ObjectWatch = {};
 
-/**
- * Handles subscribe/unsubscribe links.
- */
-WCF.User.ObjectWatch.Subscribe = Class.extend({
-       /**
-        * CSS selector for subscribe buttons
-        * @var string
-        */
-       _buttonSelector: '.jsSubscribeButton',
-       
-       /**
-        * list of buttons
-        * @var object
-        */
-       _buttons: { },
-       
-       /**
-        * dialog overlay
-        * @var object
-        */
-       _dialog: null,
-       
-       /**
-        * system notification
-        * @var WCF.System.Notification
-        */
-       _notification: null,
-       
-       /**
-        * reload page on unsubscribe
-        * @var boolean
-        */
-       _reloadOnUnsubscribe: false,
-       
-       /**
-        * WCF.User.ObjectWatch.Subscribe object.
-        * 
-        * @param       boolean         reloadOnUnsubscribe
-        */
-       init: function(reloadOnUnsubscribe) {
-               this._buttons = { };
-               this._notification = null;
-               this._reloadOnUnsubscribe = (reloadOnUnsubscribe === true);
-               
-               // initialize proxy
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               // bind event listeners
-               $(this._buttonSelector).each($.proxy(function(index, button) {
-                       var $button = $(button);
-                       $button.addClass('pointer');
-                       var $objectType = $button.data('objectType');
-                       var $objectID = $button.data('objectID');
+if (COMPILER_TARGET_DEFAULT) {
+       /**
+        * Handles subscribe/unsubscribe links.
+        */
+       WCF.User.ObjectWatch.Subscribe = Class.extend({
+               /**
+                * CSS selector for subscribe buttons
+                * @var        string
+                */
+               _buttonSelector: '.jsSubscribeButton',
+               
+               /**
+                * list of buttons
+                * @var        object
+                */
+               _buttons: {},
+               
+               /**
+                * dialog overlay
+                * @var        object
+                */
+               _dialog: null,
+               
+               /**
+                * system notification
+                * @var        WCF.System.Notification
+                */
+               _notification: null,
+               
+               /**
+                * reload page on unsubscribe
+                * @var        boolean
+                */
+               _reloadOnUnsubscribe: false,
+               
+               /**
+                * WCF.User.ObjectWatch.Subscribe object.
+                *
+                * @param        boolean                reloadOnUnsubscribe
+                */
+               init: function (reloadOnUnsubscribe) {
+                       this._buttons = {};
+                       this._notification = null;
+                       this._reloadOnUnsubscribe = (reloadOnUnsubscribe === true);
                        
-                       if (this._buttons[$objectType] === undefined) {
-                               this._buttons[$objectType] = {};
-                       }
+                       // initialize proxy
+                       this._proxy = new WCF.Action.Proxy({
+                               success: $.proxy(this._success, this)
+                       });
                        
-                       this._buttons[$objectType][$objectID] = $button.click($.proxy(this._click, this));
-               }, this));
-               
-               WCF.System.Event.addListener('com.woltlab.wcf.objectWatch', 'update', $.proxy(this._updateSubscriptionStatus, this));
-       },
-       
-       /**
-        * Handles a click on a subscribe button.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               event.preventDefault();
-               var $button = $(event.currentTarget);
-               
-               this._proxy.setOption('data', {
-                       actionName: 'manageSubscription',
-                       className: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
-                       parameters: {
-                               objectID: $button.data('objectID'),
-                               objectType: $button.data('objectType')
-                       }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Handles successful AJAX requests.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               if (data.actionName === 'manageSubscription') {
-                       if (this._dialog === null) {
-                               this._dialog = $('<div>' + data.returnValues.template + '</div>').hide().appendTo(document.body);
-                               this._dialog.wcfDialog({
-                                       title: WCF.Language.get('wcf.user.objectWatch.manageSubscription')
-                               });
-                       }
-                       else {
-                               this._dialog.html(data.returnValues.template);
-                               this._dialog.wcfDialog('open');
-                       }
+                       // bind event listeners
+                       $(this._buttonSelector).each($.proxy(function (index, button) {
+                               var $button = $(button);
+                               $button.addClass('pointer');
+                               var $objectType = $button.data('objectType');
+                               var $objectID = $button.data('objectID');
+                               
+                               if (this._buttons[$objectType] === undefined) {
+                                       this._buttons[$objectType] = {};
+                               }
+                               
+                               this._buttons[$objectType][$objectID] = $button.click($.proxy(this._click, this));
+                       }, this));
                        
-                       // bind event listener
-                       this._dialog.find('.formSubmit > .jsButtonSave').data('objectID', data.returnValues.objectID).data('objectType', data.returnValues.objectType).click($.proxy(this._save, this));
-                       var $enableNotification = this._dialog.find('input[name=enableNotification]').disable();
+                       WCF.System.Event.addListener('com.woltlab.wcf.objectWatch', 'update', $.proxy(this._updateSubscriptionStatus, this));
+               },
+               
+               /**
+                * Handles a click on a subscribe button.
+                *
+                * @param        object                event
+                */
+               _click: function (event) {
+                       event.preventDefault();
+                       var $button = $(event.currentTarget);
                        
-                       // toggle subscription
-                       this._dialog.find('input[name=subscribe]').change(function(event) {
-                               var $input = $(event.currentTarget);
-                               if ($input.val() == 1) {
-                                       $enableNotification.enable();
+                       this._proxy.setOption('data', {
+                               actionName: 'manageSubscription',
+                               className: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
+                               parameters: {
+                                       objectID: $button.data('objectID'),
+                                       objectType: $button.data('objectType')
+                               }
+                       });
+                       this._proxy.sendRequest();
+               },
+               
+               /**
+                * Handles successful AJAX requests.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        jQuery                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       if (data.actionName === 'manageSubscription') {
+                               if (this._dialog === null) {
+                                       this._dialog = $('<div>' + data.returnValues.template + '</div>').hide().appendTo(document.body);
+                                       this._dialog.wcfDialog({
+                                               title: WCF.Language.get('wcf.user.objectWatch.manageSubscription')
+                                       });
                                }
                                else {
-                                       $enableNotification.disable();
+                                       this._dialog.html(data.returnValues.template);
+                                       this._dialog.wcfDialog('open');
+                               }
+                               
+                               // bind event listener
+                               this._dialog.find('.formSubmit > .jsButtonSave').data('objectID', data.returnValues.objectID).data('objectType', data.returnValues.objectType).click($.proxy(this._save, this));
+                               var $enableNotification = this._dialog.find('input[name=enableNotification]').disable();
+                               
+                               // toggle subscription
+                               this._dialog.find('input[name=subscribe]').change(function (event) {
+                                       var $input = $(event.currentTarget);
+                                       if ($input.val() == 1) {
+                                               $enableNotification.enable();
+                                       }
+                                       else {
+                                               $enableNotification.disable();
+                                       }
+                               });
+                               
+                               // setup
+                               var $selectedOption = this._dialog.find('input[name=subscribe]:checked');
+                               if ($selectedOption.length && $selectedOption.val() == 1) {
+                                       $enableNotification.enable();
                                }
-                       });
-                       
-                       // setup
-                       var $selectedOption = this._dialog.find('input[name=subscribe]:checked');
-                       if ($selectedOption.length && $selectedOption.val() == 1) {
-                               $enableNotification.enable();
                        }
-               }
-               else if (data.actionName === 'saveSubscription' && this._dialog.is(':visible')) {
-                       this._dialog.wcfDialog('close');
-                       
-                       this._updateSubscriptionStatus({
-                               isSubscribed: data.returnValues.subscribe,
-                               objectID: data.returnValues.objectID
-                       });
-                       
-                       
-                       // show notification
-                       if (this._notification === null) {
-                               this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success.edit'));
+                       else if (data.actionName === 'saveSubscription' && this._dialog.is(':visible')) {
+                               this._dialog.wcfDialog('close');
+                               
+                               this._updateSubscriptionStatus({
+                                       isSubscribed: data.returnValues.subscribe,
+                                       objectID: data.returnValues.objectID
+                               });
+                               
+                               
+                               // show notification
+                               if (this._notification === null) {
+                                       this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success.edit'));
+                               }
+                               
+                               this._notification.show();
                        }
+               },
+               
+               /**
+                * Saves the subscription.
+                *
+                * @param        object                event
+                */
+               _save: function (event) {
+                       var $button = this._buttons[$(event.currentTarget).data('objectType')][$(event.currentTarget).data('objectID')];
+                       var $subscribe = this._dialog.find('input[name=subscribe]:checked').val();
+                       var $enableNotification = (this._dialog.find('input[name=enableNotification]').is(':checked')) ? 1 : 0;
                        
-                       this._notification.show();
-               }
-       },
-       
-       /**
-        * Saves the subscription.
-        * 
-        * @param       object          event
-        */
-       _save: function(event) {
-               var $button = this._buttons[$(event.currentTarget).data('objectType')][$(event.currentTarget).data('objectID')];
-               var $subscribe = this._dialog.find('input[name=subscribe]:checked').val();
-               var $enableNotification = (this._dialog.find('input[name=enableNotification]').is(':checked')) ? 1 : 0;
-               
-               this._proxy.setOption('data', {
-                       actionName: 'saveSubscription',
-                       className: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
-                       parameters: {
-                               enableNotification: $enableNotification,
-                               objectID: $button.data('objectID'),
-                               objectType: $button.data('objectType'),
-                               subscribe: $subscribe
-                       }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Updates subscription status and icon.
-        * 
-        * @param       object          data
-        */
-       _updateSubscriptionStatus: function(data) {
-               var $button = $(this._buttonSelector + '[data-object-id=' + data.objectID + ']');
-               var $icon = $button.children('.icon');
-               if (data.isSubscribed) {
-                       $icon.removeClass('fa-bookmark-o').addClass('fa-bookmark');
-                       $button.data('isSubscribed', true);
-               }
-               else {
-                       if ($button.data('removeOnUnsubscribe')) {
-                               $button.parent().remove();
+                       this._proxy.setOption('data', {
+                               actionName: 'saveSubscription',
+                               className: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
+                               parameters: {
+                                       enableNotification: $enableNotification,
+                                       objectID: $button.data('objectID'),
+                                       objectType: $button.data('objectType'),
+                                       subscribe: $subscribe
+                               }
+                       });
+                       this._proxy.sendRequest();
+               },
+               
+               /**
+                * Updates subscription status and icon.
+                *
+                * @param        object                data
+                */
+               _updateSubscriptionStatus: function (data) {
+                       var $button = $(this._buttonSelector + '[data-object-id=' + data.objectID + ']');
+                       var $icon = $button.children('.icon');
+                       if (data.isSubscribed) {
+                               $icon.removeClass('fa-bookmark-o').addClass('fa-bookmark');
+                               $button.data('isSubscribed', true);
                        }
                        else {
-                               $icon.removeClass('fa-bookmark').addClass('fa-bookmark-o');
-                               $button.data('isSubscribed', false);
+                               if ($button.data('removeOnUnsubscribe')) {
+                                       $button.parent().remove();
+                               }
+                               else {
+                                       $icon.removeClass('fa-bookmark').addClass('fa-bookmark-o');
+                                       $button.data('isSubscribed', false);
+                               }
+                               
+                               if (this._reloadOnUnsubscribe) {
+                                       window.location.reload();
+                                       return;
+                               }
                        }
                        
-                       if (this._reloadOnUnsubscribe) {
-                               window.location.reload();
-                               return;
-                       }
-               }
-               
-               WCF.System.Event.fireEvent('com.woltlab.wcf.objectWatch', 'updatedSubscription', data);
-       }
-});
+                       WCF.System.Event.fireEvent('com.woltlab.wcf.objectWatch', 'updatedSubscription', data);
+               }
+       });
+}
+else {
+       WCF.User.ObjectWatch.Subscribe = Class.extend({
+               _buttonSelector: "",
+               _buttons: {},
+               _dialog: {},
+               _notification: {},
+               _reloadOnUnsubscribe: false,
+               init: function() {},
+               _click: function() {},
+               _success: function() {},
+               _save: function() {},
+               _updateSubscriptionStatus: function() {}
+       });
+}
index f70c1706db443beddcb293774be5d99739395594..63feb48ac8b3a958595212b20d33f3a74216c001 100755 (executable)
@@ -978,437 +978,480 @@ WCF.Dropdown = {
  */
 WCF.Dropdown.Interactive = { };
 
-/**
- * General interface to create and manage interactive dropdowns.
- */
-WCF.Dropdown.Interactive.Handler = {
-       /**
-        * global container for interactive dropdowns
-        * @var jQuery
-        */
-       _dropdownContainer: null,
-       
-       /**
-        * list of dropdown instances by identifier
-        * @var object<WCF.Dropdown.Interactive.Instance>
-        */
-       _dropdownMenus: { },
-       
+if (COMPILER_TARGET_DEFAULT) {
        /**
-        * Creates a new interactive dropdown instance.
-        * 
-        * @param       jQuery          triggerElement
-        * @param       string          identifier
-        * @param       object          options
-        * @return      WCF.Dropdown.Interactive.Instance
+        * General interface to create and manage interactive dropdowns.
         */
-       create: function(triggerElement, identifier, options) {
-               if (this._dropdownContainer === null) {
-                       this._dropdownContainer = $('<div class="dropdownMenuContainer" />').appendTo(document.body);
-                       WCF.CloseOverlayHandler.addCallback('WCF.Dropdown.Interactive.Handler', $.proxy(this.closeAll, this));
-               }
+       WCF.Dropdown.Interactive.Handler = {
+               /**
+                * global container for interactive dropdowns
+                * @var        jQuery
+                */
+               _dropdownContainer: null,
                
-               var $instance = new WCF.Dropdown.Interactive.Instance(this._dropdownContainer, triggerElement, identifier, options);
-               this._dropdownMenus[identifier] = $instance;
+               /**
+                * list of dropdown instances by identifier
+                * @var        object<WCF.Dropdown.Interactive.Instance>
+                */
+               _dropdownMenus: {},
                
-               return $instance;
-       },
-       
-       /**
-        * Opens an interactive dropdown, returns false if identifier is unknown.
-        * 
-        * @param       string          identifier
-        * @return      boolean
-        */
-       open: function(identifier) {
-               if (this._dropdownMenus[identifier]) {
-                       this._dropdownMenus[identifier].open();
+               /**
+                * Creates a new interactive dropdown instance.
+                *
+                * @param        jQuery                triggerElement
+                * @param        string                identifier
+                * @param        object                options
+                * @return        WCF.Dropdown.Interactive.Instance
+                */
+               create: function (triggerElement, identifier, options) {
+                       if (this._dropdownContainer === null) {
+                               this._dropdownContainer = $('<div class="dropdownMenuContainer" />').appendTo(document.body);
+                               WCF.CloseOverlayHandler.addCallback('WCF.Dropdown.Interactive.Handler', $.proxy(this.closeAll, this));
+                       }
                        
-                       return true;
-               }
+                       var $instance = new WCF.Dropdown.Interactive.Instance(this._dropdownContainer, triggerElement, identifier, options);
+                       this._dropdownMenus[identifier] = $instance;
+                       
+                       return $instance;
+               },
                
-               return false;
-       },
-       
-       /**
-        * Closes an interactive dropdown, returns false if identifier is unknown.
-        * 
-        * @param       string          identifier
-        * @return      boolean
-        */
-       close: function(identifier) {
-               if (this._dropdownMenus[identifier]) {
-                       this._dropdownMenus[identifier].close();
+               /**
+                * Opens an interactive dropdown, returns false if identifier is unknown.
+                *
+                * @param        string                identifier
+                * @return        boolean
+                */
+               open: function (identifier) {
+                       if (this._dropdownMenus[identifier]) {
+                               this._dropdownMenus[identifier].open();
+                               
+                               return true;
+                       }
                        
-                       return true;
-               }
+                       return false;
+               },
                
-               return false;
-       },
+               /**
+                * Closes an interactive dropdown, returns false if identifier is unknown.
+                *
+                * @param        string                identifier
+                * @return        boolean
+                */
+               close: function (identifier) {
+                       if (this._dropdownMenus[identifier]) {
+                               this._dropdownMenus[identifier].close();
+                               
+                               return true;
+                       }
+                       
+                       return false;
+               },
+               
+               /**
+                * Closes all interactive dropdowns.
+                */
+               closeAll: function () {
+                       for (var instance in this._dropdownMenus) {
+                               if (this._dropdownMenus.hasOwnProperty(instance)) {
+                                       this._dropdownMenus[instance].close();
+                               }
+                       }
+               },
+               
+               getOpenDropdown: function () {
+                       for (var instance in this._dropdownMenus) {
+                               if (this._dropdownMenus.hasOwnProperty(instance)) {
+                                       if (this._dropdownMenus[instance].isOpen()) {
+                                               return this._dropdownMenus[instance];
+                                       }
+                               }
+                       }
+                       
+                       return null;
+               },
+               
+               /**
+                * Returns the dropdown with given identifier or `undefined` if no such dropdown exists.
+                *
+                * @param        string                identifier
+                * @return        {WCF.Dropdown.Interactive.Instance?}
+                */
+               getDropdown: function (identifier) {
+                       return this._dropdownMenus[identifier];
+               }
+       };
        
        /**
-        * Closes all interactive dropdowns.
+        * Represents and manages a single interactive dropdown instance.
+        *
+        * @param        jQuery                dropdownContainer
+        * @param        jQuery                triggerElement
+        * @param        string                identifier
+        * @param        object                options
         */
-       closeAll: function() {
-               for (var instance in this._dropdownMenus) {
-                       if (this._dropdownMenus.hasOwnProperty(instance)) {
-                               this._dropdownMenus[instance].close();
+       WCF.Dropdown.Interactive.Instance = Class.extend({
+               /**
+                * dropdown container
+                * @var        jQuery
+                */
+               _container: null,
+               
+               /**
+                * inner item list
+                * @var        jQuery
+                */
+               _itemList: null,
+               
+               /**
+                * header link list
+                * @var        jQuery
+                */
+               _linkList: null,
+               
+               /**
+                * option list
+                * @var        object
+                */
+               _options: {},
+               
+               /**
+                * arrow pointer
+                * @var        jQuery
+                */
+               _pointer: null,
+               
+               /**
+                * trigger element
+                * @var        jQuery
+                */
+               _triggerElement: null,
+               
+               /**
+                * Represents and manages a single interactive dropdown instance.
+                *
+                * @param        jQuery                dropdownContainer
+                * @param        jQuery                triggerElement
+                * @param        string                identifier
+                * @param        object                options
+                */
+               init: function (dropdownContainer, triggerElement, identifier, options) {
+                       this._options = options || {};
+                       this._triggerElement = triggerElement;
+                       
+                       var $itemContainer = null;
+                       if (options.staticDropdown === true) {
+                               this._container = this._triggerElement.find('.interactiveDropdownStatic:eq(0)').data('source', identifier).click(function (event) {
+                                       event.stopPropagation();
+                               });
                        }
-               }
-       },
-       
-       getOpenDropdown: function () {
-               for (var instance in this._dropdownMenus) {
-                       if (this._dropdownMenus.hasOwnProperty(instance)) {
-                               if (this._dropdownMenus[instance].isOpen()) {
-                                       return this._dropdownMenus[instance];
+                       else {
+                               this._container = $('<div class="interactiveDropdown" data-source="' + identifier + '" />').click(function (event) {
+                                       event.stopPropagation();
+                               });
+                               
+                               var $header = $('<div class="interactiveDropdownHeader" />').appendTo(this._container);
+                               $('<span class="interactiveDropdownTitle">' + options.title + '</span>').appendTo($header);
+                               this._linkList = $('<ul class="interactiveDropdownLinks inlineList"></ul>').appendTo($header);
+                               
+                               $itemContainer = $('<div class="interactiveDropdownItemsContainer" />').appendTo(this._container);
+                               this._itemList = $('<ul class="interactiveDropdownItems" />').appendTo($itemContainer);
+                               
+                               $('<a href="' + options.showAllLink + '" class="interactiveDropdownShowAll">' + WCF.Language.get('wcf.user.panel.showAll') + '</a>').appendTo(this._container);
+                       }
+                       
+                       this._pointer = $('<span class="elementPointer"><span /></span>').appendTo(this._container);
+                       
+                       require(['Environment'], (function (Environment) {
+                               if (Environment.platform() === 'desktop') {
+                                       if ($itemContainer !== null) {
+                                               // use jQuery scrollbar on desktop, mobile browsers have a similar display built-in
+                                               $itemContainer.perfectScrollbar({
+                                                       suppressScrollX: true
+                                               });
+                                       }
                                }
+                       }).bind(this));
+                       
+                       this._container.appendTo(dropdownContainer);
+               },
+               
+               /**
+                * Returns the dropdown container.
+                *
+                * @return        jQuery
+                */
+               getContainer: function () {
+                       return this._container;
+               },
+               
+               /**
+                * Returns the inner item list.
+                *
+                * @return        jQuery
+                */
+               getItemList: function () {
+                       return this._itemList;
+               },
+               
+               /**
+                * Returns the header link list.
+                *
+                * @return        jQuery
+                */
+               getLinkList: function () {
+                       return this._linkList;
+               },
+               
+               /**
+                * Opens the dropdown.
+                */
+               open: function () {
+                       WCF.Dropdown._closeAll();
+                       
+                       this._triggerElement.addClass('open');
+                       this._container.addClass('open');
+                       
+                       WCF.System.Event.fireEvent('com.woltlab.wcf.Search', 'close');
+                       
+                       this.render();
+               },
+               
+               /**
+                * Closes the dropdown
+                */
+               close: function () {
+                       this._triggerElement.removeClass('open');
+                       this._container.removeClass('open');
+               },
+               
+               /**
+                * Returns true if dropdown instance is visible.
+                *
+                * @returns     {boolean}
+                */
+               isOpen: function () {
+                       return this._triggerElement.hasClass('open');
+               },
+               
+               /**
+                * Toggles the dropdown state, returns true if dropdown is open afterwards, else false.
+                *
+                * @return        boolean
+                */
+               toggle: function () {
+                       if (this._container.hasClass('open')) {
+                               this.close();
+                               
+                               return false;
                        }
-               }
+                       else {
+                               WCF.Dropdown.Interactive.Handler.closeAll();
+                               
+                               this.open();
+                               
+                               return true;
+                       }
+               },
                
-               return null;
-       },
+               /**
+                * Resets the inner item list and closes the dropdown.
+                */
+               resetItems: function () {
+                       this._itemList.empty();
+                       
+                       this.close();
+               },
+               
+               /**
+                * Renders the dropdown.
+                */
+               render: function () {
+                       require(['Ui/Alignment', 'Ui/Screen'], (function (UiAlignment, UiScreen) {
+                               if (UiScreen.is('screen-lg')) {
+                                       UiAlignment.set(this._container[0], this._triggerElement[0], {
+                                               horizontal: 'right',
+                                               pointer: true
+                                       });
+                               }
+                               else {
+                                       this._container.css({
+                                               bottom: '',
+                                               left: '',
+                                               right: '',
+                                               top: elById('pageHeaderPanel').clientHeight + 'px'
+                                       });
+                               }
+                       }).bind(this));
+               },
+               
+               /**
+                * Rebuilds the desktop scrollbar.
+                */
+               rebuildScrollbar: function () {
+                       require(['Environment'], function (Environment) {
+                               if (Environment.platform() === 'desktop') {
+                                       var $itemContainer = this._itemList.parent();
+                                       
+                                       // do NOT use 'update', seems to be broken
+                                       $itemContainer.perfectScrollbar('destroy');
+                                       $itemContainer.perfectScrollbar({
+                                               suppressScrollX: true
+                                       });
+                               }
+                       }.bind(this));
+               }
+       });
        
        /**
-        * Returns the dropdown with given identifier or `undefined` if no such dropdown exists.
+        * Clipboard API
         * 
-        * @param       string          identifier
-        * @return      {WCF.Dropdown.Interactive.Instance?}
+        * @deprecated  3.0 - please use `WoltLabSuite/Core/Controller/Clipboard` instead
         */
-       getDropdown: function(identifier) {
-               return this._dropdownMenus[identifier];
-       }
-};
+       WCF.Clipboard = {
+               /**
+                * Initializes the clipboard API.
+                * 
+                * @param       string          page
+                * @param       integer         hasMarkedItems
+                * @param       object          actionObjects
+                * @param       integer         pageObjectID
+                */
+               init: function(page, hasMarkedItems, actionObjects, pageObjectID) {
+                       require(['EventHandler', 'WoltLabSuite/Core/Controller/Clipboard'], function(EventHandler, ControllerClipboard) {
+                               ControllerClipboard.setup({
+                                       hasMarkedItems: (hasMarkedItems > 0),
+                                       pageClassName: page,
+                                       pageObjectId: pageObjectID
+                               });
+                               
+                               for (var type in actionObjects) {
+                                       if (actionObjects.hasOwnProperty(type)) {
+                                               (function (type) {
+                                                       EventHandler.add('com.woltlab.wcf.clipboard', type, function (data) {
+                                                               // only consider events if the action has been executed
+                                                               if (data.responseData === null) {
+                                                                       return;
+                                                               }
+                                                               
+                                                               if (actionObjects[type].hasOwnProperty(data.responseData.actionName)) {
+                                                                       actionObjects[type][data.responseData.actionName].triggerEffect(data.responseData.objectIDs);
+                                                               }
+                                                       });
+                                               })(type);
+                                       }
+                               }
+                       });
+               },
+               
+               /**
+                * Reloads the list of marked items.
+                */
+               reload: function() {
+                       require(['WoltLabSuite/Core/Controller/Clipboard'], function(ControllerClipboard) {
+                               ControllerClipboard.reload();
+                       });
+               }
+       };
+}
+else {
+       WCF.Dropdown.Interactive.Handler = {
+               _dropdownContainer: {},
+               _dropdownMenus: {},
+               create: function() {},
+               open: function() {},
+               close: function() {},
+               closeAll: function() {},
+               getOpenDropdown: function() {},
+               getDropdown: function() {}
+       };
+       
+       WCF.Dropdown.Interactive.Instance = Class.extend({
+               _container: {},
+               _itemList: {},
+               _linkList: {},
+               _options: {},
+               _pointer: {},
+               _triggerElement: {},
+               init: function() {},
+               getContainer: function() {},
+               getItemList: function() {},
+               getLinkList: function() {},
+               open: function() {},
+               close: function() {},
+               isOpen: function() {},
+               toggle: function() {},
+               resetItems: function() {},
+               render: function() {},
+               rebuildScrollbar: function() {}
+       });
+       
+       WCF.Clipboard = {
+               init: function() {},
+               reload: function() {}
+       };
+}
 
 /**
- * Represents and manages a single interactive dropdown instance.
- * 
- * @param      jQuery          dropdownContainer
- * @param      jQuery          triggerElement
- * @param      string          identifier
- * @param      object          options
+ * @deprecated Use WoltLabSuite/Core/Timer/Repeating
  */
-WCF.Dropdown.Interactive.Instance = Class.extend({
+WCF.PeriodicalExecuter = Class.extend({
        /**
-        * dropdown container
-        * @var jQuery
+        * callback for each execution cycle
+        * @var object
         */
-       _container: null,
+       _callback: null,
        
        /**
-        * inner item list
-        * @var jQuery
+        * interval
+        * @var integer
         */
-       _itemList: null,
+       _delay: 0,
        
        /**
-        * header link list
-        * @var jQuery
+        * interval id
+        * @var integer
         */
-       _linkList: null,
+       _intervalID: null,
        
        /**
-        * option list
-        * @var object
+        * execution state
+        * @var boolean
         */
-       _options: { },
+       _isExecuting: false,
        
        /**
-        * arrow pointer
-        * @var jQuery
+        * Initializes a periodical executer.
+        * 
+        * @param       function                callback
+        * @param       integer                 delay
         */
-       _pointer: null,
+       init: function(callback, delay) {
+               if (!$.isFunction(callback)) {
+                       console.debug('[WCF.PeriodicalExecuter] Given callback is invalid, aborting.');
+                       return;
+               }
+               
+               this._callback = callback;
+               this._interval = delay;
+               this.resume();
+       },
        
        /**
-        * trigger element
-        * @var jQuery
+        * Executes callback.
         */
-       _triggerElement: null,
-       
-       /**
-        * Represents and manages a single interactive dropdown instance.
-        * 
-        * @param       jQuery          dropdownContainer
-        * @param       jQuery          triggerElement
-        * @param       string          identifier
-        * @param       object          options
-        */
-       init: function(dropdownContainer, triggerElement, identifier, options) {
-               this._options = options || { };
-               this._triggerElement = triggerElement;
-               
-               var $itemContainer = null;
-               if (options.staticDropdown === true) {
-                       this._container = this._triggerElement.find('.interactiveDropdownStatic:eq(0)').data('source', identifier).click(function(event) { event.stopPropagation(); });
-               }
-               else {
-                       this._container = $('<div class="interactiveDropdown" data-source="' + identifier + '" />').click(function(event) { event.stopPropagation(); });
-                       
-                       var $header = $('<div class="interactiveDropdownHeader" />').appendTo(this._container);
-                       $('<span class="interactiveDropdownTitle">' + options.title + '</span>').appendTo($header);
-                       this._linkList = $('<ul class="interactiveDropdownLinks inlineList"></ul>').appendTo($header);
-                       
-                       $itemContainer = $('<div class="interactiveDropdownItemsContainer" />').appendTo(this._container);
-                       this._itemList = $('<ul class="interactiveDropdownItems" />').appendTo($itemContainer);
-                       
-                       $('<a href="' + options.showAllLink + '" class="interactiveDropdownShowAll">' + WCF.Language.get('wcf.user.panel.showAll') + '</a>').appendTo(this._container);
-               }
-               
-               this._pointer = $('<span class="elementPointer"><span /></span>').appendTo(this._container);
-               
-               require(['Environment'], (function(Environment) {
-                       if (Environment.platform() === 'desktop') {
-                               if ($itemContainer !== null) {
-                                       // use jQuery scrollbar on desktop, mobile browsers have a similar display built-in
-                                       $itemContainer.perfectScrollbar({
-                                               suppressScrollX: true
-                                       });
-                               }
-                       }
-               }).bind(this));
-               
-               this._container.appendTo(dropdownContainer);
-       },
-       
-       /**
-        * Returns the dropdown container.
-        * 
-        * @return      jQuery
-        */
-       getContainer: function() {
-               return this._container;
-       },
-       
-       /**
-        * Returns the inner item list.
-        * 
-        * @return      jQuery
-        */
-       getItemList: function() {
-               return this._itemList;
-       },
-       
-       /**
-        * Returns the header link list.
-        * 
-        * @return      jQuery
-        */
-       getLinkList: function() {
-               return this._linkList;
-       },
-       
-       /**
-        * Opens the dropdown.
-        */
-       open: function() {
-               WCF.Dropdown._closeAll();
-               
-               this._triggerElement.addClass('open');
-               this._container.addClass('open');
-               
-               WCF.System.Event.fireEvent('com.woltlab.wcf.Search', 'close');
-               
-               this.render();
-       },
-       
-       /**
-        * Closes the dropdown
-        */
-       close: function() {
-               this._triggerElement.removeClass('open');
-               this._container.removeClass('open');
-       },
-       
-       /**
-        * Returns true if dropdown instance is visible.
-        * 
-        * @returns     {boolean}
-        */
-       isOpen: function() {
-               return this._triggerElement.hasClass('open');
-       },
-       
-       /**
-        * Toggles the dropdown state, returns true if dropdown is open afterwards, else false.
-        * 
-        * @return      boolean
-        */
-       toggle: function() {
-               if (this._container.hasClass('open')) {
-                       this.close();
-                       
-                       return false;
-               }
-               else {
-                       WCF.Dropdown.Interactive.Handler.closeAll();
-                       
-                       this.open();
-                       
-                       return true;
-               }
-       },
-       
-       /**
-        * Resets the inner item list and closes the dropdown.
-        */
-       resetItems: function() {
-               this._itemList.empty();
-               
-               this.close();
-       },
-       
-       /**
-        * Renders the dropdown.
-        */
-       render: function() {
-               require(['Ui/Alignment', 'Ui/Screen'], (function (UiAlignment, UiScreen) {
-                       if (UiScreen.is('screen-lg')) {
-                               UiAlignment.set(this._container[0], this._triggerElement[0], {
-                                       horizontal: 'right',
-                                       pointer: true
-                               });
-                       }
-                       else {
-                               this._container.css({
-                                       bottom: '',
-                                       left: '',
-                                       right: '',
-                                       top: elById('pageHeaderPanel').clientHeight + 'px'
-                               });
-                       }
-               }).bind(this));
-       },
-       
-       /**
-        * Rebuilds the desktop scrollbar.
-        */
-       rebuildScrollbar: function() {
-               require(['Environment'], function(Environment) {
-                       if (Environment.platform() === 'desktop') {
-                               var $itemContainer = this._itemList.parent();
-                               
-                               // do NOT use 'update', seems to be broken
-                               $itemContainer.perfectScrollbar('destroy');
-                               $itemContainer.perfectScrollbar({
-                                       suppressScrollX: true
-                               });
-                       }
-               }.bind(this));
-       }
-});
-
-/**
- * Clipboard API
- * 
- * @deprecated 3.0 - please use `WoltLabSuite/Core/Controller/Clipboard` instead
- */
-WCF.Clipboard = {
-       /**
-        * Initializes the clipboard API.
-        * 
-        * @param       string          page
-        * @param       integer         hasMarkedItems
-        * @param       object          actionObjects
-        * @param       integer         pageObjectID
-        */
-       init: function(page, hasMarkedItems, actionObjects, pageObjectID) {
-               require(['EventHandler', 'WoltLabSuite/Core/Controller/Clipboard'], function(EventHandler, ControllerClipboard) {
-                       ControllerClipboard.setup({
-                               hasMarkedItems: (hasMarkedItems > 0),
-                               pageClassName: page,
-                               pageObjectId: pageObjectID
-                       });
-                       
-                       for (var type in actionObjects) {
-                               if (actionObjects.hasOwnProperty(type)) {
-                                       (function (type) {
-                                               EventHandler.add('com.woltlab.wcf.clipboard', type, function (data) {
-                                                       // only consider events if the action has been executed
-                                                       if (data.responseData === null) {
-                                                               return;
-                                                       }
-                                                       
-                                                       if (actionObjects[type].hasOwnProperty(data.responseData.actionName)) {
-                                                               actionObjects[type][data.responseData.actionName].triggerEffect(data.responseData.objectIDs);
-                                                       }
-                                               });
-                                       })(type);
-                               }
-                       }
-               });
-       },
-       
-       /**
-        * Reloads the list of marked items.
-        */
-       reload: function() {
-               require(['WoltLabSuite/Core/Controller/Clipboard'], function(ControllerClipboard) {
-                       ControllerClipboard.reload();
-               });
-       }
-};
-
-/**
- * @deprecated Use WoltLabSuite/Core/Timer/Repeating
- */
-WCF.PeriodicalExecuter = Class.extend({
-       /**
-        * callback for each execution cycle
-        * @var object
-        */
-       _callback: null,
-       
-       /**
-        * interval
-        * @var integer
-        */
-       _delay: 0,
-       
-       /**
-        * interval id
-        * @var integer
-        */
-       _intervalID: null,
-       
-       /**
-        * execution state
-        * @var boolean
-        */
-       _isExecuting: false,
-       
-       /**
-        * Initializes a periodical executer.
-        * 
-        * @param       function                callback
-        * @param       integer                 delay
-        */
-       init: function(callback, delay) {
-               if (!$.isFunction(callback)) {
-                       console.debug('[WCF.PeriodicalExecuter] Given callback is invalid, aborting.');
-                       return;
-               }
-               
-               this._callback = callback;
-               this._interval = delay;
-               this.resume();
-       },
-       
-       /**
-        * Executes callback.
-        */
-       _execute: function() {
-               if (!this._isExecuting) {
-                       try {
-                               this._isExecuting = true;
-                               this._callback(this);
-                               this._isExecuting = false;
-                       }
-                       catch (e) {
-                               this._isExecuting = false;
-                               throw e;
-                       }
-               }
-       },
+       _execute: function() {
+               if (!this._isExecuting) {
+                       try {
+                               this._isExecuting = true;
+                               this._callback(this);
+                               this._isExecuting = false;
+                       }
+                       catch (e) {
+                               this._isExecuting = false;
+                               throw e;
+                       }
+               }
+       },
        
        /**
         * Terminates loop.
@@ -1674,481 +1717,533 @@ WCF.Action.SimpleProxy = Class.extend({
        }
 });
 
-/**
- * Basic implementation for AJAXProxy-based deletion.
- * 
- * @param      string          className
- * @param      string          containerSelector
- * @param      string          buttonSelector
- */
-WCF.Action.Delete = Class.extend({
-       /**
-        * delete button selector
-        * @var string
-        */
-       _buttonSelector: '',
-       
-       /**
-        * callback function called prior to triggering the delete effect
-        * @var function
-        */
-       _callback: null,
-       
-       /**
-        * action class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * container selector
-        * @var string
-        */
-       _containerSelector: '',
-       
-       /**
-        * list of known container ids
-        * @var array<string>
-        */
-       _containers: [ ],
-       
+if (COMPILER_TARGET_DEFAULT) {
        /**
-        * Initializes 'delete'-Proxy.
-        * 
-        * @param       string          className
-        * @param       string          containerSelector
-        * @param       string          buttonSelector
+        * Basic implementation for AJAXProxy-based deletion.
+        *
+        * @param        string                className
+        * @param        string                containerSelector
+        * @param        string                buttonSelector
         */
-       init: function(className, containerSelector, buttonSelector) {
-               this._containerSelector = containerSelector;
-               this._className = className;
-               this._buttonSelector = (buttonSelector) ? buttonSelector : '.jsDeleteButton';
-               this._callback = null;
+       WCF.Action.Delete = Class.extend({
+               /**
+                * delete button selector
+                * @var        string
+                */
+               _buttonSelector: '',
                
-               this.proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
+               /**
+                * callback function called prior to triggering the delete effect
+                * @var        function
+                */
+               _callback: null,
                
-               this._initElements();
+               /**
+                * action class name
+                * @var        string
+                */
+               _className: '',
                
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.Action.Delete' + this._className.hashCode(), $.proxy(this._initElements, this));
-       },
-       
-       /**
-        * Initializes available element containers.
-        */
-       _initElements: function() {
-               $(this._containerSelector).each((function(index, container) {
-                       var $container = $(container);
-                       var $containerID = $container.wcfIdentify();
+               /**
+                * container selector
+                * @var        string
+                */
+               _containerSelector: '',
+               
+               /**
+                * list of known container ids
+                * @var        array<string>
+                */
+               _containers: [],
+               
+               /**
+                * Initializes 'delete'-Proxy.
+                *
+                * @param        string                className
+                * @param        string                containerSelector
+                * @param        string                buttonSelector
+                */
+               init: function (className, containerSelector, buttonSelector) {
+                       this._containerSelector = containerSelector;
+                       this._className = className;
+                       this._buttonSelector = (buttonSelector) ? buttonSelector : '.jsDeleteButton';
+                       this._callback = null;
                        
-                       if (!WCF.inArray($containerID, this._containers)) {
-                               var $deleteButton = $container.find(this._buttonSelector);
+                       this.proxy = new WCF.Action.Proxy({
+                               success: $.proxy(this._success, this)
+                       });
+                       
+                       this._initElements();
+                       
+                       WCF.DOMNodeInsertedHandler.addCallback('WCF.Action.Delete' + this._className.hashCode(), $.proxy(this._initElements, this));
+               },
+               
+               /**
+                * Initializes available element containers.
+                */
+               _initElements: function () {
+                       $(this._containerSelector).each((function (index, container) {
+                               var $container = $(container);
+                               var $containerID = $container.wcfIdentify();
                                
-                               if ($deleteButton.length) {
-                                       this._containers.push($containerID);
-                                       $deleteButton.click($.proxy(this._click, this));
+                               if (!WCF.inArray($containerID, this._containers)) {
+                                       var $deleteButton = $container.find(this._buttonSelector);
+                                       
+                                       if ($deleteButton.length) {
+                                               this._containers.push($containerID);
+                                               $deleteButton.click($.proxy(this._click, this));
+                                       }
                                }
+                       }).bind(this));
+               },
+               
+               /**
+                * Sends AJAX request.
+                *
+                * @param        object                event
+                */
+               _click: function (event) {
+                       var $target = $(event.currentTarget);
+                       event.preventDefault();
+                       
+                       if ($target.data('confirmMessageHtml') || $target.data('confirmMessage')) {
+                               WCF.System.Confirmation.show($target.data('confirmMessageHtml') ? $target.data('confirmMessageHtml') : $target.data('confirmMessage'), $.proxy(this._execute, this), {target: $target}, undefined, $target.data('confirmMessageHtml') ? true : false);
                        }
-               }).bind(this));
-       },
-       
-       /**
-        * Sends AJAX request.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               var $target = $(event.currentTarget);
-               event.preventDefault();
+                       else {
+                               WCF.LoadingOverlayHandler.updateIcon($target);
+                               this._sendRequest($target);
+                       }
+               },
                
-               if ($target.data('confirmMessageHtml') || $target.data('confirmMessage')) {
-                       WCF.System.Confirmation.show($target.data('confirmMessageHtml') ? $target.data('confirmMessageHtml') : $target.data('confirmMessage'), $.proxy(this._execute, this), { target: $target }, undefined, $target.data('confirmMessageHtml') ? true : false);
-               }
-               else {
-                       WCF.LoadingOverlayHandler.updateIcon($target);
-                       this._sendRequest($target);
-               }
-       },
-       
-       /**
-        * Is called if the delete effect has been triggered on the given element.
-        * 
-        * @param       jQuery          element
-        */
-       _didTriggerEffect: function(element) {
-               // does nothing
-       },
-       
-       /**
-        * Executes deletion.
-        * 
-        * @param       string          action
-        * @param       object          parameters
-        */
-       _execute: function(action, parameters) {
-               if (action === 'cancel') {
-                       return;
-               }
+               /**
+                * Is called if the delete effect has been triggered on the given element.
+                *
+                * @param        jQuery                element
+                */
+               _didTriggerEffect: function (element) {
+                       // does nothing
+               },
                
-               WCF.LoadingOverlayHandler.updateIcon(parameters.target);
-               this._sendRequest(parameters.target);
-       },
-       
-       /**
-        * Sends the request
-        * 
-        * @param       jQuery  object
-        */
-       _sendRequest: function(object) {
-               this.proxy.setOption('data', {
-                       actionName: 'delete',
-                       className: this._className,
-                       interfaceName: 'wcf\\data\\IDeleteAction',
-                       objectIDs: [ $(object).data('objectID') ]
-               });
+               /**
+                * Executes deletion.
+                *
+                * @param        string                action
+                * @param        object                parameters
+                */
+               _execute: function (action, parameters) {
+                       if (action === 'cancel') {
+                               return;
+                       }
+                       
+                       WCF.LoadingOverlayHandler.updateIcon(parameters.target);
+                       this._sendRequest(parameters.target);
+               },
                
-               this.proxy.sendRequest();
-       },
-       
-       /**
-        * Deletes items from containers.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       object          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               if (this._callback) {
-                       this._callback(data.objectIDs);
-               }
+               /**
+                * Sends the request
+                *
+                * @param        jQuery        object
+                */
+               _sendRequest: function (object) {
+                       this.proxy.setOption('data', {
+                               actionName: 'delete',
+                               className: this._className,
+                               interfaceName: 'wcf\\data\\IDeleteAction',
+                               objectIDs: [$(object).data('objectID')]
+                       });
+                       
+                       this.proxy.sendRequest();
+               },
                
-               this.triggerEffect(data.objectIDs);
-       },
-       
-       /**
-        * Sets a callback function called prior to triggering the delete effect.
-        * 
-        * @param       {function}      callback
-        */
-       setCallback: function(callback) {
-               if (typeof callback !== 'function') {
-                       throw new TypeError("[WCF.Action.Delete] Expected a valid callback for '" + this._className + "'.");
-               }
+               /**
+                * Deletes items from containers.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        object                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       if (this._callback) {
+                               this._callback(data.objectIDs);
+                       }
+                       
+                       this.triggerEffect(data.objectIDs);
+               },
                
-               this._callback = callback;
-       },
+               /**
+                * Sets a callback function called prior to triggering the delete effect.
+                *
+                * @param        {function}        callback
+                */
+               setCallback: function (callback) {
+                       if (typeof callback !== 'function') {
+                               throw new TypeError("[WCF.Action.Delete] Expected a valid callback for '" + this._className + "'.");
+                       }
+                       
+                       this._callback = callback;
+               },
+               
+               /**
+                * Triggers the delete effect for the objects with the given ids.
+                *
+                * @param        array                objectIDs
+                */
+               triggerEffect: function (objectIDs) {
+                       for (var $index in this._containers) {
+                               var $container = $('#' + this._containers[$index]);
+                               var $button = $container.find(this._buttonSelector);
+                               if (WCF.inArray($button.data('objectID'), objectIDs)) {
+                                       var self = this;
+                                       $container.wcfBlindOut('up', function () {
+                                               var $container = $(this).remove();
+                                               self._containers.splice(self._containers.indexOf($container.wcfIdentify()), 1);
+                                               self._didTriggerEffect($container);
+                                               
+                                               if ($button.data('eventName')) {
+                                                       WCF.System.Event.fireEvent('com.woltlab.wcf.action.delete', $button.data('eventName'), {
+                                                               button: $button,
+                                                               container: $container
+                                                       });
+                                               }
+                                       });
+                               }
+                       }
+               }
+       });
        
        /**
-        * Triggers the delete effect for the objects with the given ids.
-        * 
-        * @param       array           objectIDs
+        * Basic implementation for deletion of nested elements.
+        *
+        * The implementation requires the nested elements to be grouped as numbered lists
+        * (ol lists). The child elements of the deleted elements are moved to the parent
+        * element of the deleted element.
+        *
+        * @see        WCF.Action.Delete
         */
-       triggerEffect: function(objectIDs) {
-               for (var $index in this._containers) {
-                       var $container = $('#' + this._containers[$index]);
-                       var $button = $container.find(this._buttonSelector);
-                       if (WCF.inArray($button.data('objectID'), objectIDs)) {
-                               var self = this;
-                               $container.wcfBlindOut('up',function() {
-                                       var $container = $(this).remove();
-                                       self._containers.splice(self._containers.indexOf($container.wcfIdentify()), 1);
-                                       self._didTriggerEffect($container);
-                                       
-                                       if ($button.data('eventName')) {
-                                               WCF.System.Event.fireEvent('com.woltlab.wcf.action.delete', $button.data('eventName'), {
-                                                       button: $button,
-                                                       container: $container
+       WCF.Action.NestedDelete = WCF.Action.Delete.extend({
+               /**
+                * @see        WCF.Action.Delete.triggerEffect()
+                */
+               triggerEffect: function (objectIDs) {
+                       for (var $index in this._containers) {
+                               var $container = $('#' + this._containers[$index]);
+                               if (WCF.inArray($container.find(this._buttonSelector).data('objectID'), objectIDs)) {
+                                       // move children up
+                                       if ($container.has('ol').has('li').length) {
+                                               if ($container.is(':only-child')) {
+                                                       $container.parent().replaceWith($container.find('> ol'));
+                                               }
+                                               else {
+                                                       $container.replaceWith($container.find('> ol > li'));
+                                               }
+                                               
+                                               this._containers.splice(this._containers.indexOf($container.wcfIdentify()), 1);
+                                               this._didTriggerEffect($container);
+                                       }
+                                       else {
+                                               var self = this;
+                                               $container.wcfBlindOut('up', function () {
+                                                       $(this).remove();
+                                                       self._containers.splice(self._containers.indexOf($(this).wcfIdentify()), 1);
+                                                       self._didTriggerEffect($(this));
                                                });
                                        }
-                               });
+                               }
                        }
                }
-       }
-});
-
-/**
- * Basic implementation for deletion of nested elements.
- * 
- * The implementation requires the nested elements to be grouped as numbered lists
- * (ol lists). The child elements of the deleted elements are moved to the parent
- * element of the deleted element.
- * 
- * @see        WCF.Action.Delete
- */
-WCF.Action.NestedDelete = WCF.Action.Delete.extend({
-       /**
-        * @see WCF.Action.Delete.triggerEffect()
-        */
-       triggerEffect: function(objectIDs) {
-               for (var $index in this._containers) {
-                       var $container = $('#' + this._containers[$index]);
-                       if (WCF.inArray($container.find(this._buttonSelector).data('objectID'), objectIDs)) {
-                               // move children up
-                               if ($container.has('ol').has('li').length) {
-                                       if ($container.is(':only-child')) {
-                                               $container.parent().replaceWith($container.find('> ol'));
-                                       }
-                                       else {
-                                               $container.replaceWith($container.find('> ol > li'));
-                                       }
-                                       
-                                       this._containers.splice(this._containers.indexOf($container.wcfIdentify()), 1);
-                                       this._didTriggerEffect($container);
+       });
+       
+       /**
+        * Basic implementation for AJAXProxy-based toggle actions.
+        *
+        * @param        string                className
+        * @param        jQuery                containerList
+        * @param        string                buttonSelector
+        */
+       WCF.Action.Toggle = Class.extend({
+               /**
+                * toogle button selector
+                * @var        string
+                */
+               _buttonSelector: '.jsToggleButton',
+               
+               /**
+                * action class name
+                * @var        string
+                */
+               _className: '',
+               
+               /**
+                * container selector
+                * @var        string
+                */
+               _containerSelector: '',
+               
+               /**
+                * list of known container ids
+                * @var        array<string>
+                */
+               _containers: [],
+               
+               /**
+                * Initializes 'toggle'-Proxy
+                *
+                * @param        string                className
+                * @param        string                containerSelector
+                * @param        string                buttonSelector
+                */
+               init: function (className, containerSelector, buttonSelector) {
+                       this._containerSelector = containerSelector;
+                       this._className = className;
+                       this._buttonSelector = (buttonSelector) ? buttonSelector : '.jsToggleButton';
+                       this._containers = [];
+                       
+                       // initialize proxy
+                       var options = {
+                               success: $.proxy(this._success, this)
+                       };
+                       this.proxy = new WCF.Action.Proxy(options);
+                       
+                       // bind event listener
+                       this._initElements();
+                       WCF.DOMNodeInsertedHandler.addCallback('WCF.Action.Toggle' + this._className.hashCode(), $.proxy(this._initElements, this));
+               },
+               
+               /**
+                * Initializes available element containers.
+                */
+               _initElements: function () {
+                       $(this._containerSelector).each($.proxy(function (index, container) {
+                               var $container = $(container);
+                               var $containerID = $container.wcfIdentify();
+                               
+                               if (!WCF.inArray($containerID, this._containers)) {
+                                       this._containers.push($containerID);
+                                       $container.find(this._buttonSelector).click($.proxy(this._click, this));
                                }
-                               else {
-                                       var self = this;
-                                       $container.wcfBlindOut('up', function() {
-                                               $(this).remove();
-                                               self._containers.splice(self._containers.indexOf($(this).wcfIdentify()), 1);
-                                               self._didTriggerEffect($(this));
-                                       });
+                       }, this));
+               },
+               
+               /**
+                * Sends AJAX request.
+                *
+                * @param        object                event
+                */
+               _click: function (event) {
+                       var $target = $(event.currentTarget);
+                       event.preventDefault();
+                       
+                       if ($target.data('confirmMessageHtml') || $target.data('confirmMessage')) {
+                               WCF.System.Confirmation.show($target.data('confirmMessageHtml') ? $target.data('confirmMessageHtml') : $target.data('confirmMessage'), $.proxy(this._execute, this), {target: $target}, undefined, $target.data('confirmMessageHtml') ? true : false);
+                       }
+                       else {
+                               WCF.LoadingOverlayHandler.updateIcon($target);
+                               this._sendRequest($target);
+                       }
+               },
+               
+               /**
+                * Executes toggeling.
+                *
+                * @param        string                action
+                * @param        object                parameters
+                */
+               _execute: function (action, parameters) {
+                       if (action === 'cancel') {
+                               return;
+                       }
+                       
+                       WCF.LoadingOverlayHandler.updateIcon(parameters.target);
+                       this._sendRequest(parameters.target);
+               },
+               
+               _sendRequest: function (object) {
+                       this.proxy.setOption('data', {
+                               actionName: 'toggle',
+                               className: this._className,
+                               interfaceName: 'wcf\\data\\IToggleAction',
+                               objectIDs: [$(object).data('objectID')]
+                       });
+                       
+                       this.proxy.sendRequest();
+               },
+               
+               /**
+                * Toggles status icons.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        object                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       this.triggerEffect(data.objectIDs);
+               },
+               
+               /**
+                * Triggers the toggle effect for the objects with the given ids.
+                *
+                * @param        array                objectIDs
+                */
+               triggerEffect: function (objectIDs) {
+                       for (var $index in this._containers) {
+                               var $container = $('#' + this._containers[$index]);
+                               var $toggleButton = $container.find(this._buttonSelector);
+                               if (WCF.inArray($toggleButton.data('objectID'), objectIDs)) {
+                                       $container.wcfHighlight();
+                                       this._toggleButton($container, $toggleButton);
                                }
                        }
+               },
+               
+               /**
+                * Tiggers the toggle effect on a button
+                *
+                * @param        jQuery        $container
+                * @param        jQuery        $toggleButton
+                */
+               _toggleButton: function ($container, $toggleButton) {
+                       var $newTitle = '';
+                       
+                       // toggle icon source
+                       WCF.LoadingOverlayHandler.updateIcon($toggleButton, false);
+                       if ($toggleButton.hasClass('fa-square-o')) {
+                               $toggleButton.removeClass('fa-square-o').addClass('fa-check-square-o');
+                               $newTitle = ($toggleButton.data('disableTitle') ? $toggleButton.data('disableTitle') : WCF.Language.get('wcf.global.button.disable'));
+                               $toggleButton.attr('title', $newTitle);
+                       }
+                       else {
+                               $toggleButton.removeClass('fa-check-square-o').addClass('fa-square-o');
+                               $newTitle = ($toggleButton.data('enableTitle') ? $toggleButton.data('enableTitle') : WCF.Language.get('wcf.global.button.enable'));
+                               $toggleButton.attr('title', $newTitle);
+                       }
+                       
+                       // toggle css class
+                       $container.toggleClass('disabled');
                }
-       }
-});
+       });
+}
+else {
+       WCF.Action.Delete = Class.extend({
+               _buttonSelector: "",
+               _callback: {},
+               _className: "",
+               _containerSelector: "",
+               _containers: {},
+               init: function() {},
+               _initElements: function() {},
+               _click: function() {},
+               _didTriggerEffect: function() {},
+               _execute: function() {},
+               _sendRequest: function() {},
+               _success: function() {},
+               setCallback: function() {},
+               triggerEffect: function() {}
+       });
+       
+       WCF.Action.NestedDelete = WCF.Action.Delete.extend({
+               triggerEffect: function() {},
+               _buttonSelector: "",
+               _callback: {},
+               _className: "",
+               _containerSelector: "",
+               _containers: {},
+               init: function() {},
+               _initElements: function() {},
+               _click: function() {},
+               _didTriggerEffect: function() {},
+               _execute: function() {},
+               _sendRequest: function() {},
+               _success: function() {},
+               setCallback: function() {}
+       });
+       
+       WCF.Action.Toggle = Class.extend({
+               _buttonSelector: "",
+               _className: "",
+               _containerSelector: "",
+               _containers: {},
+               init: function() {},
+               _initElements: function() {},
+               _click: function() {},
+               _execute: function() {},
+               _sendRequest: function() {},
+               _success: function() {},
+               triggerEffect: function() {},
+               _toggleButton: function() {}
+       });
+}
 
 /**
- * Basic implementation for AJAXProxy-based toggle actions.
+ * Executes provided callback if scroll threshold is reached. Usuable to determine
+ * if user reached the bottom of an element to load new elements on the fly.
  * 
- * @param      string          className
- * @param      jQuery          containerList
- * @param      string          buttonSelector
+ * If you do not provide a value for 'reference' and 'target' it will assume you're
+ * monitoring page scrolls, otherwise a valid jQuery selector must be provided for both.
+ * 
+ * @param      integer         threshold
+ * @param      object          callback
+ * @param      string          reference
+ * @param      string          target
  */
-WCF.Action.Toggle = Class.extend({
+WCF.Action.Scroll = Class.extend({
        /**
-        * toogle button selector
-        * @var string
+        * callback used once threshold is reached
+        * @var object
         */
-       _buttonSelector: '.jsToggleButton',
+       _callback: null,
        
        /**
-        * action class name
-        * @var string
+        * reference object
+        * @var jQuery
         */
-       _className: '',
+       _reference: null,
        
        /**
-        * container selector
-        * @var string
+        * target object
+        * @var jQuery
         */
-       _containerSelector: '',
+       _target: null,
        
        /**
-        * list of known container ids
-        * @var array<string>
+        * threshold value
+        * @var integer
         */
-       _containers: [ ],
+       _threshold: 0,
        
        /**
-        * Initializes 'toggle'-Proxy
+        * Initializes a new WCF.Action.Scroll object.
         * 
-        * @param       string          className
-        * @param       string          containerSelector
-        * @param       string          buttonSelector
+        * @param       integer         threshold
+        * @param       object          callback
+        * @param       string          reference
+        * @param       string          target
         */
-       init: function(className, containerSelector, buttonSelector) {
-               this._containerSelector = containerSelector;
-               this._className = className;
-               this._buttonSelector = (buttonSelector) ? buttonSelector : '.jsToggleButton';
-               this._containers = [ ];
+       init: function(threshold, callback, reference, target) {
+               this._threshold = parseInt(threshold);
+               if (this._threshold === 0) {
+                       console.debug("[WCF.Action.Scroll] Given threshold is invalid, aborting.");
+                       return;
+               }
                
-               // initialize proxy
-               var options = {
-                       success: $.proxy(this._success, this)
-               };
-               this.proxy = new WCF.Action.Proxy(options);
+               if ($.isFunction(callback)) this._callback = callback;
+               if (this._callback === null) {
+                       console.debug("[WCF.Action.Scroll] Given callback is invalid, aborting.");
+                       return;
+               }
                
-               // bind event listener
-               this._initElements();
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.Action.Toggle' + this._className.hashCode(), $.proxy(this._initElements, this));
+               // bind element references
+               this._reference = $((reference) ? reference : window);
+               this._target = $((target) ? target : document);
+               
+               // watch for scroll event
+               this.start();
+               
+               // check if browser navigated back and jumped to offset before JavaScript was loaded
+               this._scroll();
        },
        
        /**
-        * Initializes available element containers.
-        */
-       _initElements: function() {
-               $(this._containerSelector).each($.proxy(function(index, container) {
-                       var $container = $(container);
-                       var $containerID = $container.wcfIdentify();
-                       
-                       if (!WCF.inArray($containerID, this._containers)) {
-                               this._containers.push($containerID);
-                               $container.find(this._buttonSelector).click($.proxy(this._click, this));
-                       }
-               }, this));
-       },
-       
-       /**
-        * Sends AJAX request.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               var $target = $(event.currentTarget);
-               event.preventDefault();
-               
-               if ($target.data('confirmMessageHtml') || $target.data('confirmMessage')) {
-                       WCF.System.Confirmation.show($target.data('confirmMessageHtml') ? $target.data('confirmMessageHtml') : $target.data('confirmMessage'), $.proxy(this._execute, this), { target: $target }, undefined, $target.data('confirmMessageHtml') ? true : false);
-               }
-               else {
-                       WCF.LoadingOverlayHandler.updateIcon($target);
-                       this._sendRequest($target);
-               }
-       },
-       
-       /**
-        * Executes toggeling.
-        * 
-        * @param       string          action
-        * @param       object          parameters
-        */
-       _execute: function(action, parameters) {
-               if (action === 'cancel') {
-                       return;
-               }
-               
-               WCF.LoadingOverlayHandler.updateIcon(parameters.target);
-               this._sendRequest(parameters.target);
-       },
-       
-       _sendRequest: function(object) {
-               this.proxy.setOption('data', {
-                       actionName: 'toggle',
-                       className: this._className,
-                       interfaceName: 'wcf\\data\\IToggleAction',
-                       objectIDs: [ $(object).data('objectID') ]
-               });
-               
-               this.proxy.sendRequest();
-       },
-       
-       /**
-        * Toggles status icons.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       object          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               this.triggerEffect(data.objectIDs);
-       },
-       
-       /**
-        * Triggers the toggle effect for the objects with the given ids.
-        * 
-        * @param       array           objectIDs
-        */
-       triggerEffect: function(objectIDs) {
-               for (var $index in this._containers) {
-                       var $container = $('#' + this._containers[$index]);
-                       var $toggleButton = $container.find(this._buttonSelector);
-                       if (WCF.inArray($toggleButton.data('objectID'), objectIDs)) {
-                               $container.wcfHighlight();
-                               this._toggleButton($container, $toggleButton);
-                       }
-               }
-       },
-       
-       /**
-        * Tiggers the toggle effect on a button
-        * 
-        * @param       jQuery  $container
-        * @param       jQuery  $toggleButton
-        */
-       _toggleButton: function($container, $toggleButton) {
-               var $newTitle = '';
-               
-               // toggle icon source
-               WCF.LoadingOverlayHandler.updateIcon($toggleButton, false);
-               if ($toggleButton.hasClass('fa-square-o')) {
-                       $toggleButton.removeClass('fa-square-o').addClass('fa-check-square-o');
-                       $newTitle = ($toggleButton.data('disableTitle') ? $toggleButton.data('disableTitle') : WCF.Language.get('wcf.global.button.disable'));
-                       $toggleButton.attr('title', $newTitle);
-               }
-               else {
-                       $toggleButton.removeClass('fa-check-square-o').addClass('fa-square-o');
-                       $newTitle = ($toggleButton.data('enableTitle') ? $toggleButton.data('enableTitle') : WCF.Language.get('wcf.global.button.enable'));
-                       $toggleButton.attr('title', $newTitle);
-               }
-               
-               // toggle css class
-               $container.toggleClass('disabled');
-       }
-});
-
-/**
- * Executes provided callback if scroll threshold is reached. Usuable to determine
- * if user reached the bottom of an element to load new elements on the fly.
- * 
- * If you do not provide a value for 'reference' and 'target' it will assume you're
- * monitoring page scrolls, otherwise a valid jQuery selector must be provided for both.
- * 
- * @param      integer         threshold
- * @param      object          callback
- * @param      string          reference
- * @param      string          target
- */
-WCF.Action.Scroll = Class.extend({
-       /**
-        * callback used once threshold is reached
-        * @var object
-        */
-       _callback: null,
-       
-       /**
-        * reference object
-        * @var jQuery
-        */
-       _reference: null,
-       
-       /**
-        * target object
-        * @var jQuery
-        */
-       _target: null,
-       
-       /**
-        * threshold value
-        * @var integer
-        */
-       _threshold: 0,
-       
-       /**
-        * Initializes a new WCF.Action.Scroll object.
-        * 
-        * @param       integer         threshold
-        * @param       object          callback
-        * @param       string          reference
-        * @param       string          target
-        */
-       init: function(threshold, callback, reference, target) {
-               this._threshold = parseInt(threshold);
-               if (this._threshold === 0) {
-                       console.debug("[WCF.Action.Scroll] Given threshold is invalid, aborting.");
-                       return;
-               }
-               
-               if ($.isFunction(callback)) this._callback = callback;
-               if (this._callback === null) {
-                       console.debug("[WCF.Action.Scroll] Given callback is invalid, aborting.");
-                       return;
-               }
-               
-               // bind element references
-               this._reference = $((reference) ? reference : window);
-               this._target = $((target) ? target : document);
-               
-               // watch for scroll event
-               this.start();
-               
-               // check if browser navigated back and jumped to offset before JavaScript was loaded
-               this._scroll();
-       },
-       
-       /**
-        * Calculates if threshold is reached and notifies callback.
+        * Calculates if threshold is reached and notifies callback.
         */
        _scroll: function() {
                var $targetHeight = this._target.height();
@@ -3069,7 +3164,6 @@ WCF.Collapsible.Remote = Class.extend({
         * @param       jQuery          buttonContainer
         */
        _createButton: function(containerID, buttonContainer) {
-               var $isOpen = this._containers[containerID].data('isOpen');
                var $button = $('<span class="collapsibleButton jsTooltip pointer icon icon16 fa-chevron-down" title="'+WCF.Language.get('wcf.global.button.collapsible')+'">').prependTo(buttonContainer);
                $button.data('containerID', containerID).click($.proxy(this._toggleContainer, this));
                
@@ -3501,222 +3595,237 @@ WCF.DOMNodeRemovedHandler = {
  */
 WCF.Option = { };
 
-/**
- * Handles option selection.
- */
-WCF.Option.Handler = Class.extend({
+if (COMPILER_TARGET_DEFAULT) {
        /**
-        * Initializes the WCF.Option.Handler class.
+        * Handles option selection.
         */
-       init: function() {
-               this._initOptions();
+       WCF.Option.Handler = Class.extend({
+               /**
+                * Initializes the WCF.Option.Handler class.
+                */
+               init: function () {
+                       this._initOptions();
+                       
+                       WCF.DOMNodeInsertedHandler.addCallback('WCF.Option.Handler', $.proxy(this._initOptions, this));
+               },
                
-               WCF.DOMNodeInsertedHandler.addCallback('WCF.Option.Handler', $.proxy(this._initOptions, this));
-       },
-       
-       /**
-        * Initializes all options.
-        */
-       _initOptions: function() {
-               $('.jsEnablesOptions').each($.proxy(this._initOption, this));
-       },
-       
-       /**
-        * Initializes an option.
-        * 
-        * @param       integer         index
-        * @param       object          option
-        */
-       _initOption: function(index, option) {
-               // execute action on init
-               this._change(option);
+               /**
+                * Initializes all options.
+                */
+               _initOptions: function () {
+                       $('.jsEnablesOptions').each($.proxy(this._initOption, this));
+               },
                
-               // bind event listener
-               $(option).change($.proxy(this._handleChange, this));
-       },
-       
-       /**
-        * Applies whenever an option is changed.
-        * 
-        * @param       object          event
-        */
-       _handleChange: function(event) {
-               this._change($(event.target));
-       },
-       
-       /**
-        * Enables or disables options on option value change.
-        * 
-        * @param       object          option
-        */
-       _change: function(option) {
-               option = $(option);
+               /**
+                * Initializes an option.
+                *
+                * @param        integer                index
+                * @param        object                option
+                */
+               _initOption: function (index, option) {
+                       // execute action on init
+                       this._change(option);
+                       
+                       // bind event listener
+                       $(option).change($.proxy(this._handleChange, this));
+               },
                
-               var disableOptions = eval(option.data('disableOptions'));
-               var enableOptions = eval(option.data('enableOptions'));
+               /**
+                * Applies whenever an option is changed.
+                *
+                * @param        object                event
+                */
+               _handleChange: function (event) {
+                       this._change($(event.target));
+               },
                
-               // determine action by type
-               switch(option.getTagName()) {
-                       case 'input':
-                               switch(option.attr('type')) {
-                                       case 'checkbox':
-                                               this._execute(option.prop('checked'), disableOptions, enableOptions);
+               /**
+                * Enables or disables options on option value change.
+                *
+                * @param        object                option
+                */
+               _change: function (option) {
+                       option = $(option);
+                       
+                       var disableOptions = eval(option.data('disableOptions'));
+                       var enableOptions = eval(option.data('enableOptions'));
+                       
+                       // determine action by type
+                       switch (option.getTagName()) {
+                               case 'input':
+                                       switch (option.attr('type')) {
+                                               case 'checkbox':
+                                                       this._execute(option.prop('checked'), disableOptions, enableOptions);
+                                                       break;
+                                               
+                                               case 'radio':
+                                                       if (option.prop('checked')) {
+                                                               var isActive = true;
+                                                               if (option.data('isBoolean') && option.val() != 1) {
+                                                                       isActive = false;
+                                                               }
+                                                               
+                                                               this._execute(isActive, disableOptions, enableOptions);
+                                                       }
+                                                       break;
+                                       }
                                        break;
+                               
+                               case 'select':
+                                       var $value = option.val();
+                                       var relevantDisableOptions = [];
+                                       var relevantEnableOptions = [];
                                        
-                                       case 'radio':
-                                               if (option.prop('checked')) {
-                                                       var isActive = true;
-                                                       if (option.data('isBoolean') && option.val() != 1) {
-                                                               isActive = false;
+                                       if (disableOptions.length > 0) {
+                                               for (var $index in disableOptions) {
+                                                       var $item = disableOptions[$index];
+                                                       
+                                                       if ($item.value == $value) {
+                                                               relevantDisableOptions.push($item.option);
                                                        }
+                                                       else {
+                                                               relevantEnableOptions.push($item.option);
+                                                       }
+                                               }
+                                       }
+                                       
+                                       if (enableOptions.length > 0) {
+                                               for (var $index in enableOptions) {
+                                                       var $item = enableOptions[$index];
                                                        
-                                                       this._execute(isActive, disableOptions, enableOptions);
+                                                       if ($item.value == $value) {
+                                                               relevantEnableOptions.push($item.option);
+                                                       }
+                                                       else {
+                                                               relevantDisableOptions.push($item.option);
+                                                       }
                                                }
+                                       }
+                                       
+                                       this._execute(true, relevantDisableOptions, relevantEnableOptions);
                                        break;
-                               }
-                       break;
-                       
-                       case 'select':
-                               var $value = option.val();
-                               var relevantDisableOptions = [];
-                               var relevantEnableOptions = [];
-                               
-                               if (disableOptions.length > 0) {
-                                       for (var $index in disableOptions) {
-                                               var $item = disableOptions[$index];
-                                               
-                                               if ($item.value == $value) {
-                                                       relevantDisableOptions.push($item.option);
-                                               }
-                                               else {
-                                                       relevantEnableOptions.push($item.option);
+                       }
+               },
+               
+               /**
+                * Enables or disables options.
+                *
+                * @param        boolean                isActive
+                * @param        array                disableOptions
+                * @param        array                enableOptions
+                */
+               _execute: function (isActive, disableOptions, enableOptions) {
+                       if (disableOptions.length > 0) {
+                               for (var $i = 0, $size = disableOptions.length; $i < $size; $i++) {
+                                       var $target = disableOptions[$i];
+                                       if ($.wcfIsset($target)) {
+                                               this._enableOption($target, !isActive);
+                                       }
+                                       else {
+                                               var $dl = $('.' + $target + 'Input');
+                                               if ($dl.length) {
+                                                       this._enableOptions($dl.children('dd').find('input, select, textarea'), !isActive);
                                                }
                                        }
                                }
-                               
-                               if (enableOptions.length > 0) {
-                                       for (var $index in enableOptions) {
-                                               var $item = enableOptions[$index];
-                                               
-                                               if ($item.value == $value) {
-                                                       relevantEnableOptions.push($item.option);
-                                               }
-                                               else {
-                                                       relevantDisableOptions.push($item.option);
+                       }
+                       
+                       if (enableOptions.length > 0) {
+                               for (var $i = 0, $size = enableOptions.length; $i < $size; $i++) {
+                                       var $target = enableOptions[$i];
+                                       if ($.wcfIsset($target)) {
+                                               this._enableOption($target, isActive);
+                                       }
+                                       else {
+                                               var $dl = $('.' + $target + 'Input');
+                                               if ($dl.length) {
+                                                       this._enableOptions($dl.children('dd').find('input, select, textarea'), isActive);
                                                }
                                        }
                                }
+                       }
+               },
+               
+               /**
+                * Enables/Disables an option.
+                *
+                * @param        string                target
+                * @param        boolean                enable
+                */
+               _enableOption: function (target, enable) {
+                       this._enableOptionElement($('#' + $.wcfEscapeID(target)), enable);
+               },
+               
+               /**
+                * Enables/Disables an option element.
+                *
+                * @param        string                target
+                * @param        boolean                enable
+                */
+               _enableOptionElement: function (element, enable) {
+                       element = $(element);
+                       var $tagName = element.getTagName();
+                       
+                       if ($tagName == 'select' || ($tagName == 'input' && (element.attr('type') == 'checkbox' || element.attr('type') == 'file' || element.attr('type') == 'radio'))) {
+                               if (enable) element.enable();
+                               else element.disable();
                                
-                               this._execute(true, relevantDisableOptions, relevantEnableOptions);
-                       break;
-               }
-       },
-       
-       /**
-        * Enables or disables options.
-        * 
-        * @param       boolean         isActive
-        * @param       array           disableOptions
-        * @param       array           enableOptions
-        */
-       _execute: function(isActive, disableOptions, enableOptions) {
-               if (disableOptions.length > 0) {
-                       for (var $i = 0, $size = disableOptions.length; $i < $size; $i++) {
-                               var $target = disableOptions[$i];
-                               if ($.wcfIsset($target)) {
-                                       this._enableOption($target, !isActive);
-                               }
-                               else {
-                                       var $dl = $('.' + $target + 'Input');
-                                       if ($dl.length) {
-                                               this._enableOptions($dl.children('dd').find('input, select, textarea'), !isActive);
-                                       }
+                               if (element.parents('.optionTypeBoolean:eq(0)')) {
+                                       var noElement = $('#' + element.wcfIdentify() + '_no');
+                                       if (enable) noElement.enable();
+                                       else noElement.disable();
                                }
                        }
-               }
+                       else {
+                               if (enable) element.removeAttr('readonly');
+                               else element.attr('readonly', true);
+                       }
+                       
+                       if (enable) {
+                               element.closest('dl').removeClass('disabled');
+                       }
+                       else {
+                               element.closest('dl').addClass('disabled');
+                       }
+               },
                
-               if (enableOptions.length > 0) {
-                       for (var $i = 0, $size = enableOptions.length; $i < $size; $i++) {
-                               var $target = enableOptions[$i];
-                               if ($.wcfIsset($target)) {
-                                       this._enableOption($target, isActive);
-                               }
-                               else {
-                                       var $dl = $('.' + $target + 'Input');
-                                       if ($dl.length) {
-                                               this._enableOptions($dl.children('dd').find('input, select, textarea'), isActive);
-                                       }
-                               }
+               /**
+                * Enables/Disables an option consisting of multiple form elements.
+                *
+                * @param        string                target
+                * @param        boolean                enable
+                */
+               _enableOptions: function (targets, enable) {
+                       for (var $i = 0, $length = targets.length; $i < $length; $i++) {
+                               this._enableOptionElement(targets[$i], enable);
                        }
                }
-       },
-       
+       });
+}
+else {
+       WCF.Option.Handler = Class.extend({
+               init: function() {},
+               _initOptions: function() {},
+               _initOption: function() {},
+               _handleChange: function() {},
+               _change: function() {},
+               _execute: function() {},
+               _enableOption: function() {},
+               _enableOptionElement: function() {},
+               _enableOptions: function() {}
+       });
+}
+
+WCF.PageVisibilityHandler = {
        /**
-        * Enables/Disables an option.
-        * 
-        * @param       string          target
-        * @param       boolean         enable
+        * list of callbacks
+        * @var WCF.Dictionary
         */
-       _enableOption: function(target, enable) {
-               this._enableOptionElement($('#' + $.wcfEscapeID(target)), enable);
-       },
+       _callbacks: new WCF.Dictionary(),
        
        /**
-        * Enables/Disables an option element.
-        * 
-        * @param       string          target
-        * @param       boolean         enable
-        */
-       _enableOptionElement: function(element, enable) {
-               element = $(element);
-               var $tagName = element.getTagName();
-               
-               if ($tagName == 'select' || ($tagName == 'input' && (element.attr('type') == 'checkbox' || element.attr('type') == 'file' || element.attr('type') == 'radio'))) {
-                       if (enable) element.enable();
-                       else element.disable();
-                       
-                       if (element.parents('.optionTypeBoolean:eq(0)')) {
-                               var noElement = $('#' + element.wcfIdentify() + '_no');
-                               if (enable) noElement.enable();
-                               else noElement.disable();
-                       }
-               }
-               else {
-                       if (enable) element.removeAttr('readonly');
-                       else element.attr('readonly', true);
-               }
-               
-               if (enable) {
-                       element.closest('dl').removeClass('disabled');
-               }
-               else {
-                       element.closest('dl').addClass('disabled');
-               }
-       },
-       
-       /**
-        * Enables/Disables an option consisting of multiple form elements.
-        * 
-        * @param       string          target
-        * @param       boolean         enable
-        */
-       _enableOptions: function(targets, enable) {
-               for (var $i = 0, $length = targets.length; $i < $length; $i++) {
-                       this._enableOptionElement(targets[$i], enable);
-               }
-       }
-});
-
-WCF.PageVisibilityHandler = {
-       /**
-        * list of callbacks
-        * @var WCF.Dictionary
-        */
-       _callbacks: new WCF.Dictionary(),
-       
-       /**
-        * indicates that event listeners are bound
-        * @var boolean
+        * indicates that event listeners are bound
+        * @var boolean
         */
        _isListening: false,
        
@@ -4120,21 +4229,17 @@ WCF.Search.Base = Class.extend({
                        case 37: // arrow-left
                        case 39: // arrow-right
                                return;
-                       break;
                        
                        case 38: // arrow up
                                this._selectPreviousItem();
                                return;
-                       break;
                        
                        case 40: // arrow down
                                this._selectNextItem();
                                return;
-                       break;
                        
                        case 13: // return key
                                return this._selectElement(event);
-                       break;
                }
                
                var $content = this._getSearchString(event);
@@ -5192,1265 +5297,1389 @@ WCF.System.Event = {
        }
 };
 
-/**
- * Worker support for frontend based upon DatabaseObjectActions.
- * 
- * @param      string          className
- * @param      string          title
- * @param      object          parameters
- * @param      object          callback
- */
-WCF.System.Worker = Class.extend({
-       /**
-        * worker aborted
-        * @var boolean
-        */
-       _aborted: false,
-       
-       /**
-        * DBOAction method name
-        * @var string
-        */
-       _actionName: '',
-       
-       /**
-        * callback invoked after worker completed
-        * @var object
-        */
-       _callback: null,
-       
-       /**
-        * DBOAction class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * dialog object
-        * @var jQuery
-        */
-       _dialog: null,
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * dialog title
-        * @var string
-        */
-       _title: '',
-       
-       /**
-        * Initializes a new worker instance.
-        * 
-        * @param       string          actionName
-        * @param       string          className
-        * @param       string          title
-        * @param       object          parameters
-        * @param       object          callback
-        * @param       object          confirmMessage
-        */
-       init: function(actionName, className, title, parameters, callback) {
-               this._aborted = false;
-               this._actionName = actionName;
-               this._callback = callback || null;
-               this._className = className;
-               this._dialog = null;
-               this._proxy = new WCF.Action.Proxy({
-                       autoSend: true,
-                       data: {
-                               actionName: this._actionName,
-                               className: this._className,
-                               parameters: parameters || { }
-                       },
-                       showLoadingOverlay: false,
-                       success: $.proxy(this._success, this)
-               });
-               this._title = title;
-       },
-       
+if (COMPILER_TARGET_DEFAULT) {
        /**
-        * Handles response from server.
-        * 
-        * @param       object          data
+        * Worker support for frontend based upon DatabaseObjectActions.
+        *
+        * @param        string                className
+        * @param        string                title
+        * @param        object                parameters
+        * @param        object                callback
         */
-       _success: function(data) {
-               // init binding
-               if (this._dialog === null) {
-                       this._dialog = $('<div />').hide().appendTo(document.body);
-                       this._dialog.wcfDialog({
-                               closeConfirmMessage: WCF.Language.get('wcf.worker.abort.confirmMessage'),
-                               closeViaModal: false,
-                               onClose: $.proxy(function() {
-                                       this._aborted = true;
-                                       this._proxy.abortPrevious();
-                                       
-                                       window.location.reload();
-                               }, this),
-                               title: this._title
-                       });
-               }
+       WCF.System.Worker = Class.extend({
+               /**
+                * worker aborted
+                * @var        boolean
+                */
+               _aborted: false,
                
-               if (this._aborted) {
-                       return;
-               }
+               /**
+                * DBOAction method name
+                * @var        string
+                */
+               _actionName: '',
                
-               if (data.returnValues.template) {
-                       this._dialog.html(data.returnValues.template);
-               }
+               /**
+                * callback invoked after worker completed
+                * @var        object
+                */
+               _callback: null,
                
-               // update progress
-               this._dialog.find('progress').attr('value', data.returnValues.progress).text(data.returnValues.progress + '%').next('span').text(data.returnValues.progress + '%');
+               /**
+                * DBOAction class name
+                * @var        string
+                */
+               _className: '',
                
-               // worker is still busy with its business, carry on
-               if (data.returnValues.progress < 100) {
-                       // send request for next loop
-                       var $parameters = data.returnValues.parameters || { };
-                       $parameters.loopCount = data.returnValues.loopCount;
-                       
-                       this._proxy.setOption('data', {
-                               actionName: this._actionName,
-                               className: this._className,
-                               parameters: $parameters
-                       });
-                       this._proxy.sendRequest();
-               }
-               else if (this._callback !== null) {
-                       this._callback(this, data);
-               }
-               else {
-                       // exchange icon
-                       this._dialog.find('.fa-spinner').removeClass('fa-spinner').addClass('fa-check green');
-                       this._dialog.find('.contentHeader h1').text(WCF.Language.get('wcf.global.worker.completed'));
-                       
-                       // display continue button
-                       var $formSubmit = $('<div class="formSubmit" />').appendTo(this._dialog);
-                       $('<button class="buttonPrimary">' + WCF.Language.get('wcf.global.button.next') + '</button>').appendTo($formSubmit).focus().click(function() {
-                               if (data.returnValues.redirectURL) {
-                                       window.location = data.returnValues.redirectURL;
-                               }
-                               else {
-                                       window.location.reload();
-                               }
+               /**
+                * dialog object
+                * @var        jQuery
+                */
+               _dialog: null,
+               
+               /**
+                * action proxy
+                * @var        WCF.Action.Proxy
+                */
+               _proxy: null,
+               
+               /**
+                * dialog title
+                * @var        string
+                */
+               _title: '',
+               
+               /**
+                * Initializes a new worker instance.
+                *
+                * @param        string                actionName
+                * @param        string                className
+                * @param        string                title
+                * @param        object                parameters
+                * @param        object                callback
+                * @param        object                confirmMessage
+                */
+               init: function (actionName, className, title, parameters, callback) {
+                       this._aborted = false;
+                       this._actionName = actionName;
+                       this._callback = callback || null;
+                       this._className = className;
+                       this._dialog = null;
+                       this._proxy = new WCF.Action.Proxy({
+                               autoSend: true,
+                               data: {
+                                       actionName: this._actionName,
+                                       className: this._className,
+                                       parameters: parameters || {}
+                               },
+                               showLoadingOverlay: false,
+                               success: $.proxy(this._success, this)
                        });
-                       
-                       this._dialog.wcfDialog('render');
-               }
-       }
-});
-
-/**
- * Default implementation for inline editors.
- * 
- * @param      string          elementSelector
- */
-WCF.InlineEditor = Class.extend({
-       /**
-        * list of registered callbacks
-        * @var array<object>
-        */
-       _callbacks: [ ],
-       
-       /**
-        * list of dropdown selections
-        * @var object
-        */
-       _dropdowns: { },
-       
-       /**
-        * list of container elements
-        * @var object
-        */
-       _elements: { },
-       
-       /**
-        * notification object
-        * @var WCF.System.Notification
-        */
-       _notification: null,
-       
-       /**
-        * list of known options
-        * @var array<object>
-        */
-       _options: [ ],
-       
-       /**
-        * action proxy
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * list of trigger elements by element id
-        * @var object<object>
-        */
-       _triggerElements: { },
-       
-       /**
-        * list of data to update upon success
-        * @var array<object>
-        */
-       _updateData: [ ],
-       
-       /**
-        * Initializes a new inline editor.
-        */
-       init: function(elementSelector) {
-               var $elements = $(elementSelector);
-               if (!$elements.length) {
-                       return;
-               }
+                       this._title = title;
+               },
                
-               this._setOptions();
-               var $quickOption = '';
-               for (var $i = 0, $length = this._options.length; $i < $length; $i++) {
-                       if (this._options[$i].isQuickOption) {
-                               $quickOption = this._options[$i].optionName;
-                               break;
+               /**
+                * Handles response from server.
+                *
+                * @param        object                data
+                */
+               _success: function (data) {
+                       // init binding
+                       if (this._dialog === null) {
+                               this._dialog = $('<div />').hide().appendTo(document.body);
+                               this._dialog.wcfDialog({
+                                       closeConfirmMessage: WCF.Language.get('wcf.worker.abort.confirmMessage'),
+                                       closeViaModal: false,
+                                       onClose: $.proxy(function () {
+                                               this._aborted = true;
+                                               this._proxy.abortPrevious();
+                                               
+                                               window.location.reload();
+                                       }, this),
+                                       title: this._title
+                               });
                        }
-               }
-               
-               var self = this;
-               $elements.each(function(index, element) {
-                       var $element = $(element);
-                       var $elementID = $element.wcfIdentify();
                        
-                       // find trigger element
-                       var $trigger = self._getTriggerElement($element);
-                       if ($trigger === null || $trigger.length !== 1) {
+                       if (this._aborted) {
                                return;
                        }
                        
-                       $trigger.on(WCF_CLICK_EVENT, $.proxy(self._show, self)).data('elementID', $elementID);
-                       if ($quickOption) {
-                               // simulate click on target action
-                               $trigger.disableSelection().data('optionName', $quickOption).dblclick($.proxy(self._click, self));
-                       }
-                       
-                       // store reference
-                       self._elements[$elementID] = $element;
-               });
-               
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               
-               WCF.CloseOverlayHandler.addCallback('WCF.InlineEditor', $.proxy(this._closeAll, this));
-               
-               this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success'), 'success');
-       },
-       
-       /**
-        * Closes all inline editors.
-        */
-       _closeAll: function() {
-               for (var $elementID in this._elements) {
-                       this._hide($elementID);
-               }
-       },
-       
-       /**
-        * Sets options for this inline editor.
-        */
-       _setOptions: function() {
-               this._options = [ ];
-       },
-       
-       /**
-        * Register an option callback for validation and execution.
-        * 
-        * @param       object          callback
-        */
-       registerCallback: function(callback) {
-               if ($.isFunction(callback)) {
-                       this._callbacks.push(callback);
-               }
-       },
-       
-       /**
-        * Returns the triggering element.
-        * 
-        * @param       jQuery          element
-        * @return      jQuery
-        */
-       _getTriggerElement: function(element) {
-               return null;
-       },
-       
-       /**
-        * Shows a dropdown menu if options are available.
-        * 
-        * @param       object          event
-        */
-       _show: function(event) {
-               event.preventDefault();
-               var $elementID = $(event.currentTarget).data('elementID');
-               
-               // build dropdown
-               var $trigger = null;
-               if (!this._dropdowns[$elementID]) {
-                       this._triggerElements[$elementID] = $trigger = this._getTriggerElement(this._elements[$elementID]).addClass('dropdownToggle');
-                       var parent = $trigger[0].parentNode;
-                       if (parent && parent.nodeName === 'LI' && parent.childElementCount === 1) {
-                               // do not add a wrapper element if the trigger is the only child
-                               parent.classList.add('dropdown');
-                       }
-                       else {
-                               $trigger.wrap('<span class="dropdown" />');
+                       if (data.returnValues.template) {
+                               this._dialog.html(data.returnValues.template);
                        }
                        
-                       this._dropdowns[$elementID] = $('<ul class="dropdownMenu" />').insertAfter($trigger);
-               }
-               this._dropdowns[$elementID].empty();
-               
-               // validate options
-               var $hasOptions = false;
-               var $lastElementType = '';
-               for (var $i = 0, $length = this._options.length; $i < $length; $i++) {
-                       var $option = this._options[$i];
+                       // update progress
+                       this._dialog.find('progress').attr('value', data.returnValues.progress).text(data.returnValues.progress + '%').next('span').text(data.returnValues.progress + '%');
                        
-                       if ($option.optionName === 'divider') {
-                               if ($lastElementType !== '' && $lastElementType !== 'divider') {
-                                       $('<li class="dropdownDivider" />').appendTo(this._dropdowns[$elementID]);
-                                       $lastElementType = $option.optionName;
-                               }
-                       }
-                       else if (this._validate($elementID, $option.optionName) || this._validateCallbacks($elementID, $option.optionName)) {
-                               var $listItem = $('<li><span>' + $option.label + '</span></li>').appendTo(this._dropdowns[$elementID]);
-                               $listItem.data('elementID', $elementID).data('optionName', $option.optionName).data('isQuickOption', ($option.isQuickOption ? true : false)).click($.proxy(this._click, this));
+                       // worker is still busy with its business, carry on
+                       if (data.returnValues.progress < 100) {
+                               // send request for next loop
+                               var $parameters = data.returnValues.parameters || {};
+                               $parameters.loopCount = data.returnValues.loopCount;
                                
-                               $hasOptions = true;
-                               $lastElementType = $option.optionName;
+                               this._proxy.setOption('data', {
+                                       actionName: this._actionName,
+                                       className: this._className,
+                                       parameters: $parameters
+                               });
+                               this._proxy.sendRequest();
                        }
-               }
-               
-               if ($hasOptions) {
-                       // if last child is divider, remove it
-                       var $lastChild = this._dropdowns[$elementID].children().last();
-                       if ($lastChild.hasClass('dropdownDivider')) {
-                               $lastChild.remove();
+                       else if (this._callback !== null) {
+                               this._callback(this, data);
                        }
-                       
-                       // check if only element is a quick option
-                       var $quickOption = null;
-                       var $count = 0;
-                       this._dropdowns[$elementID].children().each(function(index, child) {
-                               var $child = $(child);
-                               if (!$child.hasClass('dropdownDivider')) {
-                                       if ($child.data('isQuickOption')) {
-                                               $quickOption = $child;
+                       else {
+                               // exchange icon
+                               this._dialog.find('.fa-spinner').removeClass('fa-spinner').addClass('fa-check green');
+                               this._dialog.find('.contentHeader h1').text(WCF.Language.get('wcf.global.worker.completed'));
+                               
+                               // display continue button
+                               var $formSubmit = $('<div class="formSubmit" />').appendTo(this._dialog);
+                               $('<button class="buttonPrimary">' + WCF.Language.get('wcf.global.button.next') + '</button>').appendTo($formSubmit).focus().click(function () {
+                                       if (data.returnValues.redirectURL) {
+                                               window.location = data.returnValues.redirectURL;
                                        }
                                        else {
-                                               $count++;
+                                               window.location.reload();
                                        }
-                               }
-                       });
-                       
-                       if (!$count) {
-                               $quickOption.trigger('click');
-                               
-                               if (this._triggerElements[$elementID]) {
-                                       WCF.Dropdown.close(this._triggerElements[$elementID].parents('.dropdown').wcfIdentify());
-                               }
+                               });
                                
-                               return false;
+                               this._dialog.wcfDialog('render');
                        }
                }
-               
-               if ($trigger !== null) {
-                       WCF.Dropdown.initDropdown($trigger, true);
-               }
-               
-               return false;
-       },
+       });
        
        /**
-        * Validates an option.
-        * 
-        * @param       string          elementID
-        * @param       string          optionName
-        * @returns     boolean
+        * Default implementation for inline editors.
+        *
+        * @param        string                elementSelector
         */
-       _validate: function(elementID, optionName) {
-               return false;
-       },
-       
-       /**
-        * Validates an option provided by callbacks.
-        * 
-        * @param       string          elementID
-        * @param       string          optionName
-        * @return      boolean
-        */
-       _validateCallbacks: function(elementID, optionName) {
-               var $length = this._callbacks.length;
-               if ($length) {
-                       for (var $i = 0; $i < $length; $i++) {
-                               if (this._callbacks[$i].validate(this._elements[elementID], optionName)) {
-                                       return true;
+       WCF.InlineEditor = Class.extend({
+               /**
+                * list of registered callbacks
+                * @var        array<object>
+                */
+               _callbacks: [],
+               
+               /**
+                * list of dropdown selections
+                * @var        object
+                */
+               _dropdowns: {},
+               
+               /**
+                * list of container elements
+                * @var        object
+                */
+               _elements: {},
+               
+               /**
+                * notification object
+                * @var        WCF.System.Notification
+                */
+               _notification: null,
+               
+               /**
+                * list of known options
+                * @var        array<object>
+                */
+               _options: [],
+               
+               /**
+                * action proxy
+                * @var        WCF.Action.Proxy
+                */
+               _proxy: null,
+               
+               /**
+                * list of trigger elements by element id
+                * @var        object<object>
+                */
+               _triggerElements: {},
+               
+               /**
+                * list of data to update upon success
+                * @var        array<object>
+                */
+               _updateData: [],
+               
+               /**
+                * Initializes a new inline editor.
+                */
+               init: function (elementSelector) {
+                       var $elements = $(elementSelector);
+                       if (!$elements.length) {
+                               return;
+                       }
+                       
+                       this._setOptions();
+                       var $quickOption = '';
+                       for (var $i = 0, $length = this._options.length; $i < $length; $i++) {
+                               if (this._options[$i].isQuickOption) {
+                                       $quickOption = this._options[$i].optionName;
+                                       break;
                                }
                        }
-               }
+                       
+                       var self = this;
+                       $elements.each(function (index, element) {
+                               var $element = $(element);
+                               var $elementID = $element.wcfIdentify();
+                               
+                               // find trigger element
+                               var $trigger = self._getTriggerElement($element);
+                               if ($trigger === null || $trigger.length !== 1) {
+                                       return;
+                               }
+                               
+                               $trigger.on(WCF_CLICK_EVENT, $.proxy(self._show, self)).data('elementID', $elementID);
+                               if ($quickOption) {
+                                       // simulate click on target action
+                                       $trigger.disableSelection().data('optionName', $quickOption).dblclick($.proxy(self._click, self));
+                               }
+                               
+                               // store reference
+                               self._elements[$elementID] = $element;
+                       });
+                       
+                       this._proxy = new WCF.Action.Proxy({
+                               success: $.proxy(this._success, this)
+                       });
+                       
+                       WCF.CloseOverlayHandler.addCallback('WCF.InlineEditor', $.proxy(this._closeAll, this));
+                       
+                       this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success'), 'success');
+               },
                
-               return false;
-       },
-       
-       /**
-        * Handles AJAX responses.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               var $length = this._updateData.length;
-               if (!$length) {
-                       return;
-               }
+               /**
+                * Closes all inline editors.
+                */
+               _closeAll: function () {
+                       for (var $elementID in this._elements) {
+                               this._hide($elementID);
+                       }
+               },
                
-               this._updateState(data);
+               /**
+                * Sets options for this inline editor.
+                */
+               _setOptions: function () {
+                       this._options = [];
+               },
                
-               this._updateData = [ ];
-       },
-       
-       /**
-        * Update element states based upon update data.
-        * 
-        * @param       object          data
-        */
-       _updateState: function(data) { },
-       
-       /**
-        * Handles clicks within dropdown.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               var $listItem = $(event.currentTarget);
-               var $elementID = $listItem.data('elementID');
-               var $optionName = $listItem.data('optionName');
+               /**
+                * Register an option callback for validation and execution.
+                *
+                * @param        object                callback
+                */
+               registerCallback: function (callback) {
+                       if ($.isFunction(callback)) {
+                               this._callbacks.push(callback);
+                       }
+               },
                
-               if (!this._execute($elementID, $optionName)) {
-                       this._executeCallback($elementID, $optionName);
-               }
+               /**
+                * Returns the triggering element.
+                *
+                * @param        jQuery                element
+                * @return        jQuery
+                */
+               _getTriggerElement: function (element) {
+                       return null;
+               },
                
-               this._hide($elementID);
-       },
-       
-       /**
-        * Executes actions associated with an option.
-        * 
-        * @param       string          elementID
-        * @param       string          optionName
-        * @return      boolean
-        */
-       _execute: function(elementID, optionName) {
-               return false;
-       },
-       
-       /**
-        * Executes actions associated with an option provided by callbacks.
-        * 
-        * @param       string          elementID
-        * @param       string          optionName
-        * @return      boolean
-        */
-       _executeCallback: function(elementID, optionName) {
-               var $length = this._callbacks.length;
-               if ($length) {
-                       for (var $i = 0; $i < $length; $i++) {
-                               if (this._callbacks[$i].execute(this._elements[elementID], optionName)) {
-                                       return true;
+               /**
+                * Shows a dropdown menu if options are available.
+                *
+                * @param        object                event
+                */
+               _show: function (event) {
+                       event.preventDefault();
+                       var $elementID = $(event.currentTarget).data('elementID');
+                       
+                       // build dropdown
+                       var $trigger = null;
+                       if (!this._dropdowns[$elementID]) {
+                               this._triggerElements[$elementID] = $trigger = this._getTriggerElement(this._elements[$elementID]).addClass('dropdownToggle');
+                               var parent = $trigger[0].parentNode;
+                               if (parent && parent.nodeName === 'LI' && parent.childElementCount === 1) {
+                                       // do not add a wrapper element if the trigger is the only child
+                                       parent.classList.add('dropdown');
+                               }
+                               else {
+                                       $trigger.wrap('<span class="dropdown" />');
                                }
+                               
+                               this._dropdowns[$elementID] = $('<ul class="dropdownMenu" />').insertAfter($trigger);
                        }
-               }
+                       this._dropdowns[$elementID].empty();
+                       
+                       // validate options
+                       var $hasOptions = false;
+                       var $lastElementType = '';
+                       for (var $i = 0, $length = this._options.length; $i < $length; $i++) {
+                               var $option = this._options[$i];
+                               
+                               if ($option.optionName === 'divider') {
+                                       if ($lastElementType !== '' && $lastElementType !== 'divider') {
+                                               $('<li class="dropdownDivider" />').appendTo(this._dropdowns[$elementID]);
+                                               $lastElementType = $option.optionName;
+                                       }
+                               }
+                               else if (this._validate($elementID, $option.optionName) || this._validateCallbacks($elementID, $option.optionName)) {
+                                       var $listItem = $('<li><span>' + $option.label + '</span></li>').appendTo(this._dropdowns[$elementID]);
+                                       $listItem.data('elementID', $elementID).data('optionName', $option.optionName).data('isQuickOption', ($option.isQuickOption ? true : false)).click($.proxy(this._click, this));
+                                       
+                                       $hasOptions = true;
+                                       $lastElementType = $option.optionName;
+                               }
+                       }
+                       
+                       if ($hasOptions) {
+                               // if last child is divider, remove it
+                               var $lastChild = this._dropdowns[$elementID].children().last();
+                               if ($lastChild.hasClass('dropdownDivider')) {
+                                       $lastChild.remove();
+                               }
+                               
+                               // check if only element is a quick option
+                               var $quickOption = null;
+                               var $count = 0;
+                               this._dropdowns[$elementID].children().each(function (index, child) {
+                                       var $child = $(child);
+                                       if (!$child.hasClass('dropdownDivider')) {
+                                               if ($child.data('isQuickOption')) {
+                                                       $quickOption = $child;
+                                               }
+                                               else {
+                                                       $count++;
+                                               }
+                                       }
+                               });
+                               
+                               if (!$count) {
+                                       $quickOption.trigger('click');
+                                       
+                                       if (this._triggerElements[$elementID]) {
+                                               WCF.Dropdown.close(this._triggerElements[$elementID].parents('.dropdown').wcfIdentify());
+                                       }
+                                       
+                                       return false;
+                               }
+                       }
+                       
+                       if ($trigger !== null) {
+                               WCF.Dropdown.initDropdown($trigger, true);
+                       }
+                       
+                       return false;
+               },
                
-               return false;
-       },
-       
-       /**
-        * Hides a dropdown menu.
-        * 
-        * @param       string          elementID
-        */
-       _hide: function(elementID) {
-               if (this._dropdowns[elementID]) {
-                       this._dropdowns[elementID].empty().removeClass('dropdownOpen');
+               /**
+                * Validates an option.
+                *
+                * @param        string                elementID
+                * @param        string                optionName
+                * @returns        boolean
+                */
+               _validate: function (elementID, optionName) {
+                       return false;
+               },
+               
+               /**
+                * Validates an option provided by callbacks.
+                *
+                * @param        string                elementID
+                * @param        string                optionName
+                * @return        boolean
+                */
+               _validateCallbacks: function (elementID, optionName) {
+                       var $length = this._callbacks.length;
+                       if ($length) {
+                               for (var $i = 0; $i < $length; $i++) {
+                                       if (this._callbacks[$i].validate(this._elements[elementID], optionName)) {
+                                               return true;
+                                       }
+                               }
+                       }
+                       
+                       return false;
+               },
+               
+               /**
+                * Handles AJAX responses.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        jQuery                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       var $length = this._updateData.length;
+                       if (!$length) {
+                               return;
+                       }
+                       
+                       this._updateState(data);
+                       
+                       this._updateData = [];
+               },
+               
+               /**
+                * Update element states based upon update data.
+                *
+                * @param        object                data
+                */
+               _updateState: function (data) {
+               },
+               
+               /**
+                * Handles clicks within dropdown.
+                *
+                * @param        object                event
+                */
+               _click: function (event) {
+                       var $listItem = $(event.currentTarget);
+                       var $elementID = $listItem.data('elementID');
+                       var $optionName = $listItem.data('optionName');
+                       
+                       if (!this._execute($elementID, $optionName)) {
+                               this._executeCallback($elementID, $optionName);
+                       }
+                       
+                       this._hide($elementID);
+               },
+               
+               /**
+                * Executes actions associated with an option.
+                *
+                * @param        string                elementID
+                * @param        string                optionName
+                * @return        boolean
+                */
+               _execute: function (elementID, optionName) {
+                       return false;
+               },
+               
+               /**
+                * Executes actions associated with an option provided by callbacks.
+                *
+                * @param        string                elementID
+                * @param        string                optionName
+                * @return        boolean
+                */
+               _executeCallback: function (elementID, optionName) {
+                       var $length = this._callbacks.length;
+                       if ($length) {
+                               for (var $i = 0; $i < $length; $i++) {
+                                       if (this._callbacks[$i].execute(this._elements[elementID], optionName)) {
+                                               return true;
+                                       }
+                               }
+                       }
+                       
+                       return false;
+               },
+               
+               /**
+                * Hides a dropdown menu.
+                *
+                * @param        string                elementID
+                */
+               _hide: function (elementID) {
+                       if (this._dropdowns[elementID]) {
+                               this._dropdowns[elementID].empty().removeClass('dropdownOpen');
+                       }
                }
-       }
-});
-
-/**
- * Default implementation for ajax file uploads.
- * 
- * @deprecated Use WoltLabSuite/Core/Upload
- * 
- * @param      jquery          buttonSelector
- * @param      jquery          fileListSelector
- * @param      string          className
- * @param      jquery          options
- */
-WCF.Upload = Class.extend({
-       /**
-        * name of the upload field
-        * @var string
-        */
-       _name: '__files[]',
-       
-       /**
-        * button selector
-        * @var jQuery
-        */
-       _buttonSelector: null,
-       
-       /**
-        * file list selector
-        * @var jQuery
-        */
-       _fileListSelector: null,
-       
-       /**
-        * upload file
-        * @var jQuery
-        */
-       _fileUpload: null,
-       
-       /**
-        * class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * iframe for IE<10 fallback
-        * @var jQuery
-        */
-       _iframe: null,
-       
-       /**
-        * internal file id
-        * @var integer
-        */
-       _internalFileID: 0,
-       
-       /**
-        * additional options
-        * @var jQuery
-        */
-       _options: {},
-       
-       /**
-        * upload matrix
-        * @var array
-        */
-       _uploadMatrix: [],
-       
-       /**
-        * true, if the active user's browser supports ajax file uploads
-        * @var boolean
-        */
-       _supportsAJAXUpload: true,
-       
-       /**
-        * fallback overlay for stupid browsers
-        * @var jquery
-        */
-       _overlay: null,
+       });
        
        /**
-        * Initializes a new upload handler.
-        * 
-        * @param       string          buttonSelector
-        * @param       string          fileListSelector
-        * @param       string          className
-        * @param       object          options
+        * Default implementation for ajax file uploads.
+        *
+        * @deprecated        Use WoltLabSuite/Core/Upload
+        *
+        * @param        jquery                buttonSelector
+        * @param        jquery                fileListSelector
+        * @param        string                className
+        * @param        jquery                options
         */
-       init: function(buttonSelector, fileListSelector, className, options) {
-               this._buttonSelector = buttonSelector;
-               this._fileListSelector = fileListSelector;
-               this._className = className;
-               this._internalFileID = 0;
-               this._options = $.extend(true, {
-                       action: 'upload',
-                       multiple: false,
-                       url: 'index.php?ajax-upload/&t=' + SECURITY_TOKEN
-               }, options || { });
+       WCF.Upload = Class.extend({
+               /**
+                * name of the upload field
+                * @var        string
+                */
+               _name: '__files[]',
                
-               this._options.url = WCF.convertLegacyURL(this._options.url);
-               if (this._options.url.indexOf('index.php') === 0) {
-                       this._options.url = WSC_API_URL + this._options.url;
-               }
+               /**
+                * button selector
+                * @var        jQuery
+                */
+               _buttonSelector: null,
                
-               // check for ajax upload support
-               var $xhr = new XMLHttpRequest();
-               this._supportsAJAXUpload = ($xhr && ('upload' in $xhr) && ('onprogress' in $xhr.upload));
+               /**
+                * file list selector
+                * @var        jQuery
+                */
+               _fileListSelector: null,
                
-               // create upload button
-               this._createButton();
-       },
-       
-       /**
-        * Creates the upload button.
-        */
-       _createButton: function() {
-               if (this._supportsAJAXUpload) {
-                       this._fileUpload = $('<input type="file" name="' + this._name + '" ' + (this._options.multiple ? 'multiple="true" ' : '') + '/>');
-                       this._fileUpload.change($.proxy(this._upload, this));
-                       var $button = $('<p class="button uploadButton"><span>' + WCF.Language.get('wcf.global.button.upload') + '</span></p>');
-                       $button.prepend(this._fileUpload);
-               }
-               else {
-                       var $button = $('<p class="button uploadFallbackButton"><span>' + WCF.Language.get('wcf.global.button.upload') + '</span></p>');
-                       $button.click($.proxy(this._showOverlay, this));
-               }
+               /**
+                * upload file
+                * @var        jQuery
+                */
+               _fileUpload: null,
                
-               this._insertButton($button);
-       },
-       
-       /**
-        * Inserts the upload button.
-        * 
-        * @param       jQuery          button
-        */
-       _insertButton: function(button) {
-               this._buttonSelector.prepend(button);
-       },
-       
-       /**
-        * Removes the upload button.
-        */
-       _removeButton: function() {
-               var $selector = '.uploadButton';
-               if (!this._supportsAJAXUpload) {
-                       $selector = '.uploadFallbackButton';
-               }
+               /**
+                * class name
+                * @var        string
+                */
+               _className: '',
                
-               this._buttonSelector.find($selector).remove();
-       },
+               /**
+                * iframe for IE<10 fallback
+                * @var        jQuery
+                */
+               _iframe: null,
+               
+               /**
+                * internal file id
+                * @var        integer
+                */
+               _internalFileID: 0,
+               
+               /**
+                * additional options
+                * @var        jQuery
+                */
+               _options: {},
+               
+               /**
+                * upload matrix
+                * @var        array
+                */
+               _uploadMatrix: [],
+               
+               /**
+                * true, if the active user's browser supports ajax file uploads
+                * @var        boolean
+                */
+               _supportsAJAXUpload: true,
+               
+               /**
+                * fallback overlay for stupid browsers
+                * @var        jquery
+                */
+               _overlay: null,
+               
+               /**
+                * Initializes a new upload handler.
+                *
+                * @param        string                buttonSelector
+                * @param        string                fileListSelector
+                * @param        string                className
+                * @param        object                options
+                */
+               init: function (buttonSelector, fileListSelector, className, options) {
+                       this._buttonSelector = buttonSelector;
+                       this._fileListSelector = fileListSelector;
+                       this._className = className;
+                       this._internalFileID = 0;
+                       this._options = $.extend(true, {
+                               action: 'upload',
+                               multiple: false,
+                               url: 'index.php?ajax-upload/&t=' + SECURITY_TOKEN
+                       }, options || {});
+                       
+                       this._options.url = WCF.convertLegacyURL(this._options.url);
+                       if (this._options.url.indexOf('index.php') === 0) {
+                               this._options.url = WSC_API_URL + this._options.url;
+                       }
+                       
+                       // check for ajax upload support
+                       var $xhr = new XMLHttpRequest();
+                       this._supportsAJAXUpload = ($xhr && ('upload' in $xhr) && ('onprogress' in $xhr.upload));
+                       
+                       // create upload button
+                       this._createButton();
+               },
+               
+               /**
+                * Creates the upload button.
+                */
+               _createButton: function () {
+                       if (this._supportsAJAXUpload) {
+                               this._fileUpload = $('<input type="file" name="' + this._name + '" ' + (this._options.multiple ? 'multiple="true" ' : '') + '/>');
+                               this._fileUpload.change($.proxy(this._upload, this));
+                               var $button = $('<p class="button uploadButton"><span>' + WCF.Language.get('wcf.global.button.upload') + '</span></p>');
+                               $button.prepend(this._fileUpload);
+                       }
+                       else {
+                               var $button = $('<p class="button uploadFallbackButton"><span>' + WCF.Language.get('wcf.global.button.upload') + '</span></p>');
+                               $button.click($.proxy(this._showOverlay, this));
+                       }
+                       
+                       this._insertButton($button);
+               },
+               
+               /**
+                * Inserts the upload button.
+                *
+                * @param        jQuery                button
+                */
+               _insertButton: function (button) {
+                       this._buttonSelector.prepend(button);
+               },
+               
+               /**
+                * Removes the upload button.
+                */
+               _removeButton: function () {
+                       var $selector = '.uploadButton';
+                       if (!this._supportsAJAXUpload) {
+                               $selector = '.uploadFallbackButton';
+                       }
+                       
+                       this._buttonSelector.find($selector).remove();
+               },
+               
+               /**
+                * Callback for file uploads.
+                *
+                * @param        object                event
+                * @param        File                file
+                * @param        Blob                blob
+                * @return        integer
+                */
+               _upload: function (event, file, blob) {
+                       var $uploadID = null;
+                       var $files = [];
+                       if (file) {
+                               $files.push(file);
+                       }
+                       else if (blob) {
+                               var $ext = '';
+                               switch (blob.type) {
+                                       case 'image/png':
+                                               $ext = '.png';
+                                               break;
+                                       
+                                       case 'image/jpeg':
+                                               $ext = '.jpg';
+                                               break;
+                                       
+                                       case 'image/gif':
+                                               $ext = '.gif';
+                                               break;
+                               }
+                               
+                               $files.push({
+                                       name: 'pasted-from-clipboard' + $ext
+                               });
+                       }
+                       else {
+                               $files = this._fileUpload.prop('files');
+                       }
+                       
+                       if ($files.length) {
+                               var $fd = new FormData();
+                               $uploadID = this._createUploadMatrix($files);
+                               
+                               // no more files left, abort
+                               if (!this._uploadMatrix[$uploadID].length) {
+                                       return null;
+                               }
+                               
+                               for (var $i = 0, $length = $files.length; $i < $length; $i++) {
+                                       if (this._uploadMatrix[$uploadID][$i]) {
+                                               var $internalFileID = this._uploadMatrix[$uploadID][$i].data('internalFileID');
+                                               
+                                               if (blob) {
+                                                       $fd.append('__files[' + $internalFileID + ']', blob, $files[$i].name);
+                                               }
+                                               else {
+                                                       $fd.append('__files[' + $internalFileID + ']', $files[$i]);
+                                               }
+                                       }
+                               }
+                               
+                               $fd.append('actionName', this._options.action);
+                               $fd.append('className', this._className);
+                               var $additionalParameters = this._getParameters();
+                               for (var $name in $additionalParameters) {
+                                       $fd.append('parameters[' + $name + ']', $additionalParameters[$name]);
+                               }
+                               
+                               var self = this;
+                               $.ajax({
+                                       type: 'POST',
+                                       url: this._options.url,
+                                       enctype: 'multipart/form-data',
+                                       data: $fd,
+                                       contentType: false,
+                                       processData: false,
+                                       success: function (data, textStatus, jqXHR) {
+                                               self._success($uploadID, data);
+                                       },
+                                       error: $.proxy(this._error, this),
+                                       xhr: function () {
+                                               var $xhr = $.ajaxSettings.xhr();
+                                               if ($xhr) {
+                                                       $xhr.upload.addEventListener('progress', function (event) {
+                                                               self._progress($uploadID, event);
+                                                       }, false);
+                                               }
+                                               return $xhr;
+                                       },
+                                       xhrFields: {
+                                               withCredentials: true
+                                       }
+                               });
+                       }
+                       
+                       return $uploadID;
+               },
+               
+               /**
+                * Creates upload matrix for provided files.
+                *
+                * @param        array<object>                files
+                * @return        integer
+                */
+               _createUploadMatrix: function (files) {
+                       if (files.length) {
+                               var $uploadID = this._uploadMatrix.length;
+                               this._uploadMatrix[$uploadID] = [];
+                               
+                               for (var $i = 0, $length = files.length; $i < $length; $i++) {
+                                       var $file = files[$i];
+                                       var $li = this._initFile($file);
+                                       
+                                       if (!$li.hasClass('uploadFailed')) {
+                                               $li.data('filename', $file.name).data('internalFileID', this._internalFileID++);
+                                               this._uploadMatrix[$uploadID][$i] = $li;
+                                       }
+                               }
+                               
+                               return $uploadID;
+                       }
+                       
+                       return null;
+               },
+               
+               /**
+                * Callback for success event.
+                *
+                * @param        integer                uploadID
+                * @param        object                data
+                */
+               _success: function (uploadID, data) {
+               },
+               
+               /**
+                * Callback for error event.
+                *
+                * @param        jQuery                jqXHR
+                * @param        string                textStatus
+                * @param        string                errorThrown
+                */
+               _error: function (jqXHR, textStatus, errorThrown) {
+               },
+               
+               /**
+                * Callback for progress event.
+                *
+                * @param        integer                uploadID
+                * @param        object                event
+                */
+               _progress: function (uploadID, event) {
+                       var $percentComplete = Math.round(event.loaded * 100 / event.total);
+                       
+                       for (var $i in this._uploadMatrix[uploadID]) {
+                               this._uploadMatrix[uploadID][$i].find('progress').attr('value', $percentComplete);
+                       }
+               },
+               
+               /**
+                * Returns additional parameters.
+                *
+                * @return        object
+                */
+               _getParameters: function () {
+                       return {};
+               },
+               
+               /**
+                * Initializes list item for uploaded file.
+                *
+                * @return        jQuery
+                */
+               _initFile: function (file) {
+                       return $('<li>' + file.name + ' (' + file.size + ')<progress max="100" /></li>').appendTo(this._fileListSelector);
+               },
+               
+               /**
+                * Shows the fallback overlay (work in progress)
+                */
+               _showOverlay: function () {
+                       // create iframe
+                       if (this._iframe === null) {
+                               this._iframe = $('<iframe name="__fileUploadIFrame" />').hide().appendTo(document.body);
+                       }
+                       
+                       // create overlay
+                       if (!this._overlay) {
+                               this._overlay = $('<div><form enctype="multipart/form-data" method="post" action="' + this._options.url + '" target="__fileUploadIFrame" /></div>').hide().appendTo(document.body);
+                               
+                               var $form = this._overlay.find('form');
+                               $('<dl class="wide"><dd><input type="file" id="__fileUpload" name="' + this._name + '" ' + (this._options.multiple ? 'multiple="true" ' : '') + '/></dd></dl>').appendTo($form);
+                               $('<div class="formSubmit"><input type="submit" value="Upload" accesskey="s" /></div></form>').appendTo($form);
+                               
+                               $('<input type="hidden" name="isFallback" value="1" />').appendTo($form);
+                               $('<input type="hidden" name="actionName" value="' + this._options.action + '" />').appendTo($form);
+                               $('<input type="hidden" name="className" value="' + this._className + '" />').appendTo($form);
+                               var $additionalParameters = this._getParameters();
+                               for (var $name in $additionalParameters) {
+                                       $('<input type="hidden" name="' + $name + '" value="' + $additionalParameters[$name] + '" />').appendTo($form);
+                               }
+                               
+                               $form.submit($.proxy(function () {
+                                       var $file = {
+                                               name: this._getFilename(),
+                                               size: ''
+                                       };
+                                       
+                                       var $uploadID = this._createUploadMatrix([$file]);
+                                       var self = this;
+                                       this._iframe.data('loading', true).off('load').load(function () {
+                                               self._evaluateResponse($uploadID);
+                                       });
+                                       this._overlay.wcfDialog('close');
+                               }, this));
+                       }
+                       
+                       this._overlay.wcfDialog({
+                               title: WCF.Language.get('wcf.global.button.upload')
+                       });
+               },
+               
+               /**
+                * Evaluates iframe response.
+                *
+                * @param        integer                uploadID
+                */
+               _evaluateResponse: function (uploadID) {
+                       var $returnValues = $.parseJSON(this._iframe.contents().find('pre').html());
+                       this._success(uploadID, $returnValues);
+               },
+               
+               /**
+                * Returns name of selected file.
+                *
+                * @return        string
+                */
+               _getFilename: function () {
+                       return $('#__fileUpload').val().split('\\').pop();
+               }
+       });
        
        /**
-        * Callback for file uploads.
-        * 
-        * @param       object          event
-        * @param       File            file
-        * @param       Blob            blob
-        * @return      integer
+        * Default implementation for parallel AJAX file uploads.
+        *
+        * @deprecated        Use WoltLabSuite/Core/Upload
         */
-       _upload: function(event, file, blob) {
-               var $uploadID = null;
-               var $files = [ ];
-               if (file) {
-                       $files.push(file);
-               }
-               else if (blob) {
-                       var $ext = '';
-                       switch (blob.type) {
-                               case 'image/png':
-                                       $ext = '.png';
-                               break;
-                               
-                               case 'image/jpeg':
-                                       $ext = '.jpg';
-                               break;
-                               
-                               case 'image/gif':
-                                       $ext = '.gif';
-                               break;
-                       }
-                       
-                       $files.push({
-                               name: 'pasted-from-clipboard' + $ext
+       WCF.Upload.Parallel = WCF.Upload.extend({
+               /**
+                * @see        WCF.Upload.init()
+                */
+               init: function (buttonSelector, fileListSelector, className, options) {
+                       // force multiple uploads
+                       options = $.extend(true, options || {}, {
+                               multiple: true
                        });
-               }
-               else {
-                       $files = this._fileUpload.prop('files');
-               }
-               
-               if ($files.length) {
-                       var $fd = new FormData();
-                       $uploadID = this._createUploadMatrix($files);
-                       
-                       // no more files left, abort
-                       if (!this._uploadMatrix[$uploadID].length) {
-                               return null;
-                       }
                        
+                       this._super(buttonSelector, fileListSelector, className, options);
+               },
+               
+               /**
+                * @see        WCF.Upload._upload()
+                */
+               _upload: function () {
+                       var $files = this._fileUpload.prop('files');
                        for (var $i = 0, $length = $files.length; $i < $length; $i++) {
-                               if (this._uploadMatrix[$uploadID][$i]) {
-                                       var $internalFileID = this._uploadMatrix[$uploadID][$i].data('internalFileID');
-                                       
-                                       if (blob) {
-                                               $fd.append('__files[' + $internalFileID + ']', blob, $files[$i].name);
-                                       }
-                                       else {
-                                               $fd.append('__files[' + $internalFileID + ']', $files[$i]);
-                                       }
+                               var $file = $files[$i];
+                               var $formData = new FormData();
+                               var $internalFileID = this._createUploadMatrix($file);
+                               
+                               if (!this._uploadMatrix[$internalFileID].length) {
+                                       continue;
                                }
+                               
+                               $formData.append('__files[' + $internalFileID + ']', $file);
+                               $formData.append('actionName', this._options.action);
+                               $formData.append('className', this._className);
+                               var $additionalParameters = this._getParameters();
+                               for (var $name in $additionalParameters) {
+                                       $formData.append('parameters[' + $name + ']', $additionalParameters[$name]);
+                               }
+                               
+                               this._sendRequest($internalFileID, $formData);
                        }
-                       
-                       $fd.append('actionName', this._options.action);
-                       $fd.append('className', this._className);
-                       var $additionalParameters = this._getParameters();
-                       for (var $name in $additionalParameters) {
-                               $fd.append('parameters[' + $name + ']', $additionalParameters[$name]);
-                       }
-                       
+               },
+               
+               /**
+                * Sends an AJAX request to upload a file.
+                *
+                * @param        integer                internalFileID
+                * @param        FormData        formData
+                */
+               _sendRequest: function (internalFileID, formData) {
                        var self = this;
                        $.ajax({
                                type: 'POST',
                                url: this._options.url,
                                enctype: 'multipart/form-data',
-                               data: $fd,
+                               data: formData,
                                contentType: false,
                                processData: false,
-                               success: function(data, textStatus, jqXHR) {
-                                       self._success($uploadID, data);
+                               success: function (data, textStatus, jqXHR) {
+                                       self._success(internalFileID, data);
                                },
                                error: $.proxy(this._error, this),
-                               xhr: function() {
+                               xhr: function () {
                                        var $xhr = $.ajaxSettings.xhr();
                                        if ($xhr) {
-                                               $xhr.upload.addEventListener('progress', function(event) {
-                                                       self._progress($uploadID, event);
+                                               $xhr.upload.addEventListener('progress', function (event) {
+                                                       self._progress(internalFileID, event);
                                                }, false);
                                        }
                                        return $xhr;
-                               },
-                               xhrFields: {
-                                       withCredentials: true
                                }
                        });
-               }
-               
-               return $uploadID;
-       },
-       
-       /**
-        * Creates upload matrix for provided files.
-        * 
-        * @param       array<object>           files
-        * @return      integer
-        */
-       _createUploadMatrix: function(files) {
-               if (files.length) {
-                       var $uploadID = this._uploadMatrix.length;
-                       this._uploadMatrix[$uploadID] = [ ];
-                       
-                       for (var $i = 0, $length = files.length; $i < $length; $i++) {
-                               var $file = files[$i];
-                               var $li = this._initFile($file);
-                               
-                               if (!$li.hasClass('uploadFailed')) {
-                                       $li.data('filename', $file.name).data('internalFileID', this._internalFileID++);
-                                       this._uploadMatrix[$uploadID][$i] = $li;
-                               }
-                       }
-                       
-                       return $uploadID;
-               }
-               
-               return null;
-       },
-       
-       /**
-        * Callback for success event.
-        * 
-        * @param       integer         uploadID
-        * @param       object          data
-        */
-       _success: function(uploadID, data) { },
-       
-       /**
-        * Callback for error event.
-        * 
-        * @param       jQuery          jqXHR
-        * @param       string          textStatus
-        * @param       string          errorThrown
-        */
-       _error: function(jqXHR, textStatus, errorThrown) { },
-       
-       /**
-        * Callback for progress event.
-        * 
-        * @param       integer         uploadID
-        * @param       object          event
-        */
-       _progress: function(uploadID, event) {
-               var $percentComplete = Math.round(event.loaded * 100 / event.total);
-               
-               for (var $i in this._uploadMatrix[uploadID]) {
-                       this._uploadMatrix[uploadID][$i].find('progress').attr('value', $percentComplete);
-               }
-       },
-       
-       /**
-        * Returns additional parameters.
-        * 
-        * @return      object
-        */
-       _getParameters: function() {
-               return {};
-       },
-       
-       /**
-        * Initializes list item for uploaded file.
-        * 
-        * @return      jQuery
-        */
-       _initFile: function(file) {
-               return $('<li>' + file.name + ' (' + file.size + ')<progress max="100" /></li>').appendTo(this._fileListSelector);
-       },
-       
-       /**
-        * Shows the fallback overlay (work in progress)
-        */
-       _showOverlay: function() {
-               // create iframe
-               if (this._iframe === null) {
-                       this._iframe = $('<iframe name="__fileUploadIFrame" />').hide().appendTo(document.body);
-               }
+               },
                
-               // create overlay
-               if (!this._overlay) {
-                       this._overlay = $('<div><form enctype="multipart/form-data" method="post" action="' + this._options.url + '" target="__fileUploadIFrame" /></div>').hide().appendTo(document.body);
-                       
-                       var $form = this._overlay.find('form');
-                       $('<dl class="wide"><dd><input type="file" id="__fileUpload" name="' + this._name + '" ' + (this._options.multiple ? 'multiple="true" ' : '') + '/></dd></dl>').appendTo($form);
-                       $('<div class="formSubmit"><input type="submit" value="Upload" accesskey="s" /></div></form>').appendTo($form);
-                       
-                       $('<input type="hidden" name="isFallback" value="1" />').appendTo($form);
-                       $('<input type="hidden" name="actionName" value="' + this._options.action + '" />').appendTo($form);
-                       $('<input type="hidden" name="className" value="' + this._className + '" />').appendTo($form);
-                       var $additionalParameters = this._getParameters();
-                       for (var $name in $additionalParameters) {
-                               $('<input type="hidden" name="' + $name + '" value="' + $additionalParameters[$name] + '" />').appendTo($form);
-                       }
-                       
-                       $form.submit($.proxy(function() {
-                               var $file = {
-                                       name: this._getFilename(),
-                                       size: ''
-                               };
+               /**
+                * Creates upload matrix for provided file and returns its internal file id.
+                *
+                * @param        object                file
+                * @return        integer
+                */
+               _createUploadMatrix: function (file) {
+                       var $li = this._initFile(file);
+                       if (!$li.hasClass('uploadFailed')) {
+                               $li.data('filename', file.name).data('internalFileID', this._internalFileID);
+                               this._uploadMatrix[this._internalFileID++] = $li;
                                
-                               var $uploadID = this._createUploadMatrix([ $file ]);
-                               var self = this;
-                               this._iframe.data('loading', true).off('load').load(function() { self._evaluateResponse($uploadID); });
-                               this._overlay.wcfDialog('close');
-                       }, this));
-               }
-               
-               this._overlay.wcfDialog({
-                       title: WCF.Language.get('wcf.global.button.upload')
-               });
-       },
-       
-       /**
-        * Evaluates iframe response.
-        * 
-        * @param       integer         uploadID
-        */
-       _evaluateResponse: function(uploadID) {
-               var $returnValues = $.parseJSON(this._iframe.contents().find('pre').html());
-               this._success(uploadID, $returnValues);
-       },
-       
-       /**
-        * Returns name of selected file.
-        * 
-        * @return      string
-        */
-       _getFilename: function() {
-               return $('#__fileUpload').val().split('\\').pop();
-       }
-});
-
-/**
- * Default implementation for parallel AJAX file uploads.
- * 
- * @deprecated Use WoltLabSuite/Core/Upload
- */
-WCF.Upload.Parallel = WCF.Upload.extend({
-       /**
-        * @see WCF.Upload.init()
-        */
-       init: function(buttonSelector, fileListSelector, className, options) {
-               // force multiple uploads
-               options = $.extend(true, options || { }, {
-                       multiple: true
-               });
-               
-               this._super(buttonSelector, fileListSelector, className, options);
-       },
-       
-       /**
-        * @see WCF.Upload._upload()
-        */
-       _upload: function() {
-               var $files = this._fileUpload.prop('files');
-               for (var $i = 0, $length = $files.length; $i < $length; $i++) {
-                       var $file = $files[$i];
-                       var $formData = new FormData();
-                       var $internalFileID = this._createUploadMatrix($file);
-                       
-                       if (!this._uploadMatrix[$internalFileID].length) {
-                               continue;
-                       }
-                       
-                       $formData.append('__files[' + $internalFileID + ']', $file);
-                       $formData.append('actionName', this._options.action);
-                       $formData.append('className', this._className);
-                       var $additionalParameters = this._getParameters();
-                       for (var $name in $additionalParameters) {
-                               $formData.append('parameters[' + $name + ']', $additionalParameters[$name]);
-                       }
-                       
-                       this._sendRequest($internalFileID, $formData);
-               }
-       },
-       
-       /**
-        * Sends an AJAX request to upload a file.
-        * 
-        * @param       integer         internalFileID
-        * @param       FormData        formData
-        */
-       _sendRequest: function(internalFileID, formData) {
-               var self = this;
-               $.ajax({
-                       type: 'POST',
-                       url: this._options.url,
-                       enctype: 'multipart/form-data',
-                       data: formData,
-                       contentType: false,
-                       processData: false,
-                       success: function(data, textStatus, jqXHR) {
-                               self._success(internalFileID, data);
-                       },
-                       error: $.proxy(this._error, this),
-                       xhr: function() {
-                               var $xhr = $.ajaxSettings.xhr();
-                               if ($xhr) {
-                                       $xhr.upload.addEventListener('progress', function(event) {
-                                               self._progress(internalFileID, event);
-                                       }, false);
-                               }
-                               return $xhr;
-                       }
-               });
-       },
-       
-       /**
-        * Creates upload matrix for provided file and returns its internal file id.
-        * 
-        * @param       object          file
-        * @return      integer
-        */
-       _createUploadMatrix: function(file) {
-               var $li = this._initFile(file);
-               if (!$li.hasClass('uploadFailed')) {
-                       $li.data('filename', file.name).data('internalFileID', this._internalFileID);
-                       this._uploadMatrix[this._internalFileID++] = $li;
-                       
-                       return this._internalFileID - 1;
-               }
-               
-               return null;
-       },
-       
-       /**
-        * Callback for success event.
-        * 
-        * @param       integer         internalFileID
-        * @param       object          data
-        */
-       _success: function(internalFileID, data) { },
-       
-       /**
-        * Callback for progress event.
-        * 
-        * @param       integer         internalFileID
-        * @param       object          event
-        */
-       _progress: function(internalFileID, event) {
-               var $percentComplete = Math.round(event.loaded * 100 / event.total);
-               
-               this._uploadMatrix[internalFileID].find('progress').attr('value', $percentComplete);
-       },
-       
-       /**
-        * @see WCF.Upload._showOverlay()
-        */
-       _showOverlay: function() {
-               // create iframe
-               if (this._iframe === null) {
-                       this._iframe = $('<iframe name="__fileUploadIFrame" />').hide().appendTo(document.body);
-               }
-               
-               // create overlay
-               if (!this._overlay) {
-                       this._overlay = $('<div><form enctype="multipart/form-data" method="post" action="' + this._options.url + '" target="__fileUploadIFrame" /></div>').hide().appendTo(document.body);
-                       
-                       var $form = this._overlay.find('form');
-                       $('<dl class="wide"><dd><input type="file" id="__fileUpload" name="' + this._name + '" ' + (this._options.multiple ? 'multiple="true" ' : '') + '/></dd></dl>').appendTo($form);
-                       $('<div class="formSubmit"><input type="submit" value="Upload" accesskey="s" /></div></form>').appendTo($form);
-                       
-                       $('<input type="hidden" name="isFallback" value="1" />').appendTo($form);
-                       $('<input type="hidden" name="actionName" value="' + this._options.action + '" />').appendTo($form);
-                       $('<input type="hidden" name="className" value="' + this._className + '" />').appendTo($form);
-                       var $additionalParameters = this._getParameters();
-                       for (var $name in $additionalParameters) {
-                               $('<input type="hidden" name="' + $name + '" value="' + $additionalParameters[$name] + '" />').appendTo($form);
+                               return this._internalFileID - 1;
                        }
                        
-                       $form.submit($.proxy(function() {
-                               var $file = {
-                                       name: this._getFilename(),
-                                       size: ''
-                               };
-                               
-                               var $internalFileID = this._createUploadMatrix($file);
-                               var self = this;
-                               this._iframe.data('loading', true).off('load').load(function() { self._evaluateResponse($internalFileID); });
-                               this._overlay.wcfDialog('close');
-                       }, this));
-               }
-               
-               this._overlay.wcfDialog({
-                       title: WCF.Language.get('wcf.global.button.upload')
-               });
-       },
-       
-       /**
-        * Evaluates iframe response.
-        * 
-        * @param       integer         internalFileID
-        */
-       _evaluateResponse: function(internalFileID) {
-               var $returnValues = $.parseJSON(this._iframe.contents().find('pre').html());
-               this._success(internalFileID, $returnValues);
-       }
-});
-
-/**
- * Namespace for sortables.
- */
-WCF.Sortable = { };
-
-/**
- * Sortable implementation for lists.
- * 
- * @param      string          containerID
- * @param      string          className
- * @param      integer         offset
- * @param      object          options
- */
-WCF.Sortable.List = Class.extend({
-       /**
-        * additional parameters for AJAX request
-        * @var object
-        */
-       _additionalParameters: { },
-       
-       /**
-        * action class name
-        * @var string
-        */
-       _className: '',
-       
-       /**
-        * container id
-        * @var string
-        */
-       _containerID: '',
-       
-       /**
-        * container object
-        * @var jQuery
-        */
-       _container: null,
-       
-       /**
-        * notification object
-        * @var WCF.System.Notification
-        */
-       _notification: null,
-       
-       /**
-        * show order offset
-        * @var integer
-        */
-       _offset: 0,
-       
-       /**
-        * list of options
-        * @var object
-        */
-       _options: { },
-       
-       /**
-        * proxy object
-        * @var WCF.Action.Proxy
-        */
-       _proxy: null,
-       
-       /**
-        * object structure
-        * @var object
-        */
-       _structure: { },
-       
-       /**
-        * Creates a new sortable list.
-        * 
-        * @param       string          containerID
-        * @param       string          className
-        * @param       integer         offset
-        * @param       object          options
-        * @param       boolean         isSimpleSorting
-        * @param       object          additionalParameters
-        */
-       init: function(containerID, className, offset, options, isSimpleSorting, additionalParameters) {
-               this._additionalParameters = additionalParameters || { };
-               this._containerID = $.wcfEscapeID(containerID);
-               this._container = $('#' + this._containerID);
-               this._className = className;
-               this._offset = (offset) ? offset : 0;
-               this._proxy = new WCF.Action.Proxy({
-                       success: $.proxy(this._success, this)
-               });
-               this._structure = { };
+                       return null;
+               },
                
-               // init sortable
-               this._options = $.extend(true, {
-                       axis: 'y',
-                       connectWith: '#' + this._containerID + ' .sortableList',
-                       disableNesting: 'sortableNoNesting',
-                       doNotClear: true,
-                       errorClass: 'sortableInvalidTarget',
-                       forcePlaceholderSize: true,
-                       handle: '',
-                       helper: 'clone',
-                       items: 'li:not(.sortableNoSorting)',
-                       opacity: .6,
-                       placeholder: 'sortablePlaceholder',
-                       tolerance: 'pointer',
-                       toleranceElement: '> span'
-               }, options || { });
+               /**
+                * Callback for success event.
+                *
+                * @param        integer                internalFileID
+                * @param        object                data
+                */
+               _success: function (internalFileID, data) {
+               },
                
-               var sortableList = $('#' + this._containerID + ' .sortableList');
-               if (sortableList.is('tbody') && this._options.helper === 'clone') {
-                       this._options.helper = this._tableRowHelper.bind(this);
+               /**
+                * Callback for progress event.
+                *
+                * @param        integer                internalFileID
+                * @param        object                event
+                */
+               _progress: function (internalFileID, event) {
+                       var $percentComplete = Math.round(event.loaded * 100 / event.total);
                        
-                       // explicitly set column widths to avoid column resizing during dragging
-                       var thead = sortableList.prev('thead');
-                       if (thead) {
-                               thead.find('th').each(function(index, element) {
-                                       element = $(element);
-                                       
-                                       element.width(element.width());
-                               });
-                       }
-               }
-               
-               if (isSimpleSorting) {
-                       sortableList.sortable(this._options);
-               }
-               else {
-                       sortableList.nestedSortable(this._options);
-               }
+                       this._uploadMatrix[internalFileID].find('progress').attr('value', $percentComplete);
+               },
                
-               if (this._className) {
-                       var $formSubmit = this._container.find('.formSubmit');
-                       if (!$formSubmit.length) {
-                               $formSubmit = this._container.next('.formSubmit');
-                               if (!$formSubmit.length) {
-                                       console.debug("[WCF.Sortable.Simple] Unable to find form submit for saving, aborting.");
-                                       return;
+               /**
+                * @see        WCF.Upload._showOverlay()
+                */
+               _showOverlay: function () {
+                       // create iframe
+                       if (this._iframe === null) {
+                               this._iframe = $('<iframe name="__fileUploadIFrame" />').hide().appendTo(document.body);
+                       }
+                       
+                       // create overlay
+                       if (!this._overlay) {
+                               this._overlay = $('<div><form enctype="multipart/form-data" method="post" action="' + this._options.url + '" target="__fileUploadIFrame" /></div>').hide().appendTo(document.body);
+                               
+                               var $form = this._overlay.find('form');
+                               $('<dl class="wide"><dd><input type="file" id="__fileUpload" name="' + this._name + '" ' + (this._options.multiple ? 'multiple="true" ' : '') + '/></dd></dl>').appendTo($form);
+                               $('<div class="formSubmit"><input type="submit" value="Upload" accesskey="s" /></div></form>').appendTo($form);
+                               
+                               $('<input type="hidden" name="isFallback" value="1" />').appendTo($form);
+                               $('<input type="hidden" name="actionName" value="' + this._options.action + '" />').appendTo($form);
+                               $('<input type="hidden" name="className" value="' + this._className + '" />').appendTo($form);
+                               var $additionalParameters = this._getParameters();
+                               for (var $name in $additionalParameters) {
+                                       $('<input type="hidden" name="' + $name + '" value="' + $additionalParameters[$name] + '" />').appendTo($form);
                                }
+                               
+                               $form.submit($.proxy(function () {
+                                       var $file = {
+                                               name: this._getFilename(),
+                                               size: ''
+                                       };
+                                       
+                                       var $internalFileID = this._createUploadMatrix($file);
+                                       var self = this;
+                                       this._iframe.data('loading', true).off('load').load(function () {
+                                               self._evaluateResponse($internalFileID);
+                                       });
+                                       this._overlay.wcfDialog('close');
+                               }, this));
                        }
                        
-                       $formSubmit.children('button[data-type="submit"]').click($.proxy(this._submit, this));
+                       this._overlay.wcfDialog({
+                               title: WCF.Language.get('wcf.global.button.upload')
+                       });
+               },
+               
+               /**
+                * Evaluates iframe response.
+                *
+                * @param        integer                internalFileID
+                */
+               _evaluateResponse: function (internalFileID) {
+                       var $returnValues = $.parseJSON(this._iframe.contents().find('pre').html());
+                       this._success(internalFileID, $returnValues);
                }
-       },
-       
+       });
+}
+else {
+       WCF.System.Worker = Class.extend({
+               _aborted: false,
+               _actionName: "",
+               _callback: {},
+               _className: "",
+               _dialog: {},
+               _proxy: {},
+               _title: "",
+               init: function() {},
+               _success: function() {}
+       });
+       
+       WCF.InlineEditor = Class.extend({
+               _callbacks: {},
+               _dropdowns: {},
+               _elements: {},
+               _notification: {},
+               _options: {},
+               _proxy: {},
+               _triggerElements: {},
+               _updateData: {},
+               init: function() {},
+               _closeAll: function() {},
+               _setOptions: function() {},
+               registerCallback: function() {},
+               _getTriggerElement: function() {},
+               _show: function() {},
+               _validate: function() {},
+               _validateCallbacks: function() {},
+               _success: function() {},
+               _updateState: function() {},
+               _click: function() {},
+               _execute: function() {},
+               _executeCallback: function() {},
+               _hide: function() {}
+       });
+       
+       WCF.Upload = Class.extend({
+               _name: "",
+               _buttonSelector: {},
+               _fileListSelector: {},
+               _fileUpload: {},
+               _className: "",
+               _iframe: {},
+               _internalFileID: 0,
+               _options: {},
+               _uploadMatrix: {},
+               _supportsAJAXUpload: true,
+               _overlay: {},
+               init: function() {},
+               _createButton: function() {},
+               _insertButton: function() {},
+               _removeButton: function() {},
+               _upload: function() {},
+               _createUploadMatrix: function() {},
+               _success: function() {},
+               _error: function() {},
+               _progress: function() {},
+               _getParameters: function() {},
+               _initFile: function() {},
+               _showOverlay: function() {},
+               _evaluateResponse: function() {},
+               _getFilename: function() {}
+       });
+       
+       WCF.Upload.Parallel = WCF.Upload.extend({
+               init: function() {},
+               _upload: function() {},
+               _sendRequest: function() {},
+               _createUploadMatrix: function() {},
+               _success: function() {},
+               _progress: function() {},
+               _showOverlay: function() {},
+               _evaluateResponse: function() {},
+               _name: "",
+               _buttonSelector: {},
+               _fileListSelector: {},
+               _fileUpload: {},
+               _className: "",
+               _iframe: {},
+               _internalFileID: 0,
+               _options: {},
+               _uploadMatrix: {},
+               _supportsAJAXUpload: true,
+               _overlay: {},
+               _createButton: function() {},
+               _insertButton: function() {},
+               _removeButton: function() {},
+               _error: function() {},
+               _getParameters: function() {},
+               _initFile: function() {},
+               _getFilename: function() {}
+       });
+}
+
+/**
+ * Namespace for sortables.
+ */
+WCF.Sortable = { };
+
+if (COMPILER_TARGET_DEFAULT) {
        /**
-        * Fixes the width of the cells of the dragged table row.
-        * 
-        * @param       {Event}         event
-        * @param       {jQuery}        ui
-        * @return      {jQuery}
+        * Sortable implementation for lists.
+        *
+        * @param        string                containerID
+        * @param        string                className
+        * @param        integer                offset
+        * @param        object                options
         */
-       _tableRowHelper: function(event, ui) {
-               ui.children('td').each(function(index, element) {
-                       element = $(element);
-                       
-                       element.width(element.width());
-               });
+       WCF.Sortable.List = Class.extend({
+               /**
+                * additional parameters for AJAX request
+                * @var        object
+                */
+               _additionalParameters: {},
                
-               return ui;
-       },
-       
-       /**
-        * Saves object structure.
-        */
-       _submit: function() {
-               // reset structure
-               this._structure = { };
+               /**
+                * action class name
+                * @var        string
+                */
+               _className: '',
+               
+               /**
+                * container id
+                * @var        string
+                */
+               _containerID: '',
+               
+               /**
+                * container object
+                * @var        jQuery
+                */
+               _container: null,
+               
+               /**
+                * notification object
+                * @var        WCF.System.Notification
+                */
+               _notification: null,
+               
+               /**
+                * show order offset
+                * @var        integer
+                */
+               _offset: 0,
+               
+               /**
+                * list of options
+                * @var        object
+                */
+               _options: {},
+               
+               /**
+                * proxy object
+                * @var        WCF.Action.Proxy
+                */
+               _proxy: null,
+               
+               /**
+                * object structure
+                * @var        object
+                */
+               _structure: {},
                
-               // build structure
-               this._container.find('.sortableList').each($.proxy(function(index, list) {
-                       var $list = $(list);
-                       var $parentID = $list.data('objectID');
+               /**
+                * Creates a new sortable list.
+                *
+                * @param        string                containerID
+                * @param        string                className
+                * @param        integer                offset
+                * @param        object                options
+                * @param        boolean                isSimpleSorting
+                * @param        object                additionalParameters
+                */
+               init: function (containerID, className, offset, options, isSimpleSorting, additionalParameters) {
+                       this._additionalParameters = additionalParameters || {};
+                       this._containerID = $.wcfEscapeID(containerID);
+                       this._container = $('#' + this._containerID);
+                       this._className = className;
+                       this._offset = (offset) ? offset : 0;
+                       this._proxy = new WCF.Action.Proxy({
+                               success: $.proxy(this._success, this)
+                       });
+                       this._structure = {};
                        
-                       if ($parentID !== undefined) {
-                               $list.children(this._options.items).each($.proxy(function(index, listItem) {
-                                       var $objectID = $(listItem).data('objectID');
-                                       
-                                       if (!this._structure[$parentID]) {
-                                               this._structure[$parentID] = [ ];
+                       // init sortable
+                       this._options = $.extend(true, {
+                               axis: 'y',
+                               connectWith: '#' + this._containerID + ' .sortableList',
+                               disableNesting: 'sortableNoNesting',
+                               doNotClear: true,
+                               errorClass: 'sortableInvalidTarget',
+                               forcePlaceholderSize: true,
+                               handle: '',
+                               helper: 'clone',
+                               items: 'li:not(.sortableNoSorting)',
+                               opacity: .6,
+                               placeholder: 'sortablePlaceholder',
+                               tolerance: 'pointer',
+                               toleranceElement: '> span'
+                       }, options || {});
+                       
+                       var sortableList = $('#' + this._containerID + ' .sortableList');
+                       if (sortableList.is('tbody') && this._options.helper === 'clone') {
+                               this._options.helper = this._tableRowHelper.bind(this);
+                               
+                               // explicitly set column widths to avoid column resizing during dragging
+                               var thead = sortableList.prev('thead');
+                               if (thead) {
+                                       thead.find('th').each(function (index, element) {
+                                               element = $(element);
+                                               
+                                               element.width(element.width());
+                                       });
+                               }
+                       }
+                       
+                       if (isSimpleSorting) {
+                               sortableList.sortable(this._options);
+                       }
+                       else {
+                               sortableList.nestedSortable(this._options);
+                       }
+                       
+                       if (this._className) {
+                               var $formSubmit = this._container.find('.formSubmit');
+                               if (!$formSubmit.length) {
+                                       $formSubmit = this._container.next('.formSubmit');
+                                       if (!$formSubmit.length) {
+                                               console.debug("[WCF.Sortable.Simple] Unable to find form submit for saving, aborting.");
+                                               return;
                                        }
-                                       
-                                       this._structure[$parentID].push($objectID);
-                               }, this));
+                               }
+                               
+                               $formSubmit.children('button[data-type="submit"]').click($.proxy(this._submit, this));
                        }
-               }, this));
+               },
                
-               // send request
-               var $parameters = $.extend(true, {
-                       data: {
-                               offset: this._offset,
-                               structure: this._structure
-                       }
-               }, this._additionalParameters);
+               /**
+                * Fixes the width of the cells of the dragged table row.
+                *
+                * @param        {Event}                event
+                * @param        {jQuery}        ui
+                * @return        {jQuery}
+                */
+               _tableRowHelper: function (event, ui) {
+                       ui.children('td').each(function (index, element) {
+                               element = $(element);
+                               
+                               element.width(element.width());
+                       });
+                       
+                       return ui;
+               },
                
-               this._proxy.setOption('data', {
-                       actionName: 'updatePosition',
-                       className: this._className,
-                       interfaceName: 'wcf\\data\\ISortableAction',
-                       parameters: $parameters
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Shows notification upon success.
-        * 
-        * @param       object          data
-        * @param       string          textStatus
-        * @param       jQuery          jqXHR
-        */
-       _success: function(data, textStatus, jqXHR) {
-               if (this._notification === null) {
-                       this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success.edit'));
-               }
+               /**
+                * Saves object structure.
+                */
+               _submit: function () {
+                       // reset structure
+                       this._structure = {};
+                       
+                       // build structure
+                       this._container.find('.sortableList').each($.proxy(function (index, list) {
+                               var $list = $(list);
+                               var $parentID = $list.data('objectID');
+                               
+                               if ($parentID !== undefined) {
+                                       $list.children(this._options.items).each($.proxy(function (index, listItem) {
+                                               var $objectID = $(listItem).data('objectID');
+                                               
+                                               if (!this._structure[$parentID]) {
+                                                       this._structure[$parentID] = [];
+                                               }
+                                               
+                                               this._structure[$parentID].push($objectID);
+                                       }, this));
+                               }
+                       }, this));
+                       
+                       // send request
+                       var $parameters = $.extend(true, {
+                               data: {
+                                       offset: this._offset,
+                                       structure: this._structure
+                               }
+                       }, this._additionalParameters);
+                       
+                       this._proxy.setOption('data', {
+                               actionName: 'updatePosition',
+                               className: this._className,
+                               interfaceName: 'wcf\\data\\ISortableAction',
+                               parameters: $parameters
+                       });
+                       this._proxy.sendRequest();
+               },
                
-               this._notification.show();
-       }
-});
+               /**
+                * Shows notification upon success.
+                *
+                * @param        object                data
+                * @param        string                textStatus
+                * @param        jQuery                jqXHR
+                */
+               _success: function (data, textStatus, jqXHR) {
+                       if (this._notification === null) {
+                               this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success.edit'));
+                       }
+                       
+                       this._notification.show();
+               }
+       });
+}
+else {
+       WCF.Sortable.List = Class.extend({
+               _additionalParameters: {},
+               _className: "",
+               _containerID: "",
+               _container: {},
+               _notification: {},
+               _offset: 0,
+               _options: {},
+               _proxy: {},
+               _structure: {},
+               init: function() {},
+               _tableRowHelper: function() {},
+               _submit: function() {},
+               _success: function() {}
+       });
+}
 
 WCF.Popover = Class.extend({
        /**
@@ -7294,7 +7523,6 @@ jQuery.fn.extend({
                                
                        case 'getDate':
                                return window.__wcf_bc_datePicker.getDate(element);
-                               break;
                        
                        case 'option':
                                if (parameters[0] === 'onClose') {
@@ -7429,185 +7657,202 @@ $.widget('ui.wcfPages', {
  */
 WCF.Category = { };
 
-/**
- * Handles selection of categories.
- */
-WCF.Category.NestedList = Class.extend({
-       /**
-        * list of categories
-        * @var object
-        */
-       _categories: { },
-       
+if (COMPILER_TARGET_DEFAULT) {
        /**
-        * Initializes the WCF.Category.NestedList object.
+        * Handles selection of categories.
         */
-       init: function() {
-               var self = this;
-               $('.jsCategory').each(function(index, category) {
-                       var $category = $(category).data('parentCategoryID', null).change($.proxy(self._updateSelection, self));
-                       self._categories[$category.val()] = $category;
-                       
-                       // find child categories
-                       var $childCategoryIDs = [ ];
-                       $category.parents('li').find('.jsChildCategory').each(function(innerIndex, childCategory) {
-                               var $childCategory = $(childCategory).data('parentCategoryID', $category.val()).change($.proxy(self._updateSelection, self));
-                               self._categories[$childCategory.val()] = $childCategory;
-                               $childCategoryIDs.push($childCategory.val());
+       WCF.Category.NestedList = Class.extend({
+               /**
+                * list of categories
+                * @var        object
+                */
+               _categories: {},
+               
+               /**
+                * Initializes the WCF.Category.NestedList object.
+                */
+               init: function () {
+                       var self = this;
+                       $('.jsCategory').each(function (index, category) {
+                               var $category = $(category).data('parentCategoryID', null).change($.proxy(self._updateSelection, self));
+                               self._categories[$category.val()] = $category;
                                
-                               if ($childCategory.is(':checked')) {
-                                       $category.prop('checked', 'checked');
-                               }
+                               // find child categories
+                               var $childCategoryIDs = [];
+                               $category.parents('li').find('.jsChildCategory').each(function (innerIndex, childCategory) {
+                                       var $childCategory = $(childCategory).data('parentCategoryID', $category.val()).change($.proxy(self._updateSelection, self));
+                                       self._categories[$childCategory.val()] = $childCategory;
+                                       $childCategoryIDs.push($childCategory.val());
+                                       
+                                       if ($childCategory.is(':checked')) {
+                                               $category.prop('checked', 'checked');
+                                       }
+                               });
+                               
+                               $category.data('childCategoryIDs', $childCategoryIDs);
                        });
-                       
-                       $category.data('childCategoryIDs', $childCategoryIDs);
-               });
-       },
-       
-       /**
-        * Updates selection of categories.
-        * 
-        * @param       object          event
-        */
-       _updateSelection: function(event) {
-               var $category = $(event.currentTarget);
-               var $parentCategoryID = $category.data('parentCategoryID');
+               },
                
-               if ($category.is(':checked')) {
-                       // child category
-                       if ($parentCategoryID !== null) {
-                               // mark parent category as checked
-                               this._categories[$parentCategoryID].prop('checked', 'checked');
+               /**
+                * Updates selection of categories.
+                *
+                * @param        object                event
+                */
+               _updateSelection: function (event) {
+                       var $category = $(event.currentTarget);
+                       var $parentCategoryID = $category.data('parentCategoryID');
+                       
+                       if ($category.is(':checked')) {
+                               // child category
+                               if ($parentCategoryID !== null) {
+                                       // mark parent category as checked
+                                       this._categories[$parentCategoryID].prop('checked', 'checked');
+                               }
                        }
-               }
-               else {
-                       // top-level category
-                       if ($parentCategoryID === null) {
-                               // unmark all child categories
-                               var $childCategoryIDs = $category.data('childCategoryIDs');
-                               for (var $i = 0, $length = $childCategoryIDs.length; $i < $length; $i++) {
-                                       this._categories[$childCategoryIDs[$i]].prop('checked', false);
+                       else {
+                               // top-level category
+                               if ($parentCategoryID === null) {
+                                       // unmark all child categories
+                                       var $childCategoryIDs = $category.data('childCategoryIDs');
+                                       for (var $i = 0, $length = $childCategoryIDs.length; $i < $length; $i++) {
+                                               this._categories[$childCategoryIDs[$i]].prop('checked', false);
+                                       }
                                }
                        }
                }
-       }
-});
-
-/**
- * Handles selection of categories.
- */
-WCF.Category.FlexibleCategoryList = Class.extend({
-       /**
-        * category list container
-        * @var jQuery
-        */
-       _list: null,
+       });
        
        /**
-        * list of children per category id
-        * @var object<integer>
+        * Handles selection of categories.
         */
-       _categories: { },
-       
-       init: function(elementID) {
-               this._list = $('#' + elementID);
-               
-               this._buildStructure();
+       WCF.Category.FlexibleCategoryList = Class.extend({
+               /**
+                * category list container
+                * @var        jQuery
+                */
+               _list: null,
                
-               this._list.find('input:checked').each(function() {
-                       $(this).trigger('change');
-               });
+               /**
+                * list of children per category id
+                * @var        object<integer>
+                */
+               _categories: {},
                
-               if (this._list.children('li').length < 2) {
-                       this._list.addClass('flexibleCategoryListDisabled');
-                       return;
-               }
-       },
-       
-       _buildStructure: function() {
-               var self = this;
-               this._list.find('.jsCategory').each(function(i, category) {
-                       var $category = $(category).change(self._updateSelection.bind(self));
-                       var $categoryID = parseInt($category.val());
-                       var $childCategories = [ ];
+               init: function (elementID) {
+                       this._list = $('#' + elementID);
                        
-                       $category.parents('li:eq(0)').find('.jsChildCategory').each(function(j, childCategory) {
-                               var $childCategory = $(childCategory);
-                               $childCategory.data('parentCategory', $category).change(self._updateSelection.bind(self));
-                               
-                               var $childCategoryID = parseInt($childCategory.val());
-                               $childCategories.push($childCategory);
-                               
-                               var $subChildCategories = [ ];
+                       this._buildStructure();
+                       
+                       this._list.find('input:checked').each(function () {
+                               $(this).trigger('change');
+                       });
+                       
+                       if (this._list.children('li').length < 2) {
+                               this._list.addClass('flexibleCategoryListDisabled');
+                               return;
+                       }
+               },
+               
+               _buildStructure: function () {
+                       var self = this;
+                       this._list.find('.jsCategory').each(function (i, category) {
+                               var $category = $(category).change(self._updateSelection.bind(self));
+                               var $categoryID = parseInt($category.val());
+                               var $childCategories = [];
                                
-                               $childCategory.parents('li:eq(0)').find('.jsSubChildCategory').each(function(k, subChildCategory) {
-                                       var $subChildCategory = $(subChildCategory);
-                                       $subChildCategory.data('parentCategory', $childCategory).change(self._updateSelection.bind(self));
-                                       $subChildCategories.push($subChildCategory);
+                               $category.parents('li:eq(0)').find('.jsChildCategory').each(function (j, childCategory) {
+                                       var $childCategory = $(childCategory);
+                                       $childCategory.data('parentCategory', $category).change(self._updateSelection.bind(self));
+                                       
+                                       var $childCategoryID = parseInt($childCategory.val());
+                                       $childCategories.push($childCategory);
+                                       
+                                       var $subChildCategories = [];
+                                       
+                                       $childCategory.parents('li:eq(0)').find('.jsSubChildCategory').each(function (k, subChildCategory) {
+                                               var $subChildCategory = $(subChildCategory);
+                                               $subChildCategory.data('parentCategory', $childCategory).change(self._updateSelection.bind(self));
+                                               $subChildCategories.push($subChildCategory);
+                                       });
+                                       
+                                       self._categories[$childCategoryID] = $subChildCategories;
                                });
                                
-                               self._categories[$childCategoryID] = $subChildCategories;
+                               self._categories[$categoryID] = $childCategories;
                        });
-                       
-                       self._categories[$categoryID] = $childCategories;
-               });
-       },
-       
-       _updateSelection: function(event) {
-               var $category = $(event.currentTarget);
-               var $categoryID = parseInt($category.val());
-               var $parentCategory = $category.data('parentCategory');
+               },
                
-               if ($category.is(':checked')) {
-                       if ($parentCategory) {
-                               $parentCategory.prop('checked', 'checked');
-                               
-                               $parentCategory = $parentCategory.data('parentCategory');
+               _updateSelection: function (event) {
+                       var $category = $(event.currentTarget);
+                       var $categoryID = parseInt($category.val());
+                       var $parentCategory = $category.data('parentCategory');
+                       
+                       if ($category.is(':checked')) {
                                if ($parentCategory) {
                                        $parentCategory.prop('checked', 'checked');
-                               }
-                       }
-               }
-               else {
-                       // uncheck child categories
-                       if (this._categories[$categoryID]) {
-                               for (var $i = 0, $length = this._categories[$categoryID].length; $i < $length; $i++) {
-                                       var $childCategory = this._categories[$categoryID][$i];
-                                       $childCategory.prop('checked', false);
                                        
-                                       var $childCategoryID = parseInt($childCategory.val());
-                                       if (this._categories[$childCategoryID]) {
-                                               for (var $j = 0, $innerLength = this._categories[$childCategoryID].length; $j < $innerLength; $j++) {
-                                                       this._categories[$childCategoryID][$j].prop('checked', false);
-                                               }
+                                       $parentCategory = $parentCategory.data('parentCategory');
+                                       if ($parentCategory) {
+                                               $parentCategory.prop('checked', 'checked');
                                        }
                                }
                        }
-                       
-                       // uncheck direct parent if it has no more checked children
-                       if ($parentCategory) {
-                               var $parentCategoryID = parseInt($parentCategory.val());
-                               for (var $i = 0, $length = this._categories[$parentCategoryID].length; $i < $length; $i++) {
-                                       if (this._categories[$parentCategoryID][$i].prop('checked')) {
-                                               // at least one child is checked, break
-                                               return;
+                       else {
+                               // uncheck child categories
+                               if (this._categories[$categoryID]) {
+                                       for (var $i = 0, $length = this._categories[$categoryID].length; $i < $length; $i++) {
+                                               var $childCategory = this._categories[$categoryID][$i];
+                                               $childCategory.prop('checked', false);
+                                               
+                                               var $childCategoryID = parseInt($childCategory.val());
+                                               if (this._categories[$childCategoryID]) {
+                                                       for (var $j = 0, $innerLength = this._categories[$childCategoryID].length; $j < $innerLength; $j++) {
+                                                               this._categories[$childCategoryID][$j].prop('checked', false);
+                                                       }
+                                               }
                                        }
                                }
                                
-                               $parentCategory = $parentCategory.data('parentCategory');
+                               // uncheck direct parent if it has no more checked children
                                if ($parentCategory) {
-                                       $parentCategoryID = parseInt($parentCategory.val());
+                                       var $parentCategoryID = parseInt($parentCategory.val());
                                        for (var $i = 0, $length = this._categories[$parentCategoryID].length; $i < $length; $i++) {
                                                if (this._categories[$parentCategoryID][$i].prop('checked')) {
                                                        // at least one child is checked, break
                                                        return;
                                                }
                                        }
+                                       
+                                       $parentCategory = $parentCategory.data('parentCategory');
+                                       if ($parentCategory) {
+                                               $parentCategoryID = parseInt($parentCategory.val());
+                                               for (var $i = 0, $length = this._categories[$parentCategoryID].length; $i < $length; $i++) {
+                                                       if (this._categories[$parentCategoryID][$i].prop('checked')) {
+                                                               // at least one child is checked, break
+                                                               return;
+                                                       }
+                                               }
+                                       }
                                }
                        }
                }
-       }
-});
+       });
+}
+else {
+       WCF.Category.NestedList = Class.extend({
+               _categories: {},
+               init: function() {},
+               _updateSelection: function() {}
+       });
+       
+       WCF.Category.FlexibleCategoryList = Class.extend({
+               _list: {},
+               _categories: {},
+               init: function() {},
+               _buildStructure: function() {},
+               _updateSelection: function() {}
+       });
+}
 
 /**
  * Initializes WCF.Condition namespace.