Enable upload related classes for guests in accelerated mode
authorAlexander Ebert <ebert@woltlab.com>
Thu, 13 Feb 2020 09:38:52 +0000 (10:38 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Thu, 13 Feb 2020 09:38:52 +0000 (10:38 +0100)
These classes are required to support attachment in the contact form.

wcfsetup/install/files/js/WCF.Attachment.js
wcfsetup/install/files/js/WCF.js

index b6ac5d74bd9c61b851ad6a0517757f7c51e2799d..031726505900541598fc4cf2983084d582b88ff5 100644 (file)
  */
 WCF.Attachment = {};
 
-if (COMPILER_TARGET_DEFAULT) {
+/**
+ * Attachment upload function
+ *
+ * @see        WCF.Upload
+ */
+WCF.Attachment.Upload = WCF.Upload.extend({
        /**
-        * Attachment upload function
-        *
-        * @see        WCF.Upload
+        * list of upload ids which should be automatically inserted
+        * @var        array<integer>
         */
-       WCF.Attachment.Upload = WCF.Upload.extend({
-               /**
-                * list of upload ids which should be automatically inserted
-                * @var        array<integer>
-                */
-               _autoInsert: [],
-               
-               /**
-                * reference to 'Insert All' button
-                * @var        jQuery
-                */
-               _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,
+       _autoInsert: [],
+       
+       /**
+        * reference to 'Insert All' button
+        * @var        jQuery
+        */
+       _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: {},
+       
+       /**
+        * additional options
+        * @var Object
+        */
+       _options: {},
+       
+       /**
+        * @see        WCF.Upload.init()
+        */
+       init: function (buttonSelector, fileListSelector, objectType, objectID, tmpHash, parentObjectID, maxUploads, editorId, options) {
+               this._super(buttonSelector, fileListSelector, 'wcf\\data\\attachment\\AttachmentAction', {
+                       multiple: true,
+                       maxUploads: maxUploads
+               });
                
-               /**
-                * temporary hash to identify uploaded attachments
-                * @var        string
-                */
-               _tmpHash: '',
+               this._autoInsert = [];
+               this._objectType = objectType;
+               this._objectID = parseInt(objectID);
+               this._tmpHash = tmpHash;
+               this._parentObjectID = parseInt(parentObjectID);
+               this._editorId = editorId;
+               this._options = $.extend(true, this._options, options || {});
                
-               /**
-                * id of the parent object of the object the uploaded attachments belongs to
-                * @var        string
-                */
-               _parentObjectID: 0,
+               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));
                
-               /**
-                * editor id
-                * @var        string
-                */
-               _editorId: '',
+               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));
                
-               /**
-                * replace img element on load
-                * @var Object
-                */
-               _replaceOnLoad: {},
+               this._makeSortable();
                
-               /**
-                * additional options
-                * @var Object
-                */
-               _options: {},
+               // for backwards compatibility, the object is still created but only inserted
+               // if an editor is used
+               this._insertAllButton = $('<p class="button jsButtonAttachmentInsertAll">' + WCF.Language.get('wcf.attachment.insertAll') + '</p>').hide();
                
-               /**
-                * @see        WCF.Upload.init()
-                */
-               init: function (buttonSelector, fileListSelector, objectType, objectID, tmpHash, parentObjectID, maxUploads, editorId, options) {
-                       this._super(buttonSelector, fileListSelector, 'wcf\\data\\attachment\\AttachmentAction', {
-                               multiple: true,
-                               maxUploads: maxUploads
-                       });
-                       
-                       this._autoInsert = [];
-                       this._objectType = objectType;
-                       this._objectID = parseInt(objectID);
-                       this._tmpHash = tmpHash;
-                       this._parentObjectID = parseInt(parentObjectID);
-                       this._editorId = editorId;
-                       this._options = $.extend(true, this._options, options || {});
+               if (this._editorId) {
+                       this._insertAllButton.appendTo(this._buttonSelector);
+                       this._insertAllButton.click($.proxy(this._insertAll, this));
                        
-                       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();
+                       if (this._fileListSelector.children('li:not(.uploadFailed)').length) {
+                               this._insertAllButton.show();
+                       }
                        
-                       // for backwards compatibility, the object is still created but only inserted
-                       // if an editor is used
-                       this._insertAllButton = $('<p class="button jsButtonAttachmentInsertAll">' + WCF.Language.get('wcf.attachment.insertAll') + '</p>').hide();
+                       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));
                        
-                       if (this._editorId) {
-                               this._insertAllButton.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', 'autosaveMetaData_' + this._editorId, (function (data) {
+                               if (!data.tmpHashes || !Array.isArray(data.tmpHashes)) {
+                                       data.tmpHashes = [];
                                }
                                
-                               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));
+                               var index = data.tmpHashes.indexOf(tmpHash);
                                
-                               WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'autosaveMetaData_' + this._editorId, (function (data) {
-                                       if (!data.tmpHashes || !Array.isArray(data.tmpHashes)) {
-                                               data.tmpHashes = [];
+                               var count = this._fileListSelector.children('li:not(.uploadFailed)').length;
+                               if (count > 0) {
+                                       if (index === -1) {
+                                               data.tmpHashes.push(tmpHash);
                                        }
+                               }
+                               else if (index !== -1) {
+                                       data.tmpHashes.splice(index);
+                               }
+                       }).bind(this));
+                       
+                       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' || ~~thumbnail > 0);
                                        
-                                       var index = data.tmpHashes.indexOf(tmpHash);
+                                       var image = elCreate('img');
+                                       image.className = 'woltlabAttachment';
+                                       image.src = images[attachmentId][(thumbnail ? 'thumbnailUrl' : 'url')];
+                                       elData(image, 'attachment-id', attachmentId);
                                        
-                                       var count = this._fileListSelector.children('li:not(.uploadFailed)').length;
-                                       if (count > 0) {
-                                               if (index === -1) {
-                                                       data.tmpHashes.push(tmpHash);
-                                               }
-                                       }
-                                       else if (index !== -1) {
-                                               data.tmpHashes.splice(index);
-                                       }
-                               }).bind(this));
-                               
-                               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' || ~~thumbnail > 0);
-                                               
-                                               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.removeAllListeners('com.woltlab.wcf.redactor2', 'autosaveMetaData_' + this._editorId);
+                                       var float = data.attributes[1] || 'none';
+                                       if (float === 'left') image.classList.add('messageFloatObjectLeft');
+                                       else if (float === 'right') image.classList.add('messageFloatObjectRight');
                                        
