Added support for quotes
authorAlexander Ebert <ebert@woltlab.com>
Sun, 28 Jun 2015 16:12:46 +0000 (18:12 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Mon, 29 Jun 2015 10:45:21 +0000 (12:45 +0200)
wcfsetup/install/files/js/3rdParty/redactor/plugins/wbbcode.js
wcfsetup/install/files/js/3rdParty/redactor/plugins/wmonkeypatch.js
wcfsetup/install/files/js/3rdParty/redactor/plugins/wutil.js
wcfsetup/install/files/js/WoltLab/WCF/BBCode/FromHtml.js
wcfsetup/install/files/js/WoltLab/WCF/BBCode/Parser.js
wcfsetup/install/files/js/WoltLab/WCF/BBCode/ToHtml.js

index 003eef90036c163ad54b22e789cd5db11ffd3a6e..0b561df451e841af5372ec2d32dd29720a74966d 100644 (file)
@@ -106,11 +106,10 @@ RedactorPlugins.wbbcode = function() {
                        var $tooltip = $('.redactor-toolbar-tooltip-html:not(.jsWbbcode)').addClass('jsWbbcode').text(WCF.Language.get('wcf.bbcode.button.toggleBBCode'));
                        
                        var $fixBR = function(editor) {
-                               editor.find('br').each(function(index, br) {
-                                       if (br.children.length) {
-                                               $(br).empty();
-                                       }
-                               });
+                               var elements = editor[0].querySelectorAll('br:not(:empty)');
+                               for (var i = 0, length = elements.length; i < length; i++) {
+                                       elements[0].innerHTML = '';
+                               }
                        };
                        
                        this.code.toggle = (function() {
@@ -1857,6 +1856,7 @@ RedactorPlugins.wbbcode = function() {
                 * Inserting block-level elements (e.g. quotes or code bbcode) can lead to void paragraphs.
                 */
                fixBlockLevelElements: function() {
+                       return;
                        var $removeVoidElements = (function(referenceElement, position) {
                                var $sibling = referenceElement[position];
                                if ($sibling && $sibling.nodeType === Node.ELEMENT_NODE && $sibling.tagName === 'P') {
index d0cd2e9aaf03579f07ca6b1b22a8972ccc4a5cfd..d0c7e23f3731cec088433b246d02522b57104e5c 100644 (file)
@@ -105,151 +105,160 @@ RedactorPlugins.wmonkeypatch = function() {
                                }
                        }).bind(this));
                        
-                       var $setCaretBeforeOrAfter = (function(element, setBefore) {
+                       var isTargetElement = function(element) {
+                               // [quote]
+                               if (element.nodeName === 'BLOCKQUOTE') {
+                                       return true;
+                               }
+                               
+                               // [code]
+                               if (element.nodeName === 'DIV' && element.classList.contains('codeBox')) {
+                                       return true;
+                               }
+                               
+                               return false;
+                       };
+                       
+                       var getOffset = function(element) {
+                               var offsets = element.getBoundingClientRect();
+                               
+                               return {
+                                       left: offsets.left + document.body.scrollLeft,
+                                       top: offsets.top + document.body.scrollTop
+                               };
+                       };
+                       
+                       function getVerticalBoundaries(element) {
+                               var offset = getOffset(element);
+                               var styles = window.getComputedStyle(element);
+                               
+                               return {
+                                       bottom: offset.top + element.offsetHeight + parseInt(styles.marginBottom),
+                                       top: offset.top - parseInt(styles.marginTop)
+                               };
+                       };
+                       
+                       var setCaretBeforeOrAfter = (function(element, setBefore) {
+                               var ref;
                                if (setBefore) {
-                                       if (element.previousElementSibling && (element.previousElementSibling.tagName === 'P' || element.previousElementSibling.tagName === 'DIV')) {
-                                               this.caret.setEnd(element.previousElementSibling);
+                                       ref = element.previousSibling;
+                                       if (ref === null) {
+                                               var space = this.utils.createSpaceElement();
+                                               element.parentNode.insertBefore(space, element);
+                                               
+                                               this.caret.setEnd(space);
                                        }
                                        else {
-                                               this.wutil.setCaretBefore(element);
+                                               this.caret[(ref.nodeType === Node.ELEMENT_NODE && ref.nodeName === 'BR') ? 'setBefore' : 'setAfter'](ref);
                                        }
                                }
                                else {
-                                       if (element.nextElementSibling && (element.nextElementSibling.tagName === 'P' || element.nextElementSibling.tagName === 'DIV')) {
-                                               this.caret.setEnd(element.nextElementSibling);
+                                       ref = element.nextSibling;
+                                       if (ref === null) {
+                                               var space = this.utils.createSpaceElement();
+                                               if (element.nextSibling === null) element.parentNode.appendChild(space);
+                                               else element.parentNode.insertBefore(space, element.nextSibling);
+                                               
+                                               this.caret.setEnd(space);
                                        }
                                        else {
-                                               this.wutil.setCaretAfter(element);
+                                               this.caret.setBefore(ref);
                                        }
                                }
                        }).bind(this);
                        
-                       var $editorPadding = null;
+                       var editor = this.$editor[0];
                        this.$editor.on('click.wmonkeypatch', (function(event) {
-                               if (event.target === this.$editor[0]) {
-                                       var $range = (window.getSelection().rangeCount) ? window.getSelection().getRangeAt(0) : null;
-                                       
-                                       if ($range && $range.collapsed) {
-                                               var $current = $range.startContainer;
+                               var range = (window.getSelection().rangeCount) ? window.getSelection().getRangeAt(0) : null;
+                               
+                               if (event.target === editor) {
+                                       var boundaries, element;
+                                       for (var i = 0, length = editor.childElementCount; i < length; i++) {
+                                               element = editor.children[i];
                                                
-                                               // this can occur if click occurs within the editor padding
-                                               var $offsets = this.$editor.offset();
-                                               if ($editorPadding === null) {
-                                                       $editorPadding = {
-                                                               left: this.$editor.cssAsNumber('padding-left'),
-                                                               top: this.$editor.cssAsNumber('padding-top')
-                                                       };
+                                               if (!isTargetElement(element)) {
+                                                       continue;
                                                }
                                                
-                                               if (event.pageY <= $offsets.top + $editorPadding.top) {
-                                                       var $firstChild = this.$editor[0].children[0];
-                                                       if ($firstChild.tagName !== 'BLOCKQUOTE' && ($firstChild.tagName !== 'DIV' || !/\bcodeBox\b/.test($firstChild.className))) {
-                                                               return;
-                                                       }
+                                               boundaries = getVerticalBoundaries(element);
+                                               
+                                               if (event.pageY > boundaries.bottom) {
+                                                       continue;
                                                }
-                                               else {
-                                                       if (event.pageX <= $offsets.left + $editorPadding.left) {
-                                                               return;
-                                                       }
-                                                       else {
-                                                               if (event.pageX > $offsets.left + this.$editor.width()) {
-                                                                       return;
-                                                               }
-                                                       }
+                                               else if (event.pageY < boundaries.top) {
+                                                       break;
                                                }
                                                
-                                               while ($current && $current !== this.$editor[0]) {
-                                                       if ($current.nodeType === Node.ELEMENT_NODE) {
-                                                               if ($current.tagName === 'BLOCKQUOTE' || ($current.tagName === 'DIV' && /\bcodeBox\b/.test($current.className))) {
-                                                                       var $offset = $($current).offset();
-                                                                       if (event.pageY <= $offset.top) {
-                                                                               $setCaretBeforeOrAfter($current, true);
-                                                                       }
-                                                                       else {
-                                                                               $setCaretBeforeOrAfter($current, false);
-                                                                       }
-                                                                       
-                                                                       // stop processing
-                                                                       return false;
+                                               if (event.pageY >= boundaries.top && event.pageY <= boundaries.bottom) {
+                                                       var diffToTop = event.pageY - boundaries.top;
+                                                       var height = boundaries.bottom - boundaries.top;
+                                                       var setBefore = (diffToTop <= (height / 2));
+                                                       
+                                                       var ref = element[setBefore ? 'previousSibling' : 'nextSibling'];
+                                                       while (ref !== null) {
+                                                               if (ref.nodeType === Node.TEXT_NODE && ref.textContent !== '') {
+                                                                       // non-empty text node, default behavior is okay
+                                                                       return;
+                                                               }
+                                                               else if (ref.nodeType === Node.ELEMENT_NODE && !isTargetElement(ref)) {
+                                                                       // a non-blocking element such as a formatted line or something, default behavior is okay
+                                                                       return;
                                                                }
+                                                               
+                                                               ref = ref[setBefore ? 'previousSibling' : 'nextSibling'];
                                                        }
                                                        
-                                                       $current = $current.parentElement;
+                                                       setCaretBeforeOrAfter(element, setBefore);
                                                }
                                        }
                                        
-                                       var $elements = this.$editor.children('blockquote, div.codeBox');
-                                       $elements.each(function(index, element) {
-                                               var $element = $(element);
-                                               var $offset = $element.offset();
-                                               
-                                               if (event.pageY <= $offset.top) {
-                                                       $setCaretBeforeOrAfter(element, true);
-                                                       
-                                                       return false;
-                                               }
-                                               else {
-                                                       var $height = $element.outerHeight() + (parseInt($element.css('margin-bottom'), 10) || 0);
-                                                       if (event.pageY <= $offset.top + $height) {
-                                                               $setCaretBeforeOrAfter(element, false);
-                                                               
-                                                               return false;
-                                                       }
-                                               }
-                                       });
-                                       
                                        return false;
                                }
-                               else if (event.target.tagName === 'LI') {
+                               else if (event.target.nodeName === 'LI') {
                                        // work-around for #1942
-                                       var $range = (window.getSelection().rangeCount) ? window.getSelection().getRangeAt(0) : null;
-                                       var $caretInsideList = false;
-                                       if ($range !== null) {
-                                               if (!$range.collapsed) {
-                                                       return;
-                                               }
-                                               
-                                               var $current = $range.startContainer;
-                                               while ($current !== null && $current !== this.$editor[0]) {
-                                                       if ($current.tagName === 'LI') {
-                                                               $caretInsideList = true;
+                                       var caretInsideList = false;
+                                       if (range !== null && range.collapsed) {
+                                               var current = range.startContainer;
+                                               while (current !== null && current !== editor) {
+                                                       if (current.nodeName === 'LI') {
+                                                               caretInsideList = true;
                                                                break;
                                                        }
                                                        
-                                                       $current = $current.parentElement;
+                                                       current = current.parentNode;
                                                }
                                        }
                                        
-                                       if (!$caretInsideList || $range === null) {
-                                               var $node = document.createTextNode('\u200b');
-                                               var $firstChild = event.target.children[0];
-                                               $firstChild.appendChild($node);
+                                       if (!caretInsideList || range === null) {
+                                               var node = document.createTextNode('\u200b');
+                                               var firstChild = event.target.children[0];
+                                               firstChild.appendChild(node);
                                                
-                                               this.caret.setEnd($firstChild);
+                                               this.caret.setEnd(firstChild);
                                        }
                                }
-                               else if (event.target.tagName === 'BLOCKQUOTE') {
-                                       var $range = (window.getSelection().rangeCount) ? window.getSelection().getRangeAt(0) : null;
-                                       if ($range !== null && $range.collapsed) {
+                               else if (event.target.nodeName === 'BLOCKQUOTE') {
+                                       range = (window.getSelection().rangeCount) ? window.getSelection().getRangeAt(0) : null;
+                                       if (range !== null && range.collapsed) {
                                                // check if caret is now inside a quote
-                                               var $blockquote = null;
-                                               var $current = ($range.startContainer.nodeType === Node.TEXT_NODE) ? $range.startContainer.parentElement : $range.startContainer;
-                                               while ($current !== null && $current !== this.$editor[0]) {
-                                                       if ($current.tagName === 'BLOCKQUOTE') {
-                                                               $blockquote = $current;
+                                               var blockquote = null;
+                                               var current = range.startContainer;
+                                               while (current !== null && current !== editor) {
+                                                       if (current.nodeName === 'BLOCKQUOTE') {
+                                                               blockquote = current;
                                                                break;
                                                        }
                                                        
-                                                       $current = $current.parentElement;
+                                                       current = current.parentNode;
                                                }
                                                
-                                               if ($blockquote !== null && $blockquote !== event.target) {
+                                               if (blockquote !== null && blockquote !== event.target) {
                                                        // click occured within inner quote margin, check if click happened before inner quote
-                                                       if (event.pageY <= $($blockquote).offset().top) {
-                                                               $setCaretBeforeOrAfter($blockquote, true);
+                                                       if (event.pageY <= getOffset(blockquote).top) {
+                                                               setCaretBeforeOrAfter(blockquote, true);
                                                        }
                                                        else {
-                                                               $setCaretBeforeOrAfter($blockquote, false);
+                                                               setCaretBeforeOrAfter(blockquote, false);
                                                        }
                                                }
                                        }
index d41694bb41a95be8f6a4b1f0b5030fc3a87ffcb3..ceb0244e44938d0e13662df0db44570bc504bfc2 100644 (file)
@@ -894,7 +894,7 @@ RedactorPlugins.wutil = function() {
                                $wasInWysiwygMode = true;
                        }
                        
-                       value = this.wutil.addNewlines(value);
+                       //value = this.wutil.addNewlines(value);
                        this.$textarea.val(value);
                        
                        if ($wasInWysiwygMode) {
@@ -951,44 +951,6 @@ RedactorPlugins.wutil = function() {
                 *  - pasting lists/list-items in lists can yield empty <li></li>
                 */
                fixDOM: function() {
-                       var $current = this.$editor[0].childNodes[0];
-                       var $nextSibling = $current;
-                       var $p = null;
-                       
-                       while ($nextSibling) {
-                               $current = $nextSibling;
-                               $nextSibling = $current.nextSibling;
-                               
-                               if ($current.nodeType === Element.ELEMENT_NODE) {
-                                       if (this.reIsBlock.test($current.tagName)) {
-                                               $p = null;
-                                       }
-                                       else {
-                                               if ($p === null) {
-                                                       $p = $('<p />').insertBefore($current);
-                                               }
-                                               
-                                               $p.append($current);
-                                       }
-                               }
-                               else if ($current.nodeType === Element.TEXT_NODE) {
-                                       if ($p === null) {
-                                               // check for ghost paragraphs next
-                                               if ($nextSibling) {
-                                                       if ($nextSibling.nodeType === Element.ELEMENT_NODE && $nextSibling.tagName === 'P' && $nextSibling.innerHTML === '\u200B') {
-                                                               var $afterNextSibling = $nextSibling.nextSibling;
-                                                               this.$editor[0].removeChild($nextSibling);
-                                                               $nextSibling = $afterNextSibling;
-                                                       }
-                                               }
-                                               
-                                               $p = $('<p />').insertBefore($current);
-                                       }
-                                       
-                                       $p.append($current);
-                               }
-                       }
-                       
                        var $listItems = this.$editor[0].getElementsByTagName('li');
                        for (var $i = 0, $length = $listItems.length; $i < $length; $i++) {
                                var $listItem = $listItems[$i];
index 583a9d0acc6a0052449a446284590f107af047cb..8612a5d1a06e880ff20c90f3c339f6ab9837e790 100644 (file)
@@ -55,6 +55,7 @@ define(['DOM/Traverse'], function(DOMTraverse) {
                                { tagName: 'OL', callback: this._convertList.bind(this) },
                                { tagName: 'TABLE', callback: this._convertTable.bind(this) },
                                { tagName: 'UL', callback: this._convertList.bind(this) },
+                               { tagName: 'BLOCKQUOTE', callback: this._convertBlockquote.bind(this) },
                                
                                // convert these last
                                { tagName: 'SPAN', callback: this._convertSpan.bind(this) },
@@ -92,6 +93,31 @@ define(['DOM/Traverse'], function(DOMTraverse) {
                        }
                },
                
+               _convertBlockquote: function(element) {
+                       var author = element.getAttribute('data-author') || '';
+                       var link = element.getAttribute('cite') || '';
+                       
+                       var open = '[quote]';
+                       if (author) {
+                               if (link) {
+                                       open = "[quote='" + author + "','" + link + "']";
+                               }
+                               else {
+                                       open = "[quote='" + author + "']";
+                               }
+                       }
+                       
+                       var header = DOMTraverse.childByTag(element, 'HEADER');
+                       if (header !== null) element.removeChild(header);
+                       
+                       var divs = DOMTraverse.childrenByTag(element, 'DIV');
+                       for (var i = 0, length = divs.length; i < length; i++) {
+                               divs[i].outerHTML = divs[i].innerHTML + '\n';
+                       }
+                       
+                       element.outerHTML = open + element.innerHTML.replace(/^\n*/, '').replace(/\n*$/, '') + '[/quote]\n';
+               },
+               
                _convertList: function(element) {
                        var open;
                        
index 2b8999f5a3dceda5d05eb66514594a03f04fc8aa..13bc0708890591addd5a8fb87dccacb24c3d661c 100644 (file)
@@ -10,7 +10,8 @@ define([], function() {
                },
                
                _splitTags: function(message) {
-                       var validTags = 'attach|b|color|i|list|url|table|td|tr';
+                       // TODO: `validTags` should be dynamic similar to the PHP implementation
+                       var validTags = 'attach|b|color|i|list|url|table|td|tr|quote';
                        var pattern = '(\\\[(?:/(?:' + validTags + ')|(?:' + validTags + ')'
                                + '(?:='
                                        + '(?:\\\'[^\\\'\\\\]*(?:\\\\.[^\\\'\\\\]*)*\\\'|[^,\\\]]*)'
@@ -26,7 +27,7 @@ define([], function() {
                                        continue;
                                }
                                else if (part.match(isBBCode)) {
-                                       tag = { name: '', closing: false, source: part };
+                                       tag = { name: '', closing: false, attributes: [], source: part };
                                        
                                        if (part[1] === '/') {
                                                tag.name = part.substring(2, part.length - 1);
@@ -106,13 +107,13 @@ define([], function() {
                                        break;
                                }
                                
-                               tag = { name: item.name, closing: true, source: '[/' + item.name + ']' };
+                               tag = { name: item.name, closing: true, attributes: item.attributes.slice(), source: '[/' + item.name + ']' };
                                item.pair = stack.length;
                                
                                stack.push(tag);
                                
                                openTags.pop();
-                               reopenTags.push({ name: item.name, closing: false, source: item.source });
+                               reopenTags.push({ name: item.name, closing: false, attributes: item.attributes.slice(), source: item.source });
                        }
                        
                        return reopenTags.reverse();
@@ -129,25 +130,25 @@ define([], function() {
                },
                
                _parseAttributes: function(attrString) {
-                       var tmp = attrString.match(/(?:^|,)('[^'\\\\]*(?:\\\\.[^'\\\\]*)*'|[^,]*)/g);
+                       var tmp = attrString.split(/(?:^|,)('[^'\\\\]*(?:\\\\.[^'\\\\]*)*'|[^,]*)/g);
                        
                        var attribute, attributes = [];
                        for (var i = 0, length = tmp.length; i < length; i++) {
                                attribute = tmp[i];
                                
                                if (attribute !== '') {
-                                       if (attribute[0] === "'" && attribute.substr(-1) === "'") {
-                                               attributes.push(attribute.substring(1, attribute.length - 1));
+                                       if (attribute.charAt(0) === "'" && attribute.substr(-1) === "'") {
+                                               attributes.push(attribute.substring(1, attribute.length - 1).trim());
                                        }
                                        else {
-                                               attributes.push(attribute);
+                                               attributes.push(attribute.trim());
                                        }
                                }
                        }
                        
                        return attributes;
                }
-       }
+       };
        
        return BBCodeParser;
 });
index b5c02461bd6028e447621cac7112041397d57903..86ac7ad04d39179bb7cc77947915e90627aadd41 100644 (file)
@@ -1,4 +1,4 @@
-define(['WoltLab/WCF/BBCode/Parser'], function(BBCodeParser) {
+define(['Language', 'StringUtil', 'WoltLab/WCF/BBCode/Parser'], function(Language, StringUtil, BBCodeParser) {
        "use strict";
        
        var _bbcodes = null;
@@ -59,19 +59,20 @@ define(['WoltLab/WCF/BBCode/Parser'], function(BBCodeParser) {
                                // callback replacement
                                color: this._replaceColor.bind(this),
                                list: this._replaceList.bind(this),
+                               quote: this._replaceQuote.bind(this),
                                url: this._replaceUrl.bind(this)
                        };
                        
-                       _removeNewlineAfter = ['table', 'td', 'tr'];
+                       _removeNewlineAfter = ['quote', 'table', 'td', 'tr'];
                        _removeNewlineBefore = ['table', 'td', 'tr'];
                },
                
                _replace: function(stack, item, index) {
-                       var pair = stack[item.pair], replace = _bbcodes[item.name], tmp;
+                       var replace = _bbcodes[item.name], tmp;
                        
                        if (replace === undefined) {
                                // treat as plain text
-                               stack[item.pair] = pair.source;
+                               stack[item.pair] = stack[item.pair].source;
                                
                                return item.source;
                        }
@@ -110,12 +111,12 @@ define(['WoltLab/WCF/BBCode/Parser'], function(BBCodeParser) {
                                return '<' + replace + '>';
                        }
                        else {
-                               return replace(stack, item, pair, index);
+                               return replace(stack, item, index);
                        }
                },
                
-               _replaceColor: function(stack, item, pair) {
-                       if (item.attributes === undefined || !item.attributes.length || !item.attributes[0].match(/^[a-z0-9#]+$/i)) {
+               _replaceColor: function(stack, item, index) {
+                       if (!item.attributes.length || !item.attributes[0].match(/^[a-z0-9#]+$/i)) {
                                stack[item.pair] = '';
                                
                                return '';
@@ -123,11 +124,11 @@ define(['WoltLab/WCF/BBCode/Parser'], function(BBCodeParser) {
                        
                        stack[item.pair] = '</span>';
                        
-                       return '<span style="color: ' + item.attributes[0] + '">';
+                       return '<span style="color: ' + StringUtil.escapeHTML(item.attributes[0]) + '">';
                },
                
-               _replaceList: function(stack, item, pair, index) {
-                       var type = (item.attributes === undefined || !items.attributes.length) ? '' : item.attributes[0].trim();
+               _replaceList: function(stack, item, index) {
+                       var type = (items.attributes.length) ? item.attributes[0] : '';
                        
                        // replace list items
                        for (var i = index + 1; i < item.pair; i++) {
@@ -150,9 +151,49 @@ define(['WoltLab/WCF/BBCode/Parser'], function(BBCodeParser) {
                        return '<ul>';
                },
                
-               _replaceUrl: function(stack, item, pair) {
+               _replaceQuote: function(stack, item, index) {
+                       var author = '', link = '';
+                       if (item.attributes.length > 1) {
+                               author = item.attributes[0];
+                               link = item.attributes[1];
+                       }
+                       else if (item.attributes.length === 1) {
+                               author = item.attributes[0];
+                       }
+                       
+                       stack[item.pair] = '</div></blockquote>';
+                       
+                       // get rid of the trailing newline for quote content
+                       for (var i = item.pair - 1; i > index; i--) {
+                               if (typeof stack[i] === 'string') {
+                                       stack[i] = stack[i].replace(/\n$/, '');
+                                       break;
+                               }
+                       }
+                       
+                       var header = '';
+                       if (author) {
+                               if (link) header = '<a href="' + StringUtil.escapeHTML(link) + '" tabindex="-1">';
+                               header += Language.get('wcf.bbcode.quote.title.javascript', { quoteAuthor: author });
+                               if (link) header += '</a>';
+                       }
+                       else {
+                               header = '<small>' + Language.get('wcf.bbcode.quote.title.clickToSet') + '</small>';
+                       }
+                       
+                       return '<blockquote class="quoteBox container containerPadding quoteBoxSimple" cite="' + StringUtil.escapeHTML(link) + '" data-author="' + StringUtil.escapeHTML(author) + '">'
+                                       + '<header contenteditable="false">'
+                                               + '<h3>'
+                                                       + header
+                                               + '</h3>'
+                                               + '<a class="redactorQuoteEdit"></a>'
+                                       + '</header>'
+                                       + '<div>\u200b';
+               },
+               
+               _replaceUrl: function(stack, item, index) {
                        // ignore url bbcode without arguments
-                       if (item.attributes === undefined || !item.attributes.length) {
+                       if (!item.attributes.length) {
                                stack[item.pair] = '';
                                
                                return '';
@@ -160,7 +201,7 @@ define(['WoltLab/WCF/BBCode/Parser'], function(BBCodeParser) {
                        
                        stack[item.pair] = '</a>';
                        
-                       return '<a href="' + item.attributes[0] + '">';
+                       return '<a href="' + StringUtil.escapeHTML(item.attributes[0]) + '">';
                }
        };