-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) {
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, '<');
{ 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) {
},
_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);
+ }
}
}
}
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
+ });
}
};
_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 + ')'
+ '(?:='
+ '(?:\\\'[^\\\'\\\\]*(?:\\\\.[^\\\'\\\\]*)*\\\'|[^,\\\]]*)'
_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) {
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++;
+ }
}
}
-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);
}
message = stack.join('');
-
+ var x = message;
+ console.debug(x);
message = message.replace(/\n/g, '<br>');
return message;
// 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)
_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) {
}
},
+ _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] = '<li>' + (content[j] ? StringUtil.escapeHTML(content[j]) : '\u200b') + '</li>';
+ }
+
+ stack[i] = content.join('');
+ }
+
+ if (!before && empty !== -1) {
+ for (var i = item.pair - 1; i >= empty; i--) {
+ stack[i] = '';
+ }
+ }
+
+ stack[item.pair] = '</ol></div></div>';
+
+ return '<div class="codeBox container" contenteditable="false" data-highlighter="' + highlighter + '" data-filename="' + (filename ? StringUtil.escapeHTML(filename) : '') + '">'
+ + '<div>'
+ + '<div>'
+ + '<h3>' + __REDACTOR_CODE_HIGHLIGHTERS[highlighter] + (filename ? ': ' + StringUtil.escapeHTML(filename) : '') + '</h3>'
+ + '</div>'
+ + '<ol start="' + (lineNumber > 1 ? lineNumber : 1) + '">';
+ },
+
_replaceColor: function(stack, item, index) {
if (!item.attributes.length || !item.attributes[0].match(/^[a-z0-9#]+$/i)) {
stack[item.pair] = '';