-                                       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);
-                       
-                       var callbackUploadId = (function(uploadId) {
-                               if (replace === null) {
-                                       this._autoInsert.push(uploadId);
-                               }
-                               else {
-                                       this._replaceOnLoad[uploadId] = replace;
+                                       var metacode = data.metacode;
+                                       metacode.parentNode.insertBefore(image, metacode);
+                                       elRemove(metacode);
+                                       
+                                       data.cancel = true;
                                }
-
-                               data.uploadID = uploadId;
-                       }).bind(this);
+                       }).bind(this));
                        
-                       if (data.file) {
-                               $uploadID = this._upload(undefined, data.file, undefined, callbackUploadId);
+                       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.removeAllListeners('com.woltlab.wcf.redactor2', 'autosaveMetaData_' + this._editorId);
+                               
+                               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);
+               
+               var callbackUploadId = (function(uploadId) {
+                       if (replace === null) {
+                               this._autoInsert.push(uploadId);
                        }
                        else {
-                               $uploadID = this._upload(undefined, undefined, data.blob, callbackUploadId);
-                               replace = data.replace || null;
+                               this._replaceOnLoad[uploadId] = replace;
                        }
-               },
+
+                       data.uploadID = uploadId;
+               }).bind(this);
                
-               /**
-                * 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')
-                                       };
-                               }
-                       });
-                       
-                       return images;
-               },
+               if (data.file) {
+                       $uploadID = this._upload(undefined, data.file, undefined, callbackUploadId);
+               }
+               else {
+                       $uploadID = this._upload(undefined, undefined, data.blob, callbackUploadId);
+                       replace = data.replace || null;
+               }
+       },
+       
+       /**
+        * Sets the attachments representing an image.
+        *
+        * @return      {Object}
+        */
+       _getImageAttachments: function () {
+               var images = {};
                
-               /**
-                * Adds parameters for the inline editor.
-                *
-                * @param        object                data
-                */
-               _submitInline: function (data) {
-                       if (this._tmpHash) {
-                               data.tmpHash = this._tmpHash;
-                               
-                               var metaData = {};
-                               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'getMetaData_' + this._editorId, metaData);
-                               if (metaData.tmpHashes && Array.isArray(metaData.tmpHashes) && metaData.tmpHashes.length > 0) {
-                                       data.tmpHash += ',' + metaData.tmpHashes.join(',');
-                               }
+               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')
+                               };
                        }
-               },
+               });
                
