4 * @author Alexander Ebert
5 * @copyright 2001-2019 WoltLab GmbH
6 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
7 * @module WoltLabSuite/Core/Ui/Redactor/Quote
8 * @woltlabExcludeBundle tiny
10 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
) {
12 Core
= tslib_1
.__importStar(Core
);
13 Util_1
= tslib_1
.__importDefault(Util_1
);
14 EventHandler
= tslib_1
.__importStar(EventHandler
);
15 Language
= tslib_1
.__importStar(Language
);
16 StringUtil
= tslib_1
.__importStar(StringUtil
);
17 Dialog_1
= tslib_1
.__importDefault(Dialog_1
);
18 UiRedactorMetacode
= tslib_1
.__importStar(UiRedactorMetacode
);
19 UiRedactorPseudoHeader
= tslib_1
.__importStar(UiRedactorPseudoHeader
);
20 let _headerHeight
= 0;
21 class UiRedactorQuote
{
23 * Initializes the quote management.
25 constructor(editor
, button
) {
26 this._knownElements
= new WeakSet();
28 this._editor
= editor
;
29 this._elementId
= this._editor
.$element
[0].id
;
30 EventHandler
.add("com.woltlab.wcf.redactor2", `observe_load_${this._elementId}`, () => this._observeLoad());
31 this._editor
.button
.addCallback(button
, this._click
.bind(this));
32 // bind listeners on init
35 EventHandler
.add("com.woltlab.wcf.redactor2", `insertQuote_${this._elementId}`, (data
) => this._insertQuote(data
));
41 if (this._editor
.WoltLabSource
.isActive()) {
44 EventHandler
.fire("com.woltlab.wcf.redactor2", "showEditor");
45 const editor
= this._editor
.core
.editor()[0];
46 this._editor
.selection
.restore();
47 this._editor
.buffer
.set();
48 // caret must be within a `<p>`, if it is not: move it
49 let block
= this._editor
.selection
.block();
50 if (block
=== false) {
51 this._editor
.focus
.end();
52 block
= this._editor
.selection
.block();
54 while (block
&& block
.parentElement
!== editor
) {
55 block
= block
.parentElement
;
57 const quote
= document
.createElement("woltlab-quote");
58 quote
.dataset
.author
= data
.author
;
59 quote
.dataset
.link
= data
.link
;
60 let content
= data
.content
;
62 content
= StringUtil
.escapeHTML(content
);
63 content
= `<p>${content}</p>`;
64 content
= content
.replace(/\n\n/g, "</p><p>");
65 content
= content
.replace(/\n/g, "<br>");
68 content
= UiRedactorMetacode
.convertFromHtml(this._editor
.$element
[0].id
, content
);
70 // bypass the editor as `insert.html()` doesn't like us
71 quote
.innerHTML
= content
;
72 const blockParent
= block
.parentElement
;
73 blockParent
.insertBefore(quote
, block
.nextSibling
);
74 if (block
.nodeName
=== "P" && (block
.innerHTML
=== "<br>" || block
.innerHTML
.replace(/\u200B/g, "") === "")) {
75 blockParent
.removeChild(block
);
77 // avoid adjacent blocks that are not paragraphs
78 let sibling
= quote
.previousElementSibling
;
79 if (sibling
&& sibling
.nodeName
!== "P") {
80 sibling
= document
.createElement("p");
81 sibling
.textContent
= "\u200B";
82 quote
.insertAdjacentElement("beforebegin", sibling
);
84 this._editor
.WoltLabCaret
.paragraphAfterBlock(quote
);
85 this._editor
.buffer
.set();
88 * Toggles the quote block on button click.
91 this._editor
.button
.toggle({}, "woltlab-quote", "func", "block.format");
92 const quote
= this._editor
.selection
.block();
93 if (quote
&& quote
.nodeName
=== "WOLTLAB-QUOTE") {
94 this._setTitle(quote
);
95 quote
.addEventListener("click", (ev
) => this._edit(ev
));
96 // work-around for Safari
97 this._editor
.caret
.end(quote
);
101 * Binds event listeners and sets quote title on both editor
102 * initialization and when switching back from code view.
105 document
.querySelectorAll("woltlab-quote").forEach((quote
) => {
106 if (!this._knownElements
.has(quote
)) {
107 quote
.addEventListener("mousedown", (ev
) => this._edit(ev
));
108 this._setTitle(quote
);
109 this._knownElements
.add(quote
);
114 * Opens the dialog overlay to edit the quote's properties.
117 const quote
= event
.currentTarget
;
118 if (_headerHeight
=== 0) {
119 _headerHeight
= UiRedactorPseudoHeader
.getHeight(quote
);
121 // check if the click hit the header
122 const offset
= Util_1
.default.offset(quote
);
123 if (event
.pageY
> offset
.top
&& event
.pageY
< offset
.top
+ _headerHeight
) {
124 event
.preventDefault();
125 this._editor
.selection
.save();
127 Dialog_1
.default.open(this);
131 * Saves the changes to the quote's properties.
136 const id
= `redactor-quote-${this._elementId}`;
137 const urlInput
= document
.getElementById(`${id}-url`);
138 const url
= urlInput
.value
.replace(/\u200B/g, "").trim();
139 // simple test to check if it at least looks like it could be a valid url
140 if (url
.length
&& !/^https?:\/\/[^/]+/.test(url
)) {
141 Util_1
.default.innerError(urlInput
, Language
.get("wcf.editor.quote.url.error.invalid"));
145 Util_1
.default.innerError(urlInput
, false);
147 const quote
= this._quote
;
149 const author
= document
.getElementById(id
+ "-author");
150 quote
.dataset
.author
= author
.value
;
152 quote
.dataset
.link
= url
;
153 this._setTitle(quote
);
154 this._editor
.caret
.after(quote
);
155 Dialog_1
.default.close(this);
158 * Sets or updates the quote's header title.
161 const title
= Language
.get("wcf.editor.quote.title", {
162 author
: quote
.dataset
.author
,
163 url
: quote
.dataset
.url
,
165 if (quote
.dataset
.title
!== title
) {
166 quote
.dataset
.title
= title
;
170 event
.preventDefault();
171 const quote
= this._quote
;
172 let caretEnd
= quote
.nextElementSibling
|| quote
.previousElementSibling
;
173 if (caretEnd
=== null && quote
.parentElement
!== this._editor
.core
.editor()[0]) {
174 caretEnd
= quote
.parentElement
;
176 if (caretEnd
=== null) {
177 this._editor
.code
.set("");
178 this._editor
.focus
.end();
182 this._editor
.caret
.end(caretEnd
);
184 Dialog_1
.default.close(this);
187 const id
= `redactor-quote-${this._elementId}`;
188 const idAuthor
= `${id}-author`;
189 const idButtonDelete
= `${id}-button-delete`;
190 const idButtonSave
= `${id}-button-save`;
191 const idUrl
= `${id}-url`;
196 window
.setTimeout(() => {
197 this._editor
.selection
.restore();
199 Dialog_1
.default.destroy(this);
202 const button
= document
.getElementById(idButtonDelete
);
203 button
.addEventListener("click", (ev
) => this._delete(ev
));
206 const author
= document
.getElementById(idAuthor
);
207 author
.value
= this._quote
.dataset
.author
|| "";
208 const url
= document
.getElementById(idUrl
);
209 url
.value
= this._quote
.dataset
.link
|| "";
211 title
: Language
.get("wcf.editor.quote.edit"),
213 source
: `<div class="section">
216 <label for="${idAuthor}">${Language.get("wcf.editor.quote.author")}</label>
219 <input type="text" id="${idAuthor}" class="long" data-dialog-submit-on-enter="true">
224 <label for="${idUrl}">${Language.get("wcf.editor.quote.url")}</label>
227 <input type="text" id="${idUrl}" class="long" data-dialog-submit-on-enter="true">
228 <small>${Language.get("wcf.editor.quote.url.description")}</small>
232 <div class="formSubmit">
233 <button id="${idButtonSave}" class="buttonPrimary" data-type="submit">${Language.get("wcf.global.button.save")}</button>
234 <button id="${idButtonDelete}">${Language.get("wcf.global.button.delete")}</button>
239 Core
.enableLegacyInheritance(UiRedactorQuote
);
240 return UiRedactorQuote
;