|
@@ -0,0 +1,3728 @@
|
|
|
+/**
|
|
|
+ * SCEditor
|
|
|
+ * 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
|
|
|
+ *
|
|
|
+ * @fileoverview SCEditor - A lightweight WYSIWYG BBCode and HTML editor
|
|
|
+ * @author Sam Clarke
|
|
|
+ * @version 1.3.7
|
|
|
+ * @requires jQuery
|
|
|
+ */
|
|
|
+
|
|
|
+// ==ClosureCompiler==
|
|
|
+// @output_file_name jquery.sceditor.min.js
|
|
|
+// @compilation_level SIMPLE_OPTIMIZATIONS
|
|
|
+// ==/ClosureCompiler==
|
|
|
+
|
|
|
+/*jshint smarttabs: true, scripturl: true, jquery: true, devel:true, eqnull:true, curly: false */
|
|
|
+/*global XMLSerializer: true*/
|
|
|
+
|
|
|
+;(function ($, window, document) {
|
|
|
+ 'use strict';
|
|
|
+
|
|
|
+ var _templates = {
|
|
|
+ html: '<!DOCTYPE html>' +
|
|
|
+ '<html>' +
|
|
|
+ '<head>' +
|
|
|
+ '<!--[if IE]><style>* {min-height: auto !important}</style><![endif]-->' +
|
|
|
+ '<meta http-equiv="Content-Type" content="text/html;charset={charset}" />' +
|
|
|
+ '<link rel="stylesheet" type="text/css" href="{style}" />' +
|
|
|
+ '</head>' +
|
|
|
+ '<body contenteditable="true"></body>' +
|
|
|
+ '</html>',
|
|
|
+
|
|
|
+ toolbarButton: '<a class="sceditor-button sceditor-button-{name}" data-sceditor-command="{name}" unselectable="on"><div unselectable="on">{dispName}</div></a>',
|
|
|
+
|
|
|
+ emoticon: '<img src="{url}" data-sceditor-emoticon="{key}" alt="{key}" />',
|
|
|
+
|
|
|
+ fontOpt: '<a class="sceditor-font-option" href="#" data-font="{font}"><font face="{font}">{font}</font></a>',
|
|
|
+
|
|
|
+ sizeOpt: '<a class="sceditor-fontsize-option" data-size="{size}" style="line-height:{points}pt" href="#"><font size="{size}">{size}</font></a>',
|
|
|
+
|
|
|
+ pastetext: '<div><label for="txt">{label}</label> ' +
|
|
|
+ '<textarea cols="20" rows="7" id="txt"></textarea></div>' +
|
|
|
+ '<div><input type="button" class="button" value="{insert}" /></div>',
|
|
|
+
|
|
|
+ table: '<div><label for="rows">{rows}</label><input type="text" id="rows" value="2" /></div>' +
|
|
|
+ '<div><label for="cols">{cols}</label><input type="text" id="cols" value="2" /></div>' +
|
|
|
+ '<div><input type="button" class="button" value="{insert}" /></div>',
|
|
|
+
|
|
|
+ image: '<div><label for="link">{url}</label> <input type="text" id="image" value="http://" /></div>' +
|
|
|
+ '<div><label for="width">{width}</label> <input type="text" id="width" size="2" /></div>' +
|
|
|
+ '<div><label for="height">{height}</label> <input type="text" id="height" size="2" /></div>' +
|
|
|
+ '<div><input type="button" class="button" value="{insert}" /></div>',
|
|
|
+
|
|
|
+ email: '<div><label for="email">{label}</label> <input type="text" id="email" /></div>' +
|
|
|
+ '<div><input type="button" class="button" value="{insert}" /></div>',
|
|
|
+
|
|
|
+ link: '<div><label for="link">{url}</label> <input type="text" id="link" value="http://" /></div>' +
|
|
|
+ '<div><label for="des">{desc}</label> <input type="text" id="des" /></div>' +
|
|
|
+ '<div><input type="button" class="button" value="{ins}" /></div>',
|
|
|
+
|
|
|
+ youtubeMenu: '<div><label for="link">{label}</label> <input type="text" id="link" value="http://" /></div><div><input type="button" class="button" value="{insert}" /></div>',
|
|
|
+
|
|
|
+ youtube: '<iframe width="560" height="315" src="http://www.youtube.com/embed/{id}?wmode=opaque" data-youtube-id="{id}" frameborder="0" allowfullscreen></iframe>'
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * <p>Replaces any params in a template with the passed params.</p>
|
|
|
+ *
|
|
|
+ * <p>If createHTML is passed it will use jQuery to create the HTML. The
|
|
|
+ * same as doing: $(editor.tmpl("html", {params...}));</p>
|
|
|
+ *
|
|
|
+ * @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 $textarea = $(el);
|
|
|
+ var textarea = el;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 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 editors textarea for viewing source
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ var $textEditor;
|
|
|
+ var textEditor;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The current dropdown
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ var $dropdown;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Array of all the commands key press functions
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ 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
|
|
|
+ */
|
|
|
+ var preLoadCache = [];
|
|
|
+
|
|
|
+ var rangeHelper;
|
|
|
+
|
|
|
+ var $blurElm;
|
|
|
+
|
|
|
+ var init,
|
|
|
+ replaceEmoticons,
|
|
|
+ handleCommand,
|
|
|
+ saveRange,
|
|
|
+ handlePasteEvt,
|
|
|
+ handlePasteData,
|
|
|
+ handleKeyPress,
|
|
|
+ handleFormReset,
|
|
|
+ handleMouseDown,
|
|
|
+ initEditor,
|
|
|
+ initToolBar,
|
|
|
+ initKeyPressFuncs,
|
|
|
+ initResize,
|
|
|
+ documentClickHandler,
|
|
|
+ formSubmitHandler,
|
|
|
+ initEmoticons,
|
|
|
+ getWysiwygDoc,
|
|
|
+ handleWindowResize,
|
|
|
+ initLocale,
|
|
|
+ updateToolBar,
|
|
|
+ textEditorSelectedText,
|
|
|
+ autofocus;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * All the commands supported by the editor
|
|
|
+ */
|
|
|
+ base.commands = $.extend({}, (options.commands || $.sceditor.commands));
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Initializer. Creates the editor iframe and textarea
|
|
|
+ * @private
|
|
|
+ * @name sceditor.init
|
|
|
+ */
|
|
|
+ init = function () {
|
|
|
+ $textarea.data("sceditor", base);
|
|
|
+ base.options = $.extend({}, $.sceditor.defaultOptions, options);
|
|
|
+
|
|
|
+ // Load locale
|
|
|
+ if(base.options.locale && base.options.locale !== "en")
|
|
|
+ initLocale();
|
|
|
+
|
|
|
+ // if either width or height are % based, add the resize handler to update the editor
|
|
|
+ // when the window is resized
|
|
|
+ var h = base.options.height, w = base.options.width;
|
|
|
+ if((h && (h + "").indexOf("%") > -1) || (w && (w + "").indexOf("%") > -1))
|
|
|
+ $(window).resize(handleWindowResize);
|
|
|
+
|
|
|
+ $editorContainer = $('<div class="sceditor-container" />').insertAfter($textarea);
|
|
|
+
|
|
|
+ // create the editor
|
|
|
+ initToolBar();
|
|
|
+ initEditor();
|
|
|
+ initKeyPressFuncs();
|
|
|
+
|
|
|
+ if(base.options.resizeEnabled)
|
|
|
+ initResize();
|
|
|
+
|
|
|
+ if(base.options.id)
|
|
|
+ $editorContainer.attr('id', base.options.id);
|
|
|
+
|
|
|
+ $(document).click(documentClickHandler);
|
|
|
+ $(textarea.form)
|
|
|
+ .attr('novalidate','novalidate')
|
|
|
+ .bind("reset", handleFormReset)
|
|
|
+ .submit(formSubmitHandler);
|
|
|
+
|
|
|
+ // load any textarea value into the editor
|
|
|
+ base.val($textarea.hide().val());
|
|
|
+/*
|
|
|
+ // Pass the value though the getTextHandler if it is set so that
|
|
|
+ // BBCode, ect. can be converted
|
|
|
+ if(base.options.getTextHandler && base.options.supportedWysiwyg)
|
|
|
+ {
|
|
|
+ val = base.options.getTextHandler(val);
|
|
|
+
|
|
|
+ base.setWysiwygEditorValue(val);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ base.toggleTextMode();
|
|
|
+ base.setTextareaValue(val);
|
|
|
+ }
|
|
|
+*/
|
|
|
+ if(base.options.autofocus)
|
|
|
+ autofocus();
|
|
|
+
|
|
|
+ // force into source mode if is a browser that can't handle
|
|
|
+ // full editing
|
|
|
+ if(!$.sceditor.isWysiwygSupported())
|
|
|
+ base.toggleTextMode();
|
|
|
+
|
|
|
+ if(base.options.toolbar.indexOf('emoticon') !== -1)
|
|
|
+ initEmoticons();
|
|
|
+
|
|
|
+ // Can't use load event as it gets fired before CSS is loaded
|
|
|
+ // in some browsers
|
|
|
+ if(base.options.autoExpand)
|
|
|
+ var interval = setInterval(function() {
|
|
|
+ if (!document.readyState || document.readyState === "complete") {
|
|
|
+ base.expandToContent();
|
|
|
+ clearInterval(interval);
|
|
|
+ }
|
|
|
+ }, 10);
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Creates the editor iframe and textarea
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ initEditor = function () {
|
|
|
+ var $doc, $body;
|
|
|
+
|
|
|
+ $textEditor = $('<textarea></textarea>').hide();
|
|
|
+ $wysiwygEditor = $('<iframe frameborder="0"></iframe>');
|
|
|
+
|
|
|
+ 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($textEditor);
|
|
|
+ wysiwygEditor = $wysiwygEditor[0];
|
|
|
+ textEditor = $textEditor[0];
|
|
|
+
|
|
|
+ base.width(base.options.width || $textarea.width());
|
|
|
+ base.height(base.options.height || $textarea.height());
|
|
|
+
|
|
|
+ getWysiwygDoc().open();
|
|
|
+ getWysiwygDoc().write(_tmpl("html", {
|
|
|
+ charset: base.options.charset,
|
|
|
+ style: base.options.style
|
|
|
+ }));
|
|
|
+ getWysiwygDoc().close();
|
|
|
+
|
|
|
+ base.readOnly(!!base.options.readOnly);
|
|
|
+
|
|
|
+ $doc = $(getWysiwygDoc());
|
|
|
+ $body = $doc.find("body");
|
|
|
+
|
|
|
+ // Add IE version class to the HTML element so can apply
|
|
|
+ // conditional styling without CSS hacks
|
|
|
+ if($.sceditor.ie)
|
|
|
+ $doc.find("html").addClass('ie' + $.sceditor.ie);
|
|
|
+
|
|
|
+ // iframe overflow fix
|
|
|
+ if(/iPhone|iPod|iPad| wosbrowser\//i.test(navigator.userAgent))
|
|
|
+ $body.height('100%');
|
|
|
+
|
|
|
+ // set the key press event
|
|
|
+ $body.keypress(handleKeyPress);
|
|
|
+ $doc.keypress(handleKeyPress)
|
|
|
+ .mousedown(handleMouseDown)
|
|
|
+ .bind("beforedeactivate keyup", saveRange)
|
|
|
+ .focus(function() {
|
|
|
+ lastRange = null;
|
|
|
+ });
|
|
|
+
|
|
|
+ if(base.options.rtl)
|
|
|
+ {
|
|
|
+ $body.attr('dir', 'rtl');
|
|
|
+ $textEditor.attr('dir', 'rtl');
|
|
|
+ }
|
|
|
+
|
|
|
+ if(base.options.enablePasteFiltering)
|
|
|
+ $body.bind("paste", handlePasteEvt);
|
|
|
+
|
|
|
+ if(base.options.autoExpand)
|
|
|
+ $doc.bind("keyup", base.expandToContent);
|
|
|
+
|
|
|
+ rangeHelper = new $.sceditor.rangeHelper(wysiwygEditor.contentWindow);
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Creates the toolbar and appends it to the container
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ initToolBar = function () {
|
|
|
+ var $group, $button, buttons,
|
|
|
+ i, x, buttonClick,
|
|
|
+ groups = base.options.toolbar.split("|");
|
|
|
+
|
|
|
+ buttonClick = function () {
|
|
|
+ var self = $(this);
|
|
|
+
|
|
|
+ if(!self.hasClass('disabled'))
|
|
|
+ handleCommand(self, base.commands[self.data("sceditor-command")]);
|
|
|
+
|
|
|
+ return false;
|
|
|
+ };
|
|
|
+
|
|
|
+ $toolbar = $('<div class="sceditor-toolbar" />');
|
|
|
+ var rows = base.options.toolbar.split("||");
|
|
|
+ for (var r=0; r < rows.length; r++) {
|
|
|
+ var row = $('<div class="sceditor-row" />');
|
|
|
+ var groups = rows[r].split("|"),
|
|
|
+ buttons, accessibilityName, button, i;
|
|
|
+
|
|
|
+ for (i=0; i < groups.length; i++) {
|
|
|
+ $group = $('<div class="sceditor-group" />');
|
|
|
+ buttons = groups[i].split(",");
|
|
|
+
|
|
|
+ for (x=0; x < buttons.length; x++) {
|
|
|
+ // the button must be a valid command otherwise ignore it
|
|
|
+ if(!base.commands[buttons[x]])
|
|
|
+ continue;
|
|
|
+
|
|
|
+ $button = _tmpl("toolbarButton", {
|
|
|
+ name: buttons[x],
|
|
|
+ dispName: base.commands[buttons[x]].tooltip || buttons[x]
|
|
|
+ }, true).click(buttonClick);
|
|
|
+
|
|
|
+ if(base.commands[buttons[x]].hasOwnProperty("tooltip"))
|
|
|
+ $button.attr('title', base._(base.commands[buttons[x]].tooltip));
|
|
|
+
|
|
|
+ if(base.commands[buttons[x]].exec)
|
|
|
+ $button.data('sceditor-wysiwygmode', true);
|
|
|
+ else
|
|
|
+ $button.addClass('disabled');
|
|
|
+
|
|
|
+ if(base.commands[buttons[x]].txtExec)
|
|
|
+ $button.data('sceditor-txtmode', true);
|
|
|
+
|
|
|
+ $group.append($button);
|
|
|
+ }
|
|
|
+ row.append($group);
|
|
|
+ }
|
|
|
+ $toolbar.append(row);
|
|
|
+ }
|
|
|
+
|
|
|
+ // append the toolbar to the toolbarContainer option if given
|
|
|
+ if(base.options.toolbarContainer)
|
|
|
+ $(base.options.toolbarContainer).append($toolbar);
|
|
|
+ else
|
|
|
+ $editorContainer.append($toolbar);
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Autofocus the editor
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ autofocus = function() {
|
|
|
+ var doc = wysiwygEditor.contentWindow.document,
|
|
|
+ body = doc.body, rng;
|
|
|
+
|
|
|
+ if(!doc.createRange)
|
|
|
+ return base.focus();
|
|
|
+
|
|
|
+ if(!body.firstChild)
|
|
|
+ return;
|
|
|
+
|
|
|
+ rng = doc.createRange();
|
|
|
+ rng.setStart(body.firstChild, 0);
|
|
|
+ rng.setEnd(body.firstChild, 0);
|
|
|
+
|
|
|
+ rangeHelper.selectRange(rng);
|
|
|
+ body.focus();
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Gets the readOnly property of the editor
|
|
|
+ *
|
|
|
+ * @since 1.3.5
|
|
|
+ * @function
|
|
|
+ * @memberOf jQuery.sceditor.prototype
|
|
|
+ * @name readOnly
|
|
|
+ * @return {boolean}
|
|
|
+ */
|
|
|
+ /**
|
|
|
+ * Sets the readOnly property of the editor
|
|
|
+ *
|
|
|
+ * @param {boolean} readOnly
|
|
|
+ * @since 1.3.5
|
|
|
+ * @function
|
|
|
+ * @memberOf jQuery.sceditor.prototype
|
|
|
+ * @name readOnly^2
|
|
|
+ * @return {this}
|
|
|
+ */
|
|
|
+ base.readOnly = function(readOnly) {
|
|
|
+ if(typeof readOnly !== 'boolean')
|
|
|
+ return $textEditor.attr('readonly') === 'readonly';
|
|
|
+
|
|
|
+ getWysiwygDoc().body.contentEditable = !readOnly;
|
|
|
+
|
|
|
+ if(!readOnly)
|
|
|
+ $textEditor.removeAttr('readonly');
|
|
|
+ else
|
|
|
+ $textEditor.attr('readonly', 'readonly');
|
|
|
+
|
|
|
+ updateToolBar(readOnly);
|
|
|
+
|
|
|
+ return this;
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Updates the toolbar to disable/enable the appropriate buttons
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ updateToolBar = function(disable) {
|
|
|
+ $toolbar.find('.sceditor-button').removeClass('disabled');
|
|
|
+
|
|
|
+ $toolbar.find('.sceditor-button').each(function () {
|
|
|
+ var button = $(this);
|
|
|
+
|
|
|
+ if(disable === true)
|
|
|
+ button.addClass('disabled');
|
|
|
+ else if(base.inSourceMode() && !button.data('sceditor-txtmode'))
|
|
|
+ button.addClass('disabled');
|
|
|
+ else if (!base.inSourceMode() && !button.data('sceditor-wysiwygmode'))
|
|
|
+ button.addClass('disabled');
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Creates an array of all the key press functions
|
|
|
+ * like emoticons, ect.
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ initKeyPressFuncs = function () {
|
|
|
+ $.each(base.commands, function (command, values) {
|
|
|
+ if(values.keyPress)
|
|
|
+ keyPressFuncs.push(values.keyPress);
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Gets the width of the editor in px
|
|
|
+ *
|
|
|
+ * @since 1.3.5
|
|
|
+ * @function
|
|
|
+ * @memberOf jQuery.sceditor.prototype
|
|
|
+ * @name width
|
|
|
+ * @return {int}
|
|
|
+ */
|
|
|
+ /**
|
|
|
+ * Sets the width of the editor
|
|
|
+ *
|
|
|
+ * @param {int} width Width in px
|
|
|
+ * @since 1.3.5
|
|
|
+ * @function
|
|
|
+ * @memberOf jQuery.sceditor.prototype
|
|
|
+ * @name width^2
|
|
|
+ * @return {this}
|
|
|
+ */
|
|
|
+ base.width = function (width) {
|
|
|
+ if(!width)
|
|
|
+ return $editorContainer.width();
|
|
|
+
|
|
|
+ $editorContainer.width(width);
|
|
|
+
|
|
|
+ // fix the height and width of the textarea/iframe
|
|
|
+ $wysiwygEditor.width(width);
|
|
|
+ $wysiwygEditor.width(width + (width - $wysiwygEditor.outerWidth(true)));
|
|
|
+
|
|
|
+ $textEditor.width(width);
|
|
|
+ $textEditor.width(width + (width - $textEditor.outerWidth(true)));
|
|
|
+
|
|
|
+ return this;
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 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}
|
|
|
+ */
|
|
|
+ base.height = function (height) {
|
|
|
+ if(!height)
|
|
|
+ return $editorContainer.height();
|
|
|
+
|
|
|
+ $editorContainer.height(height);
|
|
|
+
|
|
|
+ height -= !base.options.toolbarContainer ? $toolbar.outerHeight(true) : 0;
|
|
|
+
|
|
|
+ // fix the height and width of the textarea/iframe
|
|
|
+ $wysiwygEditor.height(height);
|
|
|
+ $wysiwygEditor.height(height + (height - $wysiwygEditor.outerHeight(true)));
|
|
|
+
|
|
|
+ $textEditor.height(height);
|
|
|
+ $textEditor.height(height + (height - $textEditor.outerHeight(true)));
|
|
|
+
|
|
|
+ return this;
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Expands the editor to the size of it's content
|
|
|
+ *
|
|
|
+ * @since 1.3.5
|
|
|
+ * @param {Boolean} [ignoreMaxHeight=false]
|
|
|
+ * @function
|
|
|
+ * @name expandToContent
|
|
|
+ * @memberOf jQuery.sceditor.prototype
|
|
|
+ * @see #resizeToContent
|
|
|
+ */
|
|
|
+ base.expandToContent = function(ignoreMaxHeight) {
|
|
|
+ var doc = getWysiwygDoc(),
|
|
|
+ currentHeight = $editorContainer.height(),
|
|
|
+ height = doc.body.scrollHeight || doc.documentElement.scrollHeight,
|
|
|
+ padding = (currentHeight - $wysiwygEditor.height()),
|
|
|
+ maxHeight = base.options.resizeMaxHeight || ((base.options.height || $textarea.height()) * 2);
|
|
|
+
|
|
|
+ height += padding;
|
|
|
+
|
|
|
+ if(ignoreMaxHeight !== true && height > maxHeight)
|
|
|
+ height = maxHeight;
|
|
|
+
|
|
|
+ if(height > currentHeight)
|
|
|
+ base.height(height);
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Creates the resizer.
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ initResize = function () {
|
|
|
+ var $grip = $('<div class="sceditor-grip" />'),
|
|
|
+ // cover is used to cover the editor iframe so document still gets mouse move events
|
|
|
+ $cover = $('<div class="sceditor-resize-cover" />'),
|
|
|
+ startX = 0,
|
|
|
+ startY = 0,
|
|
|
+ startWidth = 0,
|
|
|
+ startHeight = 0,
|
|
|
+ origWidth = $editorContainer.width(),
|
|
|
+ origHeight = $editorContainer.height(),
|
|
|
+ dragging = false,
|
|
|
+ minHeight, maxHeight, minWidth, maxWidth, mouseMoveFunc;
|
|
|
+
|
|
|
+ minHeight = base.options.resizeMinHeight || origHeight / 1.5;
|
|
|
+ maxHeight = base.options.resizeMaxHeight || origHeight * 2.5;
|
|
|
+ minWidth = base.options.resizeMinWidth || origWidth / 1.25;
|
|
|
+ maxWidth = base.options.resizeMaxWidth || origWidth * 1.25;
|
|
|
+
|
|
|
+ mouseMoveFunc = function (e) {
|
|
|
+ var newHeight = startHeight + (e.pageY - startY),
|
|
|
+ newWidth = startWidth + (e.pageX - startX);
|
|
|
+
|
|
|
+ if (newWidth >= minWidth && (maxWidth < 0 || newWidth <= maxWidth))
|
|
|
+ base.width(newWidth);
|
|
|
+
|
|
|
+ if (newHeight >= minHeight && (maxHeight < 0 || newHeight <= maxHeight))
|
|
|
+ base.height(newHeight);
|
|
|
+
|
|
|
+ e.preventDefault();
|
|
|
+ };
|
|
|
+
|
|
|
+ $editorContainer.append($grip);
|
|
|
+ $editorContainer.append($cover.hide());
|
|
|
+
|
|
|
+ $grip.mousedown(function (e) {
|
|
|
+ startX = e.pageX;
|
|
|
+ startY = e.pageY;
|
|
|
+ startWidth = $editorContainer.width();
|
|
|
+ startHeight = $editorContainer.height();
|
|
|
+ dragging = true;
|
|
|
+
|
|
|
+ $editorContainer.addClass('resizing');
|
|
|
+ $cover.show();
|
|
|
+ $(document).bind('mousemove', mouseMoveFunc);
|
|
|
+ e.preventDefault();
|
|
|
+ });
|
|
|
+
|
|
|
+ $(document).mouseup(function (e) {
|
|
|
+ if(!dragging)
|
|
|
+ return;
|
|
|
+
|
|
|
+ dragging = false;
|
|
|
+ $cover.hide();
|
|
|
+
|
|
|
+ $editorContainer.removeClass('resizing');
|
|
|
+ $(document).unbind('mousemove', mouseMoveFunc);
|
|
|
+ e.preventDefault();
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Handles the forms submit event
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ formSubmitHandler = function(e) {
|
|
|
+ base.updateTextareaValue();
|
|
|
+ $(this).removeAttr('novalidate');
|
|
|
+
|
|
|
+ if(this.checkValidity && !this.checkValidity())
|
|
|
+ e.preventDefault();
|
|
|
+
|
|
|
+ $(this).attr('novalidate','novalidate');
|
|
|
+ base.blur();
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Destroys the editor, removing all elements and
|
|
|
+ * event handlers.
|
|
|
+ *
|
|
|
+ * @function
|
|
|
+ * @name destory
|
|
|
+ * @memberOf jQuery.sceditor.prototype
|
|
|
+ */
|
|
|
+ base.destory = function () {
|
|
|
+ $(document).unbind('click', documentClickHandler);
|
|
|
+ $(window).unbind('resize', handleWindowResize);
|
|
|
+
|
|
|
+ $(textarea.form).removeAttr('novalidate')
|
|
|
+ .unbind('submit', formSubmitHandler)
|
|
|
+ .unbind("reset", handleFormReset);
|
|
|
+
|
|
|
+ $(getWysiwygDoc()).find('*').remove();
|
|
|
+ $(getWysiwygDoc()).unbind("keypress mousedown beforedeactivate keyup focus paste keypress");
|
|
|
+
|
|
|
+ $editorContainer.find('*').remove();
|
|
|
+ $editorContainer.remove();
|
|
|
+
|
|
|
+ $textarea.removeData("sceditor").removeData("sceditorbbcode").show();
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Preloads the emoticon images
|
|
|
+ * Idea from: http://engineeredweb.com/blog/09/12/preloading-images-jquery-and-javascript
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ initEmoticons = function () {
|
|
|
+ // prefix emoticon root to emoticon urls
|
|
|
+ if(base.options.emoticonsRoot && base.options.emoticons)
|
|
|
+ {
|
|
|
+ $.each(base.options.emoticons, function (idx, emoticons) {
|
|
|
+ $.each(emoticons, function (key, url) {
|
|
|
+ base.options.emoticons[idx][key] = base.options.emoticonsRoot + url;
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ var emoticons = $.extend({}, base.options.emoticons.more, base.options.emoticons.dropdown, base.options.emoticons.hidden),
|
|
|
+ emoticon;
|
|
|
+
|
|
|
+ $.each(emoticons, function (key, url) {
|
|
|
+ // @todo Why did I (emanuele) add this?
|
|
|
+ if (key == '')
|
|
|
+ emoticon = document.createElement('br');
|
|
|
+ else
|
|
|
+ {
|
|
|
+ emoticon = document.createElement('img');
|
|
|
+ emoticon.src = url;
|
|
|
+ }
|
|
|
+ preLoadCache.push(emoticon);
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 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 string content The HTML content of the dropdown
|
|
|
+ * @param bool ieUnselectable 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) {
|
|
|
+ base.closeDropDown();
|
|
|
+
|
|
|
+ // 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');
|
|
|
+ }
|
|
|
+
|
|
|
+ var css = {
|
|
|
+ top: menuItem.offset().top,
|
|
|
+ left: menuItem.offset().left
|
|
|
+ };
|
|
|
+
|
|
|
+ $.extend(css, base.options.dropDownCss);
|
|
|
+
|
|
|
+ $dropdown = $('<div class="sceditor-dropdown sceditor-' + dropDownName + '" />')
|
|
|
+ .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
|
|
|
+ */
|
|
|
+ documentClickHandler = function (e) {
|
|
|
+ // ignore right clicks
|
|
|
+ if(e.which !== 3)
|
|
|
+ base.closeDropDown();
|
|
|
+ };
|
|
|
+
|
|
|
+ handlePasteEvt = function(e) {
|
|
|
+ var elm = getWysiwygDoc().body,
|
|
|
+ checkCount = 0,
|
|
|
+ pastearea = elm.ownerDocument.createElement('div'),
|
|
|
+ prePasteContent = elm.ownerDocument.createDocumentFragment();
|
|
|
+
|
|
|
+ rangeHelper.saveRange();
|
|
|
+ document.body.appendChild(pastearea);
|
|
|
+
|
|
|
+ if (e && e.clipboardData && e.clipboardData.getData)
|
|
|
+ {
|
|
|
+ var html, handled=true;
|
|
|
+
|
|
|
+ if ((html = e.clipboardData.getData('text/html')) || (html = e.clipboardData.getData('text/plain')))
|
|
|
+ pastearea.innerHTML = html;
|
|
|
+ else
|
|
|
+ handled = false;
|
|
|
+
|
|
|
+ if(handled)
|
|
|
+ {
|
|
|
+ handlePasteData(elm, pastearea);
|
|
|
+
|
|
|
+ if (e.preventDefault)
|
|
|
+ {
|
|
|
+ e.stopPropagation();
|
|
|
+ e.preventDefault();
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ while(elm.firstChild)
|
|
|
+ prePasteContent.appendChild(elm.firstChild);
|
|
|
+
|
|
|
+ function handlePaste(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 inscase empty input is posted or
|
|
|
+ // something gose wrong.
|
|
|
+ if(checkCount > 25)
|
|
|
+ {
|
|
|
+ while(prePasteContent.firstChild)
|
|
|
+ elm.appendChild(prePasteContent.firstChild);
|
|
|
+
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ ++checkCount;
|
|
|
+ setTimeout(function () {
|
|
|
+ handlePaste(elm, pastearea);
|
|
|
+ }, 20);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ handlePaste(elm, pastearea);
|
|
|
+
|
|
|
+ base.focus();
|
|
|
+
|
|
|
+ return true;
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param {Element} elm
|
|
|
+ * @param {Element} pastearea
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ handlePasteData = function(elm, pastearea) {
|
|
|
+ // fix any invalid nesting
|
|
|
+ $.sceditor.dom.fixNesting(pastearea);
|
|
|
+
|
|
|
+ var pasteddata = pastearea.innerHTML;
|
|
|
+
|
|
|
+ if(base.options.getHtmlHandler)
|
|
|
+ pasteddata = base.options.getHtmlHandler(pasteddata, $(pastearea));
|
|
|
+
|
|
|
+ pastearea.parentNode.removeChild(pastearea);
|
|
|
+
|
|
|
+ if(base.options.getTextHandler)
|
|
|
+ pasteddata = base.options.getTextHandler(pasteddata, true);
|
|
|
+
|
|
|
+ rangeHelper.restoreRange();
|
|
|
+ rangeHelper.insertHTML(pasteddata);
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Closes the current drop down
|
|
|
+ *
|
|
|
+ * @param bool focus If to focus the editor on close
|
|
|
+ * @function
|
|
|
+ * @name closeDropDown
|
|
|
+ * @memberOf jQuery.sceditor.prototype
|
|
|
+ */
|
|
|
+ base.closeDropDown = function (focus) {
|
|
|
+ if($dropdown) {
|
|
|
+ $dropdown.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;
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * <p>Inserts HTML into WYSIWYG editor.</p>
|
|
|
+ *
|
|
|
+ * <p>If endHtml is specified instead of the inserted HTML replacing the selected
|
|
|
+ * text the selected text will be placed between html and endHtml. If there is
|
|
|
+ * no selected text html and endHtml will be concated together.</p>
|
|
|
+ *
|
|
|
+ * @param {string} html
|
|
|
+ * @param {string} [endHtml=null]
|
|
|
+ * @param {boolean} [overrideCodeBlocking=false]
|
|
|
+ * @function
|
|
|
+ * @name wysiwygEditorInsertHtml
|
|
|
+ * @memberOf jQuery.sceditor.prototype
|
|
|
+ */
|
|
|
+ base.wysiwygEditorInsertHtml = function (html, endHtml, overrideCodeBlocking) {
|
|
|
+ base.focus();
|
|
|
+
|
|
|
+ // don't apply to code elements
|
|
|
+ if(!overrideCodeBlocking && ($(rangeHelper.parentNode()).is('code') ||
|
|
|
+ $(rangeHelper.parentNode()).parents('code').length !== 0))
|
|
|
+ return;
|
|
|
+
|
|
|
+ rangeHelper.insertHTML(html, endHtml);
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 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) {
|
|
|
+ var escape = function(str) {
|
|
|
+ if(!str)
|
|
|
+ return str;
|
|
|
+
|
|
|
+ return str.replace(/&/g, "&")
|
|
|
+ .replace(/</g, "<")
|
|
|
+ .replace(/>/g, ">")
|
|
|
+ .replace(/ /g, " ")
|
|
|
+ .replace(/\r\n|\r/g, "\n")
|
|
|
+ .replace(/\n/g, "<br />");
|
|
|
+ };
|
|
|
+
|
|
|
+ base.wysiwygEditorInsertHtml(escape(text), escape(endText));
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * <p>Inserts text into either WYSIWYG or textEditor depending on which
|
|
|
+ * mode the editor is in.</p>
|
|
|
+ *
|
|
|
+ * <p>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.</p>
|
|
|
+ *
|
|
|
+ * @param {String} text
|
|
|
+ * @param {String} [endText=null]
|
|
|
+ * @since 1.3.5
|
|
|
+ * @function
|
|
|
+ * @name insertText
|
|
|
+ * @memberOf jQuery.sceditor.prototype
|
|
|
+ */
|
|
|
+ base.insertText = function (text, endText) {
|
|
|
+ if(base.inSourceMode())
|
|
|
+ base.textEditorInsertText(text, endText);
|
|
|
+ else
|
|
|
+ base.wysiwygEditorInsertText(text, endText);
|
|
|
+
|
|
|
+ return this;
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Like wysiwygEditorInsertHtml but inserts text into the text
|
|
|
+ * (source mode) editor instead
|
|
|
+ *
|
|
|
+ * @param {String} text
|
|
|
+ * @param {String} [endText=null]
|
|
|
+ * @function
|
|
|
+ * @name textEditorInsertText
|
|
|
+ * @memberOf jQuery.sceditor.prototype
|
|
|
+ */
|
|
|
+ base.textEditorInsertText = function (text, endText) {
|
|
|
+ var range, start, end, txtLen;
|
|
|
+
|
|
|
+ textEditor.focus();
|
|
|
+
|
|
|
+ if(typeof textEditor.selectionStart !== "undefined")
|
|
|
+ {
|
|
|
+ start = textEditor.selectionStart;
|
|
|
+ end = textEditor.selectionEnd;
|
|
|
+ txtLen = text.length;
|
|
|
+
|
|
|
+ if(endText)
|
|
|
+ text += textEditor.value.substring(start, end) + endText;
|
|
|
+
|
|
|
+ textEditor.value = textEditor.value.substring(0, start) + text + textEditor.value.substring(end, textEditor.value.length);
|
|
|
+
|
|
|
+ if(endText)
|
|
|
+ textEditor.selectionStart = (start + text.length) - endText.length;
|
|
|
+ else
|
|
|
+ textEditor.selectionStart = start + text.length;
|
|
|
+
|
|
|
+ textEditor.selectionEnd = textEditor.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
|
|
|
+ textEditor.value += text + endText;
|
|
|
+
|
|
|
+ textEditor.focus();
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Gets the current rangeHelper instance
|
|
|
+ *
|
|
|
+ * @return jQuery.sceditor.rangeHelper
|
|
|
+ * @function
|
|
|
+ * @name getRangeHelper
|
|
|
+ * @memberOf jQuery.sceditor.prototype
|
|
|
+ */
|
|
|
+ base.getRangeHelper = function () {
|
|
|
+ return rangeHelper;
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Gets the value of the editor
|
|
|
+ *
|
|
|
+ * @since 1.3.5
|
|
|
+ * @return {string}
|
|
|
+ * @function
|
|
|
+ * @name val
|
|
|
+ * @memberOf jQuery.sceditor.prototype
|
|
|
+ */
|
|
|
+ /**
|
|
|
+ * Sets the value of the editor
|
|
|
+ *
|
|
|
+ * @param {String} val
|
|
|
+ * @param {Boolean} [filter]
|
|
|
+ * @return {this}
|
|
|
+ * @since 1.3.5
|
|
|
+ * @function
|
|
|
+ * @name val^2
|
|
|
+ * @memberOf jQuery.sceditor.prototype
|
|
|
+ */
|
|
|
+ base.val = function (val, filter) {
|
|
|
+ if(typeof val === "string")
|
|
|
+ {
|
|
|
+ if(base.inSourceMode())
|
|
|
+ base.setTextareaValue(val);
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if(filter !== false && base.options.getTextHandler)
|
|
|
+ val = base.options.getTextHandler(val);
|
|
|
+
|
|
|
+ base.setWysiwygEditorValue(val);
|
|
|
+ }
|
|
|
+
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ return base.inSourceMode() ?
|
|
|
+ base.getTextareaValue(false) :
|
|
|
+ base.getWysiwygEditorValue();
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * <p>Inserts HTML/BBCode into the editor</p>
|
|
|
+ *
|
|
|
+ * <p>If end is supplied any slected text will be placed between
|
|
|
+ * start and end. If there is no selected text start and end
|
|
|
+ * will be concated together.</p>
|
|
|
+ *
|
|
|
+ * @param {String} start
|
|
|
+ * @param {String} [end=null]
|
|
|
+ * @param {Boolean} [filter=true]
|
|
|
+ * @param {Boolean} [convertEmoticons=true]
|
|
|
+ * @return {this}
|
|
|
+ * @since 1.3.5
|
|
|
+ * @function
|
|
|
+ * @name insert
|
|
|
+ * @memberOf jQuery.sceditor.prototype
|
|
|
+ */
|
|
|
+ base.insert = function (start, end, filter, convertEmoticons) {
|
|
|
+ if(base.inSourceMode())
|
|
|
+ base.textEditorInsertText(start, end);
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if(end)
|
|
|
+ {
|
|
|
+ var html = base.getRangeHelper().selectedHtml(),
|
|
|
+ frag = $('<div>').appendTo($('body')).hide().html(html);
|
|
|
+
|
|
|
+ if(filter !== false && base.options.getHtmlHandler)
|
|
|
+ {
|
|
|
+ html = base.options.getHtmlHandler(html, frag);
|
|
|
+ frag.remove();
|
|
|
+ }
|
|
|
+
|
|
|
+ start += html + end;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(filter !== false && base.options.getTextHandler)
|
|
|
+ start = base.options.getTextHandler(start, true);
|
|
|
+
|
|
|
+ if(convertEmoticons !== false)
|
|
|
+ start = replaceEmoticons(start);
|
|
|
+
|
|
|
+ base.wysiwygEditorInsertHtml(start);
|
|
|
+ }
|
|
|
+
|
|
|
+ return this;
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Gets the WYSIWYG editors HTML which is between the body tags
|
|
|
+ *
|
|
|
+ * @param {bool} [filter=true]
|
|
|
+ * @return {string}
|
|
|
+ * @function
|
|
|
+ * @name getWysiwygEditorValue
|
|
|
+ * @memberOf jQuery.sceditor.prototype
|
|
|
+ */
|
|
|
+ base.getWysiwygEditorValue = function (filter) {
|
|
|
+ // Possible replacement:
|
|
|
+ // if(!$.sceditor.isWysiwygSupported())
|
|
|
+ if (!base.options.supportedWysiwyg)
|
|
|
+ return;
|
|
|
+
|
|
|
+ var $body = $wysiwygEditor.contents().find("body"),
|
|
|
+ html;
|
|
|
+
|
|
|
+ // fix any invalid nesting
|
|
|
+ $.sceditor.dom.fixNesting($body.get(0));
|
|
|
+ html = $body.html();
|
|
|
+
|
|
|
+ if(filter !== false && base.options.getHtmlHandler)
|
|
|
+ html = base.options.getHtmlHandler(html, $body);
|
|
|
+
|
|
|
+ return html;
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Gets the text editor value
|
|
|
+ *
|
|
|
+ * @param {bool} [filter=true]
|
|
|
+ * @return {string}
|
|
|
+ * @function
|
|
|
+ * @name getTextareaValue
|
|
|
+ * @memberOf jQuery.sceditor.prototype
|
|
|
+ */
|
|
|
+ base.getTextareaValue = function (filter) {
|
|
|
+ var val = $textEditor.val();
|
|
|
+
|
|
|
+ if(filter !== false && base.options.getTextHandler)
|
|
|
+ val = base.options.getTextHandler(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 = '<p>' + ($.sceditor.ie ? '' : '<br />') + '</p>';
|
|
|
+
|
|
|
+ getWysiwygDoc().body.innerHTML = replaceEmoticons(value);
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Sets the text editor value
|
|
|
+ *
|
|
|
+ * @param {string} value
|
|
|
+ * @function
|
|
|
+ * @name setTextareaValue
|
|
|
+ * @memberOf jQuery.sceditor.prototype
|
|
|
+ */
|
|
|
+ base.setTextareaValue = function (value) {
|
|
|
+ $textEditor.val(value);
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Updates the textarea that the editor is replacing
|
|
|
+ * with the value currently inside the editor.
|
|
|
+ *
|
|
|
+ * @function
|
|
|
+ * @name updateTextareaValue
|
|
|
+ * @memberOf jQuery.sceditor.prototype
|
|
|
+ */
|
|
|
+ base.updateTextareaValue = function () {
|
|
|
+ if(base.inSourceMode())
|
|
|
+ $textarea.val(base.getTextareaValue(false));
|
|
|
+ else
|
|
|
+ $textarea.val(base.getWysiwygEditorValue());
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Replaces any emoticon codes in the passed HTML with their emoticon images
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ replaceEmoticons = function (html) {
|
|
|
+ if(base.options.toolbar.indexOf('emoticon') === -1)
|
|
|
+ return html;
|
|
|
+
|
|
|
+ var emoticons = $.extend({}, base.options.emoticons.more, base.options.emoticons.dropdown, base.options.emoticons.hidden);
|
|
|
+
|
|
|
+ $.each(emoticons, function (key, url) {
|
|
|
+ // @todo Why did I (emanuele) add this?
|
|
|
+ if (key == '')
|
|
|
+ return;
|
|
|
+ // escape the key before using it as a regex
|
|
|
+ // and append the regex to only find emoticons outside
|
|
|
+ // of HTML tags
|
|
|
+ var reg = $.sceditor.regexEscape(key) + "(?=([^\\<\\>]*?<(?!/code)|[^\\<\\>]*?$))",
|
|
|
+ group = '';
|
|
|
+
|
|
|
+ // Make sure the emoticon is surrounded by whitespace or is at the start/end of a string or html tag
|
|
|
+ if(base.options.emoticonsCompat)
|
|
|
+ {
|
|
|
+ reg = "((>|^|\\s|\xA0|\u2002|\u2003|\u2009| ))" + reg + "(?=(\\s|$|<|\xA0|\u2002|\u2003|\u2009| ))";
|
|
|
+ group = '$1';
|
|
|
+ }
|
|
|
+
|
|
|
+ html = html.replace(
|
|
|
+ new RegExp(reg, 'gm'),
|
|
|
+ group + _tmpl('emoticon', {key: key, url: url})
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ return html;
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * If the editor is in source code mode
|
|
|
+ *
|
|
|
+ * @return {bool}
|
|
|
+ * @function
|
|
|
+ * @name inSourceMode
|
|
|
+ * @memberOf jQuery.sceditor.prototype
|
|
|
+ */
|
|
|
+ base.inSourceMode = function () {
|
|
|
+ return $textEditor.is(':visible');
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 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.toggleTextMode();
|
|
|
+
|
|
|
+ return this;
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Switches between the WYSIWYG and plain text modes
|
|
|
+ *
|
|
|
+ * @function
|
|
|
+ * @name toggleTextMode
|
|
|
+ * @memberOf jQuery.sceditor.prototype
|
|
|
+ */
|
|
|
+ base.toggleTextMode = function () {
|
|
|
+ /*
|
|
|
+ // Possible replacement
|
|
|
+ // don't allow switching to WYSIWYG if doesn't support it
|
|
|
+ if(!$.sceditor.isWysiwygSupported() && base.inSourceMode())
|
|
|
+ return;
|
|
|
+
|
|
|
+ if(base.inSourceMode())
|
|
|
+ */
|
|
|
+
|
|
|
+
|
|
|
+ if(base.inSourceMode() && base.options.supportedWysiwyg)
|
|
|
+ base.setWysiwygEditorValue(base.getTextareaValue());
|
|
|
+ else
|
|
|
+ base.setTextareaValue(base.getWysiwygEditorValue());
|
|
|
+
|
|
|
+ lastRange = null;
|
|
|
+ $textEditor.toggle();
|
|
|
+ $wysiwygEditor.toggle();
|
|
|
+
|
|
|
+ $editorContainer.removeClass('sourceMode');
|
|
|
+ $editorContainer.removeClass('wysiwygMode');
|
|
|
+
|
|
|
+ if(base.inSourceMode())
|
|
|
+ $editorContainer.addClass('sourceMode');
|
|
|
+ else
|
|
|
+ $editorContainer.addClass('wysiwygMode');
|
|
|
+
|
|
|
+ updateToolBar();
|
|
|
+ };
|
|
|
+
|
|
|
+ textEditorSelectedText = function () {
|
|
|
+ textEditor.focus();
|
|
|
+
|
|
|
+ if(textEditor.selectionStart != null)
|
|
|
+ return textEditor.value.substring(textEditor.selectionStart, textEditor.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.textEditorInsertText.apply(base, command.txtExec);
|
|
|
+ else
|
|
|
+ command.txtExec.call(base, caller, textEditorSelectedText());
|
|
|
+ }
|
|
|
+
|
|
|
+ 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);
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Fucuses the editors input area
|
|
|
+ *
|
|
|
+ * @return {this}
|
|
|
+ * @function
|
|
|
+ * @name focus
|
|
|
+ * @memberOf jQuery.sceditor.prototype
|
|
|
+ */
|
|
|
+ base.focus = function () {
|
|
|
+ if(!base.inSourceMode())
|
|
|
+ {
|
|
|
+ wysiwygEditor.contentWindow.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
|
|
|
+ textEditor.focus();
|
|
|
+
|
|
|
+ return this;
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Blurs the editors input area
|
|
|
+ *
|
|
|
+ * @return {this}
|
|
|
+ * @function
|
|
|
+ * @name blur
|
|
|
+ * @memberOf jQuery.sceditor.prototype
|
|
|
+ * @since 1.3.6
|
|
|
+ */
|
|
|
+ base.blur = function () {
|
|
|
+ // 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 = $('<input style="width:0; height:0; opacity:0; filter: alpha(opacity=0)" type="text" />').appendTo($editorContainer);
|
|
|
+
|
|
|
+ $blurElm.removeAttr("disabled")
|
|
|
+ .focus()
|
|
|
+ .blur()
|
|
|
+ .attr("disabled", "disabled");
|
|
|
+
|
|
|
+ return this;
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 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)
|
|
|
+ return;
|
|
|
+
|
|
|
+ lastRange = rangeHelper.selectedRange();
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Executes a command on the WYSIWYG editor
|
|
|
+ *
|
|
|
+ * @param {String|Function} 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 comannds to code elements
|
|
|
+ if($parentNode.is('code') || $parentNode.parents('code').length !== 0)
|
|
|
+ return;
|
|
|
+
|
|
|
+ if(getWysiwygDoc())
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ executed = getWysiwygDoc().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));
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Handles any key press in the WYSIWYG editor
|
|
|
+ *
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ handleKeyPress = function(e) {
|
|
|
+ base.closeDropDown();
|
|
|
+
|
|
|
+ var $parentNode = $(rangeHelper.parentNode());
|
|
|
+
|
|
|
+ // "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('<br />', null, true);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // make sure there is always a newline after code or quote tags
|
|
|
+ var d = getWysiwygDoc();
|
|
|
+ $.sceditor.dom.rTraverse(d.body, function(node) {
|
|
|
+ if((node.nodeType === 3 && node.nodeValue !== "") ||
|
|
|
+ node.nodeName.toLowerCase() === 'br') {
|
|
|
+ // this is the last text or br node, if its in a code or quote tag
|
|
|
+ // then add a newline after it
|
|
|
+ if($(node).parents('code, blockquote').length > 0)
|
|
|
+ $(d.body).append(d.createElement('br'));
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }, true);
|
|
|
+
|
|
|
+ // don't apply to code elements
|
|
|
+ if($parentNode.is('code') || $parentNode.parents('code').length !== 0)
|
|
|
+ return;
|
|
|
+
|
|
|
+ var i = keyPressFuncs.length;
|
|
|
+ while(i--)
|
|
|
+ keyPressFuncs[i].call(base, e, wysiwygEditor, $textEditor);
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Handles any mousedown press in the WYSIWYG editor
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ handleFormReset = function() {
|
|
|
+ base.val($textarea.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 deisgns.
|
|
|
+ * @ignore
|
|
|
+ */
|
|
|
+ handleWindowResize = function() {
|
|
|
+ if(base.options.height && base.options.height.toString().indexOf("%") > -1)
|
|
|
+ base.height($editorContainer.parent().height() *
|
|
|
+ (parseFloat(base.options.height) / 100));
|
|
|
+
|
|
|
+ if(base.options.width && base.options.width.toString().indexOf("%") > -1)
|
|
|
+ base.width($editorContainer.parent().width() *
|
|
|
+ (parseFloat(base.options.width) / 100));
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 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 + '}';
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Init the locale variable with the specified locale if possible
|
|
|
+ * @private
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+ initLocale = function() {
|
|
|
+ if($.sceditor.locale[base.options.locale])
|
|
|
+ locale = $.sceditor.locale[base.options.locale];
|
|
|
+ else
|
|
|
+ {
|
|
|
+ var lang = base.options.locale.split("-");
|
|
|
+
|
|
|
+ if($.sceditor.locale[lang[0]])
|
|
|
+ locale = $.sceditor.locale[lang[0]];
|
|
|
+ }
|
|
|
+
|
|
|
+ if(locale && locale.dateFormat)
|
|
|
+ base.options.dateFormat = locale.dateFormat;
|
|
|
+ };
|
|
|
+
|
|
|
+ // run the initializer
|
|
|
+ init();
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Detects which version of IE is being used if any.
|
|
|
+ *
|
|
|
+ * Will be the IE version number or undefined if not IE.
|
|
|
+ *
|
|
|
+ * Source: https://gist.github.com/527683
|
|
|
+ * @type {int}
|
|
|
+ * @memberOf jQuery.sceditor
|
|
|
+ */
|
|
|
+ $.sceditor.ie = (function(){
|
|
|
+ var undef,
|
|
|
+ v = 3,
|
|
|
+ div = document.createElement('div'),
|
|
|
+ all = div.getElementsByTagName('i');
|
|
|
+
|
|
|
+ do {
|
|
|
+ div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->';
|
|
|
+ } while (all[0]);
|
|
|
+
|
|
|
+ return v > 4 ? v : undef;
|
|
|
+ }());
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Detects if WYSIWYG is supported by the browser
|
|
|
+ *
|
|
|
+ * @return {bool}
|
|
|
+ * @memberOf jQuery.sceditor
|
|
|
+ */
|
|
|
+ $.sceditor.isWysiwygSupported = function() {
|
|
|
+ var contentEditable = $('<div contenteditable="true">')[0].contentEditable,
|
|
|
+ contentEditableSupported = typeof contentEditable !== 'undefined' && contentEditable !== 'inherit',
|
|
|
+ userAgent = navigator.userAgent,
|
|
|
+ match;
|
|
|
+
|
|
|
+ if(!contentEditableSupported)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ // I think blackberry supports it or will at least
|
|
|
+ // give a valid value for the contentEditable detection above
|
|
|
+ // so it's' not included here.
|
|
|
+
|
|
|
+
|
|
|
+ // The latest WebOS dose support contentEditable.
|
|
|
+ // But I still till need to check if all supported
|
|
|
+ // versions of WebOS support contentEditable
|
|
|
+
|
|
|
+
|
|
|
+ // I hate having to use UA sniffing but as some mobile browsers say they support
|
|
|
+ // contentediable/design mode when it isn't usable (i.e. you can't eneter text, ect.).
|
|
|
+ // This is the only way I can think of to detect them. It's also how every other editor
|
|
|
+ // I've seen detects them
|
|
|
+ var isUnsupported = /Opera Mobi|Opera Mini/i.test(userAgent);
|
|
|
+
|
|
|
+ if(/Android/i.test(userAgent))
|
|
|
+ {
|
|
|
+ isUnsupported = true;
|
|
|
+
|
|
|
+ if(/Safari/.test(userAgent))
|
|
|
+ {
|
|
|
+ // android browser 534+ supports content editable
|
|
|
+ match = /Safari\/(\d+)/.exec(userAgent);
|
|
|
+ isUnsupported = (!match || !match[1] ? true : match[1] < 534);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Amazon Silk doesn't but as it uses webkit like android
|
|
|
+ // it might in a later version if it uses version >= 534
|
|
|
+ if(/ Silk\//i.test(userAgent))
|
|
|
+ {
|
|
|
+ match = /AppleWebKit\/(\d+)/.exec(userAgent);
|
|
|
+ isUnsupported = (!match || !match[1] ? true : match[1] < 534);
|
|
|
+ }
|
|
|
+
|
|
|
+ // iOS 5+ supports content editable
|
|
|
+ if(/iPhone|iPod|iPad/i.test(userAgent))
|
|
|
+ isUnsupported = !/OS 5(_\d)+ like Mac OS X/i.test(userAgent);
|
|
|
+
|
|
|
+ // FireFox dose support WYSIWYG on mobiles so override
|
|
|
+ // any previous value if using FF
|
|
|
+ if(/fennec/i.test(userAgent))
|
|
|
+ isUnsupported = false;
|
|
|
+
|
|
|
+ return !isUnsupported;
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Escapes a string so it's safe to use in regex
|
|
|
+ *
|
|
|
+ * @param {string} str
|
|
|
+ * @return {string}
|
|
|
+ * @memberOf jQuery.sceditor
|
|
|
+ */
|
|
|
+ $.sceditor.regexEscape = function (str) {
|
|
|
+ return str.replace(/[\$\?\[\]\.\*\(\)\|\\]/g, "\\$&")
|
|
|
+ .replace("<", "<")
|
|
|
+ .replace(">", ">");
|
|
|
+ };
|
|
|
+
|
|
|
+ $.sceditor.locale = {};
|
|
|
+
|
|
|
+ $.sceditor.commands = {
|
|
|
+ // START_COMMAND: Bold
|
|
|
+ bold: {
|
|
|
+ exec: "bold",
|
|
|
+ tooltip: "Bold"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+ // START_COMMAND: Italic
|
|
|
+ italic: {
|
|
|
+ exec: "italic",
|
|
|
+ tooltip: "Italic"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+ // START_COMMAND: Underline
|
|
|
+ underline: {
|
|
|
+ exec: "underline",
|
|
|
+ tooltip: "Underline"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+ // START_COMMAND: Strikethrough
|
|
|
+ strike: {
|
|
|
+ exec: "strikethrough",
|
|
|
+ tooltip: "Strikethrough"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+ // START_COMMAND: Subscript
|
|
|
+ subscript: {
|
|
|
+ exec: "subscript",
|
|
|
+ tooltip: "Subscript"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+ // START_COMMAND: Superscript
|
|
|
+ superscript: {
|
|
|
+ exec: "superscript",
|
|
|
+ tooltip: "Superscript"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+
|
|
|
+ // START_COMMAND: Left
|
|
|
+ left: {
|
|
|
+ exec: "justifyleft",
|
|
|
+ tooltip: "Align left"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+ // START_COMMAND: Centre
|
|
|
+ center: {
|
|
|
+ exec: "justifycenter",
|
|
|
+ tooltip: "Center"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+ // START_COMMAND: Right
|
|
|
+ right: {
|
|
|
+ exec: "justifyright",
|
|
|
+ tooltip: "Align right"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+ // START_COMMAND: Justify
|
|
|
+ justify: {
|
|
|
+ exec: "justifyfull",
|
|
|
+ tooltip: "Justify"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+
|
|
|
+ // START_COMMAND: Font
|
|
|
+ font: {
|
|
|
+ _dropDown: function(editor, caller, callback) {
|
|
|
+ var fonts = editor.options.fonts.split(","),
|
|
|
+ content = $("<div />"),
|
|
|
+ /** @private */
|
|
|
+ clickFunc = function () {
|
|
|
+ callback($(this).data('font'));
|
|
|
+ editor.closeDropDown(true);
|
|
|
+ return false;
|
|
|
+ };
|
|
|
+
|
|
|
+ for (var i=0; i < fonts.length; i++)
|
|
|
+ content.append(_tmpl('fontOpt', {font: fonts[i]}, true).click(clickFunc));
|
|
|
+
|
|
|
+ editor.createDropDown(caller, "font-picker", content);
|
|
|
+ },
|
|
|
+ exec: function (caller) {
|
|
|
+ var editor = this;
|
|
|
+
|
|
|
+ $.sceditor.command.get('font')._dropDown(
|
|
|
+ editor,
|
|
|
+ caller,
|
|
|
+ function(fontName) {
|
|
|
+ editor.execCommand("fontname", fontName);
|
|
|
+ }
|
|
|
+ );
|
|
|
+ },
|
|
|
+ tooltip: "Font Name"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+ // START_COMMAND: Size
|
|
|
+ size: {
|
|
|
+ _dropDown: function(editor, caller, callback) {
|
|
|
+ var sizes = [0, 8, 10, 12, 14, 18, 24, 36];
|
|
|
+ var content = $("<div />"),
|
|
|
+ /** @private */
|
|
|
+ clickFunc = function (e) {
|
|
|
+ callback($(this).data('size'));
|
|
|
+ editor.closeDropDown(true);
|
|
|
+ e.preventDefault();
|
|
|
+ };
|
|
|
+
|
|
|
+ for (var i=1; i<= 7; i++)
|
|
|
+ content.append(_tmpl('sizeOpt', {size: i, points: sizes[i]}, true).click(clickFunc));
|
|
|
+
|
|
|
+ editor.createDropDown(caller, "fontsize-picker", content);
|
|
|
+ },
|
|
|
+ exec: function (caller) {
|
|
|
+ var editor = this;
|
|
|
+
|
|
|
+ $.sceditor.command.get('size')._dropDown(
|
|
|
+ editor,
|
|
|
+ caller,
|
|
|
+ function(fontSize) {
|
|
|
+ editor.execCommand("fontsize", fontSize);
|
|
|
+ }
|
|
|
+ );
|
|
|
+ },
|
|
|
+ tooltip: "Font Size"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+ // START_COMMAND: Colour
|
|
|
+ color: {
|
|
|
+ _dropDown: function(editor, caller, callback) {
|
|
|
+ var genColor = {r: 255, g: 255, b: 255},
|
|
|
+ content = $("<div />"),
|
|
|
+ colorColumns = editor.options.colors?editor.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 = colorColumns[i]?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 = colors[x]?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};
|
|
|
+ }
|
|
|
+
|
|
|
+ content.append(html.join(''))
|
|
|
+ .find('a')
|
|
|
+ .click(function (e) {
|
|
|
+ callback($(this).attr('data-color'));
|
|
|
+ editor.closeDropDown(true);
|
|
|
+ e.preventDefault();
|
|
|
+ });
|
|
|
+
|
|
|
+ editor.createDropDown(caller, "color-picker", content);
|
|
|
+ },
|
|
|
+ exec: function (caller) {
|
|
|
+ var editor = this;
|
|
|
+
|
|
|
+ $.sceditor.command.get('color')._dropDown(
|
|
|
+ editor,
|
|
|
+ caller,
|
|
|
+ function(color) {
|
|
|
+ editor.execCommand("forecolor", color);
|
|
|
+ }
|
|
|
+ );
|
|
|
+ },
|
|
|
+ tooltip: "Font Color"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+ // START_COMMAND: Remove Format
|
|
|
+ removeformat: {
|
|
|
+ exec: "removeformat",
|
|
|
+ tooltip: "Remove Formatting"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+
|
|
|
+ // START_COMMAND: Cut
|
|
|
+ cut: {
|
|
|
+ exec: "cut",
|
|
|
+ tooltip: "Cut",
|
|
|
+ errorMessage: "Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+ // START_COMMAND: Copy
|
|
|
+ copy: {
|
|
|
+ exec: "copy",
|
|
|
+ tooltip: "Copy",
|
|
|
+ errorMessage: "Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+ // START_COMMAND: Paste
|
|
|
+ paste: {
|
|
|
+ exec: "paste",
|
|
|
+ tooltip: "Paste",
|
|
|
+ errorMessage: "Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+ // START_COMMAND: Paste Text
|
|
|
+ pastetext: {
|
|
|
+ exec: function (caller) {
|
|
|
+ var val,
|
|
|
+ editor = this,
|
|
|
+ content = _tmpl("pastetext", {
|
|
|
+ label: editor._("Paste your text inside the following box:"),
|
|
|
+ insert: editor._("Insert")
|
|
|
+ }, true);
|
|
|
+
|
|
|
+ content.find('.button').click(function (e) {
|
|
|
+ val = content.find("#txt").val();
|
|
|
+
|
|
|
+ if(val)
|
|
|
+ editor.wysiwygEditorInsertText(val);
|
|
|
+
|
|
|
+ editor.closeDropDown(true);
|
|
|
+ e.preventDefault();
|
|
|
+ });
|
|
|
+
|
|
|
+ editor.createDropDown(caller, "pastetext", content);
|
|
|
+ },
|
|
|
+ tooltip: "Paste Text"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+ // START_COMMAND: Bullet List
|
|
|
+ bulletlist: {
|
|
|
+ exec: "insertunorderedlist",
|
|
|
+ tooltip: "Bullet list"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+ // START_COMMAND: Ordered List
|
|
|
+ orderedlist: {
|
|
|
+ exec: "insertorderedlist",
|
|
|
+ tooltip: "Numbered list"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+
|
|
|
+ // START_COMMAND: Table
|
|
|
+ table: {
|
|
|
+ exec: function (caller) {
|
|
|
+ var editor = this,
|
|
|
+ content = _tmpl("table", {
|
|
|
+ rows: editor._("Rows:"),
|
|
|
+ cols: editor._("Cols:"),
|
|
|
+ insert: editor._("Insert")
|
|
|
+ }, true);
|
|
|
+
|
|
|
+ content.find('.button').click(function (e) {
|
|
|
+ var rows = content.find("#rows").val() - 0,
|
|
|
+ cols = content.find("#cols").val() - 0,
|
|
|
+ html = '<table>';
|
|
|
+
|
|
|
+ if(rows < 1 || cols < 1)
|
|
|
+ return;
|
|
|
+
|
|
|
+ for (var row=0; row < rows; row++) {
|
|
|
+ html += '<tr>';
|
|
|
+
|
|
|
+ for (var col=0; col < cols; col++)
|
|
|
+ html += '<td>' + ($.sceditor.ie ? '' : '<br class="sceditor-ignore" />') + '</td>';
|
|
|
+
|
|
|
+ html += '</tr>';
|
|
|
+ }
|
|
|
+
|
|
|
+ html += '</table>';
|
|
|
+
|
|
|
+ editor.wysiwygEditorInsertHtml(html);
|
|
|
+ editor.closeDropDown(true);
|
|
|
+ e.preventDefault();
|
|
|
+ });
|
|
|
+
|
|
|
+ editor.createDropDown(caller, "inserttable", content);
|
|
|
+ },
|
|
|
+ tooltip: "Insert a table"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+
|
|
|
+ // START_COMMAND: Horizontal Rule
|
|
|
+ horizontalrule: {
|
|
|
+ exec: "inserthorizontalrule",
|
|
|
+ tooltip: "Insert a horizontal rule"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+
|
|
|
+ // START_COMMAND: Code
|
|
|
+ code: {
|
|
|
+ exec: function () {
|
|
|
+ this.wysiwygEditorInsertHtml('<code>', '<br /></code>');
|
|
|
+ },
|
|
|
+ tooltip: "Code"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+
|
|
|
+ // START_COMMAND: Image
|
|
|
+ image: {
|
|
|
+ exec: function (caller) {
|
|
|
+ var editor = this,
|
|
|
+ content = _tmpl("image", {
|
|
|
+ url: editor._("URL:"),
|
|
|
+ width: editor._("Width (optional):"),
|
|
|
+ height: editor._("Height (optional):"),
|
|
|
+ insert: editor._("Insert")
|
|
|
+ }, true);
|
|
|
+
|
|
|
+ content.find('.button').click(function (e) {
|
|
|
+ var val = content.find("#image").val(),
|
|
|
+ attrs = '',
|
|
|
+ width, height;
|
|
|
+
|
|
|
+ if((width = content.find("#width").val()))
|
|
|
+ attrs += ' width="' + width + '"';
|
|
|
+ if((height = content.find("#height").val()))
|
|
|
+ attrs += ' height="' + height + '"';
|
|
|
+
|
|
|
+ if(val && val !== "http://")
|
|
|
+ editor.wysiwygEditorInsertHtml('<img' + attrs + ' src="' + val + '" />');
|
|
|
+
|
|
|
+ editor.closeDropDown(true);
|
|
|
+ e.preventDefault();
|
|
|
+ });
|
|
|
+
|
|
|
+ editor.createDropDown(caller, "insertimage", content);
|
|
|
+ },
|
|
|
+ tooltip: "Insert an image"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+
|
|
|
+ // START_COMMAND: E-mail
|
|
|
+ email: {
|
|
|
+ exec: function (caller) {
|
|
|
+ var editor = this,
|
|
|
+ content = _tmpl("email", {
|
|
|
+ label: editor._("E-mail:"),
|
|
|
+ insert: editor._("Insert")
|
|
|
+ }, true);
|
|
|
+
|
|
|
+ content.find('.button').click(function (e) {
|
|
|
+ var val = content.find("#email").val();
|
|
|
+
|
|
|
+ if(val)
|
|
|
+ {
|
|
|
+ // needed for IE to reset the last range
|
|
|
+ editor.focus();
|
|
|
+
|
|
|
+ if(!editor.getRangeHelper().selectedHtml())
|
|
|
+ editor.wysiwygEditorInsertHtml('<a href="' + 'mailto:' + val + '">' + val + '</a>');
|
|
|
+ else
|
|
|
+ editor.execCommand("createlink", 'mailto:' + val);
|
|
|
+ }
|
|
|
+
|
|
|
+ editor.closeDropDown(true);
|
|
|
+ e.preventDefault();
|
|
|
+ });
|
|
|
+
|
|
|
+ editor.createDropDown(caller, "insertemail", content);
|
|
|
+ },
|
|
|
+ tooltip: "Insert an email"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+
|
|
|
+ // START_COMMAND: Link
|
|
|
+ link: {
|
|
|
+ exec: function (caller) {
|
|
|
+ var editor = this,
|
|
|
+ content = _tmpl("link", {
|
|
|
+ url: editor._("URL:"),
|
|
|
+ desc: editor._("Description (optional):"),
|
|
|
+ ins: editor._("Insert")
|
|
|
+ }, true);
|
|
|
+
|
|
|
+ content.find('.button').click(function (e) {
|
|
|
+ var val = content.find("#link").val(),
|
|
|
+ description = content.find("#des").val();
|
|
|
+
|
|
|
+ if(val !== "" && val !== "http://") {
|
|
|
+ // needed for IE to reset the last range
|
|
|
+ editor.focus();
|
|
|
+
|
|
|
+ if(!editor.getRangeHelper().selectedHtml() || description)
|
|
|
+ {
|
|
|
+ if(!description)
|
|
|
+ description = val;
|
|
|
+
|
|
|
+ editor.wysiwygEditorInsertHtml('<a href="' + val + '">' + description + '</a>');
|
|
|
+ }
|
|
|
+ else
|
|
|
+ editor.execCommand("createlink", val);
|
|
|
+ }
|
|
|
+
|
|
|
+ editor.closeDropDown(true);
|
|
|
+ e.preventDefault();
|
|
|
+ });
|
|
|
+
|
|
|
+ editor.createDropDown(caller, "insertlink", content);
|
|
|
+ },
|
|
|
+ tooltip: "Insert a link"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+
|
|
|
+ // START_COMMAND: Unlink
|
|
|
+ unlink: {
|
|
|
+ exec: "unlink",
|
|
|
+ tooltip: "Unlink"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+
|
|
|
+
|
|
|
+ // START_COMMAND: Quote
|
|
|
+ quote: {
|
|
|
+ exec: function (caller, html, author) {
|
|
|
+ var before = '<blockquote>',
|
|
|
+ end = '</blockquote>';
|
|
|
+
|
|
|
+ // if there is HTML passed set end to null so any selected
|
|
|
+ // text is replaced
|
|
|
+ if(html)
|
|
|
+ {
|
|
|
+ author = (author ? '<cite>' + author + '</cite>' : '');
|
|
|
+
|
|
|
+ before = before + author + html + end + '<br />';
|
|
|
+ end = null;
|
|
|
+ }
|
|
|
+ // if not add a newline to the end of the inserted quote
|
|
|
+ else if(this.getRangeHelper().selectedHtml() === "")
|
|
|
+ end = '<br />' + end;
|
|
|
+
|
|
|
+ this.wysiwygEditorInsertHtml(before, end);
|
|
|
+ },
|
|
|
+ tooltip: "Insert a Quote"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+
|
|
|
+ // START_COMMAND: Emoticons
|
|
|
+ emoticon: {
|
|
|
+ exec: function (caller) {
|
|
|
+ var appendEmoticon,
|
|
|
+ editor = this,
|
|
|
+ end = (editor.options.emoticonsCompat ? ' ' : ''),
|
|
|
+ content = $('<div />'),
|
|
|
+ line = $('<div />');
|
|
|
+
|
|
|
+ appendEmoticon = function (code, emoticon) {
|
|
|
+ line.append($('<img />')
|
|
|
+ .attr({
|
|
|
+ src: emoticon,
|
|
|
+ alt: code
|
|
|
+ })
|
|
|
+ .click(function (e) {
|
|
|
+ editor.insert($(this).attr('alt') + end);
|
|
|
+ editor.closeDropDown(true);
|
|
|
+ e.preventDefault();
|
|
|
+ })
|
|
|
+ );
|
|
|
+
|
|
|
+ if(line.children().length > 3) {
|
|
|
+ content.append(line);
|
|
|
+ line = $('<div />');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ $.each(editor.options.emoticons.dropdown, appendEmoticon);
|
|
|
+
|
|
|
+ if(line.children().length > 0)
|
|
|
+ content.append(line);
|
|
|
+
|
|
|
+ if(editor.options.emoticons.more) {
|
|
|
+ var more = $(
|
|
|
+ this._('<a class="sceditor-more">{0}</a>', this._("More"))
|
|
|
+ ).click(function () {
|
|
|
+ var emoticons = $.extend({}, editor.options.emoticons.dropdown, editor.options.emoticons.more);
|
|
|
+ content = $('<div />');
|
|
|
+
|
|
|
+ $.each(emoticons, appendEmoticon);
|
|
|
+
|
|
|
+ if(line.children().length > 0)
|
|
|
+ content.append(line);
|
|
|
+
|
|
|
+ editor.createDropDown(caller, "insertemoticon", content);
|
|
|
+ return false;
|
|
|
+ });
|
|
|
+
|
|
|
+ content.append(more);
|
|
|
+ }
|
|
|
+
|
|
|
+ editor.createDropDown(caller, "insertemoticon", content);
|
|
|
+ },
|
|
|
+ txtExec: function(caller) {
|
|
|
+ $.sceditor.command.get('emoticon').exec.call(this, caller);
|
|
|
+ },
|
|
|
+ keyPress: function (e)
|
|
|
+ {
|
|
|
+ // make sure emoticons command is in the toolbar before running
|
|
|
+ if(this.options.toolbar.indexOf('emoticon') === -1)
|
|
|
+ return;
|
|
|
+
|
|
|
+ var editor = this,
|
|
|
+ pos = 0,
|
|
|
+ curChar = String.fromCharCode(e.which);
|
|
|
+
|
|
|
+ if(!editor.EmoticonsCache) {
|
|
|
+ editor.EmoticonsCache = [];
|
|
|
+
|
|
|
+ $.each($.extend({}, editor.options.emoticons.more, editor.options.emoticons.dropdown, editor.options.emoticons.hidden), function(key, url) {
|
|
|
+ editor.EmoticonsCache[pos++] = [
|
|
|
+ key,
|
|
|
+ _tmpl("emoticon", {key: key, url: url})
|
|
|
+ ];
|
|
|
+ });
|
|
|
+
|
|
|
+ editor.EmoticonsCache.sort(function(a, b){
|
|
|
+ return a[0].length - b[0].length;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ if(!editor.longestEmoticonCode)
|
|
|
+ editor.longestEmoticonCode = editor.EmoticonsCache[editor.EmoticonsCache.length - 1][0].length;
|
|
|
+
|
|
|
+ if(editor.getRangeHelper().raplaceKeyword(editor.EmoticonsCache, true, true, editor.longestEmoticonCode, editor.options.emoticonsCompat, curChar))
|
|
|
+ {
|
|
|
+ if(/^\s$/.test(curChar) && editor.options.emoticonsCompat)
|
|
|
+ return true;
|
|
|
+
|
|
|
+ e.preventDefault();
|
|
|
+ e.stopPropagation();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ tooltip: "Insert an emoticon"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+
|
|
|
+ // START_COMMAND: YouTube
|
|
|
+ youtube: {
|
|
|
+ _dropDown: function (editor, caller, handleIdFunc) {
|
|
|
+ var matches,
|
|
|
+ content = _tmpl("youtubeMenu", {
|
|
|
+ label: editor._("Video URL:"),
|
|
|
+ insert: editor._("Insert")
|
|
|
+ }, true);
|
|
|
+
|
|
|
+ content.find('.button').click(function (e) {
|
|
|
+ var val = content.find("#link").val().replace("http://", "");
|
|
|
+
|
|
|
+ if (val !== "") {
|
|
|
+ matches = val.match(/(?:v=|v\/|embed\/|youtu.be\/)(.{11})/);
|
|
|
+ if (matches) val = matches[1];
|
|
|
+
|
|
|
+ if (/^[a-zA-Z0-9_\-]{11}$/.test(val))
|
|
|
+ handleIdFunc(val);
|
|
|
+ else
|
|
|
+ alert('Invalid YouTube video');
|
|
|
+ }
|
|
|
+
|
|
|
+ editor.closeDropDown(true);
|
|
|
+ e.preventDefault();
|
|
|
+ });
|
|
|
+
|
|
|
+ editor.createDropDown(caller, "insertlink", content);
|
|
|
+ },
|
|
|
+ exec: function (caller) {
|
|
|
+ var editor = this;
|
|
|
+
|
|
|
+ $.sceditor.command.get('youtube')._dropDown(
|
|
|
+ editor,
|
|
|
+ caller,
|
|
|
+ function(id) {
|
|
|
+ editor.wysiwygEditorInsertHtml(_tmpl("youtube", { id: id }));
|
|
|
+ }
|
|
|
+ );
|
|
|
+ },
|
|
|
+ tooltip: "Insert a YouTube video"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+
|
|
|
+ // START_COMMAND: Date
|
|
|
+ date: {
|
|
|
+ _date: function (editor) {
|
|
|
+ var now = new Date(),
|
|
|
+ year = now.getYear(),
|
|
|
+ month = now.getMonth()+1,
|
|
|
+ day = now.getDate();
|
|
|
+
|
|
|
+ if(year < 2000)
|
|
|
+ year = 1900 + year;
|
|
|
+ if(month < 10)
|
|
|
+ month = "0" + month;
|
|
|
+ if(day < 10)
|
|
|
+ day = "0" + day;
|
|
|
+
|
|
|
+ return editor.options.dateFormat.replace(/year/i, year).replace(/month/i, month).replace(/day/i, day);
|
|
|
+ },
|
|
|
+ exec: function () {
|
|
|
+ this.insertText($.sceditor.command.get('date')._date(this));
|
|
|
+ },
|
|
|
+ txtExec: function () {
|
|
|
+ this.insertText($.sceditor.command.get('date')._date(this));
|
|
|
+ },
|
|
|
+ tooltip: "Insert current date"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+
|
|
|
+ // START_COMMAND: Time
|
|
|
+ time: {
|
|
|
+ _time: function () {
|
|
|
+ var now = new Date(),
|
|
|
+ hours = now.getHours(),
|
|
|
+ mins = now.getMinutes(),
|
|
|
+ secs = now.getSeconds();
|
|
|
+
|
|
|
+ if(hours < 10)
|
|
|
+ hours = "0" + hours;
|
|
|
+ if(mins < 10)
|
|
|
+ mins = "0" + mins;
|
|
|
+ if(secs < 10)
|
|
|
+ secs = "0" + secs;
|
|
|
+
|
|
|
+ return hours + ':' + mins + ':' + secs;
|
|
|
+ },
|
|
|
+ exec: function () {
|
|
|
+ this.insertText($.sceditor.command.get('time')._time());
|
|
|
+ },
|
|
|
+ txtExec: function () {
|
|
|
+ this.insertText($.sceditor.command.get('time')._time());
|
|
|
+ },
|
|
|
+ tooltip: "Insert current time"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+
|
|
|
+
|
|
|
+ // START_COMMAND: Ltr
|
|
|
+ ltr: {
|
|
|
+ exec: function() {
|
|
|
+ var editor = this,
|
|
|
+ elm = editor.getRangeHelper().getFirstBlockParent(),
|
|
|
+ $elm = $(elm);
|
|
|
+
|
|
|
+ editor.focus();
|
|
|
+
|
|
|
+ if(!elm || $elm.is('body'))
|
|
|
+ {
|
|
|
+ editor.execCommand("formatBlock", "p");
|
|
|
+
|
|
|
+ elm = editor.getRangeHelper().getFirstBlockParent();
|
|
|
+ $elm = $(elm);
|
|
|
+
|
|
|
+ if(!elm || $elm.is('body'))
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if($elm.css('direction') === 'ltr')
|
|
|
+ $(elm).css('direction', '');
|
|
|
+ else
|
|
|
+ $(elm).attr('direction', 'ltr');
|
|
|
+ },
|
|
|
+ tooltip: "Left-to-Right"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+
|
|
|
+ // START_COMMAND: Rtl
|
|
|
+ rtl: {
|
|
|
+ exec: function() {
|
|
|
+ var editor = this,
|
|
|
+ elm = editor.getRangeHelper().getFirstBlockParent(),
|
|
|
+ $elm = $(elm);
|
|
|
+
|
|
|
+ editor.focus();
|
|
|
+
|
|
|
+ if(!elm || $elm.is('body'))
|
|
|
+ {
|
|
|
+ editor.execCommand("formatBlock", "p");
|
|
|
+
|
|
|
+ elm = editor.getRangeHelper().getFirstBlockParent();
|
|
|
+ $elm = $(elm);
|
|
|
+
|
|
|
+ if(!elm || $elm.is('body'))
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if($elm.css('direction') === 'rtl')
|
|
|
+ $(elm).css('direction', '');
|
|
|
+ else
|
|
|
+ $(elm).css('direction', 'rtl');
|
|
|
+ },
|
|
|
+ tooltip: "Right-to-Left"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+
|
|
|
+
|
|
|
+ // START_COMMAND: Print
|
|
|
+ print: {
|
|
|
+ exec: "print",
|
|
|
+ tooltip: "Print"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+
|
|
|
+ // START_COMMAND: Source
|
|
|
+ source: {
|
|
|
+ exec: function () {
|
|
|
+ this.toggleTextMode();
|
|
|
+ },
|
|
|
+ txtExec: function () {
|
|
|
+ this.toggleTextMode();
|
|
|
+ },
|
|
|
+ tooltip: "View source"
|
|
|
+ },
|
|
|
+ // END_COMMAND
|
|
|
+
|
|
|
+ // this is here so that commands above can be removed
|
|
|
+ // without having to remove the , after the last one.
|
|
|
+ // Needed for IE.
|
|
|
+ ignore: {}
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Range helper class
|
|
|
+ * @class rangeHelper
|
|
|
+ * @name jQuery.sceditor.rangeHelper
|
|
|
+ */
|
|
|
+ $.sceditor.rangeHelper = function(w, d) {
|
|
|
+ var win, doc,
|
|
|
+ isW3C = true,
|
|
|
+ startMarker = "sceditor-start-marker",
|
|
|
+ endMarker = "sceditor-end-marker",
|
|
|
+ base = this,
|
|
|
+ init, _createMarker, _getOuterText, _selectOuterText;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @constructor
|
|
|
+ * @param Window window
|
|
|
+ * @param Document document
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ init = function (window, document) {
|
|
|
+ doc = document || window.contentDocument || window.document;
|
|
|
+ win = window;
|
|
|
+ isW3C = !!window.getSelection;
|
|
|
+ }(w, d);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * <p>Inserts HTML into the current range replacing any selected
|
|
|
+ * text.</p>
|
|
|
+ *
|
|
|
+ * <p>If endHTML is specified the selected contents will be put between
|
|
|
+ * html and endHTML. If there is nothing selected html and endHTML are
|
|
|
+ * just concated together.</p>
|
|
|
+ *
|
|
|
+ * @param {string} html
|
|
|
+ * @param {string} endHTML
|
|
|
+ * @function
|
|
|
+ * @name insertHTML
|
|
|
+ * @memberOf jQuery.sceditor.rangeHelper.prototype
|
|
|
+ */
|
|
|
+ base.insertHTML = function(html, endHTML) {
|
|
|
+ var node, div;
|
|
|
+
|
|
|
+ if(endHTML)
|
|
|
+ html += base.selectedHtml() + endHTML;
|
|
|
+
|
|
|
+ if(isW3C)
|
|
|
+ {
|
|
|
+ div = doc.createElement('div');
|
|
|
+ node = doc.createDocumentFragment();
|
|
|
+ div.innerHTML = html;
|
|
|
+
|
|
|
+ while(div.firstChild)
|
|
|
+ node.appendChild(div.firstChild);
|
|
|
+
|
|
|
+ base.insertNode(node);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ base.selectedRange().pasteHTML(html);
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * <p>The same as insertHTML except with DOM nodes instead</p>
|
|
|
+ *
|
|
|
+ * <p><strong>Warning:</strong> the nodes must belong to the
|
|
|
+ * document they are being inserted into. Some browsers
|
|
|
+ * will throw exceptions if they don't.</p>
|
|
|
+ *
|
|
|
+ * @param {Node} node
|
|
|
+ * @param {Node} endNode
|
|
|
+ *
|
|
|
+ * @function
|
|
|
+ * @name insertNode
|
|
|
+ * @memberOf jQuery.sceditor.rangeHelper.prototype
|
|
|
+ */
|
|
|
+ base.insertNode = function(node, endNode) {
|
|
|
+ if(isW3C)
|
|
|
+ {
|
|
|
+ var toInsert = doc.createDocumentFragment(),
|
|
|
+ range = base.selectedRange(),
|
|
|
+ selection, selectAfter;
|
|
|
+
|
|
|
+ toInsert.appendChild(node);
|
|
|
+
|
|
|
+ if(endNode)
|
|
|
+ {
|
|
|
+ toInsert.appendChild(range.extractContents());
|
|
|
+ toInsert.appendChild(endNode);
|
|
|
+ }
|
|
|
+
|
|
|
+ selectAfter = toInsert.lastChild;
|
|
|
+ range.deleteContents();
|
|
|
+ range.insertNode(toInsert);
|
|
|
+
|
|
|
+ selection = doc.createRange();
|
|
|
+ selection.setStartAfter(selectAfter);
|
|
|
+ base.selectRange(selection);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ base.insertHTML(node.outerHTML, endNode?endNode.outerHTML:null);
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * <p>Clones the selected Range</p>
|
|
|
+ *
|
|
|
+ * <p>IE <= 8 will return a TextRange, all other browsers
|
|
|
+ * will return a Range object.</p>
|
|
|
+ *
|
|
|
+ * @return {Range|TextRange}
|
|
|
+ * @function
|
|
|
+ * @name cloneSelected
|
|
|
+ * @memberOf jQuery.sceditor.rangeHelper.prototype
|
|
|
+ */
|
|
|
+ base.cloneSelected = function() {
|
|
|
+ if(!isW3C)
|
|
|
+ return base.selectedRange().duplicate();
|
|
|
+
|
|
|
+ return base.selectedRange().cloneRange();
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * <p>Gets the selected Range</p>
|
|
|
+ *
|
|
|
+ * <p>IE <= 8 will return a TextRange, all other browsers
|
|
|
+ * will return a Range object.</p>
|
|
|
+ *
|
|
|
+ * @return {Range|TextRange}
|
|
|
+ * @function
|
|
|
+ * @name selectedRange
|
|
|
+ * @memberOf jQuery.sceditor.rangeHelper.prototype
|
|
|
+ */
|
|
|
+ base.selectedRange = function() {
|
|
|
+ var sel;
|
|
|
+
|
|
|
+ if(win.getSelection)
|
|
|
+ sel = win.getSelection();
|
|
|
+ else
|
|
|
+ sel = doc.selection;
|
|
|
+
|
|
|
+ if(sel.getRangeAt && sel.rangeCount <= 0)
|
|
|
+ sel.addRange(doc.createRange());
|
|
|
+
|
|
|
+ if(!isW3C)
|
|
|
+ return sel.createRange();
|
|
|
+
|
|
|
+ return sel.getRangeAt(0);
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Gets the currently selected HTML
|
|
|
+ *
|
|
|
+ * @return {string}
|
|
|
+ * @function
|
|
|
+ * @name selectedHtml
|
|
|
+ * @memberOf jQuery.sceditor.rangeHelper.prototype
|
|
|
+ */
|
|
|
+ base.selectedHtml = function() {
|
|
|
+ var range = base.selectedRange();
|
|
|
+
|
|
|
+ if(!range)
|
|
|
+ return '';
|
|
|
+
|
|
|
+ // IE9+ and all other browsers
|
|
|
+ if (window.XMLSerializer)
|
|
|
+ return new XMLSerializer().serializeToString(range.cloneContents());
|
|
|
+
|
|
|
+ // IE < 9
|
|
|
+ if(!isW3C)
|
|
|
+ {
|
|
|
+ if(range.text !== '' && range.htmlText)
|
|
|
+ return range.htmlText;
|
|
|
+ }
|
|
|
+
|
|
|
+ return '';
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Gets the parent node of the selected contents in the range
|
|
|
+ *
|
|
|
+ * @return {HTMLElement}
|
|
|
+ * @function
|
|
|
+ * @name parentNode
|
|
|
+ * @memberOf jQuery.sceditor.rangeHelper.prototype
|
|
|
+ */
|
|
|
+ base.parentNode = function() {
|
|
|
+ var range = base.selectedRange();
|
|
|
+
|
|
|
+ if(isW3C)
|
|
|
+ return range.commonAncestorContainer;
|
|
|
+ else
|
|
|
+ return range.parentElement();
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Gets the first block level parent of the selected
|
|
|
+ * contents of the range.
|
|
|
+ *
|
|
|
+ * @return {HTMLElement}
|
|
|
+ * @function
|
|
|
+ * @name getFirstBlockParent
|
|
|
+ * @memberOf jQuery.sceditor.rangeHelper.prototype
|
|
|
+ */
|
|
|
+ base.getFirstBlockParent = function() {
|
|
|
+ var func = function(node) {
|
|
|
+ if(!$.sceditor.dom.isInline(node))
|
|
|
+ return node;
|
|
|
+
|
|
|
+ var p = node.parentNode;
|
|
|
+ if(p)
|
|
|
+ return func(p);
|
|
|
+
|
|
|
+ return null;
|
|
|
+ };
|
|
|
+
|
|
|
+ return func(base.parentNode());
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Inserts a node at either the start or end of the current selection
|
|
|
+ *
|
|
|
+ * @param {Bool} start
|
|
|
+ * @param {Node} node
|
|
|
+ * @function
|
|
|
+ * @name insertNodeAt
|
|
|
+ * @memberOf jQuery.sceditor.rangeHelper.prototype
|
|
|
+ */
|
|
|
+ base.insertNodeAt = function(start, node) {
|
|
|
+ var range = base.cloneSelected();
|
|
|
+
|
|
|
+ range.collapse(start);
|
|
|
+
|
|
|
+ if(range.insertNode)
|
|
|
+ range.insertNode(node);
|
|
|
+ else
|
|
|
+ range.pasteHTML(node.outerHTML);
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Creates a marker node
|
|
|
+ *
|
|
|
+ * @param {String} id
|
|
|
+ * @return {Node}
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ _createMarker = function(id) {
|
|
|
+ base.removeMarker(id);
|
|
|
+
|
|
|
+ var marker = doc.createElement("span");
|
|
|
+ marker.id = id;
|
|
|
+ marker.style.lineHeight = "0";
|
|
|
+ marker.style.display = "none";
|
|
|
+ marker.className = "sceditor-selection";
|
|
|
+
|
|
|
+ return marker;
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Inserts start/end markers for the current selection
|
|
|
+ * which can be used by restoreRange to re-select the
|
|
|
+ * range.
|
|
|
+ *
|
|
|
+ * @memberOf jQuery.sceditor.rangeHelper.prototype
|
|
|
+ * @function
|
|
|
+ * @name insertMarkers
|
|
|
+ */
|
|
|
+ base.insertMarkers = function() {
|
|
|
+ base.insertNodeAt(true, _createMarker(startMarker));
|
|
|
+ base.insertNodeAt(false, _createMarker(endMarker));
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Gets the marker with the specified ID
|
|
|
+ *
|
|
|
+ * @param {String} id
|
|
|
+ * @return {Node}
|
|
|
+ * @function
|
|
|
+ * @name getMarker
|
|
|
+ * @memberOf jQuery.sceditor.rangeHelper.prototype
|
|
|
+ */
|
|
|
+ base.getMarker = function(id) {
|
|
|
+ return doc.getElementById(id);
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Removes the marker with the specified ID
|
|
|
+ *
|
|
|
+ * @param {String} id
|
|
|
+ * @function
|
|
|
+ * @name removeMarker
|
|
|
+ * @memberOf jQuery.sceditor.rangeHelper.prototype
|
|
|
+ */
|
|
|
+ base.removeMarker = function(id) {
|
|
|
+ var marker = base.getMarker(id);
|
|
|
+
|
|
|
+ if(marker)
|
|
|
+ marker.parentNode.removeChild(marker);
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Removes the start/end markers
|
|
|
+ *
|
|
|
+ * @function
|
|
|
+ * @name removeMarkers
|
|
|
+ * @memberOf jQuery.sceditor.rangeHelper.prototype
|
|
|
+ */
|
|
|
+ base.removeMarkers = function() {
|
|
|
+ base.removeMarker(startMarker);
|
|
|
+ base.removeMarker(endMarker);
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Saves the current range location. Alias of insertMarkers()
|
|
|
+ *
|
|
|
+ * @function
|
|
|
+ * @name saveRage
|
|
|
+ * @memberOf jQuery.sceditor.rangeHelper.prototype
|
|
|
+ */
|
|
|
+ base.saveRange = function() {
|
|
|
+ base.insertMarkers();
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Select the specified range
|
|
|
+ *
|
|
|
+ * @param {Range|TextRange} range
|
|
|
+ * @function
|
|
|
+ * @name selectRange
|
|
|
+ * @memberOf jQuery.sceditor.rangeHelper.prototype
|
|
|
+ */
|
|
|
+ base.selectRange = function(range) {
|
|
|
+ if(!isW3C)
|
|
|
+ range.select();
|
|
|
+ else
|
|
|
+ {
|
|
|
+ win.getSelection().removeAllRanges();
|
|
|
+ win.getSelection().addRange(range);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Restores the last range saved by saveRange() or insertMarkers()
|
|
|
+ *
|
|
|
+ * @function
|
|
|
+ * @name restoreRange
|
|
|
+ * @memberOf jQuery.sceditor.rangeHelper.prototype
|
|
|
+ */
|
|
|
+ base.restoreRange = function() {
|
|
|
+ var range = base.selectedRange(),
|
|
|
+ start = base.getMarker(startMarker),
|
|
|
+ end = base.getMarker(endMarker);
|
|
|
+
|
|
|
+ if(!start || !end)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ if(!isW3C)
|
|
|
+ {
|
|
|
+ range = doc.body.createTextRange();
|
|
|
+ var marker = doc.body.createTextRange();
|
|
|
+
|
|
|
+ marker.moveToElementText(start);
|
|
|
+ range.setEndPoint('StartToStart', marker);
|
|
|
+ range.moveStart('character', 0);
|
|
|
+
|
|
|
+ marker.moveToElementText(end);
|
|
|
+ range.setEndPoint('EndToStart', marker);
|
|
|
+ range.moveEnd('character', 0);
|
|
|
+
|
|
|
+ base.selectRange(range);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ range = doc.createRange();
|
|
|
+ range.setStartBefore(start);
|
|
|
+ range.setEndAfter(end);
|
|
|
+
|
|
|
+ base.selectRange(range);
|
|
|
+ }
|
|
|
+
|
|
|
+ base.removeMarkers();
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Selects the text left and right of the current selection
|
|
|
+ * @param int left
|
|
|
+ * @param int right
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ _selectOuterText = function(left, right) {
|
|
|
+ var range = base.cloneSelected();
|
|
|
+
|
|
|
+ range.collapse(false);
|
|
|
+ if(!isW3C)
|
|
|
+ {
|
|
|
+ range.moveStart('character', 0-left);
|
|
|
+ range.moveEnd('character', right);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ range.setStart(range.startContainer, range.startOffset-left);
|
|
|
+ range.setEnd(range.endContainer, range.endOffset+right);
|
|
|
+ //range.deleteContents();
|
|
|
+ }
|
|
|
+
|
|
|
+ base.selectRange(range);
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Gets the text left or right of the current selection
|
|
|
+ * @param bool before
|
|
|
+ * @param int length
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ _getOuterText = function(before, length) {
|
|
|
+ var ret = "",
|
|
|
+ range = base.cloneSelected();
|
|
|
+
|
|
|
+ range.collapse(false);
|
|
|
+ if(before)
|
|
|
+ {
|
|
|
+ if(!isW3C)
|
|
|
+ {
|
|
|
+ range.moveStart('character', 0-length);
|
|
|
+ ret = range.text;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ ret = range.startContainer.textContent.substr(0, range.startOffset);
|
|
|
+ ret = ret.substr(Math.max(0, ret.length - length));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if(!isW3C)
|
|
|
+ {
|
|
|
+ range.moveEnd('character', length);
|
|
|
+ ret = range.text;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ ret = range.startContainer.textContent.substr(range.startOffset, length);
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Replaces keys with values based on the current range
|
|
|
+ *
|
|
|
+ * @param {Array} rep
|
|
|
+ * @param {Bool} includePrev If to include text before or just text after
|
|
|
+ * @param {Bool} repSorted If the keys array is pre sorted
|
|
|
+ * @param {Int} longestKey Length of the longest key
|
|
|
+ * @param {Bool} requireWhiteSpace If the key must be surrounded by whitespace
|
|
|
+ * @function
|
|
|
+ * @name raplaceKeyword
|
|
|
+ * @memberOf jQuery.sceditor.rangeHelper.prototype
|
|
|
+ */
|
|
|
+ base.raplaceKeyword = function(rep, includeAfter, repSorted, longestKey, requireWhiteSpace, curChar) {
|
|
|
+ if(!repSorted)
|
|
|
+ rep.sort(function(a, b){
|
|
|
+ return a.length - b.length;
|
|
|
+ });
|
|
|
+
|
|
|
+ var maxKeyLen = longestKey || rep[rep.length-1][0].length,
|
|
|
+ before, after, str, i, start, left, pat, lookStart;
|
|
|
+
|
|
|
+ before = after = str = "";
|
|
|
+
|
|
|
+ if(requireWhiteSpace)
|
|
|
+ {
|
|
|
+ // forcing spaces around doesn't work with textRanges as they will select text
|
|
|
+ // on the other side of an image causing space-img-key to be returned as
|
|
|
+ // space-key which would be valid when it's not.
|
|
|
+ if(!isW3C)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ ++maxKeyLen;
|
|
|
+ }
|
|
|
+
|
|
|
+ before = _getOuterText(true, maxKeyLen);
|
|
|
+
|
|
|
+ if(includeAfter)
|
|
|
+ after = _getOuterText(false, maxKeyLen);
|
|
|
+
|
|
|
+ str = before + (curChar!=null?curChar:"") + after;
|
|
|
+ i = rep.length;
|
|
|
+ while(i--)
|
|
|
+ {
|
|
|
+ pat = new RegExp("(?:[\\s\xA0\u2002\u2003\u2009])" + $.sceditor.regexEscape(rep[i][0]) + "(?=[\\s\xA0\u2002\u2003\u2009])");
|
|
|
+ lookStart = before.length - 1 - rep[i][0].length;
|
|
|
+
|
|
|
+ if(requireWhiteSpace)
|
|
|
+ --lookStart;
|
|
|
+
|
|
|
+ lookStart = Math.max(0, lookStart);
|
|
|
+
|
|
|
+ if((!requireWhiteSpace && (start = str.indexOf(rep[i][0], lookStart)) > -1) ||
|
|
|
+ (requireWhiteSpace && (start = str.substr(lookStart).search(pat)) > -1))
|
|
|
+ {
|
|
|
+ if(requireWhiteSpace)
|
|
|
+ start += lookStart + 1;
|
|
|
+
|
|
|
+ // make sure the substr is between before and after not entierly in one
|
|
|
+ // or the other
|
|
|
+ if(start > before.length || start+rep[i][0].length + (requireWhiteSpace?1:0) < before.length)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ left = before.length - start;
|
|
|
+ _selectOuterText(left, rep[i][0].length-left-(curChar!=null&&/^\S/.test(curChar)?1:0));
|
|
|
+ base.insertHTML(rep[i][1]);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ };
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Static DOM helper class
|
|
|
+ * @class dom
|
|
|
+ * @name jQuery.sceditor.dom
|
|
|
+ */
|
|
|
+ $.sceditor.dom =
|
|
|
+ /** @lends jQuery.sceditor.dom */
|
|
|
+ {
|
|
|
+ /**
|
|
|
+ * Loop all child nodes of the passed node
|
|
|
+ *
|
|
|
+ * The function should accept 1 parameter being the node.
|
|
|
+ * If the function returns false the loop will be exited.
|
|
|
+ *
|
|
|
+ * @param {HTMLElement} node
|
|
|
+ * @param {function} func Function that is called for every node, should accept 1 param for the node
|
|
|
+ * @param {bool} innermostFirst If the innermost node should be passed to the function before it's parents
|
|
|
+ * @param {bool} siblingsOnly If to only traverse the nodes siblings
|
|
|
+ * @param {bool} reverse If to traverse the nodes in reverse
|
|
|
+ */
|
|
|
+ traverse: function(node, func, innermostFirst, siblingsOnly, reverse) {
|
|
|
+ if(node)
|
|
|
+ {
|
|
|
+ node = reverse ? node.lastChild : node.firstChild;
|
|
|
+
|
|
|
+ while(node != null)
|
|
|
+ {
|
|
|
+ var next = reverse ? node.previousSibling : node.nextSibling;
|
|
|
+
|
|
|
+ if(!innermostFirst && func(node) === false)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ // traverse all children
|
|
|
+ if(!siblingsOnly && this.traverse(node, func, innermostFirst, siblingsOnly, reverse) === false)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ if(innermostFirst && func(node) === false)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ // move to next child
|
|
|
+ node = next;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Like traverse but loops in reverse
|
|
|
+ * @see traverse
|
|
|
+ */
|
|
|
+ rTraverse: function(node, func, innermostFirst, siblingsOnly) {
|
|
|
+ this.traverse(node, func, innermostFirst, siblingsOnly, true);
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * List of block level elements seperated by bars (|)
|
|
|
+ * @type {string}
|
|
|
+ */
|
|
|
+ blockLevelList: "|body|hr|p|div|h1|h2|h3|h4|h5|h6|address|pre|form|table|tbody|thead|tfoot|th|tr|td|li|ol|ul|blockquote|center|",
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Checks if an element is inline
|
|
|
+ *
|
|
|
+ * @return {bool}
|
|
|
+ */
|
|
|
+ isInline: function(elm, includeCodeAsBlock) {
|
|
|
+ if(!elm || elm.nodeType !== 1)
|
|
|
+ return true;
|
|
|
+
|
|
|
+ if(includeCodeAsBlock && elm.tagName.toLowerCase() === 'code')
|
|
|
+ return false;
|
|
|
+
|
|
|
+ return $.sceditor.dom.blockLevelList.indexOf("|" + elm.tagName.toLowerCase() + "|") < 0;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Copys the CSS from 1 node to another
|
|
|
+ *
|
|
|
+ * @param {HTMLElement} from
|
|
|
+ * @param {HTMLElement} to
|
|
|
+ */
|
|
|
+ copyCSS: function(from, to) {
|
|
|
+ to.style.cssText = from.style.cssText;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Fixes block level elements inside in inline elements.
|
|
|
+ *
|
|
|
+ * @param {HTMLElement} node
|
|
|
+ */
|
|
|
+ fixNesting: function(node) {
|
|
|
+ var base = this,
|
|
|
+ getLastInlineParent = function(node) {
|
|
|
+ while(base.isInline(node.parentNode, true))
|
|
|
+ node = node.parentNode;
|
|
|
+
|
|
|
+ return node;
|
|
|
+ };
|
|
|
+
|
|
|
+ base.traverse(node, function(node) {
|
|
|
+ // if node is an element, and it is blocklevel and the parent isn't block level
|
|
|
+ // then it needs fixing
|
|
|
+ if(node.nodeType === 1 && !base.isInline(node, true) && base.isInline(node.parentNode, true))
|
|
|
+ {
|
|
|
+ var parent = getLastInlineParent(node),
|
|
|
+ rParent = parent.parentNode,
|
|
|
+ before = base.extractContents(parent, node),
|
|
|
+ middle = node;
|
|
|
+
|
|
|
+ // copy current styling so when moved out of the parent
|
|
|
+ // it still has the same styling
|
|
|
+ base.copyCSS(middle, middle);
|
|
|
+
|
|
|
+ rParent.insertBefore(before, parent);
|
|
|
+ rParent.insertBefore(middle, parent);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Finds the common parent of two nodes
|
|
|
+ *
|
|
|
+ * @param {HTMLElement} node1
|
|
|
+ * @param {HTMLElement} node2
|
|
|
+ * @return {HTMLElement}
|
|
|
+ */
|
|
|
+ findCommonAncestor: function(node1, node2) {
|
|
|
+ // not as fast as making two arrays of parents and comparing
|
|
|
+ // but is a lot smaller and as it's currently only used with
|
|
|
+ // fixing invalid nesting it doesn't need to be very fast
|
|
|
+ return $(node1).parents().has($(node2)).first();
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Removes unused whitespace from the root and all it's children
|
|
|
+ *
|
|
|
+ * @param HTMLElement root
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+ removeWhiteSpace: function(root) {
|
|
|
+ // 00A0 is non-breaking space which should not be striped
|
|
|
+ var regex = /[^\S|\u00A0]+/g;
|
|
|
+
|
|
|
+ this.traverse(root, function(node) {
|
|
|
+ if(node.nodeType === 3 && $(node).parents('code, pre').length === 0 && node.nodeValue)
|
|
|
+ {
|
|
|
+ // new lines in text nodes are always ignored in normal handling
|
|
|
+ node.nodeValue = node.nodeValue.replace(/[\r\n]/, "");
|
|
|
+
|
|
|
+ //remove empty nodes
|
|
|
+ if(!node.nodeValue.length)
|
|
|
+ {
|
|
|
+ node.parentNode.removeChild(node);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(!/\S|\u00A0/.test(node.nodeValue))
|
|
|
+ node.nodeValue = " ";
|
|
|
+ else if(regex.test(node.nodeValue))
|
|
|
+ node.nodeValue = node.nodeValue.replace(regex, " ");
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Extracts all the nodes between the start and end nodes
|
|
|
+ *
|
|
|
+ * @param {HTMLElement} startNode The node to start extracting at
|
|
|
+ * @param {HTMLElement} endNode The node to stop extracting at
|
|
|
+ * @return {DocumentFragment}
|
|
|
+ */
|
|
|
+ extractContents: function(startNode, endNode) {
|
|
|
+ var base = this,
|
|
|
+ $commonAncestor = base.findCommonAncestor(startNode, endNode),
|
|
|
+ commonAncestor = !$commonAncestor?null:$commonAncestor.get(0),
|
|
|
+ startReached = false,
|
|
|
+ endReached = false;
|
|
|
+
|
|
|
+ return (function extract(root) {
|
|
|
+ var df = startNode.ownerDocument.createDocumentFragment();
|
|
|
+
|
|
|
+ base.traverse(root, function(node) {
|
|
|
+ // if end has been reached exit loop
|
|
|
+ if(endReached || (node === endNode && startReached))
|
|
|
+ {
|
|
|
+ endReached = true;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(node === startNode)
|
|
|
+ startReached = true;
|
|
|
+
|
|
|
+ var c, n;
|
|
|
+ if(startReached)
|
|
|
+ {
|
|
|
+ // if the start has been reached and this elm contains
|
|
|
+ // the end node then clone it
|
|
|
+ if(jQuery.contains(node, endNode) && node.nodeType === 1)
|
|
|
+ {
|
|
|
+ c = extract(node);
|
|
|
+ n = node.cloneNode(false);
|
|
|
+
|
|
|
+ n.appendChild(c);
|
|
|
+ df.appendChild(n);
|
|
|
+ }
|
|
|
+ // otherwise just move it
|
|
|
+ else
|
|
|
+ df.appendChild(node);
|
|
|
+ }
|
|
|
+ // if this node contains the start node then add it
|
|
|
+ else if(jQuery.contains(node, startNode) && node.nodeType === 1)
|
|
|
+ {
|
|
|
+ c = extract(node);
|
|
|
+ n = node.cloneNode(false);
|
|
|
+
|
|
|
+ n.appendChild(c);
|
|
|
+ df.appendChild(n);
|
|
|
+ }
|
|
|
+ }, false);
|
|
|
+
|
|
|
+ return df;
|
|
|
+ }(commonAncestor));
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Static command helper class
|
|
|
+ * @class command
|
|
|
+ * @name jQuery.sceditor.command
|
|
|
+ */
|
|
|
+ $.sceditor.command =
|
|
|
+ /** @lends jQuery.sceditor.command */
|
|
|
+ {
|
|
|
+ /**
|
|
|
+ * Gets a command
|
|
|
+ *
|
|
|
+ * @param {String} name
|
|
|
+ * @return {Object|null}
|
|
|
+ * @since v1.3.5
|
|
|
+ */
|
|
|
+ get: function(name) {
|
|
|
+ return $.sceditor.commands[name] || null;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * <p>Adds a command to the editor or updates an exisiting
|
|
|
+ * command if a command with the specified name already exists.</p>
|
|
|
+ *
|
|
|
+ * <p>Once a command is add it can be included in the toolbar by
|
|
|
+ * adding it's name to the toolbar option in the constructor. It
|
|
|
+ * can also be executed manually by calling {@link jQuery.sceditor.execCommand}</p>
|
|
|
+ *
|
|
|
+ * @example
|
|
|
+ * $.sceditor.command.set("hello",
|
|
|
+ * {
|
|
|
+ * exec: function() {
|
|
|
+ * alert("Hello World!");
|
|
|
+ * }
|
|
|
+ * });
|
|
|
+ *
|
|
|
+ * @param {String} name
|
|
|
+ * @param {Object} cmd
|
|
|
+ * @return {this|false} Returns false if name or cmd is false
|
|
|
+ * @since v1.3.5
|
|
|
+ */
|
|
|
+ set: function(name, cmd) {
|
|
|
+ if(!name || !cmd)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ // merge any existing command properties
|
|
|
+ cmd = $.extend($.sceditor.commands[name] || {}, cmd);
|
|
|
+
|
|
|
+ cmd.remove = function() { $.sceditor.command.remove(name); };
|
|
|
+
|
|
|
+ $.sceditor.commands[name] = cmd;
|
|
|
+ return this;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Removes a command
|
|
|
+ *
|
|
|
+ * @param {String} name
|
|
|
+ * @return {this}
|
|
|
+ * @since v1.3.5
|
|
|
+ */
|
|
|
+ remove: function(name) {
|
|
|
+ if($.sceditor.commands[name])
|
|
|
+ delete $.sceditor.commands[name];
|
|
|
+
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Checks if a command with the specified name exists
|
|
|
+ *
|
|
|
+ * @param {String} name
|
|
|
+ * @return {Bool}
|
|
|
+ * @deprecated Since v1.3.5
|
|
|
+ * @memberOf jQuery.sceditor
|
|
|
+ */
|
|
|
+ $.sceditor.commandExists = function(name) {
|
|
|
+ return !!$.sceditor.command.get(name);
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Adds/updates a command.
|
|
|
+ *
|
|
|
+ * Only name and exec are required. Exec is only required if
|
|
|
+ * the command dose not already exist.
|
|
|
+ *
|
|
|
+ * @param {String} name The commands name
|
|
|
+ * @param {String|Function} exec The commands exec function or string for the native execCommand
|
|
|
+ * @param {String} tooltip The commands tooltip text
|
|
|
+ * @param {Function} keypress Function that gets called every time a key is pressed
|
|
|
+ * @param {Function|Array} txtExec Called when the command is executed in source mode or array containing prepend and optional append
|
|
|
+ * @return {Bool}
|
|
|
+ * @deprecated Since v1.3.5
|
|
|
+ * @memberOf jQuery.sceditor
|
|
|
+ */
|
|
|
+ $.sceditor.setCommand = function(name, exec, tooltip, keypress, txtExec) {
|
|
|
+ return !!$.sceditor.command.set(name, {
|
|
|
+ exec: exec,
|
|
|
+ tooltip: tooltip,
|
|
|
+ keypress: keypress,
|
|
|
+ txtExec: txtExec
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ $.sceditor.defaultOptions = {
|
|
|
+ // Toolbar buttons order and groups. Should be comma seperated and have a bar | to seperate groups
|
|
|
+ toolbar: "bold,italic,underline,strike,subscript,superscript|left,center,right,justify|" +
|
|
|
+ "font,size,color,removeformat|cut,copy,paste,pastetext|bulletlist,orderedlist|" +
|
|
|
+ "table|code,quote|horizontalrule,image,email,link,unlink|emoticon,youtube,date,time|" +
|
|
|
+ "ltr,rtl|print,source",
|
|
|
+
|
|
|
+ // Stylesheet to include in the WYSIWYG editor. Will style the WYSIWYG elements
|
|
|
+ style: "jquery.sceditor.default.css",
|
|
|
+
|
|
|
+ // Comma seperated list of fonts for the font selector
|
|
|
+ fonts: "Arial,Arial Black,Comic Sans MS,Courier New,Georgia,Impact,Sans-serif,Serif,Times New Roman,Trebuchet MS,Verdana",
|
|
|
+
|
|
|
+ // Colors should be comma seperated and have a bar | to signal a new column. If null the colors will be auto generated.
|
|
|
+ colors: null,
|
|
|
+
|
|
|
+ locale: "en",
|
|
|
+
|
|
|
+ charset: "utf-8",
|
|
|
+
|
|
|
+ // compatibility mode for if you have emoticons such as :/ This mode requires
|
|
|
+ // emoticons to be surrounded by whitespace or end of line chars. This mode
|
|
|
+ // has limited As You Type emoticon converstion support (end of line chars)
|
|
|
+ // are not accepted as whitespace so only emoticons surrounded by whitespace
|
|
|
+ // will work
|
|
|
+ emoticonsCompat: false,
|
|
|
+ emoticonsRoot: '',
|
|
|
+ emoticons: {
|
|
|
+ dropdown: {
|
|
|
+ ":)": "emoticons/smile.png",
|
|
|
+ ":angel:": "emoticons/angel.png",
|
|
|
+ ":angry:": "emoticons/angry.png",
|
|
|
+ "8-)": "emoticons/cool.png",
|
|
|
+ ":'(": "emoticons/cwy.png",
|
|
|
+ ":ermm:": "emoticons/ermm.png",
|
|
|
+ ":D": "emoticons/grin.png",
|
|
|
+ "<3": "emoticons/heart.png",
|
|
|
+ ":(": "emoticons/sad.png",
|
|
|
+ ":O": "emoticons/shocked.png",
|
|
|
+ ":P": "emoticons/tongue.png",
|
|
|
+ ";)": "emoticons/wink.png"
|
|
|
+ },
|
|
|
+ more: {
|
|
|
+ ":alien:": "emoticons/alien.png",
|
|
|
+ ":blink:": "emoticons/blink.png",
|
|
|
+ ":blush:": "emoticons/blush.png",
|
|
|
+ ":cheerful:": "emoticons/cheerful.png",
|
|
|
+ ":devil:": "emoticons/devil.png",
|
|
|
+ ":dizzy:": "emoticons/dizzy.png",
|
|
|
+ ":getlost:": "emoticons/getlost.png",
|
|
|
+ ":happy:": "emoticons/happy.png",
|
|
|
+ ":kissing:": "emoticons/kissing.png",
|
|
|
+ ":ninja:": "emoticons/ninja.png",
|
|
|
+ ":pinch:": "emoticons/pinch.png",
|
|
|
+ ":pouty:": "emoticons/pouty.png",
|
|
|
+ ":sick:": "emoticons/sick.png",
|
|
|
+ ":sideways:": "emoticons/sideways.png",
|
|
|
+ ":silly:": "emoticons/silly.png",
|
|
|
+ ":sleeping:": "emoticons/sleeping.png",
|
|
|
+ ":unsure:": "emoticons/unsure.png",
|
|
|
+ ":woot:": "emoticons/w00t.png",
|
|
|
+ ":wassat:": "emoticons/wassat.png"
|
|
|
+ },
|
|
|
+ hidden: {
|
|
|
+ ":whistling:": "emoticons/whistling.png",
|
|
|
+ ":love:": "emoticons/wub.png"
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // Width of the editor. Set to null for automatic with
|
|
|
+ width: null,
|
|
|
+
|
|
|
+ // Height of the editor including toolbat. Set to null for automatic height
|
|
|
+ height: null,
|
|
|
+
|
|
|
+ // If to allow the editor to be resized
|
|
|
+ resizeEnabled: true,
|
|
|
+
|
|
|
+ // Min resize to width, set to null for half textarea width or -1 for unlimited
|
|
|
+ resizeMinWidth: null,
|
|
|
+ // Min resize to height, set to null for half textarea height or -1 for unlimited
|
|
|
+ resizeMinHeight: null,
|
|
|
+ // Max resize to height, set to null for double textarea height or -1 for unlimited
|
|
|
+ resizeMaxHeight: null,
|
|
|
+ // Max resize to width, set to null for double textarea width or -1 for unlimited
|
|
|
+ resizeMaxWidth: null,
|
|
|
+
|
|
|
+ getHtmlHandler: null,
|
|
|
+ getTextHandler: null,
|
|
|
+
|
|
|
+ // date format. year, month and day will be replaced with the users current year, month and day.
|
|
|
+ dateFormat: "year-month-day",
|
|
|
+
|
|
|
+ toolbarContainer: null,
|
|
|
+
|
|
|
+ // Curently experimental
|
|
|
+ enablePasteFiltering: false,
|
|
|
+
|
|
|
+ readOnly: false,
|
|
|
+ rtl: false,
|
|
|
+ autofocus: false,
|
|
|
+ autoExpand: false,
|
|
|
+
|
|
|
+ // If to run the editor without WYSIWYG support
|
|
|
+ runWithoutWysiwygSupport: false,
|
|
|
+
|
|
|
+ id: null,
|
|
|
+
|
|
|
+ //add css to dropdown menu (eg. z-index)
|
|
|
+ dropDownCss: { }
|
|
|
+ };
|
|
|
+
|
|
|
+ $.fn.sceditor = function (options) {
|
|
|
+ if((!options || !options.runWithoutWysiwygSupport) && !$.sceditor.isWysiwygSupported())
|
|
|
+ return;
|
|
|
+
|
|
|
+ return this.each(function () {
|
|
|
+ (new $.sceditor(this, options));
|
|
|
+ });
|
|
|
+ };
|
|
|
+})(jQuery, window, document);
|
|
|
+
|
|
|
+(function($) {
|
|
|
+ var extensionMethods = {
|
|
|
+ InsertText: function(text, bClear) {
|
|
|
+ var bIsSource = this.inSourceMode();
|
|
|
+
|
|
|
+ // @TODO make it put the quote close to the current selection
|
|
|
+
|
|
|
+ if (!bIsSource)
|
|
|
+ this.toggleTextMode();
|
|
|
+
|
|
|
+ var current_value = bClear ? text + "\n" : this.getTextareaValue(false) + "\n" + text + "\n";
|
|
|
+ this.setTextareaValue(current_value);
|
|
|
+
|
|
|
+ if (!bIsSource)
|
|
|
+ this.toggleTextMode();
|
|
|
+
|
|
|
+ },
|
|
|
+ getText: function() {
|
|
|
+ if(this.inSourceMode())
|
|
|
+ var current_value = this.getTextareaValue(false);
|
|
|
+ else
|
|
|
+ var current_value = this.getWysiwygEditorValue();
|
|
|
+
|
|
|
+ return current_value;
|
|
|
+ },
|
|
|
+ appendEmoticon: function (code, emoticon) {
|
|
|
+ if (code == '')
|
|
|
+ line.append($('<br />'));
|
|
|
+ else
|
|
|
+ line.append($('<img />')
|
|
|
+ .attr({
|
|
|
+ src: emoticon,
|
|
|
+ alt: code,
|
|
|
+ })
|
|
|
+ .click(function (e) {
|
|
|
+ var start = '', end = '';
|
|
|
+
|
|
|
+ if (base.options.emoticonsCompat)
|
|
|
+ {
|
|
|
+ start = '<span> ';
|
|
|
+ end = ' </span>';
|
|
|
+ }
|
|
|
+
|
|
|
+ if (base.inSourceMode())
|
|
|
+ base.textEditorInsertText(' ' + $(this).attr('alt') + ' ');
|
|
|
+ else
|
|
|
+ base.wysiwygEditorInsertHtml(start + '<img src="' + $(this).attr("src") +
|
|
|
+ '" data-sceditor-emoticon="' + $(this).attr('alt') + '" />' + end);
|
|
|
+
|
|
|
+ e.preventDefault();
|
|
|
+ })
|
|
|
+ );
|
|
|
+
|
|
|
+ if (line.children().length > 0)
|
|
|
+ content.append(line);
|
|
|
+
|
|
|
+ $(".sceditor-toolbar").append(content);
|
|
|
+ },
|
|
|
+ storeLastState: function (){
|
|
|
+ this.wasSource = this.inSourceMode();
|
|
|
+ },
|
|
|
+ setTextMode: function () {
|
|
|
+ if (!this.inSourceMode())
|
|
|
+ this.toggleTextMode();
|
|
|
+ },
|
|
|
+ createPermanentDropDown: function() {
|
|
|
+ var emoticons = $.extend({}, this.options.emoticons.dropdown);
|
|
|
+ var popup_exists = false;
|
|
|
+ content = $('<div class="sceditor-insertemoticon" />');
|
|
|
+ line = $('<div />');
|
|
|
+
|
|
|
+ base = this;
|
|
|
+ for (smiley_popup in this.options.emoticons.popup)
|
|
|
+ {
|
|
|
+ popup_exists = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (popup_exists)
|
|
|
+ {
|
|
|
+ this.options.emoticons.more = this.options.emoticons.popup;
|
|
|
+ moreButton = $('<div class="sceditor-more-button" />').attr({class: "sceditor-more"}).text('[' + this._('More') + ']').click(function () {
|
|
|
+ if ($(".sceditor-smileyPopup").length > 0)
|
|
|
+ {
|
|
|
+ $(".sceditor-smileyPopup").fadeIn('fast');
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ var emoticons = $.extend({}, base.options.emoticons.popup);
|
|
|
+ var popup_position;
|
|
|
+ var titlebar = $('<div class="catbg sceditor-popup-grip"/>');
|
|
|
+ popupContent = $('<div id="sceditor-popup"/>');
|
|
|
+ allowHide = true;
|
|
|
+ popupContent.append(titlebar);
|
|
|
+ line = $('<div />');
|
|
|
+ closeButton = $('<span />').text('[' + base._('Close') + ']').click(function () {
|
|
|
+ $(".sceditor-smileyPopup").fadeOut('fast');
|
|
|
+ });
|
|
|
+
|
|
|
+ $.each(emoticons, base.appendEmoticon);
|
|
|
+
|
|
|
+ if (line.children().length > 0)
|
|
|
+ popupContent.append(line);
|
|
|
+ if (typeof closeButton !== "undefined")
|
|
|
+ popupContent.append(closeButton);
|
|
|
+
|
|
|
+ // 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(base.ieUnselectable !== false) {
|
|
|
+ content = $(content);
|
|
|
+ content.find(':not(input,textarea)').filter(function() { return this.nodeType===1; }).attr('unselectable', 'on');
|
|
|
+ }
|
|
|
+
|
|
|
+ $dropdown = $('<div class="sceditor-dropdown sceditor-smileyPopup" />').append(popupContent);
|
|
|
+
|
|
|
+ $dropdown.appendTo($('body'));
|
|
|
+ dropdownIgnoreLastClick = true;
|
|
|
+ $dropdown.css({
|
|
|
+ position: "fixed",
|
|
|
+ top: $(window).height() * 0.2,
|
|
|
+ left: $(window).width() * 0.5 - ($dropdown.width() / 2),
|
|
|
+ "max-width": "50%"
|
|
|
+ });
|
|
|
+
|
|
|
+ $('.sceditor-smileyPopup').animaDrag({
|
|
|
+ speed: 150,
|
|
|
+ interval: 120,
|
|
|
+ grip: '.sceditor-popup-grip'
|
|
|
+ });
|
|
|
+ // stop clicks within the dropdown from being handled
|
|
|
+ $dropdown.click(function (e) {
|
|
|
+ e.stopPropagation();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ $.each(emoticons, base.appendEmoticon);
|
|
|
+ if (typeof moreButton !== "undefined")
|
|
|
+ content.append(moreButton);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ $.extend(true, $['sceditor'].prototype, extensionMethods);
|
|
|
+})(jQuery);
|
|
|
+
|
|
|
+$.sceditor.setCommand(
|
|
|
+ 'ftp',
|
|
|
+ function (caller) {
|
|
|
+ var editor = this,
|
|
|
+ content = $(this._('<form><div><label for="link">{0}</label> <input type="text" id="link" value="ftp://" /></div>' +
|
|
|
+ '<div><label for="des">{1}</label> <input type="text" id="des" value="" /></div></form>',
|
|
|
+ this._("URL:"),
|
|
|
+ this._("Description (optional):")
|
|
|
+ ))
|
|
|
+ .submit(function () {return false;});
|
|
|
+
|
|
|
+ content.append($(
|
|
|
+ this._('<div><input type="button" class="button" value="{0}" /></div>',
|
|
|
+ this._("Insert")
|
|
|
+ )).click(function (e) {
|
|
|
+ var val = $(this).parent("form").find("#link").val(),
|
|
|
+ description = $(this).parent("form").find("#des").val();
|
|
|
+
|
|
|
+ if(val !== "" && val !== "ftp://") {
|
|
|
+ // needed for IE to reset the last range
|
|
|
+ editor.focus();
|
|
|
+
|
|
|
+ if(!editor.getRangeHelper().selectedHtml() || description)
|
|
|
+ {
|
|
|
+ if(!description)
|
|
|
+ description = val;
|
|
|
+
|
|
|
+ editor.wysiwygEditorInsertHtml('<a href="' + val + '">' + description + '</a>');
|
|
|
+ }
|
|
|
+ else
|
|
|
+ editor.execCommand("createlink", val);
|
|
|
+ }
|
|
|
+
|
|
|
+ editor.closeDropDown(true);
|
|
|
+ e.preventDefault();
|
|
|
+ }));
|
|
|
+
|
|
|
+ editor.createDropDown(caller, "insertlink", content);
|
|
|
+ },
|
|
|
+ 'Insert FTP Link'
|
|
|
+);
|
|
|
+$.sceditor.setCommand(
|
|
|
+ 'glow',
|
|
|
+ function () {
|
|
|
+ this.wysiwygEditorInsertHtml('[glow=red,2,300]', '[/glow]');
|
|
|
+ },
|
|
|
+ 'Glow'
|
|
|
+);
|
|
|
+$.sceditor.setCommand(
|
|
|
+ 'shadow',
|
|
|
+ function () {
|
|
|
+ this.wysiwygEditorInsertHtml('[shadow=red,left]', '[/shadow]');
|
|
|
+ },
|
|
|
+ 'Shadow'
|
|
|
+);
|
|
|
+$.sceditor.setCommand(
|
|
|
+ 'tt',
|
|
|
+ function () {
|
|
|
+ this.wysiwygEditorInsertHtml('<tt>', '</tt>');
|
|
|
+ },
|
|
|
+ 'Teletype'
|
|
|
+);
|
|
|
+$.sceditor.setCommand(
|
|
|
+ 'pre',
|
|
|
+ function () {
|
|
|
+ this.wysiwygEditorInsertHtml('<pre>', '</pre>');
|
|
|
+ },
|
|
|
+ 'Pre'
|
|
|
+);
|
|
|
+
|
|
|
+/**
|
|
|
+ * AnimaDrag
|
|
|
+ * Animated jQuery Drag and Drop Plugin
|
|
|
+ * Version 0.5.1 beta
|
|
|
+ * Author Abel Mohler
|
|
|
+ * Released with the MIT License: http://www.opensource.org/licenses/mit-license.php
|
|
|
+ */
|
|
|
+(function($){
|
|
|
+ $.fn.animaDrag = function(o, callback) {
|
|
|
+ var defaults = {
|
|
|
+ speed: 400,
|
|
|
+ interval: 300,
|
|
|
+ easing: null,
|
|
|
+ cursor: 'move',
|
|
|
+ boundary: document.body,
|
|
|
+ grip: null,
|
|
|
+ overlay: true,
|
|
|
+ after: function(e) {},
|
|
|
+ during: function(e) {},
|
|
|
+ before: function(e) {},
|
|
|
+ afterEachAnimation: function(e) {}
|
|
|
+ }
|
|
|
+ if(typeof callback == 'function') {
|
|
|
+ defaults.after = callback;
|
|
|
+ }
|
|
|
+ o = $.extend(defaults, o || {});
|
|
|
+ return this.each(function() {
|
|
|
+ var id, startX, startY, draggableStartX, draggableStartY, dragging = false, Ev, draggable = this,
|
|
|
+ grip = ($(this).find(o.grip).length > 0) ? $(this).find(o.grip) : $(this);
|
|
|
+ if(o.boundary) {
|
|
|
+ var limitTop = $(o.boundary).offset().top, limitLeft = $(o.boundary).offset().left,
|
|
|
+ limitBottom = limitTop + $(o.boundary).innerHeight(), limitRight = limitLeft + $(o.boundary).innerWidth();
|
|
|
+ }
|
|
|
+ grip.mousedown(function(e) {
|
|
|
+ o.before.call(draggable, e);
|
|
|
+
|
|
|
+ var lastX, lastY;
|
|
|
+ dragging = true;
|
|
|
+
|
|
|
+ Ev = e;
|
|
|
+
|
|
|
+ startX = lastX = e.pageX;
|
|
|
+ startY = lastY = e.pageY;
|
|
|
+ draggableStartX = $(draggable).offset().left;
|
|
|
+ draggableStartY = $(draggable).offset().top;
|
|
|
+
|
|
|
+ $(draggable).css({
|
|
|
+ position: 'absolute',
|
|
|
+ left: draggableStartX + 'px',
|
|
|
+ top: draggableStartY + 'px',
|
|
|
+ cursor: o.cursor,
|
|
|
+ zIndex: '1010'
|
|
|
+ }).addClass('anima-drag').appendTo(document.body);
|
|
|
+ if(o.overlay && $('#anima-drag-overlay').length == 0) {
|
|
|
+ $('<div id="anima-drag-overlay"></div>').css({
|
|
|
+ position: 'absolute',
|
|
|
+ top: '0',
|
|
|
+ left: '0',
|
|
|
+ zIndex: '1000',
|
|
|
+ width: $(document.body).outerWidth() + 'px',
|
|
|
+ height: $(document.body).outerHeight() + 'px'
|
|
|
+ }).appendTo(document.body);
|
|
|
+ }
|
|
|
+ else if(o.overlay) {
|
|
|
+ $('#anima-drag-overlay').show();
|
|
|
+ }
|
|
|
+ id = setInterval(function() {
|
|
|
+ if(lastX != Ev.pageX || lastY != Ev.pageY) {
|
|
|
+ var positionX = draggableStartX - (startX - Ev.pageX), positionY = draggableStartY - (startY - Ev.pageY);
|
|
|
+ if(positionX < limitLeft && o.boundary) {
|
|
|
+ positionX = limitLeft;
|
|
|
+ }
|
|
|
+ else if(positionX + $(draggable).innerWidth() > limitRight && o.boundary) {
|
|
|
+ positionX = limitRight - $(draggable).outerWidth();
|
|
|
+ }
|
|
|
+ if(positionY < limitTop && o.boundary) {
|
|
|
+ positionY = limitTop;
|
|
|
+ }
|
|
|
+ else if(positionY + $(draggable).innerHeight() > limitBottom && o.boundary) {
|
|
|
+ positionY = limitBottom - $(draggable).outerHeight();
|
|
|
+ }
|
|
|
+ $(draggable).stop().animate({
|
|
|
+ left: positionX + 'px',
|
|
|
+ top: positionY + 'px'
|
|
|
+ }, o.speed, o.easing, function(){o.afterEachAnimation.call(draggable, Ev)});
|
|
|
+ }
|
|
|
+ lastX = Ev.pageX;
|
|
|
+ lastY = Ev.pageY;
|
|
|
+ }, o.interval);
|
|
|
+ ($.browser.safari || e.preventDefault());
|
|
|
+ });
|
|
|
+ $(document).mousemove(function(e) {
|
|
|
+ if(dragging) {
|
|
|
+ Ev = e;
|
|
|
+ o.during.call(draggable, e);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ $(document).mouseup(function(e) {
|
|
|
+ if(dragging) {
|
|
|
+ $(draggable).css({
|
|
|
+ cursor: '',
|
|
|
+ zIndex: '990'
|
|
|
+ }).removeClass('anima-drag');
|
|
|
+ $('#anima-drag-overlay').hide().appendTo(document.body);
|
|
|
+ clearInterval(id);
|
|
|
+ o.after.call(draggable, e);
|
|
|
+ dragging = false;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+})(jQuery);
|