-               /**
-                * 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');
+               return images;
+       },
+       
+       /**
+        * Adds parameters for the inline editor.
+        *
+        * @param        object                data
+        */
+       _submitInline: function (data) {
+               if (this._tmpHash) {
+                       data.tmpHash = this._tmpHash;
                        
-                       // 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;
+                       var metaData = {};
+                       WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'getMetaData_' + this._editorId, metaData);
+                       if (metaData.tmpHashes && Array.isArray(metaData.tmpHashes) && metaData.tmpHashes.length > 0) {
+                               data.tmpHash += ',' + metaData.tmpHashes.join(',');
+                       }
+               }
+       },
+       
+       /**
+        * 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);
                        }
                        
-                       // remove previous errors
-                       $innerError.remove();
+                       $innerError.html($errorMessage);
                        
-                       return true;
-               },
+                       return false;
+               }
                
-               /**
-                * 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();
-                       }
+               // 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();
+               }
+               
+               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')
+                       });
+               }
+       },
+       
+       /**
+        * @see        WCF.Upload._upload()
+        */
+       _upload: function (event, file, blob, callbackUploadId) {
+               var _super = this._super.bind(this);
+               
+               require([
+                       'WoltLabSuite/Core/FileUtil',
+                       'WoltLabSuite/Core/Image/ImageUtil',
+                       'WoltLabSuite/Core/Image/Resizer',
+                       'WoltLabSuite/Core/Ajax/Status'
+               ], (function (FileUtil, ImageUtil, ImageResizer, AjaxStatus) {
+                       AjaxStatus.show();
                        
-                       if (!$listItems.length) {
-                               this._fileListSelector.hide();
-                       }
+                       var files = [];
                        
-                       if (this._editorId && data.button) {
-                               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'deleteAttachment_' + this._editorId, {
-                                       attachmentId: data.button.data('objectID')
-                               });
+                       if (file) {
+                               files.push(file);
+                       }
+                       else if (blob) {
+                               files.push(FileUtil.blobToFile(blob, 'pasted-from-clipboard'));
+                       }
+                       else {
+                               files = this._fileUpload.prop('files');
                        }
-               },
-               
-               /**
-                * @see        WCF.Upload._upload()
-                */
-               _upload: function (event, file, blob, callbackUploadId) {
-                       var _super = this._super.bind(this);
                        
-                       require([
-                               'WoltLabSuite/Core/FileUtil',
-                               'WoltLabSuite/Core/Image/ImageUtil',
-                               'WoltLabSuite/Core/Image/Resizer',
-                               'WoltLabSuite/Core/Ajax/Status'
-                       ], (function (FileUtil, ImageUtil, ImageResizer, AjaxStatus) {
-                               AjaxStatus.show();
+                       // We resolve with the unaltered list of files in case auto scaling is disabled.
+                       var promise = Promise.resolve(files);
+                       
+                       if (this._options.autoScale && this._options.autoScale.enable) {
+                               var maxSize = this._buttonSelector.data('maxSize');
                                
-                               var files = [];
+                               var resizer = new ImageResizer();
                                
-                               if (file) {
-                                       files.push(file);
-                               }
-                               else if (blob) {
-                                       files.push(FileUtil.blobToFile(blob, 'pasted-from-clipboard'));
-                               }
-                               else {
-                                       files = this._fileUpload.prop('files');
+                               // Resize the images in series.
+                               // As our resizer is based on Pica it will use multiple workers per image if possible.
+                               promise = Array.prototype.reduce.call(files, (function (acc, file) {
+                                       return acc.then((function (arr) {
+                                               var timeout = new Promise(function (resolve, reject) {
+                                                       // We issue one timeout per image, thus multiple timeout
+                                                       // handlers will run in parallel
+                                                       setTimeout(function () {
+                                                               resolve(file);
+                                                       }, 10000);
+                                               });
+                                               
+                                               var promise = resizer.loadFile(file)
+                                                       .then((function (result) {
+                                                               var exif = result.exif;
+                                                               var maxWidth = this._options.autoScale.maxWidth;
+                                                               var maxHeight = this._options.autoScale.maxHeight;
+                                                               var quality = this._options.autoScale.quality;
+                                                               
+                                                               if (window.devicePixelRatio >= 2) {
+                                                                       var realWidth = window.screen.width * window.devicePixelRatio;
+                                                                       var realHeight = window.screen.height * window.devicePixelRatio;
+                                                                       // Check whether the width of the image is roughly the width of the physical screen, and
+                                                                       // the height of the image is at least the height of the physical screen.
+                                                                       if (realWidth - 10 < result.image.width && result.image.width < realWidth + 10 && realHeight - 10 < result.image.height) {
+                                                                               // This appears to be a screenshot from a HiDPI device in portrait mode: Scale to logical size
+                                                                               maxWidth = Math.min(maxWidth, window.screen.width);
+                                                                       }
+                                                               }
+                                                               
+                                                               return resizer.resize(result.image, maxWidth, maxHeight, quality, file.size > maxSize, timeout)
+                                                                       .then((function (resizedImage) {
+                                                                               // Check whether the image actually was resized
+                                                                               if (resizedImage === undefined) {
+                                                                                       return file;
+                                                                               }
+                                                                               
+                                                                               var fileType = this._options.autoScale.fileType;
+                                                                               
+                                                                               if (this._options.autoScale.fileType === 'keep' || ImageUtil.containsTransparentPixels(resizedImage)) {
+                                                                                       fileType = file.type;
+                                                                               }
+                                                                               
+                                                                               return resizer.saveFile({
+                                                                                       exif: exif,
+                                                                                       image: resizedImage
+                                                                               }, file.name, fileType, quality);
+                                                                       }).bind(this))
+                                                                       .then(function (resizedFile) {
+                                                                               if (resizedFile.size > file.size) {
+                                                                                       console.debug('[WCF.Attachment] File size of "' + file.name + '" increased, uploading untouched image.');
+                                                                                       return file;
+                                                                               }
+                                                                               
+                                                                               return resizedFile;
+                                                                       });
+                                                       }).bind(this))
+                                                       .catch(function (error) {
+                                                               console.debug('[WCF.Attachment] Failed to resize image "' + file.name + '":', error);
+                                                               return file;
+                                                       });
+                                               
+                                               return Promise.race([timeout, promise])
+                                                       .then(function (file) {
+                                                               arr.push(file);
+                                                               return arr;
+                                                       });
+                                       }).bind(this));
+                               }).bind(this), Promise.resolve([]));
+                       }
+                       
+                       promise.then((function (files) {
+                               var uploadID = undefined;
+                               
+                               if (this._validateLimit()) {
+                                       uploadID = _super(event, undefined, undefined, files);
                                }
                                
-                               // We resolve with the unaltered list of files in case auto scaling is disabled.
-                               var promise = Promise.resolve(files);
+                               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();
+                               }
                                
-                               if (this._options.autoScale && this._options.autoScale.enable) {
-                                       var maxSize = this._buttonSelector.data('maxSize');
-                                       
-                                       var resizer = new ImageResizer();
-                                       
-                                       // Resize the images in series.
-                                       // As our resizer is based on Pica it will use multiple workers per image if possible.
-                                       promise = Array.prototype.reduce.call(files, (function (acc, file) {
-                                               return acc.then((function (arr) {
-                                                       var timeout = new Promise(function (resolve, reject) {
-                                                               // We issue one timeout per image, thus multiple timeout
-                                                               // handlers will run in parallel
-                                                               setTimeout(function () {
-                                                                       resolve(file);
-                                                               }, 10000);
-                                                       });
-                                                       
-                                                       var promise = resizer.loadFile(file)
-                                                               .then((function (result) {
-                                                                       var exif = result.exif;
-                                                                       var maxWidth = this._options.autoScale.maxWidth;
-                                                                       var maxHeight = this._options.autoScale.maxHeight;
-                                                                       var quality = this._options.autoScale.quality;
-                                                                       
-                                                                       if (window.devicePixelRatio >= 2) {
-                                                                               var realWidth = window.screen.width * window.devicePixelRatio;
-                                                                               var realHeight = window.screen.height * window.devicePixelRatio;
-                                                                               // Check whether the width of the image is roughly the width of the physical screen, and
-                                                                               // the height of the image is at least the height of the physical screen.
-                                                                               if (realWidth - 10 < result.image.width && result.image.width < realWidth + 10 && realHeight - 10 < result.image.height) {
-                                                                                       // This appears to be a screenshot from a HiDPI device in portrait mode: Scale to logical size
-                                                                                       maxWidth = Math.min(maxWidth, window.screen.width);
-                                                                               }
-                                                                       }
-                                                                       
-                                                                       return resizer.resize(result.image, maxWidth, maxHeight, quality, file.size > maxSize, timeout)
-                                                                               .then((function (resizedImage) {
-                                                                                       // Check whether the image actually was resized
-                                                                                       if (resizedImage === undefined) {
-                                                                                               return file;
-                                                                                       }
-                                                                                       
-                                                                                       var fileType = this._options.autoScale.fileType;
-                                                                                       
-                                                                                       if (this._options.autoScale.fileType === 'keep' || ImageUtil.containsTransparentPixels(resizedImage)) {
-                                                                                               fileType = file.type;
-                                                                                       }
-                                                                                       
-                                                                                       return resizer.saveFile({
-                                                                                               exif: exif,
-                                                                                               image: resizedImage
-                                                                                       }, file.name, fileType, quality);
-                                                                               }).bind(this))
-                                                                               .then(function (resizedFile) {
-                                                                                       if (resizedFile.size > file.size) {
-                                                                                               console.debug('[WCF.Attachment] File size of "' + file.name + '" increased, uploading untouched image.');
-                                                                                               return file;
-                                                                                       }
-                                                                                       
-                                                                                       return resizedFile;
-                                                                               });
-                                                               }).bind(this))
-                                                               .catch(function (error) {
-                                                                       console.debug('[WCF.Attachment] Failed to resize image "' + file.name + '":', error);
-                                                                       return file;
-                                                               });
-                                                       
-                                                       return Promise.race([timeout, promise])
-                                                               .then(function (file) {
-                                                                       arr.push(file);
-                                                                       return arr;
-                                                               });
-                                               }).bind(this));
-                                       }).bind(this), Promise.resolve([]));
+                               if (typeof callbackUploadId === 'function') {
+                                       callbackUploadId(uploadID);
                                }
                                
-                               promise.then((function (files) {
-                                       var uploadID = undefined;
-                                       
-                                       if (this._validateLimit()) {
-                                               uploadID = _super(event, undefined, undefined, files);
-                                       }
-                                       
-                                       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();
-                                       }
-                                       
-                                       if (typeof callbackUploadId === 'function') {
-                                               callbackUploadId(uploadID);
-                                       }
-                                       
-                                       return uploadID;
-                               }).bind(this))
-                               .catch(function (error) {
-                                       console.debug('[WCF.Attachment] Failed to upload attachments:', error);
-                               })
-                               .finally(AjaxStatus.hide);
-                       }).bind(this), function (error) {
-                               console.debug('[WCF.Attachment] Failed to load modules:', error);
-                       });
-               },
+                               return uploadID;
+                       }).bind(this))
+                       .catch(function (error) {
+                               console.debug('[WCF.Attachment] Failed to upload attachments:', error);
+                       })
+                       .finally(AjaxStatus.hide);
+               }).bind(this), function (error) {
+                       console.debug('[WCF.Attachment] Failed to load modules:', error);
+               });
+       },
+       
+       /**
+        * @see        WCF.Upload._createUploadMatrix()
+        */
+       _createUploadMatrix: function (files) {
+               // remove failed uploads
+               this._fileListSelector.children('li.uploadFailed').remove();
                
-               /**
-                * @see        WCF.Upload._createUploadMatrix()
-                */
-               _createUploadMatrix: function (files) {
-                       // remove failed uploads
-                       this._fileListSelector.children('li.uploadFailed').remove();
-                       
-                       return this._super(files);
-               },
+               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();
                
-               /**
-                * @see        WCF.Upload._getParameters()
-                */
-               _getParameters: function () {
-                       return {
-                               objectType: this._objectType,
-                               objectID: this._objectID,
-                               tmpHash: this._tmpHash,
-                               parentObjectID: this._parentObjectID
-                       };
-               },
+               // validate file size
+               if (this._buttonSelector.data('maxSize') < file.size) {
+                       // remove progress bar
+                       $li.find('progress').remove();
+                       
+                       // upload icon
+                       $li.children('.fa-spinner').removeClass('fa-spinner').addClass('fa-ban');
+                       
+                       // error message
+                       $li.find('div > div').append($('<small class="innerError">' + WCF.Language.get('wcf.attachment.upload.error.tooLarge') + '</small>'));
+                       $li.addClass('uploadFailed');
+               }
                
-               /**
-                * @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();
+               return $li;
+       },
+       
+       /**
+        * Returns true if thumbnails are enabled and should be
+        * used instead of the original images.
+        *
+        * @return      {boolean}
+        * @protected
+        */
+       _useThumbnail: function() {
+               return elDataBool(this._fileListSelector[0], 'enable-thumbnails');
+       },
+       
+       /**
+        * @see        WCF.Upload._success()
+        */
+       _success: function (uploadID, data) {
+               var attachmentData;
+               for (var $i in this._uploadMatrix[uploadID]) {
+                       if (!this._uploadMatrix[uploadID].hasOwnProperty($i)) {
+                               continue;
+                       }
                        
-                       // validate file size
-                       if (this._buttonSelector.data('maxSize') < file.size) {
-                               // remove progress bar
-                               $li.find('progress').remove();
+                       // get li
+                       var $li = this._uploadMatrix[uploadID][$i];
+                       
+                       // remove progress bar
+                       $li.find('progress').remove();
+                       
+                       // get filename and check result
+                       var $filename = $li.data('filename');
+                       var $internalFileID = $li.data('internalFileID');
+                       if (data.returnValues && data.returnValues.attachments[$internalFileID]) {
+                               attachmentData = data.returnValues.attachments[$internalFileID];
                                
-                               // upload icon
-                               $li.children('.fa-spinner').removeClass('fa-spinner').addClass('fa-ban');
+                               // 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);
+                               }
                                
-                               // error message
-                               $li.find('div > div').append($('<small class="innerError">' + WCF.Language.get('wcf.attachment.upload.error.tooLarge') + '</small>'));
-                               $li.addClass('uploadFailed');
-                       }
-                       
-                       return $li;
-               },
-               
-               /**
-                * Returns true if thumbnails are enabled and should be
-                * used instead of the original images.
-                *
-                * @return      {boolean}
-                * @protected
-                */
-               _useThumbnail: function() {
-                       return elDataBool(this._fileListSelector[0], 'enable-thumbnails');
-               },
-               
-               /**
-                * @see        WCF.Upload._success()
-                */
-               _success: function (uploadID, data) {
-                       var attachmentData;
-                       for (var $i in this._uploadMatrix[uploadID]) {
-                               if (!this._uploadMatrix[uploadID].hasOwnProperty($i)) {
-                                       continue;
+                               // update attachment link
+                               var $link = $('<a href=""></a>');
+                               $link.text($filename).attr('href', attachmentData.url);
+                               $link[0].target = '_blank';
+                               
+                               if (attachmentData.isImage != 0) {
+                                       $link.addClass('jsImageViewer').attr('title', $filename);
                                }
+                               $li.find('p').empty().append($link);
                                
-                               // get li
-                               var $li = this._uploadMatrix[uploadID][$i];
+                               // update file size
+                               $li.find('small').append(attachmentData.formattedFilesize);
                                
-                               // remove progress bar
-                               $li.find('progress').remove();
+                               // 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);
                                
-                               // 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('objectID', attachmentData.attachmentID);
+                               
+                               if (this._editorId) {
+                                       if (attachmentData.tinyURL || (!this._useThumbnail() && attachmentData.isImage)) {
+                                               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.data('height', attachmentData.height);
-                                               $li.data('width', attachmentData.width);
-                                               elData($li[0], 'is-image', attachmentData.isImage);
+                                               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));
                                        }
-                                       // show file icon
                                        else {
-                                               $li.children('.fa-spinner').removeClass('fa-spinner').addClass('fa-' + attachmentData.iconName);
-                                       }
-                                       
-                                       // update attachment link
-                                       var $link = $('<a href=""></a>');
-                                       $link.text($filename).attr('href', attachmentData.url);
-                                       $link[0].target = '_blank';
-                                       
-                                       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 || (!this._useThumbnail() && attachmentData.isImage)) {
-                                                       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));
-                                               }
-                                       }
-                                       
-                                       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;
+                                               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));
                                        }
                                }
