Merge branch '5.3' into 5.4
authorMatthias Schmidt <gravatronics@live.com>
Fri, 25 Jun 2021 06:40:13 +0000 (08:40 +0200)
committerMatthias Schmidt <gravatronics@live.com>
Fri, 25 Jun 2021 06:40:13 +0000 (08:40 +0200)
1  2 
ts/WoltLabSuite/Core/Ui/Message/InlineEditor.ts
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/InlineEditor.js

index 18b5b50273975b2ea65d103519966ccae66f48e4,0000000000000000000000000000000000000000..4b177aadbfbd259387f3804f63ae6eaf2da2f4a7
mode 100644,000000..100644
--- /dev/null
@@@ -1,707 -1,0 +1,709 @@@
-  * @copyright  2001-2019 WoltLab GmbH
 +/**
 + * Flexible message inline editor.
 + *
 + * @author  Alexander Ebert
-       const pollContainer = document.createElement("div");
-       pollContainer.className = "jsInlineEditorHideContent";
-       DomUtil.setInnerHtml(pollContainer, data.returnValues.poll);
++ * @copyright  2001-2021 WoltLab GmbH
 + * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 + * @module  WoltLabSuite/Core/Ui/Message/InlineEditor
 + */
 +
 +import * as Ajax from "../../Ajax";
 +import { AjaxCallbackObject, AjaxCallbackSetup, ResponseData } from "../../Ajax/Data";
 +import * as Core from "../../Core";
 +import DomChangeListener from "../../Dom/Change/Listener";
 +import DomUtil from "../../Dom/Util";
 +import * as Environment from "../../Environment";
 +import * as EventHandler from "../../Event/Handler";
 +import * as Language from "../../Language";
 +import { NotificationAction } from "../Dropdown/Data";
 +import * as UiDropdownReusable from "../Dropdown/Reusable";
 +import * as UiNotification from "../Notification";
 +import * as UiScroll from "../Scroll";
 +import {
 +  AjaxResponseEditor,
 +  AjaxResponseMessage,
 +  ElementVisibility,
 +  ItemData,
 +  MessageInlineEditorOptions,
 +} from "./InlineEditor/Data";
 +
 +interface ElementData {
 +  button: HTMLAnchorElement;
 +  messageBody: HTMLElement;
 +  messageBodyEditor: HTMLElement | null;
 +  messageFooter: HTMLElement;
 +  messageFooterButtons: HTMLUListElement;
 +  messageHeader: HTMLElement;
 +  messageText: HTMLElement;
 +}
 +
 +interface ValidationData {
 +  api: UiMessageInlineEditor;
 +  parameters: ArbitraryObject;
 +  valid: boolean;
 +  promises: Promise<void>[];
 +}
 +
 +class UiMessageInlineEditor implements AjaxCallbackObject {
 +  protected _activeDropdownElement: HTMLElement | null;
 +  protected _activeElement: HTMLElement | null;
 +  protected _dropdownMenu: HTMLUListElement | null;
 +  protected _elements: WeakMap<HTMLElement, ElementData>;
 +  protected _options: MessageInlineEditorOptions;
 +
 +  /**
 +   * Initializes the message inline editor.
 +   */
 +  constructor(opts: Partial<MessageInlineEditorOptions>) {
 +    this.init(opts);
 +  }
 +
 +  /**
 +   * Helper initialization method for legacy inheritance support.
 +   */
 +  protected init(opts: Partial<MessageInlineEditorOptions>): void {
 +    // Define the properties again, the constructor might not be
 +    // called in legacy implementations.
 +    this._activeDropdownElement = null;
 +    this._activeElement = null;
 +    this._dropdownMenu = null;
 +    this._elements = new WeakMap<HTMLElement, ElementData>();
 +
 +    this._options = Core.extend(
 +      {
 +        canEditInline: false,
 +
 +        className: "",
 +        containerId: 0,
 +        dropdownIdentifier: "",
 +        editorPrefix: "messageEditor",
 +
 +        messageSelector: ".jsMessage",
 +
 +        quoteManager: null,
 +      },
 +      opts,
 +    ) as MessageInlineEditorOptions;
 +
 +    this.rebuild();
 +
 +    DomChangeListener.add(`Ui/Message/InlineEdit_${this._options.className}`, () => this.rebuild());
 +  }
 +
 +  /**
 +   * Initializes each applicable message, should be called whenever new
 +   * messages are being displayed.
 +   */
 +  rebuild(): void {
 +    document.querySelectorAll(this._options.messageSelector).forEach((element: HTMLElement) => {
 +      if (this._elements.has(element)) {
 +        return;
 +      }
 +
 +      const button = element.querySelector(".jsMessageEditButton") as HTMLAnchorElement;
 +      if (button !== null) {
 +        const canEdit = Core.stringToBool(element.dataset.canEdit || "");
 +        const canEditInline = Core.stringToBool(element.dataset.canEditInline || "");
 +
 +        if (this._options.canEditInline || canEditInline) {
 +          button.addEventListener("click", (ev) => this._clickDropdown(element, ev));
 +          button.classList.add("jsDropdownEnabled");
 +
 +          if (canEdit) {
 +            button.addEventListener("dblclick", (ev) => this._click(element, ev));
 +          }
 +        } else if (canEdit) {
 +          button.addEventListener("click", (ev) => this._click(element, ev));
 +        }
 +      }
 +
 +      const messageBody = element.querySelector(".messageBody") as HTMLElement;
 +      const messageFooter = element.querySelector(".messageFooter") as HTMLElement;
 +      const messageFooterButtons = messageFooter.querySelector(".messageFooterButtons") as HTMLUListElement;
 +      const messageHeader = element.querySelector(".messageHeader") as HTMLElement;
 +      const messageText = messageBody.querySelector(".messageText") as HTMLElement;
 +
 +      this._elements.set(element, {
 +        button,
 +        messageBody,
 +        messageBodyEditor: null,
 +        messageFooter,
 +        messageFooterButtons,
 +        messageHeader,
 +        messageText,
 +      });
 +    });
 +  }
 +
 +  /**
 +   * Handles clicks on the edit button or the edit dropdown item.
 +   */
 +  protected _click(element: HTMLElement | null, event: MouseEvent | null): void {
 +    if (element === null) {
 +      element = this._activeDropdownElement;
 +    }
 +    if (event) {
 +      event.preventDefault();
 +    }
 +
 +    if (this._activeElement === null) {
 +      this._activeElement = element;
 +
 +      this._prepare();
 +
 +      Ajax.api(this, {
 +        actionName: "beginEdit",
 +        parameters: {
 +          containerID: this._options.containerId,
 +          objectID: this._getObjectId(element!),
 +        },
 +      });
 +    } else {
 +      UiNotification.show("wcf.message.error.editorAlreadyInUse", undefined, "warning");
 +    }
 +  }
 +
 +  /**
 +   * Creates and opens the dropdown on first usage.
 +   */
 +  protected _clickDropdown(element: HTMLElement, event: MouseEvent): void {
 +    event.preventDefault();
 +
 +    const button = event.currentTarget as HTMLElement;
 +    if (button.classList.contains("dropdownToggle")) {
 +      return;
 +    }
 +
 +    button.classList.add("dropdownToggle");
 +    button.parentElement!.classList.add("dropdown");
 +    button.addEventListener("click", (event) => {
 +      event.preventDefault();
 +      event.stopPropagation();
 +
 +      this._activeDropdownElement = element;
 +      UiDropdownReusable.toggleDropdown(this._options.dropdownIdentifier, button);
 +    });
 +
 +    // build dropdown
 +    if (this._dropdownMenu === null) {
 +      this._dropdownMenu = document.createElement("ul");
 +      this._dropdownMenu.className = "dropdownMenu";
 +
 +      const items = this._dropdownGetItems();
 +
 +      EventHandler.fire("com.woltlab.wcf.inlineEditor", `dropdownInit_${this._options.dropdownIdentifier}`, {
 +        items: items,
 +      });
 +
 +      this._dropdownBuild(items);
 +
 +      UiDropdownReusable.init(this._options.dropdownIdentifier, this._dropdownMenu);
 +      UiDropdownReusable.registerCallback(this._options.dropdownIdentifier, (containerId, action) =>
 +        this._dropdownToggle(containerId, action),
 +      );
 +    }
 +
 +    setTimeout(() => button.click(), 10);
 +  }
 +
 +  /**
 +   * Creates the dropdown menu on first usage.
 +   */
 +  protected _dropdownBuild(items: ItemData[]): void {
 +    items.forEach((item) => {
 +      const listItem = document.createElement("li");
 +      listItem.dataset.item = item.item;
 +
 +      if (item.item === "divider") {
 +        listItem.className = "dropdownDivider";
 +      } else {
 +        const label = document.createElement("span");
 +        label.textContent = Language.get(item.label!);
 +        listItem.appendChild(label);
 +
 +        if (item.item === "editItem") {
 +          listItem.addEventListener("click", (ev) => this._click(null, ev));
 +        } else {
 +          listItem.addEventListener("click", (ev) => this._clickDropdownItem(ev));
 +        }
 +      }
 +
 +      this._dropdownMenu!.appendChild(listItem);
 +    });
 +  }
 +
 +  /**
 +   * Callback for dropdown toggle.
 +   */
 +  protected _dropdownToggle(containerId: string, action: NotificationAction): void {
 +    const elementData = this._elements.get(this._activeDropdownElement!)!;
 +    const buttonParent = elementData.button.parentElement!;
 +
 +    if (action === "close") {
 +      buttonParent.classList.remove("dropdownOpen");
 +      elementData.messageFooterButtons.classList.remove("forceVisible");
 +
 +      return;
 +    }
 +
 +    buttonParent.classList.add("dropdownOpen");
 +    elementData.messageFooterButtons.classList.add("forceVisible");
 +
 +    const visibility = new Map<string, boolean>(Object.entries(this._dropdownOpen()));
 +
 +    EventHandler.fire("com.woltlab.wcf.inlineEditor", `dropdownOpen_${this._options.dropdownIdentifier}`, {
 +      element: this._activeDropdownElement,
 +      visibility,
 +    });
 +
 +    const dropdownMenu = this._dropdownMenu!;
 +
 +    let visiblePredecessor = false;
 +    const children = Array.from(dropdownMenu.children);
 +    children.forEach((listItem: HTMLElement, index) => {
 +      const item = listItem.dataset.item!;
 +
 +      if (item === "divider") {
 +        if (visiblePredecessor) {
 +          DomUtil.show(listItem);
 +
 +          visiblePredecessor = false;
 +        } else {
 +          DomUtil.hide(listItem);
 +        }
 +      } else {
 +        if (visibility.get(item) === false) {
 +          DomUtil.hide(listItem);
 +
 +          // check if previous item was a divider
 +          if (index > 0 && index + 1 === children.length) {
 +            const previousElementSibling = listItem.previousElementSibling as HTMLElement;
 +            if (previousElementSibling.dataset.item === "divider") {
 +              DomUtil.hide(previousElementSibling);
 +            }
 +          }
 +        } else {
 +          DomUtil.show(listItem);
 +
 +          visiblePredecessor = true;
 +        }
 +      }
 +    });
 +  }
 +
 +  /**
 +   * Returns the list of dropdown items for this type.
 +   */
 +  protected _dropdownGetItems(): ItemData[] {
 +    // This should be an abstract method, but cannot be marked as such for backwards compatibility.
 +    return [];
 +  }
 +
 +  /**
 +   * Invoked once the dropdown for this type is shown, expects a list of type name and a boolean value
 +   * to represent the visibility of each item. Items that do not appear in this list will be considered
 +   * visible.
 +   */
 +  protected _dropdownOpen(): ElementVisibility {
 +    // This should be an abstract method, but cannot be marked as such for backwards compatibility.
 +    return {};
 +  }
 +
 +  /**
 +   * Invoked whenever the user selects an item from the dropdown menu, the selected item is passed as argument.
 +   */
 +  protected _dropdownSelect(_item: string): void {
 +    // This should be an abstract method, but cannot be marked as such for backwards compatibility.
 +  }
 +
 +  /**
 +   * Handles clicks on a dropdown item.
 +   */
 +  protected _clickDropdownItem(event: MouseEvent): void {
 +    event.preventDefault();
 +
 +    const target = event.currentTarget as HTMLElement;
 +    const item = target.dataset.item!;
 +    const data = {
 +      cancel: false,
 +      element: this._activeDropdownElement,
 +      item,
 +    };
 +    EventHandler.fire("com.woltlab.wcf.inlineEditor", `dropdownItemClick_${this._options.dropdownIdentifier}`, data);
 +
 +    if (data.cancel) {
 +      event.preventDefault();
 +    } else {
 +      this._dropdownSelect(item);
 +    }
 +  }
 +
 +  /**
 +   * Prepares the message for editor display.
 +   */
 +  protected _prepare(): void {
 +    const data = this._elements.get(this._activeElement!)!;
 +
 +    const messageBodyEditor = document.createElement("div");
 +    messageBodyEditor.className = "messageBody editor";
 +    data.messageBodyEditor = messageBodyEditor;
 +
 +    const icon = document.createElement("span");
 +    icon.className = "icon icon48 fa-spinner";
 +    messageBodyEditor.appendChild(icon);
 +
 +    data.messageBody.insertAdjacentElement("afterend", messageBodyEditor);
 +
 +    DomUtil.hide(data.messageBody);
 +  }
 +
 +  /**
 +   * Shows the message editor.
 +   */
 +  protected _showEditor(data: AjaxResponseEditor): void {
 +    const id = this._getEditorId();
 +    const activeElement = this._activeElement!;
 +    const elementData = this._elements.get(activeElement)!;
 +
 +    activeElement.classList.add("jsInvalidQuoteTarget");
 +    const icon = elementData.messageBodyEditor!.querySelector(".icon") as HTMLElement;
 +    icon.remove();
 +
 +    const messageBody = elementData.messageBodyEditor!;
 +    const editor = document.createElement("div");
 +    editor.className = "editorContainer";
 +    DomUtil.setInnerHtml(editor, data.returnValues.template);
 +    messageBody.appendChild(editor);
 +
 +    // bind buttons
 +    const formSubmit = editor.querySelector(".formSubmit") as HTMLElement;
 +
 +    const buttonSave = formSubmit.querySelector('button[data-type="save"]') as HTMLButtonElement;
 +    buttonSave.addEventListener("click", () => this._save());
 +
 +    const buttonCancel = formSubmit.querySelector('button[data-type="cancel"]') as HTMLButtonElement;
 +    buttonCancel.addEventListener("click", () => this._restoreMessage());
 +
 +    EventHandler.add("com.woltlab.wcf.redactor", `submitEditor_${id}`, (data: { cancel: boolean }) => {
 +      data.cancel = true;
 +
 +      this._save();
 +    });
 +
 +    // hide message header and footer
 +    DomUtil.hide(elementData.messageHeader);
 +    DomUtil.hide(elementData.messageFooter);
 +
 +    if (Environment.editor() === "redactor") {
 +      window.setTimeout(() => {
 +        if (this._options.quoteManager) {
 +          this._options.quoteManager.setAlternativeEditor(id);
 +        }
 +
 +        UiScroll.element(activeElement);
 +      }, 250);
 +    } else {
 +      const editorElement = document.getElementById(id) as HTMLElement;
 +      editorElement.focus();
 +    }
 +  }
 +
 +  /**
 +   * Restores the message view.
 +   */
 +  protected _restoreMessage(): void {
 +    const activeElement = this._activeElement!;
 +    const elementData = this._elements.get(activeElement)!;
 +
 +    this._destroyEditor();
 +
 +    elementData.messageBodyEditor!.remove();
 +    elementData.messageBodyEditor = null;
 +
 +    DomUtil.show(elementData.messageBody);
 +    DomUtil.show(elementData.messageFooter);
 +    DomUtil.show(elementData.messageHeader);
 +    activeElement.classList.remove("jsInvalidQuoteTarget");
 +
 +    this._activeElement = null;
 +
 +    if (this._options.quoteManager) {
 +      this._options.quoteManager.clearAlternativeEditor();
 +    }
 +  }
 +
 +  /**
 +   * Saves the editor message.
 +   */
 +  protected _save(): void {
 +    const parameters = {
 +      containerID: this._options.containerId,
 +      data: {
 +        message: "",
 +      },
 +      objectID: this._getObjectId(this._activeElement!),
 +      removeQuoteIDs: this._options.quoteManager ? this._options.quoteManager.getQuotesMarkedForRemoval() : [],
 +    };
 +
 +    const id = this._getEditorId();
 +
 +    // add any available settings
 +    const settingsContainer = document.getElementById(`settings_${id}`);
 +    if (settingsContainer) {
 +      settingsContainer
 +        .querySelectorAll("input, select, textarea")
 +        .forEach((element: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement) => {
 +          if (element.nodeName === "INPUT" && (element.type === "checkbox" || element.type === "radio")) {
 +            if (!(element as HTMLInputElement).checked) {
 +              return;
 +            }
 +          }
 +
 +          const name = element.name;
 +          if (Object.prototype.hasOwnProperty.call(parameters, name)) {
 +            throw new Error(`Variable overshadowing, key '${name}' is already present.`);
 +          }
 +
 +          parameters[name] = element.value.trim();
 +        });
 +    }
 +
 +    EventHandler.fire("com.woltlab.wcf.redactor2", `getText_${id}`, parameters.data);
 +
 +    let validateResult: unknown = this._validate(parameters);
 +
 +    // Legacy validation methods returned a plain boolean.
 +    if (!(validateResult instanceof Promise)) {
 +      if (validateResult === false) {
 +        validateResult = Promise.reject();
 +      } else {
 +        validateResult = Promise.resolve();
 +      }
 +    }
 +
 +    (validateResult as Promise<void[]>).then(
 +      () => {
 +        EventHandler.fire("com.woltlab.wcf.redactor2", `submit_${id}`, parameters);
 +
 +        Ajax.api(this, {
 +          actionName: "save",
 +          parameters: parameters,
 +        });
 +
 +        this._hideEditor();
 +      },
 +      (e) => {
 +        const errorMessage = (e as Error).message;
 +        console.log(`Validation of post edit failed: ${errorMessage}`);
 +      },
 +    );
 +  }
 +
 +  /**
 +   * Validates the message and invokes listeners to perform additional validation.
 +   */
 +  protected _validate(parameters: ArbitraryObject): Promise<void[]> {
 +    // remove all existing error elements
 +    this._activeElement!.querySelectorAll(".innerError").forEach((el) => el.remove());
 +
 +    const data: ValidationData = {
 +      api: this,
 +      parameters: parameters,
 +      valid: true,
 +      promises: [],
 +    };
 +
 +    EventHandler.fire("com.woltlab.wcf.redactor2", `validate_${this._getEditorId()}`, data);
 +
 +    if (data.valid) {
 +      data.promises.push(Promise.resolve());
 +    } else {
 +      data.promises.push(Promise.reject());
 +    }
 +
 +    return Promise.all(data.promises);
 +  }
 +
 +  /**
 +   * Throws an error by showing an inline error for the target element.
 +   */
 +  throwError(element: HTMLElement, message: string): void {
 +    DomUtil.innerError(element, message);
 +  }
 +
 +  /**
 +   * Shows the update message.
 +   */
 +  protected _showMessage(data: AjaxResponseMessage): void {
 +    const activeElement = this._activeElement!;
 +    const editorId = this._getEditorId();
 +    const elementData = this._elements.get(activeElement)!;
 +
 +    // set new content
 +    DomUtil.setInnerHtml(elementData.messageBody.querySelector(".messageText")!, data.returnValues.message);
 +
 +    // handle attachment list
 +    if (typeof data.returnValues.attachmentList === "string") {
 +      elementData.messageFooter
 +        .querySelectorAll(".attachmentThumbnailList, .attachmentFileList")
 +        .forEach((el) => el.remove());
 +
 +      const element = document.createElement("div");
 +      DomUtil.setInnerHtml(element, data.returnValues.attachmentList);
 +
 +      let node;
 +      while (element.childNodes.length) {
 +        node = element.childNodes[element.childNodes.length - 1];
 +        elementData.messageFooter.insertBefore(node, elementData.messageFooter.firstChild);
 +      }
 +    }
 +
 +    if (typeof data.returnValues.poll === "string") {
 +      const poll = elementData.messageBody.querySelector(".pollContainer");
 +      if (poll !== null) {
 +        // The poll container is wrapped inside `.jsInlineEditorHideContent`.
 +        poll.parentElement!.remove();
 +      }
 +
-       elementData.messageBody.insertAdjacentElement("afterbegin", pollContainer);
++      if (data.returnValues.poll !== "") {
++        const pollContainer = document.createElement("div");
++        pollContainer.className = "jsInlineEditorHideContent";
++        DomUtil.setInnerHtml(pollContainer, data.returnValues.poll);
 +
++        elementData.messageBody.insertAdjacentElement("afterbegin", pollContainer);
++      }
 +    }
 +
 +    this._restoreMessage();
 +
 +    this._updateHistory(this._getHash(this._getObjectId(activeElement)));
 +
 +    EventHandler.fire("com.woltlab.wcf.redactor", `autosaveDestroy_${editorId}`);
 +
 +    UiNotification.show();
 +
 +    if (this._options.quoteManager) {
 +      this._options.quoteManager.clearAlternativeEditor();
 +      this._options.quoteManager.countQuotes();
 +    }
 +  }
 +
 +  /**
 +   * Hides the editor from view.
 +   */
 +  protected _hideEditor(): void {
 +    const elementData = this._elements.get(this._activeElement!)!;
 +    const editorContainer = elementData.messageBodyEditor!.querySelector(".editorContainer") as HTMLElement;
 +    DomUtil.hide(editorContainer);
 +
 +    const icon = document.createElement("span");
 +    icon.className = "icon icon48 fa-spinner";
 +    elementData.messageBodyEditor!.appendChild(icon);
 +  }
 +
 +  /**
 +   * Restores the previously hidden editor.
 +   */
 +  protected _restoreEditor(): void {
 +    const elementData = this._elements.get(this._activeElement!)!;
 +    const messageBodyEditor = elementData.messageBodyEditor!;
 +
 +    const icon = messageBodyEditor.querySelector(".fa-spinner") as HTMLElement;
 +    icon.remove();
 +
 +    const editorContainer = messageBodyEditor.querySelector(".editorContainer") as HTMLElement;
 +    if (editorContainer !== null) {
 +      DomUtil.show(editorContainer);
 +    }
 +  }
 +
 +  /**
 +   * Destroys the editor instance.
 +   */
 +  protected _destroyEditor(): void {
 +    EventHandler.fire("com.woltlab.wcf.redactor2", `autosaveDestroy_${this._getEditorId()}`);
 +    EventHandler.fire("com.woltlab.wcf.redactor2", `destroy_${this._getEditorId()}`);
 +  }
 +
 +  /**
 +   * Returns the hash added to the url after successfully editing a message.
 +   */
 +  protected _getHash(objectId: string): string {
 +    return `#message${objectId}`;
 +  }
 +
 +  /**
 +   * Updates the history to avoid old content when going back in the browser
 +   * history.
 +   */
 +  protected _updateHistory(hash: string): void {
 +    window.location.hash = hash;
 +  }
 +
 +  /**
 +   * Returns the unique editor id.
 +   */
 +  protected _getEditorId(): string {
 +    return this._options.editorPrefix + this._getObjectId(this._activeElement!).toString();
 +  }
 +
 +  /**
 +   * Returns the element's `data-object-id` value.
 +   */
 +  protected _getObjectId(element: HTMLElement): string {
 +    return element.dataset.objectId || "";
 +  }
 +
 +  _ajaxFailure(data: ResponseData): boolean {
 +    const elementData = this._elements.get(this._activeElement!)!;
 +    const editor = elementData.messageBodyEditor!.querySelector(".redactor-layer") as HTMLElement;
 +
 +    // handle errors occurring on editor load
 +    if (editor === null) {
 +      this._restoreMessage();
 +
 +      return true;
 +    }
 +
 +    this._restoreEditor();
 +
 +    if (!data || data.returnValues === undefined || data.returnValues.realErrorMessage === undefined) {
 +      return true;
 +    }
 +
 +    DomUtil.innerError(editor, data.returnValues.realErrorMessage);
 +
 +    return false;
 +  }
 +
 +  _ajaxSuccess(data: ResponseData): void {
 +    switch (data.actionName) {
 +      case "beginEdit":
 +        this._showEditor(data as AjaxResponseEditor);
 +        break;
 +
 +      case "save":
 +        this._showMessage(data as AjaxResponseMessage);
 +        break;
 +    }
 +  }
 +
 +  _ajaxSetup(): ReturnType<AjaxCallbackSetup> {
 +    return {
 +      data: {
 +        className: this._options.className,
 +        interfaceName: "wcf\\data\\IMessageInlineEditorAction",
 +      },
 +      silent: true,
 +    };
 +  }
 +
 +  /** @deprecated  3.0 - used only for backward compatibility with `WCF.Message.InlineEditor` */
 +  legacyEdit(containerId: string): void {
 +    this._click(document.getElementById(containerId), null);
 +  }
 +}
 +
 +Core.enableLegacyInheritance(UiMessageInlineEditor);
 +
 +export = UiMessageInlineEditor;
