Convert `Ui/Redactor/Spoiler` to TypeScript
authorAlexander Ebert <ebert@woltlab.com>
Thu, 5 Nov 2020 09:50:47 +0000 (10:50 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Thu, 5 Nov 2020 09:50:47 +0000 (10:50 +0100)
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Spoiler.js
wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Code.ts
wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Spoiler.js [deleted file]
wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Spoiler.ts [new file with mode: 0644]

index 3ef74dae6e5c0b2979c41616c5abde65341fadd1..c7fdbbefaf4a986563528141eb167669589744d6 100644 (file)
  * Manages spoilers.
  *
  * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
+ * @copyright  2001-2019 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module      WoltLabSuite/Core/Ui/Redactor/Spoiler
  */
-define(['EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', './PseudoHeader'], function (EventHandler, EventKey, Language, StringUtil, DomUtil, UiDialog, UiRedactorPseudoHeader) {
+define(["require", "exports", "tslib", "../../Core", "../../Dom/Util", "../../Event/Handler", "../../Language", "../Dialog", "./PseudoHeader"], function (require, exports, tslib_1, Core, Util_1, EventHandler, Language, Dialog_1, UiRedactorPseudoHeader) {
     "use strict";
-    if (!COMPILER_TARGET_DEFAULT) {
-        var Fake = function () { };
-        Fake.prototype = {
-            init: function () { },
-            _bbcodeSpoiler: function () { },
-            _observeLoad: function () { },
-            _edit: function () { },
-            _setTitle: function () { },
-            _delete: function () { },
-            _dialogSetup: function () { },
-            _dialogSubmit: function () { }
-        };
-        return Fake;
-    }
-    var _headerHeight = 0;
-    /**
-     * @param       {Object}        editor  editor instance
-     * @constructor
-     */
-    function UiRedactorSpoiler(editor) { this.init(editor); }
-    UiRedactorSpoiler.prototype = {
+    Core = tslib_1.__importStar(Core);
+    Util_1 = tslib_1.__importDefault(Util_1);
+    EventHandler = tslib_1.__importStar(EventHandler);
+    Language = tslib_1.__importStar(Language);
+    Dialog_1 = tslib_1.__importDefault(Dialog_1);
+    UiRedactorPseudoHeader = tslib_1.__importStar(UiRedactorPseudoHeader);
+    let _headerHeight = 0;
+    class UiRedactorSpoiler {
         /**
          * Initializes the spoiler management.
-         *
-         * @param       {Object}        editor  editor instance
          */
-        init: function (editor) {
+        constructor(editor) {
+            this._spoiler = null;
             this._editor = editor;
             this._elementId = this._editor.$element[0].id;
-            this._spoiler = null;
-            EventHandler.add('com.woltlab.wcf.redactor2', 'bbcode_spoiler_' + this._elementId, this._bbcodeSpoiler.bind(this));
-            EventHandler.add('com.woltlab.wcf.redactor2', 'observe_load_' + this._elementId, this._observeLoad.bind(this));
-            // static bind to ensure that removing works
-            this._callbackEdit = this._edit.bind(this);
+            EventHandler.add("com.woltlab.wcf.redactor2", `bbcode_spoiler_${this._elementId}`, (data) => this._bbcodeSpoiler(data));
+            EventHandler.add("com.woltlab.wcf.redactor2", `observe_load_${this._elementId}`, () => this._observeLoad());
             // bind listeners on init
             this._observeLoad();
-        },
+        }
         /**
          * Intercepts the insertion of `[spoiler]` tags and uses
          * the custom `<woltlab-spoiler>` element instead.
-         *
-         * @param       {Object}        data    event data
-         * @protected
          */
-        _bbcodeSpoiler: function (data) {
+        _bbcodeSpoiler(data) {
             data.cancel = true;
-            this._editor.button.toggle({}, 'woltlab-spoiler', 'func', 'block.format');
-            var spoiler = this._editor.selection.block();
+            this._editor.button.toggle({}, "woltlab-spoiler", "func", "block.format");
+            let spoiler = this._editor.selection.block();
             if (spoiler) {
                 // iOS Safari might set the caret inside the spoiler.
-                if (spoiler.nodeName === 'P') {
-                    spoiler = spoiler.parentNode;
+                if (spoiler.nodeName === "P") {
+                    spoiler = spoiler.parentElement;
                 }
-                if (spoiler.nodeName === 'WOLTLAB-SPOILER') {
+                if (spoiler.nodeName === "WOLTLAB-SPOILER") {
                     this._setTitle(spoiler);
-                    spoiler.addEventListener('click', this._callbackEdit);
+                    spoiler.addEventListener("click", (ev) => this._edit(ev));
                     // work-around for Safari
                     this._editor.caret.end(spoiler);
                 }
             }
-        },
+        }
         /**
          * Binds event listeners and sets quote title on both editor
          * initialization and when switching back from code view.
-         *
-         * @protected
          */
-        _observeLoad: function () {
-            elBySelAll('woltlab-spoiler', this._editor.$editor[0], (function (spoiler) {
-                spoiler.addEventListener('mousedown', this._callbackEdit);
+        _observeLoad() {
+            this._editor.$editor[0].querySelectorAll("woltlab-spoiler").forEach((spoiler) => {
+                spoiler.addEventListener("mousedown", (ev) => this._edit(ev));
                 this._setTitle(spoiler);
-            }).bind(this));
-        },
+            });
+        }
         /**
          * Opens the dialog overlay to edit the spoiler's properties.
-         *
-         * @param       {Event}         event           event object
-         * @protected
          */
-        _edit: function (event) {
-            var spoiler = event.currentTarget;
+        _edit(event) {
+            const spoiler = event.currentTarget;
             if (_headerHeight === 0) {
                 _headerHeight = UiRedactorPseudoHeader.getHeight(spoiler);
             }
             // check if the click hit the header
-            var offset = DomUtil.offset(spoiler);
-            if (event.pageY > offset.top && event.pageY < (offset.top + _headerHeight)) {
+            const offset = Util_1.default.offset(spoiler);
+            if (event.pageY > offset.top && event.pageY < offset.top + _headerHeight) {
                 event.preventDefault();
                 this._editor.selection.save();
                 this._spoiler = spoiler;
-                UiDialog.open(this);
+                Dialog_1.default.open(this);
             }
-        },
+        }
         /**
          * Saves the changes to the spoiler's properties.
          *
          * @protected
          */
-        _dialogSubmit: function () {
-            elData(this._spoiler, 'label', elById('redactor-spoiler-' + this._elementId + '-label').value);
-            this._setTitle(this._spoiler);
-            this._editor.caret.after(this._spoiler);
-            UiDialog.close(this);
-        },
+        _dialogSubmit() {
+            const spoiler = this._spoiler;
+            const label = document.getElementById("redactor-spoiler-" + this._elementId + "-label");
+            spoiler.dataset.label = label.value;
+            this._setTitle(spoiler);
+            this._editor.caret.after(spoiler);
+            Dialog_1.default.close(this);
+        }
         /**
          * Sets or updates the spoiler's header title.
-         *
-         * @param       {Element}       spoiler     spoiler element
-         * @protected
          */
-        _setTitle: function (spoiler) {
-            var title = Language.get('wcf.editor.spoiler.title', { label: elData(spoiler, 'label') });
-            if (elData(spoiler, 'title') !== title) {
-                elData(spoiler, 'title', title);
+        _setTitle(spoiler) {
+            const title = Language.get("wcf.editor.spoiler.title", { label: spoiler.dataset.label || "" });
+            if (spoiler.dataset.title !== title) {
+                spoiler.dataset.title = title;
             }
-        },
-        _delete: function (event) {
+        }
+        _delete(event) {
             event.preventDefault();
-            var caretEnd = this._spoiler.nextElementSibling || this._spoiler.previousElementSibling;
-            if (caretEnd === null && this._spoiler.parentNode !== this._editor.core.editor()[0]) {
-                caretEnd = this._spoiler.parentNode;
+            const spoiler = this._spoiler;
+            let caretEnd = spoiler.nextElementSibling || spoiler.previousElementSibling;
+            if (caretEnd === null && spoiler.parentElement !== this._editor.core.editor()[0]) {
+                caretEnd = spoiler.parentElement;
             }
             if (caretEnd === null) {
-                this._editor.code.set('');
+                this._editor.code.set("");
                 this._editor.focus.end();
             }
             else {
-                elRemove(this._spoiler);
+                spoiler.remove();
                 this._editor.caret.end(caretEnd);
             }
-            UiDialog.close(this);
-        },
-        _dialogSetup: function () {
-            var id = 'redactor-spoiler-' + this._elementId, idButtonDelete = id + '-button-delete', idButtonSave = id + '-button-save', idLabel = id + '-label';
+            Dialog_1.default.close(this);
+        }
+        _dialogSetup() {
+            const id = `redactor-spoiler-${this._elementId}`;
+            const idButtonDelete = `${id}-button-delete`;
+            const idButtonSave = `${id}-button-save`;
+            const idLabel = `${id}-label`;
             return {
                 id: id,
                 options: {
-                    onClose: (function () {
+                    onClose: () => {
                         this._editor.selection.restore();
-                        UiDialog.destroy(this);
-                    }).bind(this),
-                    onSetup: (function () {
-                        elById(idButtonDelete).addEventListener('click', this._delete.bind(this));
-                    }).bind(this),
-                    onShow: (function () {
-                        elById(idLabel).value = elData(this._spoiler, 'label');
-                    }).bind(this),
-                    title: Language.get('wcf.editor.spoiler.edit')
+                        Dialog_1.default.destroy(this);
+                    },
+                    onSetup: () => {
+                        const button = document.getElementById(idButtonDelete);
+                        button.addEventListener("click", (ev) => this._delete(ev));
+                    },
+                    onShow: () => {
+                        const label = document.getElementById(idLabel);
+                        label.value = this._spoiler.dataset.label || "";
+                    },
+                    title: Language.get("wcf.editor.spoiler.edit"),
                 },
-                source: '<div class="section">'
-                    + '<dl>'
-                    + '<dt><label for="' + idLabel + '">' + Language.get('wcf.editor.spoiler.label') + '</label></dt>'
-                    + '<dd>'
-                    + '<input type="text" id="' + idLabel + '" class="long" data-dialog-submit-on-enter="true">'
-                    + '<small>' + Language.get('wcf.editor.spoiler.label.description') + '</small>'
-                    + '</dd>'
-                    + '</dl>'
-                    + '</div>'
-                    + '<div class="formSubmit">'
-                    + '<button id="' + idButtonSave + '" class="buttonPrimary" data-type="submit">' + Language.get('wcf.global.button.save') + '</button>'
-                    + '<button id="' + idButtonDelete + '">' + Language.get('wcf.global.button.delete') + '</button>'
-                    + '</div>'
+                source: `<div class="section">
+          <dl>
+            <dt>
+              <label for="${idLabel}">${Language.get("wcf.editor.spoiler.label")}</label>
+            </dt>
+            <dd>
+              <input type="text" id="${idLabel}" class="long" data-dialog-submit-on-enter="true">
+              <small>${Language.get("wcf.editor.spoiler.label.description")}</small>
+            </dd>
+          </dl>
+        </div>
+        <div class="formSubmit">
+          <button id="${idButtonSave}" class="buttonPrimary" data-type="submit">${Language.get("wcf.global.button.save")}</button>
+          <button id="${idButtonDelete}">${Language.get("wcf.global.button.delete")}</button>
+        </div>`,
             };
         }
-    };
+    }
+    Core.enableLegacyInheritance(UiRedactorSpoiler);
     return UiRedactorSpoiler;
 });
index a2cb0fa107650be4a16cc34b358cbe66d1463106..80abe05afc33c9adbdb5d56cc90b9673389f9f8f 100644 (file)
@@ -14,7 +14,7 @@ import * as Language from "../../Language";
 import * as StringUtil from "../../StringUtil";
 import UiDialog from "../Dialog";
 import { DialogCallbackObject, DialogCallbackSetup } from "../Dialog/Data";
-import { RedactorEditor } from "./Editor";
+import { RedactorEditor, WoltLabEventData } from "./Editor";
 import * as UiRedactorPseudoHeader from "./PseudoHeader";
 import PrismMeta from "../../prism-meta";
 
@@ -51,7 +51,7 @@ class UiRedactorCode implements DialogCallbackObject {
   /**
    * Intercepts the insertion of `[code]` tags and uses a native `<pre>` instead.
    */
-  protected _bbcodeCode(data: { cancel: boolean }): void {
+  protected _bbcodeCode(data: WoltLabEventData): void {
     data.cancel = true;
 
     let pre = this._editor.selection.block();
diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Spoiler.js b/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Spoiler.js
deleted file mode 100644 (file)
index 2c71b30..0000000
+++ /dev/null
@@ -1,214 +0,0 @@
-/**
- * Manages spoilers.
- *
- * @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/Redactor/Spoiler
- */
-define(['EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', './PseudoHeader'], function (EventHandler, EventKey, Language, StringUtil, DomUtil, UiDialog, UiRedactorPseudoHeader) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _bbcodeSpoiler: function() {},
-                       _observeLoad: function() {},
-                       _edit: function() {},
-                       _setTitle: function() {},
-                       _delete: function() {},
-                       _dialogSetup: function() {},
-                       _dialogSubmit: function() {}
-               };
-               return Fake;
-       }
-       
-       var _headerHeight = 0;
-       
-       /**
-        * @param       {Object}        editor  editor instance
-        * @constructor
-        */
-       function UiRedactorSpoiler(editor) { this.init(editor); }
-       UiRedactorSpoiler.prototype = {
-               /**
-                * Initializes the spoiler management.
-                * 
-                * @param       {Object}        editor  editor instance
-                */
-               init: function(editor) {
-                       this._editor = editor;
-                       this._elementId = this._editor.$element[0].id;
-                       this._spoiler = null;
-                       
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'bbcode_spoiler_' + this._elementId, this._bbcodeSpoiler.bind(this));
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'observe_load_' + this._elementId, this._observeLoad.bind(this));
-                       
-                       // static bind to ensure that removing works
-                       this._callbackEdit = this._edit.bind(this);
-                       
-                       // bind listeners on init
-                       this._observeLoad();
-               },
-               
-               /**
-                * Intercepts the insertion of `[spoiler]` tags and uses
-                * the custom `<woltlab-spoiler>` element instead.
-                * 
-                * @param       {Object}        data    event data
-                * @protected
-                */
-               _bbcodeSpoiler: function(data) {
-                       data.cancel = true;
-                       
-                       this._editor.button.toggle({}, 'woltlab-spoiler', 'func', 'block.format');
-                       
-                       var spoiler = this._editor.selection.block();
-                       if (spoiler) {
-                               // iOS Safari might set the caret inside the spoiler.
-                               if (spoiler.nodeName === 'P') {
-                                       spoiler = spoiler.parentNode;
-                               }
-
-                               if (spoiler.nodeName === 'WOLTLAB-SPOILER') {
-                                       this._setTitle(spoiler);
-
-                                       spoiler.addEventListener('click', this._callbackEdit);
-
-                                       // work-around for Safari
-                                       this._editor.caret.end(spoiler);
-                               }
-                       }
-               },
-               
-               /**
-                * Binds event listeners and sets quote title on both editor
-                * initialization and when switching back from code view.
-                * 
-                * @protected
-                */
-               _observeLoad: function() {
-                       elBySelAll('woltlab-spoiler', this._editor.$editor[0], (function(spoiler) {
-                               spoiler.addEventListener('mousedown', this._callbackEdit);
-                               this._setTitle(spoiler);
-                       }).bind(this));
-               },
-               
-               /**
-                * Opens the dialog overlay to edit the spoiler's properties.
-                * 
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _edit: function(event) {
-                       var spoiler = event.currentTarget;
-                       
-                       if (_headerHeight === 0) {
-                               _headerHeight = UiRedactorPseudoHeader.getHeight(spoiler);
-                       }
-                       
-                       // check if the click hit the header
-                       var offset = DomUtil.offset(spoiler);
-                       if (event.pageY > offset.top && event.pageY < (offset.top + _headerHeight)) {
-                               event.preventDefault();
-                               
-                               this._editor.selection.save();
-                               this._spoiler = spoiler;
-                               
-                               UiDialog.open(this);
-                       }
-               },
-               
-               /**
-                * Saves the changes to the spoiler's properties.
-                * 
-                * @protected
-                */
-               _dialogSubmit: function() {
-                       elData(this._spoiler, 'label', elById('redactor-spoiler-' + this._elementId + '-label').value);
-                       
-                       this._setTitle(this._spoiler);
-                       this._editor.caret.after(this._spoiler);
-                       
-                       UiDialog.close(this);
-               },
-               
-               /**
-                * Sets or updates the spoiler's header title.
-                * 
-                * @param       {Element}       spoiler     spoiler element
-                * @protected
-                */
-               _setTitle: function(spoiler) {
-                       var title = Language.get('wcf.editor.spoiler.title', { label: elData(spoiler, 'label') });
-                       
-                       if (elData(spoiler, 'title') !== title) {
-                               elData(spoiler, 'title', title);
-                       }
-               },
-               
-               _delete: function (event) {
-                       event.preventDefault();
-                       
-                       var caretEnd = this._spoiler.nextElementSibling || this._spoiler.previousElementSibling;
-                       if (caretEnd === null && this._spoiler.parentNode !== this._editor.core.editor()[0]) {
-                               caretEnd = this._spoiler.parentNode;
-                       }
-                       
-                       if (caretEnd === null) {
-                               this._editor.code.set('');
-                               this._editor.focus.end();
-                       }
-                       else {
-                               elRemove(this._spoiler);
-                               this._editor.caret.end(caretEnd);
-                       }
-                       
-                       UiDialog.close(this);
-               },
-               
-               _dialogSetup: function() {
-                       var id = 'redactor-spoiler-' + this._elementId,
-                           idButtonDelete = id + '-button-delete',
-                           idButtonSave = id + '-button-save',
-                           idLabel = id + '-label';
-                       
-                       return {
-                               id: id,
-                               options: {
-                                       onClose: (function () {
-                                               this._editor.selection.restore();
-                                               
-                                               UiDialog.destroy(this);
-                                       }).bind(this),
-                                       
-                                       onSetup: (function() {
-                                               elById(idButtonDelete).addEventListener('click', this._delete.bind(this));
-                                       }).bind(this),
-                                       
-                                       onShow: (function() {
-                                               elById(idLabel).value = elData(this._spoiler, 'label');
-                                       }).bind(this),
-                                       
-                                       title: Language.get('wcf.editor.spoiler.edit')
-                               },
-                               source: '<div class="section">'
-                                       + '<dl>'
-                                               + '<dt><label for="' + idLabel + '">' + Language.get('wcf.editor.spoiler.label') + '</label></dt>'
-                                               + '<dd>'
-                                                       + '<input type="text" id="' + idLabel + '" class="long" data-dialog-submit-on-enter="true">'
-                                                       + '<small>' + Language.get('wcf.editor.spoiler.label.description') + '</small>'
-                                               + '</dd>'
-                                       + '</dl>'
-                               + '</div>'
-                               + '<div class="formSubmit">'
-                                       + '<button id="' + idButtonSave + '" class="buttonPrimary" data-type="submit">' + Language.get('wcf.global.button.save') + '</button>'
-                                       + '<button id="' + idButtonDelete + '">' + Language.get('wcf.global.button.delete') + '</button>'
-                               + '</div>'
-                       };
-               }
-       };
-       
-       return UiRedactorSpoiler;
-});
diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Spoiler.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Spoiler.ts
new file mode 100644 (file)
index 0000000..f6e760c
--- /dev/null
@@ -0,0 +1,201 @@
+/**
+ * Manages spoilers.
+ *
+ * @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/Redactor/Spoiler
+ */
+
+import * as Core from "../../Core";
+import { DialogCallbackObject, DialogCallbackSetup } from "../Dialog/Data";
+import DomUtil from "../../Dom/Util";
+import * as EventHandler from "../../Event/Handler";
+import * as Language from "../../Language";
+import UiDialog from "../Dialog";
+import { RedactorEditor, WoltLabEventData } from "./Editor";
+import * as UiRedactorPseudoHeader from "./PseudoHeader";
+
+let _headerHeight = 0;
+
+class UiRedactorSpoiler implements DialogCallbackObject {
+  protected readonly _editor: RedactorEditor;
+  protected readonly _elementId: string;
+  protected _spoiler: HTMLElement | null = null;
+
+  /**
+   * Initializes the spoiler management.
+   */
+  constructor(editor: RedactorEditor) {
+    this._editor = editor;
+    this._elementId = this._editor.$element[0].id;
+
+    EventHandler.add("com.woltlab.wcf.redactor2", `bbcode_spoiler_${this._elementId}`, (data) =>
+      this._bbcodeSpoiler(data),
+    );
+    EventHandler.add("com.woltlab.wcf.redactor2", `observe_load_${this._elementId}`, () => this._observeLoad());
+
+    // bind listeners on init
+    this._observeLoad();
+  }
+
+  /**
+   * Intercepts the insertion of `[spoiler]` tags and uses
+   * the custom `<woltlab-spoiler>` element instead.
+   */
+  protected _bbcodeSpoiler(data: WoltLabEventData): void {
+    data.cancel = true;
+
+    this._editor.button.toggle({}, "woltlab-spoiler", "func", "block.format");
+
+    let spoiler = this._editor.selection.block();
+    if (spoiler) {
+      // iOS Safari might set the caret inside the spoiler.
+      if (spoiler.nodeName === "P") {
+        spoiler = spoiler.parentElement!;
+      }
+
+      if (spoiler.nodeName === "WOLTLAB-SPOILER") {
+        this._setTitle(spoiler);
+
+        spoiler.addEventListener("click", (ev) => this._edit(ev));
+
+        // work-around for Safari
+        this._editor.caret.end(spoiler);
+      }
+    }
+  }
+
+  /**
+   * Binds event listeners and sets quote title on both editor
+   * initialization and when switching back from code view.
+   */
+  protected _observeLoad(): void {
+    this._editor.$editor[0].querySelectorAll("woltlab-spoiler").forEach((spoiler: HTMLElement) => {
+      spoiler.addEventListener("mousedown", (ev) => this._edit(ev));
+      this._setTitle(spoiler);
+    });
+  }
+
+  /**
+   * Opens the dialog overlay to edit the spoiler's properties.
+   */
+  protected _edit(event: MouseEvent): void {
+    const spoiler = event.currentTarget as HTMLElement;
+
+    if (_headerHeight === 0) {
+      _headerHeight = UiRedactorPseudoHeader.getHeight(spoiler);
+    }
+
+    // check if the click hit the header
+    const offset = DomUtil.offset(spoiler);
+    if (event.pageY > offset.top && event.pageY < offset.top + _headerHeight) {
+      event.preventDefault();
+
+      this._editor.selection.save();
+      this._spoiler = spoiler;
+
+      UiDialog.open(this);
+    }
+  }
+
+  /**
+   * Saves the changes to the spoiler's properties.
+   *
+   * @protected
+   */
+  _dialogSubmit(): void {
+    const spoiler = this._spoiler!;
+
+    const label = document.getElementById("redactor-spoiler-" + this._elementId + "-label") as HTMLInputElement;
+    spoiler.dataset.label = label.value;
+
+    this._setTitle(spoiler);
+    this._editor.caret.after(spoiler);
+
+    UiDialog.close(this);
+  }
+
+  /**
+   * Sets or updates the spoiler's header title.
+   */
+  protected _setTitle(spoiler: HTMLElement): void {
+    const title = Language.get("wcf.editor.spoiler.title", { label: spoiler.dataset.label || "" });
+
+    if (spoiler.dataset.title !== title) {
+      spoiler.dataset.title = title;
+    }
+  }
+
+  protected _delete(event: MouseEvent): void {
+    event.preventDefault();
+
+    const spoiler = this._spoiler!;
+
+    let caretEnd = spoiler.nextElementSibling || spoiler.previousElementSibling;
+    if (caretEnd === null && spoiler.parentElement !== this._editor.core.editor()[0]) {
+      caretEnd = spoiler.parentElement;
+    }
+
+    if (caretEnd === null) {
+      this._editor.code.set("");
+      this._editor.focus.end();
+    } else {
+      spoiler.remove();
+      this._editor.caret.end(caretEnd);
+    }
+
+    UiDialog.close(this);
+  }
+
+  _dialogSetup(): ReturnType<DialogCallbackSetup> {
+    const id = `redactor-spoiler-${this._elementId}`;
+    const idButtonDelete = `${id}-button-delete`;
+    const idButtonSave = `${id}-button-save`;
+    const idLabel = `${id}-label`;
+
+    return {
+      id: id,
+      options: {
+        onClose: () => {
+          this._editor.selection.restore();
+
+          UiDialog.destroy(this);
+        },
+
+        onSetup: () => {
+          const button = document.getElementById(idButtonDelete) as HTMLButtonElement;
+          button.addEventListener("click", (ev) => this._delete(ev));
+        },
+
+        onShow: () => {
+          const label = document.getElementById(idLabel) as HTMLInputElement;
+          label.value = this._spoiler!.dataset.label || "";
+        },
+
+        title: Language.get("wcf.editor.spoiler.edit"),
+      },
+      source: `<div class="section">
+          <dl>
+            <dt>
+              <label for="${idLabel}">${Language.get("wcf.editor.spoiler.label")}</label>
+            </dt>
+            <dd>
+              <input type="text" id="${idLabel}" class="long" data-dialog-submit-on-enter="true">
+              <small>${Language.get("wcf.editor.spoiler.label.description")}</small>
+            </dd>
+          </dl>
+        </div>
+        <div class="formSubmit">
+          <button id="${idButtonSave}" class="buttonPrimary" data-type="submit">${Language.get(
+        "wcf.global.button.save",
+      )}</button>
+          <button id="${idButtonDelete}">${Language.get("wcf.global.button.delete")}</button>
+        </div>`,
+    };
+  }
+}
+
+Core.enableLegacyInheritance(UiRedactorSpoiler);
+
+export = UiRedactorSpoiler;