-                               else {
-                                       // upload icon
-                                       $li.children('.fa-spinner').removeClass('fa-spinner').addClass('fa-ban');
-                                       var $errorMessage = '';
-                                       
-                                       // error handling
-                                       if (data.returnValues && data.returnValues.errors[$internalFileID]) {
-                                               var errorData = data.returnValues.errors[$internalFileID];
-                                               $errorMessage = errorData.errorType;
-                                               
-                                               if ($errorMessage === 'uploadFailed' && errorData.additionalData.phpLimitExceeded) {
-                                                       $errorMessage = 'uploadPhpLimit';
+                               
+                               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
+                                                       });
                                                }
                                        }
-                                       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');
+                                       this._replaceOnLoad[uploadID] = null;
                                }
+                       }
+                       else {
+                               // upload icon
+                               $li.children('.fa-spinner').removeClass('fa-spinner').addClass('fa-ban');
+                               var $errorMessage = '';
                                
-                               if (WCF.inArray(uploadID, this._autoInsert)) {
-                                       this._autoInsert.splice(this._autoInsert.indexOf(uploadID), 1);
+                               // error handling
+                               if (data.returnValues && data.returnValues.errors[$internalFileID]) {
+                                       var errorData = data.returnValues.errors[$internalFileID];
+                                       $errorMessage = errorData.errorType;
                                        
-                                       if (!$li.hasClass('uploadFailed')) {
-                                               var btn = $li.find('.jsButtonAttachmentInsertThumbnail');
-                                               if (!btn.length) btn = $li.find('.jsButtonAttachmentInsertFull');
-                                               
-                                               btn.trigger('click');
+                                       if ($errorMessage === 'uploadFailed' && errorData.additionalData.phpLimitExceeded) {
+                                               $errorMessage = 'uploadPhpLimit';
                                        }
                                }
+                               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');
                        }
                        
-                       this._makeSortable();
-                       
-                       if (this._fileListSelector.children('li:not(.uploadFailed)').length) {
-                               this._insertAllButton.show();
-                       }
-                       else {
-                               this._insertAllButton.hide();
+                       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');
+                                       
+                                       btn.trigger('click');
+                               }
                        }
-                       
-                       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')
-                       });
-               },
+               this._makeSortable();
                
