From caa02b15a2f322b017ffd642a4ed818c1ded3917 Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Sun, 28 Jun 2015 01:36:44 +0200 Subject: [PATCH] Added support for more BBCodes (e.g. tables) --- .../js/3rdParty/redactor/plugins/wbbcode.js | 549 ------------------ .../files/js/WoltLab/WCF/BBCode/FromHtml.js | 100 +++- .../files/js/WoltLab/WCF/BBCode/Parser.js | 2 +- .../files/js/WoltLab/WCF/BBCode/ToHtml.js | 41 +- 4 files changed, 126 insertions(+), 566 deletions(-) diff --git a/wcfsetup/install/files/js/3rdParty/redactor/plugins/wbbcode.js b/wcfsetup/install/files/js/3rdParty/redactor/plugins/wbbcode.js index d009526bf7..003eef9003 100644 --- a/wcfsetup/install/files/js/3rdParty/redactor/plugins/wbbcode.js +++ b/wcfsetup/install/files/js/3rdParty/redactor/plugins/wbbcode.js @@ -337,16 +337,6 @@ RedactorPlugins.wbbcode = function() { return '@@@' + $uuid + '@@@'; }); - // drop all new lines - html = html.replace(/\r?\n/g, ''); - - // remove empty links - html = html.replace(/]*?><\/a>/g, ''); - - // unwrap

