Merge remote-tracking branch 'refs/remotes/origin/3.0'
authorMarcel Werk <burntime@woltlab.com>
Sun, 8 Apr 2018 16:30:44 +0000 (18:30 +0200)
committerMarcel Werk <burntime@woltlab.com>
Sun, 8 Apr 2018 16:30:44 +0000 (18:30 +0200)
# Conflicts:
# wcfsetup/install/files/js/WCF.Attachment.js

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

index c8d1fb10f120bed205cfa9bd6082972ec1b00208,7d4602d340bd264e4a635db1fe2135645164495c..1aac9d0ab9aa804d9e2a1cdafe829c989adb4da7
   */
  WCF.Attachment = {};
  
 -/**
 - * Attachment upload function
 - * 
 - * @see       WCF.Upload
 - */
 -WCF.Attachment.Upload = WCF.Upload.extend({
 -      /**
 -       * list of upload ids which should be automatically inserted
 -       * @var array<integer>
 -       */
 -      _autoInsert: [ ],
 -      
 -      /**
 -       * 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,
 -      
 +if (COMPILER_TARGET_DEFAULT) {
        /**
 -       * editor id
 -       * @var string
 +       * Attachment upload function
 +       *
 +       * @see        WCF.Upload
         */
 -      _editorId: '',
 -      
 -      /**
 -       * replace img element on load
 -       * @var Object
 -       */
 -      _replaceOnLoad: {},
 -      
 -      /**
 -       * @see WCF.Upload.init()
 -       */
 -      init: function(buttonSelector, fileListSelector, objectType, objectID, tmpHash, parentObjectID, maxUploads, editorId) {
 -              this._super(buttonSelector, fileListSelector, 'wcf\\data\\attachment\\AttachmentAction', { multiple: true, maxUploads: maxUploads });
 +      WCF.Attachment.Upload = WCF.Upload.extend({
 +              /**
 +               * list of upload ids which should be automatically inserted
 +               * @var        array<integer>
 +               */
 +              _autoInsert: [],
                
 -              this._autoInsert = [ ];
 -              this._objectType = objectType;
 -              this._objectID = parseInt(objectID);
 -              this._tmpHash = tmpHash;
 -              this._parentObjectID = parseInt(parentObjectID);
 -              this._editorId = editorId;
 +              /**
 +               * reference to 'Insert All' button
 +               * @var        jQuery
 +               */
 +              _insertAllButton: null,
                
 -              this._buttonSelector.children('p.button').click($.proxy(this._validateLimit, this));
 -              this._fileListSelector.find('.jsButtonInsertAttachment').click($.proxy(this._insert, this));
 -              this._fileListSelector.find('.jsButtonAttachmentInsertThumbnail').click($.proxy(this._insert, this));
 -              this._fileListSelector.find('.jsButtonAttachmentInsertFull').click($.proxy(this._insert, this));
 +              /**
 +               * object type of the object the uploaded attachments belong to
 +               * @var        string
 +               */
 +              _objectType: '',
                
 -              WCF.DOMNodeRemovedHandler.addCallback('WCF.Attachment.Upload', $.proxy(this._removeLimitError, this));
 -              WCF.System.Event.addListener('com.woltlab.wcf.action.delete', 'attachment_' + this._editorId, $.proxy(this._removeLimitError, this));
 +              /**
 +               * id of the object the uploaded attachments belong to
 +               * @var        string
 +               */
 +              _objectID: 0,
                
 -              this._makeSortable();
 +              /**
 +               * temporary hash to identify uploaded attachments
 +               * @var        string
 +               */
 +              _tmpHash: '',
                
 -              this._insertAllButton = $('<p class="button jsButtonAttachmentInsertAll">' + WCF.Language.get('wcf.attachment.insertAll') + '</p>').hide().appendTo(this._buttonSelector);
 -              this._insertAllButton.click($.proxy(this._insertAll, this));
 +              /**
 +               * id of the parent object of the object the uploaded attachments belongs to
 +               * @var        string
 +               */
 +              _parentObjectID: 0,
                
 -              if (this._fileListSelector.children('li:not(.uploadFailed)').length) {
 -                      this._insertAllButton.show();
 -              }
 +              /**
 +               * editor id
 +               * @var        string
 +               */
 +              _editorId: '',
 +              
 +              /**
 +               * replace img element on load
 +               * @var Object
 +               */
 +              _replaceOnLoad: {},
                
 -              if (this._editorId) {
 -                      WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'submit_' + this._editorId, this._submitInline.bind(this));
 -                      WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'reset_' + this._editorId, this._reset.bind(this));
 -                      WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'dragAndDrop_' + this._editorId, this._editorUpload.bind(this));
 -                      WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'pasteFromClipboard_' + this._editorId, this._editorUpload.bind(this));
 +              /**
 +               * @see        WCF.Upload.init()
 +               */
 +              init: function (buttonSelector, fileListSelector, objectType, objectID, tmpHash, parentObjectID, maxUploads, editorId) {
 +                      this._super(buttonSelector, fileListSelector, 'wcf\\data\\attachment\\AttachmentAction', {
 +                              multiple: true,
 +                              maxUploads: maxUploads
 +                      });
                        
 -                      var metacodeAttachUuid = WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'metacode_attach_' + this._editorId, (function(data) {
 -                              var images = this._getImageAttachments();
 -                              var attachmentId = data.attributes[0] || 0;
 -                              if (images.hasOwnProperty(attachmentId)) {
 -                                      var thumbnail = data.attributes[2];
 -                                      thumbnail = (thumbnail === true || thumbnail === 'true' || ~~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');
 +                      this._autoInsert = [];
 +                      this._objectType = objectType;
 +                      this._objectID = parseInt(objectID);
 +                      this._tmpHash = tmpHash;
 +                      this._parentObjectID = parseInt(parentObjectID);
 +                      this._editorId = editorId;
 +                      
 +                      this._buttonSelector.children('p.button').click($.proxy(this._validateLimit, this));
 +                      this._fileListSelector.find('.jsButtonInsertAttachment').click($.proxy(this._insert, this));
 +                      this._fileListSelector.find('.jsButtonAttachmentInsertThumbnail').click($.proxy(this._insert, this));
 +                      this._fileListSelector.find('.jsButtonAttachmentInsertFull').click($.proxy(this._insert, this));
 +                      
 +                      WCF.DOMNodeRemovedHandler.addCallback('WCF.Attachment.Upload', $.proxy(this._removeLimitError, this));
 +                      WCF.System.Event.addListener('com.woltlab.wcf.action.delete', 'attachment_' + this._editorId, $.proxy(this._removeLimitError, this));
 +                      
 +                      this._makeSortable();
 +                      
 +                      this._insertAllButton = $('<p class="button jsButtonAttachmentInsertAll">' + WCF.Language.get('wcf.attachment.insertAll') + '</p>').hide().appendTo(this._buttonSelector);
 +                      this._insertAllButton.click($.proxy(this._insertAll, this));
 +                      
 +                      if (this._fileListSelector.children('li:not(.uploadFailed)').length) {
 +                              this._insertAllButton.show();
 +                      }
 +                      
 +                      if (this._editorId) {
 +                              WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'submit_' + this._editorId, this._submitInline.bind(this));
 +                              WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'reset_' + this._editorId, this._reset.bind(this));
 +                              WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'dragAndDrop_' + this._editorId, this._editorUpload.bind(this));
 +                              WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'pasteFromClipboard_' + this._editorId, this._editorUpload.bind(this));
 +                              
 +                              WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'autosaveMetaData_' + this._editorId, (function (data) {
 +                                      if (!data.tmpHashes || !Array.isArray(data.tmpHashes)) {
 +                                              data.tmpHashes = [];
 +                                      }
                                        
 -                                      var metacode = data.metacode;
 -                                      metacode.parentNode.insertBefore(image, metacode);
 -                                      elRemove(metacode);
 +                                      var index = data.tmpHashes.indexOf(tmpHash);
                                        
 -                                      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);
 +                                      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));
                                
 -                              WCF.System.Event.removeListener('com.woltlab.wcf.redactor2', 'metacode_attach_' + this._editorId, metacodeAttachUuid);
 -                      }).bind(this));
 -              }
 -      },
 -      
 -      /**
 -       * Handles drag & drop uploads and clipboard paste.
 -       * 
 -       * @param       object          data
 -       */
 -      _editorUpload: function(data) {
 -              var $uploadID, replace = null;
 -              
 -              // show tab
 -              this._fileListSelector.closest('.messageTabMenu').messageTabMenu('showTab', 'attachments', true);
 -              
 -              if (data.file) {
 -                      $uploadID = this._upload(undefined, data.file);
 -              }
 -              else {
 -                      $uploadID = this._upload(undefined, undefined, data.blob);
 -                      replace = data.replace || null;
 -              }
 -              
 -              if (replace === null) {
 -                      this._autoInsert.push($uploadID);
 -              }
 -              else {
 -                      this._replaceOnLoad[$uploadID] = replace;
 -              }
 -              
 -              data.uploadID = $uploadID;
 -      },
 -      
 -      /**
 -       * Sets the attachments representing an image.
 -       * 
 -       * @return      {Object}
 -       */
 -      _getImageAttachments: function() {
 -              var images = {};
 -              
 -              this._fileListSelector.children('li').each(function(index, attachment) {
 -                      var $attachment = $(attachment);
 -                      if ($attachment.data('isImage')) {
 -                              images[~~$attachment.data('objectID')] = {
 -                                      thumbnailUrl: $attachment.find('.jsButtonAttachmentInsertThumbnail').data('url'),
 -                                      url: $attachment.find('.jsButtonAttachmentInsertFull').data('url')
 -                              };
 +                              var metacodeAttachUuid = WCF.System.Event.addListener('com.woltlab.wcf.redactor2', 'metacode_attach_' + this._editorId, (function (data) {
 +                                      var images = this._getImageAttachments();
 +                                      var attachmentId = data.attributes[0] || 0;
 +                                      if (images.hasOwnProperty(attachmentId)) {
 +                                              var thumbnail = data.attributes[2];
-                                               thumbnail = (thumbnail === true || thumbnail === 'true');
++                                              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);
 +                                      
 +                                      WCF.System.Event.removeListener('com.woltlab.wcf.redactor2', 'metacode_attach_' + this._editorId, metacodeAttachUuid);
 +                              }).bind(this));
                        }
 -              });
 -              
 -              return images;
 -      },
 -      
 -      /**
 -       * Adds parameters for the inline editor.
 -       * 
 -       * @param       object          data
 -       */
 -      _submitInline: function(data) {
 -              if (this._tmpHash) {
 -                      data.tmpHash = this._tmpHash;
 -              }
 -      },
 -      
 -      /**
 -       * Resets the attachment container.
 -       */
 -      _reset: function() {
 -              this._fileListSelector.hide().empty();
 -              this._insertAllButton.hide();
 -              this._validateLimit();
 -      },
 -      
 -      /**
 -       * Validates upload limits.
 -       * 
 -       * @return      boolean
 -       */
 -      _validateLimit: function() {
 -              var $innerError = this._buttonSelector.next('small.innerError');
 +              },
                
 -              // check maximum uploads
 -              var $max = this._options.maxUploads - this._fileListSelector.children('li:not(.uploadFailed)').length;
 -              var $filesLength = (this._fileUpload) ? this._fileUpload.prop('files').length : 0;
 -              if ($max <= 0 || $max < $filesLength) {
 -                      // reached limit
 -                      var $errorMessage = ($max <= 0) ? WCF.Language.get('wcf.attachment.upload.error.reachedLimit') : WCF.Language.get('wcf.attachment.upload.error.reachedRemainingLimit').replace(/#remaining#/, $max);
 -                      if (!$innerError.length) {
 -                              $innerError = $('<small class="innerError" />').insertAfter(this._buttonSelector);
 +              /**
 +               * Handles drag & drop uploads and clipboard paste.
 +               *
 +               * @param        object                data
 +               */
 +              _editorUpload: function (data) {
 +                      var $uploadID, replace = null;
 +                      
 +                      // show tab
 +                      this._fileListSelector.closest('.messageTabMenu').messageTabMenu('showTab', 'attachments', true);
 +                      
 +                      if (data.file) {
 +                              $uploadID = this._upload(undefined, data.file);
 +                      }
 +                      else {
 +                              $uploadID = this._upload(undefined, undefined, data.blob);
 +                              replace = data.replace || null;
                        }
                        
 -                      $innerError.html($errorMessage);
 +                      if (replace === null) {
 +                              this._autoInsert.push($uploadID);
 +                      }
 +                      else {
 +                              this._replaceOnLoad[$uploadID] = replace;
 +                      }
                        
 -                      return false;
 -              }
 -              
 -              // remove previous errors
 -              $innerError.remove();
 -              
 -              return true;
 -      },
 -      
 -      /**
 -       * Removes the limit error message.
 -       * 
 -       * @param       object          data
 -       */
 -      _removeLimitError: function(data) {
 -              var $listItems = this._fileListSelector.children('li');
 -              if (!$listItems.filter(':not(.uploadFailed)').length) {
 -                      this._insertAllButton.hide();
 -              }
 -              
 -              if (!$listItems.length) {
 -                      this._fileListSelector.hide();
 -              }
 +                      data.uploadID = $uploadID;
 +              },
                
 -              if (this._editorId && data.button) {
 -                      WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'deleteAttachment_' + this._editorId, {
 -                              attachmentId: data.button.data('objectID')
 +              /**
 +               * Sets the attachments representing an image.
 +               *
 +               * @return      {Object}
 +               */
 +              _getImageAttachments: function () {
 +                      var images = {};
 +                      
 +                      this._fileListSelector.children('li').each(function (index, attachment) {
 +                              var $attachment = $(attachment);
 +                              if ($attachment.data('isImage')) {
 +                                      images[~~$attachment.data('objectID')] = {
 +                                              thumbnailUrl: $attachment.find('.jsButtonAttachmentInsertThumbnail').data('url'),
 +                                              url: $attachment.find('.jsButtonAttachmentInsertFull').data('url')
 +                                      };
 +                              }
                        });
 -              }
 -      },
 -      
 -      /**
 -       * @see WCF.Upload._upload()
 -       */
 -      _upload: function(event, file, blob) {
 -              var $uploadID = undefined;
 -              
 -              if (this._validateLimit()) {
 -                      $uploadID = this._super(event, file, blob);
 -              }
 +                      
 +                      return images;
 +              },
                
 -              if (this._fileUpload) {
 -                      // remove and re-create the upload button since the 'files' property
 -                      // of the input field is readonly thus it can't be reset
 -                      this._removeButton();
 -                      this._createButton();
 -              }
 +              /**
 +               * Adds parameters for the inline editor.
 +               *
 +               * @param        object                data
 +               */
 +              _submitInline: function (data) {
 +                      if (this._tmpHash) {
 +                              data.tmpHash = this._tmpHash;
 +                              
 +                              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(',');
 +                              }
 +                      }
 +              },
                
 -              return $uploadID;
 -      },
 -      
 -      /**
 -       * @see WCF.Upload._createUploadMatrix()
 -       */
 -      _createUploadMatrix: function(files) {
 -              // remove failed uploads
 -              this._fileListSelector.children('li.uploadFailed').remove();
 +              /**
 +               * Resets the attachment container.
 +               */
 +              _reset: function () {
 +                      this._fileListSelector.hide().empty();
 +                      this._insertAllButton.hide();
 +                      this._validateLimit();
 +              },
                
 -              return this._super(files);
 -      },
 -      
 -      /**
 -       * @see WCF.Upload._getParameters()
 -       */
 -      _getParameters: function() {
 -              return {
 -                      objectType: this._objectType,
 -                      objectID: this._objectID,
 -                      tmpHash: this._tmpHash,
 -                      parentObjectID: this._parentObjectID
 -              };
 -      },
 -      
 -      /**
 -       * @see WCF.Upload._initFile()
 -       */
 -      _initFile: function(file) {
 -              var $li = $('<li class="box64"><span class="icon icon64 fa-spinner" /><div><div><p>'+file.name+'</p><small><progress max="100"></progress></small></div><ul></ul></div></li>').data('filename', file.name);
 -              this._fileListSelector.append($li);
 -              this._fileListSelector.show();
 +              /**
 +               * Validates upload limits.
 +               *
 +               * @return        boolean
 +               */
 +              _validateLimit: function () {
 +                      var $innerError = this._buttonSelector.next('small.innerError');
 +                      
 +                      // check maximum uploads
 +                      var $max = this._options.maxUploads - this._fileListSelector.children('li:not(.uploadFailed)').length;
 +                      var $filesLength = (this._fileUpload) ? this._fileUpload.prop('files').length : 0;
 +                      if ($max <= 0 || $max < $filesLength) {
 +                              // reached limit
 +                              var $errorMessage = ($max <= 0) ? WCF.Language.get('wcf.attachment.upload.error.reachedLimit') : WCF.Language.get('wcf.attachment.upload.error.reachedRemainingLimit').replace(/#remaining#/, $max);
 +                              if (!$innerError.length) {
 +                                      $innerError = $('<small class="innerError" />').insertAfter(this._buttonSelector);
 +                              }
 +                              
 +                              $innerError.html($errorMessage);
 +                              
 +                              return false;
 +                      }
 +                      
 +                      // remove previous errors
 +                      $innerError.remove();
 +                      
 +                      return true;
 +              },
                
 -              // validate file size
 -              if (this._buttonSelector.data('maxSize') < file.size) {
 -                      // remove progress bar
 -                      $li.find('progress').remove();
 +              /**
 +               * Removes the limit error message.
 +               *
 +               * @param        object                data
 +               */
 +              _removeLimitError: function (data) {
 +                      var $listItems = this._fileListSelector.children('li');
 +                      if (!$listItems.filter(':not(.uploadFailed)').length) {
 +                              this._insertAllButton.hide();
 +                      }
                        
 -                      // upload icon
 -                      $li.children('.fa-spinner').removeClass('fa-spinner').addClass('fa-ban');
 +                      if (!$listItems.length) {
 +                              this._fileListSelector.hide();
 +                      }
                        
 -                      // error message
 -                      $li.find('div > div').append($('<small class="innerError">' + WCF.Language.get('wcf.attachment.upload.error.tooLarge') + '</small>'));
 -                      $li.addClass('uploadFailed');
 -              }
 +                      if (this._editorId && data.button) {
 +                              WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'deleteAttachment_' + this._editorId, {
 +                                      attachmentId: data.button.data('objectID')
 +                              });
 +                      }
 +              },
                
 -              return $li;
 -      },
 -      
 -      /**
 -       * 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;
 +              /**
 +               * @see        WCF.Upload._upload()
 +               */
 +              _upload: function (event, file, blob) {
 +                      var $uploadID = undefined;
 +                      
 +                      if (this._validateLimit()) {
 +                              $uploadID = this._super(event, file, blob);
                        }
                        
 -                      // get li
 -                      var $li = this._uploadMatrix[uploadID][$i];
 +                      if (this._fileUpload) {
 +                              // remove and re-create the upload button since the 'files' property
 +                              // of the input field is readonly thus it can't be reset
 +                              this._removeButton();
 +                              this._createButton();
 +                      }
                        
 -                      // remove progress bar
 -                      $li.find('progress').remove();
 +                      return $uploadID;
 +              },
 +              
 +              /**
 +               * @see        WCF.Upload._createUploadMatrix()
 +               */
 +              _createUploadMatrix: function (files) {
 +                      // remove failed uploads
 +                      this._fileListSelector.children('li.uploadFailed').remove();
                        
 -                      // get filename and check result
 -                      var $filename = $li.data('filename');
 -                      var $internalFileID = $li.data('internalFileID');
 -                      if (data.returnValues && data.returnValues.attachments[$internalFileID]) {
 -                              attachmentData = data.returnValues.attachments[$internalFileID];
 -                              
 -                              // show thumbnail
 -                              if (attachmentData.tinyURL) {
 -                                      $li.children('.fa-spinner').replaceWith($('<img src="' + attachmentData.tinyURL + '" alt="" class="attachmentTinyThumbnail" />'));
 -                                      
 -                                      $li.data('height', attachmentData.height);
 -                                      $li.data('width', attachmentData.width);
 -                                      elData($li[0], 'is-image', attachmentData.isImage);
 -                              }
 -                              // show file icon
 -                              else {
 -                                      $li.children('.fa-spinner').removeClass('fa-spinner').addClass('fa-paperclip');
 -                              }
 +                      return this._super(files);
 +              },
 +              
 +              /**
 +               * @see        WCF.Upload._getParameters()
 +               */
 +              _getParameters: function () {
 +                      return {
 +                              objectType: this._objectType,
 +                              objectID: this._objectID,
 +                              tmpHash: this._tmpHash,
 +                              parentObjectID: this._parentObjectID
 +                      };
 +              },
 +              
 +              /**
 +               * @see        WCF.Upload._initFile()
 +               */
 +              _initFile: function (file) {
 +                      var $li = $('<li class="box64"><span class="icon icon64 fa-spinner" /><div><div><p>' + file.name + '</p><small><progress max="100"></progress></small></div><ul></ul></div></li>').data('filename', file.name);
 +                      this._fileListSelector.append($li);
 +                      this._fileListSelector.show();
 +                      
 +                      // validate file size
 +                      if (this._buttonSelector.data('maxSize') < file.size) {
 +                              // remove progress bar
 +                              $li.find('progress').remove();
                                
 -                              // update attachment link
 -                              var $link = $('<a href=""></a>');
 -                              $link.text($filename).attr('href', attachmentData.url);
 +                              // upload icon
 +                              $li.children('.fa-spinner').removeClass('fa-spinner').addClass('fa-ban');
                                
 -                              if (attachmentData.isImage != 0) {
 -                                      $link.addClass('jsImageViewer').attr('title', $filename);
 +                              // error message
 +                              $li.find('div > div').append($('<small class="innerError">' + WCF.Language.get('wcf.attachment.upload.error.tooLarge') + '</small>'));
 +                              $li.addClass('uploadFailed');
 +                      }
 +                      
 +                      return $li;
 +              },
 +              
 +              /**
 +               * 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;
                                }
 -                              $li.find('p').empty().append($link);
                                
 -                              // update file size
 -                              $li.find('small').append(attachmentData.formattedFilesize);
 +                              // get li
 +                              var $li = this._uploadMatrix[uploadID][$i];
                                
 -                              // init buttons
 -                              var $buttonList = $li.find('ul').addClass('buttonGroup');
 -                              var $deleteButton = $('<li><span class="button small jsDeleteButton" data-object-id="'+attachmentData.attachmentID+'" data-confirm-message="'+WCF.Language.get('wcf.attachment.delete.sure')+'" data-event-name="attachment_' + this._editorId + '">' + WCF.Language.get('wcf.global.button.delete') + '</span></li>');
 -                              $buttonList.append($deleteButton);
 +                              // remove progress bar
 +                              $li.find('progress').remove();
                                
 -                              $li.data('objectID', attachmentData.attachmentID);
 -                              
 -                              if (this._editorId) {
 -                                      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));
 -                                              }
 +                              // 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" />'));
                                                
 -                                              var $insertOriginal = $('<li><span class="button small jsButtonAttachmentInsertFull" data-object-id="' + attachmentData.attachmentID + '" data-url="' + WCF.String.escapeHTML(attachmentData.url) + '">' + WCF.Language.get('wcf.attachment.insertFull') + '</span></li>').appendTo($buttonList);
 -                                              $insertOriginal.children('span.button').click($.proxy(this._insert, this));
 +                                              $li.data('height', attachmentData.height);
 +                                              $li.data('width', attachmentData.width);
 +                                              elData($li[0], 'is-image', attachmentData.isImage);
                                        }
 +                                      // show file icon
                                        else {
 -                                              var $insertPlain = $('<li><span class="button small jsButtonAttachmentInsertPlain" data-object-id="' + attachmentData.attachmentID + '">' + WCF.Language.get('wcf.attachment.insert') + '</span></li>');
 -                                              $insertPlain.appendTo($buttonList).children('span.button').click($.proxy(this._insert, this));
 +                                              $li.children('.fa-spinner').removeClass('fa-spinner').addClass('fa-' + attachmentData.iconName);
                                        }
 -                              }
 -                              
 -                              if (this._replaceOnLoad.hasOwnProperty(uploadID)) {
 -                                      if (!$li.hasClass('uploadFailed')) {
 -                                              var img = this._replaceOnLoad[uploadID];
 -                                              if (img && img.parentNode) {
 -                                                      WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'replaceAttachment_' + this._editorId, {
 -                                                              attachmentId: attachmentData.attachmentID,
 -                                                              img: img,
 -                                                              src: (attachmentData.thumbnailURL) ? attachmentData.thumbnailURL : attachmentData.url
 -                                                      });
 +                                      
 +                                      // update attachment link
 +                                      var $link = $('<a href=""></a>');
 +                                      $link.text($filename).attr('href', attachmentData.url);
 +                                      
 +                                      if (attachmentData.isImage != 0) {
 +                                              $link.addClass('jsImageViewer').attr('title', $filename);
 +                                      }
 +                                      $li.find('p').empty().append($link);
 +                                      
 +                                      // update file size
 +                                      $li.find('small').append(attachmentData.formattedFilesize);
 +                                      
 +                                      // init buttons
 +                                      var $buttonList = $li.find('ul').addClass('buttonGroup');
 +                                      var $deleteButton = $('<li><span class="button small jsDeleteButton" data-object-id="' + attachmentData.attachmentID + '" data-confirm-message="' + WCF.Language.get('wcf.attachment.delete.sure') + '" data-event-name="attachment_' + this._editorId + '">' + WCF.Language.get('wcf.global.button.delete') + '</span></li>');
 +                                      $buttonList.append($deleteButton);
 +                                      
 +                                      $li.data('objectID', attachmentData.attachmentID);
 +                                      
 +                                      if (this._editorId) {
 +                                              if (attachmentData.tinyURL || (!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));
                                                }
                                        }