lainchan/js/sceditor/development/plugins/xhtml.js
2017-09-03 23:15:56 +09:00

1320 lines
26 KiB
JavaScript

/**
* SCEditor XHTML Plugin
* http://www.sceditor.com/
*
* Copyright (C) 2011-2013, Sam Clarke (samclarke.com)
*
* SCEditor is licensed under the MIT license:
* http://www.opensource.org/licenses/mit-license.php
*
* @author Sam Clarke
* @requires jQuery
*/
/*global prompt: true*/
(function ($) {
'use strict';
var SCEditor = $.sceditor;
var sceditorPlugins = SCEditor.plugins;
var dom = SCEditor.dom;
var defaultCommandsOverrides = {
bold: {
txtExec: [
'<strong>',
'</strong>'
]
},
italic: {
txtExec: [
'<em>',
'</em>'
]
},
underline: {
txtExec: [
'<span style="text-decoration: underline;">',
'</span>'
]
},
strike: {
txtExec: [
'<span style="text-decoration: line-through;">',
'</span>'
]
},
subscript: {
txtExec: [
'<sub>',
'</sub>'
]
},
superscript: {
txtExec: [
'<sup>',
'</sup>'
]
},
left: {
txtExec: [
'<div style="text-align: left;">',
'</div>'
]
},
center: {
txtExec: [
'<div style="text-align: center;">',
'</div>'
]
},
right: {
txtExec: [
'<div style="text-align: right;">',
'</div>'
]
},
justify: {
txtExec: [
'<div style="text-align: justify;">',
'</div>'
]
},
font: {
txtExec: function (caller) {
var editor = this;
SCEditor.command.get('font')._dropDown(
editor,
caller,
function (fontName) {
editor.insertText('<span style="font-family: ' +
fontName + ';">', '</span>');
}
);
}
},
size: {
txtExec: function (caller) {
var editor = this;
SCEditor.command.get('size')._dropDown(
editor,
caller,
function (fontSize) {
editor.insertText('<span style="font-size: ' +
fontSize + ';">', '</span>');
}
);
}
},
color: {
txtExec: function (caller) {
var editor = this;
SCEditor.command.get('color')._dropDown(
editor,
caller,
function (color) {
editor.insertText('<span style="color: ' +
color + ';">', '</span>');
}
);
}
},
bulletlist: {
txtExec: [
'<ul><li>',
'</li></ul>'
]
},
orderedlist: {
txtExec: [
'<ol><li>',
'</li></ol>'
]
},
table: {
txtExec: [
'<table><tr><td>',
'</td></tr></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 src="' + url + '" />');
}
}
},
email: {
txtExec: function (caller, sel) {
var email, text,
display = sel && sel.indexOf('@') > -1 ? null : sel;
email = prompt(
this._('Enter the e-mail address:'),
(display ? '' : sel)
);
text = prompt(
this._('Enter the displayed text:'),
display || email
) || email;
if (email) {
this.insertText(
'<a href="mailto:' + email + '">' + text + '</a>'
);
}
}
},
link: {
txtExec: function (caller, sel) {
var display = sel && sel.indexOf('http://') > -1 ? null : sel,
url = prompt(this._('Enter URL:'),
(display ? 'http://' : sel)),
text = prompt(this._('Enter the displayed text:'),
display || url) || url;
if (url) {
this.insertText(
'<a href="' + url + '">' + text + '</a>'
);
}
}
},
quote: {
txtExec: [
'<blockquote>',
'</blockquote>'
]
},
youtube: {
txtExec: function (caller) {
var editor = this;
SCEditor.command.get('youtube')._dropDown(
editor,
caller,
function (id) {
editor.insertText(
'<iframe width="560" height="315" ' +
'src="https://www.youtube.com/embed/{id}?' +
'wmode=opaque" data-youtube-id="' + id + '" ' +
'frameborder="0" allowfullscreen></iframe>'
);
}
);
}
},
rtl: {
txtExec: [
'<div stlye="direction: rtl;">',
'</div>'
]
},
ltr: {
txtExec: [
'<div stlye="direction: ltr;">',
'</div>'
]
}
};
/**
* XHTMLSerializer part of the XHTML plugin.
*
* @class XHTMLSerializer
* @name jQuery.sceditor.XHTMLSerializer
* @since v1.4.1
*/
SCEditor.XHTMLSerializer = function () {
var base = this;
var opts = {
indentStr: '\t'
};
/**
* Array containing the output, used as it's faster
* than string concatenation in slow browsers.
* @type {Array}
* @private
*/
var outputStringBuilder = [];
/**
* Current indention level
* @type {Number}
* @private
*/
var currentIndent = 0;
/**
* @private
*/
var escapeEntites,
trim,
serializeNode,
handleDoc,
handleElement,
handleCdata,
handleComment,
handleText,
output,
canIndent;
// TODO: use escape.entities
/**
* Escapes XHTML entities
*
* @param {String} str
* @return {String}
* @private
*/
escapeEntites = function (str) {
var entites = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;'
};
return !str ? '' : str.replace(/[&<>"]/g, function (entity) {
return entites[entity] || entity;
});
};
/**
* @param {string} str
* @return {string}
* @private
*/
trim = function (str) {
return str
// New lines will be shown as spaces so just convert to spaces.
.replace(/[\r\n]/, ' ')
.replace(/[^\S|\u00A0]+/g, ' ');
};
/**
* Serializes a node to XHTML
*
* @param {Node} node Node to serialize
* @param {Boolean} onlyChildren If to only serialize the nodes
* children and not the node
* itself
* @return {String} The serialized node
* @name serialize
* @memberOf jQuery.sceditor.XHTMLSerializer.prototype
* @since v1.4.1
*/
base.serialize = function (node, onlyChildren) {
outputStringBuilder = [];
if (onlyChildren) {
node = node.firstChild;
while (node) {
serializeNode(node);
node = node.nextSibling;
}
} else {
serializeNode(node);
}
return outputStringBuilder.join('');
};
/**
* Serializes a node to the outputStringBuilder
*
* @param {Node} node
* @return {Void}
* @private
*/
serializeNode = function (node, parentIsPre) {
switch (node.nodeType) {
case 1: // element
var tagName = node.nodeName.toLowerCase();
// IE comment
if (tagName === '!') {
handleComment(node);
} else {
handleElement(node, parentIsPre);
}
break;
case 3: // text
handleText(node, parentIsPre);
break;
case 4: // cdata section
handleCdata(node);
break;
case 8: // comment
handleComment(node);
break;
case 9: // document
case 11: // document fragment
handleDoc(node);
break;
// Ignored types
case 2: // attribute
case 5: // entity ref
case 6: // entity
case 7: // processing instruction
case 10: // document type
case 12: // notation
break;
}
};
/**
* Handles doc node
* @param {Node} node
* @return {void}
* @private
*/
handleDoc = function (node) {
var child = node.firstChild;
while (child) {
serializeNode(child);
child = child.nextSibling;
}
};
/**
* Handles element nodes
* @param {Node} node
* @return {void}
* @private
*/
handleElement = function (node, parentIsPre) {
var child, attr, attrValue,
tagName = node.nodeName.toLowerCase(),
isIframe = tagName === 'iframe',
attrIdx = node.attributes.length,
firstChild = node.firstChild,
// pre || pre-wrap with any vendor prefix
isPre = parentIsPre ||
/pre(?:\-wrap)?$/i.test($(node).css('whiteSpace')),
selfClosing = !node.firstChild && !dom.canHaveChildren(node) &&
!isIframe;
if ($(node).hasClass('sceditor-ignore')) {
return;
}
output('<' + tagName, !parentIsPre && canIndent(node));
while (attrIdx--) {
attr = node.attributes[attrIdx];
// IE < 8 returns all possible attributes not just specified
// ones. IE < 8 also doesn't say value on input is specified
// so just assume it is.
if (!SCEditor.ie || attr.specified ||
(tagName === 'input' && attr.name === 'value')) {
// IE < 8 doesn't return the CSS for the style attribute
if (SCEditor.ie < 8 && /style/i.test(attr.name)) {
attrValue = node.style.cssText;
} else {
attrValue = attr.value;
}
output(' ' + attr.name.toLowerCase() + '="' +
escapeEntites(attrValue) + '"', false);
}
}
output(selfClosing ? ' />' : '>', false);
if (!isIframe) {
child = firstChild;
}
while (child) {
currentIndent++;
serializeNode(child, isPre);
child = child.nextSibling;
currentIndent--;
}
if (!selfClosing) {
output(
'</' + tagName + '>',
!isPre && !isIframe && canIndent(node) &&
firstChild && canIndent(firstChild)
);
}
};
/**
* Handles CDATA nodes
* @param {Node} node
* @return {void}
* @private
*/
handleCdata = function (node) {
output('<![CDATA[' + escapeEntites(node.nodeValue) + ']]>');
};
/**
* Handles comment nodes
* @param {Node} node
* @return {void}
* @private
*/
handleComment = function (node) {
output('<!-- ' + escapeEntites(node.nodeValue) + ' -->');
};
/**
* Handles text nodes
* @param {Node} node
* @return {void}
* @private
*/
handleText = function (node, parentIsPre) {
var text = node.nodeValue;
if (!parentIsPre) {
text = trim(text);
}
if (text) {
output(escapeEntites(text), !parentIsPre && canIndent(node));
}
};
/**
* Adds a string to the outputStringBuilder.
*
* The string will be indented unless indent is set to boolean false.
* @param {String} str
* @param {Boolean} indent
* @return {void}
* @private
*/
output = function (str, indent) {
var i = currentIndent;
if (indent !== false) {
// Don't add a new line if it's the first element
if (outputStringBuilder.length) {
outputStringBuilder.push('\n');
}
while (i--) {
outputStringBuilder.push(opts.indentStr);
}
}
outputStringBuilder.push(str);
};
/**
* Checks if should indent the node or not
* @param {Node} node
* @return {boolean}
* @private
*/
canIndent = function (node) {
var prev = node.previousSibling;
if (node.nodeType !== 1 && prev) {
return !dom.isInline(prev);
}
// first child of a block element
if (!prev && !dom.isInline(node.parentNode)) {
return true;
}
return !dom.isInline(node);
};
};
/**
* SCEditor XHTML plugin
* @class xhtml
* @name jQuery.sceditor.plugins.xhtml
* @since v1.4.1
*/
sceditorPlugins.xhtml = function () {
var base = this;
/**
* Tag converstions cache
* @type {Object}
* @private
*/
var tagConvertersCache = {};
/**
* Attributes filter cache
* @type {Object}
* @private
*/
var attrsCache = {};
/**
* Private methods
* @private
*/
var convertTags,
convertNode,
isEmpty,
removeTags,
mergeAttribsFilters,
removeAttribs,
wrapInlines;
/**
* Init
* @return {void}
*/
base.init = function () {
if (!$.isEmptyObject(sceditorPlugins.xhtml.converters || {})) {
$.each(
sceditorPlugins.xhtml.converters,
function (idx, converter) {
$.each(converter.tags, function (tagname) {
if (!tagConvertersCache[tagname]) {
tagConvertersCache[tagname] = [];
}
tagConvertersCache[tagname].push(converter);
});
}
);
}
this.commands = $.extend(true,
{}, defaultCommandsOverrides, this.commands);
};
/**
* Converts the WYSIWYG content to XHTML
* @param {String} html
* @param {Node} domBody
* @return {String}
* @memberOf jQuery.sceditor.plugins.xhtml.prototype
*/
base.signalToSource = function (html, domBody) {
domBody = domBody.jquery ? domBody[0] : domBody;
convertTags(domBody);
removeTags(domBody);
removeAttribs(domBody);
wrapInlines(domBody);
return (new SCEditor.XHTMLSerializer()).serialize(domBody, true);
};
/**
* Converts the XHTML to WYSIWYG content.
*
* This doesn't currently do anything as XHTML
* is valid WYSIWYG content.
* @param {String} text
* @return {String}
* @memberOf jQuery.sceditor.plugins.xhtml.prototype
*/
base.signalToWysiwyg = function (text) {
return text;
};
/**
* Deprecated, use dom.convertElement() instead.
* @deprecated
*/
base.convertTagTo = dom.convertElement;
/**
* Runs all converters for the specified tagName
* against the DOM node.
* @param {String} tagName
* @param {jQuery} $node
* @return {Node} node
* @private
*/
convertNode = function (tagName, $node, node) {
if (!tagConvertersCache[tagName]) {
return;
}
$.each(tagConvertersCache[tagName], function (idx, converter) {
if (converter.tags[tagName]) {
$.each(converter.tags[tagName], function (attr, values) {
if (!node.getAttributeNode) {
return;
}
attr = node.getAttributeNode(attr);
// IE < 8 always returns an attribute regardless of if
// it has been specified so must check it.
if (!attr || (SCEditor.ie < 8 && !attr.specified)) {
return;
}
if (values && $.inArray(attr.value, values) < 0) {
return;
}
converter.conv.call(base, node, $node);
});
} else if (converter.conv) {
converter.conv.call(base, node, $node);
}
});
};
/**
* Converts any tags/attributes to their XHTML equivalents
* @param {Node} node
* @return {Void}
* @private
*/
convertTags = function (node) {
dom.traverse(node, function (node) {
var $node = $(node),
tagName = node.nodeName.toLowerCase();
convertNode('*', $node, node);
convertNode(tagName, $node, node);
}, true);
};
/**
* Tests if a node is empty and can be removed.
*
* @param {Node} node
* @return {Boolean}
* @private
*/
isEmpty = function (node, excludeBr) {
var childNodes = node.childNodes,
tagName = node.nodeName.toLowerCase(),
nodeValue = node.nodeValue,
childrenLength = childNodes.length;
if (excludeBr && tagName === 'br') {
return true;
}
if ($(node).hasClass('sceditor-ignore')) {
return true;
}
if (!dom.canHaveChildren(node)) {
return false;
}
// \S|\u00A0 = any non space char
if (nodeValue && /\S|\u00A0/.test(nodeValue)) {
return false;
}
while (childrenLength--) {
if (!isEmpty(childNodes[childrenLength],
excludeBr && !node.previousSibling && !node.nextSibling)) {
return false;
}
}
return true;
};
/**
* Removes any tags that are not white listed or if no
* tags are white listed it will remove any tags that
* are black listed.
*
* @param {Node} rootNode
* @return {Void}
* @private
*/
removeTags = function (rootNode) {
dom.traverse(rootNode, function (node) {
var remove,
tagName = node.nodeName.toLowerCase(),
parentNode = node.parentNode,
nodeType = node.nodeType,
isBlock = !dom.isInline(node),
previousSibling = node.previousSibling,
nextSibling = node.nextSibling,
isTopLevel = parentNode === rootNode,
noSiblings = !previousSibling && !nextSibling,
empty = tagName !== 'iframe' && isEmpty(node,
isTopLevel && noSiblings && tagName !== 'br'),
document = node.ownerDocument,
allowedtags = sceditorPlugins.xhtml.allowedTags,
disallowedTags = sceditorPlugins.xhtml.disallowedTags;
// 3 = text node
if (nodeType === 3) {
return;
}
if (nodeType === 4) {
tagName = '!cdata';
} else if (tagName === '!' || nodeType === 8) {
tagName = '!comment';
}
if (empty) {
remove = true;
// 3 is text node which do not get filtered
} else if (allowedtags && allowedtags.length) {
remove = ($.inArray(tagName, allowedtags) < 0);
} else if (disallowedTags && disallowedTags.length) {
remove = ($.inArray(tagName, disallowedTags) > -1);
}
if (remove) {
if (!empty) {
if (isBlock && previousSibling &&
dom.isInline(previousSibling)) {
parentNode.insertBefore(
document.createTextNode(' '), node);
}
// Insert all the childen after node
while (node.firstChild) {
parentNode.insertBefore(node.firstChild,
nextSibling);
}
if (isBlock && nextSibling &&
dom.isInline(nextSibling)) {
parentNode.insertBefore(
document.createTextNode(' '), nextSibling);
}
}
parentNode.removeChild(node);
}
}, true);
};
/**
* Merges two sets of attribute filters into one
*
* @param {Object} filtersA
* @param {Object} filtersB
* @return {Object}
* @private
*/
mergeAttribsFilters = function (filtersA, filtersB) {
var ret = {};
if (filtersA) {
$.extend(ret, filtersA);
}
if (!filtersB) {
return ret;
}
$.each(filtersB, function (attrName, values) {
if ($.isArray(values)) {
ret[attrName] = $.merge(ret[attrName] || [], values);
} else if (!ret[attrName]) {
ret[attrName] = null;
}
});
return ret;
};
/**
* Wraps adjacent inline child nodes of root
* in paragraphs.
*
* @param {Node} root
* @private
*/
wrapInlines = function (root) {
var adjacentInlines = [];
var wrapAdjacents = function () {
if (adjacentInlines.length) {
$('<p>', root.ownerDocument)
.insertBefore(adjacentInlines[0])
.append(adjacentInlines);
adjacentInlines = [];
}
};
// Strip empty text nodes so they don't get wrapped.
dom.removeWhiteSpace(root);
var node = root.firstChild;
while (node) {
if (dom.isInline(node) && !$(node).is('.sceditor-ignore')) {
adjacentInlines.push(node);
} else {
wrapAdjacents();
}
node = node.nextSibling;
}
wrapAdjacents();
};
/**
* Removes any attributes that are not white listed or
* if no attributes are white listed it will remove
* any attributes that are black listed.
* @param {Node} node
* @return {Void}
* @private
*/
removeAttribs = function (node) {
var tagName, attr, attrName, attrsLength, validValues, remove,
allowedAttribs = sceditorPlugins.xhtml.allowedAttribs,
isAllowed = allowedAttribs &&
!$.isEmptyObject(allowedAttribs),
disallowedAttribs = sceditorPlugins.xhtml.disallowedAttribs,
isDisallowed = disallowedAttribs &&
!$.isEmptyObject(disallowedAttribs);
attrsCache = {};
dom.traverse(node, function (node) {
if (!node.attributes) {
return;
}
tagName = node.nodeName.toLowerCase();
attrsLength = node.attributes.length;
if (attrsLength) {
if (!attrsCache[tagName]) {
if (isAllowed) {
attrsCache[tagName] = mergeAttribsFilters(
allowedAttribs['*'],
allowedAttribs[tagName]
);
} else {
attrsCache[tagName] = mergeAttribsFilters(
disallowedAttribs['*'],
disallowedAttribs[tagName]
);
}
}
while (attrsLength--) {
attr = node.attributes[attrsLength];
attrName = attr.name;
validValues = attrsCache[tagName][attrName];
remove = false;
if (isAllowed) {
remove = validValues !== null &&
(!$.isArray(validValues) ||
$.inArray(attr.value, validValues) < 0);
} else if (isDisallowed) {
remove = validValues === null ||
($.isArray(validValues) &&
$.inArray(attr.value, validValues) > -1);
}
if (remove) {
node.removeAttribute(attrName);
}
}
}
});
};
};
/**
* Tag conveters, a converter is applied to all
* tags that match the criteria.
* @type {Array}
* @name jQuery.sceditor.plugins.xhtml.converters
* @since v1.4.1
*/
sceditorPlugins.xhtml.converters = [
{
tags: {
'*': {
width: null
}
},
conv: function (node, $node) {
$node.css('width', $node.attr('width')).removeAttr('width');
}
},
{
tags: {
'*': {
height: null
}
},
conv: function (node, $node) {
$node.css('height', $node.attr('height')).removeAttr('height');
}
},
{
tags: {
'li': {
value: null
}
},
conv: function (node, $node) {
if (SCEditor.ie < 8) {
node.removeAttribute('value');
} else {
$node.removeAttr('value');
}
}
},
{
tags: {
'*': {
text: null
}
},
conv: function (node, $node) {
$node.css('color', $node.attr('text')).removeAttr('text');
}
},
{
tags: {
'*': {
color: null
}
},
conv: function (node, $node) {
$node.css('color', $node.attr('color')).removeAttr('color');
}
},
{
tags: {
'*': {
face: null
}
},
conv: function (node, $node) {
$node.css('fontFamily', $node.attr('face')).removeAttr('face');
}
},
{
tags: {
'*': {
align: null
}
},
conv: function (node, $node) {
$node.css('textAlign', $node.attr('align')).removeAttr('align');
}
},
{
tags: {
'*': {
border: null
}
},
conv: function (node, $node) {
$node
.css('borderWidth',$node.attr('border'))
.removeAttr('border');
}
},
{
tags: {
applet: {
name: null
},
img: {
name: null
},
layer: {
name: null
},
map: {
name: null
},
object: {
name: null
},
param: {
name: null
}
},
conv: function (node, $node) {
if (!$node.attr('id')) {
$node.attr('id', $node.attr('name'));
}
$node.removeAttr('name');
}
},
{
tags: {
'*': {
vspace: null
}
},
conv: function (node, $node) {
$node
.css('marginTop', $node.attr('vspace') - 0)
.css('marginBottom', $node.attr('vspace') - 0)
.removeAttr('vspace');
}
},
{
tags: {
'*': {
hspace: null
}
},
conv: function (node, $node) {
$node
.css('marginLeft', $node.attr('hspace') - 0)
.css('marginRight', $node.attr('hspace') - 0)
.removeAttr('hspace');
}
},
{
tags: {
'hr': {
noshade: null
}
},
conv: function (node, $node) {
$node.css('borderStyle', 'solid').removeAttr('noshade');
}
},
{
tags: {
'*': {
nowrap: null
}
},
conv: function (node, $node) {
$node.css('white-space', 'nowrap').removeAttr('nowrap');
}
},
{
tags: {
big: null
},
conv: function (node) {
$(this.convertTagTo(node, 'span')).css('fontSize', 'larger');
}
},
{
tags: {
small: null
},
conv: function (node) {
$(this.convertTagTo(node, 'span')).css('fontSize', 'smaller');
}
},
{
tags: {
b: null
},
conv: function (node) {
$(this.convertTagTo(node, 'strong'));
}
},
{
tags: {
u: null
},
conv: function (node) {
$(this.convertTagTo(node, 'span'))
.css('textDecoration', 'underline');
}
},
{
tags: {
i: null
},
conv: function (node) {
$(this.convertTagTo(node, 'em'));
}
},
{
tags: {
s: null,
strike: null
},
conv: function (node) {
$(this.convertTagTo(node, 'span'))
.css('textDecoration', 'line-through');
}
},
{
tags: {
dir: null
},
conv: function (node) {
this.convertTagTo(node, 'ul');
}
},
{
tags: {
center: null
},
conv: function (node) {
$(this.convertTagTo(node, 'div'))
.css('textAlign', 'center');
}
},
{
tags: {
font: {
size: null
}
},
conv: function (node, $node) {
var size = $node.css('fontSize'),
fontSize = size;
// IE < 8 sets a font tag with no size to +0 so
// should just skip it.
if (fontSize !== '+0') {
// IE 8 and below incorrectly returns the value of the size
// attribute instead of the px value so must convert it
if (SCEditor.ie < 9) {
fontSize = 10;
if (size > 1) {
fontSize = 13;
}
if (size > 2) {
fontSize = 16;
}
if (size > 3) {
fontSize = 18;
}
if (size > 4) {
fontSize = 24;
}
if (size > 5) {
fontSize = 32;
}
if (size > 6) {
fontSize = 48;
}
}
$node.css('fontSize', fontSize);
}
$node.removeAttr('size');
}
},
{
tags: {
font: null
},
conv: function (node) {
// All it's attributes will be converted
// by the attribute converters
this.convertTagTo(node, 'span');
}
},
{
tags: {
'*': {
type: ['_moz']
}
},
conv: function (node, $node) {
$node.removeAttr('type');
}
},
{
tags: {
'*': {
'_moz_dirty': null
}
},
conv: function (node, $node) {
$node.removeAttr('_moz_dirty');
}
},
{
tags: {
'*': {
'_moz_editor_bogus_node': null
}
},
conv: function (node, $node) {
$node.remove();
}
}
];
/**
* Allowed attributes map.
*
* To allow an attribute for all tags use * as the tag name.
*
* Leave empty or null to allow all attributes. (the disallow
* list will be used to filter them instead)
* @type {Object}
* @name jQuery.sceditor.plugins.xhtml.allowedAttribs
* @since v1.4.1
*/
sceditorPlugins.xhtml.allowedAttribs = {};
/**
* Attributes that are not allowed.
*
* Only used if allowed attributes is null or empty.
* @type {Object}
* @name jQuery.sceditor.plugins.xhtml.disallowedAttribs
* @since v1.4.1
*/
sceditorPlugins.xhtml.disallowedAttribs = {};
/**
* Array containing all the allowed tags.
*
* If null or empty all tags will be allowed.
* @type {Array}
* @name jQuery.sceditor.plugins.xhtml.allowedTags
* @since v1.4.1
*/
sceditorPlugins.xhtml.allowedTags = [];
/**
* Array containing all the disallowed tags.
*
* Only used if allowed tags is null or empty.
* @type {Array}
* @name jQuery.sceditor.plugins.xhtml.disallowedTags
* @since v1.4.1
*/
sceditorPlugins.xhtml.disallowedTags = [];
}(jQuery));