From 8fd9b97a7b0db01891c91f7bb829ca2842734d52 Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Thu, 5 Nov 2020 00:48:30 +0100 Subject: [PATCH] Convert `Ui/Redactor/Quote` to TypeScript --- .../js/WoltLabSuite/Core/Ui/Redactor/Quote.js | 284 ++++++++-------- .../WoltLabSuite/Core/Ui/Redactor/Editor.ts | 7 + .../ts/WoltLabSuite/Core/Ui/Redactor/Quote.js | 310 ------------------ .../ts/WoltLabSuite/Core/Ui/Redactor/Quote.ts | 297 +++++++++++++++++ 4 files changed, 436 insertions(+), 462 deletions(-) delete mode 100644 wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Quote.js create mode 100644 wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Quote.ts diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Quote.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Quote.js index c7b10ec872..4cad2cafe9 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Quote.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Quote.js @@ -2,253 +2,233 @@ * Manages quotes. * * @author Alexander Ebert - * @copyright 2001-2019 WoltLab GmbH + * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License * @module WoltLabSuite/Core/Ui/Redactor/Quote */ -define(['Core', 'EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', './Metacode', './PseudoHeader'], function (Core, EventHandler, EventKey, Language, StringUtil, DomUtil, UiDialog, UiRedactorMetacode, UiRedactorPseudoHeader) { +define(["require", "exports", "tslib", "../../Core", "../../Dom/Util", "../../Event/Handler", "../../Language", "../../StringUtil", "../Dialog", "./Metacode", "./PseudoHeader"], function (require, exports, tslib_1, Core, Util_1, EventHandler, Language, StringUtil, Dialog_1, UiRedactorMetacode, UiRedactorPseudoHeader) { "use strict"; - if (!COMPILER_TARGET_DEFAULT) { - var Fake = function () { }; - Fake.prototype = { - init: function () { }, - _insertQuote: function () { }, - _click: function () { }, - _observeLoad: function () { }, - _edit: function () { }, - _save: function () { }, - _setTitle: function () { }, - _delete: function () { }, - _dialogSetup: function () { }, - _dialogSubmit: function () { } - }; - return Fake; - } - var _headerHeight = 0; - /** - * @param {Object} editor editor instance - * @param {jQuery} button toolbar button - * @constructor - */ - function UiRedactorQuote(editor, button) { this.init(editor, button); } - UiRedactorQuote.prototype = { + Core = tslib_1.__importStar(Core); + Util_1 = tslib_1.__importDefault(Util_1); + EventHandler = tslib_1.__importStar(EventHandler); + Language = tslib_1.__importStar(Language); + StringUtil = tslib_1.__importStar(StringUtil); + Dialog_1 = tslib_1.__importDefault(Dialog_1); + UiRedactorMetacode = tslib_1.__importStar(UiRedactorMetacode); + UiRedactorPseudoHeader = tslib_1.__importStar(UiRedactorPseudoHeader); + let _headerHeight = 0; + class UiRedactorQuote { /** * Initializes the quote management. - * - * @param {Object} editor editor instance - * @param {jQuery} button toolbar button */ - init: function (editor, button) { + constructor(editor, button) { this._quote = null; - this._quotes = elByTag('woltlab-quote', editor.$editor[0]); this._editor = editor; this._elementId = this._editor.$element[0].id; - EventHandler.add('com.woltlab.wcf.redactor2', 'observe_load_' + this._elementId, this._observeLoad.bind(this)); + EventHandler.add("com.woltlab.wcf.redactor2", `observe_load_${this._elementId}`, () => this._observeLoad()); this._editor.button.addCallback(button, this._click.bind(this)); - // static bind to ensure that removing works - this._callbackEdit = this._edit.bind(this); // bind listeners on init this._observeLoad(); // quote manager - EventHandler.add('com.woltlab.wcf.redactor2', 'insertQuote_' + this._elementId, this._insertQuote.bind(this)); - }, + EventHandler.add("com.woltlab.wcf.redactor2", `insertQuote_${this._elementId}`, (data) => this._insertQuote(data)); + } /** * Inserts a quote. - * - * @param {Object} data quote data - * @protected */ - _insertQuote: function (data) { + _insertQuote(data) { if (this._editor.WoltLabSource.isActive()) { return; } - EventHandler.fire('com.woltlab.wcf.redactor2', 'showEditor'); - var editor = this._editor.core.editor()[0]; + EventHandler.fire("com.woltlab.wcf.redactor2", "showEditor"); + const editor = this._editor.core.editor()[0]; this._editor.selection.restore(); this._editor.buffer.set(); // caret must be within a `

`, if it is not: move it - var block = this._editor.selection.block(); + let block = this._editor.selection.block(); if (block === false) { this._editor.focus.end(); block = this._editor.selection.block(); } - while (block && block.parentNode !== editor) { - block = block.parentNode; + while (block && block.parentElement !== editor) { + block = block.parentElement; } - var quote = elCreate('woltlab-quote'); - elData(quote, 'author', data.author); - elData(quote, 'link', data.link); - var content = data.content; + const quote = document.createElement("woltlab-quote"); + quote.dataset.author = data.author; + quote.dataset.link = data.link; + let content = data.content; if (data.isText) { content = StringUtil.escapeHTML(content); - content = '

' + content + '

'; - content = content.replace(/\n\n/g, '

'); - content = content.replace(/\n/g, '
'); + content = `

${content}

`; + content = content.replace(/\n\n/g, "

"); + content = content.replace(/\n/g, "
"); } else { - //noinspection JSUnresolvedFunction content = UiRedactorMetacode.convertFromHtml(this._editor.$element[0].id, content); } // bypass the editor as `insert.html()` doesn't like us quote.innerHTML = content; - block.parentNode.insertBefore(quote, block.nextSibling); - if (block.nodeName === 'P' && (block.innerHTML === '
' || block.innerHTML.replace(/\u200B/g, '') === '')) { - block.parentNode.removeChild(block); + const blockParent = block.parentElement; + blockParent.insertBefore(quote, block.nextSibling); + if (block.nodeName === "P" && (block.innerHTML === "
" || block.innerHTML.replace(/\u200B/g, "") === "")) { + blockParent.removeChild(block); } // avoid adjacent blocks that are not paragraphs - var sibling = quote.previousElementSibling; - if (sibling && sibling.nodeName !== 'P') { - sibling = elCreate('p'); - sibling.textContent = '\u200B'; - quote.parentNode.insertBefore(sibling, quote); + let sibling = quote.previousElementSibling; + if (sibling && sibling.nodeName !== "P") { + sibling = document.createElement("p"); + sibling.textContent = "\u200B"; + quote.insertAdjacentElement("beforebegin", sibling); } this._editor.WoltLabCaret.paragraphAfterBlock(quote); this._editor.buffer.set(); - }, + } /** * Toggles the quote block on button click. - * - * @protected */ - _click: function () { - this._editor.button.toggle({}, 'woltlab-quote', 'func', 'block.format'); - var quote = this._editor.selection.block(); - if (quote && quote.nodeName === 'WOLTLAB-QUOTE') { + _click() { + this._editor.button.toggle({}, "woltlab-quote", "func", "block.format"); + const quote = this._editor.selection.block(); + if (quote && quote.nodeName === "WOLTLAB-QUOTE") { this._setTitle(quote); - quote.addEventListener('click', this._callbackEdit); + quote.addEventListener("click", (ev) => this._edit(ev)); // work-around for Safari this._editor.caret.end(quote); } - }, + } /** * Binds event listeners and sets quote title on both editor * initialization and when switching back from code view. - * - * @protected */ - _observeLoad: function () { - var quote; - for (var i = 0, length = this._quotes.length; i < length; i++) { - quote = this._quotes[i]; - quote.addEventListener('mousedown', this._callbackEdit); + _observeLoad() { + document.querySelectorAll("woltlab-quote").forEach((quote) => { + quote.addEventListener("mousedown", (ev) => this._edit(ev)); this._setTitle(quote); - } - }, + }); + } /** * Opens the dialog overlay to edit the quote's properties. - * - * @param {Event} event event object - * @protected */ - _edit: function (event) { - var quote = event.currentTarget; + _edit(event) { + const quote = event.currentTarget; if (_headerHeight === 0) { _headerHeight = UiRedactorPseudoHeader.getHeight(quote); } // check if the click hit the header - var offset = DomUtil.offset(quote); - if (event.pageY > offset.top && event.pageY < (offset.top + _headerHeight)) { + const offset = Util_1.default.offset(quote); + if (event.pageY > offset.top && event.pageY < offset.top + _headerHeight) { event.preventDefault(); this._editor.selection.save(); this._quote = quote; - UiDialog.open(this); + Dialog_1.default.open(this); } - }, + } /** * Saves the changes to the quote's properties. * * @protected */ - _dialogSubmit: function () { - var id = 'redactor-quote-' + this._elementId; - var urlInput = elById(id + '-url'); - var url = urlInput.value.replace(/\u200B/g, '').trim(); + _dialogSubmit() { + const id = `redactor-quote-${this._elementId}`; + const urlInput = document.getElementById(`${id}-url`); + const url = urlInput.value.replace(/\u200B/g, "").trim(); // simple test to check if it at least looks like it could be a valid url - if (url.length && !/^https?:\/\/[^\/]+/.test(url)) { - elInnerError(urlInput, Language.get('wcf.editor.quote.url.error.invalid')); + if (url.length && !/^https?:\/\/[^/]+/.test(url)) { + Util_1.default.innerError(urlInput, Language.get("wcf.editor.quote.url.error.invalid")); return; } else { - elInnerError(urlInput, false); + Util_1.default.innerError(urlInput, false); } + const quote = this._quote; // set author - elData(this._quote, 'author', elById(id + '-author').value); + const author = document.getElementById(id + "-author"); + quote.dataset.author = author.value; // set url - elData(this._quote, 'link', url); - this._setTitle(this._quote); - this._editor.caret.after(this._quote); - UiDialog.close(this); - }, + quote.dataset.link = url; + this._setTitle(quote); + this._editor.caret.after(quote); + Dialog_1.default.close(this); + } /** * Sets or updates the quote's header title. - * - * @param {Element} quote quote element - * @protected */ - _setTitle: function (quote) { - var title = Language.get('wcf.editor.quote.title', { - author: elData(quote, 'author'), - url: elData(quote, 'url') + _setTitle(quote) { + const title = Language.get("wcf.editor.quote.title", { + author: quote.dataset.author, + url: quote.dataset.url, }); - if (elData(quote, 'title') !== title) { - elData(quote, 'title', title); + if (quote.dataset.title !== title) { + quote.dataset.title = title; } - }, - _delete: function (event) { + } + _delete(event) { event.preventDefault(); - var caretEnd = this._quote.nextElementSibling || this._quote.previousElementSibling; - if (caretEnd === null && this._quote.parentNode !== this._editor.core.editor()[0]) { - caretEnd = this._quote.parentNode; + const quote = this._quote; + let caretEnd = quote.nextElementSibling || quote.previousElementSibling; + if (caretEnd === null && quote.parentElement !== this._editor.core.editor()[0]) { + caretEnd = quote.parentElement; } if (caretEnd === null) { - this._editor.code.set(''); + this._editor.code.set(""); this._editor.focus.end(); } else { - elRemove(this._quote); + quote.remove(); this._editor.caret.end(caretEnd); } - UiDialog.close(this); - }, - _dialogSetup: function () { - var id = 'redactor-quote-' + this._elementId, idAuthor = id + '-author', idButtonDelete = id + '-button-delete', idButtonSave = id + '-button-save', idUrl = id + '-url'; + Dialog_1.default.close(this); + } + _dialogSetup() { + const id = `redactor-quote-${this._elementId}`; + const idAuthor = `${id}-author`; + const idButtonDelete = `${id}-button-delete`; + const idButtonSave = `${id}-button-save`; + const idUrl = `${id}-url`; 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(idAuthor).value = elData(this._quote, 'author'); - elById(idUrl).value = elData(this._quote, 'link'); - }).bind(this), - title: Language.get('wcf.editor.quote.edit') + Dialog_1.default.destroy(this); + }, + onSetup: () => { + const button = document.getElementById(idButtonDelete); + button.addEventListener("click", (ev) => this._delete(ev)); + }, + onShow: () => { + const author = document.getElementById(idAuthor); + author.value = this._quote.dataset.author || ""; + const url = document.getElementById(idUrl); + url.value = this._quote.dataset.link || ""; + }, + title: Language.get("wcf.editor.quote.edit"), }, - source: '

' - + '
' - + '
' - + '
' - + '' - + '
' - + '
' - + '
' - + '
' - + '
' - + '' - + '' + Language.get('wcf.editor.quote.url.description') + '' - + '
' - + '
' - + '
' - + '
' - + '' - + '' - + '
' + source: `
+
+
+ +
+
+ +
+
+
+
+ +
+
+ + ${Language.get("wcf.editor.quote.url.description")} +
+
+
+
+ + +
`, }; } - }; + } + Core.enableLegacyInheritance(UiRedactorQuote); return UiRedactorQuote; }); diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Editor.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Editor.ts index a22ac42525..714bd4e386 100644 --- a/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Editor.ts +++ b/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Editor.ts @@ -11,6 +11,7 @@ export interface RedactorEditor { set(): void; }; button: { + addCallback(button: JQuery, callback: () => void): void; toggle(event: MouseEvent | object, btnName: string, type: string, callback: string, args?: object): void; }; caret: { @@ -46,9 +47,15 @@ export interface RedactorEditor { isEmpty(html: string): boolean; }; + WoltLabCaret: { + paragraphAfterBlock(quote: HTMLElement): void; + }; WoltLabEvent: { register(event: string, callback: (data: WoltLabEventData) => void): void; }; + WoltLabSource: { + isActive(): boolean; + }; } export interface WoltLabEventData { diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Quote.js b/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Quote.js deleted file mode 100644 index 2541bed0c6..0000000000 --- a/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Quote.js +++ /dev/null @@ -1,310 +0,0 @@ -/** - * Manages quotes. - * - * @author Alexander Ebert - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License - * @module WoltLabSuite/Core/Ui/Redactor/Quote - */ -define(['Core', 'EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', './Metacode', './PseudoHeader'], function (Core, EventHandler, EventKey, Language, StringUtil, DomUtil, UiDialog, UiRedactorMetacode, UiRedactorPseudoHeader) { - "use strict"; - - if (!COMPILER_TARGET_DEFAULT) { - var Fake = function() {}; - Fake.prototype = { - init: function() {}, - _insertQuote: function() {}, - _click: function() {}, - _observeLoad: function() {}, - _edit: function() {}, - _save: function() {}, - _setTitle: function() {}, - _delete: function() {}, - _dialogSetup: function() {}, - _dialogSubmit: function() {} - }; - return Fake; - } - - var _headerHeight = 0; - - /** - * @param {Object} editor editor instance - * @param {jQuery} button toolbar button - * @constructor - */ - function UiRedactorQuote(editor, button) { this.init(editor, button); } - UiRedactorQuote.prototype = { - /** - * Initializes the quote management. - * - * @param {Object} editor editor instance - * @param {jQuery} button toolbar button - */ - init: function(editor, button) { - this._quote = null; - this._quotes = elByTag('woltlab-quote', editor.$editor[0]); - this._editor = editor; - this._elementId = this._editor.$element[0].id; - - EventHandler.add('com.woltlab.wcf.redactor2', 'observe_load_' + this._elementId, this._observeLoad.bind(this)); - - this._editor.button.addCallback(button, this._click.bind(this)); - - // static bind to ensure that removing works - this._callbackEdit = this._edit.bind(this); - - // bind listeners on init - this._observeLoad(); - - // quote manager - EventHandler.add('com.woltlab.wcf.redactor2', 'insertQuote_' + this._elementId, this._insertQuote.bind(this)); - }, - - /** - * Inserts a quote. - * - * @param {Object} data quote data - * @protected - */ - _insertQuote: function (data) { - if (this._editor.WoltLabSource.isActive()) { - return; - } - - EventHandler.fire('com.woltlab.wcf.redactor2', 'showEditor'); - - var editor = this._editor.core.editor()[0]; - this._editor.selection.restore(); - - this._editor.buffer.set(); - - // caret must be within a `

`, if it is not: move it - var block = this._editor.selection.block(); - if (block === false) { - this._editor.focus.end(); - block = this._editor.selection.block(); - } - - while (block && block.parentNode !== editor) { - block = block.parentNode; - } - - var quote = elCreate('woltlab-quote'); - elData(quote, 'author', data.author); - elData(quote, 'link', data.link); - - var content = data.content; - if (data.isText) { - content = StringUtil.escapeHTML(content); - content = '

' + content + '

'; - content = content.replace(/\n\n/g, '

'); - content = content.replace(/\n/g, '
'); - } - else { - //noinspection JSUnresolvedFunction - content = UiRedactorMetacode.convertFromHtml(this._editor.$element[0].id, content); - } - - // bypass the editor as `insert.html()` doesn't like us - quote.innerHTML = content; - - block.parentNode.insertBefore(quote, block.nextSibling); - - if (block.nodeName === 'P' && (block.innerHTML === '
' || block.innerHTML.replace(/\u200B/g, '') === '')) { - block.parentNode.removeChild(block); - } - - // avoid adjacent blocks that are not paragraphs - var sibling = quote.previousElementSibling; - if (sibling && sibling.nodeName !== 'P') { - sibling = elCreate('p'); - sibling.textContent = '\u200B'; - quote.parentNode.insertBefore(sibling, quote); - } - - this._editor.WoltLabCaret.paragraphAfterBlock(quote); - - this._editor.buffer.set(); - }, - - /** - * Toggles the quote block on button click. - * - * @protected - */ - _click: function() { - this._editor.button.toggle({}, 'woltlab-quote', 'func', 'block.format'); - - var quote = this._editor.selection.block(); - if (quote && quote.nodeName === 'WOLTLAB-QUOTE') { - this._setTitle(quote); - - quote.addEventListener('click', this._callbackEdit); - - // work-around for Safari - this._editor.caret.end(quote); - } - }, - - /** - * Binds event listeners and sets quote title on both editor - * initialization and when switching back from code view. - * - * @protected - */ - _observeLoad: function() { - var quote; - for (var i = 0, length = this._quotes.length; i < length; i++) { - quote = this._quotes[i]; - - quote.addEventListener('mousedown', this._callbackEdit); - this._setTitle(quote); - } - }, - - /** - * Opens the dialog overlay to edit the quote's properties. - * - * @param {Event} event event object - * @protected - */ - _edit: function(event) { - var quote = event.currentTarget; - - if (_headerHeight === 0) { - _headerHeight = UiRedactorPseudoHeader.getHeight(quote); - } - - // check if the click hit the header - var offset = DomUtil.offset(quote); - if (event.pageY > offset.top && event.pageY < (offset.top + _headerHeight)) { - event.preventDefault(); - - this._editor.selection.save(); - this._quote = quote; - - UiDialog.open(this); - } - }, - - /** - * Saves the changes to the quote's properties. - * - * @protected - */ - _dialogSubmit: function() { - var id = 'redactor-quote-' + this._elementId; - var urlInput = elById(id + '-url'); - - var url = urlInput.value.replace(/\u200B/g, '').trim(); - // simple test to check if it at least looks like it could be a valid url - if (url.length && !/^https?:\/\/[^\/]+/.test(url)) { - elInnerError(urlInput, Language.get('wcf.editor.quote.url.error.invalid')); - return; - } - else { - elInnerError(urlInput, false); - } - - // set author - elData(this._quote, 'author', elById(id + '-author').value); - - // set url - elData(this._quote, 'link', url); - - this._setTitle(this._quote); - this._editor.caret.after(this._quote); - - UiDialog.close(this); - }, - - /** - * Sets or updates the quote's header title. - * - * @param {Element} quote quote element - * @protected - */ - _setTitle: function(quote) { - var title = Language.get('wcf.editor.quote.title', { - author: elData(quote, 'author'), - url: elData(quote, 'url') - }); - - if (elData(quote, 'title') !== title) { - elData(quote, 'title', title); - } - }, - - _delete: function (event) { - event.preventDefault(); - - var caretEnd = this._quote.nextElementSibling || this._quote.previousElementSibling; - if (caretEnd === null && this._quote.parentNode !== this._editor.core.editor()[0]) { - caretEnd = this._quote.parentNode; - } - - if (caretEnd === null) { - this._editor.code.set(''); - this._editor.focus.end(); - } - else { - elRemove(this._quote); - this._editor.caret.end(caretEnd); - } - - UiDialog.close(this); - }, - - _dialogSetup: function() { - var id = 'redactor-quote-' + this._elementId, - idAuthor = id + '-author', - idButtonDelete = id + '-button-delete', - idButtonSave = id + '-button-save', - idUrl = id + '-url'; - - 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(idAuthor).value = elData(this._quote, 'author'); - elById(idUrl).value = elData(this._quote, 'link'); - }).bind(this), - - title: Language.get('wcf.editor.quote.edit') - }, - source: '

' - + '
' - + '
' - + '
' - + '' - + '
' - + '
' - + '
' - + '
' - + '
' - + '' - + '' + Language.get('wcf.editor.quote.url.description') + '' - + '
' - + '
' - + '
' - + '
' - + '' - + '' - + '
' - }; - } - }; - - return UiRedactorQuote; -}); diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Quote.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Quote.ts new file mode 100644 index 0000000000..78090e7af4 --- /dev/null +++ b/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Quote.ts @@ -0,0 +1,297 @@ +/** + * Manages quotes. + * + * @author Alexander Ebert + * @copyright 2001-2019 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Ui/Redactor/Quote + */ + +import * as Core from "../../Core"; +import DomUtil from "../../Dom/Util"; +import * as EventHandler from "../../Event/Handler"; +import * as Language from "../../Language"; +import * as StringUtil from "../../StringUtil"; +import UiDialog from "../Dialog"; +import { DialogCallbackSetup } from "../Dialog/Data"; +import { RedactorEditor } from "./Editor"; +import * as UiRedactorMetacode from "./Metacode"; +import * as UiRedactorPseudoHeader from "./PseudoHeader"; + +interface QuoteData { + author: string; + content: string; + isText: boolean; + link: string; +} + +let _headerHeight = 0; + +class UiRedactorQuote { + protected readonly _editor: RedactorEditor; + protected readonly _elementId: string; + protected _quote: HTMLElement | null = null; + + /** + * Initializes the quote management. + */ + constructor(editor: RedactorEditor, button: JQuery) { + this._editor = editor; + this._elementId = this._editor.$element[0].id; + + EventHandler.add("com.woltlab.wcf.redactor2", `observe_load_${this._elementId}`, () => this._observeLoad()); + + this._editor.button.addCallback(button, this._click.bind(this)); + + // bind listeners on init + this._observeLoad(); + + // quote manager + EventHandler.add("com.woltlab.wcf.redactor2", `insertQuote_${this._elementId}`, (data) => this._insertQuote(data)); + } + + /** + * Inserts a quote. + */ + protected _insertQuote(data: QuoteData): void { + if (this._editor.WoltLabSource.isActive()) { + return; + } + + EventHandler.fire("com.woltlab.wcf.redactor2", "showEditor"); + + const editor = this._editor.core.editor()[0]; + this._editor.selection.restore(); + + this._editor.buffer.set(); + + // caret must be within a `

`, if it is not: move it + let block = this._editor.selection.block(); + if (block === false) { + this._editor.focus.end(); + block = this._editor.selection.block() as HTMLElement; + } + + while (block && block.parentElement !== editor) { + block = block.parentElement!; + } + + const quote = document.createElement("woltlab-quote"); + quote.dataset.author = data.author; + quote.dataset.link = data.link; + + let content = data.content; + if (data.isText) { + content = StringUtil.escapeHTML(content); + content = `

${content}

`; + content = content.replace(/\n\n/g, "

"); + content = content.replace(/\n/g, "
"); + } else { + content = UiRedactorMetacode.convertFromHtml(this._editor.$element[0].id, content); + } + + // bypass the editor as `insert.html()` doesn't like us + quote.innerHTML = content; + + const blockParent = block.parentElement!; + blockParent.insertBefore(quote, block.nextSibling); + + if (block.nodeName === "P" && (block.innerHTML === "
" || block.innerHTML.replace(/\u200B/g, "") === "")) { + blockParent.removeChild(block); + } + + // avoid adjacent blocks that are not paragraphs + let sibling = quote.previousElementSibling; + if (sibling && sibling.nodeName !== "P") { + sibling = document.createElement("p"); + sibling.textContent = "\u200B"; + quote.insertAdjacentElement("beforebegin", sibling); + } + + this._editor.WoltLabCaret.paragraphAfterBlock(quote); + + this._editor.buffer.set(); + } + + /** + * Toggles the quote block on button click. + */ + protected _click(): void { + this._editor.button.toggle({}, "woltlab-quote", "func", "block.format"); + + const quote = this._editor.selection.block(); + if (quote && quote.nodeName === "WOLTLAB-QUOTE") { + this._setTitle(quote); + + quote.addEventListener("click", (ev) => this._edit(ev)); + + // work-around for Safari + this._editor.caret.end(quote); + } + } + + /** + * Binds event listeners and sets quote title on both editor + * initialization and when switching back from code view. + */ + protected _observeLoad(): void { + document.querySelectorAll("woltlab-quote").forEach((quote: HTMLElement) => { + quote.addEventListener("mousedown", (ev) => this._edit(ev)); + this._setTitle(quote); + }); + } + + /** + * Opens the dialog overlay to edit the quote's properties. + */ + protected _edit(event: MouseEvent): void { + const quote = event.currentTarget as HTMLElement; + + if (_headerHeight === 0) { + _headerHeight = UiRedactorPseudoHeader.getHeight(quote); + } + + // check if the click hit the header + const offset = DomUtil.offset(quote); + if (event.pageY > offset.top && event.pageY < offset.top + _headerHeight) { + event.preventDefault(); + + this._editor.selection.save(); + this._quote = quote; + + UiDialog.open(this); + } + } + + /** + * Saves the changes to the quote's properties. + * + * @protected + */ + _dialogSubmit(): void { + const id = `redactor-quote-${this._elementId}`; + const urlInput = document.getElementById(`${id}-url`) as HTMLInputElement; + + const url = urlInput.value.replace(/\u200B/g, "").trim(); + // simple test to check if it at least looks like it could be a valid url + if (url.length && !/^https?:\/\/[^/]+/.test(url)) { + DomUtil.innerError(urlInput, Language.get("wcf.editor.quote.url.error.invalid")); + + return; + } else { + DomUtil.innerError(urlInput, false); + } + + const quote = this._quote!; + + // set author + const author = document.getElementById(id + "-author") as HTMLInputElement; + quote.dataset.author = author.value; + + // set url + quote.dataset.link = url; + + this._setTitle(quote); + this._editor.caret.after(quote); + + UiDialog.close(this); + } + + /** + * Sets or updates the quote's header title. + */ + protected _setTitle(quote: HTMLElement): void { + const title = Language.get("wcf.editor.quote.title", { + author: quote.dataset.author!, + url: quote.dataset.url!, + }); + + if (quote.dataset.title !== title) { + quote.dataset.title = title; + } + } + + protected _delete(event: MouseEvent): void { + event.preventDefault(); + + const quote = this._quote!; + + let caretEnd = quote.nextElementSibling || quote.previousElementSibling; + if (caretEnd === null && quote.parentElement !== this._editor.core.editor()[0]) { + caretEnd = quote.parentElement; + } + + if (caretEnd === null) { + this._editor.code.set(""); + this._editor.focus.end(); + } else { + quote.remove(); + this._editor.caret.end(caretEnd); + } + + UiDialog.close(this); + } + + _dialogSetup(): ReturnType { + const id = `redactor-quote-${this._elementId}`; + const idAuthor = `${id}-author`; + const idButtonDelete = `${id}-button-delete`; + const idButtonSave = `${id}-button-save`; + const idUrl = `${id}-url`; + + 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 author = document.getElementById(idAuthor) as HTMLInputElement; + author.value = this._quote!.dataset.author || ""; + + const url = document.getElementById(idUrl) as HTMLInputElement; + url.value = this._quote!.dataset.link || ""; + }, + + title: Language.get("wcf.editor.quote.edit"), + }, + source: `

+
+
+ +
+
+ +
+
+
+
+ +
+
+ + ${Language.get("wcf.editor.quote.url.description")} +
+
+
+
+ + +
`, + }; + } +} + +Core.enableLegacyInheritance(UiRedactorQuote); + +export = UiRedactorQuote; -- 2.20.1