Merge branch '5.3'
authorMatthias Schmidt <gravatronics@live.com>
Tue, 25 May 2021 06:13:26 +0000 (08:13 +0200)
committerMatthias Schmidt <gravatronics@live.com>
Tue, 25 May 2021 06:13:26 +0000 (08:13 +0200)
1  2 
ts/WoltLabSuite/Core/Media/Clipboard.ts
ts/WoltLabSuite/Core/Media/Manager/Base.ts
ts/WoltLabSuite/Core/Media/Manager/Editor.ts
wcfsetup/install/files/js/WoltLabSuite/Core/Media/Clipboard.js
wcfsetup/install/files/js/WoltLabSuite/Core/Media/Manager/Base.js
wcfsetup/install/files/js/WoltLabSuite/Core/Media/Manager/Editor.js

index bef7eb617b5fa8d01cdcfdbf1f8a10f04f7cdb7b,0000000000000000000000000000000000000000..11bf324931a4565729d1d9984d09839b47b3aa5f
mode 100644,000000..100644
--- /dev/null
@@@ -1,142 -1,0 +1,149 @@@
-   Clipboard.setup({
-     hasMarkedItems: hasMarkedItems,
-     pageClassName: pageClassName,
-   });
 +/**
 + * Initializes modules required for media clipboard.
 + *
 + * @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/Clipboard
 + * @woltlabExcludeBundle tiny
 + */
 +
 +import MediaManager from "./Manager/Base";
 +import MediaManagerEditor from "./Manager/Editor";
 +import * as Clipboard from "../Controller/Clipboard";
 +import * as UiNotification from "../Ui/Notification";
 +import * as UiDialog from "../Ui/Dialog";
 +import * as EventHandler from "../Event/Handler";
 +import * as Language from "../Language";
 +import * as Ajax from "../Ajax";
 +import { AjaxCallbackObject, AjaxCallbackSetup } from "../Ajax/Data";
 +import { DialogCallbackObject, DialogCallbackSetup } from "../Ui/Dialog/Data";
 +
 +let _mediaManager: MediaManager;
++const _didInit = false;
 +
 +class MediaClipboard implements AjaxCallbackObject, DialogCallbackObject {
 +  public _ajaxSetup(): ReturnType<AjaxCallbackSetup> {
 +    return {
 +      data: {
 +        className: "wcf\\data\\media\\MediaAction",
 +      },
 +    };
 +  }
 +
 +  public _ajaxSuccess(data): void {
 +    switch (data.actionName) {
 +      case "getSetCategoryDialog":
 +        UiDialog.open(this, data.returnValues.template);
 +
 +        break;
 +
 +      case "setCategory":
 +        UiDialog.close(this);
 +
 +        UiNotification.show();
 +
 +        Clipboard.reload();
 +
 +        break;
 +    }
 +  }
 +
 +  public _dialogSetup(): ReturnType<DialogCallbackSetup> {
 +    return {
 +      id: "mediaSetCategoryDialog",
 +      options: {
 +        onSetup: (content) => {
 +          content.querySelector("button")!.addEventListener("click", (event) => {
 +            event.preventDefault();
 +
 +            const category = content.querySelector('select[name="categoryID"]') as HTMLSelectElement;
 +            setCategory(~~category.value);
 +
 +            const target = event.currentTarget as HTMLButtonElement;
 +            target.disabled = true;
 +          });
 +        },
 +        title: Language.get("wcf.media.setCategory"),
 +      },
 +      source: null,
 +    };
 +  }
 +}
 +
 +const ajax = new MediaClipboard();
 +
 +let clipboardObjectIds: number[] = [];
 +
 +interface ClipboardActionData {
 +  data: {
 +    actionName: "com.woltlab.wcf.media.delete" | "com.woltlab.wcf.media.insert" | "com.woltlab.wcf.media.setCategory";
 +    parameters: {
 +      objectIDs: number[];
 +    };
 +  };
 +  responseData: null;
 +}
 +
 +/**
 + * Handles successful clipboard actions.
 + */
 +function clipboardAction(actionData: ClipboardActionData): void {
 +  const mediaIds = actionData.data.parameters.objectIDs;
 +
 +  switch (actionData.data.actionName) {
 +    case "com.woltlab.wcf.media.delete":
 +      // only consider events if the action has been executed
 +      if (actionData.responseData !== null) {
 +        _mediaManager.clipboardDeleteMedia(mediaIds);
 +      }
 +
 +      break;
 +
 +    case "com.woltlab.wcf.media.insert": {
 +      const mediaManagerEditor = _mediaManager as MediaManagerEditor;
 +      mediaManagerEditor.clipboardInsertMedia(mediaIds);
 +
 +      break;
 +    }
 +
 +    case "com.woltlab.wcf.media.setCategory":
 +      clipboardObjectIds = mediaIds;
 +
 +      Ajax.api(ajax, {
 +        actionName: "getSetCategoryDialog",
 +      });
 +
 +      break;
 +  }
 +}
 +
 +/**
 + * Sets the category of the marked media files.
 + */
 +function setCategory(categoryID: number) {
 +  Ajax.api(ajax, {
 +    actionName: "setCategory",
 +    objectIDs: clipboardObjectIds,
 +    parameters: {
 +      categoryID: categoryID,
 +    },
 +  });
 +}
 +
 +export function init(pageClassName: string, hasMarkedItems: boolean, mediaManager: MediaManager): void {
-   EventHandler.add("com.woltlab.wcf.clipboard", "com.woltlab.wcf.media", (data) => clipboardAction(data));
++  if (!_didInit) {
++    Clipboard.setup({
++      hasMarkedItems: hasMarkedItems,
++      pageClassName: pageClassName,
++    });
 +
++    EventHandler.add("com.woltlab.wcf.clipboard", "com.woltlab.wcf.media", (data) => clipboardAction(data));
++  }
++  
 +  _mediaManager = mediaManager;
++}
 +
