From: Matthias Schmidt Date: Tue, 15 Jun 2021 05:39:49 +0000 (+0200) Subject: Merge branch '5.3' into 5.4 X-Git-Tag: 5.4.0_Beta_2~17 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=9a29c75d71e47289a7e8e627d59851bd12c46b81;p=GitHub%2FWoltLab%2FWCF.git Merge branch '5.3' into 5.4 --- 9a29c75d71e47289a7e8e627d59851bd12c46b81 diff --cc ts/WoltLabSuite/Core/Ui/Redactor/Link.ts index 79ea1979d2,0000000000..e6de9bf698 mode 100644,000000..100644 --- a/ts/WoltLabSuite/Core/Ui/Redactor/Link.ts +++ b/ts/WoltLabSuite/Core/Ui/Redactor/Link.ts @@@ -1,110 -1,0 +1,106 @@@ +/** + * @woltlabExcludeBundle tiny + */ + +import DomUtil from "../../Dom/Util"; +import * as Language from "../../Language"; +import UiDialog from "../Dialog"; +import { DialogCallbackObject, DialogCallbackSetup } from "../Dialog/Data"; + +type SubmitCallback = () => boolean; + +interface LinkOptions { + insert: boolean; + submitCallback: SubmitCallback; +} + +class UiRedactorLink implements DialogCallbackObject { + private boundListener = false; + private submitCallback: SubmitCallback; + + open(options: LinkOptions) { + UiDialog.open(this); + + UiDialog.setTitle(this, Language.get("wcf.editor.link." + (options.insert ? "add" : "edit"))); + + const submitButton = document.getElementById("redactor-modal-button-action")!; + submitButton.textContent = Language.get("wcf.global.button." + (options.insert ? "insert" : "save")); + + this.submitCallback = options.submitCallback; + + // Redactor might modify the button, thus we cannot bind it in the dialog's `onSetup()` callback. + if (!this.boundListener) { + this.boundListener = true; + + submitButton.addEventListener("click", () => this.submit()); + } + } + + private submit(): void { + if (this.submitCallback()) { + UiDialog.close(this); + } else { + const url = document.getElementById("redactor-link-url") as HTMLInputElement; + + const errorMessage = url.value.trim() === "" ? "wcf.global.form.error.empty" : "wcf.editor.link.error.invalid"; + DomUtil.innerError(url, Language.get(errorMessage)); + } + } + + _dialogSetup(): ReturnType { + return { + id: "redactorDialogLink", + options: { + onClose: () => { + const url = document.getElementById("redactor-link-url") as HTMLInputElement; + const small = url.nextElementSibling; + if (small && small.nodeName === "SMALL") { + small.remove(); + } + }, + onSetup: (content) => { + const submitButton = content.querySelector(".formSubmit > .buttonPrimary") as HTMLButtonElement; + + if (submitButton !== null) { + content.querySelectorAll('input[type="url"], input[type="text"]').forEach((input: HTMLInputElement) => { + input.addEventListener("keyup", (event) => { + if (event.key === "Enter") { + submitButton.click(); + } + }); + }); + } + }, - onShow: () => { - const url = document.getElementById("redactor-link-url") as HTMLInputElement; - url.focus(); - }, + }, + source: `
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ +
`, + }; + } +} + +let uiRedactorLink: UiRedactorLink; + +export function showDialog(options: LinkOptions): void { + if (!uiRedactorLink) { + uiRedactorLink = new UiRedactorLink(); + } + + uiRedactorLink.open(options); +} diff --cc ts/WoltLabSuite/Core/Ui/Redactor/Quote.ts index ad379d9dde,0000000000..5afd1f9ef6 mode 100644,000000..100644 --- a/ts/WoltLabSuite/Core/Ui/Redactor/Quote.ts +++ b/ts/WoltLabSuite/Core/Ui/Redactor/Quote.ts @@@ -1,303 -1,0 +1,305 @@@ +/** + * Manages quotes. + * + * @author Alexander Ebert + * @copyright 2001-2019 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Ui/Redactor/Quote + * @woltlabExcludeBundle tiny + */ + +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 readonly _knownElements = new WeakSet(); + 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) => { + if (!this._knownElements.has(quote)) { + quote.addEventListener("mousedown", (ev) => this._edit(ev)); + this._setTitle(quote); + + this._knownElements.add(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(); ++ window.setTimeout(() => { ++ this._editor.selection.restore(); ++ }, 100); + + 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; diff --cc wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Link.js index 7c3c77b7de,d56a9ce64e..f1956bc9d3 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Link.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Link.js @@@ -1,95 -1,84 +1,91 @@@ -define(['Core', 'EventKey', 'Language', 'Ui/Dialog'], function(Core, EventKey, Language, UiDialog) { - "use strict"; - - if (!COMPILER_TARGET_DEFAULT) { - var Fake = function() {}; - Fake.prototype = { - showDialog: function() {}, - _submit: function() {}, - _dialogSetup: function() {} - }; - return Fake; - } - - var _boundListener = false; - var _callback = null; - - return { - showDialog: function(options) { - UiDialog.open(this); - - UiDialog.setTitle(this, Language.get('wcf.editor.link.' + (options.insert ? 'add' : 'edit'))); - - var submitButton = elById('redactor-modal-button-action'); - submitButton.textContent = Language.get('wcf.global.button.' + (options.insert ? 'insert' : 'save')); - - _callback = options.submitCallback; - - if (!_boundListener) { - _boundListener = true; - - submitButton.addEventListener(WCF_CLICK_EVENT, this._submit.bind(this)); - } - }, - - _submit: function() { - if (_callback()) { - UiDialog.close(this); - } - else { - var url = elById('redactor-link-url'); - elInnerError(url, Language.get((url.value.trim() === '' ? 'wcf.global.form.error.empty' : 'wcf.editor.link.error.invalid'))); - } - }, - - _dialogSetup: function() { - return { - id: 'redactorDialogLink', - options: { - onClose: function() { - var url = elById('redactor-link-url'); - var small = (url.nextElementSibling && url.nextElementSibling.nodeName === 'SMALL') ? url.nextElementSibling : null; - if (small !== null) { - elRemove(small); - } - }, - onSetup: function (content) { - var submitButton = elBySel('.formSubmit > .buttonPrimary', content); - - if (submitButton !== null) { - elBySelAll('input[type="url"], input[type="text"]', content, function (input) { - input.addEventListener('keyup', function (event) { - if (EventKey.Enter(event)) { - Core.triggerEvent(submitButton, 'click'); - } - }); - }); - } - } - }, - source: '
' - + '
' - + '
' - + '
' - + '
' - + '
' - + '
' - + '
' - + '
' - + '' - + '
' - }; - } - }; +/** + * @woltlabExcludeBundle tiny + */ +define(["require", "exports", "tslib", "../../Dom/Util", "../../Language", "../Dialog"], function (require, exports, tslib_1, Util_1, Language, Dialog_1) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.showDialog = void 0; + Util_1 = tslib_1.__importDefault(Util_1); + Language = tslib_1.__importStar(Language); + Dialog_1 = tslib_1.__importDefault(Dialog_1); + class UiRedactorLink { + constructor() { + this.boundListener = false; + } + open(options) { + Dialog_1.default.open(this); + Dialog_1.default.setTitle(this, Language.get("wcf.editor.link." + (options.insert ? "add" : "edit"))); + const submitButton = document.getElementById("redactor-modal-button-action"); + submitButton.textContent = Language.get("wcf.global.button." + (options.insert ? "insert" : "save")); + this.submitCallback = options.submitCallback; + // Redactor might modify the button, thus we cannot bind it in the dialog's `onSetup()` callback. + if (!this.boundListener) { + this.boundListener = true; + submitButton.addEventListener("click", () => this.submit()); + } + } + submit() { + if (this.submitCallback()) { + Dialog_1.default.close(this); + } + else { + const url = document.getElementById("redactor-link-url"); + const errorMessage = url.value.trim() === "" ? "wcf.global.form.error.empty" : "wcf.editor.link.error.invalid"; + Util_1.default.innerError(url, Language.get(errorMessage)); + } + } + _dialogSetup() { + return { + id: "redactorDialogLink", + options: { + onClose: () => { + const url = document.getElementById("redactor-link-url"); + const small = url.nextElementSibling; + if (small && small.nodeName === "SMALL") { + small.remove(); + } + }, + onSetup: (content) => { + const submitButton = content.querySelector(".formSubmit > .buttonPrimary"); + if (submitButton !== null) { + content.querySelectorAll('input[type="url"], input[type="text"]').forEach((input) => { + input.addEventListener("keyup", (event) => { + if (event.key === "Enter") { + submitButton.click(); + } + }); + }); + } + }, - onShow: () => { - const url = document.getElementById("redactor-link-url"); - url.focus(); - }, + }, + source: `
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ +
`, + }; + } + } + let uiRedactorLink; + function showDialog(options) { + if (!uiRedactorLink) { + uiRedactorLink = new UiRedactorLink(); + } + uiRedactorLink.open(options); + } + exports.showDialog = showDialog; }); diff --cc wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Quote.js index 9d22917eb6,64b0238c04..02d8bc2dd9 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Quote.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Quote.js @@@ -2,238 -2,311 +2,240 @@@ * 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 + * @woltlabExcludeBundle tiny */ -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(WCF_CLICK_EVENT, 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 () { - window.setTimeout((function () { - this._editor.selection.restore(); - }).bind(this), 100); - - UiDialog.destroy(this); - }).bind(this), - - onSetup: (function() { - elById(idButtonDelete).addEventListener(WCF_CLICK_EVENT, 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; +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"; + 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. + */ + constructor(editor, button) { + this._knownElements = new WeakSet(); + this._quote = null; + 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. + */ + _insertQuote(data) { + 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(); + } + 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. + */ + _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", (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. + */ + _observeLoad() { + document.querySelectorAll("woltlab-quote").forEach((quote) => { + if (!this._knownElements.has(quote)) { + quote.addEventListener("mousedown", (ev) => this._edit(ev)); + this._setTitle(quote); + this._knownElements.add(quote); + } + }); + } + /** + * Opens the dialog overlay to edit the quote's properties. + */ + _edit(event) { + const quote = event.currentTarget; + if (_headerHeight === 0) { + _headerHeight = UiRedactorPseudoHeader.getHeight(quote); + } + // check if the click hit the header + 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; + Dialog_1.default.open(this); + } + } + /** + * Saves the changes to the quote's properties. + * + * @protected + */ + _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)) { + Util_1.default.innerError(urlInput, Language.get("wcf.editor.quote.url.error.invalid")); + return; + } + else { + Util_1.default.innerError(urlInput, false); + } + const quote = this._quote; + // set author + const author = document.getElementById(id + "-author"); + quote.dataset.author = author.value; + // set url + 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. + */ + _setTitle(quote) { + 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; + } + } + _delete(event) { + 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); + } + 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: () => { - this._editor.selection.restore(); ++ window.setTimeout(() => { ++ this._editor.selection.restore(); ++ }, 100); + 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")} +
+
+
+
+ + +
`, + }; + } + } + Core.enableLegacyInheritance(UiRedactorQuote); + return UiRedactorQuote; }); diff --cc wcfsetup/install/lang/de.xml index 8d0a6f9002,fb8254014b..888f69aaf8 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@@ -412,7 -401,8 +412,7 @@@ - Hinweise zur Benutzung in der Entwickler-Dokumentation.]]> - {$object->name} wirklich löschen?]]> - Hinweise zur Benutzung in der Entwickler-Dokumentation.]]> ++ Hinweise zur Benutzung in der Entwickler-Dokumentation.]]> diff --cc wcfsetup/install/lang/en.xml index ba88751f93,6ae06cd205..3b6d47dc4e --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@@ -389,7 -377,8 +389,7 @@@ - usage instructions in the developer documentation.]]> - {$object->name}?]]> - usage instructions in the developer documentation.]]> ++ usage instructions in the developer documentation.]]>