/** * WYSIWYG - jQuery plugin @VERSION * (Pretty girl) * * Copyright (c) 2008-2009 Juan M Martinez, 2010-2011 Akzhan Abdulin and all contributors * https://github.com/akzhan/jwysiwyg * * Dual licensed under the MIT and GPL licenses: * http://www.opensource.org/licenses/mit-license.php * http://www.gnu.org/licenses/gpl.html * */ /*jslint browser: true, forin: true */ (function ($) { var console = window.console ? window.console : { log: $.noop, error: function(msg){ $.error(msg); } }; $.wysiwyg = $.wysiwyg || { version: '@VERSION' }; // Global configuration. Allows setting configuration information // for all editor instances at once. $.wysiwyg.config = { html: '
INITIAL_CONTENT', debug: false, controls: 'bold,italic,undo,redo', init: true, css: {}, events: {}, autoGrow: false, autoSave: true, brIE: true, // http://code.google.com/p/jwysiwyg/issues/detail?id=15 formHeight: 270, formWidth: 440, iFrameClass: null, initialContent: "Initial content
", maxHeight: 10000, // see autoGrow maxLength: 0, toolbar: false, // Allow setting a toolbar element directly. toolbarHtml: '', removeHeadings: false, replaceDivWithP: false, resizeOptions: false, rmUnusedControls: false, // https://github.com/akzhan/jwysiwyg/issues/52 rmUnwantedBr: true, // http://code.google.com/p/jwysiwyg/issues/detail?id=11 tableFiller: "Lorem ipsum", initialMinHeight: null, // Plugin references plugins: {}, // Dialog provider setting. Default is the one built into jWysiwyg. dialog: "default" }; // References the active editor instance, useful for having a global toolbar. $.wysiwyg.activeEditor = null; // Default control list, allows easily adding basic / custom controls to // all editor instances. $.wysiwyg.controls = { register: function(){ } }; // Utility Functions function parsePluginName(name) { var elements; if ("string" !== typeof (name)) return false; elements = name.split("."); if (2 > elements.length) return false; return {name: elements[0], method: elements[1]}; } // Global utility functions $.wysiwyg.utils = { extraSafeEntities: [["<", ">", "'", '"', " "], [32]], encodeEntities: function(str) { var self = this, aStr, aRet = []; if (this.extraSafeEntities[1].length === 0) { $.each(this.extraSafeEntities[0], function (i, ch) { self.extraSafeEntities[1].push(ch.charCodeAt()); }); } aStr = str.split(""); $.each(aStr, function (i) { var iC = aStr[i].charCodeAt(); if ($.inArray(iC, self.extraSafeEntities[1]) && (iC < 65 || iC > 127 || (iC > 90 && iC < 97))) { aRet.push('' + iC + ';'); } else { aRet.push(aStr[i]); } }); return aRet.join(''); }, // Replaces wrapInitialContent to make sure that plain text (usually initial content) // at least has a paragraph tag. wrapTextContent: function(str){ var found = str.match(/<\/?p>/gi); if(!found) return "" + str + "
"; else{ // :TODO: checking/replacing } return str; } }; // Plugin API $.wysiwyg.plugin = { register: function(data) { // Plugins require a name if (!data.name) console.error("Plugin name missing"); // Add the plugin unless it already exists. if (!$.wysiwyg[data.name]) $.wysiwyg[data.name] = data; return true; }, exists: function(name) { var plugin; if ("string" !== typeof (name)) return false; plugin = parsePluginName(name); return ($.wysiwyg[plugin.name] || $.wysiwyg[plugin.name][plugin.method]); } }; /** * Unifies dialog methods to allow custom implementations * * Events: * * afterOpen * * beforeShow * * afterShow * * beforeHide * * afterHide * * beforeClose * * afterClose * * Example: * var dialog = new ($.wysiwyg.dialog)($('#idToTextArea').data('wysiwyg'), {"title": "Test", "content": "form data, etc."}); * * dialog.bind("afterOpen", function () { alert('you should see a dialog behind this one!'); }); * * dialog.open(); * * */ $.wysiwyg.dialog = function (jWysiwyg, opts) { var theme = jWysiwyg.options.dialog, obj = $.wysiwyg.dialog.createDialog(jWysiwyg.options.dialog), that = this, $that = $(that); this.options = { "title": "Title", "content": "Content" } this.isOpen = false; $.extend(this.options, opts); // Opens a dialog with the specified content this.open = function () { this.isOpen = true; obj.init.apply(that, []); var $dialog = obj.show.apply(that, []); $that.trigger("afterOpen", [$dialog]); }; this.show = function () { this.isOpen = true; $that.trigger("beforeShow"); var $dialog = obj.show.apply(that, []); $that.trigger("afterShow"); }; this.hide = function () { this.isOpen = false; $that.trigger("beforeHide"); var $dialog = obj.hide.apply(that, []); $that.trigger("afterHide", [$dialog]); } // Closes the dialog window this.close = function () { this.isOpen = false; var $dialog = obj.hide.apply(that, []); $that.trigger("beforeClose", [$dialog]); obj.destroy.apply(that, []); $that.trigger("afterClose", [$dialog]); }; return this; }; // "Static" Dialog methods $.extend(true, $.wysiwyg.dialog, { _themes : {}, // sample {"Theme Name": object} _theme : "", // the current theme register : function(name, obj) { $.wysiwyg.dialog._themes[name] = obj; }, deregister : function (name) { delete $.wysiwyg.dialog._themes[name]; }, createDialog : function (name) { return new ($.wysiwyg.dialog._themes[name]); } }); // end Dialog // Wysiwyg function Wysiwyg(el, conf) { var self = this, editor = null, editorDoc = null, element = null, events = {}, form = null, handler, isDestroyed = true, options = $.extend({}, $.wysiwyg.config, conf), original = $(el), savedRange = null, timers = [], ui = {}, validKeyCodes = [8, 9, 13, 16, 17, 18, 19, 20, 27, 33, 34, 35, 36, 37, 38, 39, 40, 45, 46], viewHTML, // creating an array to make it easier to expand later. callbackMethods = [ "onBeforeInit", "onInit", "onFrameInit", "beforeCreate", "afterCreate", "beforeDestroy", "afterDestroy", "beforeSave", "afterSave" ]; // Allows the ability to trigger events easily. Also triggers both api versions and // element versions at the same time. // ie: handler.trigger('onInit', [opts]) handler = el.add(self); this.options = options; ////////////////////////////////////////////////////////////////////////////// // Private functions ////////////////////////////////////////////////////////////////////////////// // Enable deignMode function designMode(){ var attempts = 3, runner = function(attempts) { if("on" === editorDoc.designMode) { if (timers.designMode) window.clearTimeout(timers.designMode); // IE needs to reget the document element (this.editorDoc) after designMode was set if (innerDocument() !== editorDoc) initFrame(); return; } try { editorDoc.designMode = "on"; }catch(e){} attempts -= 1; if(attempts > 0) timers.designMode = window.setTimeout(function() { runner(attempts); }, 100); }; runner(attempts); } function focusEditor(){ editor.get(0).contentWindow.focus(); return self; } // Get the selection range, functions for editor instance, and within the editor. function getInternalRange() { var selection = getInteralSelection(); if (!selection) return null; if (selection.rangeCount && selection.rangeCount > 0) return selection.getRangeAt(0); // w3c else if (selection.createRange) return selection.createRange(); // IE return null; }; function getRange() { var selection = getSelection(); if (!selection) return null; if (selection.rangeCount && selection.rangeCount > 0) selection.getRangeAt(0); // w3c else if (selection.createRange) return selection.createRange(); // IE return null; }; function getRangeText() { var rng = getInternalRange(); if(rng.toString) rng = rng.toString(); else if (rng.text) rng = rng.text; // IE return rng; }; function getInternalSelection() { // Firefox: document.getSelection is deprecated if (editor.get(0).contentWindow) { if (editor.get(0).contentWindow.getSelection) return editor.get(0).contentWindow.getSelection(); if (editor.get(0).contentWindow.selection) return editor.get(0).contentWindow.selection; } if (editorDoc.getSelection) return editorDoc.getSelection(); if (editorDoc.selection) return editorDoc.selection; return null; }; function getSelection() { return (window.getSelection) ? window.getSelection() : window.document.selection; }; function initEditor(){ var newX = (original.width || original.clientWidth || 0), newY = (original.height || original.clientHeight || 0), i; form = original.closest("form"); if ($.browser.msie && parseInt($.browser.version, 10) < 8) options.autoGrow = false; if (newX === 0 && original.cols) newX = (original.cols * 8) + 21; // fix for issue 30 ( http://github.com/akzhan/jwysiwyg/issues/issue/30 ) //element.cols = 1; if (newY === 0 && original.rows) newY = (original.rows * 16) + 16; // fix for issue 30 ( http://github.com/akzhan/jwysiwyg/issues/issue/30 ) //element.rows = 1; editor = $(window.location.protocol === "https:" ? '' : "").attr("frameborder", "0"); element = $("").addClass("wysiwyg"); if (options.iFrameClass) editor.addClass(options.iFrameClass); else { editor.css({ minHeight: (newY - 6).toString() + "px", // fix for issue 12 ( http://github.com/akzhan/jwysiwyg/issues/issue/12 ) width: (newX > 50) ? (newX - 8).toString() + "px" : "" }); if ($.browser.msie && parseInt($.browser.version, 10) < 7) editor.css("height", newY.toString() + "px"); } /** * http://code.google.com/p/jwysiwyg/issues/detail?id=96 */ editor.attr("tabindex", original.attr("tabindex")); if (!options.iFrameClass) { element.css({ width: (newX > 0) ? newX.toString() + "px" : "100%" }); } original.hide().before(element); viewHTML = false; initialContent = original.val(); if (options.resizeOptions && $.fn.resizable) { element.resizable($.extend(true, { alsoResize: this.editor }, options.resizeOptions)); } // AutoSave when the form is submitted if (options.autoSave) form.bind("submit.wysiwyg", function() { self.save(); }); form.bind("reset.wysiwyg", function() { self.clear(); }); initFrame(); return self; }; function initFrame(){ var stylesheet, growHandler, saveHandler, controlList; if(options.toolbar) { ui.toolbar = $(options.toolbar) .addClass('toolbar'); }else{ ui.toolbar = $(options.toolbarHtml) .appendTo(element); } ui.toolbar .attr('user-select', 'none') .attr('unselectable', 'on') .attr('role', 'menu'); element.append($("") .css({clear: "both"})) .append(editor); editorDoc = innerDocument(); // Support for tinyMCE style control declarations: // controls:"bold,italic,underline,|undo,redo" if($.type(options.controls) == "string"){ controlList = []; $.each(options.controls.split(','), function(i, controlName){ if(controlName == "|"){ ui.addSeparator(); return true; }else controlList.push(controlName); if(!$.wysiwyg.controls[controlName]){ console.error("Control: '"+controlName+"' was not found."); return true; } ui.addControl(controlName, $.wysiwyg.controls[controlName]); return true; }); // Build a new controls object out of the array of names. options.controls = {}; $.each(controlList, function(i, controlName){ options.controls[controlName] = $.wysiwyg.controls[controlName]; }); } designMode(); editorDoc.open(); editorDoc.write( options.html /** * @link http://code.google.com/p/jwysiwyg/issues/detail?id=144 */ .replace(/INITIAL_CONTENT/, $.wysiwyg.utils.wrapTextContent(options.initialContent))); editorDoc.close(); // TODO: Check this against plugin / namespace changes. //$.wysiwyg.plugin.bind(self); // Setup any necessary events on the editor's document $(editorDoc) .trigger("initFrame.wysiwyg") .bind("click.wysiwyg", function(event) { ui.refresh(event.target ? event.target : event.srcElement); }) .keydown(function(event) { var emptyContentRegex; if (event.keyCode === 0) { // backspace emptyContentRegex = /^<([\w]+)[^>]*>(