-               /**
-                * Inserts all attachments at once.
-                */
-               _insertAll: function () {
-                       var attachment, button, preferThumbnail = this._useThumbnail();
-                       for (var i = 0, length = this._fileListSelector[0].childNodes.length; i < length; i++) {
-                               attachment = this._fileListSelector[0].childNodes[i];
-                               if (attachment.nodeName === 'LI' && !attachment.classList.contains('uploadFailed')) {
-                                       button = null;
-                                       if (preferThumbnail) {
-                                               button = elBySel('.jsButtonAttachmentInsertThumbnail, .jsButtonAttachmentInsertPlain', attachment);
-                                       }
-
-                                       if (button === null) {
-                                               button = elBySel('.jsButtonAttachmentInsertFull, .jsButtonAttachmentInsertPlain', attachment);
-                                       }
+               if (this._fileListSelector.children('li:not(.uploadFailed)').length) {
+                       this._insertAllButton.show();
+               }
+               else {
+                       this._insertAllButton.hide();
+               }
+               
+               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 () {
+               var attachment, button, preferThumbnail = this._useThumbnail();
+               for (var i = 0, length = this._fileListSelector[0].childNodes.length; i < length; i++) {
+                       attachment = this._fileListSelector[0].childNodes[i];
+                       if (attachment.nodeName === 'LI' && !attachment.classList.contains('uploadFailed')) {
+                               button = null;
+                               if (preferThumbnail) {
+                                       button = elBySel('.jsButtonAttachmentInsertThumbnail, .jsButtonAttachmentInsertPlain', attachment);
+                               }
 
-                                       window.jQuery(button).trigger('click');
+                               if (button === null) {
+                                       button = elBySel('.jsButtonAttachmentInsertFull, .jsButtonAttachmentInsertPlain', attachment);
                                }
+
+                               window.jQuery(button).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;
+               }
                
-               /**
-                * @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>'));
-                               }
-                       });
-               },
+               $attachments.addClass('sortableAttachment').children('img').addClass('sortableNode');
                
-               /**
-                * Initializes sorting for uploaded attachments.
-                */
-               _makeSortable: function () {
-                       var $attachments = this._fileListSelector.children('li:not(.uploadFailed)');
-                       if (!$attachments.length) {
-                               return;
-                       }
+               if (!this._fileListSelector.hasClass('sortableList')) {
+                       this._fileListSelector.addClass('sortableList');
                        
-                       $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'));
-                                                               });
-                                                               
-                                                               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
-                                                                                       }
+                       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
                                                                                }
-                                                                       });
-                                                               }
-                                                       }).bind(this)
-                                               }, true);
-                                       }
-                               }).bind(this));
-                       }
+                                                                       }
+                                                               });
+                                                       }
+                                               }).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 ed50680e942df1fe07e18f4433ded8d47b872d20..239bb115641c463c668191b5280354a0aff1eb1e 100755 (executable)
@@ -1698,200 +1698,200 @@ WCF.Action.SimpleProxy = Class.extend({
        }
 });
 
