Convert `Media/Manager/Editor` to TypeScript
authorMatthias Schmidt <gravatronics@live.com>
Tue, 5 Jan 2021 17:43:16 +0000 (18:43 +0100)
committerMatthias Schmidt <gravatronics@live.com>
Tue, 5 Jan 2021 17:43:16 +0000 (18:43 +0100)
wcfsetup/install/files/js/WoltLabSuite/Core/Media/Manager/Editor.js
wcfsetup/install/files/ts/WoltLabSuite/Core/Media/Manager/Editor.js [deleted file]
wcfsetup/install/files/ts/WoltLabSuite/Core/Media/Manager/Editor.ts [new file with mode: 0644]

index f83f92b5de90b3a0791208f4aa7d5debfe7b0bcd..1f3bb5f1c7736306af26c054bdd3e3d5ae048916 100644 (file)
 /**
  * Provides the media manager dialog for selecting media for Redactor editors.
  *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Media/Manager/Editor
+ * @author  Matthias Schmidt
+ * @copyright 2001-2021 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  WoltLabSuite/Core/Media/Manager/Editor
  */
-define(['Core', 'Dictionary', 'Dom/Traverse', 'EventHandler', 'Language', 'Permission', 'Ui/Dialog', 'WoltLabSuite/Core/Controller/Clipboard', 'WoltLabSuite/Core/Media/Manager/Base'], function (Core, Dictionary, DomTraverse, EventHandler, Language, Permission, UiDialog, ControllerClipboard, MediaManagerBase) {
+define(["require", "exports", "tslib", "./Base", "../../Core", "../../Event/Handler", "../../Dom/Traverse", "../../Language", "../../Ui/Dialog", "../../Controller/Clipboard"], function (require, exports, tslib_1, Base_1, Core, EventHandler, DomTraverse, Language, UiDialog, Clipboard) {
     "use strict";
-    if (!COMPILER_TARGET_DEFAULT) {
-        var Fake = function () { };
-        Fake.prototype = {
-            _addButtonEventListeners: function () { },
-            _buildInsertDialog: function () { },
-            _click: function () { },
-            _getInsertDialogId: function () { },
-            _getThumbnailSizes: function () { },
-            _insertMedia: function () { },
-            _insertMediaGallery: function () { },
-            _insertMediaItem: function () { },
-            _openInsertDialog: function () { },
-            insertMedia: function () { },
-            getMode: function () { },
-            setupMediaElement: function () { },
-            _dialogClose: function () { },
-            _dialogInit: function () { },
-            _dialogSetup: function () { },
-            _dialogShow: function () { },
-            _editMedia: function () { },
-            _editorClose: function () { },
-            _editorSuccess: function () { },
-            _removeClipboardCheckboxes: function () { },
-            _setMedia: function () { },
-            addMedia: function () { },
-            clipboardInsertMedia: function () { },
-            getDialog: function () { },
-            getOption: function () { },
-            removeMedia: function () { },
-            resetMedia: function () { },
-            setMedia: function () { }
-        };
-        return Fake;
-    }
-    /**
-     * @constructor
-     */
-    function MediaManagerEditor(options) {
-        options = Core.extend({
-            callbackInsert: null
-        }, options);
-        MediaManagerBase.call(this, options);
-        this._forceClipboard = true;
-        this._activeButton = null;
-        var context = (this._options.editor) ? this._options.editor.core.toolbar()[0] : undefined;
-        this._buttons = elByClass(this._options.buttonClass || 'jsMediaEditorButton', context);
-        for (var i = 0, length = this._buttons.length; i < length; i++) {
-            this._buttons[i].addEventListener('click', this._click.bind(this));
-        }
-        this._mediaToInsert = new Dictionary();
-        this._mediaToInsertByClipboard = false;
-        this._uploadData = null;
-        this._uploadId = null;
-        if (this._options.editor && !this._options.editor.opts.woltlab.attachments) {
-            var editorId = elData(this._options.editor.$editor[0], 'element-id');
-            var uuid1 = EventHandler.add('com.woltlab.wcf.redactor2', 'dragAndDrop_' + editorId, this._editorUpload.bind(this));
-            var uuid2 = EventHandler.add('com.woltlab.wcf.redactor2', 'pasteFromClipboard_' + editorId, this._editorUpload.bind(this));
-            EventHandler.add('com.woltlab.wcf.redactor2', 'destory_' + editorId, function () {
-                EventHandler.remove('com.woltlab.wcf.redactor2', 'dragAndDrop_' + editorId, uuid1);
-                EventHandler.remove('com.woltlab.wcf.redactor2', 'dragAndDrop_' + editorId, uuid2);
+    Base_1 = tslib_1.__importDefault(Base_1);
+    Core = tslib_1.__importStar(Core);
+    EventHandler = tslib_1.__importStar(EventHandler);
+    DomTraverse = tslib_1.__importStar(DomTraverse);
+    Language = tslib_1.__importStar(Language);
+    UiDialog = tslib_1.__importStar(UiDialog);
+    Clipboard = tslib_1.__importStar(Clipboard);
+    class MediaManagerEditor extends Base_1.default {
+        constructor(options) {
+            options = Core.extend({
+                callbackInsert: null,
+            }, options);
+            super(options);
+            this._forceClipboard = true;
+            this._activeButton = null;
+            const context = this._options.editor ? this._options.editor.core.toolbar()[0] : undefined;
+            this._buttons = (context || window.document).getElementsByClassName(this._options.buttonClass || "jsMediaEditorButton");
+            Array.from(this._buttons).forEach((button) => {
+                button.addEventListener("click", (ev) => this._click(ev));
             });
-            EventHandler.add('com.woltlab.wcf.media.upload', 'success', this._mediaUploaded.bind(this));
+            this._mediaToInsert = new Map();
+            this._mediaToInsertByClipboard = false;
+            this._uploadData = null;
+            this._uploadId = null;
+            if (this._options.editor && !this._options.editor.opts.woltlab.attachments) {
+                const editorId = this._options.editor.$editor[0].dataset.elementId;
+                const uuid1 = EventHandler.add("com.woltlab.wcf.redactor2", `dragAndDrop_${editorId}`, (data) => this._editorUpload(data));
+                const uuid2 = EventHandler.add("com.woltlab.wcf.redactor2", `pasteFromClipboard_${editorId}`, (data) => this._editorUpload(data));
+                EventHandler.add("com.woltlab.wcf.redactor2", `destroy_${editorId}`, () => {
+                    EventHandler.remove("com.woltlab.wcf.redactor2", `dragAndDrop_${editorId}`, uuid1);
+                    EventHandler.remove("com.woltlab.wcf.redactor2", `dragAndDrop_${editorId}`, uuid2);
+                });
+                EventHandler.add("com.woltlab.wcf.media.upload", "success", (data) => this._mediaUploaded(data));
+            }
         }
-    }
-    Core.inherit(MediaManagerEditor, MediaManagerBase, {
-        /**
-         * @see        WoltLabSuite/Core/Media/Manager/Base#_addButtonEventListeners
-         */
-        _addButtonEventListeners: function () {
-            MediaManagerEditor._super.prototype._addButtonEventListeners.call(this);
-            if (!this._mediaManagerMediaList)
+        _addButtonEventListeners() {
+            super._addButtonEventListeners();
+            if (!this._mediaManagerMediaList) {
                 return;
-            var listItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
-            for (var i = 0, length = listItems.length; i < length; i++) {
-                var listItem = listItems[i];
-                var insertIcon = elByClass('jsMediaInsertButton', listItem)[0];
+            }
+            DomTraverse.childrenByTag(this._mediaManagerMediaList, "LI").forEach((listItem) => {
+                const insertIcon = listItem.querySelector(".jsMediaInsertButton");
                 if (insertIcon) {
-                    insertIcon.classList.remove('jsMediaInsertButton');
-                    insertIcon.addEventListener('click', this._openInsertDialog.bind(this));
+                    insertIcon.classList.remove("jsMediaInsertButton");
+                    insertIcon.addEventListener("click", (ev) => this._openInsertDialog(ev));
                 }
-            }
-        },
+            });
+        }
         /**
          * Builds the dialog to setup inserting media files.
          */
-        _buildInsertDialog: function () {
-            var thumbnailOptions = '';
-            var thumbnailSizes = this._getThumbnailSizes();
-            for (var i = 0, length = thumbnailSizes.length; i < length; i++) {
-                thumbnailOptions += '<option value="' + thumbnailSizes[i] + '">' + Language.get('wcf.media.insert.imageSize.' + thumbnailSizes[i]) + '</option>';
-            }
-            thumbnailOptions += '<option value="original">' + Language.get('wcf.media.insert.imageSize.original') + '</option>';
-            var dialog = '<div class="section">'
-                /*+ (this._mediaToInsert.size > 1 ? '<dl>'
-                    + '<dt>' + Language.get('wcf.media.insert.type') + '</dt>'
-                    + '<dd>'
-                        + '<select name="insertType">'
-                            + '<option value="separate">' + Language.get('wcf.media.insert.type.separate') + '</option>'
-                            + '<option value="gallery">' + Language.get('wcf.media.insert.type.gallery') + '</option>'
-                        + '</select>'
-                    + '</dd>'
-                + '</dl>' : '')*/
-                + '<dl class="thumbnailSizeSelection">'
-                + '<dt>' + Language.get('wcf.media.insert.imageSize') + '</dt>'
-                + '<dd>'
-                + '<select name="thumbnailSize">'
-                + thumbnailOptions
-                + '</select>'
-                + '</dd>'
-                + '</dl>'
-                + '</div>'
-                + '<div class="formSubmit">'
-                + '<button class="buttonPrimary">' + Language.get('wcf.global.button.insert') + '</button>'
-                + '</div>';
+        _buildInsertDialog() {
+            let thumbnailOptions = "";
+            this._getThumbnailSizes().forEach((thumbnailSize) => {
+                thumbnailOptions +=
+                    '<option value="' +
+                        thumbnailSize +
+                        '">' +
+                        Language.get("wcf.media.insert.imageSize." + thumbnailSize) +
+                        "</option>";
+            });
+            thumbnailOptions += '<option value="original">' + Language.get("wcf.media.insert.imageSize.original") + "</option>";
+            const dialog = `
+      <div class="section">
+        <dl class="thumbnailSizeSelection">
+          <dt>${Language.get("wcf.media.insert.imageSize")}</dt>
+          <dd>
+            <select name="thumbnailSize">
+              ${thumbnailOptions}
+            </select>
+          </dd>
+        </dl>
+      </div>
+      <div class="formSubmit">
+        <button class="buttonPrimary">${Language.get("wcf.global.button.insert")}</button>
+      </div>`;
             UiDialog.open({
-                _dialogSetup: (function () {
+                _dialogSetup: () => {
                     return {
                         id: this._getInsertDialogId(),
                         options: {
                             onClose: this._editorClose.bind(this),
-                            onSetup: function (content) {
-                                elByClass('buttonPrimary', content)[0].addEventListener('click', this._insertMedia.bind(this));
-                                // toggle thumbnail size selection based on selected insert type
-                                /*var insertType = elBySel('select[name=insertType]', content);
-                                if (insertType !== null) {
-                                    var thumbnailSelection = elByClass('thumbnailSizeSelection', content)[0];
-                                    insertType.addEventListener('change', function(event) {
-                                        if (event.currentTarget.value === 'gallery') {
-                                            elHide(thumbnailSelection);
-                                        }
-                                        else {
-                                            elShow(thumbnailSelection);
-                                        }
-                                    });
-                                }*/
-                                var thumbnailSelection = elBySel('.thumbnailSizeSelection', content);
-                                elShow(thumbnailSelection);
-                            }.bind(this),
-                            title: Language.get('wcf.media.insert')
+                            onSetup: (content) => {
+                                content.querySelector(".buttonPrimary").addEventListener("click", (ev) => this._insertMedia(ev));
+                                content.querySelector(".thumbnailSizeSelection").style.display = "block";
+                            },
+                            title: Language.get("wcf.media.insert"),
                         },
-                        source: dialog
+                        source: dialog,
                     };
-                }).bind(this)
+                },
             });
-        },
-        /**
-         * @see        WoltLabSuite/Core/Media/Manager/Base#_click
-         */
-        _click: function (event) {
+        }
+        _click(event) {
             this._activeButton = event.currentTarget;
-            MediaManagerEditor._super.prototype._click.call(this, event);
-        },
-        /**
-         * @see        WoltLabSuite/Core/Media/Manager/Base#_dialogShow
-         */
-        _dialogShow: function () {
-            MediaManagerEditor._super.prototype._dialogShow.call(this);
+            super._click(event);
+        }
+        _dialogShow() {
+            super._dialogShow();
             // check if data needs to be uploaded
             if (this._uploadData) {
-                if (this._uploadData.file) {
-                    this._upload.uploadFile(this._uploadData.file);
+                const fileUploadData = this._uploadData;
+                if (fileUploadData.file) {
+                    this._upload.uploadFile(fileUploadData.file);
                 }
                 else {
-                    this._uploadId = this._upload.uploadBlob(this._uploadData.blob);
+                    const blobUploadData = this._uploadData;
+                    this._uploadId = this._upload.uploadBlob(blobUploadData.blob);
                 }
                 this._uploadData = null;
             }
-        },
+        }
         /**
          * Handles pasting and dragging and dropping files into the editor.
-         *
-         * @param      {object}        data    data of the uploaded file
          */
-        _editorUpload: function (data) {
+        _editorUpload(data) {
             this._uploadData = data;
             UiDialog.open(this);
-        },
+        }
         /**
          * Returns the id of the insert dialog based on the media files to be inserted.
-         *
-         * @return     {string}        insert dialog id
          */
-        _getInsertDialogId: function () {
-            var dialogId = 'mediaInsert';
-            this._mediaToInsert.forEach(function (media, mediaId) {
-                dialogId += '-' + mediaId;
+        _getInsertDialogId() {
+            let dialogId = "mediaInsert";
+            this._mediaToInsert.forEach((media, mediaId) => {
+                dialogId += `-${mediaId}`;
             });
             return dialogId;
-        },
+        }
         /**
          * Returns the supported thumbnail sizes (excluding `original`) for all media images to be inserted.
-         *
-         * @return     {string[]}
          */
-        _getThumbnailSizes: function () {
-            var sizes = [];
-            var supportedSizes = ['small', 'medium', 'large'];
-            var size, supportSize;
-            for (var i = 0, length = supportedSizes.length; i < length; i++) {
-                size = supportedSizes[i];
-                supportSize = true;
+        _getThumbnailSizes() {
+            return ["small", "medium", "large"]
+                .map((size) => {
+                let supportSize = true;
                 this._mediaToInsert.forEach(function (media) {
-                    if (!media[size + 'ThumbnailType']) {
+                    if (!media[size + "ThumbnailType"]) {
                         supportSize = false;
                     }
                 });
                 if (supportSize) {
-                    sizes.push(size);
+                    return size;
                 }
-            }
-            return sizes;
-        },
+                return null;
+            })
+                .filter((s) => s !== null);
+        }
         /**
-         * Inserts media files into redactor.
-         *
-         * @param      {Event?}        event
-         * @param      {string?}       thumbnailSize
-         * @param      {boolean?}      closeEditor
+         * Inserts media files into the editor.
          */
-        _insertMedia: function (event, thumbnailSize, closeEditor) {
+        _insertMedia(event, thumbnailSize, closeEditor = false) {
             if (closeEditor === undefined)
                 closeEditor = true;
-            var insertType = 'separate';
+            const insertType = "separate";
             // update insert options with selected values if method is called by clicking on 'insert' button
             // in dialog
             if (event) {
                 UiDialog.close(this._getInsertDialogId());
-                var dialogContent = event.currentTarget.closest('.dialogContent');
-                /*if (this._mediaToInsert.size > 1) {
-                    insertType = elBySel('select[name=insertType]', dialogContent).value;
-                }*/
-                thumbnailSize = elBySel('select[name=thumbnailSize]', dialogContent).value;
+                const dialogContent = event.currentTarget.closest(".dialogContent");
+                const thumbnailSizeSelect = dialogContent.querySelector("select[name=thumbnailSize]");
+                thumbnailSize = thumbnailSizeSelect.value;
             }
             if (this._options.callbackInsert !== null) {
                 this._options.callbackInsert(this._mediaToInsert, insertType, thumbnailSize);
             }
             else {
-                if (insertType === 'separate') {
+                if (insertType === "separate") {
                     this._options.editor.buffer.set();
-                    this._mediaToInsert.forEach(this._insertMediaItem.bind(this, thumbnailSize));
+                    this._mediaToInsert.forEach((media) => this._insertMediaItem(thumbnailSize, media));
                 }
                 else {
                     this._insertMediaGallery();
                 }
             }
             if (this._mediaToInsertByClipboard) {
-                var mediaIds = [];
+                const mediaIds = [];
                 this._mediaToInsert.forEach(function (media) {
-                    mediaIds.push(media.mediaID);
+                    mediaIds.push(~~media.mediaID);
                 });
-                ControllerClipboard.unmark('com.woltlab.wcf.media', mediaIds);
+                Clipboard.unmark("com.woltlab.wcf.media", mediaIds);
             }
-            this._mediaToInsert = new Dictionary();
+            this._mediaToInsert = new Map();
             this._mediaToInsertByClipboard = false;
             // close manager dialog
             if (closeEditor) {
                 UiDialog.close(this);
             }
-        },
+        }
         /**
-         * Inserts a series of uploaded images using a slider.
-         *
-         * @protected
+         * Inserts a series of uploaded images into the editor using a slider.
          */
-        _insertMediaGallery: function () {
-            var mediaIds = [];
-            this._mediaToInsert.forEach(function (item) {
-                mediaIds.push(item.mediaID);
+        _insertMediaGallery() {
+            const mediaIds = [];
+            this._mediaToInsert.forEach(function (media) {
+                mediaIds.push(media.mediaID);
             });
             this._options.editor.buffer.set();
-            this._options.editor.insert.text("[wsmg='" + mediaIds.join(',') + "'][/wsmg]");
-        },
+            this._options.editor.insert.text("[wsmg='" + mediaIds.join(",") + "'][/wsmg]");
+        }
         /**
-         * Inserts a single media item.
-         *
-         * @param       {string}        thumbnailSize   preferred image dimension, is ignored for non-images
-         * @param       {Object}        item            media item data
-         * @protected
+         * Inserts a single media item into the editor.
          */
-        _insertMediaItem: function (thumbnailSize, item) {
-            if (item.isImage) {
-                var sizes = ['small', 'medium', 'large', 'original'];
-                // check if size is actually available
-                var available = '', size;
-                for (var i = 0; i < 4; i++) {
-                    size = sizes[i];
-                    if (item[size + 'ThumbnailHeight'] != 0) {
+        _insertMediaItem(thumbnailSize, media) {
+            if (media.isImage) {
+                let available = "";
+                ["small", "medium", "large", "original"].some((size) => {
+                    if (media[size + "ThumbnailHeight"] != 0) {
                         available = size;
                         if (thumbnailSize == size) {
-                            break;
+                            return true;
                         }
                     }
-                }
+                    return false;
+                });
                 thumbnailSize = available;
-                if (!thumbnailSize)
-                    thumbnailSize = 'original';
-                var link = item.link;
-                if (thumbnailSize !== 'original') {
-                    link = item[thumbnailSize + 'ThumbnailLink'];
+                if (!thumbnailSize) {
+                    thumbnailSize = "original";
                 }
-                this._options.editor.insert.html('<img src="' + link + '" class="woltlabSuiteMedia" data-media-id="' + item.mediaID + '" data-media-size="' + thumbnailSize + '">');
+                let link = media.link;
+                if (thumbnailSize !== "original") {
+                    link = media[thumbnailSize + "ThumbnailLink"];
+                }
+                this._options.editor.insert.html(`<img src="${link}" class="woltlabSuiteMedia" data-media-id="${media.mediaID}" data-media-size="${thumbnailSize}">`);
             }
             else {
-                this._options.editor.insert.text("[wsm='" + item.mediaID + "'][/wsm]");
+                this._options.editor.insert.text(`[wsm='${media.mediaID}'][/wsm]`);
             }
-        },
+        }
         /**
          * Is called after media files are successfully uploaded to insert copied media.
-         *
-         * @param      {object}        data            upload data
          */
-        _mediaUploaded: function (data) {
+        _mediaUploaded(data) {
             if (this._uploadId !== null && this._upload === data.upload) {
-                if (this._uploadId === data.uploadId || (Array.isArray(this._uploadId) && this._uploadId.indexOf(data.uploadId) !== -1)) {
-                    this._mediaToInsert = Dictionary.fromObject(data.media);
-                    this._insertMedia(null, 'medium', false);
+                if (this._uploadId === data.uploadId ||
+                    (Array.isArray(this._uploadId) && this._uploadId.indexOf(data.uploadId) !== -1)) {
+                    this._mediaToInsert = new Map(data.media.entries());
+                    this._insertMedia(null, "medium", false);
                     this._uploadId = null;
                 }
             }
-        },
+        }
         /**
          * Handles clicking on the insert button.
-         *
-         * @param      {Event}         event           insert button click event
          */
-        _openInsertDialog: function (event) {
-            this.insertMedia([~~elData(event.currentTarget, 'object-id')]);
-        },
+        _openInsertDialog(event) {
+            const target = event.currentTarget;
+            this.insertMedia([~~target.dataset.objectId]);
+        }
         /**
          * Is called to insert the media files with the given ids into an editor.
-         *
-         * @param      {int[]}         mediaIds
          */
-        clipboardInsertMedia: function (mediaIds) {
+        clipboardInsertMedia(mediaIds) {
             this.insertMedia(mediaIds, true);
-        },
+        }
         /**
          * Prepares insertion of the media files with the given ids.
-         *
-         * @param      {array<int>}    mediaIds                ids of the media files to be inserted
-         * @param      {boolean?}      insertedByClipboard     is true if the media files are inserted by clipboard
          */
-        insertMedia: function (mediaIds, insertedByClipboard) {
-            this._mediaToInsert = new Dictionary();
+        insertMedia(mediaIds, insertedByClipboard) {
+            this._mediaToInsert = new Map();
             this._mediaToInsertByClipboard = insertedByClipboard || false;
             // open the insert dialog if all media files are images
-            var imagesOnly = true, media;
-            for (var i = 0, length = mediaIds.length; i < length; i++) {
+            let imagesOnly = true, media;
+            for (let i = 0, length = mediaIds.length; i < length; i++) {
                 media = this._media.get(mediaIds[i]);
                 this._mediaToInsert.set(media.mediaID, media);
                 if (!media.isImage) {
@@ -363,44 +279,43 @@ define(['Core', 'Dictionary', 'Dom/Traverse', 'EventHandler', 'Language', 'Permi
                 }
             }
             if (imagesOnly) {
-                var thumbnailSizes = this._getThumbnailSizes();
+                const thumbnailSizes = this._getThumbnailSizes();
                 if (thumbnailSizes.length) {
                     UiDialog.close(this);
-                    var dialogId = this._getInsertDialogId();
+                    const dialogId = this._getInsertDialogId();
                     if (UiDialog.getDialog(dialogId)) {
-                        UiDialog.openStatic(dialogId);
+                        UiDialog.openStatic(dialogId, null);
                     }
                     else {
                         this._buildInsertDialog();
                     }
                 }
                 else {
-                    this._insertMedia(undefined, 'original');
+                    this._insertMedia(undefined, "original");
                 }
             }
             else {
                 this._insertMedia();
             }
-        },
-        /**
-         * @see        WoltLabSuite/Core/Media/Manager/Base#getMode
-         */
-        getMode: function () {
-            return 'editor';
-        },
-        /**
-         * @see        WoltLabSuite/Core/Media/Manager/Base#setupMediaElement
-         */
-        setupMediaElement: function (media, mediaElement) {
-            MediaManagerEditor._super.prototype.setupMediaElement.call(this, media, mediaElement);
+        }
+        getMode() {
+            return "editor";
+        }
+        setupMediaElement(media, mediaElement) {
+            super.setupMediaElement(media, mediaElement);
             // add media insertion icon
-            var buttons = elBySel('nav.buttonGroupNavigation > ul', mediaElement);
-            var listItem = elCreate('li');
-            listItem.className = 'jsMediaInsertButton';
-            elData(listItem, 'object-id', media.mediaID);
+            const buttons = mediaElement.querySelector("nav.buttonGroupNavigation > ul");
+            const listItem = document.createElement("li");
+            listItem.className = "jsMediaInsertButton";
+            listItem.dataset.objectId = media.mediaID;
             buttons.appendChild(listItem);
-            listItem.innerHTML = '<a><span class="icon icon16 fa-plus jsTooltip" title="' + Language.get('wcf.media.button.insert') + '"></span> <span class="invisible">' + Language.get('wcf.media.button.insert') + '</span></a>';
+            listItem.innerHTML = `
+      <a>
+        <span class="icon icon16 fa-plus jsTooltip" title="${Language.get("wcf.global.button.insert")}"></span>
+        <span class="invisible">${Language.get("wcf.global.button.insert")}</span>
+      </a>`;
         }
-    });
+    }
+    Core.enableLegacyInheritance(MediaManagerEditor);
     return MediaManagerEditor;
 });
diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Media/Manager/Editor.js b/wcfsetup/install/files/ts/WoltLabSuite/Core/Media/Manager/Editor.js
deleted file mode 100644 (file)
index 5a6ea27..0000000
+++ /dev/null
@@ -1,470 +0,0 @@
-/**
- * Provides the media manager dialog for selecting media for Redactor editors.
- *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Media/Manager/Editor
- */
-define(['Core', 'Dictionary', 'Dom/Traverse', 'EventHandler', 'Language', 'Permission', 'Ui/Dialog', 'WoltLabSuite/Core/Controller/Clipboard', 'WoltLabSuite/Core/Media/Manager/Base'],
-       function(Core, Dictionary, DomTraverse, EventHandler, Language, Permission, UiDialog, ControllerClipboard, MediaManagerBase) {
-       "use strict";
-               
-               if (!COMPILER_TARGET_DEFAULT) {
-                       var Fake = function() {};
-                       Fake.prototype = {
-                               _addButtonEventListeners: function() {},
-                               _buildInsertDialog: function() {},
-                               _click: function() {},
-                               _getInsertDialogId: function() {},
-                               _getThumbnailSizes: function() {},
-                               _insertMedia: function() {},
-                               _insertMediaGallery: function() {},
-                               _insertMediaItem: function() {},
-                               _openInsertDialog: function() {},
-                               insertMedia: function() {},
-                               getMode: function() {},
-                               setupMediaElement: function() {},
-                               _dialogClose: function() {},
-                               _dialogInit: function() {},
-                               _dialogSetup: function() {},
-                               _dialogShow: function() {},
-                               _editMedia: function() {},
-                               _editorClose: function() {},
-                               _editorSuccess: function() {},
-                               _removeClipboardCheckboxes: function() {},
-                               _setMedia: function() {},
-                               addMedia: function() {},
-                               clipboardInsertMedia: function() {},
-                               getDialog: function() {},
-                               getOption: function() {},
-                               removeMedia: function() {},
-                               resetMedia: function() {},
-                               setMedia: function() {}
-                       };
-                       return Fake;
-               }
-       
-       /**
-        * @constructor
-        */
-       function MediaManagerEditor(options) {
-               options = Core.extend({
-                       callbackInsert: null
-               }, options);
-               
-               MediaManagerBase.call(this, options);
-               
-               this._forceClipboard = true;
-               this._activeButton = null;
-               var context = (this._options.editor) ? this._options.editor.core.toolbar()[0] : undefined;
-               this._buttons = elByClass(this._options.buttonClass || 'jsMediaEditorButton', context);
-               for (var i = 0, length = this._buttons.length; i < length; i++) {
-                       this._buttons[i].addEventListener('click', this._click.bind(this));
-               }
-               this._mediaToInsert = new Dictionary();
-               this._mediaToInsertByClipboard = false;
-               this._uploadData = null;
-               this._uploadId = null;
-               
-               if (this._options.editor && !this._options.editor.opts.woltlab.attachments) {
-                       var editorId = elData(this._options.editor.$editor[0], 'element-id');
-                       
-                       var uuid1 = EventHandler.add('com.woltlab.wcf.redactor2', 'dragAndDrop_' + editorId, this._editorUpload.bind(this));
-                       var uuid2 = EventHandler.add('com.woltlab.wcf.redactor2', 'pasteFromClipboard_' + editorId, this._editorUpload.bind(this));
-                       
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'destory_' + editorId, function() {
-                               EventHandler.remove('com.woltlab.wcf.redactor2', 'dragAndDrop_' + editorId, uuid1);
-                               EventHandler.remove('com.woltlab.wcf.redactor2', 'dragAndDrop_' + editorId, uuid2);
-                       });
-                       
-                       EventHandler.add('com.woltlab.wcf.media.upload', 'success', this._mediaUploaded.bind(this));
-               }
-       }
-       Core.inherit(MediaManagerEditor, MediaManagerBase, {
-               /**
-                * @see WoltLabSuite/Core/Media/Manager/Base#_addButtonEventListeners
-                */
-               _addButtonEventListeners: function() {
-                       MediaManagerEditor._super.prototype._addButtonEventListeners.call(this);
-                       
-                       if (!this._mediaManagerMediaList) return;
-                       
-                       var listItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
-                       for (var i = 0, length = listItems.length; i < length; i++) {
-                               var listItem = listItems[i];
-                               
-                               var insertIcon = elByClass('jsMediaInsertButton', listItem)[0];
-                               if (insertIcon) {
-                                       insertIcon.classList.remove('jsMediaInsertButton');
-                                       insertIcon.addEventListener('click', this._openInsertDialog.bind(this));
-                               }
-                       }
-               },
-               
-               /**
-                * Builds the dialog to setup inserting media files.
-                */
-               _buildInsertDialog: function() {
-                       var thumbnailOptions = '';
-                       
-                       var thumbnailSizes = this._getThumbnailSizes();
-                       for (var i = 0, length = thumbnailSizes.length; i < length; i++) {
-                               thumbnailOptions += '<option value="' + thumbnailSizes[i] + '">' + Language.get('wcf.media.insert.imageSize.' + thumbnailSizes[i]) + '</option>';
-                       }
-                       thumbnailOptions += '<option value="original">' + Language.get('wcf.media.insert.imageSize.original') + '</option>';
-                       
-                       var dialog = '<div class="section">'
-                       /*+ (this._mediaToInsert.size > 1 ? '<dl>'
-                               + '<dt>' + Language.get('wcf.media.insert.type') + '</dt>'
-                               + '<dd>'
-                                       + '<select name="insertType">'
-                                               + '<option value="separate">' + Language.get('wcf.media.insert.type.separate') + '</option>'
-                                               + '<option value="gallery">' + Language.get('wcf.media.insert.type.gallery') + '</option>'
-                                       + '</select>'
-                               + '</dd>'
-                       + '</dl>' : '')*/
-                       + '<dl class="thumbnailSizeSelection">'
-                               + '<dt>' + Language.get('wcf.media.insert.imageSize') + '</dt>'
-                               + '<dd>'
-                                       + '<select name="thumbnailSize">'
-                                               + thumbnailOptions
-                                       + '</select>'
-                               + '</dd>'
-                       + '</dl>'
-                       + '</div>'
-                       + '<div class="formSubmit">'
-                               + '<button class="buttonPrimary">' + Language.get('wcf.global.button.insert') + '</button>'
-                       + '</div>';
-                       
-                       UiDialog.open({
-                               _dialogSetup: (function() {
-                                       return {
-                                               id: this._getInsertDialogId(),
-                                               options: {
-                                                       onClose: this._editorClose.bind(this),
-                                                       onSetup: function(content) {
-                                                               elByClass('buttonPrimary', content)[0].addEventListener('click', this._insertMedia.bind(this));
-                                                               
-                                                               // toggle thumbnail size selection based on selected insert type
-                                                               /*var insertType = elBySel('select[name=insertType]', content);
-                                                               if (insertType !== null) {
-                                                                       var thumbnailSelection = elByClass('thumbnailSizeSelection', content)[0];
-                                                                       insertType.addEventListener('change', function(event) {
-                                                                               if (event.currentTarget.value === 'gallery') {
-                                                                                       elHide(thumbnailSelection);
-                                                                               }
-                                                                               else {
-                                                                                       elShow(thumbnailSelection);
-                                                                               }
-                                                                       });
-                                                               }*/
-                                                               var thumbnailSelection = elBySel('.thumbnailSizeSelection', content);
-                                                               elShow(thumbnailSelection);
-                                                       }.bind(this),
-                                                       title: Language.get('wcf.media.insert')
-                                               },
-                                               source: dialog
-                                       };
-                               }).bind(this)
-                       });
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Media/Manager/Base#_click
-                */
-               _click: function(event) {
-                       this._activeButton = event.currentTarget;
-                       
-                       MediaManagerEditor._super.prototype._click.call(this, event);
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Media/Manager/Base#_dialogShow
-                */
-               _dialogShow: function() {
-                       MediaManagerEditor._super.prototype._dialogShow.call(this);
-                       
-                       // check if data needs to be uploaded
-                       if (this._uploadData) {
-                               if (this._uploadData.file) {
-                                       this._upload.uploadFile(this._uploadData.file);
-                               }
-                               else {
-                                       this._uploadId = this._upload.uploadBlob(this._uploadData.blob);
-                               }
-                               
-                               this._uploadData = null;
-                       }
-               },
-               
-               /**
-                * Handles pasting and dragging and dropping files into the editor. 
-                * 
-                * @param       {object}        data    data of the uploaded file
-                */
-               _editorUpload: function(data) {
-                       this._uploadData = data;
-                       
-                       UiDialog.open(this);
-               },
-               
-               /**
-                * Returns the id of the insert dialog based on the media files to be inserted.
-                * 
-                * @return      {string}        insert dialog id
-                */
-               _getInsertDialogId: function() {
-                       var dialogId = 'mediaInsert';
-                       
-                       this._mediaToInsert.forEach(function(media, mediaId) {
-                               dialogId += '-' + mediaId;
-                       });
-                       
-                       return dialogId;
-               },
-               
-               /**
-                * Returns the supported thumbnail sizes (excluding `original`) for all media images to be inserted.
-                * 
-                * @return      {string[]}
-                */
-               _getThumbnailSizes: function() {
-                       var sizes = [];
-                       
-                       var supportedSizes = ['small', 'medium', 'large'];
-                       var size, supportSize;
-                       for (var i = 0, length = supportedSizes.length; i < length; i++) {
-                               size = supportedSizes[i];
-                               
-                               supportSize = true;
-                               this._mediaToInsert.forEach(function(media) {
-                                       if (!media[size + 'ThumbnailType']) {
-                                               supportSize = false;
-                                       }
-                               });
-                               
-                               if (supportSize) {
-                                       sizes.push(size);
-                               }
-                       }
-                       
-                       return sizes;
-               },
-               
-               /**
-                * Inserts media files into redactor.
-                * 
-                * @param       {Event?}        event
-                * @param       {string?}       thumbnailSize
-                * @param       {boolean?}      closeEditor
-                */
-               _insertMedia: function(event, thumbnailSize, closeEditor) {
-                       if (closeEditor === undefined) closeEditor = true;
-                       
-                       var insertType = 'separate';
-                       
-                       // update insert options with selected values if method is called by clicking on 'insert' button
-                       // in dialog
-                       if (event) {
-                               UiDialog.close(this._getInsertDialogId());
-                               
-                               var dialogContent = event.currentTarget.closest('.dialogContent');
-                               
-                               /*if (this._mediaToInsert.size > 1) {
-                                       insertType = elBySel('select[name=insertType]', dialogContent).value;
-                               }*/
-                               thumbnailSize = elBySel('select[name=thumbnailSize]', dialogContent).value;
-                       }
-                       
-                       if (this._options.callbackInsert !== null) {
-                               this._options.callbackInsert(this._mediaToInsert, insertType, thumbnailSize);
-                       }
-                       else {
-                               if (insertType === 'separate') {
-                                       this._options.editor.buffer.set();
-                                       
-                                       this._mediaToInsert.forEach(this._insertMediaItem.bind(this, thumbnailSize));
-                               }
-                               else {
-                                       this._insertMediaGallery();
-                               }
-                       }
-                       
-                       if (this._mediaToInsertByClipboard) {
-                               var mediaIds = [];
-                               this._mediaToInsert.forEach(function(media) {
-                                       mediaIds.push(media.mediaID);
-                               });
-                               
-                               ControllerClipboard.unmark('com.woltlab.wcf.media', mediaIds);
-                       }
-                       
-                       this._mediaToInsert = new Dictionary();
-                       this._mediaToInsertByClipboard = false;
-                       
-                       // close manager dialog
-                       if (closeEditor) {
-                               UiDialog.close(this);
-                       }
-               },
-               
-               /**
-                * Inserts a series of uploaded images using a slider.
-                * 
-                * @protected
-                */
-               _insertMediaGallery: function() {
-                       var mediaIds = [];
-                       this._mediaToInsert.forEach(function(item) {
-                               mediaIds.push(item.mediaID);
-                       });
-                       
-                       this._options.editor.buffer.set();
-                       this._options.editor.insert.text("[wsmg='" + mediaIds.join(',') + "'][/wsmg]");
-               },
-               
-               /**
-                * Inserts a single media item.
-                * 
-                * @param       {string}        thumbnailSize   preferred image dimension, is ignored for non-images
-                * @param       {Object}        item            media item data
-                * @protected
-                */
-               _insertMediaItem: function(thumbnailSize, item) {
-                       if (item.isImage) {
-                               var sizes = ['small', 'medium', 'large', 'original'];
-                               
-                               // check if size is actually available
-                               var available = '', size;
-                               for (var i = 0; i < 4; i++) {
-                                       size = sizes[i];
-                                       
-                                       if (item[size + 'ThumbnailHeight'] != 0) {
-                                               available = size;
-                                               
-                                               if (thumbnailSize == size) {
-                                                       break;
-                                               }
-                                       }
-                               }
-                               
-                               thumbnailSize = available;
-                               
-                               if (!thumbnailSize) thumbnailSize = 'original';
-                               
-                               var link = item.link;
-                               if (thumbnailSize !== 'original') {
-                                       link = item[thumbnailSize + 'ThumbnailLink'];
-                               }
-                               
-                               this._options.editor.insert.html('<img src="' + link + '" class="woltlabSuiteMedia" data-media-id="' + item.mediaID + '" data-media-size="' + thumbnailSize + '">');
-                       }
-                       else {
-                               this._options.editor.insert.text("[wsm='" + item.mediaID + "'][/wsm]");
-                       }
-               },
-               
-               /**
-                * Is called after media files are successfully uploaded to insert copied media.
-                * 
-                * @param       {object}        data            upload data
-                */
-               _mediaUploaded: function(data) {
-                       if (this._uploadId !== null && this._upload === data.upload) {
-                               if (this._uploadId === data.uploadId || (Array.isArray(this._uploadId) && this._uploadId.indexOf(data.uploadId) !== -1)) {
-                                       this._mediaToInsert = Dictionary.fromObject(data.media);
-                                       this._insertMedia(null, 'medium', false);
-                                       
-                                       this._uploadId = null;
-                               }
-                       }
-               },
-               
-               /**
-                * Handles clicking on the insert button.
-                * 
-                * @param       {Event}         event           insert button click event
-                */
-               _openInsertDialog: function(event) {
-                       this.insertMedia([~~elData(event.currentTarget, 'object-id')]);
-               },
-               
-               /**
-                * Is called to insert the media files with the given ids into an editor.
-                * 
-                * @param       {int[]}         mediaIds
-                */
-               clipboardInsertMedia: function(mediaIds) {
-                       this.insertMedia(mediaIds, true);
-               },
-               
-               /**
-                * Prepares insertion of the media files with the given ids.
-                * 
-                * @param       {array<int>}    mediaIds                ids of the media files to be inserted
-                * @param       {boolean?}      insertedByClipboard     is true if the media files are inserted by clipboard
-                */
-               insertMedia: function(mediaIds, insertedByClipboard) {
-                       this._mediaToInsert = new Dictionary();
-                       this._mediaToInsertByClipboard = insertedByClipboard || false;
-                       
-                       // open the insert dialog if all media files are images
-                       var imagesOnly = true, media;
-                       for (var i = 0, length = mediaIds.length; i < length; i++) {
-                               media = this._media.get(mediaIds[i]);
-                               this._mediaToInsert.set(media.mediaID, media);
-                               
-                               if (!media.isImage) {
-                                       imagesOnly = false;
-                               }
-                       }
-                       
-                       if (imagesOnly) {
-                               var thumbnailSizes = this._getThumbnailSizes();
-                               if (thumbnailSizes.length) {
-                                       UiDialog.close(this);
-                                       var dialogId = this._getInsertDialogId();
-                                       if (UiDialog.getDialog(dialogId)) {
-                                               UiDialog.openStatic(dialogId);
-                                       }
-                                       else {
-                                               this._buildInsertDialog();
-                                       }
-                               }
-                               else {
-                                       this._insertMedia(undefined, 'original');
-                               }
-                       }
-                       else {
-                               this._insertMedia();
-                       }
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Media/Manager/Base#getMode
-                */
-               getMode: function() {
-                       return 'editor';
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Media/Manager/Base#setupMediaElement
-                */
-               setupMediaElement: function(media, mediaElement) {
-                       MediaManagerEditor._super.prototype.setupMediaElement.call(this, media, mediaElement);
-                       
-                       // add media insertion icon
-                       var buttons = elBySel('nav.buttonGroupNavigation > ul', mediaElement);
-                       
-                       var listItem = elCreate('li');
-                       listItem.className = 'jsMediaInsertButton';
-                       elData(listItem, 'object-id', media.mediaID);
-                       buttons.appendChild(listItem);
-                       
-                       listItem.innerHTML = '<a><span class="icon icon16 fa-plus jsTooltip" title="' + Language.get('wcf.media.button.insert') + '"></span> <span class="invisible">' + Language.get('wcf.media.button.insert') + '</span></a>';
-               }
-       });
-       
-       return MediaManagerEditor;
-});
diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Media/Manager/Editor.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/Media/Manager/Editor.ts
new file mode 100644 (file)
index 0000000..642deb8
--- /dev/null
@@ -0,0 +1,403 @@
+/**
+ * Provides the media manager dialog for selecting media for Redactor editors.
+ *
+ * @author  Matthias Schmidt
+ * @copyright 2001-2021 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  WoltLabSuite/Core/Media/Manager/Editor
+ */
+
+import MediaManager from "./Base";
+import * as Core from "../../Core";
+import { Media, MediaManagerEditorOptions, MediaUploadSuccessEventData } from "../Data";
+import * as EventHandler from "../../Event/Handler";
+import * as DomTraverse from "../../Dom/Traverse";
+import * as Language from "../../Language";
+import * as UiDialog from "../../Ui/Dialog";
+import * as Clipboard from "../../Controller/Clipboard";
+import { OnDropPayload } from "../../Ui/Redactor/DragAndDrop";
+
+type PasteFromClipboard = {
+  blob: Blob;
+};
+
+class MediaManagerEditor extends MediaManager<MediaManagerEditorOptions> {
+  protected _activeButton;
+  protected readonly _buttons: HTMLCollectionOf<HTMLElement>;
+  protected _mediaToInsert: Map<number, Media>;
+  protected _mediaToInsertByClipboard: boolean;
+  protected _uploadData: OnDropPayload | PasteFromClipboard | null;
+  protected _uploadId: number | null;
+
+  constructor(options: Partial<MediaManagerEditorOptions>) {
+    options = Core.extend(
+      {
+        callbackInsert: null,
+      },
+      options,
+    );
+
+    super(options);
+
+    this._forceClipboard = true;
+    this._activeButton = null;
+    const context = this._options.editor ? this._options.editor.core.toolbar()[0] : undefined;
+    this._buttons = (context || window.document).getElementsByClassName(
+      this._options.buttonClass || "jsMediaEditorButton",
+    ) as HTMLCollectionOf<HTMLElement>;
+    Array.from(this._buttons).forEach((button) => {
+      button.addEventListener("click", (ev) => this._click(ev));
+    });
+    this._mediaToInsert = new Map<number, Media>();
+    this._mediaToInsertByClipboard = false;
+    this._uploadData = null;
+    this._uploadId = null;
+
+    if (this._options.editor && !this._options.editor.opts.woltlab.attachments) {
+      const editorId = this._options.editor.$editor[0].dataset.elementId as string;
+
+      const uuid1 = EventHandler.add("com.woltlab.wcf.redactor2", `dragAndDrop_${editorId}`, (data: OnDropPayload) =>
+        this._editorUpload(data),
+      );
+      const uuid2 = EventHandler.add(
+        "com.woltlab.wcf.redactor2",
+        `pasteFromClipboard_${editorId}`,
+        (data: OnDropPayload) => this._editorUpload(data),
+      );
+
+      EventHandler.add("com.woltlab.wcf.redactor2", `destroy_${editorId}`, () => {
+        EventHandler.remove("com.woltlab.wcf.redactor2", `dragAndDrop_${editorId}`, uuid1);
+        EventHandler.remove("com.woltlab.wcf.redactor2", `dragAndDrop_${editorId}`, uuid2);
+      });
+
+      EventHandler.add("com.woltlab.wcf.media.upload", "success", (data) => this._mediaUploaded(data));
+    }
+  }
+
+  protected _addButtonEventListeners(): void {
+    super._addButtonEventListeners();
+
+    if (!this._mediaManagerMediaList) {
+      return;
+    }
+
+    DomTraverse.childrenByTag(this._mediaManagerMediaList, "LI").forEach((listItem) => {
+      const insertIcon = listItem.querySelector(".jsMediaInsertButton");
+      if (insertIcon) {
+        insertIcon.classList.remove("jsMediaInsertButton");
+        insertIcon.addEventListener("click", (ev: MouseEvent) => this._openInsertDialog(ev));
+      }
+    });
+  }
+
+  /**
+   * Builds the dialog to setup inserting media files.
+   */
+  protected _buildInsertDialog(): void {
+    let thumbnailOptions = "";
+
+    this._getThumbnailSizes().forEach((thumbnailSize) => {
+      thumbnailOptions +=
+        '<option value="' +
+        thumbnailSize +
+        '">' +
+        Language.get("wcf.media.insert.imageSize." + thumbnailSize) +
+        "</option>";
+    });
+    thumbnailOptions += '<option value="original">' + Language.get("wcf.media.insert.imageSize.original") + "</option>";
+
+    const dialog = `
+      <div class="section">
+        <dl class="thumbnailSizeSelection">
+          <dt>${Language.get("wcf.media.insert.imageSize")}</dt>
+          <dd>
+            <select name="thumbnailSize">
+              ${thumbnailOptions}
+            </select>
+          </dd>
+        </dl>
+      </div>
+      <div class="formSubmit">
+        <button class="buttonPrimary">${Language.get("wcf.global.button.insert")}</button>
+      </div>`;
+
+    UiDialog.open({
+      _dialogSetup: () => {
+        return {
+          id: this._getInsertDialogId(),
+          options: {
+            onClose: this._editorClose.bind(this),
+            onSetup: (content) => {
+              content.querySelector(".buttonPrimary")!.addEventListener("click", (ev) => this._insertMedia(ev));
+
+              (content.querySelector(".thumbnailSizeSelection") as HTMLElement).style.display = "block";
+            },
+            title: Language.get("wcf.media.insert"),
+          },
+          source: dialog,
+        };
+      },
+    });
+  }
+
+  protected _click(event: MouseEvent): void {
+    this._activeButton = event.currentTarget;
+
+    super._click(event);
+  }
+
+  protected _dialogShow(): void {
+    super._dialogShow();
+
+    // check if data needs to be uploaded
+    if (this._uploadData) {
+      const fileUploadData = this._uploadData as OnDropPayload;
+      if (fileUploadData.file) {
+        this._upload.uploadFile(fileUploadData.file);
+      } else {
+        const blobUploadData = this._uploadData as PasteFromClipboard;
+        this._uploadId = this._upload.uploadBlob(blobUploadData.blob);
+      }
+
+      this._uploadData = null;
+    }
+  }
+
+  /**
+   * Handles pasting and dragging and dropping files into the editor.
+   */
+  protected _editorUpload(data: OnDropPayload): void {
+    this._uploadData = data;
+
+    UiDialog.open(this);
+  }
+
+  /**
+   * Returns the id of the insert dialog based on the media files to be inserted.
+   */
+  protected _getInsertDialogId(): string {
+    let dialogId = "mediaInsert";
+
+    this._mediaToInsert.forEach((media, mediaId) => {
+      dialogId += `-${mediaId}`;
+    });
+
+    return dialogId;
+  }
+
+  /**
+   * Returns the supported thumbnail sizes (excluding `original`) for all media images to be inserted.
+   */
+  protected _getThumbnailSizes(): string[] {
+    return ["small", "medium", "large"]
+      .map((size) => {
+        let supportSize = true;
+        this._mediaToInsert.forEach(function (media) {
+          if (!media[size + "ThumbnailType"]) {
+            supportSize = false;
+          }
+        });
+
+        if (supportSize) {
+          return size;
+        }
+
+        return null;
+      })
+      .filter((s) => s !== null) as string[];
+  }
+
+  /**
+   * Inserts media files into the editor.
+   */
+  protected _insertMedia(event?: Event | null, thumbnailSize?: string, closeEditor = false): void {
+    if (closeEditor === undefined) closeEditor = true;
+
+    const insertType = "separate";
+
+    // update insert options with selected values if method is called by clicking on 'insert' button
+    // in dialog
+    if (event) {
+      UiDialog.close(this._getInsertDialogId());
+
+      const dialogContent = (event.currentTarget as HTMLElement).closest(".dialogContent")!;
+      const thumbnailSizeSelect = dialogContent.querySelector("select[name=thumbnailSize]") as HTMLSelectElement;
+      thumbnailSize = thumbnailSizeSelect.value;
+    }
+
+    if (this._options.callbackInsert !== null) {
+      this._options.callbackInsert(this._mediaToInsert, insertType, thumbnailSize!);
+    } else {
+      if (insertType === "separate") {
+        this._options.editor!.buffer.set();
+
+        this._mediaToInsert.forEach((media) => this._insertMediaItem(thumbnailSize!, media));
+      } else {
+        this._insertMediaGallery();
+      }
+    }
+
+    if (this._mediaToInsertByClipboard) {
+      const mediaIds: number[] = [];
+      this._mediaToInsert.forEach(function (media) {
+        mediaIds.push(~~media.mediaID);
+      });
+
+      Clipboard.unmark("com.woltlab.wcf.media", mediaIds);
+    }
+
+    this._mediaToInsert = new Map<number, Media>();
+    this._mediaToInsertByClipboard = false;
+
+    // close manager dialog
+    if (closeEditor) {
+      UiDialog.close(this);
+    }
+  }
+
+  /**
+   * Inserts a series of uploaded images into the editor using a slider.
+   */
+  protected _insertMediaGallery(): void {
+    const mediaIds: number[] = [];
+    this._mediaToInsert.forEach(function (media) {
+      mediaIds.push(media.mediaID);
+    });
+
+    this._options.editor!.buffer.set();
+    this._options.editor!.insert.text("[wsmg='" + mediaIds.join(",") + "'][/wsmg]");
+  }
+
+  /**
+   * Inserts a single media item into the editor.
+   */
+  protected _insertMediaItem(thumbnailSize: string, media: Media): void {
+    if (media.isImage) {
+      let available = "";
+      ["small", "medium", "large", "original"].some((size) => {
+        if (media[size + "ThumbnailHeight"] != 0) {
+          available = size;
+
+          if (thumbnailSize == size) {
+            return true;
+          }
+        }
+
+        return false;
+      });
+
+      thumbnailSize = available;
+
+      if (!thumbnailSize) {
+        thumbnailSize = "original";
+      }
+
+      let link = media.link;
+      if (thumbnailSize !== "original") {
+        link = media[thumbnailSize + "ThumbnailLink"];
+      }
+
+      this._options.editor!.insert.html(
+        `<img src="${link}" class="woltlabSuiteMedia" data-media-id="${media.mediaID}" data-media-size="${thumbnailSize}">`,
+      );
+    } else {
+      this._options.editor!.insert.text(`[wsm='${media.mediaID}'][/wsm]`);
+    }
+  }
+
+  /**
+   * Is called after media files are successfully uploaded to insert copied media.
+   */
+  protected _mediaUploaded(data: MediaUploadSuccessEventData): void {
+    if (this._uploadId !== null && this._upload === data.upload) {
+      if (
+        this._uploadId === data.uploadId ||
+        (Array.isArray(this._uploadId) && this._uploadId.indexOf(data.uploadId) !== -1)
+      ) {
+        this._mediaToInsert = new Map<number, Media>(data.media.entries());
+        this._insertMedia(null, "medium", false);
+
+        this._uploadId = null;
+      }
+    }
+  }
+
+  /**
+   * Handles clicking on the insert button.
+   */
+  protected _openInsertDialog(event: MouseEvent): void {
+    const target = event.currentTarget as HTMLElement;
+
+    this.insertMedia([~~target.dataset.objectId!]);
+  }
+
+  /**
+   * Is called to insert the media files with the given ids into an editor.
+   */
+  public clipboardInsertMedia(mediaIds: number[]): void {
+    this.insertMedia(mediaIds, true);
+  }
+
+  /**
+   * Prepares insertion of the media files with the given ids.
+   */
+  public insertMedia(mediaIds: number[], insertedByClipboard?: boolean): void {
+    this._mediaToInsert = new Map<number, Media>();
+    this._mediaToInsertByClipboard = insertedByClipboard || false;
+
+    // open the insert dialog if all media files are images
+    let imagesOnly = true,
+      media;
+    for (let i = 0, length = mediaIds.length; i < length; i++) {
+      media = this._media.get(mediaIds[i]);
+      this._mediaToInsert.set(media.mediaID, media);
+
+      if (!media.isImage) {
+        imagesOnly = false;
+      }
+    }
+
+    if (imagesOnly) {
+      const thumbnailSizes = this._getThumbnailSizes();
+      if (thumbnailSizes.length) {
+        UiDialog.close(this);
+        const dialogId = this._getInsertDialogId();
+        if (UiDialog.getDialog(dialogId)) {
+          UiDialog.openStatic(dialogId, null);
+        } else {
+          this._buildInsertDialog();
+        }
+      } else {
+        this._insertMedia(undefined, "original");
+      }
+    } else {
+      this._insertMedia();
+    }
+  }
+
+  public getMode(): string {
+    return "editor";
+  }
+
+  public setupMediaElement(media: Media, mediaElement: HTMLElement): void {
+    super.setupMediaElement(media, mediaElement);
+
+    // add media insertion icon
+    const buttons = mediaElement.querySelector("nav.buttonGroupNavigation > ul")!;
+
+    const listItem = document.createElement("li");
+    listItem.className = "jsMediaInsertButton";
+    listItem.dataset.objectId = (media.mediaID as unknown) as string;
+    buttons.appendChild(listItem);
+
+    listItem.innerHTML = `
+      <a>
+        <span class="icon icon16 fa-plus jsTooltip" title="${Language.get("wcf.global.button.insert")}"></span>
+        <span class="invisible">${Language.get("wcf.global.button.insert")}</span>
+      </a>`;
+  }
+}
+
+Core.enableLegacyInheritance(MediaManagerEditor);
+
+export = MediaManagerEditor;