++export function setMediaManager(mediaManager: MediaManager): void {
++  _mediaManager = mediaManager;
 +}
index 6bef72420e8d73204c4645eab6a689a652b8fe61,0000000000000000000000000000000000000000..d1db1509c732635ab6d095f24c9b24999b2bb29a
mode 100644,000000..100644
--- /dev/null
@@@ -1,552 -1,0 +1,554 @@@
-   protected readonly _id;
 +/**
 + * 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
 + * @woltlabExcludeBundle tiny
 + */
 +
 +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";
 +import { ObjectActionData } from "../../Ui/Object/Data";
 +
 +let mediaManagerCounter = 0;
 +
 +interface DialogInitAjaxResponseData {
 +  returnValues: {
 +    hasMarkedItems: number;
 +    media: object;
 +    pageCount: number;
 +  };
 +}
 +
 +interface SetMediaAdditionalData {
 +  pageCount: number;
 +  pageNo: number;
 +}
 +
 +abstract class MediaManager<TOptions extends MediaManagerOptions = MediaManagerOptions>
 +  implements DialogCallbackObject, MediaEditorCallbackObject {
 +  protected _forceClipboard = false;
 +  protected _hadInitiallyMarkedItems = false;
++  protected readonly _id: string;
 +  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;
 +
 +    this._id = `mediaManager${mediaManagerCounter++}`;
 +
 +    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) => 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: Event): 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());
 +      }
 +
 +      // 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,
 +        });
 +
 +        EventHandler.add("WoltLabSuite/Core/Ui/Object/Action", "delete", (data: ObjectActionData) =>
 +          this.removeMedia(~~data.objectElement.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();
 +      }
++    } else {
++      MediaClipboard.setMediaManager(this);
 +    }
 +
 +    // 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: Event): 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, closedEditorDialog = true): 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();
 +        }
 +      }
 +    }
 +
 +    if (closedEditorDialog) {
 +      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: (pageNo: number) => this._search!.search(pageNo),
 +        maxPage: pageCount,
 +      });
 +    } else if (this._pagination) {
 +      DomUtil.hide(this._pagination.getElement());
 +    }
 +  }
 +
 +  /**
 +   * 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).map(([mediaId, media]) => [~~mediaId, media]));
 +
 +    let info = DomTraverse.nextByClass(this._mediaManagerMediaList!, "info") as HTMLElement;
 +
 +    if (this._media.size) {
 +      if (info) {
 +        DomUtil.hide(info);
 +      }
 +    } else {
 +      if (info === null) {
 +        info = document.createElement("p");
 +        info.className = "info";
 +        info.textContent = Language.get("wcf.media.search.noResults");
 +      }
 +
 +      DomUtil.show(info);
 +      DomUtil.insertAfter(info, this._mediaManagerMediaList!);
 +    }
 +
 +    DomTraverse.childrenByTag(this._mediaManagerMediaList!, "LI").forEach((listItem) => {
 +      if (!this._media.has(~~listItem.dataset.objectId!)) {
 +        DomUtil.hide(listItem);
 +      } else {
 +        DomUtil.show(listItem);
 +      }
 +    });
 +
 +    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 by other code
 +      }
 +
 +      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.toString();
 +    label.appendChild(checkbox);
 +
 +    if (Permission.get("admin.content.cms.canManageMedia")) {
 +      const editButton = document.createElement("li");
 +      editButton.className = "jsMediaEditButton";
 +      editButton.dataset.objectId = media.mediaID.toString();
 +      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.classList.add("jsObjectAction");
 +      deleteButton.dataset.objectAction = "delete";
 +
 +      // use temporary title to not unescape html in filename
 +      const uuid = Core.getUuid();
 +      deleteButton.dataset.confirmMessage = 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;
index e38c4c39751e8467a1eac411774036db96bc5899,0000000000000000000000000000000000000000..be26f19e56d3599660e9f1700e526757bb323b8c
mode 100644,000000..100644
--- /dev/null
@@@ -1,371 -1,0 +1,371 @@@
-     return ["mediaInsert", ...this._mediaToInsert.keys()].join("-");
 +/**
 + * 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
 + * @woltlabExcludeBundle tiny
 + */
 +
 +import MediaManager from "./Base";
 +import * as Core from "../../Core";
 +import { Media, MediaInsertType, 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";
 +import DomUtil from "../../Dom/Util";
 +
 +interface 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) => 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(),
 +            onSetup: (content) => {
 +              content.querySelector(".buttonPrimary")!.addEventListener("click", (ev) => this._insertMedia(ev));
 +
 +              DomUtil.show(content.querySelector(".thumbnailSizeSelection") as HTMLElement);
 +            },
 +            title: Language.get("wcf.media.insert"),
 +          },
 +          source: dialog,
 +        };
 +      },
 +    });
 +  }
 +
 +  protected _click(event: Event): 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 {
++    return [this._id + "Insert", ...this._mediaToInsert.keys()].join("-");
 +  }
 +
 +  /**
 +   * Returns the supported thumbnail sizes (excluding `original`) for all media images to be inserted.
 +   */
 +  protected _getThumbnailSizes(): string[] {
 +    return ["small", "medium", "large"]
 +      .map((size) => {
 +        const sizeSupported = Array.from(this._mediaToInsert.values()).every((media) => {
 +          return media[size + "ThumbnailType"] !== null;
 +        });
 +
 +        if (sizeSupported) {
 +          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;
 +
 +    // 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, MediaInsertType.Separate, thumbnailSize);
 +    } else {
 +      this._options.editor!.buffer.set();
 +
 +      this._mediaToInsert.forEach((media) => this._insertMediaItem(thumbnailSize, media));
 +    }
 +
 +    if (this._mediaToInsertByClipboard) {
 +      Clipboard.unmark("com.woltlab.wcf.media", Array.from(this._mediaToInsert.keys()));
 +    }
 +
 +    this._mediaToInsert = new Map<number, Media>();
 +    this._mediaToInsertByClipboard = false;
 +
 +    // close manager dialog
 +    if (closeEditor) {
 +      UiDialog.close(this);
 +    }
 +  }
 +
 +  /**
 +   * Inserts a single media item into the editor.
 +   */
 +  protected _insertMediaItem(thumbnailSize: string | undefined, 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: Event): 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;
 +    mediaIds.forEach((mediaId) => {
 +      const media = this._media.get(mediaId)!;
 +      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.toString();
 +    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;
index d77268e501347b93c23c7cf5aa1fab0ca7fc08cc,2b731b3ae45ac57155146ed12edf62f1fc260639..21bc7b1cc612da7d463faa1274d4195d31feaa62
  /**
   * Initializes modules required for media clipboard.
 - * 
 - * @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/Clipboard
 + *
 + * @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/Clipboard
 + * @woltlabExcludeBundle tiny
   */
 -define([
 -              'Ajax',
 -              'Dom/ChangeListener',
 -              'EventHandler',
 -              'Language',
 -              'Ui/Dialog',
 -              'Ui/Notification',
 -              'WoltLabSuite/Core/Controller/Clipboard',
 -              'WoltLabSuite/Core/Media/Editor',
 -              'WoltLabSuite/Core/Media/List/Upload'
 -      ],
 -      function(
 -              Ajax,
 -              DomChangeListener,
 -              EventHandler,
 -              Language,
 -              UiDialog,
 -              UiNotification,
 -              Clipboard,
 -              MediaEditor,
 -              MediaListUpload
 -      ) {
 -      "use strict";
 -      
 -      if (!COMPILER_TARGET_DEFAULT) {
 -              var Fake = function() {};
 -              Fake.prototype = {
 -                      init: function() {},
 -                      _ajaxSetup: function() {},
 -                      _ajaxSuccess: function() {},
 -                      _clipboardAction: function() {},
 -                      _dialogSetup: function() {},
 -                      _edit: function() {},
 -                      _setCategory: function() {}
 -              };
 -              return Fake;
 -      }
 -      
 -      var _clipboardObjectIds = [];
 -      var _didInit = false;
 -      var _mediaManager;
 -      
 -      /**
 -       * @exports     WoltLabSuite/Core/Media/Clipboard
 -       */
 -      return {
 -              init: function(pageClassName, hasMarkedItems, mediaManager) {
 -                      if (!_didInit) {
 -                              Clipboard.setup({
 -                                      hasMarkedItems: hasMarkedItems,
 -                                      pageClassName: pageClassName
 -                              });
 -                              
 -                              EventHandler.add('com.woltlab.wcf.clipboard', 'com.woltlab.wcf.media', this._clipboardAction.bind(this));
 -                              
 -                              _didInit = true;
 -                      }
 -                      
 -                      _mediaManager = mediaManager;
 -              },
 -              
 -              /**
 -               * Returns the data used to setup the AJAX request object.
 -               *
 -               * @return      {object}        setup data
 -               */
 -              _ajaxSetup: function() {
 -                      return {
 -                              data: {
 -                                      className: 'wcf\\data\\media\\MediaAction'
 -                              }
 -                      }
 -              },
 -              
 -              /**
 -               * Handles successful AJAX request.
 -               *
 -               * @param       {object}        data    response data
 -               */
 -              _ajaxSuccess: function(data) {
 -                      switch (data.actionName) {
 -                              case 'getSetCategoryDialog':
 -                                      UiDialog.open(this, data.returnValues.template);
 -                                      
 -                                      break;
 -                                      
 -                              case 'setCategory':
 -                                      UiDialog.close(this);
 -                                      
 -                                      UiNotification.show();
 -                                      
 -                                      Clipboard.reload();
 -                                      
 -                                      break;
 -                      }
 -              },
 -              
 -              /**
 -               * Returns the data used to setup the dialog.
 -               * 
 -               * @return      {object}        setup data
 -               */
 -              _dialogSetup: function() {
 -                      return {
 -                              id: 'mediaSetCategoryDialog',
 -                              options: {
 -                                      onSetup: function(content) {
 -                                              elBySel('button', content).addEventListener(WCF_CLICK_EVENT, function(event) {
 -                                                      event.preventDefault();
 -                                                      
 -                                                      this._setCategory(~~elBySel('select[name="categoryID"]', content).value);
 -                                                      
 -                                                      event.currentTarget.disabled = true;
 -                                              }.bind(this));
 -                                      }.bind(this),
 -                                      title: Language.get('wcf.media.setCategory')
 -                              },
 -                              source: null
 -                      }
 -              },
 -              
 -              /**
 -               * Handles successful clipboard actions.
 -               * 
 -               * @param       {object}        actionData
 -               */
 -              _clipboardAction: function(actionData) {
 -                      var mediaIds = actionData.data.parameters.objectIDs;
 -                      
 -                      switch (actionData.data.actionName) {
 -                              case 'com.woltlab.wcf.media.delete':
 -                                      // only consider events if the action has been executed
 -                                      if (actionData.responseData !== null) {
 -                                              _mediaManager.clipboardDeleteMedia(mediaIds);
 -                                      }
 -                                      
 -                                      break;
 -                                      
 -                              case 'com.woltlab.wcf.media.insert':
 -                                      _mediaManager.clipboardInsertMedia(mediaIds);
 -                                      
 -                                      break;
 -                                      
 -                              case 'com.woltlab.wcf.media.setCategory':
 -                                      _clipboardObjectIds = mediaIds;
 -                                      
 -                                      Ajax.api(this, {
 -                                              actionName: 'getSetCategoryDialog'
 -                                      });
 -                                      
 -                                      break;
 -                      }
 -              },
 -              
 -              /**
 -               * Sets the category of the marked media files.
 -               * 
 -               * @param       {int}           categoryID      selected category id
 -               */
 -              _setCategory: function(categoryID) {
 -                      Ajax.api(this, {
 -                              actionName: 'setCategory',
 -                              objectIDs: _clipboardObjectIds,
 -                              parameters: {
 -                                      categoryID: categoryID
 -                              }
 -                      });
 -              },
 -              
 -              /**
 -               * Sets the currently active media manager.
 -               * 
 -               * @param       {WoltLabSuite/Core/Media/Manager/Base}  mediaManager
 -               */
 -              setMediaManager: function(mediaManager) {
 -                      _mediaManager = mediaManager;
 -              }
 -      }
 +define(["require", "exports", "tslib", "../Controller/Clipboard", "../Ui/Notification", "../Ui/Dialog", "../Event/Handler", "../Language", "../Ajax"], function (require, exports, tslib_1, Clipboard, UiNotification, UiDialog, EventHandler, Language, Ajax) {
 +    "use strict";
 +    Object.defineProperty(exports, "__esModule", { value: true });
-     exports.init = void 0;
++    exports.setMediaManager = exports.init = void 0;
 +    Clipboard = tslib_1.__importStar(Clipboard);
 +    UiNotification = tslib_1.__importStar(UiNotification);
 +    UiDialog = tslib_1.__importStar(UiDialog);
 +    EventHandler = tslib_1.__importStar(EventHandler);
 +    Language = tslib_1.__importStar(Language);
 +    Ajax = tslib_1.__importStar(Ajax);
 +    let _mediaManager;
++    const _didInit = false;
 +    class MediaClipboard {
 +        _ajaxSetup() {
 +            return {
 +                data: {
 +                    className: "wcf\\data\\media\\MediaAction",
 +                },
 +            };
 +        }
 +        _ajaxSuccess(data) {
 +            switch (data.actionName) {
 +                case "getSetCategoryDialog":
 +                    UiDialog.open(this, data.returnValues.template);
 +                    break;
 +                case "setCategory":
 +                    UiDialog.close(this);
 +                    UiNotification.show();
 +                    Clipboard.reload();
 +                    break;
 +            }
 +        }
 +        _dialogSetup() {
 +            return {
 +                id: "mediaSetCategoryDialog",
 +                options: {
 +                    onSetup: (content) => {
 +                        content.querySelector("button").addEventListener("click", (event) => {
 +                            event.preventDefault();
 +                            const category = content.querySelector('select[name="categoryID"]');
 +                            setCategory(~~category.value);
 +                            const target = event.currentTarget;
 +                            target.disabled = true;
 +                        });
 +                    },
 +                    title: Language.get("wcf.media.setCategory"),
 +                },
 +                source: null,
 +            };
 +        }
 +    }
 +    const ajax = new MediaClipboard();
 +    let clipboardObjectIds = [];
 +    /**
 +     * Handles successful clipboard actions.
 +     */
 +    function clipboardAction(actionData) {
 +        const mediaIds = actionData.data.parameters.objectIDs;
 +        switch (actionData.data.actionName) {
 +            case "com.woltlab.wcf.media.delete":
 +                // only consider events if the action has been executed
 +                if (actionData.responseData !== null) {
 +                    _mediaManager.clipboardDeleteMedia(mediaIds);
 +                }
 +                break;
 +            case "com.woltlab.wcf.media.insert": {
 +                const mediaManagerEditor = _mediaManager;
 +                mediaManagerEditor.clipboardInsertMedia(mediaIds);
 +                break;
 +            }
 +            case "com.woltlab.wcf.media.setCategory":
 +                clipboardObjectIds = mediaIds;
 +                Ajax.api(ajax, {
 +                    actionName: "getSetCategoryDialog",
 +                });
 +                break;
 +        }
 +    }
 +    /**
 +     * Sets the category of the marked media files.
 +     */
 +    function setCategory(categoryID) {
 +        Ajax.api(ajax, {
 +            actionName: "setCategory",
 +            objectIDs: clipboardObjectIds,
 +            parameters: {
 +                categoryID: categoryID,
 +            },
 +        });
 +    }
 +    function init(pageClassName, hasMarkedItems, mediaManager) {
-         Clipboard.setup({
-             hasMarkedItems: hasMarkedItems,
-             pageClassName: pageClassName,
-         });
++        if (!_didInit) {
++            Clipboard.setup({
++                hasMarkedItems: hasMarkedItems,
++                pageClassName: pageClassName,
++            });
++            EventHandler.add("com.woltlab.wcf.clipboard", "com.woltlab.wcf.media", (data) => clipboardAction(data));
++        }
 +        _mediaManager = mediaManager;
-         EventHandler.add("com.woltlab.wcf.clipboard", "com.woltlab.wcf.media", (data) => clipboardAction(data));
 +    }
 +    exports.init = init;
++    function setMediaManager(mediaManager) {
++        _mediaManager = mediaManager;
++    }
++    exports.setMediaManager = setMediaManager;
  });
index cf755e784fbc3e4572e23e9106485b98880d778e,2e14f0054da11cc121cc0af2668b429f1400129f..d27b87776e5d1ab316e27c31bdc77b70879a0164
  /**
   * 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
 + * @woltlabExcludeBundle tiny
   */
 -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(WCF_CLICK_EVENT, 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();
 -                              }
 -                      }
 -                      else {
 -                              MediaClipboard.setMediaManager(this);
 -                      }
 -                      
 -                      // 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
 -               * @param       {boolean}       closedEditorDialog
 -               */
 -              _editorSuccess: function(media, oldCategoryId, closedEditorDialog = true) {
 -                      // 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();
 -                                      }
 -                              }
 -                      }
 -                      
 -                      if (closedEditorDialog) {
 -                              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;
 +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";
 +    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._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);
 +            this._id = `mediaManager${mediaManagerCounter++}`;
 +            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));
 +        }
 +        /**
 +         * Adds click event listeners to media buttons.
 +         */
 +        _addButtonEventListeners() {
 +            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) => this._editMedia(ev));
 +                }
 +            });
 +        }
 +        /**
 +         * Is called when a new category is selected.
 +         */
 +        _categoryChange() {
 +            this._search.search();
 +        }
 +        /**
 +         * Handles clicks on the media manager button.
 +         */
 +        _click(event) {
 +            event.preventDefault();
 +            UiDialog.open(this);
 +        }
 +        /**
 +         * Is called if the media manager dialog is closed.
 +         */
 +        _dialogClose() {
 +            // 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.
 +         */
 +        _dialogInit(content, data) {
 +            // 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.
 +         */
 +        _dialogSetup() {
 +            return {
 +                id: this._id,
 +                options: {
 +                    onClose: () => this._dialogClose(),
 +                    onShow: () => this._dialogShow(),
 +                    title: this._options.dialogTitle,
 +                },
 +                source: {
 +                    after: (content, data) => 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.
 +         */
 +        _dialogShow() {
 +            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());
 +                }
 +                // store list items locally
 +                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,
 +                    });
 +                    EventHandler.add("WoltLabSuite/Core/Ui/Object/Action", "delete", (data) => this.removeMedia(~~data.objectElement.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 Search_1.default(this);
 +                if (!listItems.length) {
 +                    this._search.hideSearch();
 +                }
 +            }
++            else {
++                MediaClipboard.setMediaManager(this);
++            }
 +            // 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.
 +         */
 +        _editMedia(event) {
 +            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;
 +            this._mediaEditor.edit(this._media.get(~~target.dataset.objectId));
 +        }
 +        /**
 +         * Re-opens the manager dialog after closing the editor dialog.
 +         */
 +        _editorClose() {
 +            UiDialog.open(this);
 +        }
 +        /**
 +         * Re-opens the manager dialog and updates the media data after successfully editing a media file.
 +         */
 +        _editorSuccess(media, oldCategoryId, closedEditorDialog = true) {
 +            // 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();
 +                    }
 +                }
 +            }
 +            if (closedEditorDialog) {
 +                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.
 +         */
 +        _initPagination(pageCount, pageNo) {
 +            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"), newPagination);
 +                this._pagination = new Pagination_1.default(newPagination, {
 +                    activePage: pageNo,
 +                    callbackSwitch: (pageNo) => this._search.search(pageNo),
 +                    maxPage: pageCount,
 +                });
 +            }
 +            else if (this._pagination) {
 +                DomUtil.hide(this._pagination.getElement());
 +            }
 +        }
 +        /**
 +         * Removes all media clipboard checkboxes.
 +         */
 +        _removeClipboardCheckboxes() {
 +            this._mediaManagerMediaList.querySelectorAll(".mediaCheckbox").forEach((el) => el.remove());
 +        }
 +        /**
 +         * Opens the media editor after uploading a single file.
 +         *
 +         * @since 5.2
 +         */
 +        _openEditorAfterUpload(data) {
 +            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) {
 +            this._media = new Map(Object.entries(media).map(([mediaId, media]) => [~~mediaId, media]));
 +            let info = DomTraverse.nextByClass(this._mediaManagerMediaList, "info");
 +            if (this._media.size) {
 +                if (info) {
 +                    DomUtil.hide(info);
 +                }
 +            }
 +            else {
 +                if (info === null) {
 +                    info = document.createElement("p");
 +                    info.className = "info";
 +                    info.textContent = Language.get("wcf.media.search.noResults");
 +                }
 +                DomUtil.show(info);
 +                DomUtil.insertAfter(info, this._mediaManagerMediaList);
 +            }
 +            DomTraverse.childrenByTag(this._mediaManagerMediaList, "LI").forEach((listItem) => {
 +                if (!this._media.has(~~listItem.dataset.objectId)) {
 +                    DomUtil.hide(listItem);
 +                }
 +                else {
 +                    DomUtil.show(listItem);
 +                }
 +            });
 +            DomChangeListener.trigger();
 +            if (Permission.get("admin.content.cms.canManageMedia") || this._forceClipboard) {
 +                Clipboard.reload();
 +            }
 +            else {
 +                this._removeClipboardCheckboxes();
 +            }
 +        }
 +        /**
 +         * Adds a media file to the manager.
 +         */
 +        addMedia(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.
 +         */
 +        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.
 +         */
 +        getCategoryId() {
 +            if (this._mediaCategorySelect) {
 +                return ~~this._mediaCategorySelect.value;
 +            }
 +            return 0;
 +        }
 +        /**
 +         * Returns the media manager dialog element.
 +         */
 +        getDialog() {
 +            return UiDialog.getDialog(this).dialog;
 +        }
 +        /**
 +         * Returns the mode of the media manager.
 +         */
 +        getMode() {
 +            return "";
 +        }
 +        /**
 +         * Returns the media manager option with the given name.
 +         */
 +        getOption(name) {
 +            if (this._options[name]) {
 +                return this._options[name];
 +            }
 +            return null;
 +        }
 +        /**
 +         * Removes a media file.
 +         */
 +        removeMedia(mediaId) {
 +            if (this._listItems.has(mediaId)) {
 +                // remove list item
 +                try {
 +                    this._listItems.get(mediaId).remove();
 +                }
 +                catch (e) {
 +                    // ignore errors if item has already been removed by other code
 +                }
 +                this._listItems.delete(mediaId);
 +                this._media.delete(mediaId);
 +            }
 +        }
 +        /**
 +         * Changes the displayed media to the previously displayed media.
 +         */
 +        resetMedia() {
 +            // 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, template, additionalData) {
 +            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.
 +         */
 +        setupMediaElement(media, mediaElement) {
 +            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.toString();
 +            label.appendChild(checkbox);
 +            if (Permission.get("admin.content.cms.canManageMedia")) {
 +                const editButton = document.createElement("li");
 +                editButton.className = "jsMediaEditButton";
 +                editButton.dataset.objectId = media.mediaID.toString();
 +                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.classList.add("jsObjectAction");
 +                deleteButton.dataset.objectAction = "delete";
 +                // use temporary title to not unescape html in filename
 +                const uuid = Core.getUuid();
 +                deleteButton.dataset.confirmMessage = 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);
 +    return MediaManager;
  });
index 37998a94347946a06f3d2b990d737af988707653,5ae58ce6829359da822adda1bbe7ee6b7f90e5ad..38a612e5aad7b59c800d94213009bd0313e049e3
  /**
   * 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
 + * @woltlabExcludeBundle tiny
   */
 -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(WCF_CLICK_EVENT, 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(WCF_CLICK_EVENT, 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(WCF_CLICK_EVENT, 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 = this._id + 'Insert';
 -                      
 -                      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;
 +define(["require", "exports", "tslib", "./Base", "../../Core", "../../Event/Handler", "../../Dom/Traverse", "../../Language", "../../Ui/Dialog", "../../Controller/Clipboard", "../../Dom/Util"], function (require, exports, tslib_1, Base_1, Core, EventHandler, DomTraverse, Language, UiDialog, Clipboard, Util_1) {
 +    "use strict";
 +    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);
 +    Util_1 = tslib_1.__importDefault(Util_1);
 +    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));
 +            });
 +            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));
 +            }
 +        }
 +        _addButtonEventListeners() {
 +            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) => this._openInsertDialog(ev));
 +                }
 +            });
 +        }
 +        /**
 +         * Builds the dialog to setup inserting media files.
 +         */
 +        _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: () => {
 +                    return {
 +                        id: this._getInsertDialogId(),
 +                        options: {
 +                            onClose: () => this._editorClose(),
 +                            onSetup: (content) => {
 +                                content.querySelector(".buttonPrimary").addEventListener("click", (ev) => this._insertMedia(ev));
 +                                Util_1.default.show(content.querySelector(".thumbnailSizeSelection"));
 +                            },
 +                            title: Language.get("wcf.media.insert"),
 +                        },
 +                        source: dialog,
 +                    };
 +                },
 +            });
 +        }
 +        _click(event) {
 +            this._activeButton = event.currentTarget;
 +            super._click(event);
 +        }
 +        _dialogShow() {
 +            super._dialogShow();
 +            // check if data needs to be uploaded
 +            if (this._uploadData) {
 +                const fileUploadData = this._uploadData;
 +                if (fileUploadData.file) {
 +                    this._upload.uploadFile(fileUploadData.file);
 +                }
 +                else {
 +                    const blobUploadData = this._uploadData;
 +                    this._uploadId = this._upload.uploadBlob(blobUploadData.blob);
 +                }
 +                this._uploadData = null;
 +            }
 +        }
 +        /**
 +         * Handles pasting and dragging and dropping files into the editor.
 +         */
 +        _editorUpload(data) {
 +            this._uploadData = data;
 +            UiDialog.open(this);
 +        }
 +        /**
 +         * Returns the id of the insert dialog based on the media files to be inserted.
 +         */
 +        _getInsertDialogId() {
-             return ["mediaInsert", ...this._mediaToInsert.keys()].join("-");
++            return [this._id + "Insert", ...this._mediaToInsert.keys()].join("-");
 +        }
 +        /**
 +         * Returns the supported thumbnail sizes (excluding `original`) for all media images to be inserted.
 +         */
 +        _getThumbnailSizes() {
 +            return ["small", "medium", "large"]
 +                .map((size) => {
 +                const sizeSupported = Array.from(this._mediaToInsert.values()).every((media) => {
 +                    return media[size + "ThumbnailType"] !== null;
 +                });
 +                if (sizeSupported) {
 +                    return size;
 +                }
 +                return null;
 +            })
 +                .filter((s) => s !== null);
 +        }
 +        /**
 +         * Inserts media files into the editor.
 +         */
 +        _insertMedia(event, thumbnailSize, closeEditor = false) {
 +            if (closeEditor === undefined)
 +                closeEditor = true;
 +            // 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.closest(".dialogContent");
 +                const thumbnailSizeSelect = dialogContent.querySelector("select[name=thumbnailSize]");
 +                thumbnailSize = thumbnailSizeSelect.value;
 +            }
 +            if (this._options.callbackInsert !== null) {
 +                this._options.callbackInsert(this._mediaToInsert, "separate" /* Separate */, thumbnailSize);
 +            }
 +            else {
 +                this._options.editor.buffer.set();
 +                this._mediaToInsert.forEach((media) => this._insertMediaItem(thumbnailSize, media));
 +            }
 +            if (this._mediaToInsertByClipboard) {
 +                Clipboard.unmark("com.woltlab.wcf.media", Array.from(this._mediaToInsert.keys()));
 +            }
 +            this._mediaToInsert = new Map();
 +            this._mediaToInsertByClipboard = false;
 +            // close manager dialog
 +            if (closeEditor) {
 +                UiDialog.close(this);
 +            }
 +        }
 +        /**
 +         * Inserts a single media item into the editor.
 +         */
 +        _insertMediaItem(thumbnailSize, media) {
 +            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.
 +         */
 +        _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 = new Map(data.media.entries());
 +                    this._insertMedia(null, "medium", false);
 +                    this._uploadId = null;
 +                }
 +            }
 +        }
 +        /**
 +         * Handles clicking on the insert button.
 +         */
 +        _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.
 +         */
 +        clipboardInsertMedia(mediaIds) {
 +            this.insertMedia(mediaIds, true);
 +        }
 +        /**
 +         * Prepares insertion of the media files with the given ids.
 +         */
 +        insertMedia(mediaIds, insertedByClipboard) {
 +            this._mediaToInsert = new Map();
 +            this._mediaToInsertByClipboard = insertedByClipboard || false;
 +            // open the insert dialog if all media files are images
 +            let imagesOnly = true;
 +            mediaIds.forEach((mediaId) => {
 +                const media = this._media.get(mediaId);
 +                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();
 +            }
 +        }
 +        getMode() {
 +            return "editor";
 +        }
 +        setupMediaElement(media, mediaElement) {
 +            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.toString();
 +            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);
 +    return MediaManagerEditor;
  });