Added proper support for simplified-html/plain
authorAlexander Ebert <ebert@woltlab.com>
Tue, 28 Jun 2016 08:23:40 +0000 (10:23 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Tue, 28 Jun 2016 08:23:47 +0000 (10:23 +0200)
com.woltlab.wcf/templates/spoilerMetaCode.tpl
wcfsetup/install/files/js/WoltLab/WCF/Ajax/Request.js
wcfsetup/install/files/lib/system/html/node/AbstractHtmlNodeProcessor.class.php
wcfsetup/install/files/lib/system/html/output/node/HtmlOutputNodeBlockquote.class.php
wcfsetup/install/files/lib/system/html/output/node/HtmlOutputNodePre.class.php
wcfsetup/install/files/lib/system/html/output/node/HtmlOutputNodeProcessor.class.php
wcfsetup/install/files/lib/system/html/output/node/HtmlOutputNodeWoltlabMention.class.php
wcfsetup/install/files/lib/system/html/output/node/HtmlOutputNodeWoltlabSpoiler.class.php
wcfsetup/install/files/lib/util/DOMUtil.class.php
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index 74671a294158fb2a465fcf5e9bc45c08e55ca797..cfe2a5e3499b4006d78c7153386ce09f73c63a05 100644 (file)
@@ -1,4 +1,3 @@
-<!-- begin:parser_nonessential -->
 <div class="spoilerBox jsSpoilerBox">
        <header class="jsOnly">
                <a class="button small jsSpoilerToggle"{if $buttonLabel} data-has-custom-label="true"{/if}>{if $buttonLabel}{$buttonLabel}{else}{lang}wcf.bbcode.spoiler.show{/lang}{/if}</a>
@@ -7,28 +6,27 @@
        <div style="display: none">
                <!-- META_CODE_INNER_CONTENT -->
        </div>
-</div>
-
-{if !$__wcfSpoilerBBCodeJavaScript|isset}
-       {assign var='__wcfSpoilerBBCodeJavaScript' value=true}
-       <script data-relocate="true">
-               elBySelAll('.jsSpoilerBox', null, function(spoilerBox) {
-                       spoilerBox.classList.remove('jsSpoilerBox');
-                       
-                       var toggleButton = elBySel('.jsSpoilerToggle', spoilerBox);
-                       var container = toggleButton.parentNode.nextElementSibling;
-                       
-                       toggleButton.addEventListener(WCF_CLICK_EVENT, function(event) {
-                               event.preventDefault();
+       
+       {if !$__wcfSpoilerBBCodeJavaScript|isset}
+               {assign var='__wcfSpoilerBBCodeJavaScript' value=true}
+               <script data-relocate="true">
+                       elBySelAll('.jsSpoilerBox', null, function(spoilerBox) {
+                               spoilerBox.classList.remove('jsSpoilerBox');
                                
-                               toggleButton.classList.toggle('active');
-                               window[(toggleButton.classList.contains('active') ? 'elShow' : 'elHide')](container);
+                               var toggleButton = elBySel('.jsSpoilerToggle', spoilerBox);
+                               var container = toggleButton.parentNode.nextElementSibling;
                                
-                               if (!elDataBool(toggleButton, 'has-custom-label')) {
-                                       toggleButton.textContent = (toggleButton.classList.contains('active')) ? '{lang}wcf.bbcode.spoiler.hide{/lang}' : '{lang}wcf.bbcode.spoiler.show{/lang}';
-                               }
+                               toggleButton.addEventListener(WCF_CLICK_EVENT, function(event) {
+                                       event.preventDefault();
+                                       
+                                       toggleButton.classList.toggle('active');
+                                       window[(toggleButton.classList.contains('active') ? 'elShow' : 'elHide')](container);
+                                       
+                                       if (!elDataBool(toggleButton, 'has-custom-label')) {
+                                               toggleButton.textContent = (toggleButton.classList.contains('active')) ? '{lang}wcf.bbcode.spoiler.hide{/lang}' : '{lang}wcf.bbcode.spoiler.show{/lang}';
+                                       }
+                               });
                        });
-               });
-       </script>
-{/if}
-<!-- end:parser_nonessential -->
+               </script>
+       {/if}
+</div>
index 627cac6d5365b1c184716549ffd02b281a80a64e..22f618fe4c85bde3ad1ca39eb5168ac8af8489cb 100644 (file)
@@ -24,12 +24,12 @@ define(['Core', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Ui/Dialog', 'Wolt
                this._xhr = null;
                
                this._init(options);
-       };
+       }
        AjaxRequest.prototype = {
                /**
                 * Initializes the request options.
                 * 
-                * @param       {object<string, *>}     options         request options
+                * @param       {Object}        options         request options
                 */
                _init: function(options) {
                        this._options = Core.extend({
@@ -168,7 +168,7 @@ define(['Core', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Ui/Dialog', 'Wolt
                 * Sets a specific option.
                 * 
                 * @param       {string}        key     option name
-                * @param       {*}             value   option value
+                * @param       {?}             value   option value
                 */
                setOption: function(key, value) {
                        this._options[key] = value;
@@ -191,7 +191,7 @@ define(['Core', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Ui/Dialog', 'Wolt
                /**
                 * Sets request data while honoring pinned data from setup callback.
                 * 
-                * @param       {object<string, *>}     data    request data
+                * @param       {Object}        data    request data
                 */
                setData: function(data) {
                        if (this._data !== null && Core.getType(data) !== 'FormData') {
@@ -205,7 +205,7 @@ define(['Core', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Ui/Dialog', 'Wolt
                 * Handles a successful request.
                 * 
                 * @param       {XMLHttpRequest}        xhr             request object
-                * @param       {object<string, *>}     options         request options
+                * @param       {Object}                options         request options
                 */
                _success: function(xhr, options) {
                        if (!options.silent) {
@@ -242,7 +242,7 @@ define(['Core', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Ui/Dialog', 'Wolt
                 * a non-success status code or an entirely failed request.
                 * 
                 * @param       {XMLHttpRequest}        xhr             request object
-                * @param       {object<string, *>}     options         request options
+                * @param       {Object}                options         request options
                 */
                _failure: function (xhr, options) {
                        if (_ignoreAllErrors) {
@@ -260,7 +260,7 @@ define(['Core', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Ui/Dialog', 'Wolt
                        catch (e) {}
                        
                        var showError = true;
-                       if (typeof options.failure === 'function') {
+                       if (data !== null && typeof options.failure === 'function') {
                                showError = options.failure(data, xhr.responseText, xhr, options.data);
                        }
                        
@@ -296,7 +296,7 @@ define(['Core', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Ui/Dialog', 'Wolt
                /**
                 * Finalizes a request.
                 * 
-                * @param       {object<string, *>}     options         request options
+                * @param       {Object}        options         request options
                 */
                _finalize: function(options) {
                        if (typeof options.finalize === 'function') {
index 8c757d32ba5440446d787b0a232425621fde5ad6..0d9be92cf2105df5b9608b54a31c8043940542e7 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\system\html\node;
 use wcf\system\exception\SystemException;
 use wcf\system\html\IHtmlProcessor;
+use wcf\util\DOMUtil;
 use wcf\util\JSON;
 
 /**
@@ -134,11 +135,20 @@ abstract class AbstractHtmlNodeProcessor implements IHtmlNodeProcessor {
         * Replaces an element with plain text.
         * 
         * @param       \DOMElement     $element        target element
-        * @param       string          $text           text used to replace target element
+        * @param       string          $text           text used to replace target 
+        * @param       boolean         $isBlockElement true if element is a block element
         */
-       public function replaceElementWithText(\DOMElement $element, $text) {
+       public function replaceElementWithText(\DOMElement $element, $text, $isBlockElement) {
                $textNode = $element->ownerDocument->createTextNode($text);
                $element->parentNode->insertBefore($textNode, $element);
+               
+               if ($isBlockElement) {
+                       for ($i = 0; $i < 2; $i++) {
+                               $br = $element->ownerDocument->createElement('br');
+                               $element->parentNode->insertBefore($br, $element);
+                       }
+               }
+               
                $element->parentNode->removeChild($element);
        }
        
index 5ffc96d3a712f06444fec204775917cfab37ec65..9e5e15497fe11e495f6ccebf9fa8bd2882eff64b 100644 (file)
@@ -49,7 +49,8 @@ class HtmlOutputNodeBlockquote extends AbstractHtmlOutputNode {
                                        else {
                                                $htmlNodeProcessor->replaceElementWithText(
                                                        $element,
-                                                       WCF::getLanguage()->getDynamicVariable('wcf.bbcode.quote.simplified', ['cite' => $element->getAttribute('data-author')])."\n"
+                                                       WCF::getLanguage()->getDynamicVariable('wcf.bbcode.quote.simplified', ['cite' => $element->getAttribute('data-author')]),
+                                                       true
                                                );
                                        }
                                        break;
index 95d4c94d37777ab998b64302a6807a53167d6850..3cca2303284c18c478c9220c387eea87f61d54c5 100644 (file)
@@ -50,8 +50,8 @@ class HtmlOutputNodePre extends AbstractHtmlOutputNode {
                                        $nodeIdentifier = StringUtil::getRandomID();
                                        $htmlNodeProcessor->addNodeData($this, $nodeIdentifier, [
                                                'content' => $element->textContent,
-                                               'file' => ($element->hasAttribute('data-file')) ? $element->getAttribute('data-file') : '',
-                                               'highlighter' => ($element->hasAttribute('data-highlighter')) ? $element->getAttribute('data-highlighter') : '',
+                                               'file' => $element->getAttribute('data-file'),
+                                               'highlighter' => $element->getAttribute('data-highlighter'),
                                                'line' => ($element->hasAttribute('data-line')) ? $element->getAttribute('data-line') : 1
                                        ]);
                                        
@@ -60,7 +60,11 @@ class HtmlOutputNodePre extends AbstractHtmlOutputNode {
                                
                                case 'text/simplified-html':
                                case 'text/plain':
-                                       return WCF::getLanguage()->getDynamicVariable('wcf.bbcode.code.simplified', ['lines' => substr_count($element->nodeValue, "\n") + 1]);
+                                       $htmlNodeProcessor->replaceElementWithText(
+                                               $element,
+                                               WCF::getLanguage()->getDynamicVariable('wcf.bbcode.code.simplified', ['lines' => substr_count($element->nodeValue, "\n") + 1]),
+                                               true
+                                       );
                                        break;
                        }
                }
index cf0adb66cc8669082c2ffa712b1288411080d49d..175ea2884f077fcb282a0dd36b1b282861849325 100644 (file)
@@ -2,6 +2,8 @@
 namespace wcf\system\html\output\node;
 use wcf\system\html\node\AbstractHtmlNodeProcessor;
 use wcf\system\html\node\IHtmlNode;
+use wcf\util\DOMUtil;
+use wcf\util\StringUtil;
 
 /**
  * Processes a HTML string and renders the final output for display.
@@ -41,6 +43,66 @@ class HtmlOutputNodeProcessor extends AbstractHtmlNodeProcessor {
                
                // dynamic node handlers
                $this->invokeNodeHandlers('wcf\system\html\output\node\HtmlOutputNode', ['woltlab-metacode']);
+               
+               if ($this->outputType !== 'text/html') {
+                       // convert `<p>...</p>` into `...<br><br>`
+                       $paragraphs = $this->getDocument()->getElementsByTagName('p');
+                       while ($paragraphs->length) {
+                               $paragraph = $paragraphs->item(0);
+                               
+                               for ($i = 0; $i < 2; $i++) {
+                                       $br = $this->getDocument()->createElement('br');
+                                       $paragraph->appendChild($br);
+                               }
+                               
+                               DOMUtil::removeNode($paragraph, true);
+                       }
+                       
+                       if ($this->outputType === 'text/plain') {
+                               // remove all `\n` first
+                               $nodes = [];
+                               /** @var \DOMText $node */
+                               foreach ($this->getXPath()->query('//text()') as $node) {
+                                       if (strpos($node->textContent, "\n") !== false) {
+                                               $nodes[] = $node;
+                                       }
+                               }
+                               foreach ($nodes as $node) {
+                                       $textNode = $this->getDocument()->createTextNode(preg_replace('~\r?\n~', '', $node->textContent));
+                                       $node->parentNode->insertBefore($textNode, $node);
+                                       $node->parentNode->removeChild($node);
+                               }
+                               
+                               // convert `<br>` into `\n`
+                               $brs = $this->getDocument()->getElementsByTagName('br');
+                               while ($brs->length) {
+                                       $br = $brs->item(0);
+                                       
+                                       $newline = $this->getDocument()->createTextNode("\n");
+                                       $br->parentNode->insertBefore($newline, $br);
+                                       DOMUtil::removeNode($br);
+                               }
+                               
+                               // remove all other elements
+                               $elements = $this->getDocument()->getElementsByTagName('*');
+                               while ($elements->length) {
+                                       DOMUtil::removeNode($elements->item(0), true);
+                               }
+                       }
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getHtml() {
+               $html = parent::getHtml();
+               
+               if ($this->outputType === 'text/plain') {
+                       $html = StringUtil::trim($html);
+               }
+               
+               return $html;
        }
        
        /**
index 3ae4183a50f39d7e804dcc13d359c4dcaebd0ad2..d580e7ae05a497da67ff71bbf50efee9b5677a0b 100644 (file)
@@ -59,7 +59,7 @@ class HtmlOutputNodeWoltlabMention extends AbstractHtmlOutputNode {
                else if ($this->outputType === 'text/plain') {
                        /** @var \DOMElement $element */
                        foreach ($elements as $element) {
-                               $htmlNodeProcessor->replaceElementWithText($element, '@' . $element->getAttribute('data-username'));
+                               $htmlNodeProcessor->replaceElementWithText($element, '@' . $element->getAttribute('data-username'), false);
                        }
                }
        }
index 2305900c0a56c30a270fc3e2f3406831108f4921..3e75dfaba25de222b7c1ae0f6d2a67cab48fbeee 100644 (file)
@@ -24,19 +24,20 @@ class HtmlOutputNodeWoltlabSpoiler extends AbstractHtmlOutputNode {
         * @inheritDoc
         */
        public function process(array $elements, AbstractHtmlNodeProcessor $htmlNodeProcessor) {
-               if ($this->outputType === 'text/html' || $this->outputType === 'text/simplified-html') {
-                       /** @var \DOMElement $element */
-                       foreach ($elements as $element) {
+               /** @var \DOMElement $element */
+               foreach ($elements as $element) {
+                       if ($this->outputType === 'text/html') {
                                $nodeIdentifier = StringUtil::getRandomID();
                                $htmlNodeProcessor->addNodeData($this, $nodeIdentifier, ['label' => $element->getAttribute('data-label')]);
                                
                                $htmlNodeProcessor->renameTag($element, 'wcfNode-' . $nodeIdentifier);
                        }
-               }
-               else if ($this->outputType === 'text/plain') {
-                       /** @var \DOMElement $element */
-                       foreach ($elements as $element) {
-                               DOMUtil::removeNode($element);
+                       else if ($this->outputType === 'text/simplified-html' || $this->outputType === 'text/plain') {
+                               $htmlNodeProcessor->replaceElementWithText(
+                                       $element,
+                                       WCF::getLanguage()->getDynamicVariable('wcf.bbcode.spoiler.simplified', ['label' => $element->getAttribute('data-label')]),
+                                       true
+                               );
                        }
                }
        }
index c255353badee35a000288e0bbff65e38464ded67..c844c248f8250439e56af76ac7a72ff3ca2cb9bf 100644 (file)
@@ -181,7 +181,7 @@ final class DOMUtil {
         * 
         * @param       \DOMElement     $element        start element
         * @param       string          $tagName        tag name to match
-        * @return      boolean         true if there is at least one parent with the provided tag name
+        * @return      boolean         
         */
        public static function hasParent(\DOMElement $element, $tagName) {
                while ($element = $element->parentNode) {
index abcc640084c0a41a99a874b0cb23fc3e2c959f15..3444537bfc7240d1aa11824ae151a0a65c8798ef 100644 (file)
@@ -1882,6 +1882,7 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getFormattedAllowedExt
                <item name="wcf.bbcode.spoiler.hide"><![CDATA[Spoiler ausblenden]]></item>
                <item name="wcf.bbcode.spoiler.show"><![CDATA[Spoiler anzeigen]]></item>
                <item name="wcf.bbcode.spoiler.text"><![CDATA[(Versteckter Text)]]></item>
+               <item name="wcf.bbcode.spoiler.simplified"><![CDATA[(Versteckter Text)]]></item>
        </category>
        
        <category name="wcf.captcha">
index a5a16342395b20cea78ff2ac117c34065acced2e..3676669a74c100a32a4a00d8990a08af8b7dfa15 100644 (file)
@@ -1863,6 +1863,7 @@ Allowed extensions: {', '|implode:$attachmentHandler->getFormattedAllowedExtensi
                <item name="wcf.bbcode.spoiler.hide"><![CDATA[Hide Spoiler]]></item>
                <item name="wcf.bbcode.spoiler.show"><![CDATA[Display Spoiler]]></item>
                <item name="wcf.bbcode.spoiler.text"><![CDATA[(Hidden Content)]]></item>
+               <item name="wcf.bbcode.spoiler.simplified"><![CDATA[(Hidden Content)]]></item>
        </category>
        
        <category name="wcf.captcha">