From 64ed159250be6cd0351996b6095924e56d1d040a Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Tue, 30 Jun 2015 15:44:30 +0200 Subject: [PATCH] Added support for source bbcodes and improved parser --- .../files/js/WoltLab/WCF/BBCode/FromHtml.js | 100 +++++++++++++++++- .../files/js/WoltLab/WCF/BBCode/Parser.js | 29 +++-- .../files/js/WoltLab/WCF/BBCode/ToHtml.js | 98 ++++++++++++++++- 3 files changed, 212 insertions(+), 15 deletions(-) diff --git a/wcfsetup/install/files/js/WoltLab/WCF/BBCode/FromHtml.js b/wcfsetup/install/files/js/WoltLab/WCF/BBCode/FromHtml.js index ed992aca32..0e3cafdffa 100644 --- a/wcfsetup/install/files/js/WoltLab/WCF/BBCode/FromHtml.js +++ b/wcfsetup/install/files/js/WoltLab/WCF/BBCode/FromHtml.js @@ -1,8 +1,9 @@ -define(['StringUtil', 'DOM/Traverse'], function(StringUtil, DOMTraverse) { +define(['EventHandler', 'StringUtil', 'DOM/Traverse'], function(EventHandler, StringUtil, DOMTraverse) { "use strict"; var _converter = []; var _inlineConverter = {}; + var _sourceConverter = []; var BBCodeFromHtml = { convert: function(message) { @@ -18,15 +19,58 @@ define(['StringUtil', 'DOM/Traverse'], function(StringUtil, DOMTraverse) { var elements = container.getElementsByTagName('BR'); while (elements.length) elements[0].outerHTML = "\n"; + var sourceElements = this._preserveSourceElements(container); + for (var i = 0, length = _converter.length; i < length; i++) { this._convert(container, _converter[i]); } + this._restoreSourceElements(container, sourceElements); + message = this._convertSpecials(container.innerHTML); return message; }, + _preserveSourceElements: function(container) { + var elements, sourceElements = [], tmp; + + for (var i = 0, length = _sourceConverter.length; i < length; i++) { + elements = container.querySelectorAll(_sourceConverter[i].selector); + + tmp = []; + for (var j = 0, innerLength = elements.length; j < innerLength; j++) { + this._preserveSourceElement(elements[j], tmp); + } + + sourceElements.push(tmp); + } + + return sourceElements; + }, + + _restoreSourceElements: function(container, sourceElements) { + var element, elements, placeholder; + for (var i = 0, length = sourceElements.length; i < length; i++) { + elements = sourceElements[i]; + + if (elements.length === 0) { + continue; + } + + for (var j = 0, innerLength = elements.length; j < innerLength; j++) { + element = elements[j]; + placeholder = element.placeholder; + + placeholder.parentNode.insertBefore(element.fragment, placeholder); + + _sourceConverter[i].callback(placeholder.previousElementSibling); + + placeholder.parentNode.removeChild(placeholder); + } + } + }, + _convertSpecials: function(message) { message = message.replace(/&/g, '&'); message = message.replace(/</g, '<'); @@ -72,6 +116,16 @@ define(['StringUtil', 'DOM/Traverse'], function(StringUtil, DOMTraverse) { { style: 'text-align', callback: this._convertInlineTextAlign.bind(this) } ] }; + + _sourceConverter = [ + { selector: 'div.codeBox', callback: this._convertSourceCodeBox.bind(this) } + ]; + + EventHandler.fire('com.woltlab.wcf.bbcode.fromHtml', 'init', { + converter: _converter, + inlineConverter: _inlineConverter, + sourceConverter: _sourceConverter + }); }, _convert: function(container, converter) { @@ -171,14 +225,19 @@ define(['StringUtil', 'DOM/Traverse'], function(StringUtil, DOMTraverse) { }, _convertDiv: function(element) { - if (element.style.length) { + if (element.className.length || 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); + if (converter.className && element.classList.contains(converter.className)) { + converter.callback(element); + } + else if (converter.style) { + value = element.style.getPropertyValue(converter.style) || ''; + if (value) { + converter.callback(element, value); + } } } } @@ -264,6 +323,37 @@ define(['StringUtil', 'DOM/Traverse'], function(StringUtil, DOMTraverse) { else { element.outerHTML = "[url='" + href + "']" + element.innerHTML + "[/url]"; } + }, + + _convertSourceCodeBox: function(element) { + var filename = element.getAttribute('data-filename').trim() || ''; + var highlighter = element.getAttribute('data-highlighter') || ''; + window.dtdesign = element; + var list = DOMTraverse.childByTag(element.children[0], 'OL'); + var lineNumber = ~~list.getAttribute('start') || 1; + + var content = ''; + for (var i = 0, length = list.childElementCount; i < length; i++) { + if (content) content += "\n"; + content += list.children[i].textContent; + } + + var open = "[code='" + highlighter + "'," + lineNumber + ",'" + filename + "']"; + + element.outerHTML = open + content + '[/code]'; + }, + + _preserveSourceElement: function(element, sourceElements) { + var placeholder = document.createElement('var'); + element.parentNode.insertBefore(placeholder, element); + + var fragment = document.createDocumentFragment(); + fragment.appendChild(element); + + sourceElements.push({ + fragment: fragment, + placeholder: placeholder + }); } }; diff --git a/wcfsetup/install/files/js/WoltLab/WCF/BBCode/Parser.js b/wcfsetup/install/files/js/WoltLab/WCF/BBCode/Parser.js index 13bc070889..9e72dc9d54 100644 --- a/wcfsetup/install/files/js/WoltLab/WCF/BBCode/Parser.js +++ b/wcfsetup/install/files/js/WoltLab/WCF/BBCode/Parser.js @@ -11,7 +11,7 @@ define([], function() { _splitTags: function(message) { // TODO: `validTags` should be dynamic similar to the PHP implementation - var validTags = 'attach|b|color|i|list|url|table|td|tr|quote'; + var validTags = 'attach|b|code|color|i|list|url|table|td|tr|quote'; var pattern = '(\\\[(?:/(?:' + validTags + ')|(?:' + validTags + ')' + '(?:=' + '(?:\\\'[^\\\'\\\\]*(?:\\\\.[^\\\'\\\\]*)*\\\'|[^,\\\]]*)' @@ -53,12 +53,13 @@ define([], function() { _buildLinearTree: function(stack) { var item, openTags = [], reopenTags, sourceBBCode = '', tag; - for (var i = 0, length = stack.length; i < length; i++) { + for (var i = 0; i < stack.length; i++) { // do not cache stack.length, its size is dynamic item = stack[i]; if (typeof item === 'object') { - if (sourceBBCode.length && (item.name !== sourceBBCode || item.closing === false)) { + if (sourceBBCode.length && (item.name !== sourceBBCode || !item.closing)) { stack[i] = item.source; + continue; } if (item.closing) { @@ -68,14 +69,26 @@ define([], function() { stack[i] = item.source; } else { - reopenTags = this._closeUnclosedTags(stack, openTags, item.name); - tag = openTags.pop(); tag.pair = i; - for (var j = 0, innerLength = reopenTags.length; j < innerLength; j++) { - stack.splice(i, reopenTags[j]); - i++; + if (sourceBBCode === item.name) { + // join previous items in the stack + if (lastIndex + 2 < i) { + var joinWith = lastIndex + 1; + for (var j = lastIndex + 2; j < i; j++) { + stack[joinWith] += stack[j]; + stack[j] = ''; + } + } + } + else { + reopenTags = this._closeUnclosedTags(stack, openTags, item.name); + + for (var j = 0, innerLength = reopenTags.length; j < innerLength; j++) { + stack.splice(i, reopenTags[j]); + i++; + } } } diff --git a/wcfsetup/install/files/js/WoltLab/WCF/BBCode/ToHtml.js b/wcfsetup/install/files/js/WoltLab/WCF/BBCode/ToHtml.js index 151927c128..25166aede1 100644 --- a/wcfsetup/install/files/js/WoltLab/WCF/BBCode/ToHtml.js +++ b/wcfsetup/install/files/js/WoltLab/WCF/BBCode/ToHtml.js @@ -1,10 +1,14 @@ -define(['Language', 'StringUtil', 'WoltLab/WCF/BBCode/Parser'], function(Language, StringUtil, BBCodeParser) { +define(['EventHandler', 'Language', 'StringUtil', 'WoltLab/WCF/BBCode/Parser'], function(EventHandler, Language, StringUtil, BBCodeParser) { "use strict"; var _bbcodes = null; var _removeNewlineAfter = []; var _removeNewlineBefore = []; + function isNumber(value) { return value && value == ~~value; } + function isFilename(value) { return (value.indexOf('.') !== -1) || (!isNumber(value) && !isHighlighter(value)); } + function isHighlighter(value) { return __REDACTOR_CODE_HIGHLIGHTERS.hasOwnProperty(value); } + var BBCodeToHtml = { convert: function(message) { this._convertSpecials(message); @@ -25,7 +29,8 @@ define(['Language', 'StringUtil', 'WoltLab/WCF/BBCode/Parser'], function(Languag } message = stack.join(''); - + var x = message; + console.debug(x); message = message.replace(/\n/g, '
'); return message; @@ -58,6 +63,7 @@ define(['Language', 'StringUtil', 'WoltLab/WCF/BBCode/Parser'], function(Languag // callback replacement color: this._replaceColor.bind(this), + code: this._replaceCode.bind(this), list: this._replaceList.bind(this), quote: this._replaceQuote.bind(this), url: this._replaceUrl.bind(this) @@ -65,6 +71,12 @@ define(['Language', 'StringUtil', 'WoltLab/WCF/BBCode/Parser'], function(Languag _removeNewlineAfter = ['quote', 'table', 'td', 'tr']; _removeNewlineBefore = ['table', 'td', 'tr']; + + EventHandler.fire('com.woltlab.wcf.bbcode.toHtml', 'init', { + bbcodes: _bbcodes, + removeNewlineAfter: _removeNewlineAfter, + removeNewlineBefore: _removeNewlineBefore + }); }, _replace: function(stack, item, index) { @@ -115,6 +127,88 @@ define(['Language', 'StringUtil', 'WoltLab/WCF/BBCode/Parser'], function(Languag } }, + _replaceCode: function(stack, item, index) { + var attributes = item.attributes, filename = '', highlighter = 'auto', lineNumber = 0; + + // parse arguments + switch (attributes.length) { + case 1: + if (isNumber(attributes[0])) { + lineNumber = ~~attributes[0]; + } + else if (isFilename(attributes[0])) { + filename = attributes[0]; + } + else if (isHighlighter(attributes[0])) { + highlighter = attributes[0]; + } + break; + case 2: + if (isNumber(attributes[0])) { + lineNumber = ~~attributes[0]; + + if (isHighlighter(attributes[1])) { + highlighter = attributes[1]; + } + else if (isFilename(attributes[1])) { + filename = attributes[1]; + } + } + else { + if (isHighlighter(attributes[0])) highlighter = attributes[0]; + if (isFilename(attributes[1])) filename = attributes[1]; + } + break; + case 3: + if (isHighlighter(attributes[0])) highlighter = attributes[0]; + if (isNumber(attributes[1])) lineNumber = ~~attributes[1]; + if (isFilename(attributes[2])) filename = attributes[2]; + break; + } + + // transform content + var before = true, content, line, empty = -1; + for (var i = index + 1; i < item.pair; i++) { + line = stack[i]; + + if (line.trim() === '') { + if (before) { + stack[i] = ''; + continue; + } + else if (empty === -1) { + empty = i; + } + } + else { + before = false; + empty = -1; + } + + content = line.split('\n'); + for (var j = 0, innerLength = content.length; j < innerLength; j++) { + content[j] = '
  • ' + (content[j] ? StringUtil.escapeHTML(content[j]) : '\u200b') + '
  • '; + } + + stack[i] = content.join(''); + } + + if (!before && empty !== -1) { + for (var i = item.pair - 1; i >= empty; i--) { + stack[i] = ''; + } + } + + stack[item.pair] = ''; + + return '
    ' + + '
    ' + + '
    ' + + '

    ' + __REDACTOR_CODE_HIGHLIGHTERS[highlighter] + (filename ? ': ' + StringUtil.escapeHTML(filename) : '') + '

    ' + + '
    ' + + '
      '; + }, + _replaceColor: function(stack, item, index) { if (!item.attributes.length || !item.attributes[0].match(/^[a-z0-9#]+$/i)) { stack[item.pair] = ''; -- 2.20.1