index 45d9899da371feff76b18a288f2c2412551b3dcb,bf23421778dbb3cf071dd880326154963cb9bb19..dd8b174c089228968f413ffcf0e7fc52049b3abd
  /**
   * Flexible message inline editor.
 - * 
 - * @author    Alexander Ebert
 - * @copyright 2001-2021 WoltLab GmbH
 - * @license   GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 - * @module    WoltLabSuite/Core/Ui/Message/InlineEditor
 + *
 + * @author  Alexander Ebert
-  * @copyright  2001-2019 WoltLab GmbH
++ * @copyright  2001-2021 WoltLab GmbH
 + * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 + * @module  WoltLabSuite/Core/Ui/Message/InlineEditor
   */
 -define(
 -      [
 -              'Ajax',         'Core',            'Dictionary',          'Environment',
 -              'EventHandler', 'Language',        'ObjectMap',           'Dom/ChangeListener', 'Dom/Traverse',
 -              'Dom/Util',     'Ui/Notification', 'Ui/ReusableDropdown', 'WoltLabSuite/Core/Ui/Scroll'
 -      ],
 -      function(
 -              Ajax,            Core,              Dictionary,            Environment,
 -              EventHandler,    Language,          ObjectMap,             DomChangeListener,    DomTraverse,
 -              DomUtil,         UiNotification,    UiReusableDropdown,    UiScroll
 -      )
 -{
 -      "use strict";
 -      
 -      if (!COMPILER_TARGET_DEFAULT) {
 -              var Fake = function() {};
 -              Fake.prototype = {
 -                      init: function() {},
 -                      rebuild: function() {},
 -                      _click: function() {},
 -                      _clickDropdown: function() {},
 -                      _dropdownBuild: function() {},
 -                      _dropdownToggle: function() {},
 -                      _dropdownGetItems: function() {},
 -                      _dropdownOpen: function() {},
 -                      _dropdownSelect: function() {},
 -                      _clickDropdownItem: function() {},
 -                      _prepare: function() {},
 -                      _showEditor: function() {},
 -                      _restoreMessage: function() {},
 -                      _save: function() {},
 -                      _validate: function() {},
 -                      throwError: function() {},
 -                      _showMessage: function() {},
 -                      _hideEditor: function() {},
 -                      _restoreEditor: function() {},
 -                      _destroyEditor: function() {},
 -                      _getHash: function() {},
 -                      _updateHistory: function() {},
 -                      _getEditorId: function() {},
 -                      _getObjectId: function() {},
 -                      _ajaxFailure: function() {},
 -                      _ajaxSuccess: function() {},
 -                      _ajaxSetup: function() {},
 -                      legacyEdit: function() {}
 -              };
 -              return Fake;
 -      }
 -      
 -      /**
 -       * @constructor
 -       */
 -      function UiMessageInlineEditor(options) { this.init(options); }
 -      UiMessageInlineEditor.prototype = {
 -              /**
 -               * Initializes the message inline editor.
 -               * 
 -               * @param       {Object}        options         list of configuration options
 -               */
 -              init: function(options) {
 -                      this._activeDropdownElement = null;
 -                      this._activeElement = null;
 -                      this._dropdownMenu = null;
 -                      this._elements = new ObjectMap();
 -                      this._options = Core.extend({
 -                              canEditInline: false,
 -                              
 -                              className: '',
 -                              containerId: 0,
 -                              dropdownIdentifier: '',
 -                              editorPrefix: 'messageEditor',
 -                              
 -                              messageSelector: '.jsMessage',
 -                              
 -                              quoteManager: null
 -                      }, options);
 -                      
 -                      this.rebuild();
 -                      
 -                      DomChangeListener.add('Ui/Message/InlineEdit_' + this._options.className, this.rebuild.bind(this));
 -              },
 -              
 -              /**
 -               * Initializes each applicable message, should be called whenever new
 -               * messages are being displayed.
 -               */
 -              rebuild: function() {
 -                      var button, canEdit, element, elements = elBySelAll(this._options.messageSelector);
 -                      
 -                      for (var i = 0, length = elements.length; i < length; i++) {
 -                              element = elements[i];
 -                              if (this._elements.has(element)) {
 -                                      continue;
 -                              }
 -                              
 -                              button = elBySel('.jsMessageEditButton', element);
 -                              if (button !== null) {
 -                                      canEdit = elDataBool(element, 'can-edit');
 -                                      
 -                                      if (this._options.canEditInline || elDataBool(element, 'can-edit-inline')) {
 -                                              button.addEventListener(WCF_CLICK_EVENT, this._clickDropdown.bind(this, element));
 -                                              button.classList.add('jsDropdownEnabled');
 -                                              
 -                                              if (canEdit) {
 -                                                      button.addEventListener('dblclick', this._click.bind(this, element));
 -                                              }
 -                                      }
 -                                      else if (canEdit) {
 -                                              button.addEventListener(WCF_CLICK_EVENT, this._click.bind(this, element));
 -                                      }
 -                              }
 -                              
 -                              var messageBody = elBySel('.messageBody', element);
 -                              var messageFooter = elBySel('.messageFooter', element);
 -                              var messageHeader = elBySel('.messageHeader', element);
 -                              
 -                              this._elements.set(element, {
 -                                      button: button,
 -                                      messageBody: messageBody,
 -                                      messageBodyEditor: null,
 -                                      messageFooter: messageFooter,
 -                                      messageFooterButtons: elBySel('.messageFooterButtons', messageFooter),
 -                                      messageHeader: messageHeader,
 -                                      messageText: elBySel('.messageText', messageBody)
 -                              });
 -                      }
 -              },
 -              
 -              /**
 -               * Handles clicks on the edit button or the edit dropdown item.
 -               * 
 -               * @param       {Element}       element         message element
 -               * @param       {?Event}        event           event object
 -               * @protected
 -               */
 -              _click: function(element, event) {
 -                      if (element === null) element = this._activeDropdownElement;
 -                      if (event) event.preventDefault();
 -                      
 -                      if (this._activeElement === null) {
 -                              this._activeElement = element;
 -                              
 -                              this._prepare();
 -                              
 -                              Ajax.api(this, {
 -                                      actionName: 'beginEdit',
 -                                      parameters: {
 -                                              containerID: this._options.containerId,
 -                                              objectID: this._getObjectId(element)
 -                                      }
 -                              });
 -                      }
 -                      else {
 -                              UiNotification.show('wcf.message.error.editorAlreadyInUse', null, 'warning');
 -                      }
 -              },
 -              
 -              /**
 -               * Creates and opens the dropdown on first usage.
 -               * 
 -               * @param       {Element}       element         message element
 -               * @param       {Object}        event           event object
 -               * @protected
 -               */
 -              _clickDropdown: function(element, event) {
 -                      event.preventDefault();
 -                      
 -                      var button = event.currentTarget;
 -                      if (button.classList.contains('dropdownToggle')) {
 -                              return;
 -                      }
 -                      
 -                      button.classList.add('dropdownToggle');
 -                      button.parentNode.classList.add('dropdown');
 -                      (function(button, element) {
 -                              button.addEventListener(WCF_CLICK_EVENT, (function(event) {
 -                                      event.preventDefault();
 -                                      event.stopPropagation();
 -                                      
 -                                      this._activeDropdownElement = element;
 -                                      UiReusableDropdown.toggleDropdown(this._options.dropdownIdentifier, button);
 -                              }).bind(this));
 -                      }).bind(this)(button, element);
 -                      
 -                      // build dropdown
 -                      if (this._dropdownMenu === null) {
 -                              this._dropdownMenu = elCreate('ul');
 -                              this._dropdownMenu.className = 'dropdownMenu';
 -                              
 -                              var items = this._dropdownGetItems();
 -                              
 -                              EventHandler.fire('com.woltlab.wcf.inlineEditor', 'dropdownInit_' + this._options.dropdownIdentifier, {
 -                                      items: items
 -                              });
 -                              
 -                              this._dropdownBuild(items);
 -                              
 -                              UiReusableDropdown.init(this._options.dropdownIdentifier, this._dropdownMenu);
 -                              UiReusableDropdown.registerCallback(this._options.dropdownIdentifier, this._dropdownToggle.bind(this));
 -                      }
 -                      
 -                      setTimeout(function() {
 -                              Core.triggerEvent(button, WCF_CLICK_EVENT);
 -                      }, 10);
 -              },
 -              
 -              /**
 -               * Creates the dropdown menu on first usage.
 -               * 
 -               * @param       {Object}        items   list of dropdown items
 -               * @protected
 -               */
 -              _dropdownBuild: function(items) {
 -                      var item, label, listItem;
 -                      var callbackClick = this._clickDropdownItem.bind(this);
 -                      
 -                      for (var i = 0, length = items.length; i < length; i++) {
 -                              item = items[i];
 -                              listItem = elCreate('li');
 -                              elData(listItem, 'item', item.item);
 -                              
 -                              if (item.item === 'divider') {
 -                                      listItem.className = 'dropdownDivider';
 -                              }
 -                              else {
 -                                      label = elCreate('span');
 -                                      label.textContent = Language.get(item.label);
 -                                      listItem.appendChild(label);
 -                                      
 -                                      if (item.item === 'editItem') {
 -                                              listItem.addEventListener(WCF_CLICK_EVENT, this._click.bind(this, null));
 -                                      }
 -                                      else {
 -                                              listItem.addEventListener(WCF_CLICK_EVENT, callbackClick);
 -                                      }
 -                              }
 -                              
 -                              this._dropdownMenu.appendChild(listItem);
 -                      }
 -              },
 -              
 -              /**
 -               * Callback for dropdown toggle.
 -               * 
 -               * @param       {int}           containerId     container id
 -               * @param       {string}        action          toggle action, either 'open' or 'close'
 -               * @protected
 -               */
 -              _dropdownToggle: function(containerId, action) {
 -                      var elementData = this._elements.get(this._activeDropdownElement);
 -                      elementData.button.parentNode.classList[(action === 'open' ? 'add' : 'remove')]('dropdownOpen');
 -                      elementData.messageFooterButtons.classList[(action === 'open' ? 'add' : 'remove')]('forceVisible');
 -                      
 -                      if (action === 'open') {
 -                              var visibility = this._dropdownOpen();
 -                              
 -                              EventHandler.fire('com.woltlab.wcf.inlineEditor', 'dropdownOpen_' + this._options.dropdownIdentifier, {
 -                                      element: this._activeDropdownElement,
 -                                      visibility: visibility
 -                              });
 -                              
 -                              var item, listItem, visiblePredecessor = false;
 -                              for (var i = 0; i < this._dropdownMenu.childElementCount; i++) {
 -                                      listItem = this._dropdownMenu.children[i];
 -                                      item = elData(listItem, 'item');
 -                                      
 -                                      if (item === 'divider') {
 -                                              if (visiblePredecessor) {
 -                                                      elShow(listItem);
 -                                                      
 -                                                      visiblePredecessor = false;
 -                                              }
 -                                              else {
 -                                                      elHide(listItem);
 -                                              }
 -                                      }
 -                                      else {
 -                                              if (objOwns(visibility, item) && visibility[item] === false) {
 -                                                      elHide(listItem);
 -                                                      
 -                                                      // check if previous item was a divider
 -                                                      if (i > 0 && i + 1 === this._dropdownMenu.childElementCount) {
 -                                                              if (elData(listItem.previousElementSibling, 'item') === 'divider') {
 -                                                                      elHide(listItem.previousElementSibling);
 -                                                              }
 -                                                      }
 -                                              }
 -                                              else {
 -                                                      elShow(listItem);
 -                                                      
 -                                                      visiblePredecessor = true;
 -                                              }
 -                                      }
 -                              }
 -                      }
 -              },
 -              
 -              /**
 -               * Returns the list of dropdown items for this type.
 -               * 
 -               * @return      {Array<Object>}         list of objects containing the type name and label
 -               * @protected
 -               */
 -              _dropdownGetItems: function() {},
 -              
 -              /**
 -               * Invoked once the dropdown for this type is shown, expects a list of type name and a boolean value
 -               * to represent the visibility of each item. Items that do not appear in this list will be considered
 -               * visible.
 -               * 
 -               * @return      {Object<string, boolean>}
 -               * @protected
 -               */
 -              _dropdownOpen: function() {},
 -              
 -              /**
 -               * Invoked whenever the user selects an item from the dropdown menu, the selected item is passed as argument.
 -               * 
 -               * @param       {string}        item    selected dropdown item
 -               * @protected
 -               */
 -              _dropdownSelect: function(item) {},
 -              
 -              /**
 -               * Handles clicks on a dropdown item.
 -               * 
 -               * @param       {Event}         event   event object
 -               * @protected
 -               */
 -              _clickDropdownItem: function(event) {
 -                      event.preventDefault();
 -                      
 -                      //noinspection JSCheckFunctionSignatures
 -                      var item = elData(event.currentTarget, 'item');
 -                      var data = {
 -                              cancel: false,
 -                              element: this._activeDropdownElement,
 -                              item: item
 -                      };
 -                      EventHandler.fire('com.woltlab.wcf.inlineEditor', 'dropdownItemClick_' + this._options.dropdownIdentifier, data);
 -                      
 -                      if (data.cancel === true) {
 -                              event.preventDefault();
 -                      }
 -                      else {
 -                              this._dropdownSelect(item);
 -                      }
 -              },
 -              
 -              /**
 -               * Prepares the message for editor display.
 -               * 
 -               * @protected
 -               */
 -              _prepare: function() {
 -                      var data = this._elements.get(this._activeElement);
 -                      
 -                      var messageBodyEditor = elCreate('div');
 -                      messageBodyEditor.className = 'messageBody editor';
 -                      data.messageBodyEditor = messageBodyEditor;
 -                      
 -                      var icon = elCreate('span');
 -                      icon.className = 'icon icon48 fa-spinner';
 -                      messageBodyEditor.appendChild(icon);
 -                      
 -                      DomUtil.insertAfter(messageBodyEditor, data.messageBody);
 -                      
 -                      elHide(data.messageBody);
 -              },
 -              
 -              /**
 -               * Shows the message editor.
 -               * 
 -               * @param       {Object}        data            ajax response data
 -               * @protected
 -               */
 -              _showEditor: function(data) {
 -                      var id = this._getEditorId();
 -                      var elementData = this._elements.get(this._activeElement);
 -                      
 -                      this._activeElement.classList.add('jsInvalidQuoteTarget');
 -                      var icon = DomTraverse.childByClass(elementData.messageBodyEditor, 'icon');
 -                      elRemove(icon);
 -                      
 -                      var messageBody = elementData.messageBodyEditor;
 -                      var editor = elCreate('div');
 -                      editor.className = 'editorContainer';
 -                      //noinspection JSUnresolvedVariable
 -                      DomUtil.setInnerHtml(editor, data.returnValues.template);
 -                      messageBody.appendChild(editor);
 -                      
 -                      // bind buttons
 -                      var formSubmit = elBySel('.formSubmit', editor);
 -                      
 -                      var buttonSave = elBySel('button[data-type="save"]', formSubmit);
 -                      buttonSave.addEventListener(WCF_CLICK_EVENT, this._save.bind(this));
 -                      
 -                      var buttonCancel = elBySel('button[data-type="cancel"]', formSubmit);
 -                      buttonCancel.addEventListener(WCF_CLICK_EVENT, this._restoreMessage.bind(this));
 -                      
 -                      EventHandler.add('com.woltlab.wcf.redactor', 'submitEditor_' + id, (function(data) {
 -                              data.cancel = true;
 -                              
 -                              this._save();
 -                      }).bind(this));
 -                      
 -                      // hide message header and footer
 -                      elHide(elementData.messageHeader);
 -                      elHide(elementData.messageFooter);
 -                      
 -                      var editorElement = elById(id);
 -                      if (Environment.editor() === 'redactor') {
 -                              window.setTimeout((function() {
 -                                      if (this._options.quoteManager) {
 -                                              this._options.quoteManager.setAlternativeEditor(id);
 -                                      }
 -                                      
 -                                      UiScroll.element(this._activeElement);
 -                              }).bind(this), 250);
 -                      }
 -                      else {
 -                              editorElement.focus();
 -                      }
 -              },
 -              
 -              /**
 -               * Restores the message view.
 -               * 
 -               * @protected
 -               */
 -              _restoreMessage: function() {
 -                      var elementData = this._elements.get(this._activeElement);
 -                      
 -                      this._destroyEditor();
 -                      
 -                      elRemove(elementData.messageBodyEditor);
 -                      elementData.messageBodyEditor = null;
 -                      
 -                      elShow(elementData.messageBody);
 -                      elShow(elementData.messageFooter);
 -                      elShow(elementData.messageHeader);
 -                      this._activeElement.classList.remove('jsInvalidQuoteTarget');
 -                      
 -                      this._activeElement = null;
 -                      
 -                      if (this._options.quoteManager) {
 -                              this._options.quoteManager.clearAlternativeEditor();
 -                      }
 -              },
 -              
 -              /**
 -               * Saves the editor message.
 -               * 
 -               * @protected
 -               */
 -              _save: function() {
 -                      var parameters = {
 -                              containerID: this._options.containerId,
 -                              data: {
 -                                      message: ''
 -                              },
 -                              objectID: this._getObjectId(this._activeElement),
 -                              removeQuoteIDs: (this._options.quoteManager) ? this._options.quoteManager.getQuotesMarkedForRemoval() : []
 -                      };
 -                      
 -                      var id = this._getEditorId();
 -                      
 -                      // add any available settings
 -                      var settingsContainer = elById('settings_' + id);
 -                      if (settingsContainer) {
 -                              elBySelAll('input, select, textarea', settingsContainer, function (element) {
 -                                      if (element.nodeName === 'INPUT' && (element.type === 'checkbox' || element.type === 'radio')) {
 -                                              if (!element.checked) {
 -                                                      return;
 -                                              }
 -                                      }
 -                                      
 -                                      var name = element.name;
 -                                      if (parameters.hasOwnProperty(name)) {
 -                                              throw new Error("Variable overshadowing, key '" + name + "' is already present.");
 -                                      }
 -                                      
 -                                      parameters[name] = element.value.trim();
 -                              });
 -                      }
 -                      
 -                      EventHandler.fire('com.woltlab.wcf.redactor2', 'getText_' + id, parameters.data);
 -                      
 -                      var validateResult = this._validate(parameters);
 -                      
 -                      if (!(validateResult instanceof Promise)) {
 -                              if (validateResult === false) {
 -                                      validateResult = Promise.reject();
 -                              }
 -                              else {
 -                                      validateResult = Promise.resolve();
 -                              }
 -                      }
 -                      
 -                      validateResult.then(function () {
 -                              EventHandler.fire('com.woltlab.wcf.redactor2', 'submit_' + id, parameters);
 -                              
 -                              Ajax.api(this, {
 -                                      actionName: 'save',
 -                                      parameters: parameters
 -                              });
 -                              
 -                              this._hideEditor();
 -                      }.bind(this), function(e) {
 -                              console.log('Validation of post edit failed: '+ e);
 -                      });
 -              },
 -              
 -              /**
 -               * Validates the message and invokes listeners to perform additional validation.
 -               *
 -               * @param       {Object}        parameters      request parameters
 -               * @return      {boolean}       validation result
 -               * @protected
 -               */
 -              _validate: function(parameters) {
 -                      // remove all existing error elements
 -                      elBySelAll('.innerError', this._activeElement, elRemove);
 -                      
 -                      var data = {
 -                              api: this,
 -                              parameters: parameters,
 -                              valid: true,
 -                              promises: []
 -                      };
 -                      
 -                      EventHandler.fire('com.woltlab.wcf.redactor2', 'validate_' + this._getEditorId(), data);
 -                      
 -                      data.promises.push(Promise[data.valid ? 'resolve' : 'reject']());
 -                      
 -                      return Promise.all(data.promises);
 -              },
 -              
 -              /**
 -               * Throws an error by adding an inline error to target element.
 -               *
 -               * @param       {Element}       element         erroneous element
 -               * @param       {string}        message         error message
 -               */
 -              throwError: function(element, message) {
 -                      elInnerError(element, message);
 -              },
 -              
 -              /**
 -               * Shows the update message.
 -               * 
 -               * @param       {Object}        data            ajax response data
 -               * @protected
 -               */
 -              _showMessage: function(data) {
 -                      var activeElement = this._activeElement;
 -                      var editorId = this._getEditorId();
 -                      var elementData = this._elements.get(activeElement);
 -                      var attachmentLists = elBySelAll('.attachmentThumbnailList, .attachmentFileList', elementData.messageFooter);
 -                      
 -                      // set new content
 -                      //noinspection JSUnresolvedVariable
 -                      DomUtil.setInnerHtml(DomTraverse.childByClass(elementData.messageBody, 'messageText'), data.returnValues.message);
 -                      
 -                      // handle attachment list
 -                      //noinspection JSUnresolvedVariable
 -                      if (typeof data.returnValues.attachmentList === 'string') {
 -                              for (var i = 0, length = attachmentLists.length; i < length; i++) {
 -                                      elRemove(attachmentLists[i]);
 -                              }
 -                              
 -                              var element = elCreate('div');
 -                              //noinspection JSUnresolvedVariable
 -                              DomUtil.setInnerHtml(element, data.returnValues.attachmentList);
 -                              
 -                              var node;
 -                              while (element.childNodes.length) {
 -                                      node = element.childNodes[element.childNodes.length - 1];
 -                                      elementData.messageFooter.insertBefore(node, elementData.messageFooter.firstChild);
 -                              }
 -                      }
 -                      
 -                      // handle poll
 -                      //noinspection JSUnresolvedVariable
 -                      if (typeof data.returnValues.poll === 'string') {
 -                              // find current poll
 -                              var poll = elBySel('.pollContainer', elementData.messageBody);
 -                              if (poll !== null) {
 -                                      // poll content is wrapped inside `.jsInlineEditorHideContent`
 -                                      elRemove(poll.parentNode);
 -                              }
 -                              
 -                              if (data.returnValues.poll !== "") {
 -                                      var pollContainer = elCreate('div');
 -                                      pollContainer.className = 'jsInlineEditorHideContent';
 -                                      //noinspection JSUnresolvedVariable
 -                                      DomUtil.setInnerHtml(pollContainer, data.returnValues.poll);
 -                                      
 -                                      DomUtil.prepend(pollContainer, elementData.messageBody);
 -                              }
 -                      }
 -                      
 -                      this._restoreMessage();
 -                      
 -                      this._updateHistory(this._getHash(this._getObjectId(activeElement)));
 -                      
 -                      EventHandler.fire('com.woltlab.wcf.redactor', 'autosaveDestroy_' + editorId);
 -                      
 -                      UiNotification.show();
 -                      
 -                      if (this._options.quoteManager) {
 -                              this._options.quoteManager.clearAlternativeEditor();
 -                              this._options.quoteManager.countQuotes();
 -                      }
 -              },
 -              
 -              /**
 -               * Hides the editor from view.
 -               * 
 -               * @protected
 -               */
 -              _hideEditor: function() {
 -                      var elementData = this._elements.get(this._activeElement);
 -                      elHide(DomTraverse.childByClass(elementData.messageBodyEditor, 'editorContainer'));
 -                      
 -                      var icon = elCreate('span');
 -                      icon.className = 'icon icon48 fa-spinner';
 -                      elementData.messageBodyEditor.appendChild(icon);
 -              },
 -              
 -              /**
 -               * Restores the previously hidden editor.
 -               * 
 -               * @protected
 -               */
 -              _restoreEditor: function() {
 -                      var elementData = this._elements.get(this._activeElement);
 -                      var icon = elBySel('.fa-spinner', elementData.messageBodyEditor);
 -                      elRemove(icon);
 -                      
 -                      var editorContainer = DomTraverse.childByClass(elementData.messageBodyEditor, 'editorContainer');
 -                      if (editorContainer !== null) elShow(editorContainer);
 -              },
 -              
 -              /**
 -               * Destroys the editor instance.
 -               * 
 -               * @protected
 -               */
 -              _destroyEditor: function() {
 -                      EventHandler.fire('com.woltlab.wcf.redactor2', 'autosaveDestroy_' + this._getEditorId());
 -                      EventHandler.fire('com.woltlab.wcf.redactor2', 'destroy_' + this._getEditorId());
 -              },
 -              
 -              /**
 -               * Returns the hash added to the url after successfully editing a message.
 -               * 
 -               * @param       {int}   objectId        message object id
 -               * @return      string
 -               * @protected
 -               */
 -              _getHash: function(objectId) {
 -                      return '#message' + objectId;
 -              },
 -              
 -              /**
 -               * Updates the history to avoid old content when going back in the browser
 -               * history.
 -               * 
 -               * @param       {string}        hash    location hash
 -               * @protected
 -               */
 -              _updateHistory: function(hash) {
 -                      window.location.hash = hash;
 -              },
 -              
 -              /**
 -               * Returns the unique editor id.
 -               * 
 -               * @return      {string}        editor id
 -               * @protected
 -               */
 -              _getEditorId: function() {
 -                      return this._options.editorPrefix + this._getObjectId(this._activeElement);
 -              },
 -              
 -              /**
 -               * Returns the element's `data-object-id` value.
 -               * 
 -               * @param       {Element}       element         target element
 -               * @return      {int}
 -               * @protected
 -               */
 -              _getObjectId: function(element) {
 -                      return ~~elData(element, 'object-id');
 -              },
 -              
 -              _ajaxFailure: function(data) {
 -                      var elementData = this._elements.get(this._activeElement);
 -                      var editor = elBySel('.redactor-layer', elementData.messageBodyEditor);
 -                      
 -                      // handle errors occurring on editor load
 -                      if (editor === null) {
 -                              this._restoreMessage();
 -                              
 -                              return true;
 -                      }
 -                      
 -                      this._restoreEditor();
 -                      
 -                      //noinspection JSUnresolvedVariable
 -                      if (!data || data.returnValues === undefined || data.returnValues.realErrorMessage === undefined) {
 -                              return true;
 -                      }
 -                      
 -                      //noinspection JSUnresolvedVariable
 -                      elInnerError(editor, data.returnValues.realErrorMessage);
 -                      
 -                      return false;
 -              },
 -              
 -              _ajaxSuccess: function(data) {
 -                      switch (data.actionName) {
 -                              case 'beginEdit':
 -                                      this._showEditor(data);
 -                                      break;
 -                                      
 -                              case 'save':
 -                                      this._showMessage(data);
 -                                      break;
 -                      }
 -              },
 -              
 -              _ajaxSetup: function() {
 -                      return {
 -                              data: {
 -                                      className: this._options.className,
 -                                      interfaceName: 'wcf\\data\\IMessageInlineEditorAction'
 -                              },
 -                              silent: true
 -                      };
 -              },
 -              
 -              /** @deprecated 3.0 - used only for backward compatibility with `WCF.Message.InlineEditor` */
 -              legacyEdit: function(containerId) {
 -                      this._click(elById(containerId), null);
 -              }
 -      };
 -      
 -      return UiMessageInlineEditor;
 +define(["require", "exports", "tslib", "../../Ajax", "../../Core", "../../Dom/Change/Listener", "../../Dom/Util", "../../Environment", "../../Event/Handler", "../../Language", "../Dropdown/Reusable", "../Notification", "../Scroll"], function (require, exports, tslib_1, Ajax, Core, Listener_1, Util_1, Environment, EventHandler, Language, UiDropdownReusable, UiNotification, UiScroll) {
 +    "use strict";
 +    Ajax = tslib_1.__importStar(Ajax);
 +    Core = tslib_1.__importStar(Core);
 +    Listener_1 = tslib_1.__importDefault(Listener_1);
 +    Util_1 = tslib_1.__importDefault(Util_1);
 +    Environment = tslib_1.__importStar(Environment);
 +    EventHandler = tslib_1.__importStar(EventHandler);
 +    Language = tslib_1.__importStar(Language);
 +    UiDropdownReusable = tslib_1.__importStar(UiDropdownReusable);
 +    UiNotification = tslib_1.__importStar(UiNotification);
 +    UiScroll = tslib_1.__importStar(UiScroll);
 +    class UiMessageInlineEditor {
 +        /**
 +         * Initializes the message inline editor.
 +         */
 +        constructor(opts) {
 +            this.init(opts);
 +        }
 +        /**
 +         * Helper initialization method for legacy inheritance support.
 +         */
 +        init(opts) {
 +            // Define the properties again, the constructor might not be
 +            // called in legacy implementations.
 +            this._activeDropdownElement = null;
 +            this._activeElement = null;
 +            this._dropdownMenu = null;
 +            this._elements = new WeakMap();
 +            this._options = Core.extend({
 +                canEditInline: false,
 +                className: "",
 +                containerId: 0,
 +                dropdownIdentifier: "",
 +                editorPrefix: "messageEditor",
 +                messageSelector: ".jsMessage",
 +                quoteManager: null,
 +            }, opts);
 +            this.rebuild();
 +            Listener_1.default.add(`Ui/Message/InlineEdit_${this._options.className}`, () => this.rebuild());
 +        }
 +        /**
 +         * Initializes each applicable message, should be called whenever new
 +         * messages are being displayed.
 +         */
 +        rebuild() {
 +            document.querySelectorAll(this._options.messageSelector).forEach((element) => {
 +                if (this._elements.has(element)) {
 +                    return;
 +                }
 +                const button = element.querySelector(".jsMessageEditButton");
 +                if (button !== null) {
 +                    const canEdit = Core.stringToBool(element.dataset.canEdit || "");
 +                    const canEditInline = Core.stringToBool(element.dataset.canEditInline || "");
 +                    if (this._options.canEditInline || canEditInline) {
 +                        button.addEventListener("click", (ev) => this._clickDropdown(element, ev));
 +                        button.classList.add("jsDropdownEnabled");
 +                        if (canEdit) {
 +                            button.addEventListener("dblclick", (ev) => this._click(element, ev));
 +                        }
 +                    }
 +                    else if (canEdit) {
 +                        button.addEventListener("click", (ev) => this._click(element, ev));
 +                    }
 +                }
 +                const messageBody = element.querySelector(".messageBody");
 +                const messageFooter = element.querySelector(".messageFooter");
 +                const messageFooterButtons = messageFooter.querySelector(".messageFooterButtons");
 +                const messageHeader = element.querySelector(".messageHeader");
 +                const messageText = messageBody.querySelector(".messageText");
 +                this._elements.set(element, {
 +                    button,
 +                    messageBody,
 +                    messageBodyEditor: null,
 +                    messageFooter,
 +                    messageFooterButtons,
 +                    messageHeader,
 +                    messageText,
 +                });
 +            });
 +        }
 +        /**
 +         * Handles clicks on the edit button or the edit dropdown item.
 +         */
 +        _click(element, event) {
 +            if (element === null) {
 +                element = this._activeDropdownElement;
 +            }
 +            if (event) {
 +                event.preventDefault();
 +            }
 +            if (this._activeElement === null) {
 +                this._activeElement = element;
 +                this._prepare();
 +                Ajax.api(this, {
 +                    actionName: "beginEdit",
 +                    parameters: {
 +                        containerID: this._options.containerId,
 +                        objectID: this._getObjectId(element),
 +                    },
 +                });
 +            }
 +            else {
 +                UiNotification.show("wcf.message.error.editorAlreadyInUse", undefined, "warning");
 +            }
 +        }
 +        /**
 +         * Creates and opens the dropdown on first usage.
 +         */
 +        _clickDropdown(element, event) {
 +            event.preventDefault();
 +            const button = event.currentTarget;
 +            if (button.classList.contains("dropdownToggle")) {
 +                return;
 +            }
 +            button.classList.add("dropdownToggle");
 +            button.parentElement.classList.add("dropdown");
 +            button.addEventListener("click", (event) => {
 +                event.preventDefault();
 +                event.stopPropagation();
 +                this._activeDropdownElement = element;
 +                UiDropdownReusable.toggleDropdown(this._options.dropdownIdentifier, button);
 +            });
 +            // build dropdown
 +            if (this._dropdownMenu === null) {
 +                this._dropdownMenu = document.createElement("ul");
 +                this._dropdownMenu.className = "dropdownMenu";
 +                const items = this._dropdownGetItems();
 +                EventHandler.fire("com.woltlab.wcf.inlineEditor", `dropdownInit_${this._options.dropdownIdentifier}`, {
 +                    items: items,
 +                });
 +                this._dropdownBuild(items);
 +                UiDropdownReusable.init(this._options.dropdownIdentifier, this._dropdownMenu);
 +                UiDropdownReusable.registerCallback(this._options.dropdownIdentifier, (containerId, action) => this._dropdownToggle(containerId, action));
 +            }
 +            setTimeout(() => button.click(), 10);
 +        }
 +        /**
 +         * Creates the dropdown menu on first usage.
 +         */
 +        _dropdownBuild(items) {
 +            items.forEach((item) => {
 +                const listItem = document.createElement("li");
 +                listItem.dataset.item = item.item;
 +                if (item.item === "divider") {
 +                    listItem.className = "dropdownDivider";
 +                }
 +                else {
 +                    const label = document.createElement("span");
 +                    label.textContent = Language.get(item.label);
 +                    listItem.appendChild(label);
 +                    if (item.item === "editItem") {
 +                        listItem.addEventListener("click", (ev) => this._click(null, ev));
 +                    }
 +                    else {
 +                        listItem.addEventListener("click", (ev) => this._clickDropdownItem(ev));
 +                    }
 +                }
 +                this._dropdownMenu.appendChild(listItem);
 +            });
 +        }
 +        /**
 +         * Callback for dropdown toggle.
 +         */
 +        _dropdownToggle(containerId, action) {
 +            const elementData = this._elements.get(this._activeDropdownElement);
 +            const buttonParent = elementData.button.parentElement;
 +            if (action === "close") {
 +                buttonParent.classList.remove("dropdownOpen");
 +                elementData.messageFooterButtons.classList.remove("forceVisible");
 +                return;
 +            }
 +            buttonParent.classList.add("dropdownOpen");
 +            elementData.messageFooterButtons.classList.add("forceVisible");
 +            const visibility = new Map(Object.entries(this._dropdownOpen()));
 +            EventHandler.fire("com.woltlab.wcf.inlineEditor", `dropdownOpen_${this._options.dropdownIdentifier}`, {
 +                element: this._activeDropdownElement,
 +                visibility,
 +            });
 +            const dropdownMenu = this._dropdownMenu;
 +            let visiblePredecessor = false;
 +            const children = Array.from(dropdownMenu.children);
 +            children.forEach((listItem, index) => {
 +                const item = listItem.dataset.item;
 +                if (item === "divider") {
 +                    if (visiblePredecessor) {
 +                        Util_1.default.show(listItem);
 +                        visiblePredecessor = false;
 +                    }
 +                    else {
 +                        Util_1.default.hide(listItem);
 +                    }
 +                }
 +                else {
 +                    if (visibility.get(item) === false) {
 +                        Util_1.default.hide(listItem);
 +                        // check if previous item was a divider
 +                        if (index > 0 && index + 1 === children.length) {
 +                            const previousElementSibling = listItem.previousElementSibling;
 +                            if (previousElementSibling.dataset.item === "divider") {
 +                                Util_1.default.hide(previousElementSibling);
 +                            }
 +                        }
 +                    }
 +                    else {
 +                        Util_1.default.show(listItem);
 +                        visiblePredecessor = true;
 +                    }
 +                }
 +            });
 +        }
 +        /**
 +         * Returns the list of dropdown items for this type.
 +         */
 +        _dropdownGetItems() {
 +            // This should be an abstract method, but cannot be marked as such for backwards compatibility.
 +            return [];
 +        }
 +        /**
 +         * Invoked once the dropdown for this type is shown, expects a list of type name and a boolean value
 +         * to represent the visibility of each item. Items that do not appear in this list will be considered
 +         * visible.
 +         */
 +        _dropdownOpen() {
 +            // This should be an abstract method, but cannot be marked as such for backwards compatibility.
 +            return {};
 +        }
 +        /**
 +         * Invoked whenever the user selects an item from the dropdown menu, the selected item is passed as argument.
 +         */
 +        _dropdownSelect(_item) {
 +            // This should be an abstract method, but cannot be marked as such for backwards compatibility.
 +        }
 +        /**
 +         * Handles clicks on a dropdown item.
 +         */
 +        _clickDropdownItem(event) {
 +            event.preventDefault();
 +            const target = event.currentTarget;
 +            const item = target.dataset.item;
 +            const data = {
 +                cancel: false,
 +                element: this._activeDropdownElement,
 +                item,
 +            };
 +            EventHandler.fire("com.woltlab.wcf.inlineEditor", `dropdownItemClick_${this._options.dropdownIdentifier}`, data);
 +            if (data.cancel) {
 +                event.preventDefault();
 +            }
 +            else {
 +                this._dropdownSelect(item);
 +            }
 +        }
 +        /**
 +         * Prepares the message for editor display.
 +         */
 +        _prepare() {
 +            const data = this._elements.get(this._activeElement);
 +            const messageBodyEditor = document.createElement("div");
 +            messageBodyEditor.className = "messageBody editor";
 +            data.messageBodyEditor = messageBodyEditor;
 +            const icon = document.createElement("span");
 +            icon.className = "icon icon48 fa-spinner";
 +            messageBodyEditor.appendChild(icon);
 +            data.messageBody.insertAdjacentElement("afterend", messageBodyEditor);
 +            Util_1.default.hide(data.messageBody);
 +        }
 +        /**
 +         * Shows the message editor.
 +         */
 +        _showEditor(data) {
 +            const id = this._getEditorId();
 +            const activeElement = this._activeElement;
 +            const elementData = this._elements.get(activeElement);
 +            activeElement.classList.add("jsInvalidQuoteTarget");
 +            const icon = elementData.messageBodyEditor.querySelector(".icon");
 +            icon.remove();
 +            const messageBody = elementData.messageBodyEditor;
 +            const editor = document.createElement("div");
 +            editor.className = "editorContainer";
 +            Util_1.default.setInnerHtml(editor, data.returnValues.template);
 +            messageBody.appendChild(editor);
 +            // bind buttons
 +            const formSubmit = editor.querySelector(".formSubmit");
 +            const buttonSave = formSubmit.querySelector('button[data-type="save"]');
 +            buttonSave.addEventListener("click", () => this._save());
 +            const buttonCancel = formSubmit.querySelector('button[data-type="cancel"]');
 +            buttonCancel.addEventListener("click", () => this._restoreMessage());
 +            EventHandler.add("com.woltlab.wcf.redactor", `submitEditor_${id}`, (data) => {
 +                data.cancel = true;
 +                this._save();
 +            });
 +            // hide message header and footer
 +            Util_1.default.hide(elementData.messageHeader);
 +            Util_1.default.hide(elementData.messageFooter);
 +            if (Environment.editor() === "redactor") {
 +                window.setTimeout(() => {
 +                    if (this._options.quoteManager) {
 +                        this._options.quoteManager.setAlternativeEditor(id);
 +                    }
 +                    UiScroll.element(activeElement);
 +                }, 250);
 +            }
 +            else {
 +                const editorElement = document.getElementById(id);
 +                editorElement.focus();
 +            }
 +        }
 +        /**
 +         * Restores the message view.
 +         */
 +        _restoreMessage() {
 +            const activeElement = this._activeElement;
 +            const elementData = this._elements.get(activeElement);
 +            this._destroyEditor();
 +            elementData.messageBodyEditor.remove();
 +            elementData.messageBodyEditor = null;
 +            Util_1.default.show(elementData.messageBody);
 +            Util_1.default.show(elementData.messageFooter);
 +            Util_1.default.show(elementData.messageHeader);
 +            activeElement.classList.remove("jsInvalidQuoteTarget");
 +            this._activeElement = null;
 +            if (this._options.quoteManager) {
 +                this._options.quoteManager.clearAlternativeEditor();
 +            }
 +        }
 +        /**
 +         * Saves the editor message.
 +         */
 +        _save() {
 +            const parameters = {
 +                containerID: this._options.containerId,
 +                data: {
 +                    message: "",
 +                },
 +                objectID: this._getObjectId(this._activeElement),
 +                removeQuoteIDs: this._options.quoteManager ? this._options.quoteManager.getQuotesMarkedForRemoval() : [],
 +            };
 +            const id = this._getEditorId();
 +            // add any available settings
 +            const settingsContainer = document.getElementById(`settings_${id}`);
 +            if (settingsContainer) {
 +                settingsContainer
 +                    .querySelectorAll("input, select, textarea")
 +                    .forEach((element) => {
 +                    if (element.nodeName === "INPUT" && (element.type === "checkbox" || element.type === "radio")) {
 +                        if (!element.checked) {
 +                            return;
 +                        }
 +                    }
 +                    const name = element.name;
 +                    if (Object.prototype.hasOwnProperty.call(parameters, name)) {
 +                        throw new Error(`Variable overshadowing, key '${name}' is already present.`);
 +                    }
 +                    parameters[name] = element.value.trim();
 +                });
 +            }
 +            EventHandler.fire("com.woltlab.wcf.redactor2", `getText_${id}`, parameters.data);
 +            let validateResult = this._validate(parameters);
 +            // Legacy validation methods returned a plain boolean.
 +            if (!(validateResult instanceof Promise)) {
 +                if (validateResult === false) {
 +                    validateResult = Promise.reject();
 +                }
 +                else {
 +                    validateResult = Promise.resolve();
 +                }
 +            }
 +            validateResult.then(() => {
 +                EventHandler.fire("com.woltlab.wcf.redactor2", `submit_${id}`, parameters);
 +                Ajax.api(this, {
 +                    actionName: "save",
 +                    parameters: parameters,
 +                });
 +                this._hideEditor();
 +            }, (e) => {
 +                const errorMessage = e.message;
 +                console.log(`Validation of post edit failed: ${errorMessage}`);
 +            });
 +        }
 +        /**
 +         * Validates the message and invokes listeners to perform additional validation.
 +         */
 +        _validate(parameters) {
 +            // remove all existing error elements
 +            this._activeElement.querySelectorAll(".innerError").forEach((el) => el.remove());
 +            const data = {
 +                api: this,
 +                parameters: parameters,
 +                valid: true,
 +                promises: [],
 +            };
 +            EventHandler.fire("com.woltlab.wcf.redactor2", `validate_${this._getEditorId()}`, data);
 +            if (data.valid) {
 +                data.promises.push(Promise.resolve());
 +            }
 +            else {
 +                data.promises.push(Promise.reject());
 +            }
 +            return Promise.all(data.promises);
 +        }
 +        /**
 +         * Throws an error by showing an inline error for the target element.
 +         */
 +        throwError(element, message) {
 +            Util_1.default.innerError(element, message);
 +        }
 +        /**
 +         * Shows the update message.
 +         */
 +        _showMessage(data) {
 +            const activeElement = this._activeElement;
 +            const editorId = this._getEditorId();
 +            const elementData = this._elements.get(activeElement);
 +            // set new content
 +            Util_1.default.setInnerHtml(elementData.messageBody.querySelector(".messageText"), data.returnValues.message);
 +            // handle attachment list
 +            if (typeof data.returnValues.attachmentList === "string") {
 +                elementData.messageFooter
 +                    .querySelectorAll(".attachmentThumbnailList, .attachmentFileList")
 +                    .forEach((el) => el.remove());
 +                const element = document.createElement("div");
 +                Util_1.default.setInnerHtml(element, data.returnValues.attachmentList);
 +                let node;
 +                while (element.childNodes.length) {
 +                    node = element.childNodes[element.childNodes.length - 1];
 +                    elementData.messageFooter.insertBefore(node, elementData.messageFooter.firstChild);
 +                }
 +            }
 +            if (typeof data.returnValues.poll === "string") {
 +                const poll = elementData.messageBody.querySelector(".pollContainer");
 +                if (poll !== null) {
 +                    // The poll container is wrapped inside `.jsInlineEditorHideContent`.
 +                    poll.parentElement.remove();
 +                }
-                 const pollContainer = document.createElement("div");
-                 pollContainer.className = "jsInlineEditorHideContent";
-                 Util_1.default.setInnerHtml(pollContainer, data.returnValues.poll);
-                 elementData.messageBody.insertAdjacentElement("afterbegin", pollContainer);
++                if (data.returnValues.poll !== "") {
++                    const pollContainer = document.createElement("div");
++                    pollContainer.className = "jsInlineEditorHideContent";
++                    Util_1.default.setInnerHtml(pollContainer, data.returnValues.poll);
++                    elementData.messageBody.insertAdjacentElement("afterbegin", pollContainer);
++                }
 +            }
 +            this._restoreMessage();
 +            this._updateHistory(this._getHash(this._getObjectId(activeElement)));
 +            EventHandler.fire("com.woltlab.wcf.redactor", `autosaveDestroy_${editorId}`);
 +            UiNotification.show();
 +            if (this._options.quoteManager) {
 +                this._options.quoteManager.clearAlternativeEditor();
 +                this._options.quoteManager.countQuotes();
 +            }
 +        }
 +        /**
 +         * Hides the editor from view.
 +         */
 +        _hideEditor() {
 +            const elementData = this._elements.get(this._activeElement);
 +            const editorContainer = elementData.messageBodyEditor.querySelector(".editorContainer");
 +            Util_1.default.hide(editorContainer);
 +            const icon = document.createElement("span");
 +            icon.className = "icon icon48 fa-spinner";
 +            elementData.messageBodyEditor.appendChild(icon);
 +        }
 +        /**
 +         * Restores the previously hidden editor.
 +         */
 +        _restoreEditor() {
 +            const elementData = this._elements.get(this._activeElement);
 +            const messageBodyEditor = elementData.messageBodyEditor;
 +            const icon = messageBodyEditor.querySelector(".fa-spinner");
 +            icon.remove();
 +            const editorContainer = messageBodyEditor.querySelector(".editorContainer");
 +            if (editorContainer !== null) {
 +                Util_1.default.show(editorContainer);
 +            }
 +        }
 +        /**
 +         * Destroys the editor instance.
 +         */
 +        _destroyEditor() {
 +            EventHandler.fire("com.woltlab.wcf.redactor2", `autosaveDestroy_${this._getEditorId()}`);
 +            EventHandler.fire("com.woltlab.wcf.redactor2", `destroy_${this._getEditorId()}`);
 +        }
 +        /**
 +         * Returns the hash added to the url after successfully editing a message.
 +         */
 +        _getHash(objectId) {
 +            return `#message${objectId}`;
 +        }
 +        /**
 +         * Updates the history to avoid old content when going back in the browser
 +         * history.
 +         */
 +        _updateHistory(hash) {
 +            window.location.hash = hash;
 +        }
 +        /**
 +         * Returns the unique editor id.
 +         */
 +        _getEditorId() {
 +            return this._options.editorPrefix + this._getObjectId(this._activeElement).toString();
 +        }
 +        /**
 +         * Returns the element's `data-object-id` value.
 +         */
 +        _getObjectId(element) {
 +            return element.dataset.objectId || "";
 +        }
 +        _ajaxFailure(data) {
 +            const elementData = this._elements.get(this._activeElement);
 +            const editor = elementData.messageBodyEditor.querySelector(".redactor-layer");
 +            // handle errors occurring on editor load
 +            if (editor === null) {
 +                this._restoreMessage();
 +                return true;
 +            }
 +            this._restoreEditor();
 +            if (!data || data.returnValues === undefined || data.returnValues.realErrorMessage === undefined) {
 +                return true;
 +            }
 +            Util_1.default.innerError(editor, data.returnValues.realErrorMessage);
 +            return false;
 +        }
 +        _ajaxSuccess(data) {
 +            switch (data.actionName) {
 +                case "beginEdit":
 +                    this._showEditor(data);
 +                    break;
 +                case "save":
 +                    this._showMessage(data);
 +                    break;
 +            }
 +        }
 +        _ajaxSetup() {
 +            return {
 +                data: {
 +                    className: this._options.className,
 +                    interfaceName: "wcf\\data\\IMessageInlineEditorAction",
 +                },
 +                silent: true,
 +            };
 +        }
 +        /** @deprecated  3.0 - used only for backward compatibility with `WCF.Message.InlineEditor` */
 +        legacyEdit(containerId) {
 +            this._click(document.getElementById(containerId), null);
 +        }
 +    }
 +    Core.enableLegacyInheritance(UiMessageInlineEditor);
 +    return UiMessageInlineEditor;
  });