Added update support for styles (WIP)
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / js / 3rdParty / redactor / plugins / wbbcode.js
CommitLineData
5e18a011
AE
1if (!RedactorPlugins) var RedactorPlugins = {};
2
3/**
4 * Provides the smiley button and modifies the source mode to transform HTML into BBCodes.
c088b28c 5 *
5e18a011 6 * @author Alexander Ebert, Marcel Werk
86797f35 7 * @copyright 2001-2015 WoltLab GmbH
c088b28c 8 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
d45eaff6 9 */
aaec55d9
AE
10RedactorPlugins.wbbcode = function() {
11 "use strict";
12
ce826197
AE
13 var $skipOnSyncReplacementOnce = false;
14
aaec55d9
AE
15 return {
16 /**
17 * Initializes the RedactorPlugins.wbbcode plugin.
18 */
19 init: function() {
20 var $identifier = this.$textarea.wcfIdentify();
03ae19a7 21
b3d84e05 22 this.opts.initCallback = (function() {
78c7caf5
AE
23 window.addEventListener('unload', (function(event) {
24 this.code.startSync();
25
26 this.$textarea.val(this.wbbcode.convertFromHtml(this.$textarea.val()));
27 }).bind(this));
28
339afbc6
AE
29 if ($.browser.msie) {
30 this.$editor.addClass('msie');
31 }
78c7caf5 32 this.$textarea[0].setAttribute('data-is-dirty', true);
aaec55d9 33 // use stored editor contents
1858c2fb 34 var $content = $.trim(this.wutil.getOption('woltlab.originalValue'));
aaec55d9 35 if ($content.length) {
1b0b8480 36 this.wutil.replaceText($content);
6df8c3dd
AE
37
38 // ensure that the caret is not within a quote tag
39 this.wutil.selectionEndOfEditor();
aaec55d9
AE
40 }
41
1858c2fb 42 delete this.opts.woltlab.originalValue;
21682647
AE
43
44 $(document).trigger('resize');
5fb6f47a 45 this.wutil.saveSelection();
b3d84e05 46 }).bind(this);
b2d3a1d6 47
aaec55d9
AE
48 this.opts.pasteBeforeCallback = $.proxy(this.wbbcode._pasteBeforeCallback, this);
49 this.opts.pasteCallback = $.proxy(this.wbbcode._pasteCallback, this);
b2d3a1d6 50
aaec55d9 51 var $mpCleanOnSync = this.clean.onSync;
ce826197 52 this.clean.onSync = (function(html) {
eaa0f9b0
AE
53 html = html.replace(/\u200C/g, '__wcf_zwnj__');
54 html = html.replace(/\u200D/g, '__wcf_zwj__');
55
ce826197
AE
56 if ($skipOnSyncReplacementOnce === true) {
57 $skipOnSyncReplacementOnce = false;
58 }
59 else {
60 html = html.replace(/<p><br([^>]+)?><\/p>/g, '<p>@@@wcf_empty_line@@@</p>');
61 }
62
eaa0f9b0
AE
63 html = $mpCleanOnSync.call(this, html);
64
65 html = html.replace(/__wcf_zwnj__/g, '\u200C');
66 return html.replace(/__wcf_zwj__/g, '\u200D');
ce826197 67 }).bind(this);
b2d3a1d6 68
1858c2fb 69 if (this.wutil.getOption('woltlab.autosaveOnce')) {
ae4c9208 70 this.wutil.saveTextToStorage();
1858c2fb 71 delete this.opts.woltlab.autosaveOnce;
aaec55d9 72 }
b2d3a1d6 73
aaec55d9
AE
74 // we do not support table heads
75 var $tableButton = this.button.get('table');
76 if ($tableButton.length) {
77 var $dropdown = $tableButton.data('dropdown');
78
79 // remove head add/delete
80 $dropdown.find('.redactor-dropdown-add_head').parent().remove();
81 $dropdown.find('.redactor-dropdown-delete_head').parent().remove();
82
83 // add visual divider
84 $('<li class="dropdownDivider" />').insertBefore($dropdown.find('.redactor-dropdown-delete_table').parent());
85
86 // toggle dropdown options
87 $tableButton.click($.proxy(this.wbbcode._tableButtonClick, this));
88 }
5a3e1a41 89
aaec55d9
AE
90 // handle 'insert quote' button
91 WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'insertBBCode_quote_' + $identifier, $.proxy(function(data) {
92 data.cancel = true;
93
94 this.wbbcode._handleInsertQuote();
95 }, this));
d45eaff6 96
5fb44399
AE
97 // handle 'insert code' button
98 WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'insertBBCode_code_' + $identifier, $.proxy(function(data) {
99 data.cancel = true;
100
101 this.wbbcode._handleInsertCode(null, true);
102 }, this));
103
aaec55d9
AE
104 // handle keydown
105 WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'keydown_' + $identifier, $.proxy(this.wbbcode._keydownCallback, this));
106 WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'keyup_' + $identifier, $.proxy(this.wbbcode._keyupCallback, this));
d45eaff6 107
aaec55d9
AE
108 // disable automatic synchronization
109 this.code.sync = function() { };
d45eaff6 110
b8cdfecb
AE
111 // fix button label for source toggling
112 var $tooltip = $('.redactor-toolbar-tooltip-html:not(.jsWbbcode)').addClass('jsWbbcode').text(WCF.Language.get('wcf.bbcode.button.toggleBBCode'));
113
66f8f814 114 var $fixBR = function(editor) {
1c2318bb
AE
115 var elements = editor[0].querySelectorAll('br:not(:empty)');
116 for (var i = 0, length = elements.length; i < length; i++) {
117 elements[0].innerHTML = '';
118 }
66f8f814
AE
119 };
120
aaec55d9
AE
121 this.code.toggle = (function() {
122 if (this.opts.visual) {
123 this.code.startSync();
124
125 this.code.showCode();
126 this.$textarea.val(this.wbbcode.convertFromHtml(this.$textarea.val()));
127
128 this.button.get('html').children('i').removeClass('fa-square-o').addClass('fa-square');
b8cdfecb 129 $tooltip.text(WCF.Language.get('wcf.bbcode.button.toggleHTML'));
c62197d6
AE
130 }
131 else {
aaec55d9 132 this.$textarea.val(this.wbbcode.convertToHtml(this.$textarea.val()));
0e401530 133 this.code.offset = this.$textarea.val().length;
aaec55d9 134 this.code.showVisual();
5fb44399 135 this.wbbcode.fixBlockLevelElements();
0e401530 136 this.wutil.selectionEndOfEditor();
5fb44399
AE
137 this.wbbcode.observeQuotes();
138 this.wbbcode.observeCodeListings();
aaec55d9
AE
139
140 this.button.get('html').children('i').removeClass('fa-square').addClass('fa-square-o');
b8cdfecb 141 $tooltip.text(WCF.Language.get('wcf.bbcode.button.toggleBBCode'));
a427d433 142 this.wutil.fixDOM();
66f8f814 143 $fixBR(this.$editor);
b3d84e05 144
5fb6f47a 145 this.wutil.saveSelection();
c62197d6 146 }
aaec55d9 147 }).bind(this);
792fe645
AE
148
149 // insert a new line if user clicked into the editor and the last children is a quote (same behavior as arrow down)
150 this.wutil.setOption('clickCallback', (function(event) {
5fb6f47a 151 this.wutil.saveSelection();
b3d84e05 152
792fe645
AE
153 if (event.target === this.$editor[0]) {
154 if (this.$editor[0].lastElementChild && this.$editor[0].lastElementChild.tagName === 'BLOCKQUOTE') {
155 this.wutil.setCaretAfter($(this.$editor[0].lastElementChild));
156 }
157 }
158 }).bind(this));
9e2caa7b
AE
159
160 // drop ul to prevent being touched by this.clean.clearUnverifiedRemove()
161 var $index = this.opts.verifiedTags.indexOf('ul');
162 if ($index > -1) {
163 this.opts.verifiedTags.splice($index, 1);
164 }
7cd02a59
AE
165
166 // reattach event listeners
167 WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'observe_load_' + $identifier, (function(data) {
168 this.wbbcode.observeCodeListings();
169 this.wbbcode.observeQuotes();
170 }).bind(this));
86797f35
AE
171
172 WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'fixFormatting_' + $identifier, $.proxy(this.wbbcode.fixFormatting, this));
aaec55d9
AE
173 },
174
175 /**
176 * Toggles features within the table dropdown.
177 *
178 * @param object event
179 */
180 _tableButtonClick: function(event) {
181 var $button = $(event.currentTarget);
182 if (!$button.hasClass('dropact')) {
183 return;
60bf3754 184 }
6166782d 185
aaec55d9
AE
186 var $current = this.selection.getBlock() || this.selection.getCurrent();
187 var $dropdown = $button.data('dropdown');
b3b75419 188
aaec55d9
AE
189 // within table
190 $dropdown.children('li').show();
191 var $insertTable = $dropdown.find('> li > .redactor-dropdown-insert_table').parent();
192 if ($current.tagName == 'TD') {
193 $insertTable.hide();
194 }
195 else {
196 $insertTable.nextAll().hide();
197 }
198 },
199
200 /**
201 * Inserts a smiley, optionally trying to register a new smiley.
202 *
203 * @param string smileyCode
204 * @param string smileyPath
205 * @param boolean registerSmiley
206 */
207 insertSmiley: function(smileyCode, smileyPath, registerSmiley) {
208 if (registerSmiley) {
e029227c 209 this.wbbcode.registerSmiley(smileyCode, smileyPath);
6166782d 210 }
aaec55d9
AE
211
212 if (this.opts.visual) {
28d83f70
AE
213 var $parentBefore = null;
214 if (window.getSelection().rangeCount && window.getSelection().getRangeAt(0).collapsed) {
215 $parentBefore = window.getSelection().getRangeAt(0).startContainer;
216 if ($parentBefore.nodeType === Node.TEXT_NODE) {
217 $parentBefore = $parentBefore.parentElement;
218 }
219
220 if (!this.utils.isRedactorParent($parentBefore)) {
221 $parentBefore = null;
222 }
223 }
224
225 this.insert.html('<img src="' + smileyPath + '" class="smiley" alt="' + smileyCode + '" id="redactorSmiley">', false);
226
227 var $smiley = document.getElementById('redactorSmiley');
c9f2cd74 228 $smiley.removeAttribute('id');
28d83f70
AE
229 if ($parentBefore !== null) {
230 var $currentParent = window.getSelection().getRangeAt(0).startContainer;
231 if ($currentParent.nodeType === Node.TEXT_NODE) {
232 $currentParent = $currentParent.parentElement;
233 }
234
235 // smiley has been inserted outside the original caret parent, move
236 if ($parentBefore !== $currentParent) {
237 $parentBefore.appendChild($smiley);
238 }
239 }
240
030e5acd
AE
241 var $isSpace = function(sibling) {
242 if (sibling === null) return false;
243
244 if ((sibling.nodeType === Node.ELEMENT_NODE && sibling.nodeName === 'SPAN') || sibling.nodeType === Node.TEXT_NODE) {
245 if (sibling.textContent === "\u00A0") {
246 return true;
247 }
248 }
249
250 return false;
251 };
252
28d83f70
AE
253 // add spaces as paddings
254 var $parent = $smiley.parentElement;
030e5acd
AE
255 if (!$isSpace($smiley.previousSibling)) {
256 var $node = document.createTextNode('\u00A0');
257 $parent.insertBefore($node, $smiley);
28d83f70 258 }
030e5acd
AE
259
260 if (!$isSpace($smiley.nextSibling)) {
261 var $node = document.createTextNode('\u00A0');
262 if ($parent.lastChild === $smiley) {
263 $parent.appendChild($node);
264 }
265 else {
266 $parent.insertBefore($node, $smiley.nextSibling);
267 }
28d83f70 268 }
6166782d
AE
269 }
270 else {
e029227c 271 this.wutil.insertAtCaret(' ' + smileyCode + ' ');
aaec55d9
AE
272 }
273 },
274
275 /**
276 * Registers a new smiley, returns false if the smiley code is already registered.
277 *
278 * @param string smileyCode
279 * @param string smileyPath
280 * @return boolean
281 */
282 registerSmiley: function(smileyCode, smileyPath) {
283 if (__REDACTOR_SMILIES[smileyCode]) {
284 return false;
6166782d
AE
285 }
286
aaec55d9 287 __REDACTOR_SMILIES[smileyCode] = smileyPath;
49630032 288
aaec55d9
AE
289 return true;
290 },
291
292 /**
293 * Converts source contents from HTML into BBCode.
294 *
63ba12db 295 * @param string message
aaec55d9 296 */
63ba12db 297 convertFromHtml: function(message) {
8723e1f7 298 var obj = { html: message };
93d79307 299
8723e1f7
AE
300 /** @deprecated legacy event */
301 WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'beforeConvertFromHtml', obj);
76b039dc 302
8723e1f7
AE
303 /** @deprecated legacy event */
304 WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'convertFromHtml', obj);
86af9f56 305
90b4b964 306 obj.html = __REDACTOR_AMD_DEPENDENCIES.BbcodeFromHTML.convert(obj.html);
53018795 307
8723e1f7
AE
308 /** @deprecated legacy event */
309 WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'afterConvertFromHtml', obj);
d75fe0ba 310
8723e1f7 311 return obj.html;
aaec55d9
AE
312 },
313
314 /**
315 * Converts source contents from BBCode to HTML.
316 *
63ba12db 317 * @param string message
aaec55d9 318 */
63ba12db 319 convertToHtml: function(message) {
8723e1f7 320 var obj = { data: message };
aaec55d9 321
8723e1f7
AE
322 /** @deprecated legacy event */
323 WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'beforeConvertToHtml', obj);
aaec55d9 324
90b4b964 325 obj.data = __REDACTOR_AMD_DEPENDENCIES.BbcodeToHTML.convert(obj.data, {
8723e1f7
AE
326 attachments: {
327 images: this.wbbcode._getImageAttachments(),
328 thumbnailUrl: this.wutil.getOption('woltlab.attachmentThumbnailUrl'),
329 url: this.wutil.getOption('woltlab.attachmentUrl')
330 }
331 });
aaec55d9 332
8723e1f7
AE
333 /** @deprecated legacy event */
334 WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'afterConvertToHtml', obj);
aaec55d9 335
8723e1f7 336 return obj.data;
aaec55d9
AE
337 },
338
339 /**
340 * Converts certain HTML elements prior to paste in order to preserve formattings.
341 *
342 * @param string html
343 * @return string
344 */
345 _pasteBeforeCallback: function(html) {
14898683
AE
346 var container = document.createElement('div');
347 container.innerHTML = html;
348 document.body.appendChild(container);
349
350 // handle <h1> ... <h6> tags
351 var defaultSizes = {
aaec55d9
AE
352 1: 24,
353 2: 22,
354 3: 18,
355 4: 14,
356 5: 12,
357 6: 10
14898683
AE
358 }, element, elements, size, styles;
359 for (var i = 1; i <= 6; i++) {
360 elements = container.getElementsByTagName('H' + i);
361 while (elements.length) {
362 element = elements[0];
363 size = defaultSizes[i];
364
365 if (element.style.fontSize) {
366 styles = window.getComputedStyle(element);
367 size = Math.round(styles.getPropertyValue('font-size').replace(/px$/, ''));
4f1c577a 368
14898683
AE
369 if (size > 24) size = 24;
370 else if (size > 22) size = 22;
371 else if (size > 18) size = 22;
372 else if (size > 14) size = 14;
373 else if (size > 12) size = 14;
374 else if (size > 10) size = 12;
375 else size = 10;
4f1c577a 376 }
14898683
AE
377
378 element.outerHTML = '<span style="font-size: ' + size + 'pt" data-verified="redactor" rel="font-size: ' + size + 'pt">' + element.innerHTML + '</span>';
4f1c577a 379 }
14898683 380 }
aaec55d9 381
14898683 382 document.body.removeChild(container);
aaec55d9 383
14898683
AE
384 // remove CSS classes from certain elements
385 elements = container.querySelectorAll('div,p,span');
386 for (var i = 0, length = elements.length; i < length; i++) {
387 if (elements[0].className) elements[0].className = '';
388 }
a6931610 389
c5950cc1 390 // drop <wbr>
14898683
AE
391 elements = container.getElementsByTagName('WBR');
392 while (elements.length) {
393 elements[0].parentNode.removeChild(elements[0]);
394 }
aaec55d9 395
14898683 396 return container.innerHTML;
aaec55d9
AE
397 },
398
399 /**
400 * Restores and fixes formatting before inserting pasted HTML into the editor.
401 *
402 * @param string html
403 * @return string
404 */
405 _pasteCallback: function(html) {
e22eea11
AE
406 // strip background-color
407 html = html.replace(/style="([^"]+)"/, function(match, inlineStyles) {
408 var $parts = inlineStyles.split(';');
409 var $styles = [ ];
410 for (var $i = 0, $length = $parts.length; $i < $length; $i++) {
411 var $part = $parts[$i];
412 if (!$part.match(/^\s*background-color/)) {
413 $styles.push($part);
414 }
415 }
416
417 return 'style="' + $styles.join(';') + '"';
418 });
419
aaec55d9
AE
420 WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'afterPaste', { html: html });
421
aaec55d9
AE
422 return html;
423 },
424
425 /**
426 * Inserts an attachment with live preview.
427 *
428 * @param integer attachmentID
5bc2753a 429 * @param boolean insertFull
aaec55d9 430 */
5bc2753a 431 insertAttachment: function(attachmentID, insertFull) {
aaec55d9 432 attachmentID = parseInt(attachmentID);
5bc2753a
AE
433 var $attachmentUrl = this.wutil.getOption('woltlab.attachment' + (!insertFull ? 'Thumbnail' : '') + 'Url');
434 var $imageAttachments = this.wbbcode._getImageAttachments();
aaec55d9 435
5bc2753a
AE
436 if ($attachmentUrl && $imageAttachments[attachmentID] !== undefined) {
437 var $style = '';
438 if (insertFull) {
439 $style = ' style="width: ' + $imageAttachments[attachmentID].width + 'px; max-height: ' + $imageAttachments[attachmentID].height + 'px; max-width: ' + $imageAttachments[attachmentID].width + 'px;"';
440 }
441
b38f98c0 442 this.wutil.insertDynamic(
5bc2753a
AE
443 '<img src="' + $attachmentUrl.replace(/987654321/, attachmentID) + '" class="redactorEmbeddedAttachment' + (!insertFull ? ' redactorDisableResize' : '') + '" data-attachment-id="' + attachmentID + '"' + $style + ' />',
444 '[attach=' + attachmentID + (insertFull ? ',none,' + $imageAttachments[attachmentID].width : '') + '][/attach]'
aaec55d9
AE
445 );
446 }
447 else {
5bc2753a 448 this.wutil.insertDynamic('[attach=' + attachmentID + '][/attach]');
aaec55d9
AE
449 }
450 },
451
31bec02c
AE
452 /**
453 * Removes an attachment from WYSIWYG view.
454 *
455 * @param integer attachmentID
456 */
457 removeAttachment: function(attachmentID) {
458 if (!this.opts.visual) {
459 // we're not going to mess with the code view
460 return;
461 }
462
463 this.$editor.find('img.redactorEmbeddedAttachment').each(function(index, attachment) {
464 var $attachment = $(attachment);
465 if ($attachment.data('attachmentID') == attachmentID) {
466 $attachment.remove();
467 }
468 });
469 },
470
aaec55d9
AE
471 /**
472 * Returns a list of attachments representing an image.
473 *
5bc2753a 474 * @return object
aaec55d9 475 */
5bc2753a 476 _getImageAttachments: function() {
aaec55d9 477 // WCF.Attachment.Upload may have no been initialized yet, fallback to static data
5bc2753a
AE
478 var $imageAttachments = this.wutil.getOption('woltlab.attachmentImages') || [ ];
479 if ($imageAttachments.length) {
480 delete this.opts.attachmentImages;
aaec55d9 481
5bc2753a 482 return $imageAttachments;
aaec55d9
AE
483 }
484
485 var $data = {
5bc2753a 486 imageAttachments: { }
262f6083 487 };
aaec55d9 488 WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'getImageAttachments_' + this.$textarea.wcfIdentify(), $data);
79be8266 489
5bc2753a 490 return $data.imageAttachments;
aaec55d9
AE
491 },
492
493 /**
494 * Handles up/down/delete/backspace key for quote boxes.
495 *
496 * @param object data
497 */
498 _keydownCallback: function(data) {
499 switch (data.event.which) {
500 case $.ui.keyCode.BACKSPACE:
501 case $.ui.keyCode.DELETE:
502 case $.ui.keyCode.DOWN:
503 case $.ui.keyCode.ENTER:
aaec55d9 504 case $.ui.keyCode.UP:
8a388f7f 505 case $.ui.keyCode.RIGHT:
1fd096cf 506 case 83: // [S]
aaec55d9
AE
507 // handle keys
508 break;
509
510 default:
511 return;
512 break;
262f6083 513 }
aaec55d9
AE
514
515 this.selection.get();
de7b9eea 516 var current = this.selection.getCurrent();
aaec55d9
AE
517 var $parent = this.selection.getParent();
518 $parent = ($parent) ? $($parent) : $parent;
519 var $quote = ($parent) ? $parent.closest('blockquote.quoteBox', this.$editor.get()[0]) : { length: 0 };
520
521 switch (data.event.which) {
522 // backspace key
523 case $.ui.keyCode.BACKSPACE:
524 if (this.wutil.isCaret()) {
de7b9eea
AE
525 var $preventAndSelectQuote = false;
526
aaec55d9
AE
527 if ($quote.length) {
528 // check if quote is empty
529 var $isEmpty = true;
de7b9eea
AE
530 for (var $i = 0; $i < $quote[0].children.length; $i++) {
531 var $child = $quote[0].children[$i];
532 if ($child.tagName === 'DIV') {
533 if ($child.textContent.replace(/\u200b/, '').length) {
534 $isEmpty = false;
535 break;
536 }
aaec55d9 537 }
de7b9eea 538 }
aaec55d9
AE
539
540 if ($isEmpty) {
de7b9eea 541 $preventAndSelectQuote = true;
c7cf0094 542 }
a67a4ab4
AE
543 else {
544 // check if caret is at the start of the quote
545 var range = (this.selection.implicitRange === null) ? this.range : this.selection.implicitRange;
546 if (range.startOffset === 0) {
547 var element = range.startContainer, prev;
548 while ((element = element.parentNode) !== null) {
549 prev = element.previousSibling;
550 if (prev !== null) {
551 if (prev.nodeType === Node.ELEMENT_NODE && prev.nodeName === 'HEADER') {
552 $preventAndSelectQuote = true;
553 }
554
555 break;
556 }
557 }
558 }
559 }
aaec55d9 560 }
8de7bc0a 561 else {
5271d93e
AE
562 var $range = (this.selection.implicitRange === null) ? this.range : this.selection.implicitRange;
563 var $scope = $range.startContainer;
8de7bc0a 564 if ($scope.nodeType === Node.TEXT_NODE) {
5271d93e 565 if ($range.startOffset === 0 || ($range.startOffset === 1 && $scope.textContent === '\u200b')) {
8de7bc0a
AE
566 if (!$scope.previousSibling) {
567 $scope = $scope.parentElement;
568 }
569 }
570 }
571
572 if ($scope.nodeType === Node.ELEMENT_NODE) {
573 var $previous = $scope.previousSibling;
574 if ($previous && $previous.nodeType === Node.ELEMENT_NODE && $previous.tagName === 'BLOCKQUOTE') {
575 $quote = $previous;
576 $preventAndSelectQuote = true;
577 }
578 }
de7b9eea
AE
579 }
580
581 if ($preventAndSelectQuote) {
582 // expand selection and prevent delete
583 var $selection = window.getSelection();
584 if ($selection.rangeCount) $selection.removeAllRanges();
585
586 var $quoteRange = document.createRange();
587 $quoteRange.selectNode($quote[0] || $quote);
588 $selection.addRange($quoteRange);
589
590 data.cancel = true;
591 }
aaec55d9
AE
592 }
593 break;
594
595 // delete key
596 case $.ui.keyCode.DELETE:
de7b9eea
AE
597 if (this.wutil.isCaret() && this.wutil.isEndOfElement(current)) {
598 var $next = current.nextElementSibling;
599 if ($next && $next.tagName === 'BLOCKQUOTE') {
c7cf0094
AE
600 // expand selection and prevent delete
601 var $selection = window.getSelection();
602 if ($selection.rangeCount) $selection.removeAllRanges();
603
604 var $quoteRange = document.createRange();
de7b9eea 605 $quoteRange.selectNode($next);
c7cf0094
AE
606 $selection.addRange($quoteRange);
607
608 data.cancel = true;
609 }
610 }
aaec55d9
AE
611 break;
612
613 // arrow down
614 case $.ui.keyCode.DOWN:
de7b9eea 615 var $current = $(current);
aaec55d9 616 if ($current.next('blockquote').length) {
0927cb47 617 this.caret.setStart($current.next().children('div:first'));
b3b75419
AE
618
619 data.cancel = true;
620 }
aaec55d9
AE
621 else if ($parent) {
622 if ($parent.next('blockquote').length) {
0927cb47 623 this.caret.setStart($parent.next().children('div:first'));
fac79057
AE
624
625 data.cancel = true;
626 }
aaec55d9
AE
627 else if ($quote.length) {
628 var $container = $current.closest('div', $quote[0]);
629 if (!$container.next().length) {
630 // check if there is an element after the quote
631 if ($quote.next().length) {
632 this.caret.setStart($quote.next());
633 }
634 else {
635 this.wutil.setCaretAfter($quote);
636 }
637
638 data.cancel = true;
639 }
640 }
641 }
642 break;
fac79057 643
aaec55d9
AE
644 // enter
645 case $.ui.keyCode.ENTER:
646 if ($quote.length) {
647 // prevent Redactor's default behavior for <blockquote>
648 this.keydown.blockquote = false;
649 this.keydown.enterWithinBlockquote = true;
650 }
8a388f7f
AE
651 else if (current.nodeName === 'KBD') {
652 data.cancel = true;
653 }
aaec55d9 654 break;
fac79057 655
aaec55d9
AE
656 // arrow up
657 case $.ui.keyCode.UP:
658 if (!$parent || !$quote.length) {
659 return;
660 }
661
de7b9eea 662 var $container = $(current).closest('div', $quote[0]);
aaec55d9
AE
663 var $prev = $container.prev();
664 if ($prev[0].tagName === 'DIV') {
665 return;
666 }
667 else if ($prev[0].tagName === 'BLOCKQUOTE') {
aaec55d9 668 return;
aaec55d9
AE
669 }
670
671 var $previousElement = $quote.prev();
672 if ($previousElement.length === 0) {
673 this.wutil.setCaretBefore($quote);
6166782d
AE
674 }
675 else {
aaec55d9
AE
676 if ($previousElement[0].tagName === 'BLOCKQUOTE') {
677 // set focus to quote text rather than the element itself
0927cb47 678 this.caret.sendEnd($previousElement.children('div:last'));
aaec55d9
AE
679 }
680 else {
681 // focus is wrong if the previous element is empty (e.g. only a newline present)
682 if ($.trim($previousElement.html()) == '') {
683 $previousElement.html(this.opts.invisibleSpace);
684 }
685
686 this.caret.setEnd($previousElement);
fac79057 687 }
6166782d 688 }
aaec55d9
AE
689
690 data.cancel = true;
691 break;
1fd096cf 692
8a388f7f
AE
693 case $.ui.keyCode.RIGHT:
694 var range = window.getSelection().getRangeAt(0);
695 if (range.startContainer.nodeType === Node.TEXT_NODE && range.startContainer.length === range.startOffset) {
696 current = current.parentNode;
697 if (current.nodeName !== 'KBD') {
698 return;
699 }
700
701 var editor = this.$editor[0];
702 if (current.nextElementSibling === editor.lastElementChild) {
703 current = current.nextElementSibling;
704 if (current.textContent === '') {
705 current.textContent = '\u200b';
706 }
707 }
708
709 if (current === editor.lastElementChild) {
710 this.wutil.selectionEndOfEditor();
711 }
712 }
713 break;
714
1fd096cf
AE
715 // [S]
716 case 83:
b7fc0c5d
AE
717 // not supported on mobile devices anyway
718 if ($.browser.mobile) {
719 return;
720 }
721
1fd096cf
AE
722 var $submitEditor = false;
723 if (navigator.platform.match(/^Mac/)) {
724 if (data.event.ctrlKey && data.event.altKey) {
725 $submitEditor = true;
726 }
727 }
f20c9c22 728 else if (data.event.altKey && !data.event.ctrlKey) {
1fd096cf
AE
729 $submitEditor = true;
730 }
731
732 if ($submitEditor) {
733 var $data = { cancel: false };
734 WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'submitEditor_' + this.$textarea.wcfIdentify(), $data);
735
736 if ($data.cancel) {
737 data.cancel = true;
738 }
739 }
740 break;
aaec55d9
AE
741 }
742 },
743
744 /**
745 * Handles quote deletion.
746 *
747 * @param object data
748 */
749 _keyupCallback: function(data) {
b1b42cc1
AE
750 switch (data.event.which) {
751 case $.ui.keyCode.BACKSPACE:
752 case $.ui.keyCode.DELETE:
753 // check for empty <blockquote>
754 this.$editor.find('blockquote').each(function(index, blockquote) {
755 var $blockquote = $(blockquote);
756 if (!$blockquote.children('header').length) {
757 $blockquote.remove();
758 }
759 });
760 break;
761
762 case $.ui.keyCode.ENTER:
763 // fix markup for empty lines
764 for (var $i = 0, $length = this.$editor[0].children.length; $i < $length; $i++) {
765 var $child = this.$editor[0].children[$i];
766 if ($child.nodeType !== Node.ELEMENT_NODE || $child.tagName !== 'P') {
767 // not a <p> element
768 continue;
769 }
770
771 if ($child.textContent.length > 0) {
772 // element is non-empty
773 continue;
774 }
775
776 if ($child.children.length > 1 || $child.children[0].tagName === 'BR') {
777 // element contains more than one children or it is just a <br>
778 continue;
779 }
780
781 // head all the way down to the most inner node
782 $child = $child.children[0];
783 while ($child.children.length === 1) {
784 $child = $child.children[0];
785 }
786
787 // check if node has no children and it is a <br>
788 if ($child.children.length === 0 && $child.tagName === 'BR') {
789 var $parent = $child.parentNode;
790 var $node = document.createTextNode('\u200b');
791 $parent.appendChild($node);
792 $parent.removeChild($child);
793 }
794 }
795 break;
aaec55d9 796 }
aaec55d9
AE
797 },
798
799 /**
800 * Initializes source editing for quotes.
801 */
5fb44399 802 observeQuotes: function() {
7cd02a59 803 this.$editor.find('.redactorQuoteEdit').off('click.wbbcode').on('click.wbbcode', $.proxy(this.wbbcode._observeQuotesClick, this));
aaec55d9
AE
804 },
805
806 /**
807 * Handles clicks on the 'edit quote' link.
808 *
809 * @param object event
810 */
811 _observeQuotesClick: function(event) {
812 var $header = $(event.currentTarget).closest('header');
813 var $tooltip = $('<span class="redactor-link-tooltip" />');
814
815 $('<a href="#">' + WCF.Language.get('wcf.bbcode.quote.edit') + '</a>').click($.proxy(function(e) {
816 e.preventDefault();
fac79057 817
6ffa2860 818 this.wbbcode._openQuoteEditOverlay($(event.currentTarget).closest('blockquote.quoteBox'), false);
aaec55d9
AE
819 $('.redactor-link-tooltip').remove();
820 }, this)).appendTo($tooltip);
821
822 var $offset = $header.offset();
823 $tooltip.css({
824 left: $offset.left + 'px',
825 top: ($offset.top + 20) + 'px'
826 });
1bfe075b 827
1bfe075b 828 $('.redactor-link-tooltip').remove();
aaec55d9
AE
829 $tooltip.appendTo(document.body);
830
831 // prevent the cursor being placed in the quote header
f5de6230 832 this.selection.remove();
aaec55d9
AE
833 },
834
5fb44399
AE
835 /**
836 * Initializes editing for code listings.
837 */
838 observeCodeListings: function() {
7cd02a59
AE
839 this.$editor.find('.codeBox').each((function(index, codeBox) {
840 var $codeBox = $(codeBox);
841 var $editBox = $codeBox.find('.redactorEditCodeBox');
842 if (!$editBox.length) {
843 $editBox = $('<div class="redactorEditCodeBox"><div>' + WCF.Language.get('wcf.bbcode.code.edit') + '</div></div>').insertAfter($codeBox.find('> div > div > h3'));
844 }
845
846 $editBox.off('click.wbbcode').on('click.wbbcode', (function() {
5fb44399
AE
847 this.wbbcode._handleInsertCode($codeBox, false);
848 }).bind(this));
849 }).bind(this));
850 },
851
aaec55d9
AE
852 /**
853 * Opens the quote source edit dialog.
854 *
855 * @param jQuery quote
856 * @param boolean insertQuote
857 */
858 _openQuoteEditOverlay: function(quote, insertQuote) {
859 this.modal.load('quote', WCF.Language.get('wcf.bbcode.quote.' + (insertQuote ? 'insert' : 'edit')), 400);
860
861 var $button = this.modal.createActionButton(this.lang.get('save'));
862 if (insertQuote) {
65fa7701
AE
863 this.selection.save();
864
aaec55d9 865 $button.click($.proxy(function() {
145b9c6c 866 var $author = $('#redactorQuoteAuthor').val();
fac79057 867 var $link = WCF.String.escapeHTML($('#redactorQuoteLink').val());
145b9c6c 868
65fa7701 869 this.selection.restore();
ce826197 870 $skipOnSyncReplacementOnce = true;
65fa7701
AE
871 var $html = this.selection.getHtml();
872 if (this.utils.isEmpty($html)) {
873 $html = '';
874 }
875
876 var $quote = this.wbbcode.insertQuoteBBCode($author, $link, $html);
b10849be
AE
877 if ($quote !== null) {
878 // set caret inside the quote
65fa7701 879 if (!$html.length) {
150e6c3c
AE
880 // careful, Firefox is stupid and replaces an empty div with br[type=_moz]
881 if ($.browser.mozilla) {
882 $quote.children('br[type=_moz]').replaceWith('<div>' + this.opts.invisibleSpace + '</div>');
883 }
884
885 this.caret.setStart($quote.children('div')[0]);
65fa7701 886 }
b10849be 887 }
145b9c6c 888
aaec55d9 889 this.modal.close();
145b9c6c 890 }, this));
aaec55d9
AE
891 }
892 else {
893 $('#redactorQuoteAuthor').val(quote.data('author'));
894
895 // do not use prop() here, an empty cite attribute would yield the page URL instead
896 $('#redactorQuoteLink').val(WCF.String.unescapeHTML(quote.attr('cite')));
1bfe075b 897
aaec55d9 898 $button.click($.proxy(function() {
145b9c6c
AE
899 var $author = $('#redactorQuoteAuthor').val();
900 quote.data('author', $author);
901 quote.attr('data-author', $author);
902 quote.prop('cite', WCF.String.escapeHTML($('#redactorQuoteLink').val()));
903
aaec55d9 904 this.wbbcode._updateQuoteHeader(quote);
145b9c6c 905
6ffa2860 906 this.modal.close();
145b9c6c 907 }, this));
262f6083 908 }
aaec55d9
AE
909
910 this.modal.show();
911 },
912
913 /**
914 * Updates the quote's source.
915 *
916 * @param jQuery quote
917 */
918 _updateQuoteHeader: function(quote) {
919 var $author = quote.data('author');
920 var $link = quote.attr('cite');
921 if ($link) $link = WCF.String.escapeHTML($link);
922
35e4755f 923 quote.find('> header > h3').empty().append(this.wbbcode._buildQuoteHeader($author, $link));
aaec55d9
AE
924 },
925
926 /**
927 * Inserts the quote BBCode.
928 *
929 * @param string author
930 * @param string link
931 * @param string html
932 * @param string plainText
b10849be 933 * @return jQuery
aaec55d9
AE
934 */
935 insertQuoteBBCode: function(author, link, html, plainText) {
6df8c3dd
AE
936 var $openTag = '[quote]';
937 var $closingTag = '[/quote]';
938
aaec55d9
AE
939 if (author) {
940 if (link) {
6df8c3dd 941 $openTag = "[quote='" + author + "','" + link + "']";
aaec55d9
AE
942 }
943 else {
6df8c3dd 944 $openTag = "[quote='" + author + "']";
aaec55d9 945 }
262f6083 946 }
262f6083 947
b10849be 948 var $quote = null;
aaec55d9 949 if (this.wutil.inWysiwygMode()) {
6df8c3dd 950 var $id = WCF.getUUID();
c1593bbf
AE
951 var $html = '';
952 if (plainText) {
953 $html = this.wbbcode.convertToHtml($openTag + plainText + $closingTag);
954 }
955 else {
956 $html = this.wbbcode.convertToHtml($openTag + $id + $closingTag);
8594b9f6 957 $html = $html.replace($id, html.replace(/^<p>/, '').replace(/<\/p>$/, ''));
c1593bbf
AE
958 }
959
a2d36ebf 960 $html = $html.replace(/^<p>/, '').replace(/<\/p>$/, '');
6df8c3dd
AE
961
962 // assign a unique id in order to recognize the inserted quote
94780895
AE
963 $html = $html.replace(/<blockquote/, '<blockquote id="' + $id + '"');
964
4601f5e6
AE
965 if (!window.getSelection().rangeCount) {
966 this.wutil.restoreSelection();
967
968 if (!window.getSelection().rangeCount) {
969 this.$editor.focus();
970
971 if (!window.getSelection().rangeCount) {
972 this.wutil.selectionEndOfEditor();
973 }
8594b9f6
AE
974
975 this.wutil.saveSelection();
4601f5e6
AE
976 }
977 }
978
e6231e0f 979 window.getSelection().getRangeAt(0).deleteContents();
c3487b88 980
b795fdda
AE
981 this.wutil.restoreSelection();
982 var $selection = window.getSelection().getRangeAt(0);
983 var $current = $selection.startContainer;
984 while ($current) {
985 var $parent = $current.parentNode;
986 if ($parent === this.$editor[0]) {
987 break;
988 }
989
990 $current = $parent;
991 }
992
993 if ($current && $current.parentNode === this.$editor[0]) {
994 if ($current.innerHTML.length) {
67303804
AE
995 if ($current.innerHTML === '\u200b') {
996 this.caret.setEnd($current);
997 }
998 else {
999 this.wutil.setCaretAfter($current);
1000 }
b795fdda
AE
1001 }
1002 }
1003
6df8c3dd
AE
1004 this.insert.html($html, false);
1005
b10849be 1006 $quote = this.$editor.find('#' + $id);
6df8c3dd 1007 if ($quote.length) {
b10849be 1008 // quote may be empty if $innerHTML was empty, fix it
5fb44399 1009 var $inner = $quote.find('> div');
a2d36ebf
AE
1010 if ($inner.length == 1) {
1011 if ($inner[0].innerHTML === '') {
1012 $inner[0].innerHTML = this.opts.invisibleSpace;
1013 }
1014 }
1015 else if ($.browser.mozilla) {
1016 // Firefox on Mac OS X sometimes removes the "empty" div and replaces it with <br type="_moz">
1017 var $br = $quote.find('> div > br[type=_moz]');
1018 if ($br.length) {
1019 $('<div>' + this.opts.invisibleSpace + '</div>').insertBefore($br);
1020 $br.remove();
1021 }
b10849be
AE
1022 }
1023
6df8c3dd
AE
1024 $quote.removeAttr('id');
1025 this.wutil.setCaretAfter($quote[0]);
67303804
AE
1026
1027 // inserting a quote can spawn an additional empty newline in front
1028 var prev = $quote[0].previousElementSibling;
1029 if (prev !== null && prev.nodeName === 'P' && prev.innerHTML === '\u200B') {
1030 prev = prev.previousElementSibling;
1031 if (prev !== null && prev.nodeName === 'P' && (prev.innerHTML === '\u200B' || prev.innerHTML === '<br>')) {
1032 prev.parentNode.removeChild(prev.nextElementSibling);
1033 }
1034 }
6df8c3dd 1035 }
aaec55d9 1036
5fb44399
AE
1037 this.wbbcode.observeQuotes();
1038 this.wbbcode.fixBlockLevelElements();
aaec55d9 1039
fa7e9af0 1040 this.$toolbar.find('a.re-__wcf_quote').removeClass('redactor-button-disabled');
aaec55d9
AE
1041 }
1042 else {
6df8c3dd 1043 this.wutil.insertAtCaret($openTag + plainText + $closingTag);
aaec55d9 1044 }
b10849be 1045
5fb6f47a 1046 this.wutil.saveSelection();
ad7277cd 1047
b10849be 1048 return $quote;
aaec55d9
AE
1049 },
1050
1051 /**
1052 * Builds the quote source HTML.
1053 *
1054 * @param string author
1055 * @param string link
1056 * @return string
1057 */
1058 _buildQuoteHeader: function(author, link) {
1059 var $header = '';
1060 // author is empty, check if link was provided and use it instead
1061 if (!author && link) {
1062 author = link;
1063 link = '';
1064 }
1bfe075b 1065
aaec55d9 1066 if (author) {
87b8a4f3 1067 if (link) $header += '<a href="' + link + '" tabindex="-1">';
aaec55d9
AE
1068
1069 $header += WCF.Language.get('wcf.bbcode.quote.title.javascript', { quoteAuthor: WCF.String.unescapeHTML(author) });
1070
1071 if (link) $header += '</a>';
1072 }
1073 else {
1074 $header = '<small>' + WCF.Language.get('wcf.bbcode.quote.title.clickToSet') + '</small>';
1075 }
1bfe075b 1076
aaec55d9
AE
1077 return $header;
1078 },
1bfe075b 1079
aaec55d9
AE
1080 _handleInsertQuote: function() {
1081 this.wbbcode._openQuoteEditOverlay(null, true);
d44bcd93
AE
1082 },
1083
1084 /**
5fb44399
AE
1085 * Opens the code edit dialog.
1086 *
1087 * @param jQuery codeBox
1088 * @param boolean isInsert
1089 */
1090 _handleInsertCode: function(codeBox, isInsert) {
1091 this.modal.load('code', WCF.Language.get('wcf.bbcode.code.' + (isInsert ? 'insert' : 'edit')), 400);
1092
c89e6402 1093 var $button = this.modal.createActionButton(this.lang.get('save')).addClass('buttonPrimary');
5fb44399
AE
1094
1095 if (isInsert) {
943a790b
AE
1096 this.selection.get();
1097 var $selectedText = this.selection.getText();
1098
5fb44399
AE
1099 this.selection.save();
1100 this.modal.show();
1101
943a790b
AE
1102 var $codeBox = $('#redactorCodeBox').focus();
1103 $codeBox.val($selectedText);
5fb44399
AE
1104
1105 $button.click($.proxy(function() {
1106 var $codeBox = $('#redactorCodeBox');
1107 var $filename = $('#redactorCodeFilename');
1108 var $highlighter = $('#redactorCodeHighlighter');
1109 var $lineNumber = $('#redactorCodeLineNumber');
1110
3e9815e7
AE
1111 var $codeBoxContent = $codeBox.val().replace(/^\n+/, '').replace(/\n+$/, '');
1112 if ($.trim($codeBoxContent).length === 0) {
1113 if (!$codeBox.next('small.innerError').length) {
1114 $('<small class="innerError">' + WCF.Language.get('wcf.global.form.error.empty') + '</small>').insertAfter($codeBox);
1115 }
1116
1117 return;
1118 }
1119
3f2d00ee 1120 var $codeFilename = $.trim($filename.val().replace(/['"]/g, ''));
5fb44399 1121 var $bbcode = '[code=' + $highlighter.val() + ',' + $lineNumber.val() + ($codeFilename.length ? ",'" + $codeFilename + "'" : '') + ']';
d75fe0ba
AE
1122 if ($bbcode.match(/\[code=([^,]+),(\d+)\]/)) {
1123 // reverse line number and highlighter
1124 $bbcode = '[code=' + RegExp.$2 + ',' + RegExp.$1 + ']';
1125 }
1126
3e9815e7 1127 $bbcode += $codeBoxContent;
5fb44399
AE
1128 $bbcode += '[/code]';
1129
1130 this.wutil.adjustSelectionForBlockElement();
1131 this.wutil.saveSelection();
1132 var $html = this.wbbcode.convertToHtml($bbcode);
943a790b
AE
1133
1134 this.buffer.set();
30c64335 1135
5fb44399
AE
1136 this.insert.html($html, false);
1137
1138 // set caret after code listing
1139 var $codeBox = this.$editor.find('.codeBox:not(.jsRedactorCodeBox)');
1140
1141 this.wbbcode.observeCodeListings();
1142 this.wbbcode.fixBlockLevelElements();
1143
1144 // document.execCommand('insertHTML') seems to drop 'contenteditable="false"' for root element
1145 $codeBox.attr('contenteditable', 'false');
d539db44 1146 this.wutil.setCaretAfter($codeBox[0]);
5fb44399
AE
1147
1148 this.modal.close();
1149 }, this));
1150 }
1151 else {
c89e6402
AE
1152 var $deleteButton = this.modal.createActionButton(WCF.Language.get('wcf.global.button.delete'));
1153 $deleteButton.click((function() {
1154 this.buffer.set();
1155
1156 codeBox.remove();
1157
1158 this.modal.close();
1159 }).bind(this));
1160
5fb44399
AE
1161 this.modal.show();
1162
1163 var $codeBox = $('#redactorCodeBox').focus();
1164 var $filename = $('#redactorCodeFilename');
1165 var $highlighter = $('#redactorCodeHighlighter');
1166 var $lineNumber = $('#redactorCodeLineNumber');
1167
1168 $highlighter.val(codeBox.data('highlighter'));
1169 $filename.val(codeBox.data('filename') || '');
1170 var $list = codeBox.find('> div > ol');
1171 $lineNumber.val(parseInt($list.prop('start')));
1172
1173 var $code = '';
1174 $list.children('li').each(function(index, listItem) {
d9750c8b 1175 $code += $(listItem).text().replace(/^\u200b$/, '') + "\n";
5fb44399
AE
1176 });
1177 $codeBox.val($code.replace(/^\n+/, '').replace(/\n+$/, ''));
1178
1179 $button.click($.proxy(function() {
3e9815e7
AE
1180 var $codeBoxContent = $codeBox.val().replace(/^\n+/, '').replace(/\n+$/, '');
1181 if ($.trim($codeBoxContent).length === 0) {
1182 if (!$codeBox.next('small.innerError').length) {
1183 $('<small class="innerError">' + WCF.Language.get('wcf.global.form.error.empty') + '</small>').insertAfter($codeBox);
1184 }
1185
1186 return;
1187 }
1188
5fb44399
AE
1189 var $selectedHighlighter = $highlighter.val();
1190 codeBox.data('highlighter', $selectedHighlighter);
1191 codeBox.attr('data-highlighter', $selectedHighlighter);
1192
1193 var $headline = __REDACTOR_CODE_HIGHLIGHTERS[$selectedHighlighter];
3f2d00ee 1194 var $codeFilename = $.trim($filename.val().replace(/['"]/g, ''));
5fb44399
AE
1195 if ($codeFilename) {
1196 $headline += ': ' + WCF.String.escapeHTML($codeFilename);
1197 codeBox.data('filename', $codeFilename);
1198 codeBox.attr('data-filename', $codeFilename);
1199 }
1200 else {
1201 codeBox.removeAttr('data-filename');
1202 codeBox.removeData('filename');
1203 }
1204
1205 codeBox.data('highlighter', $highlighter.val());
1206 codeBox.find('> div > div > h3').html($headline);
1207
1208 var $list = codeBox.find('> div > ol').empty();
1209 var $start = parseInt($lineNumber.val());
1210 $list.prop('start', ($start > 1 ? $start : 1));
1211
3e9815e7 1212 $codeBoxContent = $codeBoxContent.split('\n');
5fb44399 1213 var $codeContent = '';
3e9815e7
AE
1214 for (var $i = 0; $i < $codeBoxContent.length; $i++) {
1215 $codeContent += '<li>' + WCF.String.escapeHTML($codeBoxContent[$i]) + '</li>';
5fb44399
AE
1216 }
1217 $list.append($($codeContent));
1218
1219 this.modal.close();
1220 }, this));
1221 }
1222 },
1223
1224 /**
30c64335 1225 * Inserting block-level elements (e.g. quotes or code bbcode) can lead to void paragraphs.
d44bcd93 1226 */
5fb44399 1227 fixBlockLevelElements: function() {
1c2318bb 1228 return;
30c64335
AE
1229 var $removeVoidElements = (function(referenceElement, position) {
1230 var $sibling = referenceElement[position];
1231 if ($sibling && $sibling.nodeType === Node.ELEMENT_NODE && $sibling.tagName === 'P') {
1232 if (!$sibling.innerHTML.length) {
1233 $sibling.parentElement.removeChild($sibling);
1234 }
67303804 1235 /*else if ($sibling.innerHTML === '\u200b') {
30c64335
AE
1236 var $adjacentSibling = $sibling[position];
1237 if ($adjacentSibling && $adjacentSibling.nodeType === Node.ELEMENT_NODE && $adjacentSibling.tagName === 'P' && $adjacentSibling.innerHTML.length) {
1238 $sibling.parentElement.removeChild($sibling);
1239 }
67303804 1240 }*/
d44bcd93 1241 }
a20ad78a
AE
1242 }).bind(this);
1243
30c64335
AE
1244 this.$editor.find('blockquote, .codeBox').each(function() {
1245 $removeVoidElements(this, 'previousElementSibling');
1246 $removeVoidElements(this, 'nextElementSibling');
1247 });
86797f35
AE
1248 },
1249
1250 /**
1251 * Fixes incorrect formatting applied to element that should be left untouched.
1252 *
1253 * @param object data
1254 */
1255 fixFormatting: function(data) {
1256 var $stripTextAlign = function(element) {
1257 element.style.removeProperty('text-align');
1258
1259 for (var $i = 0; $i < element.children.length; $i++) {
1260 $stripTextAlign(element.children[$i]);
1261 }
1262 };
1263
1264 for (var $i = 0; $i < this.alignment.blocks.length; $i++) {
1265 var $block = this.alignment.blocks[$i];
1266 switch ($block.tagName) {
1267 case 'BLOCKQUOTE':
1268 $block.style.removeProperty('text-align');
1269 $stripTextAlign($block.children[0]);
1270 break;
1271
1272 case 'DIV':
f492ce10 1273 if (/\bcodeBox\b/.test($block.className)) {
86797f35
AE
1274 $stripTextAlign($block);
1275 }
1276 break;
1277 }
1278 }
aaec55d9
AE
1279 }
1280 };
5e18a011 1281};