+++ /dev/null
-$.Redactor.prototype.WoltLabParagraphize = function() {
- "use strict";
-
- return {
- init: function () {
- this.paragraphize.getSafes = (function (html) {
- var $div = $('<div />').append(html);
-
- // WoltLab modification: do not remove <p> inside quotes
- // remove paragraphs in blockquotes
- /*$div.find('blockquote p').replaceWith(function()
- {
- return $(this).append('<br />').contents();
- });*/
-
- $div.find(this.opts.paragraphizeBlocks.join(', ')).each($.proxy(function(i,s)
- {
- this.paragraphize.z++;
- this.paragraphize.safes[this.paragraphize.z] = s.outerHTML;
-
- return $(s).replaceWith('\n#####replace' + this.paragraphize.z + '#####\n\n');
-
-
- }, this));
-
-
- return $div.html();
- }).bind(this)
- }
- };
-};
+++ /dev/null
-/**
- * Converts a message containing HTML tags into BBCodes.
- *
- * @author Alexander Ebert
- * @copyright 2001-2015 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module WoltLabSuite/Core/Bbcode/FromHtml
- */
-define(['EventHandler', 'StringUtil', 'Dom/Traverse'], function(EventHandler, StringUtil, DomTraverse) {
- "use strict";
-
- var _converter = [];
- var _inlineConverter = {};
- var _sourceConverter = [];
-
- /**
- * Returns true if a whitespace should be inserted before or after the smiley.
- *
- * @param {Element} element image element
- * @param {boolean} before evaluate previous node
- * @return {boolean} true if a whitespace should be inserted
- */
- function addSmileyPadding(element, before) {
- var target = element[(before ? 'previousSibling' : 'nextSibling')];
- if (target === null || target.nodeType !== Node.TEXT_NODE || !/\s$/.test(target.textContent)) {
- return true;
- }
-
- return false;
- }
-
- /**
- * @module WoltLabSuite/Core/Bbcode/FromHtml
- */
- var BbcodeFromHtml = {
- /**
- * Converts a message containing HTML elements into BBCodes.
- *
- * @param {string} message message containing HTML elements
- * @return {string} message containing BBCodes
- */
- convert: function(message) {
- if (message.length) this._setup();
-
- var container = elCreate('div');
- container.innerHTML = message;
-
- // convert line breaks
- var elements = elByTag('P', container);
- while (elements.length) elements[0].outerHTML = elements[0].innerHTML;
-
- elements = elByTag('BR', container);
- while (elements.length) elements[0].outerHTML = "\n";
-
- // prevent conversion taking place inside source bbcodes
- var sourceElements = this._preserveSourceElements(container);
-
- EventHandler.fire('com.woltlab.wcf.bbcode.fromHtml', 'beforeConvert', { container: container });
-
- for (var i = 0, length = _converter.length; i < length; i++) {
- this._convert(container, _converter[i]);
- }
-
- EventHandler.fire('com.woltlab.wcf.bbcode.fromHtml', 'afterConvert', { container: container });
-
- this._restoreSourceElements(container, sourceElements);
-
- // remove remaining HTML elements
- elements = elByTag('*', container);
- while (elements.length) elements[0].outerHTML = elements[0].innerHTML;
-
- message = this._convertSpecials(container.innerHTML);
-
- return message;
- },
-
- /**
- * Replaces HTML elements mapping to source BBCodes to avoid
- * them being handled by other converters.
- *
- * @param {Element} container container element
- * @return {array<object>} list of source elements and their placeholder
- */
- _preserveSourceElements: function(container) {
- var elements, sourceElements = [], tmp;
-
- for (var i = 0, length = _sourceConverter.length; i < length; i++) {
- elements = elBySelAll(_sourceConverter[i].selector, container);
-
- tmp = [];
- for (var j = 0, innerLength = elements.length; j < innerLength; j++) {
- this._preserveSourceElement(elements[j], tmp);
- }
-
- sourceElements.push(tmp);
- }
-
- return sourceElements;
- },
-
- /**
- * Replaces an element with a placeholder.
- *
- * @param {Element} element target element
- * @param {array<object>} list of removed elements and their placeholders
- */
- _preserveSourceElement: function(element, sourceElements) {
- var placeholder = elCreate('var');
- elData(placeholder, 'source', 'wcf');
- element.parentNode.insertBefore(placeholder, element);
-
- var fragment = document.createDocumentFragment();
- fragment.appendChild(element);
-
- sourceElements.push({
- fragment: fragment,
- placeholder: placeholder
- });
- },
-
- /**
- * Reinserts source elements for parsing.
- *
- * @param {Element} container container element
- * @param {array<object>} sourceElements list of removed elements and their placeholders
- */
- _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);
-
- elRemove(placeholder);
- }
- }
- },
-
- /**
- * Converts special entities.
- *
- * @param {string} message HTML message
- * @return {string} HTML message
- */
- _convertSpecials: function(message) {
- message = message.replace(/&/g, '&');
- message = message.replace(/</g, '<');
- message = message.replace(/>/g, '>');
-
- return message;
- },
-
- /**
- * Sets up converters applied to elements in linear order.
- */
- _setup: function() {
- if (_converter.length) {
- return;
- }
-
- _converter = [
- // simple replacement
- { tagName: 'STRONG', bbcode: 'b' },
- { tagName: 'DEL', bbcode: 's' },
- { tagName: 'EM', bbcode: 'i' },
- { tagName: 'SUB', bbcode: 'sub' },
- { tagName: 'SUP', bbcode: 'sup' },
- { tagName: 'U', bbcode: 'u' },
- { tagName: 'KBD', bbcode: 'tt' },
-
- // callback replacement
- { tagName: 'A', callback: this._convertUrl.bind(this) },
- { tagName: 'IMG', callback: this._convertImage.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: 'BLOCKQUOTE', callback: this._convertBlockquote.bind(this) },
-
- // convert these last
- { tagName: 'SPAN', callback: this._convertSpan.bind(this) },
- { tagName: 'DIV', callback: this._convertDiv.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) }
- ]
- };
-
- _sourceConverter = [
- { selector: 'div.codeBox', callback: this._convertSourceCodeBox.bind(this) }
- ];
-
- EventHandler.fire('com.woltlab.wcf.bbcode.fromHtml', 'init', {
- converter: _converter,
- inlineConverter: _inlineConverter,
- sourceConverter: _sourceConverter
- });
- },
-
- /**
- * Converts an element into a raw string.
- *
- * @param {Element} container container element
- * @param {object} converter converter object
- */
- _convert: function(container, converter) {
- if (typeof converter === 'function') {
- converter(container);
- return;
- }
-
- var element, elements = elByTag(converter.tagName, container);
- while (elements.length) {
- element = elements[0];
-
- if (converter.bbcode) {
- element.outerHTML = '[' + converter.bbcode + ']' + element.innerHTML + '[/' + converter.bbcode + ']';
- }
- else {
- converter.callback(element);
- }
- }
- },
-
- /**
- * Converts <blockquote> into [quote].
- *
- * @param {Element} element target element
- */
- _convertBlockquote: function(element) {
- var author = elData(element, 'author');
- var link = elAttr(element, 'cite');
-
- var open = '[quote]';
- if (author) {
- author = StringUtil.escapeHTML(author).replace(/(\\)?'/g, function(match, isEscaped) { return isEscaped ? match : "\\'"; });
- if (link) {
- open = "[quote='" + author + "','" + StringUtil.escapeHTML(link) + "']";
- }
- else {
- open = "[quote='" + author + "']";
- }
- }
-
- var header = DomTraverse.childByTag(element, 'HEADER');
- if (header !== null) element.removeChild(header);
-
- var divs = DomTraverse.childrenByTag(element, 'DIV');
- for (var i = 0, length = divs.length; i < length; i++) {
- divs[i].outerHTML = divs[i].innerHTML + '\n';
- }
-
- element.outerHTML = open + element.innerHTML.replace(/^\n*/, '').replace(/\n*$/, '') + '[/quote]\n';
- },
-
- /**
- * Converts <img> into smilies, [attach] or [img].
- *
- * @param {Element} element target element
- */
- _convertImage: function(element) {
- if (element.classList.contains('smiley')) {
- // smiley
- element.outerHTML = (addSmileyPadding(element, true) ? ' ' : '') + elAttr(element, 'alt') + (addSmileyPadding(element, false) ? ' ' : '');
- return;
- }
-
- var float = element.style.getPropertyValue('float') || 'none';
- var width = element.style.getPropertyValue('width');
- width = (typeof width === 'string') ? ~~width.replace(/px$/, '') : 0;
-
- if (element.classList.contains('redactorEmbeddedAttachment')) {
- var attachmentId = elData(element, 'attachment-id');
-
- if (width > 0) {
- element.outerHTML = "[attach=" + attachmentId + "," + float + "," + width + "][/attach]";
- }
- else if (float !== 'none') {
- element.outerHTML = "[attach=" + attachmentId + "," + float + "][/attach]";
- }
- else {
- element.outerHTML = "[attach=" + attachmentId + "][/attach]";
- }
- }
- else {
- // regular image
- var source = element.src.trim();
-
- if (width > 0) {
- element.outerHTML = "[img='" + source + "'," + float + "," + width + "][/img]";
- }
- else if (float !== 'none') {
- element.outerHTML = "[img='" + source + "'," + float + "][/img]";
- }
- else {
- element.outerHTML = "[img]" + source + "[/img]";
- }
- }
- },
-
- /**
- * Converts <ol> and <ul> into [list].
- *
- * @param {Element} element target element
- */
- _convertList: function(element) {
- var open;
-
- if (element.nodeName === 'OL') {
- open = '[list=1]';
- }
- else {
- var type = element.style.getPropertyValue('list-style-type') || '';
- if (type === '') {
- open = '[list]';
- }
- else {
- open = '[list=' + (type === 'lower-latin' ? 'a' : type) + ']';
- }
- }
-
- element.outerHTML = open + element.innerHTML + '[/list]';
- },
-
- /**
- * Converts <li> into [*] unless it is not encapsulated in <ol> or <ul>.
- *
- * @param {Element} element target element
- */
- _convertListItem: function(element) {
- if (element.parentNode.nodeName !== 'UL' && element.parentNode.nodeName !== 'OL') {
- element.outerHTML = element.innerHTML;
- }
- else {
- element.outerHTML = '[*]' + element.innerHTML;
- }
- },
-
- /**
- * Converts <span> into a series of BBCodes including [color], [font] and [size].
- *
- * @param {Element} element target element
- */
- _convertSpan: function(element) {
- if (element.style.length || element.className) {
- var converter, value;
- for (var i = 0, length = _inlineConverter.span.length; i < length; i++) {
- converter = _inlineConverter.span[i];
-
- if (converter.style) {
- value = element.style.getPropertyValue(converter.style) || '';
- if (value) {
- converter.callback(element, value);
- }
- }
- else {
- if (element.classList.contains(converter.className)) {
- converter.callback(element);
- }
- }
- }
- }
-
- element.outerHTML = element.innerHTML;
- },
-
- /**
- * Converts <div> into a series of BBCodes including [align].
- *
- * @param {Element} element target element
- */
- _convertDiv: function(element) {
- 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];
-
- 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);
- }
- }
- }
- }
-
- element.outerHTML = element.innerHTML;
- },
-
- /**
- * Converts the CSS style `color` into [color].
- *
- * @param {Element} element target element
- */
- _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';
- 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]';
- },
-
- /**
- * Converts the CSS style `font-size` into [size].
- *
- * @param {Element} element target element
- */
- _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]';
- }
- },
-
- /**
- * Converts the CSS style `font-family` into [font].
- *
- * @param {Element} element target element
- */
- _convertInlineFontFamily: function(element, value) {
- element.innerHTML = '[font=' + value.replace(/'/g, '') + ']' + element.innerHTML + '[/font]';
- },
-
- /**
- * Converts the CSS style `text-align` into [align].
- *
- * @param {Element} element target element
- */
- _convertInlineTextAlign: function(element, value) {
- if (['center', 'justify', 'left', 'right'].indexOf(value) !== -1) {
- element.innerHTML = '[align=' + value + ']' + element.innerHTML + '[/align]';
- }
- },
-
- /**
- * Converts tables and their children into BBCodes.
- *
- * @param {Element} element target element
- */
- _convertTable: function(element) {
- var elements = elByTag('TD', element);
- while (elements.length) {
- elements[0].outerHTML = '[td]' + elements[0].innerHTML + '[/td]\n';
- }
-
- elements = elByTag('TR', element);
- while (elements.length) {
- elements[0].outerHTML = '\n[tr]\n' + elements[0].innerHTML + '[/tr]';
- }
-
- var tbody = DomTraverse.childByTag(element, 'TBODY');
- var innerHtml = (tbody === null) ? element.innerHTML : tbody.innerHTML;
- element.outerHTML = '\n[table]' + innerHtml + '\n[/table]\n';
- },
-
- /**
- * Converts <a> into [email] or [url].
- *
- * @param {Element} element target element
- */
- _convertUrl: function(element) {
- var content = element.textContent.trim(), href = element.href.trim(), tagName = 'url';
-
- if (href === '' || content === '') {
- // empty href or content
- element.outerHTML = element.innerHTML;
- return;
- }
-
- if (href.indexOf('mailto:') === 0) {
- href = href.substr(7);
- tagName = 'email';
- }
-
- if (href === content) {
- element.outerHTML = '[' + tagName + ']' + href + '[/' + tagName + ']';
- }
- else {
- element.outerHTML = "[" + tagName + "='" + href + "']" + element.innerHTML + "[/" + tagName + "]";
- }
- },
-
- /**
- * Converts <div class="codeBox"> into [code].
- *
- * @param {Element} element target element
- */
- _convertSourceCodeBox: function(element) {
- var filename = elData(element, 'filename').trim() || '';
- var highlighter = elData(element, 'highlighter');
- window.dtdesign = element;
- var list = DomTraverse.childByTag(element.children[0], 'OL');
- var lineNumber = ~~elAttr(list, '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]';
- }
- };
-
- return BbcodeFromHtml;
-});
+++ /dev/null
-/**
- * Versatile BBCode parser based upon the PHP implementation.
- *
- * @author Alexander Ebert
- * @copyright 2001-2015 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module WoltLabSuite/Core/Bbcode/Parser
- */
-define([], function() {
- "use strict";
-
- /**
- * @module WoltLabSuite/Core/Bbcode/Parser
- */
- var BbcodeParser = {
- /**
- * Parses a message and returns an XML-conform linear tree.
- *
- * @param {string} message message containing BBCodes
- * @return {array<mixed>} linear tree
- */
- parse: function(message) {
- var stack = this._splitTags(message);
- this._buildLinearTree(stack);
-
- return stack;
- },
-
- /**
- * Splits message into strings and BBCode objects.
- *
- * @param {string} message message containing BBCodes
- * @returns {array<mixed>} linear tree
- */
- _splitTags: function(message) {
- var validTags = __REDACTOR_BBCODES.join('|');
- var pattern = '(\\\[(?:/(?:' + validTags + ')|(?:' + validTags + ')'
- + '(?:='
- + '(?:\\\'[^\\\'\\\\]*(?:\\\\.[^\\\'\\\\]*)*\\\'|[^,\\\]]*)'
- + '(?:,(?:\\\'[^\\\'\\\\]*(?:\\\\.[^\\\'\\\\]*)*\'|[^,\\\]]*))*'
- + ')?)\\\])';
-
- var isBBCode = new RegExp('^' + pattern + '$', 'i');
- var part, parts = message.split(new RegExp(pattern, 'i')), stack = [], tag;
- for (var i = 0, length = parts.length; i < length; i++) {
- part = parts[i];
-
- if (part === '') {
- continue;
- }
- else if (part.match(isBBCode)) {
- tag = { name: '', closing: false, attributes: [], source: part };
-
- if (part[1] === '/') {
- tag.name = part.substring(2, part.length - 1);
- tag.closing = true;
- }
- else if (part.match(/^\[([a-z0-9]+)=?(.*)\]$/i)) {
- tag.name = RegExp.$1;
-
- if (RegExp.$2) {
- tag.attributes = this._parseAttributes(RegExp.$2);
- }
- }
-
- stack.push(tag);
- }
- else {
- stack.push(part);
- }
- }
-
- return stack;
- },
-
- /**
- * Finds pairs and enforces XML-conformity in terms of pairing and proper nesting.
- *
- * @param {array<mixed>} stack linear tree
- */
- _buildLinearTree: function(stack) {
- var item, openTags = [], reopenTags, sourceBBCode = '';
- 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)) {
- stack[i] = item.source;
- continue;
- }
-
- if (item.closing) {
- if (this._hasOpenTag(openTags, item.name)) {
- reopenTags = this._closeUnclosedTags(stack, openTags, item.name);
- for (var j = 0, innerLength = reopenTags.length; j < innerLength; j++) {
- stack.splice(i, reopenTags[j]);
- i++;
- }
-
- openTags.pop().pair = i;
- }
- else {
- // tag was never opened, treat as plain text
- stack[i] = item.source;
- }
-
- if (sourceBBCode === item.name) {
- sourceBBCode = '';
- }
- }
- else {
- openTags.push(item);
-
- if (__REDACTOR_SOURCE_BBCODES.indexOf(item.name) !== -1) {
- sourceBBCode = item.name;
- }
- }
- }
- }
-
- // close unclosed tags
- this._closeUnclosedTags(stack, openTags, '');
- },
-
- /**
- * Closes unclosed BBCodes and returns a list of BBCodes in order of appearance that should be
- * opened again to enforce proper nesting.
- *
- * @param {array<mixed>} stack linear tree
- * @param {array<object>} openTags list of unclosed elements
- * @param {string} until tag name to stop at
- * @return {array<mixed>} list of tags to open in order of appearance
- */
- _closeUnclosedTags: function(stack, openTags, until) {
- var item, reopenTags = [], tag;
-
- for (var i = openTags.length - 1; i >= 0; i--) {
- item = openTags[i];
-
- if (item.name === until) {
- break;
- }
-
- tag = { name: item.name, closing: true, attributes: item.attributes.slice(), source: '[/' + item.name + ']' };
- item.pair = stack.length;
-
- stack.push(tag);
-
- openTags.pop();
- reopenTags.push({ name: item.name, closing: false, attributes: item.attributes.slice(), source: item.source });
- }
-
- return reopenTags.reverse();
- },
-
- /**
- * Returns true if given BBCode was opened before.
- *
- * @param {array<object>} openTags list of unclosed elements
- * @param {string} name BBCode to search for
- * @returns {boolean} false if tag was not opened before
- */
- _hasOpenTag: function(openTags, name) {
- for (var i = openTags.length - 1; i >= 0; i--) {
- if (openTags[i].name === name) {
- return true;
- }
- }
-
- return false;
- },
-
- /**
- * Parses the attribute list and returns a list of attributes without enclosing quotes.
- *
- * @param {string} attrString comma separated string with optional quotes per attribute
- * @returns {array<string>} list of attributes
- */
- _parseAttributes: function(attrString) {
- var tmp = attrString.split(/(?:^|,)('[^'\\\\]*(?:\\\\.[^'\\\\]*)*'|[^,]*)/g);
-
- var attribute, attributes = [];
- for (var i = 0, length = tmp.length; i < length; i++) {
- attribute = tmp[i];
-
- if (attribute !== '') {
- if (attribute.charAt(0) === "'" && attribute.substr(-1) === "'") {
- attributes.push(attribute.substring(1, attribute.length - 1).trim());
- }
- else {
- attributes.push(attribute.trim());
- }
- }
- }
-
- return attributes;
- }
- };
-
- return BbcodeParser;
-});
+++ /dev/null
-/**
- * Converts a message containing BBCodes into HTML.
- *
- * @author Alexander Ebert
- * @copyright 2001-2015 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module WoltLabSuite/Core/Bbcode/ToHtml
- */
-define(['Core', 'EventHandler', 'Language', 'StringUtil', 'WoltLabSuite/Core/Bbcode/Parser'], function(Core, EventHandler, Language, StringUtil, BbcodeParser) {
- "use strict";
-
- var _bbcodes = null;
- var _options = {};
- var _removeNewlineAfter = [];
- var _removeNewlineBefore = [];
-
- /**
- * Returns true if given value is a non-zero integer.
- *
- * @param {string} value target value
- * @return {boolean} true if `value` is a non-zero integer
- */
- function isNumber(value) {
- return value && value == ~~value;
- }
-
- /**
- * Returns true if given value appears to be a filename, which means that it contains a dot
- * or is neither numeric nor a known highlighter.
- *
- * @param {string} value target value
- * @return {boolean} true if `value` appears to be a filename
- */
- function isFilename(value) {
- return (value.indexOf('.') !== -1) || (!isNumber(value) && !isHighlighter(value));
- }
-
- /**
- * Returns true if given value is a known highlighter.
- *
- * @param {string} value target value
- * @return {boolean} true if `value` is a known highlighter
- */
- function isHighlighter(value) {
- return objOwns(__REDACTOR_CODE_HIGHLIGHTERS, value);
- }
-
- /**
- * @module WoltLabSuite/Core/Bbcode/ToHtml
- */
- var BbcodeToHtml = {
- /**
- * Converts a message containing BBCodes to HTML.
- *
- * @param {string} message message containing BBCodes
- * @return {string} HTML message
- */
- convert: function(message, options) {
- _options = Core.extend({
- attachments: {
- images: {},
- thumbnailUrl: '',
- url: ''
- }
- }, options);
-
- this._convertSpecials(message);
-
- var stack = BbcodeParser.parse(message);
-
- if (stack.length) {
- this._initBBCodes();
- }
-
- EventHandler.fire('com.woltlab.wcf.bbcode.toHtml', 'beforeConvert', { stack: stack });
-
- var item, value;
- for (var i = 0, length = stack.length; i < length; i++) {
- item = stack[i];
-
- if (typeof item === 'object') {
- value = this._convert(stack, item, i);
- if (Array.isArray(value)) {
- stack[i] = (value[0] === null ? item.source : value[0]);
- stack[item.pair] = (value[1] === null ? stack[item.pair].source : value[1]);
- }
- else {
- stack[i] = value;
- }
- }
- }
-
- EventHandler.fire('com.woltlab.wcf.bbcode.toHtml', 'afterConvert', { stack: stack });
-
- message = stack.join('');
-
- message = message.replace(/\n/g, '<br>');
-
- return message;
- },
-
- /**
- * Converts special characters to their entities.
- *
- * @param {string} message message containing BBCodes
- * @return {string} message with replaced special characters
- */
- _convertSpecials: function(message) {
- message = message.replace(/&/g, '&');
- message = message.replace(/</g, '<');
- message = message.replace(/>/g, '>');
-
- return message;
- },
-
- /**
- * Sets up converters applied to HTML elements.
- */
- _initBBCodes: function() {
- if (_bbcodes !== null) {
- return;
- }
-
- _bbcodes = {
- // simple replacements
- b: 'strong',
- i: 'em',
- u: 'u',
- s: 'del',
- sub: 'sub',
- sup: 'sup',
- table: 'table',
- td: 'td',
- tr: 'tr',
- tt: 'kbd',
-
- // callback replacement
- align: this._convertAlignment.bind(this),
- attach: this._convertAttachment.bind(this),
- color: this._convertColor.bind(this),
- code: this._convertCode.bind(this),
- email: this._convertEmail.bind(this),
- list: this._convertList.bind(this),
- quote: this._convertQuote.bind(this),
- size: this._convertSize.bind(this),
- url: this._convertUrl.bind(this),
- img: this._convertImage.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
- });
- },
-
- /**
- * Converts an item from the stack.
- *
- * @param {array<mixed>} stack linear list of BBCode tags and regular strings
- * @param {object} item current BBCode tag object
- * @param {int} index current stack index representing `item`
- * @return {(string|array)} string if only the current item should be replaced or an array with
- * the first item used for the opening tag and the second item for the closing tag
- */
- _convert: function(stack, item, index) {
- var replace = _bbcodes[item.name], tmp;
-
- if (replace === undefined) {
- // treat as plain text
- return [null, null];
- }
-
- 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$/, '');
- }
- }
-
- // replace smilies
- this._convertSmilies(stack);
-
- if (typeof replace === 'string') {
- return ['<' + replace + '>', '</' + replace + '>'];
- }
- else {
- return replace(stack, item, index);
- }
- },
-
- /**
- * Converts [align] into <div style="text-align: ...">.
- *
- * @param {array<mixed>} stack linear list of BBCode tags and regular strings
- * @param {object} item current BBCode tag object
- * @param {int} index current stack index representing `item`
- * @returns {array} first item represents the opening tag, the second the closing one
- */
- _convertAlignment: function(stack, item, index) {
- var align = (item.attributes.length) ? item.attributes[0] : '';
- if (['center', 'justify', 'left', 'right'].indexOf(align) === -1) {
- return [null, null];
- }
-
- return ['<div style="text-align: ' + align + '">', '</div>'];
- },
-
- /**
- * Converts [attach] into an <img> or to plain text if attachment is a non-image.
- *
- * @param {array<mixed>} stack linear list of BBCode tags and regular strings
- * @param {object} item current BBCode tag object
- * @param {int} index current stack index representing `item`
- * @returns {array} first item represents the opening tag, the second the closing one
- */
- _convertAttachment: function(stack, item, index) {
- var attachmentId = 0, attributes = item.attributes, length = attributes.length;
- if (!_options.attachments.url) {
- length = 0;
- }
- else if (length > 0) {
- attachmentId = ~~attributes[0];
- if (!objOwns(_options.attachments.images, attachmentId)) {
- length = 0;
- }
- }
-
- if (length === 0) {
- return [null, null];
- }
-
- var maxHeight = ~~_options.attachments.images[attachmentId].height;
- var maxWidth = ~~_options.attachments.images[attachmentId].width;
- var styles = ['max-height: ' + maxHeight + 'px', 'max-width: ' + maxWidth + 'px'];
-
- if (length > 1) {
- if (item.attributes[1] === 'left' || attributes[1] === 'right') {
- styles.push('float: ' + attributes[1]);
- styles.push('margin: ' + (attributes[1] === 'left' ? '0 15px 7px 0' : '0 0 7px 15px'));
- }
- }
-
- var width, baseUrl = _options.attachments.thumbnailUrl;
- if (length > 2) {
- width = ~~attributes[2] || 0;
- if (width) {
- if (width > maxWidth) width = maxWidth;
-
- styles.push('width: ' + width + 'px');
- baseUrl = _options.attachments.url;
- }
- }
-
- return [
- '<img src="' + baseUrl.replace(/987654321/, attachmentId) + '" class="redactorEmbeddedAttachment redactorDisableResize" data-attachment-id="' + attachmentId + '"' + (styles.length ? ' style="' + styles.join(';') + '"' : '') + '>',
- ''
- ];
- },
-
- /**
- * Converts [code] to <div class="codeBox">.
- *
- * @param {array<mixed>} stack linear list of BBCode tags and regular strings
- * @param {object} item current BBCode tag object
- * @param {int} index current stack index representing `item`
- * @returns {array} first item represents the opening tag, the second the closing one
- */
- _convertCode: 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] = '';
- }
- }
-
- 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) + '">',
- '</ol></div></div>'
- ];
- },
-
- /**
- * Converts [color] to <span style="color: ...">.
- *
- * @param {array<mixed>} stack linear list of BBCode tags and regular strings
- * @param {object} item current BBCode tag object
- * @param {int} index current stack index representing `item`
- * @returns {array} first item represents the opening tag, the second the closing one
- */
- _convertColor: function(stack, item, index) {
- if (!item.attributes.length || !item.attributes[0].match(/^[a-z0-9#]+$/i)) {
- return [null, null];
- }
-
- return ['<span style="color: ' + StringUtil.escapeHTML(item.attributes[0]) + '">', '</span>'];
- },
-
- /**
- * Converts [email] to <a href="mailto: ...">.
- *
- * @param {array<mixed>} stack linear list of BBCode tags and regular strings
- * @param {object} item current BBCode tag object
- * @param {int} index current stack index representing `item`
- * @returns {array} first item represents the opening tag, the second the closing one
- */
- _convertEmail: function(stack, item, index) {
- var email = '';
- if (item.attributes.length) {
- email = item.attributes[0];
- }
- else {
- var element;
- for (var i = index + 1; i < item.pair; i++) {
- element = stack[i];
-
- if (typeof element === 'object') {
- email = '';
- break;
- }
- else {
- email += element;
- }
- }
-
- // no attribute present and element is empty, handle as plain text
- if (email.trim() === '') {
- return [null, null];
- }
- }
-
- return ['<a href="mailto:' + StringUtil.escapeHTML(email) + '">', '</a>'];
- },
-
- /**
- * Converts [img] to <img>.
- *
- * @param {array<mixed>} stack linear list of BBCode tags and regular strings
- * @param {object} item current BBCode tag object
- * @param {int} index current stack index representing `item`
- * @returns {array} first item represents the opening tag, the second the closing one
- */
- _convertImage: function(stack, item, index) {
- var float = 'none', source = '', width = 0;
-
- switch (item.attributes.length) {
- case 0:
- if (index + 1 < item.pair && typeof stack[index + 1] === 'string') {
- source = stack[index + 1];
- stack[index + 1] = '';
- }
- else {
- // [img] without attributes and content, discard
- return '';
- }
- break;
-
- case 1:
- source = item.attributes[0];
- break;
-
- case 2:
- source = item.attributes[0];
- float = item.attributes[1];
- break;
-
- case 3:
- source = item.attributes[0];
- float = item.attributes[1];
- width = ~~item.attributes[2];
- break;
- }
-
- if (float !== 'left' && float !== 'right') float = 'none';
-
- var styles = [];
- if (width > 0) {
- styles.push('width: ' + width + 'px');
- }
-
- if (float !== 'none') {
- styles.push('float: ' + float);
- styles.push('margin: ' + (float === 'left' ? '0 15px 7px 0' : '0 0 7px 15px'));
- }
-
- return ['<img src="' + StringUtil.escapeHTML(source) + '"' + (styles.length ? ' style="' + styles.join(';') + '"' : '') + '>', ''];
- },
-
- /**
- * Converts [list] to <ol> or <ul>.
- *
- * @param {array<mixed>} stack linear list of BBCode tags and regular strings
- * @param {object} item current BBCode tag object
- * @param {int} index current stack index representing `item`
- * @returns {array} first item represents the opening tag, the second the closing one
- */
- _convertList: function(stack, item, index) {
- var type = (item.attributes.length) ? item.attributes[0] : '';
-
- // replace list items
- for (var i = index + 1; i < item.pair; i++) {
- if (typeof stack[i] === 'string') {
- stack[i] = stack[i].replace(/\[\*\]/g, '<li>');
- }
- }
-
- if (type == '1' || type === 'decimal') {
- return ['<ol>', '</ol>'];
- }
-
- if (type.length && type.match(/^(?:none|circle|square|disc|decimal|lower-roman|upper-roman|decimal-leading-zero|lower-greek|lower-latin|upper-latin|armenian|georgian)$/)) {
- return ['<ul style="list-style-type: ' + type + '">', '</ul>'];
- }
-
- return ['<ul>', '</ul>'];
- },
-
- /**
- * Converts [quote] to <blockquote>.
- *
- * @param {array<mixed>} stack linear list of BBCode tags and regular strings
- * @param {object} item current BBCode tag object
- * @param {int} index current stack index representing `item`
- * @returns {array} first item represents the opening tag, the second the closing one
- */
- _convertQuote: function(stack, item, index) {
- var author = '', link = '';
- if (item.attributes.length > 1) {
- author = item.attributes[0];
- link = item.attributes[1];
- }
- else if (item.attributes.length === 1) {
- author = item.attributes[0];
- }
-
- // get rid of the trailing newline for quote content
- for (var i = item.pair - 1; i > index; i--) {
- if (typeof stack[i] === 'string') {
- stack[i] = stack[i].replace(/\n$/, '');
- break;
- }
- }
-
- var header = '';
- if (author) {
- if (link) header = '<a href="' + StringUtil.escapeHTML(link) + '" tabindex="-1">';
- header += Language.get('wcf.bbcode.quote.title.javascript', { quoteAuthor: author.replace(/\\'/g, "'") });
- if (link) header += '</a>';
- }
- else {
- header = '<small>' + Language.get('wcf.bbcode.quote.title.clickToSet') + '</small>';
- }
-
- return [
- '<blockquote class="quoteBox container containerPadding quoteBoxSimple" cite="' + StringUtil.escapeHTML(link) + '" data-author="' + StringUtil.escapeHTML(author) + '">'
- + '<header contenteditable="false">'
- + '<h3>'
- + header
- + '</h3>'
- + '<a class="redactorQuoteEdit"></a>'
- + '</header>'
- + '<div>\u200b',
- '</div></blockquote>'
- ];
- },
-
- /**
- * Converts smiley codes into <img>.
- *
- * @param {array<mixed>} stack linear list of BBCode tags and regular strings
- */
- _convertSmilies: function(stack) {
- var altValue, item, regexp;
- for (var i = 0, length = stack.length; i < length; i++) {
- item = stack[i];
-
- if (typeof item === 'string') {
- for (var smileyCode in __REDACTOR_SMILIES) {
- if (objOwns(__REDACTOR_SMILIES, smileyCode)) {
- altValue = smileyCode.replace(/</g, '<').replace(/>/g, '>');
- regexp = new RegExp('(\\s|^)' + StringUtil.escapeRegExp(smileyCode) + '(?=\\s|$)', 'gi');
- item = item.replace(regexp, '$1<img src="' + __REDACTOR_SMILIES[smileyCode] + '" class="smiley" alt="' + altValue + '">');
- }
- }
-
- stack[i] = item;
- }
- else if (__REDACTOR_SOURCE_BBCODES.indexOf(item.name) !== -1) {
- // skip processing content
- i = item.pair;
- }
- }
- },
-
- /**
- * Converts [size] to <span style="font-size: ...">.
- *
- * @param {array<mixed>} stack linear list of BBCode tags and regular strings
- * @param {object} item current BBCode tag object
- * @param {int} index current stack index representing `item`
- * @returns {array} first item represents the opening tag, the second the closing one
- */
- _convertSize: function(stack, item, index) {
- if (!item.attributes.length || ~~item.attributes[0] === 0) {
- return [null, null];
- }
-
- return ['<span style="font-size: ' + ~~item.attributes[0] + 'pt">', '</span>'];
- },
-
- /**
- * Converts [url] to <a>.
- *
- * @param {array<mixed>} stack linear list of BBCode tags and regular strings
- * @param {object} item current BBCode tag object
- * @param {int} index current stack index representing `item`
- * @returns {array} first item represents the opening tag, the second the closing one
- */
- _convertUrl: function(stack, item, index) {
- // ignore url bbcode without arguments
- if (!item.attributes.length) {
- return [null, null];
- }
-
- return ['<a href="' + StringUtil.escapeHTML(item.attributes[0]) + '">', '</a>'];
- }
- };
-
- return BbcodeToHtml;
-});