-if (COMPILER_TARGET_DEFAULT) {
+/**
+ * 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: '',
+       
        /**
-        * Basic implementation for AJAXProxy-based deletion.
+        * 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
         */
-       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: '',
+       init: function (className, containerSelector, buttonSelector) {
+               this._containerSelector = containerSelector;
+               this._className = className;
+               this._buttonSelector = (buttonSelector) ? buttonSelector : '.jsDeleteButton';
+               this._callback = null;
                
-               /**
-                * container selector
-                * @var        string
-                */
-               _containerSelector: '',
+               this.proxy = new WCF.Action.Proxy({
+                       success: $.proxy(this._success, this)
+               });
                
-               /**
-                * list of known container ids
-                * @var        array<string>
-                */
-               _containers: [],
+               this._initElements();
                
-               /**
-                * 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;
-                       
-                       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();
                        
-                       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 (!WCF.inArray($containerID, this._containers)) {
+                               var $deleteButton = $container.find(this._buttonSelector);
                                
-                               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));
-                                       }
+                               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);
-                       }
-                       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
-               },
+               }).bind(this));
+       },
+       
+       /**
+        * Sends AJAX request.
+        *
+        * @param        object                event
+        */
+       _click: function (event) {
+               var $target = $(event.currentTarget);
+               event.preventDefault();
                
-               /**
-                * 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);
-               },
+               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;
+               }
                
-               /**
-                * 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();
-               },
+               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')]
+               });
                
-               /**
-                * 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.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);
+               }
                
-               /**
-                * 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;
-               },
+               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 + "'.");
+               }
                
-               /**
-                * 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
-                                                       });
-                                               }
-                                       });
-                               }
+               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
+                                               });
+                                       }
+                               });
                        }
                }
-       });
-       
+       }
+});
+
+if (COMPILER_TARGET_DEFAULT) {
        /**
         * Basic implementation for deletion of nested elements.
         *
@@ -2104,23 +2104,6 @@ if (COMPILER_TARGET_DEFAULT) {
        });
 }
 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: "",
@@ -5451,7 +5434,10 @@ if (COMPILER_TARGET_DEFAULT) {
                        }
                        
                        // update progress
-                       this._dialog.find('progress').attr('value', data.returnValues.progress).text(data.returnValues.progress + '%').next('span').text(data.returnValues.progress + '%');
+                       this._dialog.find('progress').attr(
+                               'value',
+                               data.returnValues.progress
+                       ).text(data.returnValues.progress + '%').next('span').text(data.returnValues.progress + '%');
                        
                        // worker is still busy with its business, carry on
                        if (data.returnValues.progress < 100) {
@@ -5472,11 +5458,13 @@ if (COMPILER_TARGET_DEFAULT) {
                        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'));
+                               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 () {
+                               $('<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;
                                        }
@@ -5577,7 +5565,10 @@ if (COMPILER_TARGET_DEFAULT) {
                        
                        this.rebuild();
                        
-                       WCF.DOMNodeInsertedHandler.addCallback('WCF.InlineEditor' + this._elementSelector.hashCode(), $.proxy(this.rebuild, this));
+                       WCF.DOMNodeInsertedHandler.addCallback(
+                               'WCF.InlineEditor' + this._elementSelector.hashCode(),
+                               $.proxy(this.rebuild, this)
+                       );
                        
                        this._proxy = new WCF.Action.Proxy({
                                success: $.proxy(this._success, this)
@@ -5585,13 +5576,16 @@ if (COMPILER_TARGET_DEFAULT) {
                        
                        WCF.CloseOverlayHandler.addCallback('WCF.InlineEditor', $.proxy(this._closeAll, this));
                        
-                       this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success'), 'success');
+                       this._notification = new WCF.System.Notification(
+                               WCF.Language.get('wcf.global.success'),
+                               'success'
+                       );
                },
                
                /**
                 * Identify new elements and adds the event listeners to them.
                 */
-               rebuild: function() {
+               rebuild: function () {
                        var $elements = $(this._elementSelector);
                        var self = this;
                        $elements.each(function (index, element) {
@@ -5605,10 +5599,16 @@ if (COMPILER_TARGET_DEFAULT) {
                                                return;
                                        }
                                        
-                                       $trigger.on(WCF_CLICK_EVENT, $.proxy(self._show, self)).data('elementID', $elementID);
+                                       $trigger.on(WCF_CLICK_EVENT, $.proxy(self._show, self)).data(
+                                               'elementID',
+                                               $elementID
+                                       );
                                        if (self._quickOption) {
                                                // simulate click on target action
-                                               $trigger.disableSelection().data('optionName', self._quickOption).dblclick($.proxy(self._click, self));
+                                               $trigger.disableSelection().data(
+                                                       'optionName',
+                                                       self._quickOption
+                                               ).dblclick($.proxy(self._click, self));
                                        }
                                        
                                        // store reference
@@ -5666,7 +5666,8 @@ if (COMPILER_TARGET_DEFAULT) {
                        // build dropdown
                        var $trigger = null;
                        if (!this._dropdowns[$elementID]) {
-                               this._triggerElements[$elementID] = $trigger = this._getTriggerElement(this._elements[$elementID]).addClass('dropdownToggle');
+                               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
@@ -5692,9 +5693,18 @@ if (COMPILER_TARGET_DEFAULT) {
                                                $lastElementType = $option.optionName;
                                        }
                                }
-                               else if (this._validate($elementID, $option.optionName) || this._validateCallbacks($elementID, $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));
+                                       $listItem.data('elementID', $elementID).data(
+                                               'optionName',
+                                               $option.optionName
+                                       ).data('isQuickOption', ($option.isQuickOption ? true : false)).click($.proxy(
+                                               this._click,
+                                               this
+                                       ));
                                        
                                        $hasOptions = true;
                                        $lastElementType = $option.optionName;
@@ -5857,679 +5867,622 @@ if (COMPILER_TARGET_DEFAULT) {
                        }
                }
        });
+}
+else {
+       WCF.System.Worker = Class.extend({
+               _aborted: false,
+               _actionName: "",
+               _callback: {},
+               _className: "",
+               _dialog: {},
+               _proxy: {},
+               _title: "",
+               init: function() {},
+               _success: function() {}
+       });
        
-       /**
-        * 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
-                */
+       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() {}
+       });
+}
+
+/**
+ * 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
+        */
+       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 || {});
                
-               /**
-                * 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>');
-                               elAttr($button[0], 'role', 'button');
-                               $button.prepend(this._fileUpload);
-                               
-                               this._fileUpload[0].addEventListener('focus', function() {
-                                       if (this.classList.contains('focus-visible')) {
-                                               $button[0].classList.add('active');
-                                       }
-                               });
-                               this._fileUpload[0].addEventListener('blur', function() { $button[0].classList.remove('active'); });
-                       }
-                       else {
-                               var $button = $('<p class="button uploadFallbackButton"><span>' + WCF.Language.get('wcf.global.button.upload') + '</span></p>');
-                               elAttr($button[0], 'role', 'button');
-                               elAttr($button[0], 'tabindex', '0');
-                               $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
-                * @param        Array|FileList      list of files
-                * @return       integer
-                */
-               _upload: function (event, file, blob, files) {
-                       var $uploadID = null;
-                       var $files = [];
-                       
-                       if (typeof files !== 'undefined') {
-                               $files = files;
-                       }
-                       else {
-                               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], $files[$i].name);
-                                               }
-                                       }
-                               }
-                               
-                               $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 {};
-               },
+               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;
+               }
                
