Revamped quotes to bypass Redactor's limitations
authorAlexander Ebert <ebert@woltlab.com>
Sun, 21 Aug 2016 20:25:11 +0000 (22:25 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Sun, 21 Aug 2016 20:25:18 +0000 (22:25 +0200)
14 files changed:
com.woltlab.wcf/templates/messageQuoteList.tpl
com.woltlab.wcf/templates/wysiwyg.tpl
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabBlock.js
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabKeydown.js
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabParagraphize.js [new file with mode: 0644]
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabQuote.js
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabSpoiler.js
wcfsetup/install/files/js/WCF.Message.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Quote.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Spoiler.js
wcfsetup/install/files/lib/system/html/input/filter/MessageHtmlInputFilter.class.php
wcfsetup/install/files/lib/system/html/output/node/HtmlOutputNodeBlockquote.class.php [deleted file]
wcfsetup/install/files/lib/system/html/output/node/HtmlOutputNodeWoltlabQuote.class.php [new file with mode: 0644]
wcfsetup/install/files/style/bbcode/quote.scss

index 0e6a7b51b2e23dbba380138278577665149edbb3..77441efd8f8196b073a1fdadb4a63f063d1daa6a 100644 (file)
@@ -33,7 +33,7 @@
                                <div class="messageText">
                                        <ul class="messageQuoteItemList">
                                                {foreach from=$message key=quoteID item=quote}
-                                                       <li data-quote-id="{@$quoteID}">
+                                                       <li data-quote-id="{@$quoteID}" data-is-full-quote="{if $message->isFullQuote($quoteID)}true{else}false{/if}">
                                                                <span>
                                                                        <input type="checkbox" value="1" id="quote_{@$quoteID}" class="jsCheckbox">
                                                                        {if $supportPaste}<span class="icon icon16 fa-plus jsTooltip jsInsertQuote" title="{lang}wcf.message.quote.insertQuote{/lang}"></span>{/if}
index 581427c7902fb790b5fe7a6a1598a1f4888db1fa..cf1e9b0a7f184fa646cf2a4aec96cf8cd65e4432 100644 (file)
@@ -25,6 +25,7 @@
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabMedia.js?v={@LAST_UPDATE_TIME}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabMention.js?v={@LAST_UPDATE_TIME}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabModal.js?v={@LAST_UPDATE_TIME}',
+                       '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabParagraphize.js?v={@LAST_UPDATE_TIME}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabQuote.js?v={@LAST_UPDATE_TIME}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabSize.js?v={@LAST_UPDATE_TIME}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabSmiley.js?v={@LAST_UPDATE_TIME}',
                                        'WoltLabInlineCode',
                                        'WoltLabLink',
                                        'WoltLabModal',
+                                       'WoltLabParagraphize',
                                        'WoltLabQuote',
                                        'WoltLabSize',
                                        'WoltLabSmiley',
                        // already and we can safely add all icons
                        config.plugins.push('WoltLabButton');
                        
+                       var content = element.value;
+                       element.value = '';
+                       
+                       config.callbacks = config.callbacks || { };
+                       config.callbacks.init = function() {
+                               // slight delay to allow Redactor to initialize itself
+                               window.setTimeout(function() {
+                                       $(element).redactor('code.set', content);
+                               }, 10);
+                       };
+                       
                        $(element).redactor(config);
                });
        });
index fece7b12d30f1b6b17d2ae3a5d84d85ace51060d..f260b1fe8d822c801cbb6c9ded922e424ef34029 100644 (file)
@@ -28,6 +28,42 @@ $.Redactor.prototype.WoltLabBlock = function() {
                                
                                return (this.utils.isCollapsed()) ? this.block.formatCollapsed(tag, attr, value, type) : this.block.formatUncollapsed(tag, attr, value, type);
                        }).bind(this);