- html = html.replace(/

<\/p>

<\/p>/g, '

'); - // unwrap code boxes for (var $uuid in $cachedCodeListings) { html = html.replace(new RegExp('

<\/p>@@@' + $uuid + '@@@

<\/p>'), '@@@' + $uuid + '@@@'); @@ -355,50 +345,9 @@ RedactorPlugins.wbbcode = function() { // handle empty paragraphs not followed by an empty one html = html.replace(/

<\/p>

(?!
)/g, '

@@@wcf_empty_line@@@

'); - // remove
right in front of

(does not match


since it has been converted already) - html = html.replace(/<\/p>/g, '

'); - - // convert paragraphs into single lines - var $parts = html.split(/(<\/?(?:div|p)>)/); - var $tmp = ''; - var $buffer = ''; - for (var $i = 0; $i < $parts.length; $i++) { - var $part = $parts[$i]; - if ($part == '

' || $part == '

') { - continue; - } - else if ($part == '

' || $part == '
') { - $buffer = $.trim($buffer); - if ($buffer != '@@@wcf_empty_line@@@') { - $buffer += "\n"; - } - - $tmp += $buffer; - $buffer = ''; - } - else { - if ($i == 0 || $i + 1 == $parts.length) { - $tmp += $part; - } - else { - $buffer += $part; - } - } - } - - if ($buffer) { - $tmp += $buffer; - $buffer = ''; - } - - html = $tmp; - html = html.replace(/@@@wcf_empty_line@@@/g, '\n'); html = html.replace(/\n\n$/, '\n'); - // convert all
into \n - html = html.replace(/
$/, ''); - html = html.replace(/
/g, '\n'); // drop
, they are pointless because the editor already adds a newline after them html = html.replace(/
/g, ''); @@ -432,210 +381,6 @@ RedactorPlugins.wbbcode = function() { }); html = html.replace(/(?:\n*)<\/blockquote>\n?/gi, '\n[/quote]\n'); - // [email] - html = html.replace(/]*?href=(["'])mailto:(.+?)\1.*?>([\s\S]+?)<\/a>/gi, '[email=$2]$3[/email]'); - - // remove empty links - html = html.replace(/]+><\/a>/, ''); - - // [url] - html = html.replace(/]*?href=(["'])(.+?)\1.*?>([\s\S]+?)<\/a>/gi, function(match, x, url, text) { - if (url == text) return '[url]' + url + '[/url]'; - - return "[url='" + url + "']" + text + "[/url]"; - }); - - // [b] - html = html.replace(/<(?:b|strong)>/gi, function() { - if ($searchFor.indexOf('b') === -1) $searchFor.push('b'); - - return '[b]'; - }); - html = html.replace(/<\/(?:b|strong)>/gi, '[/b]'); - - // [i] - html = html.replace(/<(?:i|em)>/gi, function() { - if ($searchFor.indexOf('i') === -1) $searchFor.push('i'); - - return '[i]'; - }); - html = html.replace(/<\/(?:i|em)>/gi, '[/i]'); - - // [u] - html = html.replace(//gi, function() { - if ($searchFor.indexOf('u') === -1) $searchFor.push('u'); - - return '[u]'; - }); - html = html.replace(/<\/u>/gi, '[/u]'); - - // [sub] - html = html.replace(//gi, function() { - if ($searchFor.indexOf('sub') === -1) $searchFor.push('sub'); - - return '[sub]'; - }); - html = html.replace(/<\/sub>/gi, '[/sub]'); - - // [sup] - html = html.replace(//gi, function() { - if ($searchFor.indexOf('sup') === -1) $searchFor.push('sup'); - - return '[sup]'; - }); - html = html.replace(/<\/sup>/gi, '[/sup]'); - - // [s] - html = html.replace(/<(?:s(trike)?|del)>/gi, function() { - if ($searchFor.indexOf('s') === -1) $searchFor.push('s'); - - return '[s]'; - }); - html = html.replace(/<\/(?:s(trike)?|del)>/gi, '[/s]'); - - // handle [color], [size], [font] and [tt] - var $components = html.split(/(<\/?span[^>]*>)/); - - var $buffer = [ ]; - var $openElements = [ ]; - var $result = ''; - var $pixelToPoint = { - 11: 8, - 13: 10, - 16: 12, - 19: 14, - 24: 18, - 29: 22, - 32: 24, - 48: 36 - }; - - for (var $i = 0; $i < $components.length; $i++) { - var $value = $components[$i]; - - if ($value == '') { - var $opening = $openElements.pop(); - var $tmp = $opening.start + $buffer.pop() + $opening.end; - - if ($buffer.length) { - $buffer[$buffer.length - 1] += $tmp; - } - else { - $result += $tmp; - } - } - else { - if ($value.match(/^]*?)>/)) { - var $style = RegExp.$1; - var $start; - var $end; - - if ($style.match(/(?:^|;\s*)color: ?rgb\((\d{1,3}), ?(\d{1,3}), ?(\d{1,3})\);?/i)) { - var $r = RegExp.$1; - var $g = RegExp.$2; - var $b = RegExp.$3; - - var $hex = ("0123456789ABCDEF".charAt(($r - $r % 16) / 16) + '' + "0123456789ABCDEF".charAt($r % 16)) + '' + ("0123456789ABCDEF".charAt(($g - $g % 16) / 16) + '' + "0123456789ABCDEF".charAt($g % 16)) + '' + ("0123456789ABCDEF".charAt(($b - $b % 16) / 16) + '' + "0123456789ABCDEF".charAt($b % 16)); - $start = '[color=#' + $hex + ']'; - $end = '[/color=#' + $hex + ']'; - - if ($searchFor.indexOf('color') === -1) $searchFor.push('color'); - } - else if ($style.match(/(?:^|;\s*)color: ?([^;]+);?/i)) { - $start = '[color=' + RegExp.$1 + ']'; - $end = '[/color=' + RegExp.$1 + ']'; - - if ($searchFor.indexOf('color') === -1) $searchFor.push('color'); - } - else if ($style.match(/font-size: ?(\d+)(pt|px);?/i)) { - if (RegExp.$2 == 'pt') { - $start = '[size=' + RegExp.$1 + ']'; - $end = '[/size=' + RegExp.$1 + ']'; - - if ($searchFor.indexOf('size') === -1) $searchFor.push('size'); - } - else { - if ($pixelToPoint[RegExp.$1]) { - $start = '[size=' + $pixelToPoint[RegExp.$1] + ']'; - $end = '[/size=' + $pixelToPoint[RegExp.$1] + ']'; - - if ($searchFor.indexOf('size') === -1) $searchFor.push('size'); - } - else { - // unsupported size - $start = ''; - $end = ''; - } - } - } - else if ($style.match(/font-family: ?([^;]+);?/)) { - $start = "[font='" + RegExp.$1.replace(/'/g, '') + "']"; - $end = "[/font='" + RegExp.$1.replace(/'/g, '') + "']"; - - if ($searchFor.indexOf('font') === -1) $searchFor.push('font'); - } - else { - $start = ''; - $end = ''; - } - - $buffer[$buffer.length] = ''; - $openElements[$buffer.length] = { - start: $start, - end: $end - }; - } - else if ($value.match(/^/)) { - $buffer[$buffer.length] = ''; - $openElements[$buffer.length] = { - start: '[tt]', - end: '[/tt]' - }; - } - else { - // unrecognized span, ignore - $buffer[$buffer.length] = ''; - $openElements[$buffer.length] = { - start: '', - end: '' - }; - } - } - else { - if ($buffer.length) { - $buffer[$buffer.length - 1] += $value; - } - else { - $result += $value; - } - } - } - } - - html = $result; - - // [align] - html = html.replace(/<(div|p) style="text-align: ?(left|center|right|justify);? ?">([\s\S]*?)\n/gi, function(match, tag, alignment, content) { - if ($searchFor.indexOf('align') === -1) $searchFor.push('align'); - - return '[align=' + alignment + ']' + $.trim(content) + '[/align=' + alignment + ']\n'; - }); - - if ($searchFor.length) { - var $didReplace = true; - while ($didReplace) { - $didReplace = false; - html = html.replace(new RegExp('\\[\\/((?:' + $searchFor.join('|') + ')=[^\\]]+?)\\]\n\\[\\1\\]', 'gi'), function() { - $didReplace = true; - - return '\n'; - }); - } - - html = html.replace(new RegExp('\\[\\/(' + $searchFor.join('|') + ')=[^\\]]+?\\]', 'gi'), '[/$1]'); - } - // smileys html = html.replace(/ ?]*?alt="([^"]+?)"[^>]*?class="smiley"[^>]*?> ?/gi, ' $1 '); // firefox html = html.replace(/ ?]*?class="smiley"[^>]*?alt="([^"]+?)"[^>]*?> ?/gi, ' $1 '); // chrome, ie @@ -710,47 +455,9 @@ RedactorPlugins.wbbcode = function() { return "[img]" + source + "[/img]"; }); - // [*] - html = html.replace(/
  • /gi, '[*]'); - html = html.replace(/<\/li>/gi, '\n'); - - // [list] - html = html.replace(/
      /gi, '[list]'); - html = html.replace(/<(ol|ul style="list-style-type: decimal")>/gi, '[list=1]'); - html = html.replace(/
        /gi, '[list=$1]'); - html = html.replace(/<\/(ul|ol)>/gi, '[/list]'); - - // ensure there is a newline in front of a [list] - html = html.replace(/\n?\[list\]/g, '\n[list]'); - - // drop newline between [/list] and [*] - html = html.replace(/\[\/list\]\n\[\*\]/g, '[/list][*]'); - - // drop newline between two [/list] - html = html.replace(/\[\/list\]\n\[\/list\]/g, '[/list][/list]'); - - // [table] - html = html.replace(/]*>/gi, '[table]\n'); - html = html.replace(/<\/table>\n?/gi, '[/table]\n'); - - // remove - html = html.replace(/([\s\S]*?)<\/tbody>/, function(match, p1) { - return $.trim(p1); - }); - - // remove empty s - html = html.replace(/<\/tr>/gi, ''); - // [tr] - html = html.replace(//gi, '[tr]\n'); - html = html.replace(/<\/tr>/gi, '[/tr]\n'); - // [td]+[align] html = html.replace(/([\s\S]*?)<\/td>/gi, "[td][align=$1]$2[/align][/td]"); - // [td] - html = html.replace(/(\t)*(\t)*/gi, '[td]'); - html = html.replace(/(\t)*<\/td>/gi, '[/td]\n'); - // cache redactor's selection markers var $cachedMarkers = { }; html.replace(/<\/span>/, function(match) { @@ -856,13 +563,6 @@ RedactorPlugins.wbbcode = function() { // remove 0x200B (unicode zero width space) data = this.wutil.removeZeroWidthSpace(data); - // Convert & to its HTML entity. - data = data.replace(/&/g, '&'); - - // Convert < and > to their HTML entities. - data = data.replace(//g, '>'); - // cache source code tags var $cachedCodes = [ ]; var $regExp = new RegExp('\\[(' + __REDACTOR_SOURCE_BBCODES.join('|') + ')([\\S\\s]+?)\\[\\/\\1\\]', 'gi'); @@ -872,56 +572,10 @@ RedactorPlugins.wbbcode = function() { return '@@' + $key + '@@'; }); - // [url] - data = data.replace(/\[url\]([^"]+?)\[\/url]/gi, '$1' + this.opts.invisibleSpace); - data = data.replace(/\[url\='([^'"]+)']([\s\S]+?)\[\/url]/gi, '$2' + this.opts.invisibleSpace); - data = data.replace(/\[url\=([^'"\]]+)]([\s\S]+?)\[\/url]/gi, '$2' + this.opts.invisibleSpace); - // [email] data = data.replace(/\[email\]([^"]+?)\[\/email]/gi, '$1' + this.opts.invisibleSpace); data = data.replace(/\[email\=([^"\]]+)](.+?)\[\/email]/gi, '$2' + this.opts.invisibleSpace); - // cleanup inline formattings that could stack in a weird way - - // replaces [b][b] with [b] - data = data.replace(/\[(b|i|s|sub|sup|u)\]\[\1\]/gi, '[$1]'); - - // replaces [/b][/b] with [/b] - data = data.replace(/\[(\/(?:b|i|s|sub|sup|u))\]\[\1\]/gi, '[$1]'); - - // drops [b][/b] (we can safely remove them because empty lines will preserve their formatting due to the expand formatting function - data = data.replace(/\[(b|i|s|sub|sup|u)\]\[\/\1\]/gi, ''); - - // [b] - data = data.replace(/\[b\]([\s\S]*?)\[\/b]/gi, (function(match, content) { - return this.wbbcode._expandFormatting(content, '', ''); - }).bind(this)); - - // [i] - data = data.replace(/\[i\]([\s\S]*?)\[\/i]/gi, (function(match, content) { - return this.wbbcode._expandFormatting(content, '', ''); - }).bind(this)); - - // [u] - data = data.replace(/\[u\]([\s\S]*?)\[\/u]/gi, (function(match, content) { - return this.wbbcode._expandFormatting(content, '', ''); - }).bind(this)); - - // [s] - data = data.replace(/\[s\]([\s\S]*?)\[\/s]/gi, (function(match, content) { - return this.wbbcode._expandFormatting(content, '', ''); - }).bind(this)); - - // [sub] - data = data.replace(/\[sub\]([\s\S]*?)\[\/sub]/gi, (function(match, content) { - return this.wbbcode._expandFormatting(content, '', ''); - }).bind(this)); - - // [sup] - data = data.replace(/\[sup\]([\s\S]*?)\[\/sup]/gi, (function(match, content) { - return this.wbbcode._expandFormatting(content, '', ''); - }).bind(this)); - // [img] data = data.replace(/\[img\]([^"]+?)\[\/img\]/gi,''); data = data.replace(/\[img='?([^"]*?)'?,'?(left|right)'?\]\[\/img\]/gi, function(match, src, alignment) { @@ -948,99 +602,6 @@ RedactorPlugins.wbbcode = function() { }); data = data.replace(/\[img='?([^"]*?)'?\]\[\/img\]/gi,''); - // [size] - data = data.replace(/\[size=(\d+)\]([\s\S]*?)\[\/size\]/gi, (function(match, size, content) { - return this.wbbcode._expandFormatting(content, '', ''); - }).bind(this)); - - // [color] - data = data.replace(/\[color=([#a-z0-9]*?)\]([\s\S]*?)\[\/color\]/gi, (function(match, color, content) { - return this.wbbcode._expandFormatting(content, '', ''); - }).bind(this)); - - // [font] - data = data.replace(/\[font='?([a-z,\- ]*?)'?\]([\s\S]*?)\[\/font\]/gi, (function(match, fontFamily, content) { - return this.wbbcode._expandFormatting(content, '', ''); - }).bind(this)); - - // [align] - data = data.replace(/\[align=(left|right|center|justify)\]([\s\S]*?)\[\/align\]/gi, (function(match, alignment, content) { - return this.wbbcode._expandFormatting(content, '

        ', '

        '); - }).bind(this)); - - // search for [*] not preceeded by [list by searching for the first occurence of [list and then check the left - var $firstList = data.indexOf('[list'); - if ($firstList > 0) { - var $tmp = data.substr(0, $firstList); - $tmp = $tmp.replace(/\[\*\]/g, ''); - data = $tmp + data.substr($firstList); - } - - // search for [*] not followed by [/list] - var $lastList = data.lastIndexOf('[/list]'); - if ($lastList === -1) { - // drop all [list*] and [*] - data = data.replace(/\[\*\]/g, ''); - data = data.replace(/\[list[^\]]*\]/g, ''); - } - else { - var $tmp = data.substr($lastList + 7); - $tmp = $tmp.replace(/\[\*\]/g, ''); - data = data.substr(0, $lastList + 7) + $tmp; - } - - // [*] - data = data.replace(/\[\*\]([\s\S]*?)(?=\[\*\]|\[\/list\])/gi, function(match, content) { - return '
      • ' + $.trim(content) + '
      • '; - }); - - // fix superflous newlines with nested lists - data = data.replace(/\n*(\[list\]<\/li>)/g, '$1'); - - // [list] - data = data.replace(/\[list\]/gi, '
          '); - data = data.replace(/\[list=1\]/gi, '
            '); - data = data.replace(/\[list=a\]/gi, '
              '); - data = data.replace(/\[list=(none|circle|square|disc|decimal|lower-roman|upper-roman|decimal-leading-zero|lower-greek|lower-latin|upper-latin|armenian|georgian)\]/gi, '
                '); - data = data.replace(/\[\/list\]/gi, '
              '); - - // trim whitespaces within [table] - data = data.replace(/\[table\]([\S\s]*?)\[\/table\]/gi, function(match, p1) { - return '[table]' + $.trim(p1) + '[/table]'; - }); - - // [table] - data = data.replace(/\[table\]\n*/gi, ''); - data = data.replace(/\[\/table\](\n*)?/gi, function(match, newlines) { - if (newlines) { - // tables cause an additional newline if there already was a newline afterwards - if (newlines.match(/\n/g).length > 2) { - newlines = newlines.replace(/^\n/, ''); - } - - return '
              ' + newlines; - } - - return ''; - }); - // [tr] - data = data.replace(/\[tr\]\n*/gi, ''); - data = data.replace(/\[\/tr\]\n*/gi, ''); - // [td] - data = data.replace(/\[td\]\n*/gi, ''); - data = data.replace(/\[\/td\]\n*/gi, ''); - - // trim whitespaces within - data = data.replace(/([\S\s]*?)<\/td>/gi, function(match, p1) { - var $tdContent = $.trim(p1); - if (!$tdContent.length) { - // unicode zero-width space - $tdContent = '​'; - } - - return '' + $tdContent + ''; - }); - // attachments var $attachmentUrl = this.wutil.getOption('woltlab.attachmentUrl'); var $attachmentThumbnailUrl = this.wutil.getOption('woltlab.attachmentThumbnailUrl'); @@ -1116,9 +677,6 @@ RedactorPlugins.wbbcode = function() { // remove "javascript:" data = data.replace(/(javascript):/gi, '$1:'); - // unify line breaks - data = data.replace(/(\r|\r\n)/g, "\n"); - // extract [quote] bbcodes to prevent line break handling below var $cachedQuotes = [ ]; var $knownQuotes = [ ]; @@ -1179,71 +737,6 @@ RedactorPlugins.wbbcode = function() { // drop trailing line breaks data = data.replace(/\n*$/, ''); - // line-breaks within list items must be a
              instead of

              - var $listItems = [ ]; - data = data.replace(/(
            • [\s\S]*?<\/li>)/g, function(match) { - match = $.trim(match).replace(/\n/, '
              '); - - var $key = WCF.getUUID(); - $listItems.push({ - key: $key, - content: match - }); - - return $key; - }); - - // convert line breaks into

              or empty lines to


              - var $tmp = data.split("\n"); - - data = ''; - for (var $i = 0, $length = $tmp.length; $i < $length; $i++) { - var $line = $.trim($tmp[$i]); - - if ($line.match(/^<([a-z]+)/) || $line.match(/<\/([a-z]+)>$/)) { - if (this.reIsBlock.test(RegExp.$1.toUpperCase()) || RegExp.$1.toUpperCase() === 'TABLE') { - // check if line starts and ends with the same tag - if ($line.match(/^<([a-z]+).*<\/\1>/)) { - data += $line; - } - else { - data += $line + '
              '; - } - } - else { - data += '

              ' + $line + '

              '; - } - } - else { - if (!$line) { - $line = '
              '; - } - else if ($line.match(/^@@([0-9\-]+)@@$/)) { - if (WCF.inArray(RegExp.$1, $knownQuotes)) { - // prevent quote being nested inside a

              block - data += $line; - continue; - } - } - - data += '

              ' + $line + '

              '; - } - } - - // fix newlines in tables represented with

              ...

              instead of
              - data = data.replace(/([\s\S]+?)<\/td>/g, function(match, content) { - content = content.replace(/(<[uo]l)/g, '$1'); - - return '' + content.replace(/

              <\/p>/g, '
              ').replace(/

              /g, '').replace(/<\/p>/g, '
              ').replace(/
              $/, '') + ''; - }); - - // insert list items - if ($listItems.length) { - for (var $i = $listItems.length - 1; $i >= 0; $i--) { - data = data.replace($listItems[$i].key, $listItems[$i].content); - } - } - // insert quotes if ($cachedQuotes.length) { // [quote] @@ -1324,10 +817,6 @@ RedactorPlugins.wbbcode = function() { } } - // remove

              wrapping a quote or a div - data = data.replace(/<(?:div|p)><(blockquote|div)/g, '<$1'); - data = data.replace(/<\/(blockquote|div)><\/(?:div|p)>/g, ''); - // insert codes if ($cachedCodes.length) { for (var $i = $cachedCodes.length - 1; $i >= 0; $i--) { @@ -1454,44 +943,6 @@ RedactorPlugins.wbbcode = function() { return data; }, - /** - * Expands formatting to convert markup like [b]Hello\nWorld[/b] into [b]Hello[/b]\n[b]World[/b]. - * - * @param string content - * @param string openingTag - * @param string closingTag - * @return string - */ - _expandFormatting: function(content, openingTag, closingTag) { - if (!content.length) { - return openingTag + this.opts.invisibleSpace + closingTag; - } - - // check for unclosed tags in tables - var $index = content.indexOf('[/td]'); - if ($index !== -1) { - var $tmp = content.substring(0, $index); - if ($tmp.indexOf('[td]') === -1) { - return openingTag + $tmp + closingTag + content.substring($index); - } - } - - var $tmp = content.split("\n"); - content = ''; - - for (var $i = 0, $length = $tmp.length; $i < $length; $i++) { - var $line = $tmp[$i]; - if ($line.length === 0) { - $line = this.opts.invisibleSpace; - } - - if (content.length) content += '\n'; - content += openingTag + $line + closingTag; - } - - return content; - }, - /** * Converts certain HTML elements prior to paste in order to preserve formattings. * diff --git a/wcfsetup/install/files/js/WoltLab/WCF/BBCode/FromHtml.js b/wcfsetup/install/files/js/WoltLab/WCF/BBCode/FromHtml.js index b4db14d541..2ce1f0542b 100644 --- a/wcfsetup/install/files/js/WoltLab/WCF/BBCode/FromHtml.js +++ b/wcfsetup/install/files/js/WoltLab/WCF/BBCode/FromHtml.js @@ -1,8 +1,8 @@ -define([], function() { +define(['DOM/Traverse'], function(DOMTraverse) { "use strict"; var _converter = []; - var _inlineConverter = []; + var _inlineConverter = {}; var BBCodeFromHtml = { convert: function(message) { @@ -53,15 +53,24 @@ define([], function() { { tagName: 'A', callback: this._convertUrl.bind(this) }, { tagName: 'LI', callback: this._convertListItem.bind(this) }, { tagName: 'OL', callback: this._convertList.bind(this) }, + { tagName: 'TABLE', callback: this._convertTable.bind(this) }, { tagName: 'UL', callback: this._convertList.bind(this) }, - { tagName: 'SPAN', callback: this._convertSpan.bind(this) } + + // convert these last + { tagName: 'SPAN', callback: this._convertSpan.bind(this) }, + { tagName: 'DIV', callback: this._convertDiv.bind(this) } ]; - _inlineConverter = [ - { style: 'color', callback: this._convertInlineColor.bind(this) }, - { style: 'font-size', callback: this._convertInlineFontSize.bind(this) }, - { style: 'font-family', callback: this._convertInlineFontFamily.bind(this) } - ]; + _inlineConverter = { + span: [ + { style: 'color', callback: this._convertInlineColor.bind(this) }, + { style: 'font-size', callback: this._convertInlineFontSize.bind(this) }, + { style: 'font-family', callback: this._convertInlineFontFamily.bind(this) } + ], + div: [ + { style: 'text-align', callback: this._convertInlineTextAlign.bind(this) } + ] + }; }, _convert: function(container, converter) { @@ -114,8 +123,8 @@ define([], function() { _convertSpan: function(element) { if (element.style.length || element.className) { var converter, value; - for (var i = 0, length = _inlineConverters.length; i < length; i++) { - converter = _inlineConverters[i]; + for (var i = 0, length = _inlineConverter.span.length; i < length; i++) { + converter = _inlineConverter.span[i]; if (converter.style) { value = element.style.getPropertyValue(converter.style) || ''; @@ -134,17 +143,80 @@ define([], function() { element.outerHTML = element.innerHTML; }, - _convertInlineColor: function(element, color) { - if (color.match(/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/i)) { + _convertDiv: function(element) { + if (element.style.length) { + var converter, value; + for (var i = 0, length = _inlineConverter.div.length; i < length; i++) { + converter = _inlineConverter.div[i]; + + value = element.style.getPropertyValue(converter.style) || ''; + if (value) { + converter.callback(element, value); + } + } + } + + element.outerHTML = element.innerHTML; + }, + + _convertInlineColor: function(element, value) { + if (value.match(/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/i)) { var r = RegExp.$1; var g = RegExp.$2; var b = RegExp.$3; var chars = '0123456789ABCDEF'; - color = '#' + (chars.charAt((r - r % 16) / 16) + '' + chars.charAt(r % 16)) + '' + (chars.charAt((g - g % 16) / 16) + '' + chars.charAt(g % 16)) + '' + (chars.charAt((b - b % 16) / 16) + '' + chars.charAt(b % 16)); + value = '#' + (chars.charAt((r - r % 16) / 16) + '' + chars.charAt(r % 16)) + '' + (chars.charAt((g - g % 16) / 16) + '' + chars.charAt(g % 16)) + '' + (chars.charAt((b - b % 16) / 16) + '' + chars.charAt(b % 16)); + } + + element.innerHTML = '[color=' + value + ']' + element.innerHTML + '[/color]'; + }, + + _convertInlineFontSize: function(element, value) { + if (value.match(/^(\d+)pt$/)) { + value = RegExp.$1; + } + else if (value.match(/^(\d+)(px|em|rem|%)$/)) { + value = window.getComputedStyle(value).fontSize.replace(/^(\d+).*$/, '$1'); + value = Math.round(value); + } + else { + // unknown or unsupported value, ignore + value = ''; + } + + if (value) { + // min size is 8 and maximum is 36 + value = Math.min(Math.max(value, 8), 36); + + element.innerHTML = '[size=' + value + ']' + element.innerHTML + '[/size]'; + } + }, + + _convertInlineFontFamily: function(element, value) { + element.innerHTML = '[font=' + value.replace(/'/g, '') + ']' + element.innerHTML + '[/font]'; + }, + + _convertInlineTextAlign: function(element, value) { + if (value === 'left' || value === 'right' || value === 'justify') { + element.innerHTML = '[align=' + value + ']' + innerHTML + '[/align]'; + } + }, + + _convertTable: function(element) { + var elements = element.getElementsByTagName('TD'); + while (elements.length) { + elements[0].outerHTML = '[td]' + elements[0].innerHTML + '[/td]\n'; + } + + elements = element.getElementsByTagName('TR'); + while (elements.length) { + elements[0].outerHTML = '\n[tr]\n' + elements[0].innerHTML + '[/tr]'; } - element.innerHTML = '[color=' + color + ']' + element.innerHTML + '[/color]'; + var tbody = DOMTraverse.childByTag(element, 'TBODY'); + var innerHtml = (tbody === null) ? element.innerHTML : tbody.innerHTML; + element.outerHTML = '\n[table]' + innerHtml + '\n[/table]'; }, _convertUrl: function(element) { diff --git a/wcfsetup/install/files/js/WoltLab/WCF/BBCode/Parser.js b/wcfsetup/install/files/js/WoltLab/WCF/BBCode/Parser.js index eb69565bc3..2b8999f5a3 100644 --- a/wcfsetup/install/files/js/WoltLab/WCF/BBCode/Parser.js +++ b/wcfsetup/install/files/js/WoltLab/WCF/BBCode/Parser.js @@ -10,7 +10,7 @@ define([], function() { }, _splitTags: function(message) { - var validTags = 'attach|b|color|i|list|url'; + var validTags = 'attach|b|color|i|list|url|table|td|tr'; var pattern = '(\\\[(?:/(?:' + validTags + ')|(?:' + validTags + ')' + '(?:=' + '(?:\\\'[^\\\'\\\\]*(?:\\\\.[^\\\'\\\\]*)*\\\'|[^,\\\]]*)' diff --git a/wcfsetup/install/files/js/WoltLab/WCF/BBCode/ToHtml.js b/wcfsetup/install/files/js/WoltLab/WCF/BBCode/ToHtml.js index 7027f468da..796dd30871 100644 --- a/wcfsetup/install/files/js/WoltLab/WCF/BBCode/ToHtml.js +++ b/wcfsetup/install/files/js/WoltLab/WCF/BBCode/ToHtml.js @@ -2,6 +2,8 @@ define(['WoltLab/WCF/BBCode/Parser'], function(BBCodeParser) { "use strict"; var _bbcodes = null; + var _removeNewlineAfter = []; + var _removeNewlineBefore = []; var BBCodeToHtml = { convert: function(message) { @@ -50,16 +52,22 @@ define(['WoltLab/WCF/BBCode/Parser'], function(BBCodeParser) { s: 'del', sub: 'sub', sup: 'sup', + table: 'table', + td: 'td', + tr: 'tr', // callback replacement color: this._replaceColor.bind(this), list: this._replaceList.bind(this), url: this._replaceUrl.bind(this) }; + + _removeNewlineAfter = ['table', 'td', 'tr']; + _removeNewlineBefore = ['table', 'td', 'tr']; }, _replace: function(stack, item, index) { - var pair = stack[item.pair], replace = _bbcodes[item.name]; + var pair = stack[item.pair], replace = _bbcodes[item.name], tmp; if (replace === undefined) { // treat as plain text @@ -67,7 +75,36 @@ define(['WoltLab/WCF/BBCode/Parser'], function(BBCodeParser) { return item.source; } - else if (typeof replace === 'string') { + + if (_removeNewlineAfter.indexOf(item.name) !== -1) { + tmp = stack[index + 1]; + if (typeof tmp === 'string') { + stack[index + 1] = tmp.replace(/^\n/, ''); + } + + if (stack.length < item.pair + 1) { + tmp = stack[item.pair + 1]; + if (typeof tmp === 'string') { + stack[item.pair + 1] = tmp.replace(/^\n/, ''); + } + } + } + + if (_removeNewlineBefore.indexOf(item.name) !== -1) { + if (index - 1 >= 0) { + tmp = stack[index - 1]; + if (typeof tmp === 'string') { + stack[index - 1] = tmp.replace(/\n$/, ''); + } + } + + tmp = stack[item.pair - 1]; + if (typeof tmp === 'string') { + stack[item.pair - 1] = tmp.replace(/\n$/, ''); + } + } + + if (typeof replace === 'string') { stack[item.pair] = ''; return '<' + replace + '>'; -- 2.20.1