-               /**
-                * Initializes list item for uploaded file.
-                *
-                * @return        jQuery
-                */
-               _initFile: function (file) {
-                       return $('<li>' + file.name + ' (' + file.size + ')<progress max="100" /></li>').appendTo(this._fileListSelector);
-               },
+               // check for ajax upload support
+               var $xhr = new XMLHttpRequest();
+               this._supportsAJAXUpload = ($xhr && ('upload' in $xhr) && ('onprogress' in $xhr.upload));
                
-               /**
-                * 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 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>');
+                       elAttr($button[0], 'role', 'button');
+                       $button.prepend(this._fileUpload);
                        
-                       // 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);
+                       this._fileUpload[0].addEventListener('focus', function() {
+                               if (this.classList.contains('focus-visible')) {
+                                       $button[0].classList.add('active');
                                }
-                               
-                               $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);
-               },
+                       this._fileUpload[0].addEventListener('blur', function() { $button[0].classList.remove('active'); });
+               }
+               else {
+                       var $button = $('<p class="button uploadFallbackButton"><span>' + WCF.Language.get('wcf.global.button.upload') + '</span></p>');
+                       elAttr($button[0], 'role', 'button');
+                       elAttr($button[0], 'tabindex', '0');
+                       $button.click($.proxy(this._showOverlay, this));
+               }
                
-               /**
-                * Returns name of selected file.
-                *
-                * @return        string
-                */
-               _getFilename: function () {
-                       return $('#__fileUpload').val().split('\\').pop();
+               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();
+       },
        
        /**
-        * Default implementation for parallel AJAX file uploads.
+        * Callback for file uploads.
         *
-        * @deprecated        Use WoltLabSuite/Core/Upload
+        * @param        object              event
+        * @param        File                file
+        * @param        Blob                blob
+        * @param        Array|FileList      list of files
+        * @return       integer
         */