+                       
+                       var mpFormatCollapsed = this.block.formatCollapsed;
+                       this.block.formatCollapsed = (function(tag, attr, value, type) {
+                               var replaced = mpFormatCollapsed.call(this, tag, attr, value, type);
+                               
+                               for (var i = 0, length = replaced.length; i < length; i++) {
+                                       this.WoltLabBlock._paragraphize(replaced[i]);
+                               }
+                               
+                               return replaced;
+                       }).bind(this);
+                       
+                       var mpFormatUncollapsed = this.block.formatUncollapsed;
+                       this.block.formatUncollapsed = (function(tag, attr, value, type) {
+                               var replaced = mpFormatUncollapsed.call(this, tag, attr, value, type);
+                               
+                               var block, firstBlock = null;
+                               for (var i = 0, length = replaced.length; i < length; i++) {
+                                       block = replaced[i][0];
+                                       
+                                       this.WoltLabBlock._paragraphize(block);
+                                       
+                                       if (i === 0) {
+                                               firstBlock = block;
+                                       }
+                                       else {
+                                               while (block.childNodes.length) {
+                                                       firstBlock.appendChild(block.childNodes[0]);
+                                               }
+                                               
+                                               elRemove(block);
+                                       }
+                               }
+                               
+                               return $(firstBlock);
+                       }).bind(this);
                },
                
                register: function(tag, arrowKeySupport) {
@@ -47,6 +83,20 @@ $.Redactor.prototype.WoltLabBlock = function() {
                        if (arrowKeySupport) {
                                this.WoltLabKeydown.register(tag);
                        }
+               },
+               
+               _paragraphize: function (block) {
+                       if (['p', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'figure'].indexOf(block.nodeName) !== -1) {
+                               // do not paragraphize these blocks
+                               return;
+                       }
+                       
+                       var paragraph = elCreate('p');
+                       while (block.childNodes.length) {
+                               paragraph.appendChild(block.childNodes[0]);
+                       }
+                       
+                       block.appendChild(paragraph);
                }
        }
 };
index 03f4f2807d57710716223cc28ba8f93b8f2b5a44..af89fcfcf9cae8c60a1807e668fcbd7166ce0e71 100644 (file)
@@ -26,6 +26,16 @@ $.Redactor.prototype.WoltLabKeydown = function() {
                                        }
                                }
                        }).bind(this);
