/** * SCEditor BBCode Plugin * http://www.samclarke.com/2011/07/sceditor/ * * Copyright (C) 2011-2012, Sam Clarke (samclarke.com) * * SCEditor is licensed under the MIT license: * http://www.opensource.org/licenses/mit-license.php * * @author Sam Clarke * @version 1.3.7 * @requires jQuery */ // ==ClosureCompiler== // @output_file_name jquery.sceditor.min.js // @compilation_level SIMPLE_OPTIMIZATIONS // ==/ClosureCompiler== /*jshint smarttabs: true, jquery: true, eqnull:true, curly: false */ (function($) { 'use strict'; /** * BBCode plugin for SCEditor * * @param {Element} el The textarea to be converted * @return {Object} options * @class sceditorBBCodePlugin * @name jQuery.sceditorBBCodePlugin */ $.sceditorBBCodePlugin = function(element, options) { var base = this; /** * Private methods * @private */ var init, buildBbcodeCache, handleStyles, handleTags, formatString, getStyle, wrapInDivs, isEmpty, mergeTextModeCommands; base.bbcodes = $.sceditorBBCodePlugin.bbcodes; /** * cache of all the tags pointing to their bbcodes to enable * faster lookup of which bbcode a tag should have * @private */ var tagsToBbcodes = {}; /** * Same as tagsToBbcodes but instead of HTML tags it's styles * @private */ var stylesToBbcodes = {}; /** * Allowed children of specific HTML tags. Empty array if no * children other than text nodes are allowed * @private */ var validChildren = { list: ['li'], table: ['tr'], tr: ['td', 'th'], code: ['br', 'p', 'div'], youtube: [] }; /** * Initializer * @private * @name sceditorBBCodePlugin.init */ init = function() { $.data(element, "sceditorbbcode", base); base.options = $.extend({}, $.sceditor.defaultOptions, options); // build the BBCode cache buildBbcodeCache(); (new $.sceditor(element, $.extend({}, base.options, { getHtmlHandler: base.getHtmlHandler, getTextHandler: base.getTextHandler, commands: mergeTextModeCommands() }) )); }; mergeTextModeCommands = function() { var merge = { bold: { txtExec: ["[b]", "[/b]"] }, italic: { txtExec: ["[i]", "[/i]"] }, underline: { txtExec: ["[u]", "[/u]"] }, strike: { txtExec: ["[s]", "[/s]"] }, subscript: { txtExec: ["[sub]", "[/sub]"] }, superscript: { txtExec: ["[sup]", "[/sup]"] }, left: { txtExec: ["[left]", "[/left]"] }, center: { txtExec: ["[center]", "[/center]"] }, right: { txtExec: ["[right]", "[/right]"] }, justify: { txtExec: ["[justify]", "[/justify]"] }, ftp: { txtExec: ["[ftp]", "[/ftp]"] }, tt: { txtExec: ["[tt]", "[/tt]"] }, glow: { txtExec: ["[glow=red,2,300]", "[/glow]"] }, move: { txtExec: ["[[move]", "[/move]"] }, shadow: { txtExec: ["[shadow=red,left]", "[/shadow]"] }, pre: { txtExec: ["[pre]", "[/pre]"] }, // @todo: check tooltip font: { txtExec: function(caller) { var editor = this; $.sceditor.command.get('font')._dropDown( editor, caller, function(fontName) { editor.insertText("[font="+fontName+"]", "[/font]"); } ); } }, // @todo: check overlapping size: { txtExec: function(caller) { var editor = this; $.sceditor.command.get('size')._dropDown( editor, caller, function(fontSize) { editor.insertText("[size="+fontSize+"]", "[/size]"); } ); } }, color: { txtExec: function(caller) { var editor = this; $.sceditor.command.get('color')._dropDown( editor, caller, function(color) { editor.insertText("[color="+color+"]", "[/color]"); } ); } }, bulletlist: { txtExec: ["[list]\n[li]", "[/li]\n[li][/li]\n[/list]"] }, orderedlist: { txtExec: ["[list type=decimal]\n[li]", "[/li]\n[li][/li]\n[/list]"] }, table: { txtExec: ["[table]\n[tr]\n[td]", "[/td]\n[/tr]\n[/table]"] }, horizontalrule: { txtExec: ["[hr]"] }, code: { txtExec: ["[code]", "[/code]"] }, image: { txtExec: function(caller, selected) { var url = prompt(this._("Enter the image URL:"), selected); if(url) this.insertText("[img]" + url + "[/img]"); } }, email: { txtExec: function(caller, selected) { var display = selected && selected.indexOf('@') > -1 ? null : selected, email = prompt(this._("Enter the e-mail address:"), (display ? '' : selected)); if (email) { var text = prompt(this._("Enter the displayed text:"), display || email) || email; this.insertText("[email=" + email + "]" + text + "[/email]"); } } }, link: { txtExec: function(caller, selected) { var display = selected && selected.indexOf('http://') > -1 ? null : selected, url = prompt(this._("Enter URL:"), (display ? 'http://' : selected)); if (url) { var text = prompt(this._("Enter the displayed text:"), display || url) || url; this.insertText("[url=" + url + "]" + text + "[/url]"); } } }, quote: { txtExec: ["[quote]", "[/quote]"] }, youtube: { txtExec: function(caller) { var editor = this; $.sceditor.command.get('youtube')._dropDown( editor, caller, function(id) { editor.insertText("[youtube]" + id + "[/youtube]"); } ); } }, rtl: { txtExec: ["[rtl]", "[/rtl]"] }, ltr: { txtExec: ["[ltr]", "[/ltr]"] } }; return $.extend(true, {}, merge, $.sceditor.commands); }; /** * Populates tagsToBbcodes and stylesToBbcodes to enable faster lookups * * @private */ buildBbcodeCache = function() { $.each(base.bbcodes, function(bbcode, info) { if(typeof base.bbcodes[bbcode].tags !== "undefined") $.each(base.bbcodes[bbcode].tags, function(tag, values) { var isBlock = !!base.bbcodes[bbcode].isBlock; tagsToBbcodes[tag] = (tagsToBbcodes[tag] || {}); tagsToBbcodes[tag][isBlock] = (tagsToBbcodes[tag][isBlock] || {}); tagsToBbcodes[tag][isBlock][bbcode] = values; }); if(typeof base.bbcodes[bbcode].styles !== "undefined") $.each(base.bbcodes[bbcode].styles, function(style, values) { var isBlock = !!base.bbcodes[bbcode].isBlock; stylesToBbcodes[isBlock] = (stylesToBbcodes[isBlock] || {}); stylesToBbcodes[isBlock][style] = (stylesToBbcodes[isBlock][style] || {}); stylesToBbcodes[isBlock][style][bbcode] = values; }); }); }; getStyle = function(element, property) { var name = $.camelCase(property), $elm, ret, dir; // add exception for align if("text-align" === property) { $elm = $(element); if(!element.style) return null; if($elm.parent().css(property) !== $elm.css(property) && $elm.css('display') === "block" && !$elm.is('hr') && !$elm.is('th')) ret = $elm.css(property); // IE changes text-align to the same as direction so skip unless overried by user dir = element.style.direction; if(dir && ((/right/i.test(ret) && dir === 'rtl') || (/left/i.test(ret) && dir === 'ltr'))) return null; return ret; } if(element.style) return element.style[name]; return null; }; isEmpty = function(element) { var childNodes = element.childNodes, i = childNodes.length; if(element.nodeValue && /\S|\u00A0/.test(element.nodeValue)) return false; if(childNodes.length === 0 || (childNodes.length === 1 && (/br/i.test(childNodes[0].nodeName) || isEmpty(childNodes[0])))) return true; while(i--) if(!isEmpty(childNodes[i])) return false; return true; }; /** * Checks if any bbcode styles match the elements styles * * @private * @return string Content with any matching bbcode tags wrapped around it. * @Private */ handleStyles = function(element, content, blockLevel) { var elementPropVal; // convert blockLevel to boolean blockLevel = !!blockLevel; if(!stylesToBbcodes[blockLevel]) return content; $.each(stylesToBbcodes[blockLevel], function(property, bbcodes) { elementPropVal = getStyle(element[0], property); // if the parent has the same style use that instead of this one // so you dont end up with [i]parent[i]child[/i][/i] if(!elementPropVal || getStyle(element.parent()[0], property) === elementPropVal) return; $.each(bbcodes, function(bbcode, values) { if(!/\S|\u00A0/.test(content) && !base.bbcodes[bbcode].allowsEmpty && isEmpty(element[0])) return; if(!values || $.inArray(elementPropVal.toString(), values) > -1) { if($.isFunction(base.bbcodes[bbcode].format)) content = base.bbcodes[bbcode].format.call(base, element, content); else content = formatString(base.bbcodes[bbcode].format, content); } }); }); return content; }; /** * Handles a HTML tag and finds any matching bbcodes * * @private * @param jQuery element element The element to convert * @param string content The Tags text content * @param bool blockLevel If to convert block level tags * @return string Content with any matching bbcode tags wrapped around it. * @Private */ handleTags = function(element, content, blockLevel) { var tag = element[0].nodeName.toLowerCase(); // convert blockLevel to boolean blockLevel = !!blockLevel; if(tagsToBbcodes[tag] && tagsToBbcodes[tag][blockLevel]) { // loop all bbcodes for this tag $.each(tagsToBbcodes[tag][blockLevel], function(bbcode, bbcodeAttribs) { if(!/\S|\u00A0/.test(content) && !base.bbcodes[bbcode].allowsEmpty && isEmpty(element[0])) return; // if the bbcode requires any attributes then check this has // all needed if(bbcodeAttribs) { var runBbcode = false; // loop all the bbcode attribs $.each(bbcodeAttribs, function(attrib, values) { // if the element has the bbcodes attribute and the bbcode attribute // has values check one of the values matches if(!element.attr(attrib) || (values && $.inArray(element.attr(attrib), values) < 0)) return; // break this loop as we have matched this bbcode runBbcode = true; return false; }); if(!runBbcode) return; } if($.isFunction(base.bbcodes[bbcode].format)) content = base.bbcodes[bbcode].format.call(base, element, content); else content = formatString(base.bbcodes[bbcode].format, content); }); } // add newline after paragraph elements p and div (WebKit uses divs) and br tags if(blockLevel && /^(br|div|p)$/.test(tag)) { // Only treat divs/p as a newline if their last child was not a new line. if(!(/^(div|p)$/i.test(tag) && element[0].lastChild && element[0].lastChild.nodeName.toLowerCase() === "br")) content += "\n"; // needed for browsers that enter textnode then when return is pressed put the rest in a div, i.e.: // text
line 2
if("br" !== tag && !$.sceditor.dom.isInline(element[0].parentNode) && element[0].previousSibling && element[0].previousSibling.nodeType === 3) { content = "\n" + content; } } return content; }; /** * Formats a string in the format * {0}, {1}, {2}, ect. with the params provided * @private * @return string * @Private */ formatString = function() { var args = arguments; return args[0].replace(/\{(\d+)\}/g, function(str, p1) { return typeof args[p1-0+1] !== "undefined" ? args[p1-0+1] : '{' + p1 + '}'; }); }; /** * Removes any leading or trailing quotes ('") * * @return string * @memberOf jQuery.sceditorBBCodePlugin.prototype */ base.stripQuotes = function(str) { return str.replace(/^(["'])(.*?)\1$/, "$2"); }; /** * Converts HTML to BBCode * @param string html Html string, this function ignores this, it works off domBody * @param HtmlElement domBody Editors dom body object to convert * @return string BBCode which has been converted from HTML * @memberOf jQuery.sceditorBBCodePlugin.prototype */ base.getHtmlHandler = function(html, domBody) { $.sceditor.dom.removeWhiteSpace(domBody[0]); return $.trim(base.elementToBbcode(domBody)); }; /** * Converts a HTML dom element to BBCode starting from * the innermost element and working backwards * * @private * @param HtmlElement element The element to convert to BBCode * @param array vChildren Valid child tags allowed * @return string BBCode * @memberOf jQuery.sceditorBBCodePlugin.prototype */ base.elementToBbcode = function($element) { return (function toBBCode(node, vChildren) { var ret = ''; $.sceditor.dom.traverse(node, function(node) { var $node = $(node), curTag = '', tag = node.nodeName.toLowerCase(), vChild = validChildren[tag], isValidChild = true; if(typeof vChildren === 'object') { isValidChild = $.inArray(tag, vChildren) > -1; // if this tag is one of the parents allowed children // then set this tags allowed children to whatever it allows, // otherwise set to what the parent allows if(!isValidChild) vChild = vChildren; } // 3 is text element if(node.nodeType !== 3) { // skip ignored elments if($node.hasClass("sceditor-ignore")) return; // don't loop inside iframes if(tag !== 'iframe') curTag = toBBCode(node, vChild); if(isValidChild) { // code tags should skip most styles if(!$node.is('code')) { // handle inline bbcodes curTag = handleStyles($node, curTag); curTag = handleTags($node, curTag); // handle blocklevel bbcodes curTag = handleStyles($node, curTag, true); } ret += handleTags($node, curTag, true); } else ret += curTag; } else if(node.wholeText && (!node.previousSibling || node.previousSibling.nodeType !== 3)) { if($(node).parents('code').length === 0) ret += node.wholeText.replace(/ +/g, " "); else ret += node.wholeText; } else if(!node.wholeText) ret += node.nodeValue; }, false, true); return ret; }($element.get(0))); }; /** * Converts BBCode to HTML * * @param {String} text * @param {Bool} isFragment * @return {String} HTML * @memberOf jQuery.sceditorBBCodePlugin.prototype */ base.getTextHandler = function(text, isFragment) { var oldText, replaceBBCodeFunc, // Previous bbcodeRegex = /\[([^\[\s=]*?)(?:([\s=][^\[]*?))?\]((?:[\s\S(?!=\[\\\1)](?!\[\1))*?)\[\/(\1)\]/g, bbcodeRegex = /\[([^\[\s=]+)(?:([\s=][^\[\]]+))?\]((?:[\s\S](?!\[\1))*?)\[\/(\1)\]/gi, atribsRegex = /(\S+)=((?:(?:(["'])(?:\\\3|[^\3])*?\3))|(?:[^'"\s]+))/gi; replaceBBCodeFunc = function(str, bbcode, attrs, content) { var attrsMap = {}, matches; bbcode = bbcode.toLowerCase(); if(attrs) { attrs = $.trim(attrs); // if only one attribute then remove the = from the start and strip any quotes if((attrs.charAt(0) === "=" && (attrs.split("=").length - 1) <= 1) || bbcode === 'url') attrsMap.defaultattr = base.stripQuotes(attrs.substr(1)); else { if(attrs.charAt(0) === "=") attrs = "defaultattr" + attrs; if (typeof base.bbcodes[bbcode] != 'undefined' && typeof base.bbcodes[bbcode].attrs == 'function') { var declaredAttrs = base.bbcodes[bbcode].attrs(); var attrArray = new Array; var compatArray = new Array; for (var i = 0; i < declaredAttrs.length; i++) { var attrPos = attrs.indexOf(declaredAttrs[i]); if (attrPos != -1) { attrArray[attrPos] = [declaredAttrs[i], attrPos + declaredAttrs[i].length + 1]; } } for (var attrElem in attrArray) compatArray.push(attrArray[attrElem]); for (var i = 0; i < compatArray.length; i++) { if (typeof compatArray[i+1] != 'undefined') attrsMap[compatArray[i][0].toLowerCase()] = attrs.substr(compatArray[i][1], attrs.indexOf(compatArray[i+1][0]) - compatArray[i][1]).trim(); else attrsMap[compatArray[i][0].toLowerCase()] = attrs.substr(compatArray[i][1], attrs.length); } } else while((matches = atribsRegex.exec(attrs))) attrsMap[matches[1].toLowerCase()] = base.stripQuotes(matches[2]); } } if(!base.bbcodes[bbcode]) return str; if($.isFunction(base.bbcodes[bbcode].html)) return base.bbcodes[bbcode].html.call(base, bbcode, attrsMap, content); else return formatString(base.bbcodes[bbcode].html, content); }; text = text.replace(/&/g, "&") .replace(//g, ">") .replace(/\r/g, "") .replace(/(\[\/?(?:left|center|right|justify|align|rtl|ltr)\])\n/g, "$1") .replace(/\n/g, "
"); while(text !== oldText) { oldText = text; text = text.replace(bbcodeRegex, replaceBBCodeFunc); } // As hr is the only bbcode not to have a start and end tag it's // just being replace here instead of adding support for it above. text = text.replace(/\[hr\]/gi, "
") .replace(/\[\*\]/gi, "
  • "); // replace multi-spaces which are not inside tags with a non-breaking space // to preserve them. Otherwise they will just be converted to 1! text = text.replace(/ {2}(?=([^<\>]*?<|[^<\>]*?$))/g, "  "); return wrapInDivs(text, isFragment); }; /** * Wraps divs around inline HTML. Needed for IE * * @param string html * @return string HTML * @private */ wrapInDivs = function(html, excludeFirstLast) { var d = document, inlineFrag = d.createDocumentFragment(), outputDiv = d.createElement('div'), tmpDiv = d.createElement('div'), div, node, next, nodeName; $(tmpDiv).hide().appendTo(d.body); tmpDiv.innerHTML = html; node = tmpDiv.firstChild; while(node) { next = node.nextSibling; nodeName = node.nodeName.toLowerCase(); if((node.nodeType === 1 && !$.sceditor.dom.isInline(node)) || nodeName === "br") { if(inlineFrag.childNodes.length > 0 || nodeName === "br") { div = d.createElement('div'); div.appendChild(inlineFrag); // Putting BR in a div in IE9 causes it to do a double line break, // as much as I hate browser UA sniffing, to do feature detection would // be more code than it's worth for this specific bug. if(nodeName === "br" && !$.sceditor.ie) div.appendChild(d.createElement('br')); // If it's an empty DIV and in compatibility mode is below IE8 then // we must add a non-breaking space to the div otherwise the div // will be collapsed. Adding a BR works but when you press enter // to make a newline it suddenly goes back to the normal IE div // behaviour and creates two lines, one for the newline and one // for the BR. I'm sure there must be a better fix but I've yet to // find one. // Cannot do zoom: 1; or set a height on the div to fix it as that // causes resize handles to be added to the div when it's clicked on/ if(!div.childNodes.length && (d.documentMode && d.documentMode < 8 || $.sceditor.ie < 8)) div.appendChild(d.createTextNode('\u00a0')); outputDiv.appendChild(div); inlineFrag = d.createDocumentFragment(); } if(nodeName !== "br") outputDiv.appendChild(node); } else inlineFrag.appendChild(node); node = next; } if(inlineFrag.childNodes.length > 0) { div = d.createElement('div'); div.appendChild(inlineFrag); outputDiv.appendChild(div); } // needed for paste, the first shouldn't be wrapped in a div if(excludeFirstLast) { node = outputDiv.firstChild; if(node && node.nodeName.toLowerCase() === "div") { while((next = node.firstChild)) outputDiv.insertBefore(next, node); if($.sceditor.ie >= 9) outputDiv.insertBefore(d.createElement('br'), node); outputDiv.removeChild(node); } node = outputDiv.lastChild; if(node && node.nodeName.toLowerCase() === "div") { while((next = node.firstChild)) outputDiv.insertBefore(next, node); if($.sceditor.ie >= 9) outputDiv.insertBefore(d.createElement('br'), node); outputDiv.removeChild(node); } } $(tmpDiv).remove(); return outputDiv.innerHTML; }; init(); }; $.sceditorBBCodePlugin.bbcodes = { // START_COMMAND: Bold b: { tags: { b: null, strong: null }, styles: { // 401 is for FF 3.5 "font-weight": ["bold", "bolder", "401", "700", "800", "900"] }, format: "[b]{0}[/b]", html: '{0}' }, // END_COMMAND // START_COMMAND: Italic i: { tags: { i: null, em: null }, styles: { "font-style": ["italic", "oblique"] }, format: "[i]{0}[/i]", html: '{0}' }, // END_COMMAND // START_COMMAND: Underline u: { tags: { u: null }, styles: { "text-decoration": ["underline"] }, format: "[u]{0}[/u]", html: '{0}' }, // END_COMMAND // START_COMMAND: Strikethrough s: { tags: { s: null, strike: null }, styles: { "text-decoration": ["line-through"] }, format: "[s]{0}[/s]", html: '{0}' }, // END_COMMAND // START_COMMAND: Subscript sub: { tags: { sub: null }, format: "[sub]{0}[/sub]", html: '{0}' }, // END_COMMAND // START_COMMAND: Superscript sup: { tags: { sup: null }, format: "[sup]{0}[/sup]", html: '{0}' }, // END_COMMAND // START_COMMAND: Font font: { tags: { font: { face: null } }, styles: { "font-family": null }, format: function(element, content) { if(element[0].nodeName.toLowerCase() === "font" && element.attr('face')) return '[font=' + this.stripQuotes(element.attr('face')) + ']' + content + '[/font]'; return '[font=' + this.stripQuotes(element.css('font-family')) + ']' + content + '[/font]'; }, html: function(element, attrs, content) { return '' + content + ''; } }, // END_COMMAND // START_COMMAND: Size size: { tags: { font: { size: null } }, styles: { "font-size": null }, format: function(element, content) { var fontSize = element.css('fontSize'), size = 1; if(element.attr('size')) size = element.attr('size'); // Most browsers return px value but IE returns 1-7 else if(fontSize.indexOf("px") > -1) { // convert size to an int fontSize = fontSize.replace("px", "") - 0; if(fontSize > 12) size = 2; if(fontSize > 15) size = 3; if(fontSize > 17) size = 4; if(fontSize > 23) size = 5; if(fontSize > 31) size = 6; if(fontSize > 47) size = 7; } else size = fontSize; return '[size=' + size + ']' + content + '[/size]'; }, html: function(element, attrs, content) { return '' + content + ''; } }, // END_COMMAND // START_COMMAND: Color color: { tags: { font: { color: null } }, styles: { color: null }, format: function(element, content) { /** * Converts CSS rgb value into hex * @private * @return string Hex color */ var rgbToHex = function(rgbStr) { var m; function toHex(n) { n = parseInt(n,10); if(isNaN(n)) return "00"; n = Math.max(0,Math.min(n,255)).toString(16); return n.length<2 ? '0'+n : n; } // rgb(n,n,n); if((m = rgbStr.match(/rgb\((\d+),\s*?(\d+),\s*?(\d+)\)/i))) return '#' + toHex(m[1]) + toHex(m[2]-0) + toHex(m[3]-0); // expand shorthand if((m = rgbStr.match(/#([0-f])([0-f])([0-f])\s*?$/i))) return '#' + m[1] + m[1] + m[2] + m[2] + m[3] + m[3]; return rgbStr; }; var color = element.css('color'); if(element[0].nodeName.toLowerCase() === "font" && element.attr('color')) color = element.attr('color'); color = rgbToHex(color); return '[color=' + color + ']' + content + '[/color]'; }, html: function(element, attrs, content) { return '' + content + ''; } }, black: { html: '{0}' }, blue: { html: '{0}' }, green: { html: '{0}' }, red: { html: '{0}' }, white: { html: '{0}' }, // END_COMMAND // START_COMMAND: Lists list: { isBlock: true, html: function(element, attrs, content) { var style = ''; var code = 'ul'; if (attrs.type) style = ' style="list-style-type: ' + attrs.type + '"'; return '<' + code + style + '>' + content + ''; } }, ul: { tags: { ul: null }, isBlock: true, format: function(element, content) { if ($(element[0]).css('list-style-type') == 'disc') return '[list]' + content + '[/list]'; else return '[list type=' + $(element[0]).css('list-style-type') + ']' + content + '[/list]'; }, html: '' }, ol: { tags: { ol: null }, isBlock: true, format: '[list type=decimal]{0}[/list]', html: '
      {0}
    ' }, li: { tags: { li: null }, format: "[li]{0}[/li]", html: '
  • {0}
  • ' }, "*": { html: '
  • {0}
  • ' }, // END_COMMAND // START_COMMAND: Table table: { tags: { table: null }, format: "[table]{0}[/table]", html: '{0}
    ' }, tr: { tags: { tr: null }, format: "[tr]{0}[/tr]", html: '{0}' }, th: { tags: { th: null }, isBlock: true, format: "[th]{0}[/th]", html: '{0}' }, td: { tags: { td: null }, isBlock: true, format: "[td]{0}[/td]", html: '{0}
    ' }, // END_COMMAND // START_COMMAND: Emoticons emoticon: { allowsEmpty: true, tags: { img: { src: null, "data-sceditor-emoticon": null } }, format: function(element, content) { if (element.attr('data-sceditor-emoticon') == '') return content; return element.attr('data-sceditor-emoticon') + content; }, html: '{0}' }, // END_COMMAND // START_COMMAND: Horizontal Rule horizontalrule: { allowsEmpty: true, tags: { hr: null }, format: "[hr]{0}", html: "
    " }, // END_COMMAND // START_COMMAND: Image img: { allowsEmpty: true, tags: { img: { src: null } }, format: function(element, content) { var attribs = '', style = function(name) { return element.style ? element.style[name] : null; }; // check if this is an emoticon image if(typeof element.attr('data-sceditor-emoticon') !== "undefined") return content; var width = ' width=' + $(element).width(); var height = ' height=' + $(element).height(); var alt = $(element).attr('alt') != undefined ? ' alt=' + $(element).attr('alt').php_unhtmlspecialchars() : ''; return '[img' + width + height + alt + ']' + element.attr('src') + '[/img]'; }, attrs: function () { return ['alt', 'width', 'height']; }, html: function(element, attrs, content) { var attribs = "", parts; // handle [img width=340 height=240]url[/img] if(typeof attrs.width !== "undefined") attribs += ' width="' + attrs.width + '"'; if(typeof attrs.height !== "undefined") attribs += ' height="' + attrs.height + '"'; if(typeof attrs.alt !== "undefined") attribs += ' alt="' + attrs.alt + '"'; return ''; } }, // END_COMMAND // START_COMMAND: URL url: { allowsEmpty: true, tags: { a: { href: null } }, format: function(element, content) { // make sure this link is not an e-mail, if it is return e-mail BBCode if(element.attr('href').substr(0, 7) === 'mailto:') return '[email=' + element.attr('href').substr(7) + ']' + content + '[/email]'; // make sure this link is not an ftp, if it is return ftp BBCode else if(element.attr('href').substr(0, 3) === 'ftp') return '[ftp=' + element.attr('href') + ']' + content + '[/ftp]'; if(element.attr('target') !== undefined) return '[url=' + decodeURI(element.attr('href')) + ']' + content + '[/url]'; else return '[iurl=' + decodeURI(element.attr('href')) + ']' + content + '[/iurl]'; }, html: function(element, attrs, content) { if(typeof attrs.defaultattr === "undefined" || attrs.defaultattr.length === 0) attrs.defaultattr = content; return '' + content + ''; } }, iurl: { html: function(element, attrs, content) { if(typeof attrs.defaultattr === "undefined" || attrs.defaultattr.length === 0) attrs.defaultattr = content; return '' + content + ''; } }, ftp: { html: function(element, attrs, content) { if(typeof attrs.defaultattr === "undefined" || attrs.defaultattr.length === 0) attrs.defaultattr = content; return '' + content + ''; } }, // END_COMMAND // START_COMMAND: E-mail email: { html: function(element, attrs, content) { if(typeof attrs.defaultattr === "undefined") attrs.defaultattr = content; return '' + content + ''; } }, // END_COMMAND // START_COMMAND: Quote quote: { tags: { blockquote: null, cite: null }, isBlock: true, format: function(element, content) { var author = ''; var date = ''; var link = ''; if (element[0].tagName.toLowerCase() == 'cite') return ''; if ($(element).attr('author')) author = ' author=' + $(element).attr('author').php_unhtmlspecialchars(); if ($(element).attr('date')) date = ' date=' + $(element).attr('date'); if ($(element).attr('link')) link = ' link=' + $(element).attr('link'); return '[quote' + author + date + link + ']' + content + '[/quote]'; }, attrs: function () { return ['author', 'date', 'link']; }, html: function(element, attrs, content) { var attr_author = '', author = ''; var attr_date = '', sDate = ''; var attr_link = '', link = ''; if(typeof attrs.author !== "undefined" && attrs.author) { attr_author = attrs.author; author = bbc_quote_from + ': ' + attr_author; } // Links could be in the form: link=topic=71.msg201#msg201 that would fool javascript, so we need a workaround // Probably no more necessary for (var key in attrs) { if (key.substr(0, 4) == 'link' && attrs.hasOwnProperty(key)) { var attr_link = key.length > 4 ? key.substr(5) + '=' + attrs[key] : attrs[key]; link = attr_link.substr(0, 7) == 'http://' ? attr_link : smf_scripturl + '?' + attr_link; author = author == '' ? '' + bbc_quote_from + ': ' + link + '' : '' + author + ''; } } if(typeof attrs.date !== "undefined" && attrs.date) { attr_date = attrs.date; sDate = '' + new Date(attrs.date * 1000) + ''; } if (author == '' && sDate == '') author = bbc_quote; else author += ' ' + bbc_search_on; content = '
    ' + author + ' ' + sDate + '' + content + '
    '; return content; } }, // END_COMMAND // START_COMMAND: Code code: { tags: { code: null }, isBlock: true, format: function(element, content) { if ($(element[0]).hasClass('php')) return '[php]' + content.replace('[', '[') + '[/php]'; var from = ''; if ($(element).children("cite:first").length === 1) { from = $(element).children("cite:first").text(); $(element).attr({'from': from.php_htmlspecialchars()}); from = '=' + from; content = ''; $(element).children("cite:first").remove(); content = this.elementToBbcode($(element)); } else { if ($(element).attr('from') != undefined) { from = '=' + $(element).attr('from').php_unhtmlspecialchars(); } } return '[code' + from + ']' + content.replace('[', '[') + '[/code]'; }, html: function(element, attrs, content) { var from = ''; if(typeof attrs.defaultattr !== "undefined") from = '' + attrs.defaultattr + ''; return '' + from + content.replace('[', '[') + '' } }, php: { isBlock: true, format: "[php]{0}[/php]", html: '{0}' }, // END_COMMAND // START_COMMAND: Left left: { styles: { "text-align": ["left", "-webkit-left", "-moz-left", "-khtml-left"] }, isBlock: true, format: "[left]{0}[/left]", html: '
    {0}
    ' }, // END_COMMAND // START_COMMAND: Centre center: { styles: { "text-align": ["center", "-webkit-center", "-moz-center", "-khtml-center"] }, isBlock: true, format: "[center]{0}[/center]", html: '
    {0}
    ' }, // END_COMMAND // START_COMMAND: Right right: { styles: { "text-align": ["right", "-webkit-right", "-moz-right", "-khtml-right"] }, isBlock: true, format: "[right]{0}[/right]", html: '
    {0}
    ' }, // END_COMMAND // START_COMMAND: Justify justify: { styles: { "text-align": ["justify", "-webkit-justify", "-moz-justify", "-khtml-justify"] }, isBlock: true, format: "[justify]{0}[/justify]", html: '
    {0}
    ' }, // END_COMMAND // START_COMMAND: YouTube youtube: { allowsEmpty: true, tags: { iframe: { 'data-youtube-id': null } }, format: function(element, content) { if(!element.attr('data-youtube-id')) return content; return '[youtube]' + element.attr('data-youtube-id') + '[/youtube]'; }, html: '' }, // END_COMMAND // START_COMMAND: Rtl rtl: { styles: { "direction": ["rtl"] }, format: "[rtl]{0}[/rtl]", html: '
    {0}
    ' }, // END_COMMAND // START_COMMAND: Ltr ltr: { styles: { "direction": ["ltr"] }, format: "[ltr]{0}[/ltr]", html: '
    {0}
    ' }, // END_COMMAND abbr: { tags: { abbr: { title: null } }, format: function(element, content) { return '[abbr=' + element.attr('title') + ']' + content + '[/abbr]'; }, html: function(element, attrs, content) { if(typeof attrs.defaultattr === "undefined" || attrs.defaultattr.length === 0) return content; return '' + content + ''; } }, acronym: { tags: { acronym: { title: null } }, format: function(element, content) { return '[acronym=' + element.attr('title') + ']' + content + '[/acronym]'; }, html: function(element, attrs, content) { if(typeof attrs.defaultattr === "undefined" || attrs.defaultattr.length === 0) return content; return '' + content + ''; } }, bdo: { tags: { bdo: { dir: null } }, format: function(element, content) { return '[bdo=' + element.attr('dir') + ']' + content + '[/bdo]'; }, html: function(element, attrs, content) { if(typeof attrs.defaultattr === "undefined" || attrs.defaultattr.length === 0) return content; if (attrs.defaultattr != 'rtl' && attrs.defaultattr != 'ltr') return '[bdo=' + attrs.defaultattr + ']' + content + '[/bdo]'; return '' + content + ''; } }, tt: { tags: { tt: null }, format: "[tt]{0}[/tt]", html: '{0}' }, pre: { tags: { pre: null }, isBlock: true, format: "[pre]{0}[/pre]", html: "
    {0}
    \n" }, move: { tags: { marquee: null }, format: "[move]{0}[/move]", html: '{0}' }, // this is here so that commands above can be removed // without having to remove the , after the last one. // Needed for IE. ignore: {} }; /** * Static BBCode helper class * @class command * @name jQuery.sceditorBBCodePlugin.bbcode */ $.sceditorBBCodePlugin.bbcode = /** @lends jQuery.sceditorBBCodePlugin.bbcode */ { /** * Gets a BBCode * * @param {String} name * @return {Object|null} * @since v1.3.5 */ get: function(name) { return $.sceditorBBCodePlugin.bbcodes[name] || null; }, /** *

    Adds a BBCode to the parser or updates an exisiting * BBCode if a BBCode with the specified name already exists.

    * * @param {String} name * @param {Object} bbcode * @return {this|false} Returns false if name or bbcode is false * @since v1.3.5 */ set: function(name, bbcode) { if(!name || !bbcode) return false; // merge any existing command properties bbcode = $.extend($.sceditorBBCodePlugin.bbcodes[name] || {}, bbcode); bbcode.remove = function() { $.sceditorBBCodePlugin.bbcode.remove(name); }; $.sceditorBBCodePlugin.bbcodes[name] = bbcode; return this; }, /** * Removes a BBCode * * @param {String} name * @return {this} * @since v1.3.5 */ remove: function(name) { if($.sceditorBBCodePlugin.bbcodes[name]) delete $.sceditorBBCodePlugin.bbcodes[name]; return this; } }; /** * Checks if a command with the specified name exists * * @param string name * @return bool * @deprecated Since v1.3.5 * @memberOf jQuery.sceditorBBCodePlugin */ $.sceditorBBCodePlugin.commandExists = function(name) { return !!$.sceditorBBCodePlugin.bbcode.get(name); }; /** * Adds/updates a BBCode. * * @param String name The BBCode name * @param Object tags Any html tags this bbcode applies to, i.e. strong for [b] * @param Object styles Any style properties this applies to, i.e. font-weight for [b] * @param String|Function format Function or string to convert the element into BBCode * @param String|Function html String or function to format the BBCode back into HTML. * @param bool allowsEmpty If this BBCodes is allowed to be empty, e.g. [b][/b] * @return Bool * @deprecated Since v1.3.5 * @memberOf jQuery.sceditorBBCodePlugin */ $.sceditorBBCodePlugin.setCommand = function(name, tags, styles, format, html, allowsEmpty, isBlock) { return $.sceditorBBCodePlugin.bbcode.set(name, { tags: tags || {}, styles: styles || {}, allowsEmpty: allowsEmpty, isBlock: isBlock, format: format, html: html }); }; $.fn.sceditorBBCodePlugin = function(options) { if((!options || !options.runWithoutWysiwygSupport) && !$.sceditor.isWysiwygSupported) return; return this.each(function() { (new $.sceditorBBCodePlugin(this, options)); }); }; })(jQuery);