Overhauled some editor features, wysiwyg code handling
authorAlexander Ebert <ebert@woltlab.com>
Fri, 2 Jan 2015 14:32:11 +0000 (15:32 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Fri, 2 Jan 2015 14:32:11 +0000 (15:32 +0100)
14 files changed:
com.woltlab.wcf/templates/wysiwyg.tpl
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/WCF.Message.js
wcfsetup/install/files/lib/data/bbcode/BBCodeCache.class.php
wcfsetup/install/files/lib/page/AbstractPage.class.php
wcfsetup/install/files/lib/system/bbcode/BBCodeHandler.class.php
wcfsetup/install/files/lib/system/cache/builder/BBCodeCacheBuilder.class.php
wcfsetup/install/files/lib/system/request/LinkHandler.class.php
wcfsetup/install/files/style/bbcode.less
wcfsetup/install/files/style/redactor.less
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index 0395ef7e14cb84e2ef3daf8691754ab7c0addf4e..fe5537309bf9da00b26b8ea96f0bd1b131b2d75d 100644 (file)
@@ -3,6 +3,7 @@ var __REDACTOR_ICON_PATH = '{@$__wcf->getPath()}icon/';
 var __REDACTOR_BUTTONS = [ {implode from=$__wcf->getBBCodeHandler()->getButtonBBCodes() item=__bbcode}{ icon: '{$__bbcode->wysiwygIcon}', label: '{$__bbcode->buttonLabel|language}', name: '{$__bbcode->bbcodeTag}' }{/implode} ];
 var __REDACTOR_SMILIES = { {implode from=$__wcf->getSmileyCache()->getCategorySmilies() item=smiley}'{@$smiley->smileyCode|encodeJS}': '{@$smiley->getURL()|encodeJS}'{/implode} };
 var __REDACTOR_SOURCE_BBCODES = [ {implode from=$__wcf->getBBCodeHandler()->getSourceBBCodes() item=__bbcode}'{@$__bbcode->bbcodeTag}'{/implode} ];
+var __REDACTOR_CODE_HIGHLIGHTERS = { {implode from=$__wcf->getBBCodeHandler()->getHighlighters() item=__highlighter}'{@$__highlighter}': '{lang}wcf.bbcode.code.{@$__highlighter}.title{/lang}'{/implode} };
 </script>
 <script data-relocate="true">
 $(function() {
@@ -17,6 +18,17 @@ $(function() {
                'wcf.bbcode.button.superscript': '{lang}wcf.bbcode.button.superscript{/lang}',
                'wcf.bbcode.button.toggleBBCode': '{lang}wcf.bbcode.button.toggleBBCode{/lang}',
                'wcf.bbcode.button.toggleHTML': '{lang}wcf.bbcode.button.toggleHTML{/lang}',
+               'wcf.bbcode.code': '{lang}wcf.bbcode.code{/lang}',
+               'wcf.bbcode.code.edit': '{lang}wcf.bbcode.code.edit{/lang}',
+               'wcf.bbcode.code.filename': '{lang}wcf.bbcode.code.filename{/lang}',
+               'wcf.bbcode.code.filename.description': '{lang}wcf.bbcode.code.filename.description{/lang}',
+               'wcf.bbcode.code.highlighter': '{lang}wcf.bbcode.code.highlighter{/lang}',
+               'wcf.bbcode.code.highlighter.description': '{lang}wcf.bbcode.code.highlighter.description{/lang}',
+               'wcf.bbcode.code.highlighter.none': '{lang}wcf.bbcode.code.highlighter.none{/lang}',
+               'wcf.bbcode.code.insert': '{lang}wcf.bbcode.code.insert{/lang}',
+               'wcf.bbcode.code.lineNumber': '{lang}wcf.bbcode.code.lineNumber{/lang}',
+               'wcf.bbcode.code.lineNumber.description': '{lang}wcf.bbcode.code.lineNumber.description{/lang}',
+               'wcf.bbcode.code.settings': '{lang}wcf.bbcode.code.settings{/lang}',
                'wcf.bbcode.quote.edit': '{lang}wcf.bbcode.quote.edit{/lang}',
                'wcf.bbcode.quote.edit.author': '{lang}wcf.bbcode.quote.edit.author{/lang}',
                'wcf.bbcode.quote.edit.link': '{lang}wcf.bbcode.quote.edit.link{/lang}',
index aefb3fe47fdfdd0c1a6b13962a70488e73d268a8..dc8f36c635cc158ca5b152d0e9d6f7e0684521d8 100644 (file)
@@ -74,6 +74,13 @@ RedactorPlugins.wbbcode = function() {
                                this.wbbcode._handleInsertQuote();
                        }, this));
                        
+                       // handle 'insert code' button
+                       WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'insertBBCode_code_' + $identifier, $.proxy(function(data) {
+                               data.cancel = true;
+                               
+                               this.wbbcode._handleInsertCode(null, true);
+                       }, this));
+                       
                        // handle keydown
                        WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'keydown_' + $identifier, $.proxy(this.wbbcode._keydownCallback, this));
                        WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'keyup_' + $identifier, $.proxy(this.wbbcode._keyupCallback, this));