+                       
+                       var mpOnEnter = this.keydown.onEnter;
+                       this.keydown.onEnter = (function(e) {
+                               var isBlockquote = this.keydown.blockquote;
+                               if (isBlockquote) this.keydown.blockquote = false;
+                               
+                               mpOnEnter.call(this, e);
+                               
+                               if (isBlockquote) this.keydown.blockquote = isBlockquote;
+                       }).bind(this);
                },
                
                register: function (tag) {
diff --git a/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabParagraphize.js b/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabParagraphize.js
new file mode 100644 (file)
index 0000000..ce9ceeb
--- /dev/null
@@ -0,0 +1,31 @@
+$.Redactor.prototype.WoltLabParagraphize = function() {
+       "use strict";
+       
+       return {
+               init: function () {
+                       this.paragraphize.getSafes = (function (html) {
+                               var $div = $('<div />').append(html);
+                               
+                               // WoltLab modification: do not remove <p> inside quotes
+                               // remove paragraphs in blockquotes
+                               /*$div.find('blockquote p').replaceWith(function()
+                               {
+                                       return $(this).append('<br />').contents();
+                               });*/
+                               
+                               $div.find(this.opts.paragraphizeBlocks.join(', ')).each($.proxy(function(i,s)
+                               {
+                                       this.paragraphize.z++;
+                                       this.paragraphize.safes[this.paragraphize.z] = s.outerHTML;
+                                       
+                                       return $(s).replaceWith('\n#####replace' + this.paragraphize.z + '#####\n\n');
+                                       
+                                       
+                               }, this));
+                               
+                               
+                               return $div.html();
+                       }).bind(this)
+               }
+       };
+};
index 05668ce02bda06d88aaddd722be2b25773c9293e..13c40f875e9b5ddcfc454293d9456ede7efcd046 100644 (file)
@@ -5,6 +5,12 @@ $.Redactor.prototype.WoltLabQuote = function() {
                init: function() {
                        var button = this.button.add('woltlabQuote', '');
                        
+                       this.WoltLabBlock.register('woltlab-quote', true);
+                       this.opts.replaceTags.blockquote = 'woltlab-quote';
+                       
+                       // support for active button marking
+                       this.opts.activeButtonsStates['woltlab-quote'] = 'woltlabQuote';
+                       
                        require(['WoltLabSuite/Core/Ui/Redactor/Quote'], (function (UiRedactorQuote) {
                                new UiRedactorQuote(this, button);
                        }).bind(this));
index 658156572e22a5e68aa69d362d7114fa33e3eaf4..88a70d8458bef0b099420dbb249f45360c90fc2b 100644 (file)
@@ -3,6 +3,12 @@ $.Redactor.prototype.WoltLabSpoiler = function() {
        
        return {
                init: function() {
+                       // register custom block element
+                       this.WoltLabBlock.register('woltlab-spoiler', true);
+                       
+                       // support for active button marking
+                       this.opts.activeButtonsStates['woltlab-spoiler'] = 'woltlabSpoiler';
+                       
                        require(['WoltLabSuite/Core/Ui/Redactor/Spoiler'], (function (UiRedactorSpoiler) {
                                new UiRedactorSpoiler(this);
                        }).bind(this));
index 43152409af4565314211eb33245b440e861f9102..28e89559245f9beb5d45e5305b751c3f1532ca58 100644 (file)
@@ -1857,7 +1857,7 @@ WCF.Message.Quote.Manager = Class.extend({
                var message = listItem.parents('.message:eq(0)');
                var author = message.data('username');
                var link = message.data('link');
-               var isText = listItem.find('.jsQuote').length === 0;
+               var isText = elData(listItem[0], 'is-full-quote');
                
                WCF.System.Event.fireEvent('com.woltlab.wcf.redactor2', 'insertQuote_' + (this._editorIdAlternative ? this._editorIdAlternative : this._editorId), {
                        author: author,
index b13726f0b58d3f02a4fbfa04fcbaefaad38224dd..f5609ad0a3558959f72436361b802b8e0b307e33 100644 (file)
@@ -25,7 +25,7 @@ define(['Core', 'EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util'
                 * @param       {jQuery}        button  toolbar button
                 */
                init: function(editor, button) {
-                       this._blockquote = null;
+                       this._quote = null;
                        this._editor = editor;
                        this._elementId = this._editor.$element[0].id;
                        
@@ -33,9 +33,6 @@ define(['Core', 'EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util'
                        
                        this._editor.button.addCallback(button, this._click.bind(this));
                        
-                       // support for active button marking
-                       this._editor.opts.activeButtonsStates.blockquote = 'woltlabQuote';
-                       
                        // static bind to ensure that removing works
                        this._callbackEdit = this._edit.bind(this);
                        
@@ -64,42 +61,33 @@ define(['Core', 'EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util'
                                block = this._editor.selection.block();
                        }
                        
-                       if (block.nodeName !== 'P') {
-                               var redactor = this._editor.core.editor()[0];
-                               
-                               // find parent before Redactor
-                               while (block.parentNode !== redactor) {
-                                       block = block.parentNode;
-                               }
-                               
-                               // caret.after() requires a following element
-                               var next = this._editor.caret.next(block);
-                               if (next === undefined || next.nodeName !== 'P') {
-                                       var p = elCreate('p');
-                                       p.textContent = '\u200B';
-                                       
-                                       DomUtil.insertAfter(p, block);
-                               }
-                               
-                               this._editor.caret.after(block);
+                       var redactor = this._editor.core.editor()[0];
+                       while (block.parentNode && block.parentNode !== redactor) {
+                               block = block.parentNode;
                        }
                        
-                       var content = '';
-                       if (data.isText) content = this._editor.marker.html();
-                       else content = data.content;
+                       this._editor.caret.after(block);
                        
                        var quoteId = Core.getUuid();
-                       this._editor.insert.html('<blockquote id="' + quoteId + '">' + content + '</blockquote>');
+                       this._editor.insert.html('<woltlab-quote id="' + quoteId + '"></woltlab-quote>');
                        
                        var quote = elById(quoteId);
                        elData(quote, 'author', data.author);
                        elData(quote, 'link', data.link);
                        
+                       this._editor.selection.restore();
+                       var content = data.content;
+                       console.debug(data);
                        if (data.isText) {
-                               this._editor.selection.restore();
-                               this._editor.insert.text(data.content);
+                               content = StringUtil.escapeHTML(content);
+                               content = '<p>' + content + '</p>';
+                               content = content.replace(/\n\n/g, '</p><p>');
+                               content = content.replace(/\n/g, '<br>');
                        }
                        
+                       // bypass the editor as `insert.html()` doesn't like us
+                       quote.innerHTML = content;
+                       
                        quote.removeAttribute('id');
                        
                        this._editor.caret.after(quote);
@@ -113,13 +101,13 @@ define(['Core', 'EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util'
                 * @protected
                 */
                _click: function() {
-                       this._editor.button.toggle({}, 'blockquote', 'func', 'block.format');
+                       this._editor.button.toggle({}, 'woltlab-quote', 'func', 'block.format');
                        
-                       var blockquote = this._editor.selection.block();
-                       if (blockquote && blockquote.nodeName === 'BLOCKQUOTE') {
-                               this._setTitle(blockquote);
+                       var quote = this._editor.selection.block();
+                       if (quote && quote.nodeName === 'WOLTLAB-QUOTE') {
+                               this._setTitle(quote);
                                
-                               blockquote.addEventListener(WCF_CLICK_EVENT, this._callbackEdit);
+                               quote.addEventListener(WCF_CLICK_EVENT, this._callbackEdit);
                        }
                },
                
@@ -130,9 +118,9 @@ define(['Core', 'EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util'
                 * @protected
                 */
                _observeLoad: function() {
-                       elBySelAll('blockquote', this._editor.$editor[0], (function(blockquote) {
-                               blockquote.addEventListener(WCF_CLICK_EVENT, this._callbackEdit);
-                               this._setTitle(blockquote);
+                       elBySelAll('woltlab-quote', this._editor.$editor[0], (function(quote) {
+                               quote.addEventListener(WCF_CLICK_EVENT, this._callbackEdit);
+                               this._setTitle(quote);
                        }).bind(this));
                },
                
@@ -143,23 +131,23 @@ define(['Core', 'EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util'
                 * @protected
                 */
                _edit: function(event) {
-                       var blockquote = event.currentTarget;
+                       var quote = event.currentTarget;
                        
                        if (_headerHeight === 0) {
-                               _headerHeight = ~~window.getComputedStyle(blockquote).paddingTop.replace(/px$/, '');
+                               _headerHeight = ~~window.getComputedStyle(quote).paddingTop.replace(/px$/, '');
                                
-                               var styles = window.getComputedStyle(blockquote, '::before');
+                               var styles = window.getComputedStyle(quote, '::before');
                                _headerHeight += ~~styles.paddingTop.replace(/px$/, '');
                                _headerHeight += ~~styles.height.replace(/px$/, '');
                                _headerHeight += ~~styles.paddingBottom.replace(/px$/, '');
                        }
                        
                        // check if the click hit the header
-                       var offset = DomUtil.offset(blockquote);
+                       var offset = DomUtil.offset(quote);
                        if (event.pageY > offset.top && event.pageY < (offset.top + _headerHeight)) {
                                event.preventDefault();
                                
-                               this._blockquote = blockquote;
+                               this._quote = quote;
                                
                                UiDialog.open(this);
                        }
@@ -190,13 +178,13 @@ define(['Core', 'EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util'
                        }
                        
                        // set author
-                       elData(this._blockquote, 'author', elById(id + '-author').value);
+                       elData(this._quote, 'author', elById(id + '-author').value);
                        
                        // set url
-                       elData(this._blockquote, 'url', url);
+                       elData(this._quote, 'url', url);
                        
-                       this._setTitle(this._blockquote);
-                       this._editor.caret.after(this._blockquote);
+                       this._setTitle(this._quote);
+                       this._editor.caret.after(this._quote);
                        
                        UiDialog.close(this);
                },
@@ -204,17 +192,17 @@ define(['Core', 'EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util'
                /**
                 * Sets or updates the quote's header title.
                 * 
-                * @param       {Element}       blockquote     quote element
+                * @param       {Element}       quote     quote element
                 * @protected
                 */
-               _setTitle: function(blockquote) {
+               _setTitle: function(quote) {
                        var title = Language.get('wcf.editor.quote.title', {
-                               author: elData(blockquote, 'author'),
-                               url: elData(blockquote, 'url')
+                               author: elData(quote, 'author'),
+                               url: elData(quote, 'url')
                        });
                        
-                       if (elData(blockquote, 'title') !== title) {
-                               elData(blockquote, 'title', title);
+                       if (elData(quote, 'title') !== title) {
+                               elData(quote, 'title', title);
                        }
                },
                
@@ -232,8 +220,8 @@ define(['Core', 'EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util'
                                        }).bind(this),
                                        
                                        onShow: (function() {
-                                               elById(idAuthor).value = elData(this._blockquote, 'author');
-                                               elById(idUrl).value = elData(this._blockquote, 'url');
+                                               elById(idAuthor).value = elData(this._quote, 'author');
+                                               elById(idUrl).value = elData(this._quote, 'url');
                                        }).bind(this),
                                        
                                        title: Language.get('wcf.editor.quote.edit')
index 1a95278807dcf001d9f7e8e9db108c70d41159cf..5d6f9438e6868bd68fd680ea1c33e23706a33bd9 100644 (file)
@@ -30,12 +30,6 @@ define(['EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Di
                        EventHandler.add('com.woltlab.wcf.redactor2', 'bbcode_spoiler_' + this._elementId, this._bbcodeSpoiler.bind(this));
                        EventHandler.add('com.woltlab.wcf.redactor2', 'observe_load_' + this._elementId, this._observeLoad.bind(this));
                        
-                       // register custom block element
-                       this._editor.WoltLabBlock.register('woltlab-spoiler', true);
-                       
-                       // support for active button marking
-                       this._editor.opts.activeButtonsStates['woltlab-spoiler'] = 'woltlabSpoiler';
-                       
                        // static bind to ensure that removing works
                        this._callbackEdit = this._edit.bind(this);
                        
index 71320e2f12e0bb8fd6a1362969d82852b01eb23a..42e81ca2d0b5c6868aa81c74544ba95434ea37ab 100644 (file)
@@ -48,10 +48,6 @@ class MessageHtmlInputFilter implements IHtmlInputFilter {
        protected function setAttributeDefinitions(\HTMLPurifier_Config $config) {
                $definition = $config->getHTMLDefinition(true);
                
-               // quotes
-               $definition->addAttribute('blockquote', 'data-author', 'Text');
-               $definition->addAttribute('blockquote', 'data-link', 'URI');
-               
                // code
                $definition->addAttribute('pre', 'data-file', 'Text');
                $definition->addAttribute('pre', 'data-line', 'Number');
@@ -67,6 +63,12 @@ class MessageHtmlInputFilter implements IHtmlInputFilter {
                $definition->addAttribute('img', 'data-media-id', 'Number');
                $definition->addAttribute('img', 'data-media-size', new \HTMLPurifier_AttrDef_Enum(['small', 'medium', 'large', 'original']));
                
+               // quote
+               $definition->addElement('woltlab-quote', 'Block', 'Flow', '', [
+                       'data-author' => 'Text',
+                       'data-link' => 'URI'
+               ]);
+               
                // spoiler
                $definition->addElement('woltlab-spoiler', 'Block', 'Flow', '', [
                        'data-label' => 'Text'
diff --git a/wcfsetup/install/files/lib/system/html/output/node/HtmlOutputNodeBlockquote.class.php b/wcfsetup/install/files/lib/system/html/output/node/HtmlOutputNodeBlockquote.class.php
deleted file mode 100644 (file)
index b5c7d7d..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-<?php
-namespace wcf\system\html\output\node;
-use wcf\system\application\ApplicationHandler;
-use wcf\system\html\node\AbstractHtmlNodeProcessor;
-use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
-use wcf\system\request\RouteHandler;
-use wcf\system\WCF;
-use wcf\util\DOMUtil;
-use wcf\util\StringUtil;
-
-/**
- * Processes quotes.
- * 
- * @author      Alexander Ebert
- * @copyright   2001-2016 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package     WoltLabSuite\Core\System\Html\Output\Node
- * @since       3.0
- */
-class HtmlOutputNodeBlockquote extends AbstractHtmlOutputNode {
-       /**
-        * @inheritDoc
-        */
-       protected $tagName = 'blockquote';
-       
-       /**
-        * @inheritDoc
-        */
-       public function process(array $elements, AbstractHtmlNodeProcessor $htmlNodeProcessor) {
-               /** @var \DOMElement $element */
-               foreach ($elements as $element) {
-                       switch ($this->outputType) {
-                               case 'text/html':
-                                       $nodeIdentifier = StringUtil::getRandomID();
-                                       $htmlNodeProcessor->addNodeData($this, $nodeIdentifier, [
-                                               'author' => $element->getAttribute('data-author'),
-                                               'url' => $element->getAttribute('data-link')
-                                       ]);
-                                       
-                                       $htmlNodeProcessor->renameTag($element, 'wcfNode-' . $nodeIdentifier);
-                                       break;
-                               
-                               case 'text/simplified-html':
-                               case 'text/plain':
-                                       // check if this quote is within another
-                                       if (DOMUtil::hasParent($element, 'blockquote')) {
-                                               DOMUtil::removeNode($element);
-                                       }
-                                       else {
-                                               $htmlNodeProcessor->replaceElementWithText(
-                                                       $element,
-                                                       WCF::getLanguage()->getDynamicVariable('wcf.bbcode.quote.simplified', ['cite' => $element->getAttribute('data-author')]),
-                                                       true
-                                               );
-                                       }
-                                       break;
-                       }
-               }
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function replaceTag(array $data) {
-               $externalQuoteLink = (!empty($data['url'])) ? !ApplicationHandler::getInstance()->isInternalURL($data['url']) : false;
-               if (!$externalQuoteLink) {
-                       $data['url'] = preg_replace('~^https://~', RouteHandler::getProtocol(), $data['url']);
-               }
-               
-               $quoteAuthorObject = null;
-               if ($data['author'] && !$externalQuoteLink) {
-                       $quoteAuthorLC = mb_strtolower(StringUtil::decodeHTML($data['author']));
-                       foreach (MessageEmbeddedObjectManager::getInstance()->getObjects('com.woltlab.wcf.quote') as $user) {
-                               if (mb_strtolower($user->username) == $quoteAuthorLC) {
-                                       $quoteAuthorObject = $user;
-                                       break;
-                               }
-                       }
-               }
-               
-               WCF::getTPL()->assign([
-                       'quoteLink' => $data['url'],
-                       'quoteAuthor' => $data['author'],
-                       'quoteAuthorObject' => $quoteAuthorObject,
-                       'isExternalQuoteLink' => $externalQuoteLink
-               ]);
-               return WCF::getTPL()->fetch('quoteMetaCode');
-       }
-}
diff --git a/wcfsetup/install/files/lib/system/html/output/node/HtmlOutputNodeWoltlabQuote.class.php b/wcfsetup/install/files/lib/system/html/output/node/HtmlOutputNodeWoltlabQuote.class.php
new file mode 100644 (file)
index 0000000..f45d884
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+namespace wcf\system\html\output\node;
+use wcf\system\application\ApplicationHandler;
+use wcf\system\html\node\AbstractHtmlNodeProcessor;
+use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
+use wcf\system\request\RouteHandler;
+use wcf\system\WCF;
+use wcf\util\DOMUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Processes quotes.
+ * 
+ * @author      Alexander Ebert
+ * @copyright   2001-2016 WoltLab GmbH
+ * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package     WoltLabSuite\Core\System\Html\Output\Node
+ * @since       3.0
+ */
+class HtmlOutputNodeWoltlabQuote extends AbstractHtmlOutputNode {
+       /**
+        * @inheritDoc
+        */
+       protected $tagName = 'woltlab-quote';
+       
+       /**
+        * @inheritDoc
+        */
+       public function process(array $elements, AbstractHtmlNodeProcessor $htmlNodeProcessor) {
+               /** @var \DOMElement $element */
+               foreach ($elements as $element) {
+                       switch ($this->outputType) {
+                               case 'text/html':
+                                       $nodeIdentifier = StringUtil::getRandomID();
+                                       $htmlNodeProcessor->addNodeData($this, $nodeIdentifier, [
+                                               'author' => $element->getAttribute('data-author'),
+                                               'url' => $element->getAttribute('data-link')
+                                       ]);
+                                       
+                                       $htmlNodeProcessor->renameTag($element, 'wcfNode-' . $nodeIdentifier);
+                                       break;
+                               
+                               case 'text/simplified-html':
+                               case 'text/plain':
+                                       // check if this quote is within another
+                                       if (DOMUtil::hasParent($element, 'woltlab-quote')) {
+                                               DOMUtil::removeNode($element);
+                                       }
+                                       else {
+                                               $htmlNodeProcessor->replaceElementWithText(
+                                                       $element,
+                                                       WCF::getLanguage()->getDynamicVariable('wcf.bbcode.quote.simplified', ['cite' => $element->getAttribute('data-author')]),
+                                                       true
+                                               );
+                                       }
+                                       break;
+                       }
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function replaceTag(array $data) {
+               $externalQuoteLink = (!empty($data['url'])) ? !ApplicationHandler::getInstance()->isInternalURL($data['url']) : false;
+               if (!$externalQuoteLink) {
+                       $data['url'] = preg_replace('~^https://~', RouteHandler::getProtocol(), $data['url']);
+               }
+               
+               $quoteAuthorObject = null;
+               if ($data['author'] && !$externalQuoteLink) {
+                       $quoteAuthorLC = mb_strtolower(StringUtil::decodeHTML($data['author']));
+                       foreach (MessageEmbeddedObjectManager::getInstance()->getObjects('com.woltlab.wcf.quote') as $user) {
+                               if (mb_strtolower($user->username) == $quoteAuthorLC) {
+                                       $quoteAuthorObject = $user;
+                                       break;
+                               }
+                       }
+               }
+               
+               WCF::getTPL()->assign([
+                       'quoteLink' => $data['url'],
+                       'quoteAuthor' => $data['author'],
+                       'quoteAuthorObject' => $quoteAuthorObject,
+                       'isExternalQuoteLink' => $externalQuoteLink
+               ]);
+               return WCF::getTPL()->fetch('quoteMetaCode');
+       }
+}
index e3efa6aaa117902be8dbec984fc9e2db594a2b42..b2067899fa053665464f8fe77b7a28dcfb40948d 100644 (file)
@@ -1,7 +1,8 @@
-.redactor-editor blockquote,
+woltlab-quote,
 .quoteBox {
        background-color: $wcfContentBackground;
        box-shadow: 0 0 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);
+       display: block;
        font-style: italic;
        margin: 20px 0;
        padding: 20px;
@@ -20,7 +21,7 @@
        }
 }
 
-.redactor-editor blockquote::before {
+woltlab-quote::before {
        content: attr(data-title);
        cursor: pointer;
        display: block;