Replace references to the old JS for attachments
authorAlexander Ebert <ebert@woltlab.com>
Fri, 7 Jun 2024 15:13:09 +0000 (17:13 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Sat, 8 Jun 2024 10:20:25 +0000 (12:20 +0200)
com.woltlab.wcf/templates/headIncludeJavaScript.tpl
com.woltlab.wcf/templates/shared_wysiwygAttachmentFormField.tpl
wcfsetup/install/files/acp/templates/header.tpl
wcfsetup/install/files/js/.buildOrder
wcfsetup/install/files/js/WCF.Attachment.js [deleted file]

index 1c1abe30e6a3ba0f34c39ad54de51e97bf15c3bf..0f3878219531c927cda247035f91da1cd70c4787 100644 (file)
@@ -127,7 +127,6 @@ window.addEventListener('pageshow', function(event) {
        );
 </script>
 
-{js application='wcf' file='WCF.Attachment' bundle='WCF.Combined' hasTiny=true}
 {js application='wcf' file='WCF.ColorPicker' bundle='WCF.Combined' hasTiny=true}
 {js application='wcf' file='WCF.ImageViewer' bundle='WCF.Combined' hasTiny=true}
 {js application='wcf' file='WCF.Label' bundle='WCF.Combined' hasTiny=true}
index fc0867b6c1176b12ad05c31a2c0836d7002720b1..c9ff38ec850a931a49a69651dbfc2c105f06430b 100644 (file)
@@ -1,72 +1,27 @@
-<ul id="{$field->getPrefixedID()}_attachmentList" {*
-       *}class="formAttachmentList jsObjectActionContainer" {*
-       *}data-object-action-class-name="wcf\data\attachment\AttachmentAction"{*
-       *}{if !$field->getAttachmentHandler()->getAttachmentList()|count} style="display: none"{/if}{*
-*}>
-       {foreach from=$field->getAttachmentHandler()->getAttachmentList() item=$attachment}
-               <li class="box64 jsObjectActionObject" {*
-                       *}data-object-id="{@$attachment->getObjectID()}" {*
-                       *}data-height="{@$attachment->height}" {*
-                       *}data-width="{@$attachment->width}" {*
-                       *}data-is-image="{@$attachment->isImage}"{*
-               *}>
-                       {if $attachment->tinyThumbnailType}
-                               <img src="{$attachment->getThumbnailLink('tiny')}" alt="" class="attachmentTinyThumbnail">
-                       {else}
-                               {icon size=64 name=$attachment->getIconName()}
-                       {/if}
-                       
-                       <div>
-                               <div>
-                                       <p><a href="{$attachment->getLink()}" target="_blank"{if $attachment->isImage} title="{$attachment->filename}" class="jsImageViewer"{/if}>{$attachment->filename}</a></p>
-                                       <small>{@$attachment->filesize|filesize}</small>
-                               </div>
-                               
-                               <ul class="buttonGroup">
-                                       <li><button type="button" class="button small jsObjectAction" data-object-action="delete" data-confirm-message="{lang}wcf.attachment.delete.sure{/lang}">{lang}wcf.global.button.delete{/lang}</button></li>
-                                       {if $attachment->isImage}
-                                               {if $attachment->thumbnailType}
-                                                       <li><button type="button" class="button small jsButtonAttachmentInsertThumbnail" data-object-id="{@$attachment->attachmentID}" data-url="{$attachment->getThumbnailLink('thumbnail')}">{lang}wcf.attachment.insertThumbnail{/lang}</button></li>
-                                               {/if}
-                                               <li><button type="button" class="button small jsButtonAttachmentInsertFull" data-object-id="{@$attachment->attachmentID}" data-url="{$attachment->getLink()}">{lang}wcf.attachment.insertFull{/lang}</button></li>
-                                       {else}
-                                               <li><button type="button" class="button small jsButtonInsertAttachment" data-object-id="{@$attachment->attachmentID}">{lang}wcf.attachment.insert{/lang}</button></li>
-                                       {/if}
-                               </ul>
-                       </div>
-               </li>
-       {/foreach}
-</ul>
-<div id="{$field->getPrefixedID()}_uploadButton" class="formAttachmentButtons" data-max-size="{@$field->getAttachmentHandler()->getMaxSize()}"></div>
+<div class="messageTabMenuContent" id="attachments_{$field->getPrefixedWysiwygId()}">
+       {unsafe:$field->getAttachmentHandler()->getHtmlElement()}
 
-<script data-relocate="true">
-       $(function() {
-               WCF.Language.addObject({
-                       'wcf.attachment.upload.error.invalidExtension': '{jslang}wcf.attachment.upload.error.invalidExtension{/jslang}',
-                       'wcf.attachment.upload.error.tooLarge': '{jslang}wcf.attachment.upload.error.tooLarge{/jslang}',
-                       'wcf.attachment.upload.error.reachedLimit': '{jslang}wcf.attachment.upload.error.reachedLimit{/jslang}',
-                       'wcf.attachment.upload.error.reachedRemainingLimit': '{jslang}wcf.attachment.upload.error.reachedRemainingLimit{/jslang}',
-                       'wcf.attachment.upload.error.uploadFailed': '{jslang}wcf.attachment.upload.error.uploadFailed{/jslang}',
-                       'wcf.attachment.upload.error.http413': '{jslang}wcf.attachment.upload.error.http413{/jslang}',
-                       'wcf.attachment.upload.error.uploadPhpLimit': '{jslang}wcf.attachment.upload.error.uploadPhpLimit{/jslang}',
-                       'wcf.attachment.insert': '{jslang}wcf.attachment.insert{/jslang}',
-                       'wcf.attachment.insertAll': '{jslang}wcf.attachment.insertAll{/jslang}',
-                       'wcf.attachment.insertFull': '{jslang}wcf.attachment.insertFull{/jslang}',
-                       'wcf.attachment.insertThumbnail': '{jslang}wcf.attachment.insertThumbnail{/jslang}',
-                       'wcf.attachment.delete.sure': '{jslang}wcf.attachment.delete.sure{/jslang}'
-               });
-               
-               new WCF.Attachment.Upload(
-                       $('#{@$field->getPrefixedID()|encodeJS}_uploadButton'),
-                       $('#{@$field->getPrefixedID()|encodeJS}_attachmentList'),
-                       '{@$field->getAttachmentHandler()->getObjectType()->objectType}',
-                       '{@$field->getAttachmentHandler()->getObjectID()}',
-                       '{$field->getAttachmentHandler()->getTmpHashes()[0]|encodeJS}',
-                       '{@$field->getAttachmentHandler()->getParentObjectID()}',
-                       {@$field->getAttachmentHandler()->getMaxCount()},
-                       '{@$field->getPrefixedWysiwygId()}'
-               );
-       });
-</script>
+       <div class="attachment__list__existingFiles">
+               {foreach from=$field->getAttachmentHandler()->getAttachmentList() item=attachment}
+                       {unsafe:$attachment->toHtmlElement()}
+               {/foreach}
+       </div>
+
+       <dl class="wide">
+               <dt></dt>
+               <dd>
+                       <div data-max-size="{$field->getAttachmentHandler()->getMaxSize()}"></div>
+                       <small>{lang}wcf.attachment.upload.limits{/lang}</small>
+               </dd>
+       </dl>
 
-<input type="hidden" id="{$field->getPrefixedID()}_tmpHash" name="{$field->getPrefixedID()}_tmpHash" value="{$field->getAttachmentHandler()->getTmpHashes()[0]}">
+       <script data-relocate="true">
+               {jsphrase name='wcf.attachment.insert'}
+               {jsphrase name='wcf.attachment.insertFull'}
+               {jsphrase name='wcf.attachment.moreOptions'}
+
+               require(["WoltLabSuite/Core/Component/Attachment/List"], ({ setup }) => {
+                       setup("{$field->getPrefixedWysiwygId()}");
+               });
+       </script>
+</div>
index 0b3073e69d1baff7cedcf5f30ff74e0f65198498..1429f386838431e104baca4a39a61dbdb0d0070f 100644 (file)
                        {if $__wcf->user->userID}'{@$__wcf->user->username|encodeJS}'{else}''{/if}
                );
        </script>
-       {js application='wcf' file='WCF.Attachment' bundle='WCF.Combined'}
        {js application='wcf' file='WCF.Message' bundle='WCF.Combined'}
        {js application='wcf' file='WCF.Label' bundle='WCF.Combined'}
        <script>
index 6f83a6199cc5230425ee131fa44d62c7a42c36f8..8dbb87d3567fe80b18016f5c47fd75b50b8d5c05 100644 (file)
@@ -4,7 +4,6 @@
 3rdParty/jquery-ui/nestedSortable
 WCF.Assets
 WCF
-WCF.Attachment
 WCF.ColorPicker
 WCF.ImageViewer
 WCF.Label
diff --git a/wcfsetup/install/files/js/WCF.Attachment.js b/wcfsetup/install/files/js/WCF.Attachment.js
deleted file mode 100644 (file)
index 1eec070..0000000
+++ /dev/null
@@ -1,807 +0,0 @@
-"use strict";
-
-/**
- * Namespace for attachments
- */
-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,
-       
-       /**
-        * editor id
-        * @var        string
-        */
-       _editorId: '',
-       
-       /**
-        * additional options
-        * @var Object
-        */
-       _options: {},
-
-       /**
-        * @var Map<number, (attachmentId: number, url: string) => void>
-        */
-       _pendingDragAndDrop: undefined,
-
-       /**
-        * @var HTMLElement
-        */
-       _sourceElement: undefined,
-       
-       /**
-        * @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 || {});
-               this._pendingDragAndDrop = new Map();
-               this._sourceElement = document.getElementById(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));
-               this._fileListSelector.children("li").addClass("formAttachmentListItem");
-               
-               WCF.System.Event.addListener("WoltLabSuite/Core/Ui/Object/Action", "delete", (data) => this._onDelete(data));
-               
-               this._makeSortable();
-               
-               // for backwards compatibility, the object is still created but only inserted
-               // if an editor is used
-               this._insertAllButton = $('<button type="button" class="button jsButtonAttachmentInsertAll">' + WCF.Language.get('wcf.attachment.insertAll') + '</button>').hide();
-               
-               if (this._sourceElement !== null) {
-                       this._insertAllButton.appendTo(this._buttonSelector);
-                       this._insertAllButton.click($.proxy(this._insertAll, this));
-                       
-                       if (this._fileListSelector.children('li:not(.uploadFailed)').length) {
-                               this._insertAllButton.show();
-                       }
-
-                       require([
-                               "WoltLabSuite/Core/Component/Ckeditor",
-                               "WoltLabSuite/Core/Component/Ckeditor/Event"
-                       ], (
-                               { getCkeditor },
-                               { listenToCkeditor },
-                       ) => {
-                               const discardAllAttachments = () => {
-                                       // Disable the implicit deletion of signature attachments.
-                                       if (objectType === "com.woltlab.wcf.user.signature") {
-                                               return;
-                                       }
-
-                                       this._fileListSelector[0]
-                                                       .querySelectorAll('li:not(.uploadFailed) .jsObjectAction[data-object-action="delete"]')
-                                                       .forEach((button) => {
-                                                               // This is both awful and required to bypass the confirmation
-                                                               // dialog when programmatically triggering the delete button.
-                                                               delete button.dataset.confirmMessage;
-
-                                                               button.click();
-                                                       });
-                               }
-
-                               listenToCkeditor(this._sourceElement)
-                                       .reset(() => {
-                                               this._reset();
-                                       })
-                                       .uploadAttachment((payload) => {
-                                               this._editorUpload(payload);
-                                       })
-                                       .discardRecoveredData(() => {
-                                               discardAllAttachments();
-                                       })
-                                       .collectMetaData((payload) => {
-                                               if (this._tmpHash) {
-                                                       payload.metaData.tmpHash = this._tmpHash;
-                                               }
-                                       });
-                               
-                               const ckeditor = getCkeditor(this._sourceElement);
-                               if (ckeditor) {
-                                       if (ckeditor.getHtml() === "") {
-                                               // This check is performed during the CKEditor initialization,
-                                               // but the triggered event occurs too early for jQuery code.
-                                               discardAllAttachments();
-                                       }
-                               } else {
-                                       listenToCkeditor(this._sourceElement).ready(({ ckeditor }) => {
-                                               if (ckeditor.getHtml() === "") {
-                                                       // This check is performed during the CKEditor initialization,
-                                                       // but the triggered event occurs too early for jQuery code.
-                                                       discardAllAttachments();
-                                               }
-                                       });
-                               }
-                       });
-                       
-                       const form = this._fileListSelector[0].closest("form");
-                       if (form) {
-                               form.addEventListener("submit", () => {
-                                       const input = form.querySelector('input[name="tmpHash"]');
-                                       if (input) {
-                                               input.value = this._tmpHash;
-                                       }
-                               });
-                       }
-                       
-                       var syncUuid = WCF.System.Event.addListener('com.woltlab.wcf.ckeditor5', 'sync_' + this._tmpHash, this._sync.bind(this));
-                       
-                       WCF.System.Event.addListener('com.woltlab.wcf.ckeditor5', 'destroy_' + this._editorId, (function () {
-                               WCF.System.Event.removeListener('com.woltlab.wcf.ckeditor5', 'sync_' + this._tmpHash, syncUuid);
-                       }).bind(this));
-               }
-       },
-       
-       /**
-        * Handles drag & drop uploads and clipboard paste.
-        *
-        * @param        object                data
-        */
-       _editorUpload: function (data) {
-               data.promise = new Promise((resolve) => {
-                       // show tab
-                       this._fileListSelector.closest('.messageTabMenu').messageTabMenu('showTab', 'attachments', true);
-
-                       this._upload(
-                               undefined,
-                               data.file,
-                               undefined,
-                               (uploadId) => {
-                                       this._pendingDragAndDrop.set(uploadId, (attachmentId, url) => {
-                                               if (attachmentId === 0) {
-                                                       data.abortController.abort();
-                                               } else {
-                                                       resolve({
-                                                               attachmentId,
-                                                               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');
-               
-               // check maximum uploads
-               var $max = this._options.maxUploads - this._fileListSelector.children('li:not(.uploadFailed)').length;
-               var $filesLength = (this._fileUpload) ? this._fileUpload.prop('files').length : 0;
-               if ($max <= 0 || $max < $filesLength) {
-                       // reached limit
-                       var $errorMessage = ($max <= 0) ? WCF.Language.get('wcf.attachment.upload.error.reachedLimit') : WCF.Language.get('wcf.attachment.upload.error.reachedRemainingLimit').replace(/#remaining#/, $max);
-                       if (!$innerError.length) {
-                               $innerError = $('<small class="innerError" />').insertAfter(this._buttonSelector);
-                       }
-                       
-                       $innerError.html($errorMessage);
-                       
-                       return false;
-               }
-               
-               // remove previous errors
-               $innerError.remove();
-               
-               return true;
-       },
-       
-       /**
-        * Removes the limit error message.
-        *
-        * @param        object                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._sourceElement && data.button) {
-                       this._removeAttachmentFromEditor(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();
-                       
-                       var files = [];
-                       
-                       if (file) {
-                               files.push(file);
-                       }
-                       else if (blob) {
-                               files.push(FileUtil.blobToFile(blob, 'pasted-from-clipboard'));
-                       }
-                       else {
-                               files = this._fileUpload.prop('files');
-                       }
-                       
-                       // 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 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) {
-                                               // Ignore anything that is not a widely used mimetype for static images.
-                                               // GIFs are not supported due to the support for animations.
-                                               if (['image/png', 'image/jpeg', 'image/webp'].indexOf(file.type) === -1) {
-                                                       arr.push(file);
-                                                       return 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);
-                               }
-                               
-                               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);
-               });
-       },
-       
-       /**
-        * @see        WCF.Upload._createUploadMatrix()
-        */
-       _createUploadMatrix: function (files) {
-               // remove failed uploads
-               this._fileListSelector.children('li.uploadFailed').remove();
-               
-               return this._super(files);
-       },
-       
-       /**
-        * @see        WCF.Upload._getParameters()
-        */
-       _getParameters: function () {
-               return {
-                       objectType: this._objectType,
-                       objectID: this._objectID,
-                       tmpHash: this._tmpHash,
-                       parentObjectID: this._parentObjectID
-               };
-       },
-       
-       /**
-        * @see        WCF.Upload._initFile()
-        */
-       _initFile: function (file) {
-               var $li = $('<li class="box64 formAttachmentListItem"><fa-icon size="64" name="spinner"></fa-icon><div><div><p>' + WCF.String.escapeHTML(file.name) + '</p><small><progress max="100"></progress></small></div><ul></ul></div></li>').data('filename', file.name);
-               this._fileListSelector.append($li);
-               this._fileListSelector.show();
-               
-               // validate file size
-               if (this._buttonSelector.data('maxSize') < file.size) {
-                       // remove progress bar
-                       $li.find('progress').remove();
-                       
-                       // upload icon
-                       const icon = $li[0].querySelector("fa-icon");
-                       icon.setIcon("ban");
-                       
-                       // error message
-                       $li.find('div > div').append($('<small class="innerError">' + WCF.Language.get('wcf.attachment.upload.error.tooLarge') + '</small>'));
-                       $li.addClass('uploadFailed');
-               }
-               
-               return $li;
-       },
-       
-       /**
-        * @see        WCF.Upload._success()
-        */
-       _success: function (uploadID, data) {
-               var attachmentData;
-               for (var $i in this._uploadMatrix[uploadID]) {
-                       if (!this._uploadMatrix[uploadID].hasOwnProperty($i)) {
-                               continue;
-                       }
-                       
-                       // 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');
-                       let attachmentId = 0;
-                       let url = "";
-                       if (data.returnValues && data.returnValues.attachments[$internalFileID]) {
-                               attachmentData = data.returnValues.attachments[$internalFileID];
-                               
-                               elData($li[0], 'object-id', attachmentData.attachmentID);
-                               
-                               $li.addClass('jsObjectActionObject');
-                               
-                               // show thumbnail
-                               if (attachmentData.tinyURL) {
-                                       $li.find('fa-icon').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[0].querySelector("fa-icon").setIcon(attachmentData.iconName);
-                               }
-                               
-                               // update attachment link
-                               var $link = $('<a href=""></a>');
-                               $link.text($filename).attr('href', attachmentData.url);
-                               url = 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><button type="button" class="button small jsObjectAction" data-object-action="delete" data-confirm-message="' + WCF.Language.get('wcf.attachment.delete.sure') + '" data-event-name="attachment">' + WCF.Language.get('wcf.global.button.delete') + '</button></li>');
-                               $buttonList.append($deleteButton);
-                               
-                               $li.data('objectID', attachmentData.attachmentID);
-                               attachmentId = attachmentData.attachmentID;
-                               
-                               if (this._editorId) {
-                                       if (attachmentData.tinyURL) {
-                                               if (attachmentData.thumbnailURL) {
-                                                       $('<li><button type="button" class="button small jsButtonAttachmentInsertThumbnail" data-object-id="' + attachmentData.attachmentID + '" data-url="' + WCF.String.escapeHTML(attachmentData.thumbnailURL) + '">' + WCF.Language.get('wcf.attachment.insertThumbnail') + '</button></li>').appendTo($buttonList);
-                                               }
-                                               
-                                               $('<li><button type="button" class="button small jsButtonAttachmentInsertFull" data-object-id="' + attachmentData.attachmentID + '" data-url="' + WCF.String.escapeHTML(attachmentData.url) + '">' + WCF.Language.get('wcf.attachment.insertFull') + '</button></li>').appendTo($buttonList);
-                                       }
-                                       else {
-                                               $('<li><button type="button" class="button small jsButtonAttachmentInsertPlain" data-object-id="' + attachmentData.attachmentID + '">' + WCF.Language.get('wcf.attachment.insert') + '</button></li>').appendTo($buttonList);
-                                       }
-                               }
-                               
-                               this._triggerSync('new', {
-                                       html: $li[0].outerHTML
-                               });
-                               
-                               this._registerEditorButtons($li[0]);
-                       }
-                       else {
-                               // upload icon
-                               $li[0].querySelector("fa-icon").setIcon("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';
-                                       }
-                               }
-                               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');
-                       }
-
-                       const callbackDragAndDrop = this._pendingDragAndDrop.get(uploadID);
-                       if (callbackDragAndDrop !== undefined) {
-                               callbackDragAndDrop(attachmentId, url);
-
-                               this._pendingDragAndDrop.delete(uploadID);
-                       }
-                       
-                       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');
-                               }
-                       }
-               }
-               
-               this._rebuildInterface();
-       },
-       
-       _rebuildInterface: function () {
-               this._makeSortable();
-               
-               if (this._fileListSelector.children('li:not(.uploadFailed)').length) {
-                       this._insertAllButton.show();
-               }
-               else {
-                       this._insertAllButton.hide();
-               }
-               
-               WCF.DOMNodeInsertedHandler.execute();
-       },
-       
-       _registerEditorButtons: function (attachment) {
-               if (this._editorId) {
-                       elBySelAll('.jsButtonAttachmentInsertThumbnail, .jsButtonAttachmentInsertFull, .jsButtonAttachmentInsertPlain', attachment, (function(button) {
-                               button.addEventListener('click', this._insert.bind(this));
-                       }).bind(this));
-               }
-       },
-       
-       /**
-        * Inserts an attachment into WYSIWYG editor contents.
-        *
-        * @param        {Event}                event
-        */
-       _insert: function (event) {
-               const attachmentId = parseInt(event.currentTarget.dataset.objectId);
-               const url = event.currentTarget.dataset.url || "";
-
-               require(["WoltLabSuite/Core/Component/Ckeditor/Event"], ({ dispatchToCkeditor }) => {
-                       dispatchToCkeditor(this._sourceElement).insertAttachment({
-                               attachmentId,
-                               url,
-                       });
-               });
-       },
-       
-       /**
-        * Inserts all attachments at once.
-        */
-       _insertAll: function () {
-               var attachment, button;
-               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 = elBySel('.jsButtonAttachmentInsertThumbnail, .jsButtonAttachmentInsertPlain', attachment);
-                               
-                               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);
-                       const icon = listItem.querySelector('fa-icon[name="spinner"]');
-                       if (icon) {
-                               // upload icon
-                               $listItem.addClass('uploadFailed');
-                               icon.setIcon("ban");
-
-                               let message = WCF.Language.get('wcf.attachment.upload.error.uploadFailed');
-                               if (data.responseJSON && data.responseJSON.message) {
-                                       message = data.responseJSON.message;
-                               } else if (data.status == 413) {
-                                       message = WCF.Language.get('wcf.attachment.upload.error.http413');
-                               }
-
-                               $listItem.find('div > div').append($('<small class="innerError">' + message + '</small>'));
-                       }
-               });
-       },
-       
-       /**
-        * Initializes sorting for uploaded attachments.
-        */
-       _makeSortable: function () {
-               var $attachments = this._fileListSelector.children('li:not(.uploadFailed)');
-               if (!$attachments.length) {
-                       return;
-               }
-               
-               $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
-                                                                               }
-                                                                       }
-                                                               });
-                                                       }
-                                               }).bind(this)
-                                       }, true);
-                               }
-                       }).bind(this));
-               }
-       },
-       
-       /**
-        * @param {Object} data
-        * @param {HTMLElement} data.containerElement
-        * @param {unknown} data.data
-        * @param {HTMLElement} data.objectElement
-        */
-       _onDelete: function (data) {
-               if (!data.objectElement.classList.contains("formAttachmentListItem")) {
-                       return;
-               }
-
-               // Remove any copies of attachments on delete.
-               const objectId = data.objectElement.dataset.objectId;
-               const attachment = this._fileListSelector[0].querySelector(`.formAttachmentListItem[data-object-id="${objectId}"]`);
-               if (attachment !== null) {
-                       attachment.remove();
-               }
-               
-               this._removeLimitError({});
-
-               this._removeAttachmentFromEditor(objectId);
-       },
-       
-       /**
-        * @param {Object} payload
-        * @param {Object} payload.data
-        * @param {Object} payload.source
-        * @param {string} payload.type
-        */
-       _sync: function (payload) {
-               if (payload.source === this) {
-                       return;
-               }
-               
-               switch (payload.type) {
-                       case 'new':
-                               this._syncNew(payload.data);
-                               break;
-                               
-                       default:
-                               throw new Error("Unexpected type '" + payload.type + "'");
-               }
-       },
-       
-       /**
-        * @param {Object} data
-        */
-       _syncNew: function (data) {
-               require(['Dom/Util'], (function (DomUtil) {
-                       var fragment = DomUtil.createFragmentFromHtml(data.html);
-                       var attachment = elBySel('li', fragment);
-                       attachment.id = '';
-                       
-                       this._registerEditorButtons(attachment);
-                       
-                       this._fileListSelector[0].appendChild(attachment);
-                       
-                       elShow(this._fileListSelector[0]);
-                       
-                       this._rebuildInterface();
-               }).bind(this));
-       },
-       
-       /**
-        * @param {string} type
-        * @param {Object} data
-        */
-       _triggerSync: function (type, data) {
-               WCF.System.Event.fireEvent('com.woltlab.wcf.ckeditor5', 'sync_' + this._tmpHash, {
-                       source: this,
-                       type: type,
-                       data: data
-               });
-       },
-
-       _removeAttachmentFromEditor(attachmentId) {
-               require(["WoltLabSuite/Core/Component/Ckeditor/Event"], ({ dispatchToCkeditor }) => {
-                       dispatchToCkeditor(this._sourceElement).removeAttachment({
-                               attachmentId: parseInt(attachmentId),
-                       });
-               });
-       }
-});