-       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);
-               },
+       _upload: function (event, file, blob, files) {
+               var $uploadID = null;
+               var $files = [];
                
-               /**
-                * @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]);
+               if (typeof files !== 'undefined') {
+                       $files = files;
+               }
+               else {
+                       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;
                                }
                                
-                               this._sendRequest($internalFileID, $formData);
+                               $files.push({
+                                       name: 'pasted-from-clipboard' + $ext
+                               });
                        }
-               },
+                       else {
+                               $files = this._fileUpload.prop('files');
+                       }
+               }
                
-               /**
-                * Sends an AJAX request to upload a file.
-                *
-                * @param        integer                internalFileID
-                * @param        FormData        formData
-                * @return       jqXHR
-                */
-               _sendRequest: function (internalFileID, formData) {
-                       var self = this;
+               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], $files[$i].name);
+                                       }
+                               }
+                       }
+                       
+                       $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]);
+                       }
                        
-                       return $.ajax({
+                       var self = this;
+                       $.ajax({
                                type: 'POST',
                                url: this._options.url,
                                enctype: 'multipart/form-data',
-                               data: formData,
+                               data: $fd,
                                contentType: false,
                                processData: false,
                                success: function (data, textStatus, jqXHR) {
-                                       self._success(internalFileID, data);
+                                       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(internalFileID, event);
+                                                       self._progress($uploadID, event);
                                                }, false);
                                        }
                                        return $xhr;
+                               },
+                               xhrFields: {
+                                       withCredentials: true
                                }
                        });
-               },
+               }
                
-               /**
-                * 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 $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);
                                
-                               return this._internalFileID - 1;
+                               if (!$li.hasClass('uploadFailed')) {
+                                       $li.data('filename', $file.name).data('internalFileID', this._internalFileID++);
+                                       this._uploadMatrix[$uploadID][$i] = $li;
+                               }
                        }
                        
-                       return null;
-               },
+                       return $uploadID;
+               }
                
-               /**
-                * Callback for success event.
-                *
-                * @param        integer                internalFileID
-                * @param        object                data
-                */
-               _success: function (internalFileID, data) {
-               },
+               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);
                
-               /**
-                * 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);
-               },
+               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);
+               }
                
-               /**
-                * @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);
                        }
                        
-                       // 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: ''
+                               };
                                
-                               $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));
+                               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;
                        }
                        
-                       this._overlay.wcfDialog({
-                               title: WCF.Language.get('wcf.global.button.upload')
-                       });
-               },
+                       $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
+        * @return       jqXHR
+        */
+       _sendRequest: function (internalFileID, formData) {
+               var self = this;
                
-               /**
-                * Evaluates iframe response.
-                *
-                * @param        integer                internalFileID
-                */
-               _evaluateResponse: function (internalFileID) {
-                       var $returnValues = $.parseJSON(this._iframe.contents().find('pre').html());
-                       this._success(internalFileID, $returnValues);
+               return $.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;
                }
-       });
-}
-else {
-       WCF.System.Worker = Class.extend({
-               _aborted: false,
-               _actionName: "",
-               _callback: {},
-               _className: "",
-               _dialog: {},
-               _proxy: {},
-               _title: "",
-               init: function() {},
-               _success: function() {}
-       });
+               
+               return null;
+       },
        
-       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() {}
-       });
+       /**
+        * Callback for success event.
+        *
+        * @param        integer                internalFileID
+        * @param        object                data
+        */
+       _success: function (internalFileID, data) {
+       },
        
-       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() {}
-       });
+       /**
+        * 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);
+       },
        
-       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() {}
-       });
-}
+       /**
+        * @see        WCF.Upload._showOverlay()
+        */
+       _showOverlay: function () {
+               // create iframe
+               if (this._iframe === null) {
+                       this._iframe = $('<iframe name="__fileUploadIFrame" />').hide().appendTo(document.body);
+               }
+               
+               // create overlay
+               if (!this._overlay) {
+                       this._overlay = $('<div><form enctype="multipart/form-data" method="post" action="' + this._options.url + '" target="__fileUploadIFrame" /></div>').hide().appendTo(document.body);
+                       
+                       var $form = this._overlay.find('form');
+                       $('<dl class="wide"><dd><input type="file" id="__fileUpload" name="' + this._name + '" ' + (this._options.multiple ? 'multiple="true" ' : '') + '/></dd></dl>').appendTo($form);
+                       $('<div class="formSubmit"><input type="submit" value="Upload" accesskey="s" /></div></form>').appendTo($form);
+                       
+                       $('<input type="hidden" name="isFallback" value="1" />').appendTo($form);
+                       $('<input type="hidden" name="actionName" value="' + this._options.action + '" />').appendTo($form);
+                       $('<input type="hidden" name="className" value="' + this._className + '" />').appendTo($form);
+                       var $additionalParameters = this._getParameters();
+                       for (var $name in $additionalParameters) {
+                               $('<input type="hidden" name="' + $name + '" value="' + $additionalParameters[$name] + '" />').appendTo($form);
+                       }
+                       
+                       $form.submit($.proxy(function () {
+                               var $file = {
+                                       name: this._getFilename(),
+                                       size: ''
+                               };
+                               
+                               var $internalFileID = this._createUploadMatrix($file);
+                               var self = this;
+                               this._iframe.data('loading', true).off('load').load(function () {
+                                       self._evaluateResponse($internalFileID);
+                               });
+                               this._overlay.wcfDialog('close');
+                       }, this));
+               }
+               
+               this._overlay.wcfDialog({
+                       title: WCF.Language.get('wcf.global.button.upload')
+               });
+       },
+       
+       /**
+        * Evaluates iframe response.
+        *
+        * @param        integer                internalFileID
+        */
+       _evaluateResponse: function (internalFileID) {
+               var $returnValues = $.parseJSON(this._iframe.contents().find('pre').html());
+               this._success(internalFileID, $returnValues);
+       }
+});
 
 /**
  * Namespace for sortables.