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

index 94be76efbb2e570048e7d620ef9c6ab261346db1..5e7e6dc359414ef1f2871615f899997becc34172 100644 (file)
 /**
  * Provides the media manager dialog.
  *
- * @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/Base
+ * @author  Matthias Schmidt
+ * @copyright 2001-2020 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  WoltLabSuite/Core/Media/Manager/Base
  */
-define([
-    'Core', 'Dictionary', 'Dom/ChangeListener', 'Dom/Traverse',
-    'Dom/Util', 'EventHandler', 'Language', 'List',
-    'Permission', 'Ui/Dialog', 'Ui/Notification', 'WoltLabSuite/Core/Controller/Clipboard',
-    'WoltLabSuite/Core/Media/Editor', 'WoltLabSuite/Core/Media/Upload', 'WoltLabSuite/Core/Media/Manager/Search', 'StringUtil',
-    'WoltLabSuite/Core/Ui/Pagination',
-    'WoltLabSuite/Core/Media/Clipboard'
-], function (Core, Dictionary, DomChangeListener, DomTraverse, DomUtil, EventHandler, Language, List, Permission, UiDialog, UiNotification, Clipboard, MediaEditor, MediaUpload, MediaManagerSearch, StringUtil, UiPagination, MediaClipboard) {
+define(["require", "exports", "tslib", "../../Core", "../../Language", "../../Permission", "../../Dom/Change/Listener", "../../Event/Handler", "../../Dom/Traverse", "../../Dom/Util", "../../Ui/Dialog", "../../Controller/Clipboard", "../../Ui/Pagination", "../../Ui/Notification", "../../StringUtil", "./Search", "../Upload", "../Editor", "../Clipboard"], function (require, exports, tslib_1, Core, Language, Permission, DomChangeListener, EventHandler, DomTraverse, DomUtil, UiDialog, Clipboard, Pagination_1, UiNotification, StringUtil, Search_1, Upload_1, Editor_1, MediaClipboard) {
     "use strict";
-    if (!COMPILER_TARGET_DEFAULT) {
-        var Fake = function () { };
-        Fake.prototype = {
-            _addButtonEventListeners: function () { },
-            _click: function () { },
-            _dialogClose: function () { },
-            _dialogInit: function () { },
-            _dialogSetup: function () { },
-            _dialogShow: function () { },
-            _editMedia: function () { },
-            _editorClose: function () { },
-            _editorSuccess: function () { },
-            _removeClipboardCheckboxes: function () { },
-            _setMedia: function () { },
-            addMedia: function () { },
-            clipboardDeleteMedia: function () { },
-            getDialog: function () { },
-            getMode: function () { },
-            getOption: function () { },
-            removeMedia: function () { },
-            resetMedia: function () { },
-            setMedia: function () { },
-            setupMediaElement: function () { }
-        };
-        return Fake;
-    }
-    var _mediaManagerCounter = 0;
-    /**
-     * @constructor
-     */
-    function MediaManagerBase(options) {
-        this._options = Core.extend({
-            dialogTitle: Language.get('wcf.media.manager'),
-            imagesOnly: false,
-            minSearchLength: 3
-        }, options);
-        this._id = 'mediaManager' + _mediaManagerCounter++;
-        this._listItems = new Dictionary();
-        this._media = new Dictionary();
-        this._mediaManagerMediaList = null;
-        this._search = null;
-        this._upload = null;
-        this._forceClipboard = false;
-        this._hadInitiallyMarkedItems = false;
-        this._pagination = null;
-        if (Permission.get('admin.content.cms.canManageMedia')) {
-            this._mediaEditor = new MediaEditor(this);
+    Core = tslib_1.__importStar(Core);
+    Language = tslib_1.__importStar(Language);
+    Permission = tslib_1.__importStar(Permission);
+    DomChangeListener = tslib_1.__importStar(DomChangeListener);
+    EventHandler = tslib_1.__importStar(EventHandler);
+    DomTraverse = tslib_1.__importStar(DomTraverse);
+    DomUtil = tslib_1.__importStar(DomUtil);
+    UiDialog = tslib_1.__importStar(UiDialog);
+    Clipboard = tslib_1.__importStar(Clipboard);
+    Pagination_1 = tslib_1.__importDefault(Pagination_1);
+    UiNotification = tslib_1.__importStar(UiNotification);
+    StringUtil = tslib_1.__importStar(StringUtil);
+    Search_1 = tslib_1.__importDefault(Search_1);
+    Upload_1 = tslib_1.__importDefault(Upload_1);
+    Editor_1 = tslib_1.__importDefault(Editor_1);
+    MediaClipboard = tslib_1.__importStar(MediaClipboard);
+    let mediaManagerCounter = 0;
+    class MediaManager {
+        constructor(options) {
+            this._forceClipboard = false;
+            this._hadInitiallyMarkedItems = false;
+            this._id = `mediaManager${mediaManagerCounter++}`;
+            this._listItems = new Map();
+            this._media = new Map();
+            this._mediaEditor = null;
+            this._mediaManagerMediaList = null;
+            this._pagination = null;
+            this._search = null;
+            this._upload = null;
+            this._options = Core.extend({
+                dialogTitle: Language.get("wcf.media.manager"),
+                imagesOnly: false,
+                minSearchLength: 3,
+            }, options);
+            if (Permission.get("admin.content.cms.canManageMedia")) {
+                this._mediaEditor = new Editor_1.default(this);
+            }
+            DomChangeListener.add("WoltLabSuite/Core/Media/Manager", () => this._addButtonEventListeners());
+            EventHandler.add("com.woltlab.wcf.media.upload", "success", (data) => this._openEditorAfterUpload(data));
         }
-        DomChangeListener.add('WoltLabSuite/Core/Media/Manager', this._addButtonEventListeners.bind(this));
-        EventHandler.add('com.woltlab.wcf.media.upload', 'success', this._openEditorAfterUpload.bind(this));
-    }
-    MediaManagerBase.prototype = {
         /**
          * Adds click event listeners to media buttons.
          */
-        _addButtonEventListeners: function () {
-            if (!this._mediaManagerMediaList)
+        _addButtonEventListeners() {
+            if (!this._mediaManagerMediaList || !Permission.get("admin.content.cms.canManageMedia"))
                 return;
-            var listItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
-            for (var i = 0, length = listItems.length; i < length; i++) {
-                var listItem = listItems[i];
-                if (Permission.get('admin.content.cms.canManageMedia')) {
-                    var editIcon = elByClass('jsMediaEditButton', listItem)[0];
-                    if (editIcon) {
-                        editIcon.classList.remove('jsMediaEditButton');
-                        editIcon.addEventListener('click', this._editMedia.bind(this));
-                    }
+            DomTraverse.childrenByTag(this._mediaManagerMediaList, "LI").forEach((listItem) => {
+                const editIcon = listItem.querySelector(".jsMediaEditButton");
+                if (editIcon) {
+                    editIcon.classList.remove("jsMediaEditButton");
+                    editIcon.addEventListener("click", (ev) => this._editMedia(ev));
                 }
-            }
-        },
+            });
+        }
         /**
          * Is called when a new category is selected.
          */
-        _categoryChange: function () {
+        _categoryChange() {
             this._search.search();
-        },
+        }
         /**
          * Handles clicks on the media manager button.
-         *
-         * @param      {object}        event   event object
          */
-        _click: function (event) {
+        _click(event) {
             event.preventDefault();
             UiDialog.open(this);
-        },
+        }
         /**
          * Is called if the media manager dialog is closed.
          */
-        _dialogClose: function () {
+        _dialogClose() {
             // only show media clipboard if editor is open
-            if (Permission.get('admin.content.cms.canManageMedia') || this._forceClipboard) {
-                Clipboard.hideEditor('com.woltlab.wcf.media');
+            if (Permission.get("admin.content.cms.canManageMedia") || this._forceClipboard) {
+                Clipboard.hideEditor("com.woltlab.wcf.media");
             }
-        },
+        }
         /**
          * Initializes the dialog when first loaded.
-         *
-         * @param      {string}        content         dialog content
-         * @param      {object}        data            AJAX request's response data
          */
-        _dialogInit: function (content, data) {
+        _dialogInit(content, data) {
             // store media data locally
-            var media = data.returnValues.media || {};
-            for (var mediaId in media) {
-                if (objOwns(media, mediaId)) {
-                    this._media.set(~~mediaId, media[mediaId]);
-                }
-            }
+            Object.entries(data.returnValues.media || {}).forEach(([mediaId, media]) => {
+                this._media.set(~~mediaId, media);
+            });
             this._initPagination(~~data.returnValues.pageCount);
-            this._hadInitiallyMarkedItems = data.returnValues.hasMarkedItems;
-        },
+            this._hadInitiallyMarkedItems = data.returnValues.hasMarkedItems > 0;
+        }
         /**
          * Returns all data to setup the media manager dialog.
-         *
-         * @return     {object}        dialog setup data
          */
-        _dialogSetup: function () {
+        _dialogSetup() {
             return {
                 id: this._id,
                 options: {
-                    onClose: this._dialogClose.bind(this),
-                    onShow: this._dialogShow.bind(this),
-                    title: this._options.dialogTitle
+                    onClose: () => this._dialogClose(),
+                    onShow: () => this._dialogShow(),
+                    title: this._options.dialogTitle,
                 },
                 source: {
-                    after: this._dialogInit.bind(this),
+                    after: (content, data) => this._dialogInit(content, data),
                     data: {
-                        actionName: 'getManagementDialog',
-                        className: 'wcf\\data\\media\\MediaAction',
+                        actionName: "getManagementDialog",
+                        className: "wcf\\data\\media\\MediaAction",
                         parameters: {
                             mode: this.getMode(),
-                            imagesOnly: this._options.imagesOnly
-                        }
-                    }
-                }
+                            imagesOnly: this._options.imagesOnly,
+                        },
+                    },
+                },
             };
-        },
+        }
         /**
          * Is called if the media manager dialog is shown.
          */
-        _dialogShow: function () {
+        _dialogShow() {
             if (!this._mediaManagerMediaList) {
-                var dialog = this.getDialog();
-                this._mediaManagerMediaList = elByClass('mediaManagerMediaList', dialog)[0];
-                this._mediaCategorySelect = elBySel('.mediaManagerCategoryList > select', dialog);
+                const dialog = this.getDialog();
+                this._mediaManagerMediaList = dialog.querySelector(".mediaManagerMediaList");
+                this._mediaCategorySelect = dialog.querySelector(".mediaManagerCategoryList > select");
                 if (this._mediaCategorySelect) {
-                    this._mediaCategorySelect.addEventListener('change', this._categoryChange.bind(this));
+                    this._mediaCategorySelect.addEventListener("change", this._categoryChange.bind(this));
                 }
                 // store list items locally
-                var listItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
-                for (var i = 0, length = listItems.length; i < length; i++) {
-                    var listItem = listItems[i];
-                    this._listItems.set(~~elData(listItem, 'object-id'), listItem);
-                }
-                if (Permission.get('admin.content.cms.canManageMedia')) {
-                    var uploadButton = elByClass('mediaManagerMediaUploadButton', UiDialog.getDialog(this).dialog)[0];
-                    this._upload = new MediaUpload(DomUtil.identify(uploadButton), DomUtil.identify(this._mediaManagerMediaList), {
-                        mediaManager: this
+                const listItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, "LI");
+                listItems.forEach((listItem) => {
+                    this._listItems.set(~~listItem.dataset.objectId, listItem);
+                });
+                if (Permission.get("admin.content.cms.canManageMedia")) {
+                    const uploadButton = UiDialog.getDialog(this).dialog.querySelector(".mediaManagerMediaUploadButton");
+                    this._upload = new Upload_1.default(DomUtil.identify(uploadButton), DomUtil.identify(this._mediaManagerMediaList), {
+                        mediaManager: this,
                     });
-                    var deleteAction = new WCF.Action.Delete('wcf\\data\\media\\MediaAction', '.mediaFile');
-                    deleteAction._didTriggerEffect = function (element) {
-                        this.removeMedia(elData(element[0], 'object-id'));
-                    }.bind(this);
+                    // eslint-disable-next-line
+                    //@ts-ignore
+                    const deleteAction = new WCF.Action.Delete("wcf\\data\\media\\MediaAction", ".mediaFile");
+                    deleteAction._didTriggerEffect = (element) => this.removeMedia(element[0].dataset.objectId);
                 }
-                if (Permission.get('admin.content.cms.canManageMedia') || this._forceClipboard) {
-                    MediaClipboard.init('menuManagerDialog-' + this.getMode(), this._hadInitiallyMarkedItems ? true : false, this);
+                if (Permission.get("admin.content.cms.canManageMedia") || this._forceClipboard) {
+                    MediaClipboard.init("menuManagerDialog-" + this.getMode(), this._hadInitiallyMarkedItems ? true : false, this);
                 }
                 else {
                     this._removeClipboardCheckboxes();
                 }
-                this._search = new MediaManagerSearch(this);
+                this._search = new Search_1.default(this);
                 if (!listItems.length) {
                     this._search.hideSearch();
                 }
             }
             // only show media clipboard if editor is open
-            if (Permission.get('admin.content.cms.canManageMedia') || this._forceClipboard) {
-                Clipboard.showEditor('com.woltlab.wcf.media');
+            if (Permission.get("admin.content.cms.canManageMedia") || this._forceClipboard) {
+                Clipboard.showEditor();
             }
-        },
+        }
         /**
          * Opens the media editor for a media file.
-         *
-         * @param      {Event}         event           event object for clicks on edit icons
          */
-        _editMedia: function (event) {
-            if (!Permission.get('admin.content.cms.canManageMedia')) {
+        _editMedia(event) {
+            if (!Permission.get("admin.content.cms.canManageMedia")) {
                 throw new Error("You are not allowed to edit media files.");
             }
             UiDialog.close(this);
-            this._mediaEditor.edit(this._media.get(~~elData(event.currentTarget, 'object-id')));
-        },
+            const target = event.currentTarget;
+            this._mediaEditor.edit(this._media.get(~~target.dataset.objectId));
+        }
         /**
          * Re-opens the manager dialog after closing the editor dialog.
          */
-        _editorClose: function () {
+        _editorClose() {
             UiDialog.open(this);
-        },
+        }
         /**
-         * Re-opens the manager dialog and updates the media data after
-         * successfully editing a media file.
-         *
-         * @param      {object}        media           updated media file data
-         * @param      {integer}       oldCategoryId   old category id
+         * Re-opens the manager dialog and updates the media data after successfully editing a media file.
          */
-        _editorSuccess: function (media, oldCategoryId) {
+        _editorSuccess(media, oldCategoryId) {
             // if the category changed of media changed and category
             // is selected, check if media list needs to be refreshed
             if (this._mediaCategorySelect) {
-                var selectedCategoryId = ~~this._mediaCategorySelect.value;
+                const selectedCategoryId = ~~this._mediaCategorySelect.value;
                 if (selectedCategoryId) {
-                    var newCategoryId = ~~media.categoryID;
-                    if (oldCategoryId != newCategoryId && (oldCategoryId == selectedCategoryId || newCategoryId == selectedCategoryId)) {
+                    const newCategoryId = ~~media.categoryID;
+                    if (oldCategoryId != newCategoryId &&
+                        (oldCategoryId == selectedCategoryId || newCategoryId == selectedCategoryId)) {
                         this._search.search();
                     }
                 }
             }
             UiDialog.open(this);
             this._media.set(~~media.mediaID, media);
-            var listItem = this._listItems.get(~~media.mediaID);
-            var p = elByClass('mediaTitle', listItem)[0];
+            const listItem = this._listItems.get(~~media.mediaID);
+            const p = listItem.querySelector(".mediaTitle");
             if (media.isMultilingual) {
-                if (media.title && media.title[LANGUAGE_ID]) {
-                    p.textContent = media.title[LANGUAGE_ID];
+                if (media.title && media.title[window.LANGUAGE_ID]) {
+                    p.textContent = media.title[window.LANGUAGE_ID];
                 }
                 else {
                     p.textContent = media.filename;
@@ -252,113 +214,97 @@ define([
                     p.textContent = media.filename;
                 }
             }
-            var thumbnail = elByClass('mediaThumbnail', listItem)[0];
+            const thumbnail = listItem.querySelector(".mediaThumbnail");
             thumbnail.innerHTML = media.elementTag;
             // Bust browser cache by adding additional parameter.
-            var imgs = elByTag('img', thumbnail);
-            if (imgs.length) {
-                imgs[0].src += '&refresh=' + Date.now();
+            const img = thumbnail.querySelector("img");
+            if (img) {
+                img.src += `&refresh=${Date.now()}`;
             }
-        },
+        }
         /**
          * Initializes the dialog pagination.
-         *
-         * @param      {integer}       pageCount
-         * @param      {integer}       pageNo
          */
-        _initPagination: function (pageCount, pageNo) {
+        _initPagination(pageCount, pageNo) {
             if (pageNo === undefined)
                 pageNo = 1;
             if (pageCount > 1) {
-                var newPagination = elCreate('div');
-                newPagination.className = 'paginationBottom jsPagination';
-                DomUtil.replaceElement(elBySel('.jsPagination', UiDialog.getDialog(this).content), newPagination);
-                this._pagination = new UiPagination(newPagination, {
+                const newPagination = document.createElement("div");
+                newPagination.className = "paginationBottom jsPagination";
+                DomUtil.replaceElement(UiDialog.getDialog(this).content.querySelector(".jsPagination"), newPagination);
+                this._pagination = new Pagination_1.default(newPagination, {
                     activePage: pageNo,
                     callbackSwitch: this._search.search.bind(this._search),
-                    maxPage: pageCount
+                    maxPage: pageCount,
                 });
             }
             else if (this._pagination) {
-                elHide(this._pagination.getElement());
+                this._pagination.getElement().style.display = "none";
             }
-        },
+        }
         /**
          * Removes all media clipboard checkboxes.
          */
-        _removeClipboardCheckboxes: function () {
-            var checkboxes = elByClass('mediaCheckbox', this._mediaManagerMediaList);
-            while (checkboxes.length) {
-                elRemove(checkboxes[0]);
-            }
-        },
+        _removeClipboardCheckboxes() {
+            this._mediaManagerMediaList.querySelectorAll(".mediaCheckbox").forEach((el) => el.remove());
+        }
         /**
          * Opens the media editor after uploading a single file.
          *
-         * @param      {object}        data    upload event data
-         * @since      5.2
+         * @since 5.2
          */
-        _openEditorAfterUpload: function (data) {
+        _openEditorAfterUpload(data) {
             if (data.upload === this._upload && !data.isMultiFileUpload && !this._upload.hasPendingUploads()) {
-                var keys = Object.keys(data.media);
+                const keys = Object.keys(data.media);
                 if (keys.length) {
                     UiDialog.close(this);
                     this._mediaEditor.edit(this._media.get(~~data.media[keys[0]].mediaID));
                 }
             }
-        },
+        }
         /**
          * Sets the displayed media (after a search).
-         *
-         * @param      {Dictionary}    media           media to be set as active
          */
-        _setMedia: function (media) {
-            if (Core.isPlainObject(media)) {
-                this._media = Dictionary.fromObject(media);
-            }
-            else {
-                this._media = media;
-            }
-            var info = DomTraverse.nextByClass(this._mediaManagerMediaList, 'info');
+        _setMedia(media) {
+            this._media = new Map();
+            Object.entries(media).forEach(([mediaId, media]) => {
+                this._media.set(~~mediaId, media);
+            });
+            let info = DomTraverse.nextByClass(this._mediaManagerMediaList, "info");
             if (this._media.size) {
                 if (info) {
-                    elHide(info);
+                    info.style.display = "none";
                 }
             }
             else {
                 if (info === null) {
-                    info = elCreate('p');
-                    info.className = 'info';
-                    info.textContent = Language.get('wcf.media.search.noResults');
+                    info = document.createElement("p");
+                    info.className = "info";
+                    info.textContent = Language.get("wcf.media.search.noResults");
                 }
-                elShow(info);
+                info.style.display = "block";
                 DomUtil.insertAfter(info, this._mediaManagerMediaList);
             }
-            var mediaListItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
-            for (var i = 0, length = mediaListItems.length; i < length; i++) {
-                var listItem = mediaListItems[i];
-                if (!this._media.has(elData(listItem, 'object-id'))) {
-                    elHide(listItem);
+            DomTraverse.childrenByTag(this._mediaManagerMediaList, "LI").forEach((listItem) => {
+                if (!this._media.has(~~listItem.dataset.objectId)) {
+                    listItem.style.display = "none";
                 }
                 else {
-                    elShow(listItem);
+                    listItem.style.display = "block";
                 }
-            }
+            });
             DomChangeListener.trigger();
-            if (Permission.get('admin.content.cms.canManageMedia') || this._forceClipboard) {
+            if (Permission.get("admin.content.cms.canManageMedia") || this._forceClipboard) {
                 Clipboard.reload();
             }
             else {
                 this._removeClipboardCheckboxes();
             }
-        },
+        }
         /**
          * Adds a media file to the manager.
-         *
-         * @param      {object}        media           data of the media file
-         * @param      {Element}       listItem        list item representing the file
          */
-        addMedia: function (media, listItem) {
+        addMedia(media, listItem) {
             if (!media.languageID)
                 media.isMultilingual = 1;
             this._media.set(~~media.mediaID, media);
@@ -366,67 +312,54 @@ define([
             if (this._listItems.size === 1) {
                 this._search.showSearch();
             }
-        },
+        }
         /**
          * Is called after the media files with the given ids have been deleted via clipboard.
-         *
-         * @param      {int[]}         mediaIds        ids of deleted media files
          */
-        clipboardDeleteMedia: function (mediaIds) {
-            for (var i = 0, length = mediaIds.length; i < length; i++) {
-                this.removeMedia(~~mediaIds[i], true);
-            }
+        clipboardDeleteMedia(mediaIds) {
+            mediaIds.forEach((mediaId) => {
+                this.removeMedia(~~mediaId);
+            });
             UiNotification.show();
-        },
+        }
         /**
          * Returns the id of the currently selected category or `0` if no category is selected.
-         *
-         * @return     {integer}
          */
-        getCategoryId: function () {
+        getCategoryId() {
             if (this._mediaCategorySelect) {
-                return this._mediaCategorySelect.value;
+                return ~~this._mediaCategorySelect.value;
             }
             return 0;
-        },
+        }
         /**
          * Returns the media manager dialog element.
-         *
-         * @return     {Element}       media manager dialog
          */
-        getDialog: function () {
+        getDialog() {
             return UiDialog.getDialog(this).dialog;
-        },
+        }
         /**
          * Returns the mode of the media manager.
-         *
-         * @return     {string}
          */
-        getMode: function () {
-            return '';
-        },
+        getMode() {
+            return "";
+        }
         /**
          * Returns the media manager option with the given name.
-         *
-         * @param      {string}        name            option name
-         * @return     {mixed}         option value or null
          */
-        getOption: function (name) {
+        getOption(name) {
             if (this._options[name]) {
                 return this._options[name];
             }
             return null;
-        },
+        }
         /**
          * Removes a media file.
-         *
-         * @param      {int}                   mediaId         id of the removed media file
          */
-        removeMedia: function (mediaId) {
+        removeMedia(mediaId) {
             if (this._listItems.has(mediaId)) {
                 // remove list item
                 try {
-                    elRemove(this._listItems.get(mediaId));
+                    this._listItems.get(mediaId).remove();
                 }
                 catch (e) {
                     // ignore errors if item has already been removed like by WCF.Action.Delete
@@ -434,88 +367,82 @@ define([
                 this._listItems.delete(mediaId);
                 this._media.delete(mediaId);
             }
-        },
+        }
         /**
          * Changes the displayed media to the previously displayed media.
          */
-        resetMedia: function () {
+        resetMedia() {
             // calling WoltLabSuite/Core/Media/Manager/Search.search() reloads the first page of the dialog
             this._search.search();
-        },
+        }
         /**
          * Sets the media files currently displayed.
-         *
-         * @param      {object}        media           media data
-         * @param      {string}        template
-         * @param      {object}        additionalData
          */
-        setMedia: function (media, template, additionalData) {
-            var hasMedia = false;
-            for (var mediaId in media) {
-                if (objOwns(media, mediaId)) {
-                    hasMedia = true;
-                }
-            }
-            var newListItems = [];
+        setMedia(media, template, additionalData) {
+            const hasMedia = Object.entries(media).length > 0;
             if (hasMedia) {
-                var ul = elCreate('ul');
+                const ul = document.createElement("ul");
                 ul.innerHTML = template;
-                var listItems = DomTraverse.childrenByTag(ul, 'LI');
-                for (var i = 0, length = listItems.length; i < length; i++) {
-                    var listItem = listItems[i];
-                    if (!this._listItems.has(~~elData(listItem, 'object-id'))) {
-                        this._listItems.set(elData(listItem, 'object-id'), listItem);
+                DomTraverse.childrenByTag(ul, "LI").forEach((listItem) => {
+                    if (!this._listItems.has(~~listItem.dataset.objectId)) {
+                        this._listItems.set(~~listItem.dataset.objectId, listItem);
                         this._mediaManagerMediaList.appendChild(listItem);
                     }
-                }
+                });
             }
             this._initPagination(additionalData.pageCount, additionalData.pageNo);
             this._setMedia(media);
-        },
+        }
         /**
          * Sets up a new media element.
-         *
-         * @param      {object}        media           data of the media file
-         * @param      {HTMLElement}   mediaElement    element representing the media file
          */
-        setupMediaElement: function (media, mediaElement) {
-            var mediaInformation = DomTraverse.childByClass(mediaElement, 'mediaInformation');
-            var buttonGroupNavigation = elCreate('nav');
-            buttonGroupNavigation.className = 'jsMobileNavigation buttonGroupNavigation';
+        setupMediaElement(media, mediaElement) {
+            const mediaInformation = DomTraverse.childByClass(mediaElement, "mediaInformation");
+            const buttonGroupNavigation = document.createElement("nav");
+            buttonGroupNavigation.className = "jsMobileNavigation buttonGroupNavigation";
             mediaInformation.parentNode.appendChild(buttonGroupNavigation);
-            var buttons = elCreate('ul');
-            buttons.className = 'buttonList iconList';
+            const buttons = document.createElement("ul");
+            buttons.className = "buttonList iconList";
             buttonGroupNavigation.appendChild(buttons);
-            var listItem = elCreate('li');
-            listItem.className = 'mediaCheckbox';
+            const listItem = document.createElement("li");
+            listItem.className = "mediaCheckbox";
             buttons.appendChild(listItem);
-            var a = elCreate('a');
+            const a = document.createElement("a");
             listItem.appendChild(a);
-            var label = elCreate('label');
+            const label = document.createElement("label");
             a.appendChild(label);
-            var checkbox = elCreate('input');
-            checkbox.className = 'jsClipboardItem';
-            elAttr(checkbox, 'type', 'checkbox');
-            elData(checkbox, 'object-id', media.mediaID);
+            const checkbox = document.createElement("input");
+            checkbox.className = "jsClipboardItem";
+            checkbox.type = "checkbox";
+            checkbox.dataset.objectId = media.mediaID;
             label.appendChild(checkbox);
-            if (Permission.get('admin.content.cms.canManageMedia')) {
-                listItem = elCreate('li');
-                listItem.className = 'jsMediaEditButton';
-                elData(listItem, 'object-id', media.mediaID);
-                buttons.appendChild(listItem);
-                listItem.innerHTML = '<a><span class="icon icon16 fa-pencil jsTooltip" title="' + Language.get('wcf.global.button.edit') + '"></span> <span class="invisible">' + Language.get('wcf.global.button.edit') + '</span></a>';
-                listItem = elCreate('li');
-                listItem.className = 'jsDeleteButton';
-                elData(listItem, 'object-id', media.mediaID);
+            if (Permission.get("admin.content.cms.canManageMedia")) {
+                const editButton = document.createElement("li");
+                editButton.className = "jsMediaEditButton";
+                editButton.dataset.objectId = media.mediaID;
+                buttons.appendChild(editButton);
+                editButton.innerHTML = `
+        <a>
+          <span class="icon icon16 fa-pencil jsTooltip" title="${Language.get("wcf.global.button.edit")}"></span>
+          <span class="invisible">${Language.get("wcf.global.button.edit")}</span>
+        </a>`;
+                const deleteButton = document.createElement("li");
+                deleteButton.className = "jsDeleteButton";
+                deleteButton.dataset.objectId = media.mediaID;
                 // use temporary title to not unescape html in filename
-                var uuid = Core.getUuid();
-                elData(listItem, 'confirm-message-html', StringUtil.unescapeHTML(Language.get('wcf.media.delete.confirmMessage', {
-                    title: uuid
-                })).replace(uuid, StringUtil.escapeHTML(media.filename)));
-                buttons.appendChild(listItem);
-                listItem.innerHTML = '<a><span class="icon icon16 fa-times jsTooltip" title="' + Language.get('wcf.global.button.delete') + '"></span> <span class="invisible">' + Language.get('wcf.global.button.delete') + '</span></a>';
+                const uuid = Core.getUuid();
+                deleteButton.dataset.confirmMessageHtml = StringUtil.unescapeHTML(Language.get("wcf.media.delete.confirmMessage", {
+                    title: uuid,
+                })).replace(uuid, StringUtil.escapeHTML(media.filename));
+                buttons.appendChild(deleteButton);
+                deleteButton.innerHTML = `
+        <a>
+          <span class="icon icon16 fa-times jsTooltip" title="${Language.get("wcf.global.button.delete")}"></span>
+          <span class="invisible">${Language.get("wcf.global.button.delete")}</span>
+        </a>`;
             }
         }
-    };
-    return MediaManagerBase;
+    }
+    Core.enableLegacyInheritance(MediaManager);
+    return MediaManager;
 });
diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Media/Manager/Base.js b/wcfsetup/install/files/ts/WoltLabSuite/Core/Media/Manager/Base.js
deleted file mode 100644 (file)
index 8146ca0..0000000
+++ /dev/null
@@ -1,619 +0,0 @@
-/**
- * Provides the media manager dialog.
- * 
- * @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/Base
- */
-define(
-       [
-               'Core',                     'Dictionary',               'Dom/ChangeListener',              'Dom/Traverse',
-               'Dom/Util',                 'EventHandler',             'Language',                        'List',
-               'Permission',               'Ui/Dialog',                'Ui/Notification',                 'WoltLabSuite/Core/Controller/Clipboard',
-               'WoltLabSuite/Core/Media/Editor', 'WoltLabSuite/Core/Media/Upload', 'WoltLabSuite/Core/Media/Manager/Search', 'StringUtil',
-               'WoltLabSuite/Core/Ui/Pagination',
-               'WoltLabSuite/Core/Media/Clipboard'
-       ],
-       function(
-               Core,                        Dictionary,                 DomChangeListener,                 DomTraverse,
-               DomUtil,                     EventHandler,               Language,                          List,
-               Permission,                  UiDialog,                   UiNotification,                    Clipboard,
-               MediaEditor,                 MediaUpload,                MediaManagerSearch,                StringUtil,
-               UiPagination,
-               MediaClipboard
-       )
-{
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       _addButtonEventListeners: function() {},
-                       _click: function() {},
-                       _dialogClose: function() {},
-                       _dialogInit: function() {},
-                       _dialogSetup: function() {},
-                       _dialogShow: function() {},
-                       _editMedia: function() {},
-                       _editorClose: function() {},
-                       _editorSuccess: function() {},
-                       _removeClipboardCheckboxes: function() {},
-                       _setMedia: function() {},
-                       addMedia: function() {},
-                       clipboardDeleteMedia: function() {},
-                       getDialog: function() {},
-                       getMode: function() {},
-                       getOption: function() {},
-                       removeMedia: function() {},
-                       resetMedia: function() {},
-                       setMedia: function() {},
-                       setupMediaElement: function() {}
-               };
-               return Fake;
-       }
-       
-       var _mediaManagerCounter = 0;
-       
-       /**
-        * @constructor
-        */
-       function MediaManagerBase(options) {
-               this._options = Core.extend({
-                       dialogTitle: Language.get('wcf.media.manager'),
-                       imagesOnly: false,
-                       minSearchLength: 3
-               }, options);
-               
-               this._id = 'mediaManager' + _mediaManagerCounter++;
-               this._listItems = new Dictionary();
-               this._media = new Dictionary();
-               this._mediaManagerMediaList = null;
-               this._search = null;
-               this._upload = null;
-               this._forceClipboard = false;
-               this._hadInitiallyMarkedItems = false;
-               this._pagination = null;
-               
-               if (Permission.get('admin.content.cms.canManageMedia')) {
-                       this._mediaEditor = new MediaEditor(this);
-               }
-               
-               DomChangeListener.add('WoltLabSuite/Core/Media/Manager', this._addButtonEventListeners.bind(this));
-               
-               EventHandler.add('com.woltlab.wcf.media.upload', 'success', this._openEditorAfterUpload.bind(this));
-       }
-       MediaManagerBase.prototype = {
-               /**
-                * Adds click event listeners to media buttons.
-                */
-               _addButtonEventListeners: function() {
-                       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];
-                               
-                               if (Permission.get('admin.content.cms.canManageMedia')) {
-                                       var editIcon = elByClass('jsMediaEditButton', listItem)[0];
-                                       if (editIcon) {
-                                               editIcon.classList.remove('jsMediaEditButton');
-                                               editIcon.addEventListener('click', this._editMedia.bind(this));
-                                       }
-                               }
-                       }
-               },
-               
-               /**
-                * Is called when a new category is selected.
-                */
-               _categoryChange: function() {
-                       this._search.search();
-               },
-               
-               /**
-                * Handles clicks on the media manager button.
-                * 
-                * @param       {object}        event   event object
-                */
-               _click: function(event) {
-                       event.preventDefault();
-                       
-                       UiDialog.open(this);
-               },
-               
-               /**
-                * Is called if the media manager dialog is closed.
-                */
-               _dialogClose: function() {
-                       // only show media clipboard if editor is open
-                       if (Permission.get('admin.content.cms.canManageMedia') || this._forceClipboard) {
-                               Clipboard.hideEditor('com.woltlab.wcf.media');
-                       }
-               },
-               
-               /**
-                * Initializes the dialog when first loaded.
-                *
-                * @param       {string}        content         dialog content
-                * @param       {object}        data            AJAX request's response data
-                */
-               _dialogInit: function(content, data) {
-                       // store media data locally
-                       var media = data.returnValues.media || { };
-                       for (var mediaId in media) {
-                               if (objOwns(media, mediaId)) {
-                                       this._media.set(~~mediaId, media[mediaId]);
-                               }
-                       }
-                       
-                       this._initPagination(~~data.returnValues.pageCount);
-                       
-                       this._hadInitiallyMarkedItems = data.returnValues.hasMarkedItems;
-               },
-               
-               /**
-                * Returns all data to setup the media manager dialog.
-                * 
-                * @return      {object}        dialog setup data
-                */
-               _dialogSetup: function() {
-                       return {
-                               id: this._id,
-                               options: {
-                                       onClose: this._dialogClose.bind(this),
-                                       onShow: this._dialogShow.bind(this),
-                                       title: this._options.dialogTitle
-                               },
-                               source: {
-                                       after: this._dialogInit.bind(this),
-                                       data: {
-                                               actionName: 'getManagementDialog',
-                                               className: 'wcf\\data\\media\\MediaAction',
-                                               parameters: {
-                                                       mode: this.getMode(),
-                                                       imagesOnly: this._options.imagesOnly
-                                               }
-                                       }
-                               }
-                       };
-               },
-               
-               /**
-                * Is called if the media manager dialog is shown.
-                */
-               _dialogShow: function() {
-                       if (!this._mediaManagerMediaList) {
-                               var dialog = this.getDialog();
-                               
-                               this._mediaManagerMediaList = elByClass('mediaManagerMediaList', dialog)[0];
-                               
-                               this._mediaCategorySelect = elBySel('.mediaManagerCategoryList > select', dialog);
-                               if (this._mediaCategorySelect) {
-                                       this._mediaCategorySelect.addEventListener('change', this._categoryChange.bind(this));
-                               }
-                               
-                               // store list items locally
-                               var listItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
-                               for (var i = 0, length = listItems.length; i < length; i++) {
-                                       var listItem = listItems[i];
-                                       
-                                       this._listItems.set(~~elData(listItem, 'object-id'), listItem);
-                               }
-                               
-                               if (Permission.get('admin.content.cms.canManageMedia')) {
-                                       var uploadButton = elByClass('mediaManagerMediaUploadButton', UiDialog.getDialog(this).dialog)[0];
-                                       this._upload = new MediaUpload(DomUtil.identify(uploadButton), DomUtil.identify(this._mediaManagerMediaList), {
-                                               mediaManager: this
-                                       });
-                                       
-                                       var deleteAction = new WCF.Action.Delete('wcf\\data\\media\\MediaAction', '.mediaFile');
-                                       deleteAction._didTriggerEffect = function(element) {
-                                               this.removeMedia(elData(element[0], 'object-id'));
-                                       }.bind(this);
-                               }
-                               
-                               if (Permission.get('admin.content.cms.canManageMedia') || this._forceClipboard) {
-                                       MediaClipboard.init(
-                                               'menuManagerDialog-' + this.getMode(),
-                                               this._hadInitiallyMarkedItems ? true : false,
-                                               this
-                                       );
-                               }
-                               else {
-                                       this._removeClipboardCheckboxes();
-                               }
-                               
-                               this._search = new MediaManagerSearch(this);
-                               
-                               if (!listItems.length) {
-                                       this._search.hideSearch();
-                               }
-                       }
-                       
-                       // only show media clipboard if editor is open
-                       if (Permission.get('admin.content.cms.canManageMedia') || this._forceClipboard) {
-                               Clipboard.showEditor('com.woltlab.wcf.media');
-                       }
-               },
-               
-               /**
-                * Opens the media editor for a media file.
-                * 
-                * @param       {Event}         event           event object for clicks on edit icons
-                */
-               _editMedia: function(event) {
-                       if (!Permission.get('admin.content.cms.canManageMedia')) {
-                               throw new Error("You are not allowed to edit media files.");
-                       }
-                       
-                       UiDialog.close(this);
-                       
-                       this._mediaEditor.edit(this._media.get(~~elData(event.currentTarget, 'object-id')));
-               },
-               
-               /**
-                * Re-opens the manager dialog after closing the editor dialog.
-                */
-               _editorClose: function() {
-                       UiDialog.open(this);
-               },
-               
-               /**
-                * Re-opens the manager dialog and updates the media data after
-                * successfully editing a media file.
-                * 
-                * @param       {object}        media           updated media file data
-                * @param       {integer}       oldCategoryId   old category id
-                */
-               _editorSuccess: function(media, oldCategoryId) {
-                       // if the category changed of media changed and category
-                       // is selected, check if media list needs to be refreshed
-                       if (this._mediaCategorySelect) {
-                               var selectedCategoryId = ~~this._mediaCategorySelect.value;
-                               
-                               if (selectedCategoryId) {
-                                       var newCategoryId = ~~media.categoryID;
-                                       
-                                       if (oldCategoryId != newCategoryId && (oldCategoryId == selectedCategoryId || newCategoryId == selectedCategoryId)) {
-                                               this._search.search();
-                                       }
-                               }
-                       }
-                       
-                       UiDialog.open(this);
-                       
-                       this._media.set(~~media.mediaID, media);
-                       
-                       var listItem = this._listItems.get(~~media.mediaID);
-                       var p = elByClass('mediaTitle', listItem)[0];
-                       if (media.isMultilingual) {
-                               if (media.title && media.title[LANGUAGE_ID]) {
-                                       p.textContent = media.title[LANGUAGE_ID];
-                               }
-                               else {
-                                       p.textContent = media.filename;
-                               }
-                       }
-                       else {
-                               if (media.title && media.title[media.languageID]) {
-                                       p.textContent = media.title[media.languageID];
-                               }
-                               else {
-                                       p.textContent = media.filename;
-                               }
-                       }
-                       
-                       var thumbnail = elByClass('mediaThumbnail', listItem)[0];
-                       thumbnail.innerHTML = media.elementTag;
-                       // Bust browser cache by adding additional parameter.
-                       var imgs = elByTag('img', thumbnail);
-                       if (imgs.length) {
-                               imgs[0].src += '&refresh=' + Date.now();
-                       }
-               },
-               
-               /**
-                * Initializes the dialog pagination.
-                *
-                * @param       {integer}       pageCount
-                * @param       {integer}       pageNo
-                */
-               _initPagination: function(pageCount, pageNo) {
-                       if (pageNo === undefined) pageNo = 1;
-                       
-                       if (pageCount > 1) {
-                               var newPagination = elCreate('div');
-                               newPagination.className = 'paginationBottom jsPagination';
-                               DomUtil.replaceElement(elBySel('.jsPagination', UiDialog.getDialog(this).content), newPagination);
-                               
-                               this._pagination = new UiPagination(newPagination, {
-                                       activePage: pageNo,
-                                       callbackSwitch: this._search.search.bind(this._search),
-                                       maxPage: pageCount
-                               });
-                       }
-                       else if (this._pagination) {
-                               elHide(this._pagination.getElement());
-                       }
-               },
-               
-               /**
-                * Removes all media clipboard checkboxes.
-                */
-               _removeClipboardCheckboxes: function() {
-                       var checkboxes = elByClass('mediaCheckbox', this._mediaManagerMediaList);
-                       while (checkboxes.length) {
-                               elRemove(checkboxes[0]);
-                       }
-               },
-               
-               /**
-                * Opens the media editor after uploading a single file.
-                * 
-                * @param       {object}        data    upload event data
-                * @since       5.2
-                */
-               _openEditorAfterUpload: function(data) {
-                       if (data.upload === this._upload && !data.isMultiFileUpload && !this._upload.hasPendingUploads()) {
-                               var keys = Object.keys(data.media);
-                               
-                               if (keys.length) {
-                                       UiDialog.close(this);
-                                       
-                                       this._mediaEditor.edit(this._media.get(~~data.media[keys[0]].mediaID));
-                               }
-                       }
-               },
-               
-               /**
-                * Sets the displayed media (after a search).
-                * 
-                * @param       {Dictionary}    media           media to be set as active
-                */
-               _setMedia: function(media) {
-                       if (Core.isPlainObject(media)) {
-                               this._media = Dictionary.fromObject(media);
-                       }
-                       else {
-                               this._media = media;
-                       }
-                       
-                       var info = DomTraverse.nextByClass(this._mediaManagerMediaList, 'info');
-                       
-                       if (this._media.size) {
-                               if (info) {
-                                       elHide(info);
-                               }
-                       }
-                       else {
-                               if (info === null) {
-                                       info = elCreate('p');
-                                       info.className = 'info';
-                                       info.textContent = Language.get('wcf.media.search.noResults');
-                               }
-                               
-                               elShow(info);
-                               DomUtil.insertAfter(info, this._mediaManagerMediaList);
-                       }
-                       
-                       var mediaListItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
-                       for (var i = 0, length = mediaListItems.length; i < length; i++) {
-                               var listItem = mediaListItems[i];
-                               
-                               if (!this._media.has(elData(listItem, 'object-id'))) {
-                                       elHide(listItem);
-                               }
-                               else {
-                                       elShow(listItem);
-                               }
-                       }
-                       
-                       DomChangeListener.trigger();
-                       
-                       if (Permission.get('admin.content.cms.canManageMedia') || this._forceClipboard) {
-                               Clipboard.reload();
-                       }
-                       else {
-                               this._removeClipboardCheckboxes();
-                       }
-               },
-               
-               /**
-                * Adds a media file to the manager.
-                * 
-                * @param       {object}        media           data of the media file
-                * @param       {Element}       listItem        list item representing the file
-                */
-               addMedia: function(media, listItem) {
-                       if (!media.languageID) media.isMultilingual = 1;
-                       
-                       this._media.set(~~media.mediaID, media);
-                       this._listItems.set(~~media.mediaID, listItem);
-                       
-                       if (this._listItems.size === 1) {
-                               this._search.showSearch();
-                       }
-               },
-               
-               /**
-                * Is called after the media files with the given ids have been deleted via clipboard.
-                * 
-                * @param       {int[]}         mediaIds        ids of deleted media files
-                */
-               clipboardDeleteMedia: function(mediaIds) {
-                       for (var i = 0, length = mediaIds.length; i < length; i++) {
-                               this.removeMedia(~~mediaIds[i], true);
-                       }
-                       
-                       UiNotification.show();
-               },
-               
-               /**
-                * Returns the id of the currently selected category or `0` if no category is selected.
-                * 
-                * @return      {integer}
-                */
-               getCategoryId: function() {
-                       if (this._mediaCategorySelect) {
-                               return this._mediaCategorySelect.value;
-                       }
-                       
-                       return 0;
-               },
-               
-               /**
-                * Returns the media manager dialog element.
-                * 
-                * @return      {Element}       media manager dialog
-                */
-               getDialog: function() {
-                       return UiDialog.getDialog(this).dialog;
-               },
-               
-               /**
-                * Returns the mode of the media manager.
-                *
-                * @return      {string}
-                */
-               getMode: function() {
-                       return '';
-               },
-               
-               /**
-                * Returns the media manager option with the given name.
-                * 
-                * @param       {string}        name            option name
-                * @return      {mixed}         option value or null
-                */
-               getOption: function(name) {
-                       if (this._options[name]) {
-                               return this._options[name];
-                       }
-                       
-                       return null;
-               },
-               
-               /**
-                * Removes a media file.
-                *
-                * @param       {int}                   mediaId         id of the removed media file
-                */
-               removeMedia: function(mediaId) {
-                       if (this._listItems.has(mediaId)) {
-                               // remove list item
-                               try {
-                                       elRemove(this._listItems.get(mediaId));
-                               }
-                               catch (e) {
-                                       // ignore errors if item has already been removed like by WCF.Action.Delete
-                               }
-                               
-                               this._listItems.delete(mediaId);
-                               this._media.delete(mediaId);
-                       }
-               },
-               
-               /**
-                * Changes the displayed media to the previously displayed media.
-                */
-               resetMedia: function() {
-                       // calling WoltLabSuite/Core/Media/Manager/Search.search() reloads the first page of the dialog
-                       this._search.search();
-               },
-               
-               /**
-                * Sets the media files currently displayed.
-                * 
-                * @param       {object}        media           media data
-                * @param       {string}        template        
-                * @param       {object}        additionalData
-                */
-               setMedia: function(media, template, additionalData) {
-                       var hasMedia = false;
-                       for (var mediaId in media) {
-                               if (objOwns(media, mediaId)) {
-                                       hasMedia = true;
-                               }
-                       }
-                       
-                       var newListItems = [];
-                       if (hasMedia) {
-                               var ul = elCreate('ul');
-                               ul.innerHTML = template;
-                               
-                               var listItems = DomTraverse.childrenByTag(ul, 'LI');
-                               for (var i = 0, length = listItems.length; i < length; i++) {
-                                       var listItem = listItems[i];
-                                       if (!this._listItems.has(~~elData(listItem, 'object-id'))) {
-                                               this._listItems.set(elData(listItem, 'object-id'), listItem);
-                                               
-                                               this._mediaManagerMediaList.appendChild(listItem);
-                                       }
-                               }
-                       }
-                       
-                       this._initPagination(additionalData.pageCount, additionalData.pageNo);
-                       
-                       this._setMedia(media);
-               },
-               
-               /**
-                * Sets up a new media element.
-                * 
-                * @param       {object}        media           data of the media file
-                * @param       {HTMLElement}   mediaElement    element representing the media file
-                */
-               setupMediaElement: function(media, mediaElement) {
-                       var mediaInformation = DomTraverse.childByClass(mediaElement, 'mediaInformation');
-                       
-                       var buttonGroupNavigation = elCreate('nav');
-                       buttonGroupNavigation.className = 'jsMobileNavigation buttonGroupNavigation';
-                       mediaInformation.parentNode.appendChild(buttonGroupNavigation);
-                       
-                       var buttons = elCreate('ul');
-                       buttons.className = 'buttonList iconList';
-                       buttonGroupNavigation.appendChild(buttons);
-                       
-                       var listItem = elCreate('li');
-                       listItem.className = 'mediaCheckbox';
-                       buttons.appendChild(listItem);
-                       
-                       var a = elCreate('a');
-                       listItem.appendChild(a);
-                       
-                       var label = elCreate('label');
-                       a.appendChild(label);
-                       
-                       var checkbox = elCreate('input');
-                       checkbox.className = 'jsClipboardItem';
-                       elAttr(checkbox, 'type', 'checkbox');
-                       elData(checkbox, 'object-id', media.mediaID);
-                       label.appendChild(checkbox);
-                       
-                       if (Permission.get('admin.content.cms.canManageMedia')) {
-                               listItem = elCreate('li');
-                               listItem.className = 'jsMediaEditButton';
-                               elData(listItem, 'object-id', media.mediaID);
-                               buttons.appendChild(listItem);
-                               
-                               listItem.innerHTML = '<a><span class="icon icon16 fa-pencil jsTooltip" title="' + Language.get('wcf.global.button.edit') + '"></span> <span class="invisible">' + Language.get('wcf.global.button.edit') + '</span></a>';
-                               
-                               listItem = elCreate('li');
-                               listItem.className = 'jsDeleteButton';
-                               elData(listItem, 'object-id', media.mediaID);
-                               
-                               // use temporary title to not unescape html in filename
-                               var uuid = Core.getUuid();
-                               elData(listItem, 'confirm-message-html', StringUtil.unescapeHTML(Language.get('wcf.media.delete.confirmMessage', {
-                                       title: uuid
-                               })).replace(uuid, StringUtil.escapeHTML(media.filename)));
-                               buttons.appendChild(listItem);
-                               
-                               listItem.innerHTML = '<a><span class="icon icon16 fa-times jsTooltip" title="' + Language.get('wcf.global.button.delete') + '"></span> <span class="invisible">' + Language.get('wcf.global.button.delete') + '</span></a>';
-                       }
-               }
-       };
-       
-       return MediaManagerBase;
-});
diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Media/Manager/Base.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/Media/Manager/Base.ts
new file mode 100644 (file)
index 0000000..9ea0747
--- /dev/null
@@ -0,0 +1,550 @@
+/**
+ * Provides the media manager dialog.
+ *
+ * @author  Matthias Schmidt
+ * @copyright 2001-2020 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  WoltLabSuite/Core/Media/Manager/Base
+ */
+
+import * as Core from "../../Core";
+import { Media, MediaManagerOptions, MediaEditorCallbackObject, MediaUploadSuccessEventData } from "../Data";
+import * as Language from "../../Language";
+import * as Permission from "../../Permission";
+import * as DomChangeListener from "../../Dom/Change/Listener";
+import * as EventHandler from "../../Event/Handler";
+import * as DomTraverse from "../../Dom/Traverse";
+import * as DomUtil from "../../Dom/Util";
+import * as UiDialog from "../../Ui/Dialog";
+import { DialogCallbackSetup, DialogCallbackObject } from "../../Ui/Dialog/Data";
+import * as Clipboard from "../../Controller/Clipboard";
+import UiPagination from "../../Ui/Pagination";
+import * as UiNotification from "../../Ui/Notification";
+import * as StringUtil from "../../StringUtil";
+import MediaManagerSearch from "./Search";
+import MediaUpload from "../Upload";
+import MediaEditor from "../Editor";
+import * as MediaClipboard from "../Clipboard";
+
+let mediaManagerCounter = 0;
+
+type DialogInitAjaxResponseData = {
+  returnValues: {
+    hasMarkedItems: number;
+    media: object;
+    pageCount: number;
+  };
+};
+
+type SetMediaAdditionalData = {
+  pageCount: number;
+  pageNo: number;
+};
+
+abstract class MediaManager<TOptions extends MediaManagerOptions = MediaManagerOptions>
+  implements DialogCallbackObject, MediaEditorCallbackObject {
+  protected _forceClipboard = false;
+  protected _hadInitiallyMarkedItems = false;
+  protected readonly _id = `mediaManager${mediaManagerCounter++}`;
+  protected readonly _listItems = new Map<number, HTMLLIElement>();
+  protected _media = new Map<number, Media>();
+  protected _mediaCategorySelect: HTMLSelectElement | null;
+  protected readonly _mediaEditor: MediaEditor | null = null;
+  protected _mediaManagerMediaList: HTMLElement | null = null;
+  protected _pagination: UiPagination | null = null;
+  protected _search: MediaManagerSearch | null = null;
+  protected _upload: any = null;
+  protected readonly _options: TOptions;
+
+  constructor(options: Partial<TOptions>) {
+    this._options = Core.extend(
+      {
+        dialogTitle: Language.get("wcf.media.manager"),
+        imagesOnly: false,
+        minSearchLength: 3,
+      },
+      options,
+    ) as TOptions;
+
+    if (Permission.get("admin.content.cms.canManageMedia")) {
+      this._mediaEditor = new MediaEditor(this);
+    }
+
+    DomChangeListener.add("WoltLabSuite/Core/Media/Manager", () => this._addButtonEventListeners());
+
+    EventHandler.add("com.woltlab.wcf.media.upload", "success", (data: MediaUploadSuccessEventData) =>
+      this._openEditorAfterUpload(data),
+    );
+  }
+
+  /**
+   * Adds click event listeners to media buttons.
+   */
+  protected _addButtonEventListeners(): void {
+    if (!this._mediaManagerMediaList || !Permission.get("admin.content.cms.canManageMedia")) return;
+
+    DomTraverse.childrenByTag(this._mediaManagerMediaList, "LI").forEach((listItem) => {
+      const editIcon = listItem.querySelector(".jsMediaEditButton");
+      if (editIcon) {
+        editIcon.classList.remove("jsMediaEditButton");
+        editIcon.addEventListener("click", (ev: MouseEvent) => this._editMedia(ev));
+      }
+    });
+  }
+
+  /**
+   * Is called when a new category is selected.
+   */
+  protected _categoryChange(): void {
+    this._search!.search();
+  }
+
+  /**
+   * Handles clicks on the media manager button.
+   */
+  protected _click(event: MouseEvent): void {
+    event.preventDefault();
+
+    UiDialog.open(this);
+  }
+
+  /**
+   * Is called if the media manager dialog is closed.
+   */
+  protected _dialogClose(): void {
+    // only show media clipboard if editor is open
+    if (Permission.get("admin.content.cms.canManageMedia") || this._forceClipboard) {
+      Clipboard.hideEditor("com.woltlab.wcf.media");
+    }
+  }
+
+  /**
+   * Initializes the dialog when first loaded.
+   */
+  protected _dialogInit(content: HTMLElement, data: DialogInitAjaxResponseData): void {
+    // store media data locally
+    Object.entries(data.returnValues.media || {}).forEach(([mediaId, media]) => {
+      this._media.set(~~mediaId, media);
+    });
+
+    this._initPagination(~~data.returnValues.pageCount);
+
+    this._hadInitiallyMarkedItems = data.returnValues.hasMarkedItems > 0;
+  }
+
+  /**
+   * Returns all data to setup the media manager dialog.
+   */
+  public _dialogSetup(): ReturnType<DialogCallbackSetup> {
+    return {
+      id: this._id,
+      options: {
+        onClose: () => this._dialogClose(),
+        onShow: () => this._dialogShow(),
+        title: this._options.dialogTitle,
+      },
+      source: {
+        after: (content: HTMLElement, data: DialogInitAjaxResponseData) => this._dialogInit(content, data),
+        data: {
+          actionName: "getManagementDialog",
+          className: "wcf\\data\\media\\MediaAction",
+          parameters: {
+            mode: this.getMode(),
+            imagesOnly: this._options.imagesOnly,
+          },
+        },
+      },
+    };
+  }
+
+  /**
+   * Is called if the media manager dialog is shown.
+   */
+  protected _dialogShow(): void {
+    if (!this._mediaManagerMediaList) {
+      const dialog = this.getDialog();
+
+      this._mediaManagerMediaList = dialog.querySelector(".mediaManagerMediaList");
+
+      this._mediaCategorySelect = dialog.querySelector(".mediaManagerCategoryList > select");
+      if (this._mediaCategorySelect) {
+        this._mediaCategorySelect.addEventListener("change", this._categoryChange.bind(this));
+      }
+
+      // store list items locally
+      const listItems = DomTraverse.childrenByTag(this._mediaManagerMediaList!, "LI");
+      listItems.forEach((listItem: HTMLLIElement) => {
+        this._listItems.set(~~listItem.dataset.objectId!, listItem);
+      });
+
+      if (Permission.get("admin.content.cms.canManageMedia")) {
+        const uploadButton = UiDialog.getDialog(this)!.dialog.querySelector(".mediaManagerMediaUploadButton")!;
+        this._upload = new MediaUpload(DomUtil.identify(uploadButton), DomUtil.identify(this._mediaManagerMediaList!), {
+          mediaManager: this,
+        });
+
+        // eslint-disable-next-line
+        //@ts-ignore
+        const deleteAction = new WCF.Action.Delete("wcf\\data\\media\\MediaAction", ".mediaFile");
+        deleteAction._didTriggerEffect = (element) => this.removeMedia(element[0].dataset.objectId);
+      }
+
+      if (Permission.get("admin.content.cms.canManageMedia") || this._forceClipboard) {
+        MediaClipboard.init("menuManagerDialog-" + this.getMode(), this._hadInitiallyMarkedItems ? true : false, this);
+      } else {
+        this._removeClipboardCheckboxes();
+      }
+
+      this._search = new MediaManagerSearch(this);
+
+      if (!listItems.length) {
+        this._search.hideSearch();
+      }
+    }
+
+    // only show media clipboard if editor is open
+    if (Permission.get("admin.content.cms.canManageMedia") || this._forceClipboard) {
+      Clipboard.showEditor();
+    }
+  }
+
+  /**
+   * Opens the media editor for a media file.
+   */
+  protected _editMedia(event: MouseEvent): void {
+    if (!Permission.get("admin.content.cms.canManageMedia")) {
+      throw new Error("You are not allowed to edit media files.");
+    }
+
+    UiDialog.close(this);
+
+    const target = event.currentTarget as HTMLElement;
+
+    this._mediaEditor!.edit(this._media.get(~~target.dataset.objectId!)!);
+  }
+
+  /**
+   * Re-opens the manager dialog after closing the editor dialog.
+   */
+  _editorClose(): void {
+    UiDialog.open(this);
+  }
+
+  /**
+   * Re-opens the manager dialog and updates the media data after successfully editing a media file.
+   */
+  _editorSuccess(media: Media, oldCategoryId?: number): void {
+    // if the category changed of media changed and category
+    // is selected, check if media list needs to be refreshed
+    if (this._mediaCategorySelect) {
+      const selectedCategoryId = ~~this._mediaCategorySelect.value;
+
+      if (selectedCategoryId) {
+        const newCategoryId = ~~media.categoryID;
+
+        if (
+          oldCategoryId != newCategoryId &&
+          (oldCategoryId == selectedCategoryId || newCategoryId == selectedCategoryId)
+        ) {
+          this._search!.search();
+        }
+      }
+    }
+
+    UiDialog.open(this);
+
+    this._media.set(~~media.mediaID, media);
+
+    const listItem = this._listItems.get(~~media.mediaID)!;
+    const p = listItem.querySelector(".mediaTitle")!;
+    if (media.isMultilingual) {
+      if (media.title && media.title[window.LANGUAGE_ID]) {
+        p.textContent = media.title[window.LANGUAGE_ID];
+      } else {
+        p.textContent = media.filename;
+      }
+    } else {
+      if (media.title && media.title[media.languageID!]) {
+        p.textContent = media.title[media.languageID!];
+      } else {
+        p.textContent = media.filename;
+      }
+    }
+
+    const thumbnail = listItem.querySelector(".mediaThumbnail")!;
+    thumbnail.innerHTML = media.elementTag;
+    // Bust browser cache by adding additional parameter.
+    const img = thumbnail.querySelector("img");
+    if (img) {
+      img.src += `&refresh=${Date.now()}`;
+    }
+  }
+
+  /**
+   * Initializes the dialog pagination.
+   */
+  protected _initPagination(pageCount: number, pageNo?: number): void {
+    if (pageNo === undefined) pageNo = 1;
+
+    if (pageCount > 1) {
+      const newPagination = document.createElement("div");
+      newPagination.className = "paginationBottom jsPagination";
+      DomUtil.replaceElement(
+        UiDialog.getDialog(this)!.content.querySelector(".jsPagination") as HTMLElement,
+        newPagination,
+      );
+
+      this._pagination = new UiPagination(newPagination, {
+        activePage: pageNo,
+        callbackSwitch: this._search!.search.bind(this._search),
+        maxPage: pageCount,
+      });
+    } else if (this._pagination) {
+      this._pagination.getElement().style.display = "none";
+    }
+  }
+
+  /**
+   * Removes all media clipboard checkboxes.
+   */
+  _removeClipboardCheckboxes(): void {
+    this._mediaManagerMediaList!.querySelectorAll(".mediaCheckbox").forEach((el) => el.remove());
+  }
+
+  /**
+   * Opens the media editor after uploading a single file.
+   *
+   * @since 5.2
+   */
+  _openEditorAfterUpload(data: MediaUploadSuccessEventData): void {
+    if (data.upload === this._upload && !data.isMultiFileUpload && !this._upload.hasPendingUploads()) {
+      const keys = Object.keys(data.media);
+
+      if (keys.length) {
+        UiDialog.close(this);
+
+        this._mediaEditor!.edit(this._media.get(~~data.media[keys[0]].mediaID)!);
+      }
+    }
+  }
+
+  /**
+   * Sets the displayed media (after a search).
+   */
+  _setMedia(media: object): void {
+    this._media = new Map<number, Media>();
+    Object.entries(media).forEach(([mediaId, media]) => {
+      this._media.set(~~mediaId, media);
+    });
+
+    let info = DomTraverse.nextByClass(this._mediaManagerMediaList!, "info") as HTMLElement;
+
+    if (this._media.size) {
+      if (info) {
+        info.style.display = "none";
+      }
+    } else {
+      if (info === null) {
+        info = document.createElement("p");
+        info.className = "info";
+        info.textContent = Language.get("wcf.media.search.noResults");
+      }
+
+      info.style.display = "block";
+      DomUtil.insertAfter(info, this._mediaManagerMediaList!);
+    }
+
+    DomTraverse.childrenByTag(this._mediaManagerMediaList!, "LI").forEach((listItem) => {
+      if (!this._media.has(~~listItem.dataset.objectId!)) {
+        listItem.style.display = "none";
+      } else {
+        listItem.style.display = "block";
+      }
+    });
+
+    DomChangeListener.trigger();
+
+    if (Permission.get("admin.content.cms.canManageMedia") || this._forceClipboard) {
+      Clipboard.reload();
+    } else {
+      this._removeClipboardCheckboxes();
+    }
+  }
+
+  /**
+   * Adds a media file to the manager.
+   */
+  public addMedia(media: Media, listItem: HTMLLIElement): void {
+    if (!media.languageID) media.isMultilingual = 1;
+
+    this._media.set(~~media.mediaID, media);
+    this._listItems.set(~~media.mediaID, listItem);
+
+    if (this._listItems.size === 1) {
+      this._search!.showSearch();
+    }
+  }
+
+  /**
+   * Is called after the media files with the given ids have been deleted via clipboard.
+   */
+  public clipboardDeleteMedia(mediaIds: number[]): void {
+    mediaIds.forEach((mediaId) => {
+      this.removeMedia(~~mediaId);
+    });
+
+    UiNotification.show();
+  }
+
+  /**
+   * Returns the id of the currently selected category or `0` if no category is selected.
+   */
+  public getCategoryId(): number {
+    if (this._mediaCategorySelect) {
+      return ~~this._mediaCategorySelect.value;
+    }
+
+    return 0;
+  }
+
+  /**
+   * Returns the media manager dialog element.
+   */
+  getDialog(): HTMLElement {
+    return UiDialog.getDialog(this)!.dialog;
+  }
+
+  /**
+   * Returns the mode of the media manager.
+   */
+  public getMode(): string {
+    return "";
+  }
+
+  /**
+   * Returns the media manager option with the given name.
+   */
+  public getOption(name: string): any {
+    if (this._options[name]) {
+      return this._options[name];
+    }
+
+    return null;
+  }
+
+  /**
+   * Removes a media file.
+   */
+  public removeMedia(mediaId: number): void {
+    if (this._listItems.has(mediaId)) {
+      // remove list item
+      try {
+        this._listItems.get(mediaId)!.remove();
+      } catch (e) {
+        // ignore errors if item has already been removed like by WCF.Action.Delete
+      }
+
+      this._listItems.delete(mediaId);
+      this._media.delete(mediaId);
+    }
+  }
+
+  /**
+   * Changes the displayed media to the previously displayed media.
+   */
+  public resetMedia(): void {
+    // calling WoltLabSuite/Core/Media/Manager/Search.search() reloads the first page of the dialog
+    this._search!.search();
+  }
+
+  /**
+   * Sets the media files currently displayed.
+   */
+  setMedia(media: object, template: string, additionalData: SetMediaAdditionalData): void {
+    const hasMedia = Object.entries(media).length > 0;
+
+    if (hasMedia) {
+      const ul = document.createElement("ul");
+      ul.innerHTML = template;
+
+      DomTraverse.childrenByTag(ul, "LI").forEach((listItem) => {
+        if (!this._listItems.has(~~listItem.dataset.objectId!)) {
+          this._listItems.set(~~listItem.dataset.objectId!, listItem);
+
+          this._mediaManagerMediaList!.appendChild(listItem);
+        }
+      });
+    }
+
+    this._initPagination(additionalData.pageCount, additionalData.pageNo);
+
+    this._setMedia(media);
+  }
+
+  /**
+   * Sets up a new media element.
+   */
+  public setupMediaElement(media: Media, mediaElement: HTMLElement): void {
+    const mediaInformation = DomTraverse.childByClass(mediaElement, "mediaInformation")!;
+
+    const buttonGroupNavigation = document.createElement("nav");
+    buttonGroupNavigation.className = "jsMobileNavigation buttonGroupNavigation";
+    mediaInformation.parentNode!.appendChild(buttonGroupNavigation);
+
+    const buttons = document.createElement("ul");
+    buttons.className = "buttonList iconList";
+    buttonGroupNavigation.appendChild(buttons);
+
+    const listItem = document.createElement("li");
+    listItem.className = "mediaCheckbox";
+    buttons.appendChild(listItem);
+
+    const a = document.createElement("a");
+    listItem.appendChild(a);
+
+    const label = document.createElement("label");
+    a.appendChild(label);
+
+    const checkbox = document.createElement("input");
+    checkbox.className = "jsClipboardItem";
+    checkbox.type = "checkbox";
+    checkbox.dataset.objectId = (media.mediaID as unknown) as string;
+    label.appendChild(checkbox);
+
+    if (Permission.get("admin.content.cms.canManageMedia")) {
+      const editButton = document.createElement("li");
+      editButton.className = "jsMediaEditButton";
+      editButton.dataset.objectId = (media.mediaID as unknown) as string;
+      buttons.appendChild(editButton);
+
+      editButton.innerHTML = `
+        <a>
+          <span class="icon icon16 fa-pencil jsTooltip" title="${Language.get("wcf.global.button.edit")}"></span>
+          <span class="invisible">${Language.get("wcf.global.button.edit")}</span>
+        </a>`;
+
+      const deleteButton = document.createElement("li");
+      deleteButton.className = "jsDeleteButton";
+      deleteButton.dataset.objectId = (media.mediaID as unknown) as string;
+
+      // use temporary title to not unescape html in filename
+      const uuid = Core.getUuid();
+      deleteButton.dataset.confirmMessageHtml = StringUtil.unescapeHTML(
+        Language.get("wcf.media.delete.confirmMessage", {
+          title: uuid,
+        }),
+      ).replace(uuid, StringUtil.escapeHTML(media.filename));
+      buttons.appendChild(deleteButton);
+
+      deleteButton.innerHTML = `
+        <a>
+          <span class="icon icon16 fa-times jsTooltip" title="${Language.get("wcf.global.button.delete")}"></span>
+          <span class="invisible">${Language.get("wcf.global.button.delete")}</span>
+        </a>`;
+    }
+  }
+}
+
+Core.enableLegacyInheritance(MediaManager);
+
+export = MediaManager;