|
@@ -1,11 +1,15 @@
|
|
|
/**
|
|
|
- * SCEditor BBCode Plugin v1.3.4
|
|
|
- * http://www.samclarke.com/2011/07/sceditor/
|
|
|
+ * 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.6
|
|
|
+ * @requires jQuery
|
|
|
*/
|
|
|
|
|
|
// ==ClosureCompiler==
|
|
@@ -18,6 +22,14 @@
|
|
|
(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;
|
|
|
|
|
@@ -32,6 +44,7 @@
|
|
|
formatString,
|
|
|
getStyle,
|
|
|
wrapInDivs,
|
|
|
+ isEmpty,
|
|
|
mergeTextModeCommands;
|
|
|
|
|
|
base.bbcodes = $.sceditorBBCodePlugin.bbcodes;
|
|
@@ -49,7 +62,7 @@
|
|
|
* @private
|
|
|
*/
|
|
|
var stylesToBbcodes = {};
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* Allowed children of specific HTML tags. Empty array if no
|
|
|
* children other than text nodes are allowed
|
|
@@ -66,10 +79,12 @@
|
|
|
|
|
|
/**
|
|
|
* Initializer
|
|
|
+ * @private
|
|
|
+ * @name sceditorBBCodePlugin.init
|
|
|
*/
|
|
|
init = function() {
|
|
|
$.data(element, "sceditorbbcode", base);
|
|
|
-
|
|
|
+
|
|
|
base.options = $.extend({}, $.sceditor.defaultOptions, options);
|
|
|
|
|
|
// build the BBCode cache
|
|
@@ -83,10 +98,8 @@
|
|
|
})
|
|
|
));
|
|
|
};
|
|
|
-
|
|
|
+
|
|
|
mergeTextModeCommands = function() {
|
|
|
- // TODO: use selection as display text if is one.
|
|
|
- // TODO: add translations of the prompts
|
|
|
var merge = {
|
|
|
bold: { txtExec: ["[b]", "[/b]"] },
|
|
|
italic: { txtExec: ["[i]", "[/i]"] },
|
|
@@ -98,149 +111,95 @@
|
|
|
center: { txtExec: ["[center]", "[/center]"] },
|
|
|
right: { txtExec: ["[right]", "[/right]"] },
|
|
|
justify: { txtExec: ["[justify]", "[/justify]"] },
|
|
|
- bulletlist: { txtExec: ["[list]\n[li]", "[/li]\n[li][/li]\n[/list]"] },
|
|
|
- orderedlist: { txtExec: ["[list type=decimal]\n[li]", "[/li]\n[li][/li]\n[/list]"] },
|
|
|
ftp: { txtExec: ["[ftp]", "[/ftp]"] },
|
|
|
tt: { txtExec: ["[tt]", "[/tt]"] },
|
|
|
glow: { txtExec: ["[glow=red,2,300]", "[/glow]"] },
|
|
|
shadow: { txtExec: ["[shadow=red,left]", "[/shadow]"] },
|
|
|
pre: { txtExec: ["[pre]", "[/pre]"] },
|
|
|
- font: {
|
|
|
- txtExec: function (caller) {
|
|
|
- var editor = this,
|
|
|
- fonts = editor.options.fonts.split(","),
|
|
|
- content = $("<div />"),
|
|
|
- clickFunc = function (e) {
|
|
|
- editor.textEditorInsertText("[font=" + $(this).data('sceditor-font') + "]", "[/font]");
|
|
|
- editor.closeDropDown(true);
|
|
|
- e.preventDefault();
|
|
|
- };
|
|
|
-
|
|
|
- for (var i=0; i < fonts.length; i++) {
|
|
|
- content.append(
|
|
|
- $('<a class="sceditor-font-option" href="#"><font face="' + fonts[i] + '">' + fonts[i] + '</font></a>')
|
|
|
- .data('sceditor-font', fonts[i])
|
|
|
- .click(clickFunc));
|
|
|
+ // @todo: check tooltip
|
|
|
+ font: { txtExec: function(caller) {
|
|
|
+ var editor = this;
|
|
|
+
|
|
|
+ $.sceditor.command.get('font')._dropDown(
|
|
|
+ editor,
|
|
|
+ caller,
|
|
|
+ function(fontName) {
|
|
|
+ editor.insertText("[font="+fontName+"]", "[/font]");
|
|
|
}
|
|
|
-
|
|
|
- editor.createDropDown(caller, "font-picker", content);
|
|
|
- },
|
|
|
- tooltip: "Font Name"
|
|
|
- },
|
|
|
- size: {
|
|
|
- txtExec: function (caller) {
|
|
|
- var sizes = [0, 8, 10, 12, 14, 18, 24, 36];
|
|
|
- var editor = this,
|
|
|
- content = $("<div />"),
|
|
|
- clickFunc = function (e) {
|
|
|
- editor.textEditorInsertText("[size=" + sizes[$(this).data('sceditor-fontsize')] + "pt]", "[/size]");
|
|
|
- editor.closeDropDown(true);
|
|
|
- e.preventDefault();
|
|
|
- };
|
|
|
-
|
|
|
- for (var i=1; i<= 7; i++) {
|
|
|
- content.append(
|
|
|
- $('<a class="sceditor-fontsize-option" style="line-height:' + sizes[i] + 'pt" href="#"><font size="' + i + '">' + sizes[i] + 'pt</font></a>')
|
|
|
- .data('sceditor-fontsize', i)
|
|
|
- .click(clickFunc));
|
|
|
+ );
|
|
|
+ } },
|
|
|
+ // @todo: check overlapping
|
|
|
+ size: { txtExec: function(caller) {
|
|
|
+ var editor = this;
|
|
|
+
|
|
|
+ $.sceditor.command.get('size')._dropDown(
|
|
|
+ editor,
|
|
|
+ caller,
|
|
|
+ function(fontSize) {
|
|
|
+ editor.insertText("[size="+fontSize+"]", "[/size]");
|
|
|
}
|
|
|
-
|
|
|
- editor.createDropDown(caller, "fontsize-picker", content);
|
|
|
- },
|
|
|
- tooltip: "Font Size"
|
|
|
- },
|
|
|
- color: {
|
|
|
- txtExec: function (caller) {
|
|
|
- var editor = this,
|
|
|
- genColor = {r: 255, g: 255, b: 255},
|
|
|
- content = $("<div />"),
|
|
|
- colorColumns = this.options.colors?this.options.colors.split("|"):new Array(21),
|
|
|
- // IE is slow at string concation so use an array
|
|
|
- html = [],
|
|
|
- htmlIndex = 0;
|
|
|
-
|
|
|
- for (var i=0; i < colorColumns.length; ++i) {
|
|
|
- var colors = (typeof colorColumns[i] !== "undefined")?colorColumns[i].split(","):new Array(21);
|
|
|
-
|
|
|
- html[htmlIndex++] = '<div class="sceditor-color-column">';
|
|
|
-
|
|
|
- for (var x=0; x < colors.length; ++x) {
|
|
|
- // use pre defined colour if can otherwise use the generated color
|
|
|
- var color = (typeof colors[x] !== "undefined")?colors[x]:"#" + genColor.r.toString(16) + genColor.g.toString(16) + genColor.b.toString(16);
|
|
|
-
|
|
|
- html[htmlIndex++] = '<a href="#" class="sceditor-color-option" style="background-color: '+color+'" data-color="'+color+'"></a>';
|
|
|
-
|
|
|
- // calculate the next generated color
|
|
|
- if(x%5===0)
|
|
|
- genColor = {r: genColor.r, g: genColor.g-51, b: 255};
|
|
|
- else
|
|
|
- genColor = {r: genColor.r, g: genColor.g, b: genColor.b-51};
|
|
|
- }
|
|
|
-
|
|
|
- html[htmlIndex++] = '</div>';
|
|
|
-
|
|
|
- // calculate the next generated color
|
|
|
- if(i%5===0)
|
|
|
- genColor = {r: genColor.r-51, g: 255, b: 255};
|
|
|
- else
|
|
|
- genColor = {r: genColor.r, g: 255, b: 255};
|
|
|
+ );
|
|
|
+ } },
|
|
|
+ color: { txtExec: function(caller) {
|
|
|
+ var editor = this;
|
|
|
+
|
|
|
+ $.sceditor.command.get('color')._dropDown(
|
|
|
+ editor,
|
|
|
+ caller,
|
|
|
+ function(color) {
|
|
|
+ editor.insertText("[color="+color+"]", "[/color]");
|
|
|
}
|
|
|
-
|
|
|
- content.append(html.join(''))
|
|
|
- .find('a')
|
|
|
- .click(function (e) {
|
|
|
- editor.textEditorInsertText("[color=" + $(this).attr('data-color') + "]", "[/color]");
|
|
|
- editor.closeDropDown(true);
|
|
|
- e.preventDefault();
|
|
|
- });
|
|
|
-
|
|
|
- editor.createDropDown(caller, "color-picker", content);
|
|
|
- },
|
|
|
- tooltip: "Font 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() {
|
|
|
- var url = prompt(this._("Enter the images URL:"));
|
|
|
-
|
|
|
+ image: { txtExec: function(caller, selected) {
|
|
|
+ var url = prompt(this._("Enter the image URL:"), selected);
|
|
|
+
|
|
|
if(url)
|
|
|
- this.textEditorInsertText("[img]" + url + "[/img]");
|
|
|
+ this.insertText("[img]" + url + "[/img]");
|
|
|
} },
|
|
|
- email: { txtExec: function() {
|
|
|
- var email = prompt(this._("Enter the e-mail address:"), "@"),
|
|
|
- text = prompt(this._("Enter the displayed text:"), email) || email;
|
|
|
-
|
|
|
+ email: { txtExec: function(caller, selected) {
|
|
|
+ var display = selected && selected.indexOf('@') > -1 ? null : selected,
|
|
|
+ email = prompt(this._("Enter the e-mail address:"), (display ? '' : selected)),
|
|
|
+ text = prompt(this._("Enter the displayed text:"), display || email) || email;
|
|
|
+
|
|
|
if(email)
|
|
|
- this.textEditorInsertText("[email=" + email + "]" + text + "[/email]");
|
|
|
+ this.insertText("[email=" + email + "]" + text + "[/email]");
|
|
|
} },
|
|
|
- link: { txtExec: function() {
|
|
|
- var url = prompt(this._("Enter the links URL:"), "http://"),
|
|
|
- text = prompt(this._("Enter the displayed text:"), url) || url;
|
|
|
-
|
|
|
+ link: { txtExec: function(caller, selected) {
|
|
|
+ var display = selected && selected.indexOf('http://') > -1 ? null : selected,
|
|
|
+ url = prompt(this._("Enter URL:"), (display ? 'http://' : selected)),
|
|
|
+ text = prompt(this._("Enter the displayed text:"), display || url) || url;
|
|
|
+
|
|
|
if(url)
|
|
|
- this.textEditorInsertText("[url=" + url + "]" + text + "[/url]");
|
|
|
+ this.insertText("[url=" + url + "]" + text + "[/url]");
|
|
|
} },
|
|
|
quote: { txtExec: ["[quote]", "[/quote]"] },
|
|
|
- youtube: { txtExec: function() {
|
|
|
- var url = prompt(this._("Enter the YouTube video URL or ID:"));
|
|
|
-
|
|
|
- if(url)
|
|
|
- {
|
|
|
- if(url.indexOf("://") > -1)
|
|
|
- url = url.replace(/^[^v]+v.(.{11}).*/,"$1");
|
|
|
-
|
|
|
- this.textEditorInsertText("[youtube]" + url + "[/youtube]");
|
|
|
- }
|
|
|
- } }
|
|
|
+ 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() {
|
|
@@ -262,59 +221,80 @@
|
|
|
});
|
|
|
});
|
|
|
};
|
|
|
-
|
|
|
+
|
|
|
getStyle = function(element, property) {
|
|
|
var name = $.camelCase(property),
|
|
|
- $elm;
|
|
|
+ $elm, ret, dir;
|
|
|
|
|
|
// add exception for align
|
|
|
if("text-align" === property)
|
|
|
{
|
|
|
$elm = $(element);
|
|
|
-
|
|
|
+
|
|
|
if($elm.parent().css(property) !== $elm.css(property) &&
|
|
|
$elm.css('display') === "block" && !$elm.is('hr') && !$elm.is('th'))
|
|
|
- return $elm.css(property);
|
|
|
+ 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)
|
|
|
+ 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,
|
|
|
- tag = element[0].nodeName.toLowerCase();
|
|
|
+ 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(elementPropVal == null || elementPropVal === "")
|
|
|
- return;
|
|
|
|
|
|
// 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(getStyle(element.parent()[0], property) === elementPropVal)
|
|
|
+ if(!elementPropVal || getStyle(element.parent()[0], property) === elementPropVal)
|
|
|
return;
|
|
|
|
|
|
$.each(bbcodes, function(bbcode, values) {
|
|
|
- if((element[0].childNodes.length === 0 || element[0].childNodes[0].nodeName.toLowerCase() === "br") &&
|
|
|
- !base.bbcodes[bbcode].allowsEmpty)
|
|
|
+ if(!/\S|\u00A0/.test(content) && !base.bbcodes[bbcode].allowsEmpty && isEmpty(element[0]))
|
|
|
return;
|
|
|
-
|
|
|
- if(values === null || $.inArray(elementPropVal.toString(), values) > -1) {
|
|
|
+
|
|
|
+ if(!values || $.inArray(elementPropVal.toString(), values) > -1) {
|
|
|
if($.isFunction(base.bbcodes[bbcode].format))
|
|
|
content = base.bbcodes[bbcode].format.call(base, element, content);
|
|
|
else
|
|
@@ -328,41 +308,37 @@
|
|
|
|
|
|
/**
|
|
|
* 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(!base.bbcodes[bbcode].allowsEmpty &&
|
|
|
- (element[0].childNodes.length === 0 || (element[0].childNodes[0].nodeName.toLowerCase() === "br" && element[0].childNodes.length === 1)) )
|
|
|
+ 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 !== null) {
|
|
|
+ if(bbcodeAttribs) {
|
|
|
var runBbcode = false;
|
|
|
|
|
|
// loop all the bbcode attribs
|
|
|
$.each(bbcodeAttribs, function(attrib, values)
|
|
|
{
|
|
|
- // check if has the bbcodes attrib
|
|
|
- if(element.attr(attrib) == null)
|
|
|
- return;
|
|
|
-
|
|
|
// if the element has the bbcodes attribute and the bbcode attribute
|
|
|
// has values check one of the values matches
|
|
|
- if(values !== null && $.inArray(element.attr(attrib), values) < 0)
|
|
|
+ if(!element.attr(attrib) || (values && $.inArray(element.attr(attrib), values) < 0))
|
|
|
return;
|
|
|
|
|
|
// break this loop as we have matched this bbcode
|
|
@@ -380,21 +356,17 @@
|
|
|
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))
|
|
|
{
|
|
|
- var parentChildren = element[0].parentNode.childNodes;
|
|
|
-
|
|
|
- // if it's a <p><br /></p> the paragraph will put the newline so skip the br
|
|
|
- if(!("br" === tag && parentChildren.length === 1) &&
|
|
|
- !("br" === tag && parentChildren[parentChildren.length-1] === element[0])) {
|
|
|
+ // 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<div>line 2</div>
|
|
|
- if("br" !== tag && !$.sceditor.dom.isInline(element.parent()[0]) && element[0].previousSibling &&
|
|
|
+ if("br" !== tag && !$.sceditor.dom.isInline(element[0].parentNode) && element[0].previousSibling &&
|
|
|
element[0].previousSibling.nodeType === 3) {
|
|
|
content = "\n" + content;
|
|
|
}
|
|
@@ -408,13 +380,14 @@
|
|
|
* {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 + '}';
|
|
|
+ return typeof args[p1-0+1] !== "undefined" ?
|
|
|
+ args[p1-0+1] :
|
|
|
+ '{' + p1 + '}';
|
|
|
});
|
|
|
};
|
|
|
|
|
@@ -422,31 +395,34 @@
|
|
|
* Removes any leading or trailing quotes ('")
|
|
|
*
|
|
|
* @return string
|
|
|
+ * @memberOf jQuery.sceditorBBCodePlugin.prototype
|
|
|
*/
|
|
|
base.stripQuotes = function(str) {
|
|
|
- return str.replace(/^["']+/, "").replace(/["']+$/, "");
|
|
|
+ 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
|
|
|
+ * @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) {
|
|
@@ -458,7 +434,7 @@
|
|
|
tag = node.nodeName.toLowerCase(),
|
|
|
vChild = validChildren[tag],
|
|
|
isValidChild = true;
|
|
|
-
|
|
|
+
|
|
|
if(typeof vChildren === 'object')
|
|
|
{
|
|
|
isValidChild = $.inArray(tag, vChildren) > -1;
|
|
@@ -469,7 +445,7 @@
|
|
|
if(!isValidChild)
|
|
|
vChild = vChildren;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 3 is text element
|
|
|
if(node.nodeType !== 3)
|
|
|
{
|
|
@@ -480,7 +456,7 @@
|
|
|
// don't loop inside iframes
|
|
|
if(tag !== 'iframe')
|
|
|
curTag = toBBCode(node, vChild);
|
|
|
-
|
|
|
+
|
|
|
if(isValidChild)
|
|
|
{
|
|
|
// code tags should skip most styles
|
|
@@ -489,11 +465,11 @@
|
|
|
// 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
|
|
@@ -509,39 +485,44 @@
|
|
|
else if(!node.wholeText)
|
|
|
ret += node.nodeValue;
|
|
|
}, false, true);
|
|
|
-
|
|
|
+
|
|
|
return ret;
|
|
|
}($element.get(0)));
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* Converts BBCode to HTML
|
|
|
- *
|
|
|
+ *
|
|
|
* @param {String} text
|
|
|
- * @param {Bool} isPaste
|
|
|
+ * @param {Bool} isFragment
|
|
|
* @return {String} HTML
|
|
|
+ * @memberOf jQuery.sceditorBBCodePlugin.prototype
|
|
|
*/
|
|
|
- base.getTextHandler = function(text, isPaste) {
|
|
|
+ base.getTextHandler = function(text, isFragment) {
|
|
|
+
|
|
|
var oldText, replaceBBCodeFunc,
|
|
|
- bbcodeRegex = /\[([^\[\s=]*?)(?:([\s=][^\[]*?))?\]((?:[\s\S(?!=\[\\\1)](?!\[\1))*?)\[\/(\1)\]/g,
|
|
|
+// Previous bbcodeRegex = /\[([^\[\s=]*?)(?:([\s=][^\[]*?))?\]((?:[\s\S(?!=\[\\\1)](?!\[\1))*?)\[\/(\1)\]/g,
|
|
|
+ bbcodeRegex = /\[([^\[\s=]+)(?:([\s=][^\[\]]+))?\]((?:[\s\S](?!\[\1))*?)\[\/(\1)\]/g,
|
|
|
atribsRegex = /(\S+)=((?:(?:(["'])(?:\\\3|[^\3])*?\3))|(?:[^'"\s]+))/g;
|
|
|
|
|
|
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));
|
|
|
+ attrsMap.defaultattr = base.stripQuotes(attrs.substr(1));
|
|
|
else
|
|
|
{
|
|
|
if(attrs.charAt(0) === "=")
|
|
|
- attrs = "defaultAttr" + attrs;
|
|
|
+ attrs = "defaultattr" + attrs;
|
|
|
|
|
|
if (typeof base.bbcodes[bbcode].attrs == 'function')
|
|
|
{
|
|
@@ -586,7 +567,7 @@
|
|
|
.replace(/</g, "<")
|
|
|
.replace(/>/g, ">")
|
|
|
.replace(/\r/g, "")
|
|
|
- .replace(/(\[\/?(?:left|center|right|justify)\])\n/g, "$1")
|
|
|
+ .replace(/(\[\/?(?:left|center|right|justify|align|rtl|ltr)\])\n/g, "$1")
|
|
|
.replace(/\n/g, "<br />");
|
|
|
|
|
|
while(text !== oldText)
|
|
@@ -597,20 +578,22 @@
|
|
|
|
|
|
// 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, "<hr>");
|
|
|
-
|
|
|
+ text = text.replace(/\[hr\]/gi, "<hr>")
|
|
|
+ .replace(/\[\*\]/gi, "<li>");
|
|
|
+
|
|
|
// 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, isPaste);
|
|
|
+
|
|
|
+ return wrapInDivs(text, isFragment);
|
|
|
};
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* Wraps divs around inline HTML. Needed for IE
|
|
|
- *
|
|
|
+ *
|
|
|
* @param string html
|
|
|
* @return string HTML
|
|
|
+ * @private
|
|
|
*/
|
|
|
wrapInDivs = function(html, excludeFirstLast)
|
|
|
{
|
|
@@ -619,10 +602,10 @@
|
|
|
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)
|
|
|
{
|
|
@@ -635,33 +618,45 @@
|
|
|
{
|
|
|
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 || $.sceditor.ie < 9))
|
|
|
+ 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 gose 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)
|
|
|
{
|
|
@@ -670,13 +665,13 @@
|
|
|
{
|
|
|
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")
|
|
|
{
|
|
@@ -685,7 +680,7 @@
|
|
|
|
|
|
if($.sceditor.ie >= 9)
|
|
|
outputDiv.insertBefore(d.createElement('br'), node);
|
|
|
-
|
|
|
+
|
|
|
outputDiv.removeChild(node);
|
|
|
}
|
|
|
}
|
|
@@ -696,7 +691,7 @@
|
|
|
|
|
|
init();
|
|
|
};
|
|
|
-
|
|
|
+
|
|
|
$.sceditorBBCodePlugin.bbcodes = {
|
|
|
// START_COMMAND: Bold
|
|
|
b: {
|
|
@@ -791,7 +786,7 @@
|
|
|
return '[font=' + this.stripQuotes(element.css('font-family')) + ']' + content + '[/font]';
|
|
|
},
|
|
|
html: function(element, attrs, content) {
|
|
|
- return '<font face="' + attrs.defaultAttr + '">' + content + '</font>';
|
|
|
+ return '<font face="' + attrs.defaultattr + '">' + content + '</font>';
|
|
|
}
|
|
|
},
|
|
|
// END_COMMAND
|
|
@@ -810,8 +805,10 @@
|
|
|
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
|
|
|
- if(fontSize.indexOf("px") > -1) {
|
|
|
+ else if(fontSize.indexOf("px") > -1) {
|
|
|
// convert size to an int
|
|
|
fontSize = fontSize.replace("px", "") - 0;
|
|
|
|
|
@@ -834,7 +831,7 @@
|
|
|
return '[size=' + size + ']' + content + '[/size]';
|
|
|
},
|
|
|
html: function(element, attrs, content) {
|
|
|
- return '<font size="' + attrs.defaultAttr + '">' + content + '</font>';
|
|
|
+ return '<font size="' + attrs.defaultattr + '">' + content + '</font>';
|
|
|
}
|
|
|
},
|
|
|
// END_COMMAND
|
|
@@ -857,38 +854,38 @@
|
|
|
*/
|
|
|
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 '<font color="' + attrs.defaultAttr + '">' + content + '</font>';
|
|
|
+ return '<font color="' + attrs.defaultattr + '">' + content + '</font>';
|
|
|
}
|
|
|
},
|
|
|
black: {
|
|
@@ -1024,6 +1021,11 @@
|
|
|
}
|
|
|
},
|
|
|
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;
|
|
@@ -1038,7 +1040,7 @@
|
|
|
return ['alt', 'width', 'height'];
|
|
|
},
|
|
|
html: function(element, attrs, content) {
|
|
|
- var attribs = "";
|
|
|
+ var attribs = "", parts;
|
|
|
|
|
|
// handle [img width=340 height=240]url[/img]
|
|
|
if(typeof attrs.width !== "undefined")
|
|
@@ -1099,10 +1101,10 @@
|
|
|
// START_COMMAND: E-mail
|
|
|
email: {
|
|
|
html: function(element, attrs, content) {
|
|
|
- if(typeof attrs.defaultAttr === "undefined")
|
|
|
- attrs.defaultAttr = content;
|
|
|
+ if(typeof attrs.defaultattr === "undefined")
|
|
|
+ attrs.defaultattr = content;
|
|
|
|
|
|
- return '<a href="mailto:' + attrs.defaultAttr + '">' + content + '</a>';
|
|
|
+ return '<a href="mailto:' + attrs.defaultattr + '">' + content + '</a>';
|
|
|
}
|
|
|
},
|
|
|
// END_COMMAND
|
|
@@ -1303,6 +1305,27 @@
|
|
|
},
|
|
|
// END_COMMAND
|
|
|
|
|
|
+
|
|
|
+ // START_COMMAND: Rtl
|
|
|
+ rtl: {
|
|
|
+ styles: {
|
|
|
+ "direction": ["rtl"]
|
|
|
+ },
|
|
|
+ format: "[rtl]{0}[/rtl]",
|
|
|
+ html: '<div style="direction: rtl">{0}</div>'
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+
|
|
|
+ // START_COMMAND: Ltr
|
|
|
+ ltr: {
|
|
|
+ styles: {
|
|
|
+ "direction": ["ltr"]
|
|
|
+ },
|
|
|
+ format: "[ltr]{0}[/ltr]",
|
|
|
+ html: '<div style="direction: ltr">{0}</div>'
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+
|
|
|
abbr: {
|
|
|
tags: {
|
|
|
abbr: {
|
|
@@ -1381,51 +1404,104 @@
|
|
|
// 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;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * <p>Adds a BBCode to the parser or updates an exisiting
|
|
|
+ * BBCode if a BBCode with the specified name already exists.</p>
|
|
|
+ *
|
|
|
+ * @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 typeof $.sceditorBBCodePlugin.bbcodes[name] !== "undefined";
|
|
|
+ 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]
|
|
|
+ * @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) {
|
|
|
- if(!name || !format || !html)
|
|
|
- return false;
|
|
|
-
|
|
|
- if(!$.sceditorBBCodePlugin.commandExists(name))
|
|
|
- $.sceditorBBCodePlugin.bbcodes[name] = {};
|
|
|
-
|
|
|
- $.sceditorBBCodePlugin.bbcodes[name].format = format;
|
|
|
- $.sceditorBBCodePlugin.bbcodes[name].html = html;
|
|
|
-
|
|
|
- if(tags)
|
|
|
- $.sceditorBBCodePlugin.bbcodes[name].tags = tags;
|
|
|
-
|
|
|
- if(styles)
|
|
|
- $.sceditorBBCodePlugin.bbcodes[name].styles = styles;
|
|
|
-
|
|
|
- if(allowsEmpty)
|
|
|
- $.sceditorBBCodePlugin.bbcodes[name].allowsEmpty = allowsEmpty;
|
|
|
-
|
|
|
- return true;
|
|
|
+ $.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));
|
|
|
});
|