@@ -106,9 +113,10 @@ RedactorPlugins.wbbcode = function() {
                                        this.$textarea.val(this.wbbcode.convertToHtml(this.$textarea.val()));
                                        this.code.offset = this.$textarea.val().length;
                                        this.code.showVisual();
-                                       this.wbbcode._fixQuotes();
+                                       this.wbbcode.fixBlockLevelElements();
                                        this.wutil.selectionEndOfEditor();
-                                       this.wbbcode._observeQuotes();
+                                       this.wbbcode.observeQuotes();
+                                       this.wbbcode.observeCodeListings();
                                        
                                        this.button.get('html').children('i').removeClass('fa-square').addClass('fa-square-o');
                                        $tooltip.text(WCF.Language.get('wcf.bbcode.button.toggleBBCode'));
@@ -220,11 +228,25 @@ RedactorPlugins.wbbcode = function() {
                        html = html.replace(/&mdash;/gi, '\u2014');
                        html = html.replace(/&dash;/gi, '\u2010');
                        
-                       // preserve newlines in <pre> tags
-                       var $cachedPreTags = { };
-                       html = html.replace(/<pre>[\s\S]+?<\/pre>/g, function(match) {
+                       // preserve code listings
+                       var $cachedCodeListings = { };
+                       html = html.replace(/<div class="codeBox[^"]+"(.*?)>\n*<div>[\s\S]+?<ol start="(\d+)">([\s\S]+?)<\/ol>\n*<\/div>\n*<\/div>/g, function(match, codeBoxAttributes, lineNumber, codeContent) {
+                               var $highlighter = '';
+                               var $filename = '';
+                               if (codeBoxAttributes.match(/data-highlighter="([a-zA-Z]+)"/)) {
+                                       $highlighter = RegExp.$1;
+                               }
+                               if (codeBoxAttributes.match(/data-filename="([^"]+)"/)) {
+                                       $filename = $.trim(RegExp.$1);
+                               }
+                               
                                var $uuid = WCF.getUUID();
-                               $cachedPreTags[$uuid] = match;
+                               $cachedCodeListings[$uuid] = {
+                                       codeContent: codeContent.replace(/<li>/g, '').replace(/<\/li>/g, '\n'),
+                                       filename: $filename,
+                                       highlighter: ($highlighter === 'plain' ? '' : $highlighter),
+                                       lineNumber: (lineNumber > 1 ? lineNumber : 0)
+                               };
                                
                                return '@@@' + $uuid + '@@@';
                        });
@@ -232,13 +254,6 @@ RedactorPlugins.wbbcode = function() {
                        // drop all new lines
                        html = html.replace(/\r?\n/g, '');
                        
-                       // restore <pre> tags
-                       if ($.getLength($cachedPreTags)) {
-                               $.each($cachedPreTags, function(key, value) {
-                                       html = html.replace('@@@' + key + '@@@', value);
-                               });
-                       }
-                       
                        // remove empty links
                        html = html.replace(/<a[^>]*?><\/a>/g, '');
                        
@@ -589,6 +604,57 @@ RedactorPlugins.wbbcode = function() {
                                }
                        }
                        
+                       // restore code listings
+                       if ($.getLength($cachedCodeListings)) {
+                               $.each($cachedCodeListings, function(uuid, listing) {
+                                       var $count = 0;
+                                       if (listing.highlighter) $count++;
+                                       if (listing.lineNumber) $count++;
+                                       if (listing.filename) $count++;
+                                       
+                                       var $attributes = '';
+                                       switch ($count) {
+                                               case 1:
+                                                       if (listing.highlighter) {
+                                                               $attributes = listing.highlighter;
+                                                       }
+                                                       else if (listing.filename) {
+                                                               $attributes = "'" + listing.filename + "'";
+                                                       }
+                                                       else {
+                                                               $attributes = listing.lineNumber;
+                                                       }
+                                               break;
+                                               
+                                               case 2:
+                                                       if (listing.highlighter) {
+                                                               $attributes = listing.highlighter;
+                                                       }
+                                                       
+                                                       if (listing.lineNumber) {
+                                                               if ($attributes.length) $attributes += ',';
+                                                               
+                                                               $attributes += listing.lineNumber;
+                                                       }
+                                                       
+                                                       if (listing.filename) {
+                                                               if ($attributes.length) $attributes += ',';
+                                                               
+                                                               $attributes += "'" + listing.filename + "'";
+                                                       }
+                                               break;
+                                               
+                                               case 3:
+                                                       $attributes = listing.highlighter + ',' + listing.lineNumber + ",'" + listing.filename + "'";
+                                               break;
+                                       }
+                                       
+                                       var $bbcode = '[code' + ($attributes.length ? '=' + $attributes : '') + ']' + listing.codeContent + '[/code]\n';
+                                       
+                                       html = html.replace(new RegExp('@@@' + uuid + '@@@', 'g'), $bbcode);
+                               });
+                       }
+                       
                        // Restore <, > and &
                        html = html.replace(/&lt;/g, '<');
                        html = html.replace(/&gt;/g, '>');
@@ -598,35 +664,8 @@ RedactorPlugins.wbbcode = function() {
                        html = html.replace(/%28/g, '(');
                        html = html.replace(/%29/g, ')');
                        
-                       // Restore %20
-                       //html = html.replace(/%20/g, ' ');
-                       
-                       // cache source code tags to preserve leading tabs
-                       /*var $cachedCodes = { };
-                       for (var $i = 0, $length = __REDACTOR_SOURCE_BBCODES.length; $i < $length; $i++) {
-                               var $bbcode = __REDACTOR_SOURCE_BBCODES[$i];
-                               
-                               var $regExp = new RegExp('\\[' + $bbcode + '([\\S\\s]+?)\\[\\/' + $bbcode + '\\]', 'gi');
-                               html = html.replace($regExp, function(match) {
-                                       var $key = match.hashCode();
-                                       $cachedCodes[$key] = match.replace(/\$/g, '$$$$');
-                                       return '@@' + $key + '@@';
-                               });
-                       }*/
-                       
-                       // ensure that [/code] is always followed by at least one empty line
-                       html = html.replace(/\[\/code\]\n\n?/g, '[/code]\n\n');
-                       
                        WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'afterConvertFromHtml', { html: html });
                        
-                       // insert codes
-                       /*if ($.getLength($cachedCodes)) {
-                               for (var $key in $cachedCodes) {
-                                       var $regex = new RegExp('@@' + $key + '@@', 'g');
-                                       html = html.replace($regex, $cachedCodes[$key]);
-                               }
-                       }*/
-                       
                        // remove all leading and trailing whitespaces, but add one empty line at the end
                        html = $.trim(html);
                        if (html.length) {
@@ -1083,16 +1122,87 @@ RedactorPlugins.wbbcode = function() {
                                        // [tt]
                                        //$value = $value.replace(/^\[tt\](.*)\[\/tt\]/, '<span class="inlineCode">$1</span>');
                                        
-                                       // preserve leading whitespaces in [code] tags
-                                       $value = $value.replace(/^\[code[^\]]*\][\S\s]*\[\/code\]$/, '<pre>$&</pre>');
+                                       // [code]
+                                       $value = $value.replace(/^\[code([^\]]*)\]([\S\s]*)\[\/code\]$/, function(matches, parameters, content) {
+                                               var $highlighter = 'plain';
+                                               var $lineNumber = 0;
+                                               var $filename = '';
+                                               
+                                               if (parameters) {
+                                                       parameters = parameters.substring(1);
+                                                       parameters = parameters.split(',');
+                                                       
+                                                       var $isNumber = function(string) { return string.match(/^\d+$/); };
+                                                       var $isFilename = function(string) { return (string.indexOf('.') !== -1); };
+                                                       var $isHighlighter = function(string) { return  (__REDACTOR_CODE_HIGHLIGHTERS[parameters[0]] !== undefined); };
+                                                       
+                                                       var $unquoteFilename = function(filename) {
+                                                               return filename.replace(/^(["'])(.*)\1$/, '$2');
+                                                       };
+                                                       
+                                                       switch (parameters.length) {
+                                                               case 1:
+                                                                       if ($isNumber(parameters[0])) {
+                                                                               $lineNumber = (parseInt(parameters[0]) > 1) ? parameters[0] : 0;
+                                                                       }
+                                                                       else if ($isFilename(parameters[0])) {
+                                                                               $filename = $unquoteFilename(parameters[0]);
+                                                                       }
+                                                                       else if ($isHighlighter(parameters[0])) {
+                                                                               $highlighter = parameters[0];
+                                                                       }
+                                                               break;
+                                                               
+                                                               case 2:
+                                                                       if ($isNumber(parameters[0])) {
+                                                                               $lineNumber = (parseInt(parameters[0]) > 1) ? parameters[0] : 0;
+                                                                               
+                                                                               if ($isFilename(parameters[1])) {
+                                                                                       $filename = $unquoteFilename(parameters[1]);
+                                                                               }
+                                                                               else if ($isHighlighter(parameters[1])) {
+                                                                                       $highlighter = parameters[1];
+                                                                               }
+                                                                       }
+                                                                       else {
+                                                                               if ($isHighlighter(parameters[0])) $highlighter = parameters[0];
+                                                                               if ($isFilename(parameters[1])) $filename = $unquoteFilename(parameters[1]);
+                                                                       }
+                                                               break;
+                                                               
+                                                               case 3:
+                                                                       if ($isHighlighter(parameters[0])) $highlighter = parameters[0];
+                                                                       if ($isNumber(parameters[1])) $lineNumber = parameters[1];
+                                                                       if ($isFilename(parameters[2])) $filename = $unquoteFilename(parameters[2]);
+                                                               break;
+                                                       }
+                                               }
+                                               
+                                               content = content.replace(/^\n+/, '').replace(/\n+$/, '').split(/\n/);
+                                               var $lines = '';
+                                               for (var $i = 0; $i < content.length; $i++) {
+                                                       $lines += '<li>' + content[$i] + '</li>';
+                                               }
+                                               
+                                               return '<div class="codeBox container" contenteditable="false" data-highlighter="' + $highlighter + '"' + ($filename ? ' data-filename="' + WCF.String.escapeHTML($filename) + '"' : '' ) + '>'
+                                                       + '<div>'
+                                                               + '<div>'
+                                                                       + '<h3>' + __REDACTOR_CODE_HIGHLIGHTERS[$highlighter] + ($filename ? ': ' + WCF.String.escapeHTML($filename) : '') + '</h3>'
+                                                               + '</div>'
+                                                               + '<ol start="' + ($lineNumber > 1 ? $lineNumber : 1) + '">'
+                                                                       + $lines
+                                                               + '</ol>'
+                                                       + '</div>'
+                                               + '</div>';
+                                       });
                                        
                                        data = data.replace($regex, $value);
                                }
                        }
                        
-                       // remove <p> wrapping a quote
-                       data = data.replace(/<p><blockquote/g, '<blockquote');
-                       data = data.replace(/<\/blockquote><\/p>/g, '</blockquote>');
+                       // remove <p> wrapping a quote or a div
+                       data = data.replace(/<p><(blockquote|div)/g, '<$1');
+                       data = data.replace(/<\/(blockquote|div)><\/p>/g, '</$1>');
                        
                        WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'afterConvertToHtml', { data: data });
                        
@@ -1114,7 +1224,7 @@ RedactorPlugins.wbbcode = function() {
                                5: 12,
                                6: 10
                        };
-                       
+                       console.debug(html);
                        // replace <h1> ... </h6> tags
                        html = html.replace(/<h([1-6])[^>]+>/g, function(match, level) {
                                return '[size=' + $levels[level] + ']';
@@ -1142,6 +1252,7 @@ RedactorPlugins.wbbcode = function() {
                 * @return      string
                 */
                _pasteCallback: function(html) {
+                       console.debug(html);
                        // reduce successive <br> by one
                        //html = html.replace(/<br[^>]*>(<br[^>]*>)+/g, '$1');
                        
@@ -1446,7 +1557,7 @@ RedactorPlugins.wbbcode = function() {
                /**
                 * Initializes source editing for quotes.
                 */
-               _observeQuotes: function() {
+               observeQuotes: function() {
                        var $editHeader = this.$editor.find('.redactorQuoteEdit:not(.jsRedactorQuoteEdit)');
                        if ($editHeader.length) {
                                $editHeader.each((function(index, editHeader) {
@@ -1485,6 +1596,19 @@ RedactorPlugins.wbbcode = function() {
                        this.selection.remove();
                },
                
+               /**
+                * Initializes editing for code listings.
+                */
+               observeCodeListings: function() {
+                       this.$editor.find('.codeBox:not(.jsRedactorCodeBox)').each((function(index, codeBox) {
+                               var $codeBox = $(codeBox).addClass('jsRedactorCodeBox');
+                               var $editBox = $('<div class="redactorEditCodeBox"><div>' + WCF.Language.get('wcf.bbcode.code.edit') + '</div></div>').insertAfter($codeBox.find('> div > div > h3'));
+                               $editBox.click((function() {
+                                       this.wbbcode._handleInsertCode($codeBox, false);
+                               }).bind(this));
+                       }).bind(this));
+               },
+               
                /**
                 * Opens the quote source edit dialog.
                 * 
@@ -1512,7 +1636,7 @@ RedactorPlugins.wbbcode = function() {
                                        if ($quote !== null) {
                                                // set caret inside the quote
                                                if (!$html.length) {
-                                                       this.caret.setStart($quote.find('> div > div')[0]);
+                                                       this.caret.setStart($quote.find('> div')[0]);
                                                }
                                        }
                                        
@@ -1591,7 +1715,7 @@ RedactorPlugins.wbbcode = function() {
                                $quote = this.$editor.find('#' + $id);
                                if ($quote.length) {
                                        // quote may be empty if $innerHTML was empty, fix it
-                                       var $inner = $quote.find('> div > div');
+                                       var $inner = $quote.find('> div');
                                        if ($inner.length == 1) {
                                                if ($inner[0].innerHTML === '') {
                                                        $inner[0].innerHTML = this.opts.invisibleSpace;
@@ -1610,8 +1734,8 @@ RedactorPlugins.wbbcode = function() {
                                        this.wutil.setCaretAfter($quote[0]);
                                }
                                
-                               this.wbbcode._observeQuotes();
-                               this.wbbcode._fixQuotes();
+                               this.wbbcode.observeQuotes();
+                               this.wbbcode.fixBlockLevelElements();
                                
                                this.$toolbar.find('a.re-__wcf_quote').removeClass('redactor-button-disabled');
                        }
@@ -1658,13 +1782,116 @@ RedactorPlugins.wbbcode = function() {
                },
                
                /**
-                * Ensures that there is a paragraph in front of each quotes because you cannot click in between two of them.
+                * Opens the code edit dialog.
+                * 
+                * @param       jQuery          codeBox
+                * @param       boolean         isInsert
+                */
+               _handleInsertCode: function(codeBox, isInsert) {
+                       this.modal.load('code', WCF.Language.get('wcf.bbcode.code.' + (isInsert ? 'insert' : 'edit')), 400);
+                       
+                       var $button = this.modal.createActionButton(this.lang.get('save'));
+                       
+                       if (isInsert) {
+                               this.selection.save();
+                               this.modal.show();
+                               
+                               $('#redactorCodeBox').focus();
+                               
+                               $button.click($.proxy(function() {
+                                       var $codeBox = $('#redactorCodeBox');
+                                       var $filename = $('#redactorCodeFilename');
+                                       var $highlighter = $('#redactorCodeHighlighter');
+                                       var $lineNumber = $('#redactorCodeLineNumber');
+                                       
+                                       var $codeFilename = $.trim($filename.val());
+                                       var $bbcode = '[code=' + $highlighter.val() + ',' + $lineNumber.val() + ($codeFilename.length ? ",'" + $codeFilename + "'" : '') + ']';
+                                       $bbcode += $codeBox.val().replace(/^\n+/, '').replace(/\n+$/, '').replace(/^$/, '\n');
+                                       $bbcode += '[/code]';
+                                       
+                                       this.wutil.adjustSelectionForBlockElement();
+                                       this.wutil.saveSelection();
+                                       var $html = this.wbbcode.convertToHtml($bbcode);
+                                       this.insert.html($html, false);
+                                       
+                                       // set caret after code listing
+                                       var $codeBox = this.$editor.find('.codeBox:not(.jsRedactorCodeBox)');
+                                       
+                                       this.wbbcode.observeCodeListings();
+                                       this.wbbcode.fixBlockLevelElements();
+                                       
+                                       // document.execCommand('insertHTML') seems to drop 'contenteditable="false"' for root element
+                                       $codeBox.attr('contenteditable', 'false');
+                                       this.caret.setAfter($codeBox);
+                                       
+                                       this.modal.close();
+                               }, this));
+                       }
+                       else {
+                               this.modal.show();
+                               
+                               var $codeBox = $('#redactorCodeBox').focus();
+                               var $filename = $('#redactorCodeFilename');
+                               var $highlighter = $('#redactorCodeHighlighter');
+                               var $lineNumber = $('#redactorCodeLineNumber');
+                               
+                               $highlighter.val(codeBox.data('highlighter'));
+                               $filename.val(codeBox.data('filename') || '');
+                               var $list = codeBox.find('> div > ol');
+                               $lineNumber.val(parseInt($list.prop('start')));
+                               
+                               var $code = '';
+                               $list.children('li').each(function(index, listItem) {
+                                       $code += $(listItem).text() + "\n";
+                               });
+                               $codeBox.val($code.replace(/^\n+/, '').replace(/\n+$/, ''));
+                               
+                               $button.click($.proxy(function() {
+                                       var $selectedHighlighter = $highlighter.val();
+                                       codeBox.data('highlighter', $selectedHighlighter);
+                                       codeBox.attr('data-highlighter', $selectedHighlighter);
+                                       
+                                       var $headline = __REDACTOR_CODE_HIGHLIGHTERS[$selectedHighlighter];
+                                       var $codeFilename = $.trim($filename.val());
+                                       if ($codeFilename) {
+                                               $headline += ': ' + WCF.String.escapeHTML($codeFilename);
+                                               codeBox.data('filename', $codeFilename);
+                                               codeBox.attr('data-filename', $codeFilename);
+                                       }
+                                       else {
+                                               codeBox.removeAttr('data-filename');
+                                               codeBox.removeData('filename');
+                                       }
+                                       
+                                       codeBox.data('highlighter', $highlighter.val());
+                                       codeBox.find('> div > div > h3').html($headline);
+                                       
+                                       var $list = codeBox.find('> div > ol').empty();
+                                       var $start = parseInt($lineNumber.val());
+                                       $list.prop('start', ($start > 1 ? $start : 1));
+                                       
+                                       var $code = $codeBox.val().replace(/^\n+/, '').replace(/\n+$/, '').replace(/^$/, '\n');
+                                       $code = $code.split('\n');
+                                       console.debug($code);
+                                       var $codeContent = '';
+                                       for (var $i = 0; $i < $code.length; $i++) {
+                                               $codeContent += '<li>' + $code[$i] + '</li>';
+                                       }
+                                       $list.append($($codeContent));
+                                       
+                                       this.modal.close();
+                               }, this));
+                       }
+               },
+               
+               /**
+                * Ensures that there is a paragraph in front of each block-level element because you cannot click in between two of them.
                 */
-               _fixQuotes: function() {
+               fixBlockLevelElements: function() {
                        var $addSpacing = (function(referenceElement, target) {
                                var $tagName = 'P';
                                
-                               // fix reference element if blockquote is within a quote (wrapped by <div>...</div>)
+                               // fix reference element if a block element is within a quote (wrapped by <div>...</div>)
                                if (referenceElement.parentElement.tagName === 'DIV' && referenceElement.parentElement !== this.$editor[0]) {
                                        referenceElement = referenceElement.parentElement;
                                        $tagName = 'DIV';
@@ -1675,16 +1902,16 @@ RedactorPlugins.wbbcode = function() {
                                        $('<' + $tagName + '>' + this.opts.invisibleSpace + '</' + $tagName + '>')[(target === 'previousElementSibling' ? 'insertBefore' : 'insertAfter')](referenceElement);
                                }
                                else if (referenceElement.previousElementSibling.tagName === $tagName) {
-                                       // previous/next element is empty or contains an empty <p></p> (blockquote is a direct children of the editor)
+                                       // previous/next element is empty or contains an empty <p></p> (block element is a direct children of the editor)
                                        if (!referenceElement[target].innerHTML.length || referenceElement[target].innerHTML.toLowerCase() === '<p></p>') {
                                                $(referenceElement[target]).html(this.opts.invisibleSpace);
                                        }
                                }
                        }).bind(this);
                        
-                       this.$editor.find('blockquote').each((function(index, blockquote) {
-                               $addSpacing(blockquote, 'previousElementSibling');
-                               $addSpacing(blockquote, 'nextElementSibling');
+                       this.$editor.find('blockquote, .codeBox').each((function(index, blockElement) {
+                               $addSpacing(blockElement, 'previousElementSibling');
+                               $addSpacing(blockElement, 'nextElementSibling');
                        }).bind(this));
                }
        };
index 4e95f3e53eb221ad6db29bb40c8f68c563e5a28c..a4ae2d01271ca8aaea086434e3f7ff31147aefa3 100644 (file)
@@ -887,6 +887,50 @@ RedactorPlugins.wmonkeypatch = function() {
                                        + '</dl>'
                                + '</fieldset>';
                        
+                       // template: code
+                       var $highlighters = '';
+                       $.each(__REDACTOR_CODE_HIGHLIGHTERS, function(k, v) {
+                               if (k === 'plain') return true;
+                               
+                               $highlighters += '<option value="' + k + '">' + v + '</option>';
+                       });
+                       
+                       this.opts.modal.code =
+                               '<fieldset>'
+                                       + '<legend>' + WCF.Language.get('wcf.bbcode.code.settings') + '</legend>'
+                                       + '<dl>'
+                                               + '<dt><label for="redactorCodeHighlighter">' + WCF.Language.get('wcf.bbcode.code.highlighter') + '</label></dt>'
+                                               + '<dd>'
+                                                       + '<select id="redactorCodeHighlighter">'
+                                                               + '<option value="">' + WCF.Language.get('wcf.bbcode.code.highlighter.none') + '</option>'
+                                                               + $highlighters
+                                                       + '</select>'
+                                                       + '<small>' + WCF.Language.get('wcf.bbcode.code.highlighter.description') + '</small>'
+                                               + '</dd>'
+                                       + '</dl>'
+                                       + '<dl>'
+                                               + '<dt><label for="redactorCodeLineNumber">' + WCF.Language.get('wcf.bbcode.code.lineNumber') + '</label></dt>'
+                                               + '<dd>'
+                                                       + '<input type="number" id="redactorCodeLineNumber" min="1" max="99999" value="1" />'
+                                                       + '<small>' + WCF.Language.get('wcf.bbcode.code.lineNumber.description') + '</small>'
+                                               + '</dd>'
+                                       + '</dl>'
+                                       + '<dl>'
+                                               + '<dt><label for="redactorCodeFilename">' + WCF.Language.get('wcf.bbcode.code.filename') + '</label></dt>'
+                                               + '<dd>'
+                                                       + '<input type="text" id="redactorCodeFilename" value="" />'
+                                                       + '<small>' + WCF.Language.get('wcf.bbcode.code.filename.description') + '</small>'
+                                               + '</dd>'
+                                       + '</dl>'
+                               + '</fieldset>'
+                               + '<fieldset>'
+                                       + '<legend>' + WCF.Language.get('wcf.bbcode.code') + '</legend>'
+                                       + '<dl class="wide">'
+                                               + '<dt></dt>'
+                                               + '<dd><textarea id="redactorCodeBox" class="long" rows="12" /></dd>'
+                                       + '</dl>'
+                               + '</fieldset>';
+                       
                        // template: table
                        this.opts.modal.table =
                                '<fieldset id="redactor-modal-table-insert">'
index 8d564b8ce6e5bffcb33104c040010cbf9b152ae0..ae9d010baf432d864bc42a230ad41cfabc7f4cb2 100644 (file)
@@ -608,7 +608,7 @@ RedactorPlugins.wutil = function() {
                
                /**
                 * Inserting block-level elements into other blocks or inline elements can mess up the entire DOM,
-                * this method tries to find the best insert location nearby.
+                * this method tries to find the best nearby insert location.
                 */
                adjustSelectionForBlockElement: function() {
                        if (document.activeElement !== this.$editor[0]) {
@@ -619,15 +619,20 @@ RedactorPlugins.wutil = function() {
                                var $startContainer = getSelection().getRangeAt(0).startContainer;
                                if ($startContainer.nodeType === Node.TEXT_NODE && $startContainer.textContent === '\u200b' && $startContainer.parentElement.tagName === 'P' && $startContainer.parentElement.parentElement === this.$editor[0]) {
                                        // caret position is fine
-                                       
                                        return;
                                }
                                else {
                                        // walk tree up until we find a direct children of the editor and place the caret afterwards
                                        var $insertAfter = $($startContainer).parentsUntil(this.$editor[0]).last();
-                                       var $p = $('<p><br></p>').insertAfter($insertAfter);
-                                       
-                                       this.caret.setEnd($p);
+                                       if ($insertAfter[0] === document.body.parentElement) {
+                                               // work-around if selection never has been within the editor before
+                                               this.wutil.selectionEndOfEditor();
+                                       }
+                                       else {
+                                               var $p = $('<p><br></p>').insertAfter($insertAfter);
+                                               
+                                               this.caret.setEnd($p);
+                                       }
                                }
                        }
                },
index 8454828b308e2080e7a2123022a47b000bec80b9..a1f8ed8e41b3f3e88716983e19691fcc7fb7f363 100644 (file)
@@ -930,7 +930,7 @@ WCF.Message.QuickReply = Class.extend({
                        this._messageField.redactor('focus.setEnd');
                        this._messageField.redactor('wutil.insertDynamic', $html, data.returnValues.template);
                        this._messageField.redactor('wutil.selectionEndOfEditor');
-                       this._messageField.redactor('wbbcode._observeQuotes');
+                       this._messageField.redactor('wbbcode.observeQuotes');
                }
                else {
                        this._messageField.val(data.returnValues.template);
index 84a2601670b83f4e3e0d6a4994e424fb16916615..5f83ba4c3e0a6d008825716a84e273e31510bff6 100644 (file)
@@ -20,12 +20,18 @@ class BBCodeCache extends SingletonFactory {
         */
        protected $cachedBBCodes = array();
        
+       /**
+        * list of known highlighters
+        * @var array<string>
+        */
+       protected $highlighters = array();
+       
        /**
         * @see \wcf\system\SingletonFactory::init()
         */
        protected function init() {
                // get bbcode cache
-               $this->cachedBBCodes = BBCodeCacheBuilder::getInstance()->getData();
+               $this->cachedBBCodes = BBCodeCacheBuilder::getInstance()->getData(array(), 'bbcodes');
        }
        
        /**
@@ -60,4 +66,17 @@ class BBCodeCache extends SingletonFactory {
        public function getBBCodeAttributes($tag) {
                return $this->cachedBBCodes[$tag]->getAttributes();
        }
+       
+       /**
+        * Returns a list of known highlighters.
+        * 
+        * @return      array<string>
+        */
+       public function getHighlighters() {
+               if (empty($this->highlighters)) {
+                       $this->highlighters = BBCodeCacheBuilder::getInstance()->getData(array(), 'highlighters');
+               }
+               
+               return $this->highlighters;
+       }
 }
index 2c0be063d23746f790334753432673d3807e279d..828f05a6bee8c5bdb51c32db3ab1502cab8c3818 100644 (file)
@@ -187,7 +187,11 @@ abstract class AbstractPage implements IPage, ITrackablePage {
                        // use $_SERVER['REQUEST_URI'] because it represents the URL used to access the site and not the internally rewritten one
                        $requestURI = preg_replace('~[?&]s=[a-f0-9]{40}~', '', $_SERVER['REQUEST_URI']);
                        if (strpos($requestURI, '%') !== false) {
-                               $requestURI = urldecode($requestURI);
+                               // DEBUG ONLY
+                               if ($_SERVER['REMOTE_ADDR'] != '5.28.86.103') {
+                                       $requestURI = urldecode($requestURI);
+                               }
+                               // /DEBUG ONLY
                        }
                        if (!StringUtil::isUTF8($requestURI)) {
                                $requestURI = StringUtil::convertEncoding('ISO-8859-1', 'UTF-8', $requestURI);
@@ -217,6 +221,15 @@ abstract class AbstractPage implements IPage, ITrackablePage {
                        }
                        
                        if ($redirect) {
+                               if ($_SERVER['REMOTE_ADDR'] == '5.28.86.103') {
+                                       header('Content-Type: text/html; charset=utf-8');
+                                       echo "<pre>";
+                                       echo "<h3>Redirect</h3>";
+                                       echo "Canonical: " . $this->canonicalURL . "\n";
+                                       echo "RequestURL: " . $requestURI . "\n";
+                                       exit;
+                               }
+                               
                                $redirectURL = $this->canonicalURL;
                                if (!empty($requestURL['query'])) {
                                        $queryString = $requestURL['query'];
index f84142cd7adf9cb3c5f8cb06ec1e56f9b39297ab..9f251a7dc079325157bc9248b0c14dbe1325da22 100644 (file)
@@ -116,4 +116,13 @@ class BBCodeHandler extends SingletonFactory {
                
                return $this->sourceBBCodes;
        }
+       
+       /**
+        * Returns a list of known highlighters.
+        * 
+        * @return      array<string>
+        */
+       public function getHighlighters() {
+               return BBCodeCache::getInstance()->getHighlighters();
+       }
 }
index f4ccbaac53ccbd67eb2838a66fabc7da11456fb4..739e55e3d2c5a3f609bcd0e291f3df54ccf5855d 100644 (file)
@@ -19,7 +19,8 @@ class BBCodeCacheBuilder extends AbstractCacheBuilder {
         * @see \wcf\system\cache\builder\AbstractCacheBuilder::rebuild()
         */
        protected function rebuild(array $parameters) {
-               $data = $attributes = array();
+               $attributes = array();
+               $data = array('bbcodes' => array(), 'highlighters' => array());
                
                // get attributes
                $sql = "SELECT          attribute.*, bbcode.bbcodeTag
@@ -44,7 +45,17 @@ class BBCodeCacheBuilder extends AbstractCacheBuilder {
                $statement->execute();
                while ($row = $statement->fetchArray()) {
                        $row['attributes'] = (isset($attributes[$row['bbcodeTag']]) ? $attributes[$row['bbcodeTag']] : array());
-                       $data[$row['bbcodeTag']] = new BBCode(null, $row);
+                       $data['bbcodes'][$row['bbcodeTag']] = new BBCode(null, $row);
+               }
+               
+               // get code highlighters
+               $highlighters = glob(WCF_DIR . 'lib/system/bbcode/highlighter/*.class.php');
+               if (is_array($highlighters)) {
+                       foreach ($highlighters as $highlighter) {
+                               if (preg_match('~\/([a-zA-Z]+)Highlighter\.class\.php$~', $highlighter, $matches)) {
+                                       $data['highlighters'][] = strtolower($matches[1]);
+                               }
+                       }
                }
                
                return $data;
index 43523329882c1b41e24931049b36bca3806b000b..ef033d4b8b76d86dc1cd4cbc7d35a36fd5ff9f6c 100644 (file)
@@ -109,6 +109,13 @@ class LinkHandler extends SingletonFactory {
                        }
                        unset($parameters['forceWCF']);
                }
+               
+               // DEBUG ONLY
+               if ($_SERVER['REMOTE_ADDR'] == '5.28.86.103') {
+                       $parameters['encodeTitle'] = true;
+               }
+               // /DEBUG ONLY
+               
                if (isset($parameters['encodeTitle'])) {
                        $encodeTitle = $parameters['encodeTitle'];
                        unset($parameters['encodeTitle']);
index 07e357458ea48cd7af6585e5fe2f627181b6ba06..c4ddc329997dade89d8e2b8c1ed04cf644624206 100644 (file)
@@ -2,6 +2,7 @@
 .codeBox {
        background-color: @wcfContentBackgroundColor;
        clear: both;
+       margin: (@wcfGapSmall + @wcfGapTiny) 0;
        
        > div {
                padding: @wcfGapMedium @wcfGapLarge @wcfGapMedium @wcfGapMedium;
index 3b035c2b47162088838fda0610057f1e11429a7b..4b778774fa98f16a7283dd7cd4908e689a1d0b36 100644 (file)
                }
        }
        
+       .codeBox {
+               overflow: hidden;
+               position: relative;
+               
+               .redactorEditCodeBox {
+                       background-color: rgba(255, 255, 255, .8);
+                       bottom: 0;
+                       left: 0;
+                       opacity: 0;
+                       position: absolute;
+                       right: 0;
+                       text-align: center;
+                       top: 0;
+                       z-index: 200;
+                       
+                       .transition(opacity, .3s, linear);
+                       
+                       > div {
+                               cursor: pointer;
+                               font-size: 1.4rem;
+                               left: 50%;
+                               padding: 1em 3em;
+                               position: absolute;
+                               top: 50%;
+                               
+                               transform: translate(-50%, -50%);
+                               -ms-transform: translate(-50%, -50%);
+                       }
+               }
+               
+               &:hover .redactorEditCodeBox {
+                       opacity: 1;
+               }
+               
+               ol {
+                       margin-bottom: 0;
+                       margin-top: 0;
+               }
+       }
+       
        &.msie .quoteBox {
                /* resets 'hasLayout' causing IE to display resize handle and wonky editing behavior */
                min-height: auto;
index 9ae24431a702567eeea26062c16e80bcf57e6352..5de18a25c46dc8bd52bf6ec35fa9104c23fc9301 100644 (file)
@@ -1760,20 +1760,31 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getFormattedAllowedExt
                <item name="wcf.bbcode.button.superscript"><![CDATA[Hochgestellt]]></item>
                <item name="wcf.bbcode.button.toggleBBCode"><![CDATA[BBCode]]></item>
                <item name="wcf.bbcode.button.toggleHTML"><![CDATA[WYSIWYG]]></item>
-               <item name="wcf.bbcode.code.text"><![CDATA[{@$highlighterTitle} ({#$lines} Zeile{if $lines != 1}n{/if})]]></item>
+               <item name="wcf.bbcode.code"><![CDATA[Quellcode]]></item>
                <item name="wcf.bbcode.code.bash.title"><![CDATA[Shell-Script]]></item>
                <item name="wcf.bbcode.code.brainfuck.title"><![CDATA[Brainfuck-Quellcode]]></item>
                <item name="wcf.bbcode.code.c.title"><![CDATA[C-Quellcode]]></item>
                <item name="wcf.bbcode.code.css.title"><![CDATA[CSS-Quellcode]]></item>
                <item name="wcf.bbcode.code.diff.title"><![CDATA[Unterschiede-Datei]]></item>
+               <item name="wcf.bbcode.code.edit"><![CDATA[Quellcode bearbeiten]]></item>
+               <item name="wcf.bbcode.code.filename"><![CDATA[Dateiname]]></item>
+               <item name="wcf.bbcode.code.filename.description"><![CDATA[Optional: Legen Sie einen anzuzeigenden Dateinamen fest.]]></item>
+               <item name="wcf.bbcode.code.highlighter"><![CDATA[Syntax-Hervorherbung]]></item>
+               <item name="wcf.bbcode.code.highlighter.description"><![CDATA[Die farbliche Hervorherbung wird im Editor nicht angezeigt.]]></item>
+               <item name="wcf.bbcode.code.highlighter.none"><![CDATA[(Keine Hervorherbung)]]></item>
                <item name="wcf.bbcode.code.html.title"><![CDATA[HTML-Quellcode]]></item>
+               <item name="wcf.bbcode.code.insert"><![CDATA[Quellcode einfügen]]></item>
                <item name="wcf.bbcode.code.java.title"><![CDATA[Java-Quellcode]]></item>
                <item name="wcf.bbcode.code.js.title"><![CDATA[JavaScript-Quellcode]]></item>
+               <item name="wcf.bbcode.code.lineNumber"><![CDATA[Start-Zeilennummer]]></item>
+               <item name="wcf.bbcode.code.lineNumber.description"><![CDATA[Optional: Legen Sie den Wert fest ab dem die Zeilennummierung startet.]]></item>
                <item name="wcf.bbcode.code.perl.title"><![CDATA[Perl-Quellcode]]></item>
                <item name="wcf.bbcode.code.php.title"><![CDATA[PHP-Quellcode]]></item>
                <item name="wcf.bbcode.code.plain.title"><![CDATA[Quellcode]]></item>
                <item name="wcf.bbcode.code.sql.title"><![CDATA[SQL-Abfrage]]></item>
+               <item name="wcf.bbcode.code.text"><![CDATA[{@$highlighterTitle} ({#$lines} Zeile{if $lines != 1}n{/if})]]></item>
                <item name="wcf.bbcode.code.python.title"><![CDATA[Python-Quellcode]]></item>
+               <item name="wcf.bbcode.code.settings"><![CDATA[Einstellungen]]></item>
                <item name="wcf.bbcode.code.tex.title"><![CDATA[TeX-Quellcode]]></item>
                <item name="wcf.bbcode.code.tpl.title"><![CDATA[Smarty-Template]]></item>
                <item name="wcf.bbcode.code.xml.title"><![CDATA[XML-Quellcode]]></item>
index 35882df7b5e3c3d6aba2de7aa11111e4440c1e17..67665c5f98273bb6765fa31f0baa05586f640731 100644 (file)
@@ -1759,21 +1759,32 @@ Allowed extensions: {', '|implode:$attachmentHandler->getFormattedAllowedExtensi
                <item name="wcf.bbcode.button.superscript"><![CDATA[Superscript]]></item>
                <item name="wcf.bbcode.button.toggleBBCode"><![CDATA[BBCode]]></item>
                <item name="wcf.bbcode.button.toggleHTML"><![CDATA[WYSIWYG]]></item>
-               <item name="wcf.bbcode.code.text"><![CDATA[{@$highlighterTitle} ({#$lines} line{if $lines != 1}s{/if})]]></item>
+               <item name="wcf.bbcode.code"><![CDATA[Source Code]]></item>
                <item name="wcf.bbcode.code.bash.title"><![CDATA[Shell-Script]]></item>
                <item name="wcf.bbcode.code.brainfuck.title"><![CDATA[Brainfuck Source Code]]></item>
                <item name="wcf.bbcode.code.c.title"><![CDATA[C Source Code]]></item>
                <item name="wcf.bbcode.code.css.title"><![CDATA[CSS Source Code]]></item>
                <item name="wcf.bbcode.code.diff.title"><![CDATA[Difference-File]]></item>
+               <item name="wcf.bbcode.code.edit"><![CDATA[Edit Source Code]]></item>
+               <item name="wcf.bbcode.code.filename"><![CDATA[Filename]]></item>
+               <item name="wcf.bbcode.code.filename.description"><![CDATA[Optional: Specify the displayed filename.]]></item>
+               <item name="wcf.bbcode.code.highlighter"><![CDATA[Syntax Highlighting]]></item>
+               <item name="wcf.bbcode.code.highlighter.description"><![CDATA[Highlighting is not shown within the editor.]]></item>
+               <item name="wcf.bbcode.code.highlighter.none"><![CDATA[(No Highlighting)]]></item>
                <item name="wcf.bbcode.code.html.title"><![CDATA[HTML Source Code]]></item>
+               <item name="wcf.bbcode.code.insert"><![CDATA[Insert Source Code]]></item>
                <item name="wcf.bbcode.code.java.title"><![CDATA[Java Source Code]]></item>
                <item name="wcf.bbcode.code.js.title"><![CDATA[JavaScript Source Code]]></item>
+               <item name="wcf.bbcode.code.lineNumber"><![CDATA[Start Line Number]]></item>
+               <item name="wcf.bbcode.code.lineNumber.description"><![CDATA[Optional: Specify the start for line enumeration.]]></item>
                <item name="wcf.bbcode.code.perl.title"><![CDATA[Perl Source Code]]></item>
                <item name="wcf.bbcode.code.php.title"><![CDATA[PHP Source Code]]></item>
                <item name="wcf.bbcode.code.plain.title"><![CDATA[Source Code]]></item>
                <item name="wcf.bbcode.code.python.title"><![CDATA[Python Source Code]]></item>
+               <item name="wcf.bbcode.code.settings"><![CDATA[Settings]]></item>
                <item name="wcf.bbcode.code.sql.title"><![CDATA[SQL-Query]]></item>
                <item name="wcf.bbcode.code.tex.title"><![CDATA[TeX Source Code]]></item>
+               <item name="wcf.bbcode.code.text"><![CDATA[{@$highlighterTitle} ({#$lines} line{if $lines != 1}s{/if})]]></item>
                <item name="wcf.bbcode.code.tpl.title"><![CDATA[Smarty-Template]]></item>
                <item name="wcf.bbcode.code.xml.title"><![CDATA[XML Source Code]]></item>
                <item name="wcf.bbcode.quote.edit"><![CDATA[Edit Quote]]></item>