interface String {
hashCode: () => string;
}
+
+ type ArbitraryObject = Record<string, unknown>;
}
/**
* Flexible message inline editor.
*
- * @author Alexander Ebert
- * @copyright 2001-2019 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
+ * @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) {
+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";
- 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 = {
+ 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.
- *
- * @param {Object} options list of configuration options
*/
- init: function (options) {
+ constructor(opts) {
this._activeDropdownElement = null;
this._activeElement = null;
this._dropdownMenu = null;
- this._elements = new ObjectMap();
+ this._elements = new WeakMap();
this._options = Core.extend({
canEditInline: false,
- className: '',
+ className: "",
containerId: 0,
- dropdownIdentifier: '',
- editorPrefix: 'messageEditor',
- messageSelector: '.jsMessage',
- quoteManager: null
- }, options);
+ dropdownIdentifier: "",
+ editorPrefix: "messageEditor",
+ messageSelector: ".jsMessage",
+ quoteManager: null,
+ }, opts);
this.rebuild();
- DomChangeListener.add('Ui/Message/InlineEdit_' + this._options.className, this.rebuild.bind(this));
- },
+ 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: function () {
- var button, canEdit, element, elements = elBySelAll(this._options.messageSelector);
- for (var i = 0, length = elements.length; i < length; i++) {
- element = elements[i];
+ rebuild() {
+ document.querySelectorAll(this._options.messageSelector).forEach((element) => {
if (this._elements.has(element)) {
- continue;
+ return;
}
- button = elBySel('.jsMessageEditButton', element);
+ const button = element.querySelector(".jsMessageEditButton");
if (button !== null) {
- canEdit = elDataBool(element, 'can-edit');
- if (this._options.canEditInline || elDataBool(element, 'can-edit-inline')) {
- button.addEventListener('click', this._clickDropdown.bind(this, element));
- button.classList.add('jsDropdownEnabled');
+ 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', this._click.bind(this, element));
+ button.addEventListener("dblclick", (ev) => this._click(element, ev));
}
}
else if (canEdit) {
- button.addEventListener('click', this._click.bind(this, element));
+ button.addEventListener("click", (ev) => this._click(element, ev));
}
}
- var messageBody = elBySel('.messageBody', element);
- var messageFooter = elBySel('.messageFooter', element);
- var messageHeader = elBySel('.messageHeader', element);
+ 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: button,
- messageBody: messageBody,
+ button,
+ messageBody,
messageBodyEditor: null,
- messageFooter: messageFooter,
- messageFooterButtons: elBySel('.messageFooterButtons', messageFooter),
- messageHeader: messageHeader,
- messageText: elBySel('.messageText', messageBody)
+ messageFooter,
+ messageFooterButtons,
+ messageHeader,
+ messageText,
});
- }
- },
+ });
+ }
/**
* 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)
+ _click(element, event) {
+ if (element === null) {
element = this._activeDropdownElement;
- if (event)
+ }
+ if (event) {
event.preventDefault();
+ }
if (this._activeElement === null) {
this._activeElement = element;
this._prepare();
Ajax.api(this, {
- actionName: 'beginEdit',
+ actionName: "beginEdit",
parameters: {
containerID: this._options.containerId,
- objectID: this._getObjectId(element)
- }
+ objectID: this._getObjectId(element),
+ },
});
}
else {
- UiNotification.show('wcf.message.error.editorAlreadyInUse', null, 'warning');
+ UiNotification.show("wcf.message.error.editorAlreadyInUse", undefined, "warning");
}
- },
+ }
/**
* Creates and opens the dropdown on first usage.
- *
- * @param {Element} element message element
- * @param {Object} event event object
- * @protected
*/
- _clickDropdown: function (element, event) {
+ _clickDropdown(element, event) {
event.preventDefault();
- var button = event.currentTarget;
- if (button.classList.contains('dropdownToggle')) {
+ const button = event.currentTarget;
+ if (button.classList.contains("dropdownToggle")) {
return;
}
- button.classList.add('dropdownToggle');
- button.parentNode.classList.add('dropdown');
- (function (button, element) {
- button.addEventListener('click', (function (event) {
- event.preventDefault();
- event.stopPropagation();
- this._activeDropdownElement = element;
- UiReusableDropdown.toggleDropdown(this._options.dropdownIdentifier, button);
- }).bind(this));
- }).bind(this)(button, element);
+ 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 = elCreate('ul');
- this._dropdownMenu.className = 'dropdownMenu';
- var items = this._dropdownGetItems();
- EventHandler.fire('com.woltlab.wcf.inlineEditor', 'dropdownInit_' + this._options.dropdownIdentifier, {
- items: items
+ 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);
- UiReusableDropdown.init(this._options.dropdownIdentifier, this._dropdownMenu);
- UiReusableDropdown.registerCallback(this._options.dropdownIdentifier, this._dropdownToggle.bind(this));
+ UiDropdownReusable.init(this._options.dropdownIdentifier, this._dropdownMenu);
+ UiDropdownReusable.registerCallback(this._options.dropdownIdentifier, (containerId, action) => this._dropdownToggle(containerId, action));
}
- setTimeout(function () {
- Core.triggerEvent(button, 'click');
- }, 10);
- },
+ setTimeout(() => button.click(), 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';
+ _dropdownBuild(items) {
+ items.forEach((item) => {
+ const listItem = document.createElement("li");
+ listItem.dataset.item = item.item;
+ if (item.item === "divider") {
+ listItem.className = "dropdownDivider";
}
else {
- label = elCreate('span');
+ const label = document.createElement("span");
label.textContent = Language.get(item.label);
listItem.appendChild(label);
- if (item.item === 'editItem') {
- listItem.addEventListener('click', this._click.bind(this, null));
+ if (item.item === "editItem") {
+ listItem.addEventListener("click", (ev) => this._click(null, ev));
}
else {
- listItem.addEventListener('click', callbackClick);
+ listItem.addEventListener("click", (ev) => this._clickDropdownItem(ev));
}
}
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);
- }
+ _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 {
- 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);
- }
+ 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 {
- elShow(listItem);
- visiblePredecessor = true;
- }
+ }
+ else {
+ Util_1.default.show(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 () { },
+ _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.
- *
- * @return {Object<string, boolean>}
- * @protected
*/
- _dropdownOpen: function () { },
+ _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.
- *
- * @param {string} item selected dropdown item
- * @protected
*/
- _dropdownSelect: function (item) { },
+ _dropdownSelect(_item) {
+ // This should be an abstract method, but cannot be marked as such for backwards compatibility.
+ }
/**
* Handles clicks on a dropdown item.
- *
- * @param {Event} event event object
- * @protected
*/
- _clickDropdownItem: function (event) {
+ _clickDropdownItem(event) {
event.preventDefault();
- //noinspection JSCheckFunctionSignatures
- var item = elData(event.currentTarget, 'item');
- var data = {
+ const target = event.currentTarget;
+ const item = target.dataset.item;
+ const data = {
cancel: false,
element: this._activeDropdownElement,
- item: item
+ item,
};
- EventHandler.fire('com.woltlab.wcf.inlineEditor', 'dropdownItemClick_' + this._options.dropdownIdentifier, data);
- if (data.cancel === true) {
+ 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: function () {
- var data = this._elements.get(this._activeElement);
- var messageBodyEditor = elCreate('div');
- messageBodyEditor.className = 'messageBody editor';
+ _prepare() {
+ const data = this._elements.get(this._activeElement);
+ const messageBodyEditor = document.createElement("div");
+ messageBodyEditor.className = "messageBody editor";
data.messageBodyEditor = messageBodyEditor;
- var icon = elCreate('span');
- icon.className = 'icon icon48 fa-spinner';
+ const icon = document.createElement("span");
+ icon.className = "icon icon48 fa-spinner";
messageBodyEditor.appendChild(icon);
- DomUtil.insertAfter(messageBodyEditor, data.messageBody);
- elHide(data.messageBody);
- },
+ data.messageBody.insertAdjacentElement("afterend", messageBodyEditor);
+ Util_1.default.hide(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);
+ _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
- var formSubmit = elBySel('.formSubmit', editor);
- var buttonSave = elBySel('button[data-type="save"]', formSubmit);
- buttonSave.addEventListener('click', this._save.bind(this));
- var buttonCancel = elBySel('button[data-type="cancel"]', formSubmit);
- buttonCancel.addEventListener('click', this._restoreMessage.bind(this));
- EventHandler.add('com.woltlab.wcf.redactor', 'submitEditor_' + id, (function (data) {
+ 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();
- }).bind(this));
+ });
// hide message header and footer
- elHide(elementData.messageHeader);
- elHide(elementData.messageFooter);
- var editorElement = elById(id);
- if (Environment.editor() === 'redactor') {
- window.setTimeout((function () {
+ 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(this._activeElement);
- }).bind(this), 250);
+ UiScroll.element(activeElement);
+ }, 250);
}
else {
+ const editorElement = document.getElementById(id);
editorElement.focus();
}
- },
+ }
/**
* Restores the message view.
- *
- * @protected
*/
- _restoreMessage: function () {
- var elementData = this._elements.get(this._activeElement);
+ _restoreMessage() {
+ const activeElement = this._activeElement;
+ const elementData = this._elements.get(activeElement);
this._destroyEditor();
- elRemove(elementData.messageBodyEditor);
+ elementData.messageBodyEditor.remove();
elementData.messageBodyEditor = null;
- elShow(elementData.messageBody);
- elShow(elementData.messageFooter);
- elShow(elementData.messageHeader);
- this._activeElement.classList.remove('jsInvalidQuoteTarget');
+ 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.
- *
- * @protected
*/
- _save: function () {
- var parameters = {
+ _save() {
+ const parameters = {
containerID: this._options.containerId,
data: {
- message: ''
+ message: "",
},
objectID: this._getObjectId(this._activeElement),
- removeQuoteIDs: (this._options.quoteManager) ? this._options.quoteManager.getQuotesMarkedForRemoval() : []
+ removeQuoteIDs: this._options.quoteManager ? this._options.quoteManager.getQuotesMarkedForRemoval() : [],
};
- var id = this._getEditorId();
+ const id = this._getEditorId();
// add any available settings
- var settingsContainer = elById('settings_' + id);
+ const settingsContainer = document.getElementById(`settings_${id}`);
if (settingsContainer) {
- elBySelAll('input, select, textarea', settingsContainer, function (element) {
- if (element.nodeName === 'INPUT' && (element.type === 'checkbox' || element.type === 'radio')) {
+ settingsContainer
+ .querySelectorAll("input, select, textarea")
+ .forEach((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.");
+ 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);
- var validateResult = this._validate(parameters);
+ 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();
validateResult = Promise.resolve();
}
}
- validateResult.then(function () {
- EventHandler.fire('com.woltlab.wcf.redactor2', 'submit_' + id, parameters);
+ validateResult.then(() => {
+ EventHandler.fire("com.woltlab.wcf.redactor2", `submit_${id}`, parameters);
Ajax.api(this, {
- actionName: 'save',
- parameters: parameters
+ actionName: "save",
+ parameters: parameters,
});
this._hideEditor();
- }.bind(this), function (e) {
- console.log('Validation of post edit failed: ' + e);
+ }, (e) => {
+ const errorMessage = e.message;
+ console.log(`Validation of post edit failed: ${errorMessage}`);
});
- },
+ }
/**
* Validates the message and invokes listeners to perform additional validation.
- *
- * @param {Object} parameters request parameters
- * @return {boolean} validation result
- * @protected
*/
- _validate: function (parameters) {
+ _validate(parameters) {
// remove all existing error elements
- elBySelAll('.innerError', this._activeElement, elRemove);
- var data = {
+ this._activeElement.querySelectorAll(".innerError").forEach((el) => el.remove());
+ const data = {
api: this,
parameters: parameters,
valid: true,
- promises: []
+ promises: [],
};
- EventHandler.fire('com.woltlab.wcf.redactor2', 'validate_' + this._getEditorId(), data);
- data.promises.push(Promise[data.valid ? 'resolve' : 'reject']());
+ 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 adding an inline error to target element.
- *
- * @param {Element} element erroneous element
- * @param {string} message error message
+ * Throws an error by showing an inline error for the target element.
*/
- throwError: function (element, message) {
- elInnerError(element, message);
- },
+ throwError(element, message) {
+ Util_1.default.innerError(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);
+ _showMessage(data) {
+ const activeElement = this._activeElement;
+ const editorId = this._getEditorId();
+ const elementData = this._elements.get(activeElement);
// set new content
- //noinspection JSUnresolvedVariable
- DomUtil.setInnerHtml(DomTraverse.childByClass(elementData.messageBody, 'messageText'), data.returnValues.message);
+ Util_1.default.setInnerHtml(elementData.messageBody.querySelector(".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;
+ 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);
}
}
- // handle poll
- //noinspection JSUnresolvedVariable
- if (typeof data.returnValues.poll === 'string') {
- // find current poll
- var poll = elBySel('.pollContainer', elementData.messageBody);
+ if (typeof data.returnValues.poll === "string") {
+ const poll = elementData.messageBody.querySelector(".pollContainer");
if (poll !== null) {
- // poll contain is wrapped inside `.jsInlineEditorHideContent`
- elRemove(poll.parentNode);
+ // The poll container is wrapped inside `.jsInlineEditorHideContent`.
+ poll.parentElement.remove();
}
- var pollContainer = elCreate('div');
- pollContainer.className = 'jsInlineEditorHideContent';
- //noinspection JSUnresolvedVariable
- DomUtil.setInnerHtml(pollContainer, data.returnValues.poll);
- DomUtil.prepend(pollContainer, elementData.messageBody);
+ 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);
+ 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';
+ _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.
- *
- * @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);
- },
+ _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.
- *
- * @protected
*/
- _destroyEditor: function () {
- EventHandler.fire('com.woltlab.wcf.redactor2', 'autosaveDestroy_' + this._getEditorId());
- EventHandler.fire('com.woltlab.wcf.redactor2', 'destroy_' + this._getEditorId());
- },
+ _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.
- *
- * @param {int} objectId message object id
- * @return string
- * @protected
*/
- _getHash: function (objectId) {
- return '#message' + objectId;
- },
+ _getHash(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) {
+ _updateHistory(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);
- },
+ _getEditorId() {
+ return this._options.editorPrefix + this._getObjectId(this._activeElement).toString();
+ }
/**
* 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);
+ _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();
- //noinspection JSUnresolvedVariable
if (!data || data.returnValues === undefined || data.returnValues.realErrorMessage === undefined) {
return true;
}
- //noinspection JSUnresolvedVariable
- elInnerError(editor, data.returnValues.realErrorMessage);
+ Util_1.default.innerError(editor, data.returnValues.realErrorMessage);
return false;
- },
- _ajaxSuccess: function (data) {
+ }
+ _ajaxSuccess(data) {
switch (data.actionName) {
- case 'beginEdit':
+ case "beginEdit":
this._showEditor(data);
break;
- case 'save':
+ case "save":
this._showMessage(data);
break;
}
- },
- _ajaxSetup: function () {
+ }
+ _ajaxSetup() {
return {
data: {
className: this._options.className,
- interfaceName: 'wcf\\data\\IMessageInlineEditorAction'
+ interfaceName: "wcf\\data\\IMessageInlineEditorAction",
},
- silent: true
+ silent: true,
};
- },
- /** @deprecated 3.0 - used only for backward compatibility with `WCF.Message.InlineEditor` */
- legacyEdit: function (containerId) {
- this._click(elById(containerId), null);
}
- };
+ /** @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;
});
+++ /dev/null
-/**
- * Flexible message inline editor.
- *
- * @author Alexander Ebert
- * @copyright 2001-2019 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('click', this._clickDropdown.bind(this, element));
- button.classList.add('jsDropdownEnabled');
-
- if (canEdit) {
- button.addEventListener('dblclick', this._click.bind(this, element));
- }
- }
- else if (canEdit) {
- button.addEventListener('click', 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('click', (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, 'click');
- }, 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('click', this._click.bind(this, null));
- }
- else {
- listItem.addEventListener('click', 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('click', this._save.bind(this));
-
- var buttonCancel = elBySel('button[data-type="cancel"]', formSubmit);
- buttonCancel.addEventListener('click', 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 contain is wrapped inside `.jsInlineEditorHideContent`
- elRemove(poll.parentNode);
- }
-
- 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;
-});
--- /dev/null
+/**
+ * Flexible message inline editor.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2019 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";
+
+interface MessageInlineEditorOptions {
+ canEditInline: boolean;
+
+ className: string;
+ containerId: number;
+ dropdownIdentifier: string;
+ editorPrefix: string;
+
+ messageSelector: string;
+
+ // This is the legacy jQuery based class.
+ quoteManager: any;
+}
+
+interface ElementData {
+ button: HTMLAnchorElement;
+ messageBody: HTMLElement;
+ messageBodyEditor: HTMLElement | null;
+ messageFooter: HTMLElement;
+ messageFooterButtons: HTMLUListElement;
+ messageHeader: HTMLElement;
+ messageText: HTMLElement;
+}
+
+interface ItemData {
+ item: "divider" | "editItem" | string;
+ label?: string;
+}
+
+interface ElementVisibility {
+ [key: string]: boolean;
+}
+
+interface ValidationData {
+ api: UiMessageInlineEditor;
+ parameters: ArbitraryObject;
+ valid: boolean;
+ promises: Promise<void>[];
+}
+
+interface AjaxResponseEditor extends ResponseData {
+ returnValues: {
+ template: string;
+ };
+}
+
+interface AjaxResponseMessage extends ResponseData {
+ returnValues: {
+ attachmentList?: string;
+ message: string;
+ poll?: string;
+ };
+}
+
+class UiMessageInlineEditor implements AjaxCallbackObject {
+ protected _activeDropdownElement: HTMLElement | null = null;
+ protected _activeElement: HTMLElement | null = null;
+ protected _dropdownMenu: HTMLUListElement | null = null;
+ protected readonly _elements = new WeakMap<HTMLElement, ElementData>();
+ protected readonly _options: MessageInlineEditorOptions;
+
+ /**
+ * Initializes the message inline editor.
+ */
+ constructor(opts: Partial<MessageInlineEditorOptions>) {
+ 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();
+ }
+
+ 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: number): 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): number {
+ 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;