Replaces any params in a template with the passed params.
*
*
If createHTML is passed it will use jQuery to create the HTML. The
* same as doing: $(editor.tmpl("html", {params...}));
*
* @param {string} templateName
* @param {Object} params
* @param {Boolean} createHTML
* @private
*/
var _tmpl = function(name, params, createHTML) {
var template = _templates[name];
$.each(params, function(name, val) {
template = template.replace(new RegExp('\\{' + name + '\\}', 'g'), val);
});
if(createHTML)
template = $(template);
return template;
};
/**
* SCEditor - A lightweight WYSIWYG editor
*
* @param {Element} el The textarea to be converted
* @return {Object} options
* @class sceditor
* @name jQuery.sceditor
*/
$.sceditor = function (el, options) {
/**
* Alias of this
* @private
*/
var base = this;
/**
* The textarea element being replaced
* @private
*/
var original = el.get ? el.get(0) : el;
var $original = $(original);
/**
* The div which contains the editor and toolbar
* @private
*/
var $editorContainer;
/**
* The editors toolbar
* @private
*/
var $toolbar;
/**
* The editors iframe which should be in design mode
* @private
*/
var $wysiwygEditor;
var wysiwygEditor;
/**
* The WYSIWYG editors body element
* @private
*/
var $wysiwygBody;
/**
* The WYSIWYG editors document
* @private
*/
var $wysiwygDoc;
/**
* The editors textarea for viewing source
* @private
*/
var $sourceEditor;
var sourceEditor;
/**
* The current dropdown
* @private
*/
var $dropdown;
/**
* Array of all the commands key press functions
* @private
* @type {Array}
*/
var keyPressFuncs = [];
/**
* Store the last cursor position. Needed for IE because it forgets
* @private
*/
var lastRange;
/**
* The editors locale
* @private
*/
var locale;
/**
* Stores a cache of preloaded images
* @private
* @type {Array}
*/
var preLoadCache = [];
/**
* The editors rangeHelper instance
* @type {jQuery.sceditor.rangeHelper}
* @private
*/
var rangeHelper;
/**
* Tags which require the new line fix
* @type {Array}
* @private
*/
var requireNewLineFix = [];
/**
* An array of button state handlers
* @type {Array}
* @private
*/
var btnStateHandlers = [];
/**
* Element which gets focused to blur the editor.
*
* This will be null until blur() is called.
* @type {HTMLElement}
* @private
*/
var $blurElm;
/**
* Plugin manager instance
* @type {jQuery.sceditor.PluginManager}
* @private
*/
var pluginManager;
/**
* The current node containing the selection/caret
* @type {Node}
* @private
*/
var currentNode;
/**
* The first block level parent of the current node
* @type {node}
* @private
*/
var currentBlockNode;
/**
* The current node selection/caret
* @type {Object}
* @private
*/
var currentSelection;
/**
* Used to make sure only 1 selection changed check is called every 100ms.
* Helps improve performance as it is checked a lot.
* @type {Boolean}
* @private
*/
var isSelectionCheckPending;
/**
* If content is required (equivalent to the HTML5 required attribute)
* @type {Boolean}
* @private
*/
var isRequired;
/**
* The inline CSS style element. Will be undefined until css() is called
* for the first time.
* @type {HTMLElement}
* @private
*/
var inlineCss;
/**
* Object containing a list of shortcut handlers
* @type {Object}
* @private
*/
var shortcutHandlers = {};
/**
* An array of all the current emoticons.
*
* Only used or populated when emoticonsCompat is enabled.
* @type {Array}
* @private
*/
var currentEmoticons = [];
/**
* Private functions
* @private
*/
var init,
replaceEmoticons,
handleCommand,
saveRange,
initEditor,
initPlugins,
initLocale,
initToolBar,
initOptions,
initEvents,
initCommands,
initResize,
initEmoticons,
getWysiwygDoc,
handlePasteEvt,
handlePasteData,
handleKeyDown,
handleBackSpace,
handleKeyPress,
handleFormReset,
handleMouseDown,
handleEvent,
handleDocumentClick,
handleWindowResize,
updateToolBar,
updateActiveButtons,
sourceEditorSelectedText,
appendNewLine,
checkSelectionChanged,
checkNodeChanged,
autofocus,
emoticonsKeyPress,
emoticonsCheckWhitespace,
currentStyledBlockNode;
/**
* All the commands supported by the editor
* @name commands
* @memberOf jQuery.sceditor.prototype
*/
base.commands = $.extend(true, {}, (options.commands || $.sceditor.commands));
/**
* Options for this editor instance
* @name opts
* @memberOf jQuery.sceditor.prototype
*/
base.opts = options = $.extend({}, $.sceditor.defaultOptions, options);
/**
* Creates the editor iframe and textarea
* @private
*/
init = function () {
$original.data("sceditor", base);
// Clone any objects in options
$.each(options, function(key, val) {
if($.isPlainObject(val))
options[key] = $.extend(true, {}, val);
});
// Load locale
if(options.locale && options.locale !== 'en')
initLocale();
$editorContainer = $('
')
.insertAfter($original)
.css('z-index', options.zIndex);
// Add IE version to the container to allow IE specific CSS
// fixes without using CSS hacks or conditional comments
if($.sceditor.ie)
$editorContainer.addClass('ie ie' + $.sceditor.ie);
isRequired = !!$original.attr('required');
$original.removeAttr('required');
// create the editor
initPlugins();
initEmoticons();
initToolBar();
initEditor();
initCommands();
initOptions();
initEvents();
// force into source mode if is a browser that can't handle
// full editing
if(!$.sceditor.isWysiwygSupported)
base.toggleSourceMode();
var loaded = function() {
$(window).unbind('load', loaded);
if(options.autofocus)
autofocus();
if(options.autoExpand)
base.expandToContent();
// Page width might have changed after CSS is loaded so
// call handleWindowResize to update any % based dimensions
handleWindowResize();
};
$(window).load(loaded);
if(document.readyState && document.readyState === 'complete')
loaded();
updateActiveButtons();
pluginManager.call('ready');
};
initPlugins = function() {
var plugins = options.plugins;
plugins = plugins ? plugins.toString().split(',') : [];
pluginManager = new $.sceditor.PluginManager(base);
$.each(plugins, function(idx, plugin) {
pluginManager.register($.trim(plugin));
});
};
/**
* Init the locale variable with the specified locale if possible
* @private
* @return void
*/
initLocale = function() {
var lang;
if($.sceditor.locale[options.locale])
locale = $.sceditor.locale[options.locale];
else
{
lang = options.locale.split('-');
if($.sceditor.locale[lang[0]])
locale = $.sceditor.locale[lang[0]];
}
if(locale && locale.dateFormat)
options.dateFormat = locale.dateFormat;
};
/**
* Creates the editor iframe and textarea
* @private
*/
initEditor = function () {
var doc, tabIndex;
// @SMF code: tabindex applied to the editor
$sourceEditor = $('').attr('tabindex', $original.attr('tabindex')).hide();
$wysiwygEditor = $('').attr('tabindex', $original.attr('tabindex'));
if(!options.spellcheck)
$sourceEditor.attr('spellcheck', 'false');
if(window.location.protocol === 'https:')
$wysiwygEditor.attr('src', 'javascript:false');
// add the editor to the HTML and store the editors element
$editorContainer.append($wysiwygEditor).append($sourceEditor);
wysiwygEditor = $wysiwygEditor[0];
sourceEditor = $sourceEditor[0];
base.width(options.width || $original.width());
base.height(options.height || $original.height());
doc = getWysiwygDoc();
doc.open();
doc.write(_tmpl('html', { spellcheck: options.spellcheck ? '' : 'spellcheck="false"', charset: options.charset, style: options.style }));
doc.close();
$wysiwygDoc = $(doc);
$wysiwygBody = $(doc.body);
base.readOnly(!!options.readOnly);
// Add IE version class to the HTML element so can apply
// conditional styling without CSS hacks
if($.sceditor.ie)
$wysiwygDoc.find('html').addClass('ie ie' + $.sceditor.ie);
// iframe overflow fix for iOS, also fixes an IE issue with the
// editor not getting focus when clicking inside
if($.sceditor.ios || $.sceditor.ie)
{
$wysiwygBody.height('100%');
if(!$.sceditor.ie)
$wysiwygBody.bind('touchend', base.focus);
}
rangeHelper = new $.sceditor.rangeHelper(wysiwygEditor.contentWindow);
// load any textarea value into the editor
base.val($original.hide().val());
tabIndex = $original.attr('tabindex');
$sourceEditor.attr('tabindex', tabIndex);
$wysiwygEditor.attr('tabindex', tabIndex);
};
/**
* Initialises options
* @private
*/
initOptions = function() {
// auto-update original textbox on blur if option set to true
if(options.autoUpdate)
{
$wysiwygBody.bind('blur', base.updateOriginal);
$sourceEditor.bind('blur', base.updateOriginal);
}
if(options.rtl === null)
options.rtl = $sourceEditor.css('direction') === 'rtl';
base.rtl(!!options.rtl);
if(options.autoExpand)
$wysiwygDoc.bind('keyup', base.expandToContent);
if(options.resizeEnabled)
initResize();
$editorContainer.attr('id', options.id);
base.emoticons(options.emoticonsEnabled);
};
/**
* Initialises events
* @private
*/
initEvents = function() {
$(document).click(handleDocumentClick);
$(original.form)
.bind('reset', handleFormReset)
.submit(base.updateOriginal);
$(window).bind('resize orientationChanged', handleWindowResize);
$wysiwygBody
.keypress(handleKeyPress)
.keydown(handleKeyDown)
.keydown(handleBackSpace)
.keyup(appendNewLine)
.bind('paste', handlePasteEvt)
.bind($.sceditor.ie ? 'selectionchange' : 'keyup focus blur contextmenu mouseup touchend click', checkSelectionChanged)
.bind('keydown keyup keypress focus blur contextmenu', handleEvent);
if(options.emoticonsCompat && window.getSelection)
$wysiwygBody.keyup(emoticonsCheckWhitespace);
$sourceEditor.bind('keydown keyup keypress focus blur contextmenu', handleEvent).keydown(handleKeyDown);
$wysiwygDoc
.keypress(handleKeyPress)
.mousedown(handleMouseDown)
.bind($.sceditor.ie ? 'selectionchange' : 'focus blur contextmenu mouseup click', checkSelectionChanged)
.bind('beforedeactivate keyup', saveRange)
.keyup(appendNewLine)
.focus(function() {
lastRange = null;
});
$editorContainer
.bind('selectionchanged', checkNodeChanged)
.bind('selectionchanged', updateActiveButtons)
.bind('selectionchanged', handleEvent)
.bind('nodechanged', handleEvent);
};
/**
* Creates the toolbar and appends it to the container
* @private
*/
initToolBar = function () {
var $group, $button,
exclude = (options.toolbarExclude || '').split(','),
groups = options.toolbar.split('|');
$toolbar = $('
The save argument specifies if to save the new sizes.
* The saved sizes can be used for things like restoring from
* maximized state. This should normally be left as true.
*
* @param {int} width Width in px
* @param {int} height Height in px
* @param {boolean} [save=true] If to store the new sizes
* @since 1.4.1
* @function
* @memberOf jQuery.sceditor.prototype
* @name dimensions^3
* @return {this}
*/
base.dimensions = function(width, height, save) {
// IE6 & IE7 add 2 pixels to the source mode textarea height which must be ignored.
// Doesn't seem to be any way to fix it with only CSS
var ieBorderBox = $.sceditor.ie < 8 || document.documentMode < 8 ? 2 : 0;
// set undefined width/height to boolean false
width = (!width && width !== 0) ? false : width;
height = (!height && height !== 0) ? false : height;
if(width === false && height === false)
return { width: base.width(), height: base.height() };
if(typeof $wysiwygEditor.data('outerWidthOffset') === 'undefined')
base.updateStyleCache();
if(width !== false)
{
if(save !== false)
options.width = width;
if(height === false)
{
height = $editorContainer.height();
save = false;
}
$editorContainer.width(width);
if(width && width.toString().indexOf('%') > -1)
width = $editorContainer.width();
$wysiwygEditor.width(width - $wysiwygEditor.data('outerWidthOffset'));
$sourceEditor.width(width - $sourceEditor.data('outerWidthOffset'));
// Fix overflow issue with iOS not breaking words unless a width is set
if($.sceditor.ios && $wysiwygBody)
$wysiwygBody.width(width - $wysiwygEditor.data('outerWidthOffset') - ($wysiwygBody.outerWidth(true) - $wysiwygBody.width()));
}
if(height !== false)
{
if(save !== false)
options.height = height;
// Convert % based heights to px
if(height && height.toString().indexOf('%') > -1)
{
height = $editorContainer.height(height).height();
$editorContainer.height('auto');
}
height -= !options.toolbarContainer ? $toolbar.outerHeight(true) : 0;
$wysiwygEditor.height(height - $wysiwygEditor.data('outerHeightOffset'));
$sourceEditor.height(height - ieBorderBox - $sourceEditor.data('outerHeightOffset'));
}
return this;
};
/**
* Updates the CSS styles cache. Shouldn't be needed unless changing the editors theme.
*
* @since 1.4.1
* @function
* @memberOf jQuery.sceditor.prototype
* @name updateStyleCache
* @return {int}
*/
base.updateStyleCache = function() {
// caching these improves FF resize performance
$wysiwygEditor.data('outerWidthOffset', $wysiwygEditor.outerWidth(true) - $wysiwygEditor.width());
$sourceEditor.data('outerWidthOffset', $sourceEditor.outerWidth(true) - $sourceEditor.width());
$wysiwygEditor.data('outerHeightOffset', $wysiwygEditor.outerHeight(true) - $wysiwygEditor.height());
$sourceEditor.data('outerHeightOffset', $sourceEditor.outerHeight(true) - $sourceEditor.height());
};
/**
* Gets the height of the editor in px
*
* @since 1.3.5
* @function
* @memberOf jQuery.sceditor.prototype
* @name height
* @return {int}
*/
/**
* Sets the height of the editor
*
* @param {int} height Height in px
* @since 1.3.5
* @function
* @memberOf jQuery.sceditor.prototype
* @name height^2
* @return {this}
*/
/**
* Sets the height of the editor
*
* The saveHeight specifies if to save the height. The stored height can be
* used for things like restoring from maximized state.
*
* @param {int} height Height in px
* @param {boolean} [saveHeight=true] If to store the height
* @since 1.4.1
* @function
* @memberOf jQuery.sceditor.prototype
* @name height^3
* @return {this}
*/
base.height = function (height, saveHeight) {
if(!height && height !== 0)
return $editorContainer.height();
base.dimensions(null, height, saveHeight);
return this;
};
/**
* Gets if the editor is maximised or not
*
* @since 1.4.1
* @function
* @memberOf jQuery.sceditor.prototype
* @name maximize
* @return {boolean}
*/
/**
* Sets if the editor is maximised or not
*
* @param {boolean} maximize If to maximise the editor
* @since 1.4.1
* @function
* @memberOf jQuery.sceditor.prototype
* @name maximize^2
* @return {this}
*/
base.maximize = function(maximize) {
if(typeof maximize === 'undefined')
return $editorContainer.is('.sceditor-maximize');
maximize = !!maximize;
// IE 6 fix
if($.sceditor.ie < 7)
$('html, body').toggleClass('sceditor-maximize', maximize);
$editorContainer.toggleClass('sceditor-maximize', maximize);
base.width(maximize ? '100%' : options.width, false);
base.height(maximize ? '100%' : options.height, false);
return this;
};
/**
* Expands the editors height to the height of it's content
*
* Unless ignoreMaxHeight is set to true it will not expand
* higher than the maxHeight option.
*
* @since 1.3.5
* @param {Boolean} [ignoreMaxHeight=false]
* @function
* @name expandToContent
* @memberOf jQuery.sceditor.prototype
* @see #resizeToContent
*/
base.expandToContent = function(ignoreMaxHeight) {
var currentHeight = $editorContainer.height(),
height = $wysiwygBody[0].scrollHeight || $wysiwygDoc[0].documentElement.scrollHeight,
padding = (currentHeight - $wysiwygEditor.height()),
maxHeight = options.resizeMaxHeight || ((options.height || $original.height()) * 2);
height += padding;
if(ignoreMaxHeight !== true && height > maxHeight)
height = maxHeight;
if(height > currentHeight)
base.height(height);
};
/**
* Destroys the editor, removing all elements and
* event handlers.
*
* Leaves only the original textarea.
*
* @function
* @name destroy
* @memberOf jQuery.sceditor.prototype
*/
base.destroy = function () {
pluginManager.destroy();
rangeHelper = null;
lastRange = null;
pluginManager = null;
$(document).unbind('click', handleDocumentClick);
$(window).unbind('resize orientationChanged', handleWindowResize);
$(original.form)
.unbind('reset', handleFormReset)
.unbind('submit', base.updateOriginal);
$wysiwygBody.unbind();
$wysiwygDoc.unbind().find('*').remove();
$sourceEditor.unbind().remove();
$toolbar.remove();
$editorContainer.unbind().find('*').unbind().remove();
$editorContainer.remove();
$original
.removeData('sceditor')
.removeData('sceditorbbcode')
.show();
if(isRequired)
$original.attr('required', 'required');
};
/**
* Creates a menu item drop down
*
* @param {HTMLElement} menuItem The button to align the drop down with
* @param {string} dropDownName Used for styling the dropown, will be a class sceditor-dropDownName
* @param {HTMLElement} content The HTML content of the dropdown
* @param {bool} [ieUnselectable=true] If to add the unselectable attribute to all the contents elements. Stops IE from deselecting the text in the editor
* @function
* @name createDropDown
* @memberOf jQuery.sceditor.prototype
*/
base.createDropDown = function (menuItem, dropDownName, content, ieUnselectable) {
// first click for create second click for close
var css,
onlyclose = $dropdown && $dropdown.is('.sceditor-' + dropDownName);
base.closeDropDown();
if (onlyclose) return;
// IE needs unselectable attr to stop it from unselecting the text in the editor.
// The editor can cope if IE does unselect the text it's just not nice.
if (ieUnselectable !== false)
{
$(content)
.find(':not(input,textarea)')
.filter(function() {
return this.nodeType===1;
})
.attr('unselectable', 'on');
}
css = {
top: menuItem.offset().top,
left: menuItem.offset().left,
marginTop: menuItem.outerHeight()
};
$.extend(css, options.dropDownCss);
$dropdown = $('
')
.css(css)
.append(content)
.appendTo($('body'))
.click(function (e) {
// stop clicks within the dropdown from being handled
e.stopPropagation();
});
};
/**
* Handles any document click and closes the dropdown if open
* @private
*/
handleDocumentClick = function (e) {
// ignore right clicks
if(e.which !== 3)
base.closeDropDown();
};
/**
* Handles the WYSIWYG editors paste event
* @private
*/
handlePasteEvt = function(e) {
var html, handlePaste,
elm = $wysiwygBody[0],
doc = $wysiwygDoc[0],
checkCount = 0,
pastearea = document.createElement('div'),
prePasteContent = doc.createDocumentFragment();
if (options.disablePasting)
return false;
if (!options.enablePasteFiltering)
return;
rangeHelper.saveRange();
document.body.appendChild(pastearea);
if (e && e.clipboardData && e.clipboardData.getData)
{
if ((html = e.clipboardData.getData('text/html')) || (html = e.clipboardData.getData('text/plain')))
{
pastearea.innerHTML = html;
handlePasteData(elm, pastearea);
return false;
}
}
while(elm.firstChild)
prePasteContent.appendChild(elm.firstChild);
// try make pastearea contenteditable and redirect to that? Might work.
// Check the tests if still exist, if not re-0create
handlePaste = function (elm, pastearea) {
if (elm.childNodes.length > 0)
{
while(elm.firstChild)
pastearea.appendChild(elm.firstChild);
while(prePasteContent.firstChild)
elm.appendChild(prePasteContent.firstChild);
handlePasteData(elm, pastearea);
}
else
{
// Allow max 25 checks before giving up.
// Needed in case an empty string is pasted or
// something goes wrong.
if(checkCount > 25)
{
while(prePasteContent.firstChild)
elm.appendChild(prePasteContent.firstChild);
rangeHelper.restoreRange();
return;
}
++checkCount;
setTimeout(function () {
handlePaste(elm, pastearea);
}, 20);
}
};
handlePaste(elm, pastearea);
base.focus();
return true;
};
/**
* Gets the pasted data, filters it and then inserts it.
* @param {Element} elm
* @param {Element} pastearea
* @private
*/
handlePasteData = function(elm, pastearea) {
// fix any invalid nesting
$.sceditor.dom.fixNesting(pastearea);
// TODO: Trigger custom paste event to allow filtering (pre and post converstion?)
var pasteddata = pastearea.innerHTML;
if(pluginManager.hasHandler('toSource'))
pasteddata = pluginManager.callOnlyFirst('toSource', pasteddata, $(pastearea));
pastearea.parentNode.removeChild(pastearea);
if(pluginManager.hasHandler('toWysiwyg'))
pasteddata = pluginManager.callOnlyFirst('toWysiwyg', pasteddata, true);
rangeHelper.restoreRange();
base.wysiwygEditorInsertHtml(pasteddata, null, true);
};
/**
* Closes any currently open drop down
*
* @param {bool} [focus=false] If to focus the editor after closing the drop down
* @function
* @name closeDropDown
* @memberOf jQuery.sceditor.prototype
*/
base.closeDropDown = function (focus) {
if($dropdown) {
$dropdown.unbind().remove();
$dropdown = null;
}
if(focus === true)
base.focus();
};
/**
* Gets the WYSIWYG editors document
* @private
*/
getWysiwygDoc = function () {
if (wysiwygEditor.contentDocument)
return wysiwygEditor.contentDocument;
if (wysiwygEditor.contentWindow && wysiwygEditor.contentWindow.document)
return wysiwygEditor.contentWindow.document;
if (wysiwygEditor.document)
return wysiwygEditor.document;
return null;
};
/**
*
Inserts HTML into WYSIWYG editor.
*
*
If endHtml is specified, any selected text will be placed between html
* and endHtml. If there is no selected text html and endHtml will just be
* concated together.
*
* @param {string} html
* @param {string} [endHtml=null]
* @param {boolean} [overrideCodeBlocking=false] If to insert the html into code tags, by default code tags only support text.
* @function
* @name wysiwygEditorInsertHtml
* @memberOf jQuery.sceditor.prototype
*/
base.wysiwygEditorInsertHtml = function (html, endHtml, overrideCodeBlocking) {
var scrollTo, $marker,
marker = '';
base.focus();
// TODO: This code tag should be configurable and should maybe convert the HTML into text
// don't apply to code elements
if(!overrideCodeBlocking && ($(currentBlockNode).is('code') || $(currentBlockNode).parents('code').length !== 0))
return;
if(endHtml)
endHtml += marker;
else
html += marker;
rangeHelper.insertHTML(html, endHtml);
// Scroll the editor to after the inserted HTML
$marker = $wysiwygBody.find('#sceditor-cursor');
scrollTo = ($marker.offset().top + ($marker.outerHeight(true) * 2)) - $wysiwygEditor.height();
$marker.remove();
// TODO: check if already in range and don't scroll if it is
$wysiwygDoc.scrollTop(scrollTo);
$wysiwygBody.scrollTop(scrollTo);
rangeHelper.saveRange();
replaceEmoticons($wysiwygBody[0]);
rangeHelper.restoreRange();
appendNewLine();
};
/**
* Like wysiwygEditorInsertHtml except it will convert any HTML into text
* before inserting it.
*
* @param {String} text
* @param {String} [endText=null]
* @function
* @name wysiwygEditorInsertText
* @memberOf jQuery.sceditor.prototype
*/
base.wysiwygEditorInsertText = function (text, endText) {
base.wysiwygEditorInsertHtml($.sceditor.escapeEntities(text), $.sceditor.escapeEntities(endText));
};
/**
*
Inserts text into the WYSIWYG or source editor depending on which
* mode the editor is in.
*
*
If endText is specified any selected text will be placed between
* text and endText. If no text is selected text and endText will
* just be concated together.
Like wysiwygEditorInsertHtml but inserts text into the
* source mode editor instead.
*
*
If endText is specified any selected text will be placed between
* text and endText. If no text is selected text and endText will
* just be concated together.
*
*
The cursor will be placed after the text param. If endText is
* specified the cursor will be placed before endText, so passing:
*
* '[b]', '[/b]'
*
*
Would cause the cursor to be placed:
*
* [b]Selected text|[/b]
*
* @param {String} text
* @param {String} [endText=null]
* @since 1.4.0
* @function
* @name sourceEditorInsertText
* @memberOf jQuery.sceditor.prototype
*/
base.sourceEditorInsertText = function (text, endText) {
var range, start, end, txtLen, scrollTop;
scrollTop = sourceEditor.scrollTop;
sourceEditor.focus();
if(typeof sourceEditor.selectionStart !== 'undefined')
{
start = sourceEditor.selectionStart;
end = sourceEditor.selectionEnd;
txtLen = text.length;
if(endText)
text += sourceEditor.value.substring(start, end) + endText;
sourceEditor.value = sourceEditor.value.substring(0, start) + text + sourceEditor.value.substring(end, sourceEditor.value.length);
sourceEditor.selectionStart = (start + text.length) - (endText ? endText.length : 0);
sourceEditor.selectionEnd = sourceEditor.selectionStart;
}
else if(typeof document.selection.createRange !== 'undefined')
{
range = document.selection.createRange();
if(endText)
text += range.text + endText;
range.text = text;
if(endText)
range.moveEnd('character', 0-endText.length);
range.moveStart('character', range.End - range.Start);
range.select();
}
else
sourceEditor.value += text + endText;
sourceEditor.scrollTop = scrollTop;
sourceEditor.focus();
};
/**
* Gets the current instance of the rangeHelper class
* for the editor.
*
* @return jQuery.sceditor.rangeHelper
* @function
* @name getRangeHelper
* @memberOf jQuery.sceditor.prototype
*/
base.getRangeHelper = function () {
return rangeHelper;
};
/**
*
Gets the value of the editor.
*
*
If the editor is in WYSIWYG mode it will return the filtered
* HTML from it (converted to BBCode if using the BBCode plugin).
* It it's in Source Mode it will return the unfiltered contents
* of the source editor (if using the BBCode plugin this will be
* BBCode again).
If filter set true the val will be passed through the filter
* function. If using the BBCode plugin it will pass the val to
* the BBCode filter to convert any BBCode into HTML.
If end is supplied any selected text will be placed between
* start and end. If there is no selected text start and end
* will be concated together.
*
*
If the filter param is set to true, the HTML/BBCode will be
* passed through any plugin filters. If using the BBCode plugin
* this will convert any BBCode into HTML.
If end is supplied any selected text will be placed between
* start and end. If there is no selected text start and end
* will be concated together.
*
*
If the filter param is set to true, the HTML/BBCode will be
* passed through any plugin filters. If using the BBCode plugin
* this will convert any BBCode into HTML.
*
*
If the allowMixed param is set to true, HTML any will not be escaped
*
* @param {String} start
* @param {String} [end=null]
* @param {Boolean} [filter=true]
* @param {Boolean} [convertEmoticons=true] If to convert emoticons
* @param {Boolean} [allowMixed=false]
* @return {this}
* @since 1.4.3
* @function
* @name insert^2
* @memberOf jQuery.sceditor.prototype
*/
base.insert = function (start, end, filter, convertEmoticons, allowMixed) {
if(base.inSourceMode())
base.sourceEditorInsertText(start, end);
else
{
// Add the selection between start and end
if(end)
{
var html = base.getRangeHelper().selectedHtml(),
frag = $('
').appendTo($('body')).hide().html(html);
if(filter !== false && pluginManager.hasHandler('toSource'))
html = pluginManager.callOnlyFirst('toSource', html, frag);
frag.remove();
start += html + end;
}
if(filter !== false && pluginManager.hasHandler('toWysiwyg'))
start = pluginManager.callOnlyFirst('toWysiwyg', start, true);
// Convert any escaped HTML back into HTML if mixed is allowed
if(filter !== false && allowMixed === true)
{
start = start.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/&/g, '&');
}
base.wysiwygEditorInsertHtml(start);
}
return this;
};
/**
* Gets the WYSIWYG editors HTML value.
*
* If using a plugin that filters the Ht Ml like the BBCode plugin
* it will return the result of the filtering (BBCode) unless the
* filter param is set to false.
*
* @param {bool} [filter=true]
* @return {string}
* @function
* @name getWysiwygEditorValue
* @memberOf jQuery.sceditor.prototype
*/
base.getWysiwygEditorValue = function(filter) {
var html, ieBookmark,
hasSelection = rangeHelper.hasSelection();
if(hasSelection)
rangeHelper.saveRange();
// IE <= 8 bookmark the current TextRange position
// and restore it after
else if(lastRange && lastRange.getBookmark)
ieBookmark = lastRange.getBookmark();
$.sceditor.dom.fixNesting($wysiwygBody[0]);
// filter the HTML and DOM through any plugins
html = $wysiwygBody.html();
if(filter !== false && pluginManager.hasHandler('toSource'))
html = pluginManager.callOnlyFirst('toSource', html, $wysiwygBody);
if(hasSelection)
{
// remove the last stored range for IE as it no longer applies
rangeHelper.restoreRange();
lastRange = null;
}
else if(ieBookmark)
{
lastRange.moveToBookmark(ieBookmark);
lastRange = null;
}
return html;
};
/**
* Gets the WYSIWYG editor's iFrame Body.
*
* @return {jQuery}
* @function
* @since 1.4.3
* @name getBody
* @memberOf jQuery.sceditor.prototype
*/
base.getBody = function () {
return $wysiwygBody;
};
/**
* Gets the WYSIWYG editors container area (whole iFrame).
*
* @return {Node}
* @function
* @since 1.4.3
* @name getContentAreaContainer
* @memberOf jQuery.sceditor.prototype
*/
base.getContentAreaContainer = function () {
return $wysiwygEditor;
};
/**
* Gets the text editor value
*
* If using a plugin that filters the text like the BBCode plugin
* it will return the result of the filtering which is BBCode to
* HTML so it will return HTML. If filter is set to false it will
* just return the contents of the source editor (BBCode).
*
* @param {bool} [filter=true]
* @return {string}
* @function
* @since 1.4.0
* @name getSourceEditorValue
* @memberOf jQuery.sceditor.prototype
*/
base.getSourceEditorValue = function (filter) {
var val = $sourceEditor.val();
if(filter !== false && pluginManager.hasHandler('toWysiwyg'))
val = pluginManager.callOnlyFirst('toWysiwyg', val);
return val;
};
/**
* Sets the WYSIWYG HTML editor value. Should only be the HTML
* contained within the body tags
*
* @param {string} value
* @function
* @name setWysiwygEditorValue
* @memberOf jQuery.sceditor.prototype
*/
base.setWysiwygEditorValue = function (value) {
if(!value)
value = '
' + ($.sceditor.ie ? '' : ' ') + '
';
$wysiwygBody[0].innerHTML = value;
replaceEmoticons($wysiwygBody[0]);
appendNewLine();
};
/**
* Sets the text editor value
*
* @param {string} value
* @function
* @name setSourceEditorValue
* @memberOf jQuery.sceditor.prototype
*/
base.setSourceEditorValue = function (value) {
$sourceEditor.val(value);
};
/**
* Updates the textarea that the editor is replacing
* with the value currently inside the editor.
*
* @function
* @name updateOriginal
* @since 1.4.0
* @memberOf jQuery.sceditor.prototype
*/
base.updateOriginal = function() {
$original.val(base.val());
};
/**
* Replaces any emoticon codes in the passed HTML with their emoticon images
* @private
*/
replaceEmoticons = function(node) {
// TODO: Make this tag configurable.
if(!options.emoticonsEnabled || $(node).parents('code').length)
return;
var doc = node.ownerDocument,
emoticonCodes = [],
emoticonRegex = [],
emoticons = $.extend({}, options.emoticons.more, options.emoticons.dropdown, options.emoticons.hidden);
$.each(emoticons, function (key) {
if(options.emoticonsCompat)
emoticonRegex[key] = new RegExp('(>|^|\\s|\xA0|\u2002|\u2003|\u2009| )' + $.sceditor.regexEscape(key) + '(\\s|$|<|\xA0|\u2002|\u2003|\u2009| )');
emoticonCodes.push(key);
});
(function convertEmoticons(node) {
node = node.firstChild;
while(node != null)
{
var parts, key, emoticon, parsedHtml, emoticonIdx, nextSibling, startIdx,
nodeParent = node.parentNode,
nodeValue = node.nodeValue;
// All none textnodes
if(node.nodeType !== 3)
{
// TODO: Make this tag configurable.
if(!$(node).is('code'))
convertEmoticons(node);
}
else if(nodeValue)
{
emoticonIdx = emoticonCodes.length;
while(emoticonIdx--)
{
key = emoticonCodes[emoticonIdx];
startIdx = options.emoticonsCompat ? nodeValue.search(emoticonRegex[key]) : nodeValue.indexOf(key);
if(startIdx > -1)
{
nextSibling = node.nextSibling;
emoticon = emoticons[key];
parts = nodeValue.substr(startIdx).split(key);
nodeValue = nodeValue.substr(0, startIdx) + parts.shift();
node.nodeValue = nodeValue;
parsedHtml = $.sceditor.dom.parseHTML(_tmpl('emoticon', {
key: key,
url: emoticon.url || emoticon,
tooltip: emoticon.tooltip || key
}), doc);
nodeParent.insertBefore(parsedHtml[0], nextSibling);
nodeParent.insertBefore(doc.createTextNode(parts.join(key)), nextSibling);
}
}
}
node = node.nextSibling;
}
}(node));
if(options.emoticonsCompat)
currentEmoticons = $wysiwygBody.find('img[data-sceditor-emoticon]');
};
/**
* If the editor is in source code mode
*
* @return {bool}
* @function
* @name inSourceMode
* @memberOf jQuery.sceditor.prototype
*/
base.inSourceMode = function () {
return $editorContainer.hasClass('sourceMode');
};
/**
* Gets if the editor is in sourceMode
*
* @return boolean
* @function
* @name sourceMode
* @memberOf jQuery.sceditor.prototype
*/
/**
* Sets if the editor is in sourceMode
*
* @param {bool} enable
* @return {this}
* @function
* @name sourceMode^2
* @memberOf jQuery.sceditor.prototype
*/
base.sourceMode = function (enable) {
if(typeof enable !== 'boolean')
return base.inSourceMode();
if((base.inSourceMode() && !enable) || (!base.inSourceMode() && enable))
base.toggleSourceMode();
return this;
};
/**
* Switches between the WYSIWYG and source modes
*
* @function
* @name toggleSourceMode
* @since 1.4.0
* @memberOf jQuery.sceditor.prototype
*/
base.toggleSourceMode = function () {
// don't allow switching to WYSIWYG if doesn't support it
if(!$.sceditor.isWysiwygSupported && base.inSourceMode())
return;
base.blur();
if(base.inSourceMode())
base.setWysiwygEditorValue(base.getSourceEditorValue());
else
base.setSourceEditorValue(base.getWysiwygEditorValue());
lastRange = null;
$sourceEditor.toggle();
$wysiwygEditor.toggle();
if(!base.inSourceMode())
$editorContainer.removeClass('wysiwygMode').addClass('sourceMode');
else
$editorContainer.removeClass('sourceMode').addClass('wysiwygMode');
updateToolBar();
updateActiveButtons();
};
/**
* Gets the selected text of the source editor
* @return {String}
* @private
*/
sourceEditorSelectedText = function () {
sourceEditor.focus();
if(sourceEditor.selectionStart != null)
return sourceEditor.value.substring(sourceEditor.selectionStart, sourceEditor.selectionEnd);
else if(document.selection.createRange)
return document.selection.createRange().text;
};
/**
* Handles the passed command
* @private
*/
handleCommand = function (caller, command) {
// check if in text mode and handle text commands
if(base.inSourceMode())
{
if(command.txtExec)
{
if($.isArray(command.txtExec))
base.sourceEditorInsertText.apply(base, command.txtExec);
else
command.txtExec.call(base, caller, sourceEditorSelectedText());
}
return;
}
if(!command.exec)
return;
if($.isFunction(command.exec))
command.exec.call(base, caller);
else
base.execCommand(command.exec, command.hasOwnProperty('execParam') ? command.execParam : null);
};
/**
* Saves the current range. Needed for IE because it forgets
* where the cursor was and what was selected
* @private
*/
saveRange = function () {
/* this is only needed for IE */
if($.sceditor.ie)
lastRange = rangeHelper.selectedRange();
};
/**
* Executes a command on the WYSIWYG editor
*
* @param {String} command
* @param {String|Boolean} [param]
* @function
* @name execCommand
* @memberOf jQuery.sceditor.prototype
*/
base.execCommand = function (command, param) {
var executed = false,
$parentNode = $(rangeHelper.parentNode());
base.focus();
// don't apply any commands to code elements
if($parentNode.is('code') || $parentNode.parents('code').length !== 0)
return;
try
{
executed = $wysiwygDoc[0].execCommand(command, false, param);
}
catch (e) {}
// show error if execution failed and an error message exists
if(!executed && base.commands[command] && base.commands[command].errorMessage)
alert(base._(base.commands[command].errorMessage));
};
/**
* Checks if the current selection has changed and triggers
* the selectionchanged event if it has.
*
* In browsers other than IE, it will check at most once every 100ms.
* This is because only IE has a selection changed event.
* @private
*/
checkSelectionChanged = function() {
var check = function() {
// rangeHelper could be null if editor was destroyed
// before the timeout had finished
if(rangeHelper && !rangeHelper.compare(currentSelection))
{
currentSelection = rangeHelper.cloneSelected();
$editorContainer.trigger($.Event('selectionchanged'));
}
isSelectionCheckPending = false;
};
if(isSelectionCheckPending)
return;
isSelectionCheckPending = true;
// In IE, this is only called on the selectionchanged event so no need to
// limit checking as it should always be valid to do.
if($.sceditor.ie)
check();
else
setTimeout(check, 100);
};
/**
* Checks if the current node has changed and triggers
* the nodechanged event if it has
* @private
*/
checkNodeChanged = function() {
// check if node has changed
var oldNode,
node = rangeHelper.parentNode();
if(currentNode !== node)
{
oldNode = currentNode;
currentNode = node;
currentBlockNode = rangeHelper.getFirstBlockParent(node);
$editorContainer.trigger($.Event('nodechanged', { oldNode: oldNode, newNode: currentNode }));
}
};
/**
*
Gets the current node that contains the selection/caret in WYSIWYG mode.
*
*
Will be null in sourceMode or if there is no selection.
Gets the first block level node that contains the selection/caret in WYSIWYG mode.
*
*
Will be null in sourceMode or if there is no selection.
* @return {Node}
* @function
* @name currentBlockNode
* @memberOf jQuery.sceditor.prototype
* @since 1.4.4
*/
base.currentBlockNode = function() {
return currentBlockNode;
};
/**
* Updates if buttons are active or not
* @private
*/
updateActiveButtons = function(e) {
var state, stateHandler, firstBlock, $button, parent,
doc = $wysiwygDoc[0],
i = btnStateHandlers.length,
inSourceMode = base.sourceMode();
if(!base.sourceMode() && !base.readOnly())
{
parent = e ? e.newNode : rangeHelper.parentNode();
firstBlock = rangeHelper.getFirstBlockParent(parent);
while(i--)
{
state = 0;
stateHandler = btnStateHandlers[i];
$button = $toolbar.find('.sceditor-button-' + stateHandler.name);
if(inSourceMode && !$button.data('sceditor-txtmode'))
$button.addClass('disabled');
else if (!inSourceMode && !$button.data('sceditor-wysiwygmode'))
$button.addClass('disabled');
else
{
if(typeof stateHandler.state === 'string')
{
try
{
state = doc.queryCommandEnabled(stateHandler.state) ? 0 : -1;
if(state > -1)
state = doc.queryCommandState(stateHandler.state) ? 1 : 0;
}
catch(ex) {}
}
else
state = stateHandler.state.call(base, parent, firstBlock);
if(state < 0)
$button.addClass('disabled');
else
$button.removeClass('disabled');
if(state > 0)
$button.addClass('active');
else
$button.removeClass('active');
}
}
}
else
$toolbar.find('.sceditor-button').removeClass('active');
};
/**
* Handles any key press in the WYSIWYG editor
*
* @private
*/
handleKeyPress = function(e) {
var $parentNode,
i = keyPressFuncs.length;
base.closeDropDown();
$parentNode = $(currentNode);
// "Fix" (OK it's a cludge) for blocklevel elements being duplicated in some browsers when
// enter is pressed instead of inserting a newline
if(e.which === 13)
{
if($parentNode.is('code,blockquote,pre') || $parentNode.parents('code,blockquote,pre').length !== 0)
{
lastRange = null;
base.wysiwygEditorInsertHtml(' ', null, true);
return false;
}
}
// TODO: Remove keyPressFuncs, which are deprecated
// don't apply to code elements
if($parentNode.is('code') || $parentNode.parents('code').length !== 0)
return;
while(i--)
keyPressFuncs[i].call(base, e, wysiwygEditor, $sourceEditor);
};
/**
* Makes sure that if there is a code or quote tag at the
* end of the editor, that there is a new line after it.
*
* If there wasn't a new line at the end you wouldn't be able
* to enter any text after a code/quote tag
* @return {void}
* @private
*/
appendNewLine = function() {
var name, requiresNewLine, div;
$.sceditor.dom.rTraverse($wysiwygBody[0], function(node) {
name = node.nodeName.toLowerCase();
// TODO: Replace requireNewLineFix with just a block level fix for any block that has styling and
// any block that isn't a plain
or
if($.inArray(name, requireNewLineFix) > -1)
requiresNewLine = true;
// find the last non-empty text node or line break.
if((node.nodeType === 3 && !/^\s*$/.test(node.nodeValue)) || name === 'br' ||
($.sceditor.ie && !node.firstChild && !$.sceditor.dom.isInline(node, false)))
{
// this is the last text or br node, if its in a code or quote tag
// then add a newline to the end of the editor
if(requiresNewLine)
{
div = $wysiwygBody[0].ownerDocument.createElement('div');
div.className = 'sceditor-nlf';
div.innerHTML = !$.sceditor.ie ? ' ' : '';
$wysiwygBody[0].appendChild(div);
}
return false;
}
});
};
/**
* Handles form reset event
* @private
*/
handleFormReset = function() {
base.val($original.val());
};
/**
* Handles any mousedown press in the WYSIWYG editor
* @private
*/
handleMouseDown = function() {
base.closeDropDown();
lastRange = null;
};
/**
* Handles the window resize event. Needed to resize then editor
* when the window size changes in fluid designs.
* @ignore
*/
handleWindowResize = function() {
var height = options.height,
width = options.width;
if(!base.maximize())
{
if(height && height.toString().indexOf("%") > -1)
base.height(height);
if(width && width.toString().indexOf("%") > -1)
base.width(width);
}
else
base.dimensions('100%', '100%', false);
};
/**
* Translates the string into the locale language.
*
* Replaces any {0}, {1}, {2}, ect. with the params provided.
*
* @param {string} str
* @param {...String} args
* @return {string}
* @function
* @name _
* @memberOf jQuery.sceditor.prototype
*/
base._ = function() {
var args = arguments;
if(locale && locale[args[0]])
args[0] = locale[args[0]];
return args[0].replace(/\{(\d+)\}/g, function(str, p1) {
return typeof args[p1-0+1] !== 'undefined' ?
args[p1-0+1] :
'{' + p1 + '}';
});
};
/**
* Passes events on to any handlers
* @private
* @return void
*/
handleEvent = function(e) {
var customEvent,
clone = $.extend({}, e);
// Send event to all plugins
pluginManager.call(clone.type + 'Event', e, base);
// convert the event into a custom event to send
delete clone.type;
customEvent = $.Event((e.target === sourceEditor ? 'scesrc' : 'scewys') + e.type, clone);
$editorContainer.trigger.apply($editorContainer, [customEvent, base]);
if(customEvent.isDefaultPrevented())
e.preventDefault();
if(customEvent.isImmediatePropagationStopped())
customEvent.stopImmediatePropagation();
if(customEvent.isPropagationStopped())
customEvent.stopPropagation();
};
/**
*
Binds a handler to the specified events
*
*
This function only binds to a limited list of supported events.
* The supported events are:
*
*
keyup
*
keydown
*
Keypress
*
blur
*
focus
*
nodechanged
* When the current node containing the selection changes in WYSIWYG mode
*
contextmenu
*
*
*
*
The events param should be a string containing the event(s)
* to bind this handler to. If multiple, they should be separated
* by spaces.
*
* @param {String} events
* @param {Function} handler
* @param {Boolean} excludeWysiwyg If to exclude adding this handler to the WYSIWYG editor
* @param {Boolean} excludeSource if to exclude adding this handler to the source editor
* @return {this}
* @function
* @name bind
* @memberOf jQuery.sceditor.prototype
* @since 1.4.1
*/
base.bind = function(events, handler, excludeWysiwyg, excludeSource) {
var i = events.length;
events = events.split(" ");
while(i--)
{
if($.isFunction(handler))
{
// Use custom events to allow passing the instance as the 2nd argument.
// Also allows unbinding without unbinding the editors own event handlers.
if(!excludeWysiwyg)
$editorContainer.bind('scewys' + events[i], handler);
if(!excludeSource)
$editorContainer.bind('scesrc' + events[i], handler);
}
}
return this;
};
/**
* Unbinds an event that was bound using bind().
*
* @param {String} events
* @param {Function} handler
* @param {Boolean} excludeWysiwyg If to exclude unbinding this handler from the WYSIWYG editor
* @param {Boolean} excludeSource if to exclude unbinding this handler from the source editor
* @return {this}
* @function
* @name unbind
* @memberOf jQuery.sceditor.prototype
* @since 1.4.1
* @see bind
*/
base.unbind = function(events, handler, excludeWysiwyg, excludeSource) {
var i = events.length;
events = events.split(" ");
while(i--)
{
if($.isFunction(handler))
{
if(!excludeWysiwyg)
$editorContainer.unbind('scewys' + events[i], handler);
if(!excludeSource)
$editorContainer.unbind('scesrc' + events[i], handler);
}
}
return this;
};
/**
* Blurs the editors input area
*
* @return {this}
* @function
* @name blur
* @memberOf jQuery.sceditor.prototype
* @since 1.3.6
*/
/**
* Adds a handler to the editors blur event
*
* @param {Function} handler
* @param {Boolean} excludeWysiwyg If to exclude adding this handler to the WYSIWYG editor
* @param {Boolean} excludeSource if to exclude adding this handler to the source editor
* @return {this}
* @function
* @name blur^2
* @memberOf jQuery.sceditor.prototype
* @since 1.4.1
*/
base.blur = function(handler, excludeWysiwyg, excludeSource) {
if($.isFunction(handler))
base.bind('blur', handler, excludeWysiwyg, excludeSource);
else if(!base.sourceMode())
{
// Must use an element that isn't display:hidden or visibility:hidden for iOS
// so create a special blur element to use
if(!$blurElm)
$blurElm = $('').appendTo($editorContainer);
$blurElm.removeAttr('disabled').show().focus().blur().hide().attr('disabled', 'disabled');
}
else
$sourceEditor.blur();
return this;
};
/**
* Fucuses the editors input area
*
* @return {this}
* @function
* @name focus
* @memberOf jQuery.sceditor.prototype
*/
/**
* Adds an event handler to the focus event
*
* @param {Function} handler
* @param {Boolean} excludeWysiwyg If to exclude adding this handler to the WYSIWYG editor
* @param {Boolean} excludeSource if to exclude adding this handler to the source editor
* @return {this}
* @function
* @name focus^2
* @memberOf jQuery.sceditor.prototype
* @since 1.4.1
*/
base.focus = function (handler, excludeWysiwyg, excludeSource) {
if($.isFunction(handler))
base.bind('focus', handler, excludeWysiwyg, excludeSource);
else
{
if(!base.inSourceMode())
{
wysiwygEditor.contentWindow.focus();
$wysiwygBody[0].focus();
// Needed for IE < 9
if(lastRange)
{
rangeHelper.selectRange(lastRange);
// remove the stored range after being set.
// If the editor loses focus it should be
// saved again.
lastRange = null;
}
}
else
sourceEditor.focus();
}
return this;
};
/**
* Adds a handler to the key down event
*
* @param {Function} handler
* @param {Boolean} excludeWysiwyg If to exclude adding this handler to the WYSIWYG editor
* @param {Boolean} excludeSource if to exclude adding this handler to the source editor
* @return {this}
* @function
* @name keyDown
* @memberOf jQuery.sceditor.prototype
* @since 1.4.1
*/
base.keyDown = function(handler, excludeWysiwyg, excludeSource) {
return base.bind('keydown', handler, excludeWysiwyg, excludeSource);
};
/**
* Adds a handler to the key press event
*
* @param {Function} handler
* @param {Boolean} excludeWysiwyg If to exclude adding this handler to the WYSIWYG editor
* @param {Boolean} excludeSource if to exclude adding this handler to the source editor
* @return {this}
* @function
* @name keyPress
* @memberOf jQuery.sceditor.prototype
* @since 1.4.1
*/
base.keyPress = function(handler, excludeWysiwyg, excludeSource) {
return base.bind('keypress', handler, excludeWysiwyg, excludeSource);
};
/**
* Adds a handler to the key up event
*
* @param {Function} handler
* @param {Boolean} excludeWysiwyg If to exclude adding this handler to the WYSIWYG editor
* @param {Boolean} excludeSource if to exclude adding this handler to the source editor
* @return {this}
* @function
* @name keyUp
* @memberOf jQuery.sceditor.prototype
* @since 1.4.1
*/
base.keyUp = function(handler, excludeWysiwyg, excludeSource) {
return base.bind('keyup', handler, excludeWysiwyg, excludeSource);
};
/**
*
Adds a handler to the node changed event.
*
*
Happends whenever the node containing the selection/caret changes in WYSIWYG mode.