/* * WYMeditor : what you see is What You Mean web-based editor * Copyright (c) 2008 Jean-Francois Hovinne, http://www.wymeditor.org/ * Dual licensed under the MIT (MIT-license.txt) * and GPL (GPL-license.txt) licenses. * * For further information visit: * http://www.wymeditor.org/ * * File: jquery.refinery.wymeditor.js * * Main JS file with core classes and functions. * See the documentation for more info. * * About: authors * * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg) * Volker Mische (vmx a-t gmx dotde) * Scott Lewis (lewiscot a-t gmail dotcom) * Bermi Ferrer (wymeditor a-t bermi dotorg) * Daniel Reszka (d.reszka a-t wymeditor dotorg) * Jonatan Lundin (jonatan.lundin _at_ gmail.com) */ /* Namespace: WYMeditor Global WYMeditor namespace. */ if(!WYMeditor) var WYMeditor = {}; //Wrap the Firebug console in WYMeditor.console (function() { if ( !window.console || !console.firebug ) { var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"]; WYMeditor.console = {}; for (var i = 0; i < names.length; ++i) WYMeditor.console[names[i]] = function() {} } else WYMeditor.console = window.console; })(); $.extend(WYMeditor, { /* Constants: Global WYMeditor constants. VERSION - Defines WYMeditor version. INSTANCES - An array of loaded WYMeditor.editor instances. STRINGS - An array of loaded WYMeditor language pairs/values. SKINS - An array of loaded WYMeditor skins. NAME - The "name" attribute. INDEX - A string replaced by the instance index. WYM_INDEX - A string used to get/set the instance index. BASE_PATH - A string replaced by WYMeditor's base path. SKIN_PATH - A string replaced by WYMeditor's skin path. WYM_PATH - A string replaced by WYMeditor's main JS file path. SKINS_DEFAULT_PATH - The skins default base path. SKINS_DEFAULT_CSS - The skins default CSS file. LANG_DEFAULT_PATH - The language files default path. IFRAME_BASE_PATH - A string replaced by the designmode iframe's base path. IFRAME_DEFAULT - The iframe's default base path. JQUERY_PATH - A string replaced by the computed jQuery path. DIRECTION - A string replaced by the text direction (rtl or ltr). LOGO - A string replaced by WYMeditor logo. TOOLS - A string replaced by the toolbar's HTML. TOOLS_ITEMS - A string replaced by the toolbar items. TOOL_NAME - A string replaced by a toolbar item's name. TOOL_TITLE - A string replaced by a toolbar item's title. TOOL_CLASS - A string replaced by a toolbar item's class. CLASSES - A string replaced by the classes panel's HTML. CLASSES_ITEMS - A string replaced by the classes items. CLASS_NAME - A string replaced by a class item's name. CLASS_TITLE - A string replaced by a class item's title. CONTAINERS - A string replaced by the containers panel's HTML. CONTAINERS_ITEMS - A string replaced by the containers items. CONTAINER_NAME - A string replaced by a container item's name. CONTAINER_TITLE - A string replaced by a container item's title. CONTAINER_CLASS - A string replaced by a container item's class. HTML - A string replaced by the HTML view panel's HTML. IFRAME - A string replaced by the designmode iframe. STATUS - A string replaced by the status panel's HTML. DIALOG_TITLE - A string replaced by a dialog's title. DIALOG_BODY - A string replaced by a dialog's HTML body. BODY - The BODY element. STRING - The "string" type. BODY,DIV,P, H1,H2,H3,H4,H5,H6, PRE,BLOCKQUOTE, A,BR,IMG, TABLE,TD,TH, UL,OL,LI - HTML elements string representation. CLASS,HREF,SRC, TITLE,ALT - HTML attributes string representation. DIALOG_LINK - A link dialog type. DIALOG_IMAGE - An image dialog type. DIALOG_TABLE - A table dialog type. DIALOG_PASTE - A 'Paste from Word' dialog type. BOLD - Command: (un)set selection to . ITALIC - Command: (un)set selection to . CREATE_LINK - Command: open the link dialog or (un)set link. INSERT_IMAGE - Command: open the image dialog or insert an image. INSERT_TABLE - Command: open the table dialog. PASTE - Command: open the paste dialog. INDENT - Command: nest a list item. OUTDENT - Command: unnest a list item. TOGGLE_HTML - Command: display/hide the HTML view. FORMAT_BLOCK - Command: set a block element to another type. PREVIEW - Command: open the preview dialog. UNLINK - Command: unset a link. INSERT_UNORDEREDLIST- Command: insert an unordered list. INSERT_ORDEREDLIST - Command: insert an ordered list. MAIN_CONTAINERS - An array of the main HTML containers used in WYMeditor. BLOCKS - An array of the HTML block elements. KEY - Standard key codes. NODE - Node types. */ VERSION : "0.5-b2-refinery", INSTANCES : [], STRINGS : [], SKINS : [], NAME : "name", INDEX : "{Wym_Index}", WYM_INDEX : "wym_index", BASE_PATH : "{Wym_Base_Path}", CSS_PATH : "{Wym_Css_Path}", WYM_PATH : "{Wym_Wym_Path}", SKINS_DEFAULT_PATH : "/images/wymeditor/skins/", SKINS_DEFAULT_CSS : "skin.css", SKINS_DEFAULT_JS : "skin.js", LANG_DEFAULT_PATH : "lang/", IFRAME_BASE_PATH : "{Wym_Iframe_Base_Path}", IFRAME_DEFAULT : "iframe/default/", JQUERY_PATH : "{Wym_Jquery_Path}", DIRECTION : "{Wym_Direction}", LOGO : "{Wym_Logo}", TOOLS : "{Wym_Tools}", TOOLS_ITEMS : "{Wym_Tools_Items}", TOOL_NAME : "{Wym_Tool_Name}", TOOL_TITLE : "{Wym_Tool_Title}", TOOL_CLASS : "{Wym_Tool_Class}", CLASSES : "{Wym_Classes}", CLASSES_ITEMS : "{Wym_Classes_Items}", CLASS_NAME : "{Wym_Class_Name}", CLASS_TITLE : "{Wym_Class_Title}", CONTAINERS : "{Wym_Containers}", CONTAINERS_ITEMS : "{Wym_Containers_Items}", CONTAINER_NAME : "{Wym_Container_Name}", CONTAINER_TITLE : "{Wym_Containers_Title}", CONTAINER_CLASS : "{Wym_Container_Class}", HTML : "{Wym_Html}", IFRAME : "{Wym_Iframe}", STATUS : "{Wym_Status}", DIALOG_TITLE : "{Wym_Dialog_Title}", DIALOG_BODY : "{Wym_Dialog_Body}", STRING : "string", BODY : "body", DIV : "div", P : "p", H1 : "h1", H2 : "h2", H3 : "h3", H4 : "h4", H5 : "h5", H6 : "h6", PRE : "pre", BLOCKQUOTE : "blockquote", A : "a", BR : "br", IMG : "img", TABLE : "table", TD : "td", TH : "th", UL : "ul", OL : "ol", LI : "li", CLASS : "class", HREF : "href", SRC : "src", TITLE : "title", TARGET : "target", ALT : "alt", DIALOG_LINK : "Link", DIALOG_IMAGE : "Image", DIALOG_TABLE : "Table", DIALOG_PASTE : "Paste_From_Word", DIALOG_CLASS : "Css_Class", BOLD : "Bold", ITALIC : "Italic", CREATE_LINK : "CreateLink", INSERT_IMAGE : "InsertImage", INSERT_TABLE : "InsertTable", INSERT_HTML : "InsertHTML", APPLY_CLASS : "Apply_Style", PASTE : "Paste", INDENT : "Indent", OUTDENT : "Outdent", TOGGLE_HTML : "ToggleHtml", FORMAT_BLOCK : "FormatBlock", PREVIEW : "Preview", UNLINK : "Unlink", INSERT_UNORDEREDLIST : "InsertUnorderedList", INSERT_ORDEREDLIST : "InsertOrderedList", MAIN_CONTAINERS : new Array("p","h1","h2","h3","h4","h5","h6","pre","blockquote"), BLOCKS : new Array("address", "blockquote", "div", "dl", "fieldset", "form", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "noscript", "ol", "p", "pre", "table", "ul", "dd", "dt", "li", "tbody", "td", "tfoot", "th", "thead", "tr"), KEY : { BACKSPACE: 8, ENTER: 13, END: 35, HOME: 36, LEFT: 37, UP: 38, RIGHT: 39, DOWN: 40, CURSOR: new Array(37, 38, 39, 40), DELETE: 46 }, NODE : { ELEMENT: 1, ATTRIBUTE: 2, TEXT: 3 }, /* Class: WYMeditor.editor WYMeditor editor main class, instanciated for each editor occurrence. */ editor : function(elem, options) { /* Constructor: WYMeditor.editor Initializes main values (index, elements, paths, ...) and call WYMeditor.editor.init which initializes the editor. Parameters: elem - The HTML element to be replaced by the editor. options - The hash of options. Returns: Nothing. See Also: */ //store the instance in the INSTANCES array and store the index this._index = WYMeditor.INSTANCES.push(this) - 1; //store the element replaced by the editor this._element = elem; //store the options this._options = options; //store the element's inner value this._html = $(elem).val(); //store the HTML option, if any if(this._options.html) this._html = this._options.html; //get or compute the base path (where the main JS file is located) this._options.basePath = this._options.basePath || this.computeBasePath(); //get or set the skin path (where the skin files are located) this._options.skinPath = this._options.skinPath || this._options.basePath + WYMeditor.SKINS_DEFAULT_PATH + this._options.skin + '/'; // set css and js skin paths this._options.cssSkinPath = (this._options.cssSkinPath || this._options.skinPath) + this._options.skin + "/"; this._options.jsSkinPath = (this._options.jsSkinPath || this._options.skinPath) + this._options.skin + "/"; //get or compute the main JS file location this._options.wymPath = this._options.wymPath || this.computeWymPath(); //get or set the language files path this._options.langPath = this._options.langPath || this._options.basePath + WYMeditor.LANG_DEFAULT_PATH; //get or set the designmode iframe's base path this._options.iframeBasePath = this._options.iframeBasePath || this._options.basePath + WYMeditor.IFRAME_DEFAULT; //get or compute the jQuery JS file location this._options.jQueryPath = this._options.jQueryPath || this.computeJqueryPath(); //initialize the editor instance this.init(); } }); /********** JQUERY **********/ /** * Replace an HTML element by WYMeditor * * @example $(".wymeditor").wymeditor( * { * * } * ); * @desc Example description here * * @name WYMeditor * @description WYMeditor is a web-based WYSIWYM XHTML editor * @param Hash hash A hash of parameters * @option Integer iExample Description here * @option String sExample Description here * * @type jQuery * @cat Plugins/WYMeditor * @author Jean-Francois Hovinne */ $.fn.wymeditor = function(options) { options = $.extend({ html: "", basePath: false, skinPath: false, jsSkinPath: false, cssSkinPath: false, wymPath: false, iframeBasePath: false, jQueryPath: false, styles: false, stylesheet: false, skin: "default", initSkin: true, loadSkin: true, lang: "en", direction: "ltr", boxHtml: "
" + "
" + WYMeditor.TOOLS + "
" + "
" + "
" + WYMeditor.CONTAINERS + WYMeditor.CLASSES + "
" + "
" + WYMeditor.HTML + WYMeditor.IFRAME + WYMeditor.STATUS + "
" + "
" + WYMeditor.LOGO + "
" + "
", logoHtml: "WYMeditor", iframeHtml:"
" + "" + "
", editorStyles: [], toolsHtml: "
" + "

{Tools}

" + "
    " + WYMeditor.TOOLS_ITEMS + "
" + "
", toolsItemHtml: "
  • " + WYMeditor.TOOL_TITLE + "
  • ", toolsItems: [ {'name': 'Bold', 'title': 'Strong', 'css': 'wym_tools_strong'}, {'name': 'Italic', 'title': 'Emphasis', 'css': 'wym_tools_emphasis'}, {'name': 'Superscript', 'title': 'Superscript', 'css': 'wym_tools_superscript'}, {'name': 'Subscript', 'title': 'Subscript', 'css': 'wym_tools_subscript'}, {'name': 'InsertOrderedList', 'title': 'Ordered_List', 'css': 'wym_tools_ordered_list'}, {'name': 'InsertUnorderedList', 'title': 'Unordered_List', 'css': 'wym_tools_unordered_list'}, {'name': 'Indent', 'title': 'Indent', 'css': 'wym_tools_indent'}, {'name': 'Outdent', 'title': 'Outdent', 'css': 'wym_tools_outdent'}, {'name': 'Undo', 'title': 'Undo', 'css': 'wym_tools_undo'}, {'name': 'Redo', 'title': 'Redo', 'css': 'wym_tools_redo'}, {'name': 'CreateLink', 'title': 'Link', 'css': 'wym_tools_link'}, {'name': 'Unlink', 'title': 'Unlink', 'css': 'wym_tools_unlink'}, {'name': 'InsertImage', 'title': 'Image', 'css': 'wym_tools_image'}, {'name': 'InsertTable', 'title': 'Table', 'css': 'wym_tools_table'}, {'name': 'Paste', 'title': 'Paste_From_Word', 'css': 'wym_tools_paste'}, {'name': 'ToggleHtml', 'title': 'HTML', 'css': 'wym_tools_html'}, {'name': 'Preview', 'title': 'Preview', 'css': 'wym_tools_preview'} ], containersHtml: "
    " + "

    {Containers}

    " + "
      " + WYMeditor.CONTAINERS_ITEMS + "
    " + "
    ", containersItemHtml:"
  • " + "" + WYMeditor.CONTAINER_TITLE + "
  • ", containersItems: [ {'name': 'P', 'title': 'Paragraph', 'css': 'wym_containers_p'}, {'name': 'H1', 'title': 'Heading_1', 'css': 'wym_containers_h1'}, {'name': 'H2', 'title': 'Heading_2', 'css': 'wym_containers_h2'}, {'name': 'H3', 'title': 'Heading_3', 'css': 'wym_containers_h3'}, {'name': 'H4', 'title': 'Heading_4', 'css': 'wym_containers_h4'}, {'name': 'H5', 'title': 'Heading_5', 'css': 'wym_containers_h5'}, {'name': 'H6', 'title': 'Heading_6', 'css': 'wym_containers_h6'}, {'name': 'PRE', 'title': 'Preformatted', 'css': 'wym_containers_pre'}, {'name': 'BLOCKQUOTE', 'title': 'Blockquote', 'css': 'wym_containers_blockquote'}, {'name': 'TH', 'title': 'Table_Header', 'css': 'wym_containers_th'} ], classesHtml: "
    " + "

    {Classes}

      " + WYMeditor.CLASSES_ITEMS + "
    ", classesItemHtml: "
  • " + WYMeditor.CLASS_TITLE + "
  • ", classesItems: [], statusHtml: "
    " + "

    {Status}

    " + "
    ", htmlHtml: "
    " + "

    {Source_Code}

    " + "" + "
    ", boxSelector: ".wym_box", toolsSelector: ".wym_tools", toolsListSelector: " ul", containersSelector:".wym_containers", classesSelector: ".wym_classes", htmlSelector: ".wym_html", iframeSelector: ".wym_iframe iframe", iframeBodySelector:".wym_iframe", statusSelector: ".wym_status", toolSelector: ".wym_tools a", containerSelector: ".wym_containers a", classSelector: ".wym_classes a", classUnhiddenSelector: ".wym_classes", classHiddenSelector: ".wym_classes_hidden", htmlValSelector: ".wym_html_val", hrefSelector: ".wym_href", srcSelector: ".wym_src", titleSelector: ".wym_title", targetSelector: ".wym_target", altSelector: ".wym_alt", textSelector: ".wym_text", rowsSelector: ".wym_rows", colsSelector: ".wym_cols", captionSelector: ".wym_caption", summarySelector: ".wym_summary", submitSelector: ".wym_submit", cancelSelector: ".wym_cancel", previewSelector: "", dialogTypeSelector: ".wym_dialog_type", dialogLinkSelector: ".wym_dialog_link", dialogImageSelector: ".wym_dialog_image", dialogTableSelector: ".wym_dialog_table", dialogPasteSelector: ".wym_dialog_paste", dialogPreviewSelector: ".wym_dialog_preview", updateSelector: ".wymupdate", updateEvent: "click", dialogFeatures: "menubar=no,titlebar=no,toolbar=no,resizable=no" + ",width=560,height=300,top=0,left=0", dialogHtml: "" + "" + "" + "" + WYMeditor.DIALOG_TITLE + "" + "" + "" + "" + WYMeditor.DIALOG_BODY + "", dialogLinkHtml: "", dialogImageHtml: "
    " + "
    " + "
    " + "" + "{Image}" + "
    " + "" + "" + "
    " + "
    " + "" + "" + "
    " + "
    " + "" + "" + "
    " + "
    " + "" + "" + "
    " + "
    " + "
    " + "
    ", dialogTableHtml: "
    " + "
    " + "" + "
    " + "" + "" + "
    " + "
    " + "" + "" + "
    " + "
    " + "" + "" + "
    " + "
    " + "" + "" + "
    " + "
    " + "" + "" + "
    " + "
    " + "
    ", dialogPasteHtml: "
    " + "
    " + "" + "
    " + "{Paste_From_Word}" + "
    " + "" + "
    " + "
    " + "" + "" + "
    " + "
    " + "
    " + "
    ", dialogPreviewHtml: "
    ", dialogStyles: [], stringDelimiterLeft: "{", stringDelimiterRight:"}", preInit: null, preBind: null, postInit: null, preInitDialog: null, postInitDialog: null }, options); return this.each(function() { new WYMeditor.editor($(this),options); }); }; /* @name extend * @description Returns the WYMeditor instance based on its index */ $.extend({ wymeditors: function(i) { return (WYMeditor.INSTANCES[i]); } }); /********** WYMeditor **********/ /* @name Wymeditor * @description WYMeditor class */ /* @name init * @description Initializes a WYMeditor instance */ WYMeditor.editor.prototype.init = function() { //load subclass - browser specific //unsupported browsers: do nothing if ($.browser.msie) { var WymClass = new WYMeditor.WymClassExplorer(this); } else if ($.browser.mozilla) { var WymClass = new WYMeditor.WymClassMozilla(this); } else if ($.browser.opera) { var WymClass = new WYMeditor.WymClassOpera(this); } else if ($.browser.safari) { var WymClass = new WYMeditor.WymClassSafari(this); } if(WymClass) { if($.isFunction(this._options.preInit)) this._options.preInit(this); var SaxListener = new WYMeditor.XhtmlSaxListener(); $.extend(SaxListener, WymClass); this.parser = new WYMeditor.XhtmlParser(SaxListener); if(this._options.styles || this._options.stylesheet){ this.configureEditorUsingRawCss(); } this.helper = new WYMeditor.XmlHelper(); //extend the Wymeditor object //don't use $.extend since 1.1.4 //$.extend(this, WymClass); for (var prop in WymClass) { this[prop] = WymClass[prop]; } //load wymbox this._box = $(this._element).hide().after(this._options.boxHtml).next(); //store the instance index in the wymbox element //but keep it compatible with jQuery < 1.2.3, see #122 if( $.isFunction( $.fn.data ) ) $.data(this._box.get(0), WYMeditor.WYM_INDEX, this._index); var h = WYMeditor.Helper; //construct the iframe var iframeHtml = this._options.iframeHtml; iframeHtml = h.replaceAll(iframeHtml, WYMeditor.INDEX, this._index); iframeHtml = h.replaceAll(iframeHtml, WYMeditor.IFRAME_BASE_PATH, this._options.iframeBasePath); //construct wymbox var boxHtml = $(this._box).html(); boxHtml = h.replaceAll(boxHtml, WYMeditor.LOGO, this._options.logoHtml); boxHtml = h.replaceAll(boxHtml, WYMeditor.TOOLS, this._options.toolsHtml); boxHtml = h.replaceAll(boxHtml, WYMeditor.CONTAINERS,this._options.containersHtml); boxHtml = h.replaceAll(boxHtml, WYMeditor.CLASSES, this._options.classesHtml); boxHtml = h.replaceAll(boxHtml, WYMeditor.HTML, this._options.htmlHtml); boxHtml = h.replaceAll(boxHtml, WYMeditor.IFRAME, iframeHtml); boxHtml = h.replaceAll(boxHtml, WYMeditor.STATUS, this._options.statusHtml); //construct tools list var aTools = eval(this._options.toolsItems); var sTools = ""; for(var i = 0; i < aTools.length; i++) { var oTool = aTools[i]; if(oTool.name && oTool.title) var sTool = this._options.toolsItemHtml; var sTool = h.replaceAll(sTool, WYMeditor.TOOL_NAME, oTool.name); sTool = h.replaceAll(sTool, WYMeditor.TOOL_TITLE, this._options.stringDelimiterLeft + oTool.title + this._options.stringDelimiterRight); sTool = h.replaceAll(sTool, WYMeditor.TOOL_CLASS, oTool.css); sTools += sTool; } boxHtml = h.replaceAll(boxHtml, WYMeditor.TOOLS_ITEMS, sTools); //construct classes list var aClasses = eval(this._options.classesItems); var sClasses = ""; for(var i = 0; i < aClasses.length; i++) { var oClass = aClasses[i]; if(oClass.name) { if (oClass.rules && oClass.rules.length > 0) { var sRules = ""; var wym = this; $.each(oClass.rules, function(index, rule) { sClass = wym._options.classesItemHtml; sClass = h.replaceAll(sClass, WYMeditor.CLASS_NAME, oClass.name + (oClass.join || "") + rule); sClass = h.replaceAll(sClass, WYMeditor.CLASS_TITLE, rule.title || titleize(rule)); sRules += sClass; }); var sClassMultiple = this._options.classesItemHtmlMultiple; sClassMultiple = h.replaceAll(sClassMultiple, WYMeditor.CLASS_TITLE, oClass.title || titleize(oClass.name)); sClassMultiple = h.replaceAll(sClassMultiple, '{classesItemHtml}', sRules); sClasses += sClassMultiple; } else { sClass = this._options.classesItemHtml; sClass = h.replaceAll(sClass, WYMeditor.CLASS_NAME, oClass.name); sClass = h.replaceAll(sClass, WYMeditor.CLASS_TITLE, oClass.title || titleize(oClass.name)); sClasses += sClass; } } } boxHtml = h.replaceAll(boxHtml, WYMeditor.CLASSES_ITEMS, sClasses); //construct containers list var aContainers = eval(this._options.containersItems); var sContainers = ""; for(var i = 0; i < aContainers.length; i++) { var oContainer = aContainers[i]; if(oContainer.name && oContainer.title) var sContainer = this._options.containersItemHtml; sContainer = h.replaceAll(sContainer, WYMeditor.CONTAINER_NAME, oContainer.name); sContainer = h.replaceAll(sContainer, WYMeditor.CONTAINER_TITLE, this._options.stringDelimiterLeft + oContainer.title + this._options.stringDelimiterRight); sContainer = h.replaceAll(sContainer, WYMeditor.CONTAINER_CLASS, oContainer.css); sContainers += sContainer; } boxHtml = h.replaceAll(boxHtml, WYMeditor.CONTAINERS_ITEMS, sContainers); //l10n boxHtml = this.replaceStrings(boxHtml); //load html in wymbox $(this._box).html(boxHtml); //hide the html value $(this._box).find(this._options.htmlSelector).hide(); //enable the skin this.loadSkin(); } }; WYMeditor.editor.prototype.bindEvents = function() { //copy the instance var wym = this; //handle click event on tools buttons $(this._box).find(this._options.toolSelector).click(function() { wym.exec($(this).attr(WYMeditor.NAME)); return(false); }); //handle click event on containers buttons $(this._box).find(this._options.containerSelector).click(function() { wym.container($(this).attr(WYMeditor.NAME)); return(false); }); //handle keyup event on html value: set the editor value $(this._box).find(this._options.htmlValSelector).keyup(function() { $(wym._doc.body).html($(this).val()); }); //handle click event on classes buttons $(this._box).find(this._options.classSelector).click(function() { var aClasses = eval(wym._options.classesItems); var sName = $(this).attr(WYMeditor.NAME); var oClass = WYMeditor.Helper.findByName(aClasses, sName); var replacers = $([]); if (oClass == null) { $.each(aClasses, function(index, classRule){ if (oClass == null && classRule.rules && classRule.rules.length > 0){ if ((indexOf = $.inArray(sName.replace(classRule.name + (classRule.join || ""), ""), classRule.rules)) > -1) { $.each(classRule.rules, function(i, rule) { if (i != indexOf) { replacers.push(classRule.name + (classRule.join || "") + rule); } }); oClass = {expr: (classRule.rules[indexOf].expr || null)} } } }); } if(oClass) { // remove all related classes. replacers.each(function(index, removable_class){ wym.removeClass(removable_class, oClass.expr); }); wym.toggleClass(sName, oClass.expr); } // now hide the menu wym.exec(WYMeditor.APPLY_CLASS); return(false); }); //handle event on update element $(this._options.updateSelector).bind(this._options.updateEvent, function() { wym.update(); }); }; WYMeditor.editor.prototype.ready = function() { return(this._doc != null); }; /********** METHODS **********/ /* @name box * @description Returns the WYMeditor container */ WYMeditor.editor.prototype.box = function() { return(this._box); }; /* @name html * @description Get/Set the html value */ WYMeditor.editor.prototype.html = function(html) { if(typeof html === 'string') $(this._doc.body).html(html); else return($(this._doc.body).html()); }; /* @name intercept_paste * @description Catch the browser paste action and open the appropriate dialog instead */ WYMeditor.editor.prototype.intercept_paste = function(e) { var wym = WYMeditor.INSTANCES[this.title]; wym.exec(WYMeditor.PASTE); e.preventDefault(); } /* @name xhtml * @description Cleans up the HTML */ WYMeditor.editor.prototype.xhtml = function() { return this.parser.parse(this.html()); }; /* @name exec * @description Executes a button command */ WYMeditor.editor.prototype.exec = function(cmd) { //base function for execCommand //open a dialog or exec switch(cmd) { case WYMeditor.CREATE_LINK: if((container = this.container()) || this._selected_image) { this.dialog(WYMeditor.DIALOG_LINK); } break; case WYMeditor.INSERT_IMAGE: this.dialog(WYMeditor.DIALOG_IMAGE); break; case WYMeditor.INSERT_TABLE: this.dialog(WYMeditor.DIALOG_TABLE); break; case WYMeditor.PASTE: this.dialog(WYMeditor.DIALOG_PASTE); break; case WYMeditor.TOGGLE_HTML: this.update(); this.toggleHtml(); //partially fixes #121 when the user manually inserts an image if(!$(this._box).find(this._options.htmlSelector).is(':visible')) { this.listen(); } break; case WYMeditor.PREVIEW: this.dialog(WYMeditor.PREVIEW); break; case WYMeditor.APPLY_CLASS: wym = this; $(wym._box).find(this._options.classUnhiddenSelector).toggleClass(this._options.classHiddenSelector.substring(1)); // substring(1) to remove the . at the start $(wym._box).find("a[name=" + WYMeditor.APPLY_CLASS +"]").toggleClass('selected'); // determine whether any classes are already selected and add the enabled class to them. $(wym._box).find(this._options.classUnhiddenSelector).find("a[name]").each(function(index, rule){ if ($(wym.selected()).hasClass($(rule).attr('name'))) { $(rule).parent().addClass('enabled'); } else { $(rule).parent().removeClass('enabled'); } }); break; default: this._exec(cmd); break; } }; /* @name container * @description Get/Set the selected container */ WYMeditor.editor.prototype.container = function(sType) { if(sType) { var container = null; if(sType.toLowerCase() == WYMeditor.TH) { container = this.container(); //find the TD or TH container switch(container.tagName.toLowerCase()) { case WYMeditor.TD: case WYMeditor.TH: break; default: var aTypes = new Array(WYMeditor.TD,WYMeditor.TH); container = this.findUp(this.container(), aTypes); break; } //if it exists, switch if(container!=null) { sType = (container.tagName.toLowerCase() == WYMeditor.TD)? WYMeditor.TH: WYMeditor.TD; this.switchTo(container,sType); this.update(); } } else { //set the container type var aTypes=new Array(WYMeditor.P,WYMeditor.H1,WYMeditor.H2,WYMeditor.H3,WYMeditor.H4,WYMeditor.H5, WYMeditor.H6,WYMeditor.PRE,WYMeditor.BLOCKQUOTE); container = this.findUp(this.container(), aTypes); if(container) { var newNode = null; //blockquotes must contain a block level element if(sType.toLowerCase() == WYMeditor.BLOCKQUOTE) { var blockquote = this.findUp(this.container(), WYMeditor.BLOCKQUOTE); if(blockquote == null) { newNode = this._doc.createElement(sType); container.parentNode.insertBefore(newNode,container); newNode.appendChild(container); this.setFocusToNode(newNode.firstChild); } else { var nodes = blockquote.childNodes; var lgt = nodes.length; var firstNode = null; if(lgt > 0) firstNode = nodes.item(0); for(var x=0; x").join(">\n")}); html = html.replace(/src=\"system\/images/, 'src="/system/images'); // make system/images calls absolute. html = html.replace(/(replace_me_with_wym-[0-9]*)/, ""); // get rid of replace_me_with_wym id tags that were forgotten about. $(this._element).val(html); $(this._box).find(this._options.htmlValSelector).val(html); }; /* @name dialog * @description Opens a dialog box */ WYMeditor.editor.prototype.dialog = function( dialogType ) { var path = this._wym._options.dialogPath + dialogType + this._wym._options.dialogFeatures; this._current_unique_stamp = this.uniqueStamp(); // change undo or redo on cancel to true to have this happen when a user closes (cancels) a dialogue this._undo_on_cancel = false; this._redo_on_cancel = false; var selected = this.selected(); //set to P if parent = BODY if ($.inArray(dialogType, [WYMeditor.DIALOG_TABLE, WYMeditor.DIALOG_PASTE]) == -1) { if (selected != null){ if(selected.tagName.toLowerCase() == WYMeditor.BODY) { this._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P); } } else { // somehow select the wymeditor textarea or simulate a click or a keydown on it. } } var wym = this; if (dialogType == WYMeditor.DIALOG_LINK && $.browser.mozilla) { selection = wym._iframe.contentWindow.getSelection(); matches = $($(selected).html().match(new RegExp(selection.anchorNode.textContent + "(.*)" + selection.focusNode.textContent))); if (matches != null && matches.length > 0 && (possible_anchor_tag = matches.last()).length > 0) { if (((href_matches = possible_anchor_tag.get(0).match(/href="([^"]*)"/)) != null) && (href = $(href_matches).last().get(0)) != null) { $(wym._doc).find('a').each(function(index, possible_match) { if ($(possible_match).html() == selection) { selected = possible_match; } }) } } } // set up handlers. imageGroup = null; wym = this; ajax_loaded_callback = function(){wym.dialog_ajax_callback(selected)} var parent_node = null; if (this._selected_image) { parent_node = this._selected_image.parentNode; } else { parent_node = selected; } if ((parent_node != null) && (dialogType != WYMeditor.DIALOG_PASTE) && (parent_node.tagName.toLowerCase() != WYMeditor.A)) { // wrap the current selection with a funky span (not required for safari) if (!this._selected_image && !$.browser.safari) { this.wrap("", ""); } } else { if (!this._selected_image) { parent_node._id_before_replaceable = parent_node.id; parent_node.id = 'replace_me_with_' + this._current_unique_stamp; } if (dialogType != WYMeditor.DIALOG_PASTE) { path += (this._wym._options.dialogFeatures.length == 0) ? "?" : "&"; port = (window.location.port.length > 0 ? (":" + window.location.port) : "") path += "current_link=" + parent_node.href.replace(window.location.protocol + "//" + window.location.hostname + port, ""); path += "&target_blank=" + (parent_node.target == "_blank" ? "true" : "false"); } } // launch thickbox dialog_title = this.replaceStrings(this.encloseString( dialogType )); switch(dialogType) { case WYMeditor.DIALOG_TABLE: { dialog_container = document.createElement("div"); dialog_container.id = 'inline_dialog_container'; dialog_container.innerHTML = this.replaceStrings(this._options.dialogTableHtml); $(document.body).after(dialog_container); tb_show(dialog_title, "#" + this._options.dialogInlineFeatures + "&inlineId=inline_dialog_container&TB_inline=true&modal=true", imageGroup); ajax_loaded_callback(); break; } case WYMeditor.DIALOG_PASTE: { dialog_container = document.createElement("div"); dialog_container.id = 'inline_dialog_container'; dialog_container.innerHTML = this.replaceStrings(this._options.dialogPasteHtml); $(document.body).after(dialog_container); tb_show(dialog_title, "#" + this._options.dialogInlineFeatures + "&inlineId=inline_dialog_container&TB_inline=true&modal=true", imageGroup); ajax_loaded_callback(); break; } default: { tb_show(dialog_title, path, imageGroup, ajax_loaded_callback); break; } } }; WYMeditor.editor.prototype.dialog_ajax_callback = function(selected) { // look for iframes wym = this; _selected = selected; (iframes = $("#" + this._options.dialogId).find('iframe')).load(function() { WYMeditor.INIT_DIALOG(wym, _selected); $(this).unbind('load'); }); if (iframes.length == 0) { WYMeditor.INIT_DIALOG(this, selected); } }; /* @name toggleHtml * @description Shows/Hides the HTML */ WYMeditor.editor.prototype.toggleHtml = function() { $(this._box).find(this._options.htmlSelector).toggle(); }; WYMeditor.editor.prototype.uniqueStamp = function() { return("wym-" + new Date().getTime()); }; WYMeditor.editor.prototype.paste = function(sData) { wym = this; wym.format_block(); var sTmp; replaceable = $(wym._doc.body).find('#replace_me_with_' + wym._current_unique_stamp); // replaceable doesn't actually get replaced here, it's just used as a marker for where the cursor was. var container = replaceable.get(0) || this.selected(); //split the data, using double newlines as the separator var aP = sData.split(wym._newLine + wym._newLine); var rExp = new RegExp(wym._newLine, "g"); //add a P for each item if(container && container.tagName.toLowerCase() != WYMeditor.BODY) { for(x = aP.length - 1; x >= 0; x--) { sTmp = aP[x]; //simple newlines are replaced by a break sTmp = sTmp.replace(rExp, "
    "); if (x == 0 && $(container).html().replace(//, "").length == 0) { $(container).html(sTmp); } else { $(container).after("

    " + sTmp + "

    "); } } } else { for(x = 0; x < aP.length; x++) { sTmp = aP[x]; //simple newlines are replaced by a break sTmp = sTmp.replace(rExp, "
    "); if (x == 0 && $(container).html().replace(//, "").length == 0) { $(container).html(sTmp); } else { $(wym._doc.body).append("

    " + sTmp + "

    "); } } } if (replaceable.get(0) != null) { // set the id of the container back. replaceable.get(0).id = replaceable.get(0)._id_before_replaceable; } }; WYMeditor.editor.prototype.insert = function(html) { // Do we have a selection? if (this._iframe.contentWindow.getSelection().focusNode != null) { // Overwrite selection with provided html this._exec(WYMeditor.INSERT_HTML, html); } else { // Fall back to the internal paste function if there's no selection this.paste(html); } }; WYMeditor.editor.prototype.wrap = function(left, right, selection) { // Do we have a selection? if (selection == null) { selection = this._iframe.contentWindow.getSelection();} if (selection.focusNode != null) { // Wrap selection with provided html this._exec( WYMeditor.INSERT_HTML, left + selection.toString() + right); } }; WYMeditor.editor.prototype.unwrap = function(selection) { // Do we have a selection? if (selection == null) { selection = this._iframe.contentWindow.getSelection();} if (selection.focusNode != null) { // Unwrap selection this._exec( WYMeditor.INSERT_HTML, selection.toString() ); } }; WYMeditor.editor.prototype.addCssRules = function(doc, aCss) { var styles = doc.styleSheets[0]; if(styles) { for(var i = 0; i < aCss.length; i++) { var oCss = aCss[i]; if(oCss.name && oCss.css) this.addCssRule(styles, oCss); } } }; WYMeditor.editor.prototype.format_block = function(selected) { //'this' should be the wymeditor instance. var wym = this; var container = selected || wym.selected(); wym._selected_image = null; var name = container.tagName.toLowerCase(); //fix forbidden main containers if( name == "strong" || name == "b" || name == "em" || name == "i" || name == "sub" || name == "sup" || name == "a" ) name = container.parentNode.tagName.toLowerCase(); if(name == WYMeditor.BODY) wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P); }; /********** CONFIGURATION **********/ WYMeditor.editor.prototype.computeBasePath = function() { if ((script_path = this.computeWymPath()) != null) { if ((src_parts = script_path.split('/')).length > 1) { src_parts.pop(); } return src_parts.join('/') + "/"; } else { return null; } }; WYMeditor.editor.prototype.computeWymPath = function() { return $('script[src*=jquery.refinery.wymeditor]').attr('src'); }; WYMeditor.editor.prototype.computeJqueryPath = function() { return $($.grep($('script'), function(s){ return (s.src && s.src.match(/jquery(-(.*)){0,1}(\.pack|\.min|\.packed)?\.js(\?.*)?$/ )) })).attr('src'); }; WYMeditor.editor.prototype.computeCssPath = function() { return $($.grep($('link'), function(s){ return (s.href && s.href.match(/wymeditor\/skins\/(.*)screen\.css(\?.*)?$/ )) })).attr('href'); }; WYMeditor.editor.prototype.configureEditorUsingRawCss = function() { var CssParser = new WYMeditor.WymCssParser(); if(this._options.stylesheet){ CssParser.parse($.ajax({url: this._options.stylesheet,async:false}).responseText); }else{ CssParser.parse(this._options.styles, false); } if(this._options.classesItems.length == 0) { this._options.classesItems = CssParser.css_settings.classesItems; } if(this._options.editorStyles.length == 0) { this._options.editorStyles = CssParser.css_settings.editorStyles; } if(this._options.dialogStyles.length == 0) { this._options.dialogStyles = CssParser.css_settings.dialogStyles; } }; /********** EVENTS **********/ WYMeditor.editor.prototype.listen = function() { //don't use $.find() on the iframe body //because of MSIE + jQuery + expando issue (#JQ1143) //$(this._doc.body).find("*").bind("mouseup", this.mouseup); $(this._doc.body).bind("mousedown", this.mousedown); var images = this._doc.body.getElementsByTagName("img"); for(var i=0; i < images.length; i++) { $(images[i]).bind("mousedown", this.mousedown); } }; WYMeditor.editor.prototype.mousedown = function(evt) { var wym = WYMeditor.INSTANCES[this.ownerDocument.title]; wym._selected_image = (this.tagName.toLowerCase() == WYMeditor.IMG) ? this : null; evt.stopPropagation(); }; /********** SKINS **********/ /* * Function: WYMeditor.loadCss * Loads a stylesheet in the document. * * Parameters: * href - The CSS path. */ WYMeditor.loadCss = function(href) { var link = document.createElement('link'); link.rel = 'stylesheet'; link.href = href; var head = $('head').get(0); head.appendChild(link); }; /* * Function: WYMeditor.editor.loadSkin * Loads the skin CSS and initialization script (if needed). */ WYMeditor.editor.prototype.loadSkin = function() { //does the user want to automatically load the CSS (default: yes)? //we also test if it hasn't been already loaded by another instance //see below for a better (second) test if(this._options.loadSkin && !WYMeditor.SKINS[this._options.skin]) { //check if it hasn't been already loaded //so we don't load it more than once //(we check the existing elements) var found = false; var rExp = new RegExp(this._options.skin + '\/' + WYMeditor.SKINS_DEFAULT_CSS + '$'); $('link').each( function() { if(this.href.match(rExp)) found = true; }); //load it, using the skin path if(!found) WYMeditor.loadCss( this._options.cssSkinPath + WYMeditor.SKINS_DEFAULT_CSS ); } //put the classname (ex. wym_skin_default) on wym_box $(this._box).addClass( "wym_skin_" + this._options.skin ); //does the user want to use some JS to initialize the skin (default: yes)? //also check if it hasn't already been loaded by another instance if(this._options.initSkin && !WYMeditor.SKINS[this._options.skin]) { eval($.ajax({url:this._options.jsSkinPath + WYMeditor.SKINS_DEFAULT_JS, async:false}).responseText); } //init the skin, if needed if(WYMeditor.SKINS[this._options.skin] && WYMeditor.SKINS[this._options.skin].init) WYMeditor.SKINS[this._options.skin].init(this); }; /********** DIALOGS **********/ WYMeditor.INIT_DIALOG = function(wym, selected, isIframe) { var selected = selected || wym.selected(); var dialog = $("#"+wym._options.dialogId); var doc = (isIframe ? dialog.find('iframe').get(0).document() : document); var dialogType = dialog.find('#wym_dialog_type').val(); var replaceable = wym._selected_image ? $(wym._selected_image) : $(wym._doc.body).find('#replace_me_with_' + wym._current_unique_stamp); dialog.find(".close_dialog").click(function(e){ wym.close_dialog(e, true); }); //pre-init functions if($.isFunction(wym._options.preInitDialog)) { wym._options.preInitDialog(wym, window); } //auto populate image fields if selected image if(wym._selected_image) { $(wym._options.dialogImageSelector).find(wym._options.srcSelector).val($(wym._selected_image).attr(WYMeditor.SRC)); $(wym._options.dialogImageSelector).find(wym._options.titleSelector).val($(wym._selected_image).attr(WYMeditor.TITLE)); $(wym._options.dialogImageSelector).find(wym._options.altSelector).val($(wym._selected_image).attr(WYMeditor.ALT)); } $(wym._options.dialogLinkSelector).find(wym._options.submitSelector).click(function() { if ((sUrl = $(wym._options.hrefSelector).val()).length > 0) { if (replaceable.get(0) != null) { var link = $('').attr({href:sUrl, title: $(wym._options.titleSelector).val()}) if ((target = $(wym._options.targetSelector).val()) != null && target.length > 0) { link.attr('target', target); } // now grab what was selected in the editor and chuck it inside the link. if (!wym._selected_image) { // ensure some attributes are copied across to the new link. link.attr({'style': replaceable.attr('style'), 'class': replaceable.attr('class')}); replaceable.after(link); link.html(replaceable.html()); replaceable.remove(); } else { if ((parent = replaceable.get(0).parentNode) != null && parent.tagName.toUpperCase() == "A") { $(parent).attr({href: link.attr('href'), title: $(wym._options.titleSelector).val(), target: target}); } else { replaceable.before(link); $(link).append(replaceable.get(0)); } } } else { wym._exec(WYMeditor.CREATE_LINK, wym._current_unique_stamp); $("a[href=" + wym._current_unique_stamp + "]", wym._doc.body) .attr(WYMeditor.HREF, sUrl) .attr(WYMeditor.TITLE, $(wym._options.titleSelector).val()) .attr(WYMeditor.TARGET, $(wym._options.targetSelector).val()); } } // fire a click event on the dialogs close button wym.close_dialog() }); $(wym._options.dialogImageSelector).find(wym._options.submitSelector).click(function() { form = $(this.form); if ((url = form.find(wym._options.srcSelector).val()) != null && url.length > 0) { wym._exec(WYMeditor.INSERT_IMAGE, wym._current_unique_stamp, selected); var image = $(wym._doc.body).find("img[src*=" + wym._current_unique_stamp + "]"); if(image.length > 0) { image.attr(WYMeditor.SRC, url) .attr(WYMeditor.TITLE, form.find(wym._options.titleSelector).val()) .attr(WYMeditor.ALT, form.find(wym._options.srcSelector).val()); if (!$.browser.safari && replaceable != null && (this._selected_image == null || (this._selected_image != null && replaceable.parentNode != null))) { replaceable.after(image).remove(); } } // fire a click event on the dialogs close button wym.close_dialog(); } }); $(wym._options.dialogTableSelector).find(wym._options.submitSelector).click(function() { if((iRows = $(wym._options.rowsSelector).val()) > 0 && (iCols = $(wym._options.colsSelector).val()) > 0) { //create the table and the caption (table = wym._doc.createElement(WYMeditor.TABLE)).createCaption().innerHTML = $(wym._options.captionSelector).val(); //create the rows and cells for(x=0; x 0) { span.parent().html(span.parent().html().replace(new RegExp([""].join("")), span.html())); } (remove_id = $(this._doc.body).find('#replace_me_with_' + this._current_unique_stamp)).attr('id', (remove_id.attr('_id_before_replaceable') || "")); if (this._undo_on_cancel == true) { this._exec("undo"); } else if (this._redo_on_cancel == true) { this._exec("redo"); } } if ($.browser.msie && parseInt($.browser.version) < 8) { this._iframe.contentWindow.focus(); } $('#inline_dialog_container').remove(); tb_remove(); if (e) { e.preventDefault(); } } /********** XHTML LEXER/PARSER **********/ /* * @name xml * @description Use these methods to generate XML and XHTML compliant tags and * escape tag attributes correctly * @author Bermi Ferrer - http://bermi.org * @author David Heinemeier Hansson http://loudthinking.com */ WYMeditor.XmlHelper = function() { this._entitiesDiv = document.createElement('div'); return this; }; /* * @name tag * @description * Returns an empty HTML tag of type *name* which by default is XHTML * compliant. Setting *open* to true will create an open tag compatible * with HTML 4.0 and below. Add HTML attributes by passing an attributes * array to *options*. For attributes with no value like (disabled and * readonly), give it a value of true in the *options* array. * * Examples: * * this.tag('br') * # =>
    * this.tag ('br', false, true) * # =>
    * this.tag ('input', $({type:'text',disabled:true }) ) * # => */ WYMeditor.XmlHelper.prototype.tag = function(name, options, open) { options = options || false; open = open || false; return '<'+name+(options ? this.tagOptions(options) : '')+(open ? '>' : ' />'); }; /* * @name contentTag * @description * Returns a XML block tag of type *name* surrounding the *content*. Add * XML attributes by passing an attributes array to *options*. For attributes * with no value like (disabled and readonly), give it a value of true in * the *options* array. You can use symbols or strings for the attribute names. * * this.contentTag ('p', 'Hello world!' ) * # =>

    Hello world!

    * this.contentTag('div', this.contentTag('p', "Hello world!"), $({class : "strong"})) * # =>

    Hello world!

    * this.contentTag("select", options, $({multiple : true})) * # => */ WYMeditor.XmlHelper.prototype.contentTag = function(name, content, options) { options = options || false; return '<'+name+(options ? this.tagOptions(options) : '')+'>'+content+''; }; /* * @name cdataSection * @description * Returns a CDATA section for the given +content+. CDATA sections * are used to escape blocks of text containing characters which would * otherwise be recognized as markup. CDATA sections begin with the string * <![CDATA[ and } with (and may not contain) the string * ]]>. */ WYMeditor.XmlHelper.prototype.cdataSection = function(content) { return ''; }; /* * @name escapeOnce * @description * Returns the escaped +xml+ without affecting existing escaped entities. * * this.escapeOnce( "1 > 2 & 3") * # => "1 > 2 & 3" */ WYMeditor.XmlHelper.prototype.escapeOnce = function(xml) { return this._fixDoubleEscape(this.escapeEntities(xml)); }; /* * @name _fixDoubleEscape * @description * Fix double-escaped entities, such as &amp;, &#123;, etc. */ WYMeditor.XmlHelper.prototype._fixDoubleEscape = function(escaped) { return escaped.replace(/&([a-z]+|(#\d+));/ig, "&$1;"); }; /* * @name tagOptions * @description * Takes an array like the one generated by Tag.parseAttributes * [["src", "http://www.editam.com/?a=b&c=d&f=g"], ["title", "Editam, CMS"]] * or an object like {src:"http://www.editam.com/?a=b&c=d&f=g", title:"Editam, CMS"} * and returns a string properly escaped like * ' src = "http://www.editam.com/?a=b&c=d&f=g" title = "Editam, <Simplified> CMS"' * which is valid for strict XHTML */ WYMeditor.XmlHelper.prototype.tagOptions = function(options) { var xml = this; xml._formated_options = ''; for (var key in options) { var formated_options = ''; var value = options[key]; if(typeof value != 'function' && value.length > 0) { if(parseInt(key) == key && typeof value == 'object'){ key = value.shift(); value = value.pop(); } if(key != '' && value != ''){ xml._formated_options += ' '+key+'="'+xml.escapeOnce(value)+'"'; } } } return xml._formated_options; }; /* * @name escapeEntities * @description * Escapes XML/HTML entities <, >, & and ". If seccond parameter is set to false it * will not escape ". If set to true it will also escape ' */ WYMeditor.XmlHelper.prototype.escapeEntities = function(string, escape_quotes) { this._entitiesDiv.innerHTML = string; this._entitiesDiv.textContent = string; var result = this._entitiesDiv.innerHTML; if(typeof escape_quotes == 'undefined'){ if(escape_quotes != false) result = result.replace('"', '"'); if(escape_quotes == true) result = result.replace('"', '''); } return result; }; /* * Parses a string conatining tag attributes and values an returns an array formated like * [["src", "http://www.editam.com"], ["title", "Editam, Simplified CMS"]] */ WYMeditor.XmlHelper.prototype.parseAttributes = function(tag_attributes) { // Use a compounded regex to match single quoted, double quoted and unquoted attribute pairs var result = []; var matches = tag_attributes.split(/((=\s*")(")("))|((=\s*\')(\')(\'))|((=\s*[^>\s]*))/g); if(matches.toString() != tag_attributes){ for (var k in matches) { var v = matches[k]; if(typeof v != 'function' && v.length != 0){ var re = new RegExp('(\\w+)\\s*'+v); if(match = tag_attributes.match(re) ){ var value = v.replace(/^[\s=]+/, ""); var delimiter = value.charAt(0); delimiter = delimiter == '"' ? '"' : (delimiter=="'"?"'":''); if(delimiter != ''){ value = delimiter == '"' ? value.replace(/^"|"+$/g, '') : value.replace(/^'|'+$/g, ''); } tag_attributes = tag_attributes.replace(match[0],''); result.push([match[1] , value]); } } } } return result; }; /** * XhtmlValidator for validating tag attributes * * @author Bermi Ferrer - http://bermi.org */ WYMeditor.XhtmlValidator = { "_attributes": { "core": { "except":[ "base", "head", "html", "meta", "param", "script", "style", "title" ], "attributes":[ "class", "id", "style", "title", "accesskey", "tabindex" ] }, "language": { "except":[ "base", "br", "hr", "iframe", "param", "script" ], "attributes": { "dir":[ "ltr", "rtl" ], "0":"lang", "1":"xml:lang" } }, "keyboard": { "attributes": { "accesskey":/^(\w){1}$/, "tabindex":/^(\d)+$/ } } }, "_events": { "window": { "only":[ "body" ], "attributes":[ "onload", "onunload" ] }, "form": { "only":[ "form", "input", "textarea", "select", "a", "label", "button" ], "attributes":[ "onchange", "onsubmit", "onreset", "onselect", "onblur", "onfocus" ] }, "keyboard": { "except":[ "base", "bdo", "br", "frame", "frameset", "head", "html", "iframe", "meta", "param", "script", "style", "title" ], "attributes":[ "onkeydown", "onkeypress", "onkeyup" ] }, "mouse": { "except":[ "base", "bdo", "br", "head", "html", "meta", "param", "script", "style", "title" ], "attributes":[ "onclick", "ondblclick", "onmousedown", "onmousemove", "onmouseover", "onmouseout", "onmouseup" ] } }, "_tags": { "a": { "attributes": { "0":"charset", "1":"coords", "2":"href", "3":"hreflang", "4":"name", "rel":/^(alternate|designates|stylesheet|start|next|prev|contents|index|glossary|copyright|chapter|section|subsection|appendix|help|bookmark| |shortcut|icon|moodalbox)+$/, "rev":/^(alternate|designates|stylesheet|start|next|prev|contents|index|glossary|copyright|chapter|section|subsection|appendix|help|bookmark| |shortcut|icon|moodalbox)+$/, "shape":/^(rect|rectangle|circ|circle|poly|polygon)$/, "5":"type", "target":/^(_blank)+$/ } }, "0":"abbr", "1":"acronym", "2":"address", "area": { "attributes": { "0":"alt", "1":"coords", "2":"href", "nohref":/^(true|false)$/, "shape":/^(rect|rectangle|circ|circle|poly|polygon)$/ }, "required":[ "alt" ] }, "3":"b", "base": { "attributes":[ "href" ], "required":[ "href" ] }, "bdo": { "attributes": { "dir":/^(ltr|rtl)$/ }, "required":[ "dir" ] }, "4":"big", "blockquote": { "attributes":[ "cite" ] }, "5":"body", "6":"br", "button": { "attributes": { "disabled":/^(disabled)$/, "type":/^(button|reset|submit)$/, "0":"value" }, "inside":"form" }, "7":"caption", "8":"cite", "9":"code", "col": { "attributes": { "align":/^(right|left|center|justify)$/, "0":"char", "1":"charoff", "span":/^(\d)+$/, "valign":/^(top|middle|bottom|baseline)$/, "2":"width" }, "inside":"colgroup" }, "colgroup": { "attributes": { "align":/^(right|left|center|justify)$/, "0":"char", "1":"charoff", "span":/^(\d)+$/, "valign":/^(top|middle|bottom|baseline)$/, "2":"width" } }, "10":"dd", "del": { "attributes": { "0":"cite", "datetime":/^([0-9]){8}/ } }, "11":"div", "12":"dfn", "13":"dl", "14":"dt", "15":"em", "fieldset": { "inside":"form" }, "form": { "attributes": { "0":"action", "1":"accept", "2":"accept-charset", "3":"enctype", "method":/^(get|post)$/ }, "required":[ "action" ] }, "head": { "attributes":[ "profile" ] }, "16":"h1", "17":"h2", "18":"h3", "19":"h4", "20":"h5", "21":"h6", "22":"hr", "html": { "attributes":[ "xmlns" ] }, "23":"i", "iframe": { "attributes":[ "src", "width", "height", "frameborder", "scrolling", "marginheight", "marginwidth" ], "required":[ "src" ] }, "img": { "attributes":{ "align":/^(right|left|center|justify)$/, "0":"alt", "1":"src", "2":"height", "3":"ismap", "4":"longdesc", "5":"usemap", "6":"width" }, "required":[ "alt", "src" ] }, "input": { "attributes": { "0":"accept", "1":"alt", "checked":/^(checked)$/, "disabled":/^(disabled)$/, "maxlength":/^(\d)+$/, "2":"name", "readonly":/^(readonly)$/, "size":/^(\d)+$/, "3":"src", "type":/^(button|checkbox|file|hidden|image|password|radio|reset|submit|text)$/, "4":"value" }, "inside":"form" }, "ins": { "attributes": { "0":"cite", "datetime":/^([0-9]){8}/ } }, "24":"kbd", "label": { "attributes":[ "for" ], "inside":"form" }, "25":"legend", "26":"li", "link": { "attributes": { "0":"charset", "1":"href", "2":"hreflang", "media":/^(all|braille|print|projection|screen|speech|,|;| )+$/i, //next comment line required by Opera! /*"rel":/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i,*/ "rel":/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i, "rev":/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i, "3":"type" }, "inside":"head" }, "map": { "attributes":[ "id", "name" ], "required":[ "id" ] }, "meta": { "attributes": { "0":"content", "http-equiv":/^(content\-type|expires|refresh|set\-cookie)$/i, "1":"name", "2":"scheme" }, "required":[ "content" ] }, "27":"noscript", "28":"ol", "optgroup": { "attributes": { "0":"label", "disabled": /^(disabled)$/ }, "required":[ "label" ] }, "option": { "attributes": { "0":"label", "disabled":/^(disabled)$/, "selected":/^(selected)$/, "1":"value" }, "inside":"select" }, "29":"p", "param": { "attributes": [ "type", "value", "name" ], "required":[ "name" ], "inside":"object" }, "embed": { "attributes": [ "width", "height", "allowfullscreen", "allowscriptaccess", "wmode", "type", "src", "flashvars" ], "inside":"object" }, "object": { "attributes":[ "archive", "classid", "codebase", "codetype", "data", "declare", "height", "name", "standby", "type", "usemap", "width" ] }, "30":"pre", "q": { "attributes":[ "cite" ] }, "31":"samp", "script": { "attributes": { "type":/^(text\/ecmascript|text\/javascript|text\/jscript|text\/vbscript|text\/vbs|text\/xml)$/, "0":"charset", "defer":/^(defer)$/, "1":"src" }, "required":[ "type" ] }, "select": { "attributes": { "disabled":/^(disabled)$/, "multiple":/^(multiple)$/, "0":"name", "1":"size" }, "inside":"form" }, "32":"small", "33":"span", "34":"strong", "style": { "attributes": { "0":"type", "media":/^(screen|tty|tv|projection|handheld|print|braille|aural|all)$/ }, "required":[ "type" ] }, "35":"sub", "36":"sup", "table": { "attributes": { "0":"border", "1":"cellpadding", "2":"cellspacing", "frame":/^(void|above|below|hsides|lhs|rhs|vsides|box|border)$/, "rules":/^(none|groups|rows|cols|all)$/, "3":"summary", "4":"width" } }, "tbody": { "attributes": { "align":/^(right|left|center|justify)$/, "0":"char", "1":"charoff", "valign":/^(top|middle|bottom|baseline)$/ } }, "td": { "attributes": { "0":"abbr", "align":/^(left|right|center|justify|char)$/, "1":"axis", "2":"char", "3":"charoff", "colspan":/^(\d)+$/, "4":"headers", "rowspan":/^(\d)+$/, "scope":/^(col|colgroup|row|rowgroup)$/, "valign":/^(top|middle|bottom|baseline)$/ } }, "textarea": { "attributes":[ "cols", "rows", "disabled", "name", "readonly" ], "required":[ "cols", "rows" ], "inside":"form" }, "tfoot": { "attributes": { "align":/^(right|left|center|justify)$/, "0":"char", "1":"charoff", "valign":/^(top|middle|bottom)$/, "2":"baseline" } }, "th": { "attributes": { "0":"abbr", "align":/^(left|right|center|justify|char)$/, "1":"axis", "2":"char", "3":"charoff", "colspan":/^(\d)+$/, "4":"headers", "rowspan":/^(\d)+$/, "scope":/^(col|colgroup|row|rowgroup)$/, "valign":/^(top|middle|bottom|baseline)$/ } }, "thead": { "attributes": { "align":/^(right|left|center|justify)$/, "0":"char", "1":"charoff", "valign":/^(top|middle|bottom|baseline)$/ } }, "37":"title", "tr": { "attributes": { "align":/^(right|left|center|justify|char)$/, "0":"char", "1":"charoff", "valign":/^(top|middle|bottom|baseline)$/ } }, "38":"tt", "39":"ul", "40":"var" }, // Temporary skiped attributes skiped_attributes : [], skiped_attribute_values : [], getValidTagAttributes: function(tag, attributes) { var valid_attributes = {}; var possible_attributes = this.getPossibleTagAttributes(tag); for(var attribute in attributes) { var value = attributes[attribute]; var h = WYMeditor.Helper; if(!h.contains(this.skiped_attributes, attribute) && !h.contains(this.skiped_attribute_values, value)){ if (typeof value != 'function' && h.contains(possible_attributes, attribute)) { if (this.doesAttributeNeedsValidation(tag, attribute)) { if(this.validateAttribute(tag, attribute, value)){ valid_attributes[attribute] = value; } }else{ valid_attributes[attribute] = value; } } } } return valid_attributes; }, getUniqueAttributesAndEventsForTag : function(tag) { var result = []; if (this._tags[tag] && this._tags[tag]['attributes']) { for (k in this._tags[tag]['attributes']) { result.push(parseInt(k) == k ? this._tags[tag]['attributes'][k] : k); } } return result; }, getDefaultAttributesAndEventsForTags : function() { var result = []; for (var key in this._events){ result.push(this._events[key]); } for (var key in this._attributes){ result.push(this._attributes[key]); } return result; }, isValidTag : function(tag) { if(this._tags[tag]){ return true; } for(var key in this._tags){ if(this._tags[key] == tag){ return true; } } return false; }, getDefaultAttributesAndEventsForTag : function(tag) { var default_attributes = []; if (this.isValidTag(tag)) { var default_attributes_and_events = this.getDefaultAttributesAndEventsForTags(); for(var key in default_attributes_and_events) { var defaults = default_attributes_and_events[key]; if(typeof defaults == 'object'){ var h = WYMeditor.Helper; if ((defaults['except'] && h.contains(defaults['except'], tag)) || (defaults['only'] && !h.contains(defaults['only'], tag))) { continue; } var tag_defaults = defaults['attributes'] ? defaults['attributes'] : defaults['events']; for(var k in tag_defaults) { default_attributes.push(typeof tag_defaults[k] != 'string' ? k : tag_defaults[k]); } } } } return default_attributes; }, doesAttributeNeedsValidation: function(tag, attribute) { return this._tags[tag] && ((this._tags[tag]['attributes'] && this._tags[tag]['attributes'][attribute]) || (this._tags[tag]['required'] && WYMeditor.Helper.contains(this._tags[tag]['required'], attribute))); }, validateAttribute : function(tag, attribute, value) { if ( this._tags[tag] && (this._tags[tag]['attributes'] && this._tags[tag]['attributes'][attribute] && value.length > 0 && !value.match(this._tags[tag]['attributes'][attribute])) || // invalid format (this._tags[tag] && this._tags[tag]['required'] && WYMeditor.Helper.contains(this._tags[tag]['required'], attribute) && value.length == 0) // required attribute ) { return false; } return typeof this._tags[tag] != 'undefined'; }, getPossibleTagAttributes : function(tag) { if (!this._possible_tag_attributes) { this._possible_tag_attributes = {}; } if (!this._possible_tag_attributes[tag]) { this._possible_tag_attributes[tag] = this.getUniqueAttributesAndEventsForTag(tag).concat(this.getDefaultAttributesAndEventsForTag(tag)); } return this._possible_tag_attributes[tag]; } }; /** * Compounded regular expression. Any of * the contained patterns could match and * when one does, it's label is returned. * * Constructor. Starts with no patterns. * @param boolean case True for case sensitive, false * for insensitive. * @access public * @author Marcus Baker (http://lastcraft.com) * @author Bermi Ferrer (http://bermi.org) */ WYMeditor.ParallelRegex = function(case_sensitive) { this._case = case_sensitive; this._patterns = []; this._labels = []; this._regex = null; return this; }; /** * Adds a pattern with an optional label. * @param string pattern Perl style regex, but ( and ) * lose the usual meaning. * @param string label Label of regex to be returned * on a match. * @access public */ WYMeditor.ParallelRegex.prototype.addPattern = function(pattern, label) { label = label || true; var count = this._patterns.length; this._patterns[count] = pattern; this._labels[count] = label; this._regex = null; }; /** * Attempts to match all patterns at once against * a string. * @param string subject String to match against. * * @return boolean True on success. * @return string match First matched portion of * subject. * @access public */ WYMeditor.ParallelRegex.prototype.match = function(subject) { if (this._patterns.length == 0) { return [false, '']; } var matches = subject.match(this._getCompoundedRegex()); if(!matches){ return [false, '']; } var match = matches[0]; for (var i = 1; i < matches.length; i++) { if (matches[i]) { return [this._labels[i-1], match]; } } return [true, matches[0]]; }; /** * Compounds the patterns into a single * regular expression separated with the * "or" operator. Caches the regex. * Will automatically escape (, ) and / tokens. * @param array patterns List of patterns in order. * @access private */ WYMeditor.ParallelRegex.prototype._getCompoundedRegex = function() { if (this._regex == null) { for (var i = 0, count = this._patterns.length; i < count; i++) { this._patterns[i] = '(' + this._untokenizeRegex(this._tokenizeRegex(this._patterns[i]).replace(/([\/\(\)])/g,'\\$1')) + ')'; } this._regex = new RegExp(this._patterns.join("|") ,this._getPerlMatchingFlags()); } return this._regex; }; /** * Escape lookahead/lookbehind blocks */ WYMeditor.ParallelRegex.prototype._tokenizeRegex = function(regex) { return regex. replace(/\(\?(i|m|s|x|U)\)/, '~~~~~~Tk1\$1~~~~~~'). replace(/\(\?(\-[i|m|s|x|U])\)/, '~~~~~~Tk2\$1~~~~~~'). replace(/\(\?\=(.*)\)/, '~~~~~~Tk3\$1~~~~~~'). replace(/\(\?\!(.*)\)/, '~~~~~~Tk4\$1~~~~~~'). replace(/\(\?\<\=(.*)\)/, '~~~~~~Tk5\$1~~~~~~'). replace(/\(\?\<\!(.*)\)/, '~~~~~~Tk6\$1~~~~~~'). replace(/\(\?\:(.*)\)/, '~~~~~~Tk7\$1~~~~~~'); }; /** * Unscape lookahead/lookbehind blocks */ WYMeditor.ParallelRegex.prototype._untokenizeRegex = function(regex) { return regex. replace(/~~~~~~Tk1(.{1})~~~~~~/, "(?\$1)"). replace(/~~~~~~Tk2(.{2})~~~~~~/, "(?\$1)"). replace(/~~~~~~Tk3(.*)~~~~~~/, "(?=\$1)"). replace(/~~~~~~Tk4(.*)~~~~~~/, "(?!\$1)"). replace(/~~~~~~Tk5(.*)~~~~~~/, "(?<=\$1)"). replace(/~~~~~~Tk6(.*)~~~~~~/, "(?", 'Comment'); }; WYMeditor.XhtmlLexer.prototype.addScriptTokens = function(scope) { this.addEntryPattern("", 'Script'); }; WYMeditor.XhtmlLexer.prototype.addCssTokens = function(scope) { this.addEntryPattern("", 'Css'); }; WYMeditor.XhtmlLexer.prototype.addTagTokens = function(scope) { this.addSpecialPattern("<\\s*[a-z0-9:\-]+\\s*>", scope, 'OpeningTag'); this.addEntryPattern("<[a-z0-9:\-]+"+'[\\\/ \\\>]+', scope, 'OpeningTag'); this.addInTagDeclarationTokens('OpeningTag'); this.addSpecialPattern("", scope, 'ClosingTag'); }; WYMeditor.XhtmlLexer.prototype.addInTagDeclarationTokens = function(scope) { this.addSpecialPattern('\\s+', scope, 'Ignore'); this.addAttributeTokens(scope); this.addExitPattern('/>', scope); this.addExitPattern('>', scope); }; WYMeditor.XhtmlLexer.prototype.addAttributeTokens = function(scope) { this.addSpecialPattern("\\s*[a-z-_0-9]*:?[a-z-_0-9]+\\s*(?=\=)\\s*", scope, 'TagAttributes'); this.addEntryPattern('=\\s*"', scope, 'DoubleQuotedAttribute'); this.addPattern("\\\\\"", 'DoubleQuotedAttribute'); this.addExitPattern('"', 'DoubleQuotedAttribute'); this.addEntryPattern("=\\s*'", scope, 'SingleQuotedAttribute'); this.addPattern("\\\\'", 'SingleQuotedAttribute'); this.addExitPattern("'", 'SingleQuotedAttribute'); this.addSpecialPattern('=\\s*[^>\\s]*', scope, 'UnquotedAttribute'); }; /** * XHTML Parser. * * This XHTML parser will trigger the events available on on * current SaxListener * * @author Bermi Ferrer (http://bermi.org) */ WYMeditor.XhtmlParser = function(Listener, mode) { var mode = mode || 'Text'; this._Lexer = new WYMeditor.XhtmlLexer(this); this._Listener = Listener; this._mode = mode; this._matches = []; this._last_match = ''; this._current_match = ''; return this; }; WYMeditor.XhtmlParser.prototype.parse = function(raw) { this._Lexer.parse(this.beforeParsing(raw)); return this.afterParsing(this._Listener.getResult()); }; WYMeditor.XhtmlParser.prototype.beforeParsing = function(raw) { if(raw.match(/class="MsoNormal"/) || raw.match(/ns = "urn:schemas-microsoft-com/)){ // Useful for cleaning up content pasted from other sources (MSWord) this._Listener.avoidStylingTagsAndAttributes(); } return this._Listener.beforeParsing(raw); }; WYMeditor.XhtmlParser.prototype.afterParsing = function(parsed) { if(this._Listener._avoiding_tags_implicitly){ this._Listener.allowStylingTagsAndAttributes(); } return this._Listener.afterParsing(parsed); }; WYMeditor.XhtmlParser.prototype.Ignore = function(match, state) { return true; }; WYMeditor.XhtmlParser.prototype.Text = function(text) { this._Listener.addContent(text); return true; }; WYMeditor.XhtmlParser.prototype.Comment = function(match, status) { return this._addNonTagBlock(match, status, 'addComment'); }; WYMeditor.XhtmlParser.prototype.Script = function(match, status) { return this._addNonTagBlock(match, status, 'addScript'); }; WYMeditor.XhtmlParser.prototype.Css = function(match, status) { return this._addNonTagBlock(match, status, 'addCss'); }; WYMeditor.XhtmlParser.prototype._addNonTagBlock = function(match, state, type) { switch (state){ case WYMeditor.LEXER_ENTER: this._non_tag = match; break; case WYMeditor.LEXER_UNMATCHED: this._non_tag += match; break; case WYMeditor.LEXER_EXIT: switch(type) { case 'addComment': this._Listener.addComment(this._non_tag+match); break; case 'addScript': this._Listener.addScript(this._non_tag+match); break; case 'addCss': this._Listener.addCss(this._non_tag+match); break; } } return true; }; WYMeditor.XhtmlParser.prototype.OpeningTag = function(match, state) { switch (state){ case WYMeditor.LEXER_ENTER: this._tag = this.normalizeTag(match); this._tag_attributes = {}; break; case WYMeditor.LEXER_SPECIAL: this._callOpenTagListener(this.normalizeTag(match)); break; case WYMeditor.LEXER_EXIT: this._callOpenTagListener(this._tag, this._tag_attributes); } return true; }; WYMeditor.XhtmlParser.prototype.ClosingTag = function(match, state) { this._callCloseTagListener(this.normalizeTag(match)); return true; }; WYMeditor.XhtmlParser.prototype._callOpenTagListener = function(tag, attributes) { var attributes = attributes || {}; this.autoCloseUnclosedBeforeNewOpening(tag); if(this._Listener.isBlockTag(tag)){ this._Listener._tag_stack.push(tag); this._Listener.fixNestingBeforeOpeningBlockTag(tag, attributes); this._Listener.openBlockTag(tag, attributes); this._increaseOpenTagCounter(tag); }else if(this._Listener.isInlineTag(tag)){ this._Listener.inlineTag(tag, attributes); }else{ this._Listener.openUnknownTag(tag, attributes); this._increaseOpenTagCounter(tag); } this._Listener.last_tag = tag; this._Listener.last_tag_opened = true; this._Listener.last_tag_attributes = attributes; }; WYMeditor.XhtmlParser.prototype._callCloseTagListener = function(tag) { if(this._decreaseOpenTagCounter(tag)){ this.autoCloseUnclosedBeforeTagClosing(tag); if(this._Listener.isBlockTag(tag)){ var expected_tag = this._Listener._tag_stack.pop(); if(expected_tag == false){ return; }else if(expected_tag != tag){ tag = expected_tag; } this._Listener.closeBlockTag(tag); }else{ this._Listener.closeUnknownTag(tag); } }else{ this._Listener.closeUnopenedTag(tag); } this._Listener.last_tag = tag; this._Listener.last_tag_opened = false; }; WYMeditor.XhtmlParser.prototype._increaseOpenTagCounter = function(tag) { this._Listener._open_tags[tag] = this._Listener._open_tags[tag] || 0; this._Listener._open_tags[tag]++; }; WYMeditor.XhtmlParser.prototype._decreaseOpenTagCounter = function(tag) { if(this._Listener._open_tags[tag]){ this._Listener._open_tags[tag]--; if(this._Listener._open_tags[tag] == 0){ this._Listener._open_tags[tag] = undefined; } return true; } return false; }; WYMeditor.XhtmlParser.prototype.autoCloseUnclosedBeforeNewOpening = function(new_tag) { this._autoCloseUnclosed(new_tag, false); }; WYMeditor.XhtmlParser.prototype.autoCloseUnclosedBeforeTagClosing = function(tag) { this._autoCloseUnclosed(tag, true); }; WYMeditor.XhtmlParser.prototype._autoCloseUnclosed = function(new_tag, closing) { var closing = closing || false; if(this._Listener._open_tags){ for (var tag in this._Listener._open_tags) { var counter = this._Listener._open_tags[tag]; if(counter > 0 && this._Listener.shouldCloseTagAutomatically(tag, new_tag, closing)){ this._callCloseTagListener(tag, true); } } } }; WYMeditor.XhtmlParser.prototype.getTagReplacements = function() { return this._Listener.getTagReplacements(); }; WYMeditor.XhtmlParser.prototype.normalizeTag = function(tag) { tag = tag.replace(/^([\s<\/>]*)|([\s<\/>]*)$/gm,'').toLowerCase(); var tags = this._Listener.getTagReplacements(); if(tags[tag]){ return tags[tag]; } return tag; }; WYMeditor.XhtmlParser.prototype.TagAttributes = function(match, state) { if(WYMeditor.LEXER_SPECIAL == state){ this._current_attribute = match; } return true; }; WYMeditor.XhtmlParser.prototype.DoubleQuotedAttribute = function(match, state) { if(WYMeditor.LEXER_UNMATCHED == state){ this._tag_attributes[this._current_attribute] = match; } return true; }; WYMeditor.XhtmlParser.prototype.SingleQuotedAttribute = function(match, state) { if(WYMeditor.LEXER_UNMATCHED == state){ this._tag_attributes[this._current_attribute] = match; } return true; }; WYMeditor.XhtmlParser.prototype.UnquotedAttribute = function(match, state) { this._tag_attributes[this._current_attribute] = match.replace(/^=/,''); return true; }; /** * XHTML Sax parser. * * @author Bermi Ferrer (http://bermi.org) */ WYMeditor.XhtmlSaxListener = function() { this.output = ''; this.helper = new WYMeditor.XmlHelper(); this._open_tags = {}; this.validator = WYMeditor.XhtmlValidator; this._tag_stack = []; this.avoided_tags = []; this.entities = { ' ':' ','¡':'¡','¢':'¢', '£':'£','¤':'¤','¥':'¥', '¦':'¦','§':'§','¨':'¨', '©':'©','ª':'ª','«':'«', '¬':'¬','­':'­','®':'®', '¯':'¯','°':'°','±':'±', '²':'²','³':'³','´':'´', 'µ':'µ','¶':'¶','·':'·', '¸':'¸','¹':'¹','º':'º', '»':'»','¼':'¼','½':'½', '¾':'¾','¿':'¿','À':'À', 'Á':'Á','Â':'Â','Ã':'Ã', 'Ä':'Ä','Å':'Å','Æ':'Æ', 'Ç':'Ç','È':'È','É':'É', 'Ê':'Ê','Ë':'Ë','Ì':'Ì', 'Í':'Í','Î':'Î','Ï':'Ï', 'Ð':'Ð','Ñ':'Ñ','Ò':'Ò', 'Ó':'Ó','Ô':'Ô','Õ':'Õ', 'Ö':'Ö','×':'×','Ø':'Ø', 'Ù':'Ù','Ú':'Ú','Û':'Û', 'Ü':'Ü','Ý':'Ý','Þ':'Þ', 'ß':'ß','à':'à','á':'á', 'â':'â','ã':'ã','ä':'ä', 'å':'å','æ':'æ','ç':'ç', 'è':'è','é':'é','ê':'ê', 'ë':'ë','ì':'ì','í':'í', 'î':'î','ï':'ï','ð':'ð', 'ñ':'ñ','ò':'ò','ó':'ó', 'ô':'ô','õ':'õ','ö':'ö', '÷':'÷','ø':'ø','ù':'ù', 'ú':'ú','û':'û','ü':'ü', 'ý':'ý','þ':'þ','ÿ':'ÿ', 'Œ':'Œ','œ':'œ','Š':'Š', 'š':'š','Ÿ':'Ÿ','ƒ':'ƒ', 'ˆ':'ˆ','˜':'˜','Α':'Α', 'Β':'Β','Γ':'Γ','Δ':'Δ', 'Ε':'Ε','Ζ':'Ζ','Η':'Η', 'Θ':'Θ','Ι':'Ι','Κ':'Κ', 'Λ':'Λ','Μ':'Μ','Ν':'Ν', 'Ξ':'Ξ','Ο':'Ο','Π':'Π', 'Ρ':'Ρ','Σ':'Σ','Τ':'Τ', 'Υ':'Υ','Φ':'Φ','Χ':'Χ', 'Ψ':'Ψ','Ω':'Ω','α':'α', 'β':'β','γ':'γ','δ':'δ', 'ε':'ε','ζ':'ζ','η':'η', 'θ':'θ','ι':'ι','κ':'κ', 'λ':'λ','μ':'μ','ν':'ν', 'ξ':'ξ','ο':'ο','π':'π', 'ρ':'ρ','ς':'ς','σ':'σ', 'τ':'τ','υ':'υ','φ':'φ', 'χ':'χ','ψ':'ψ','ω':'ω', 'ϑ':'ϑ','ϒ':'ϒ','ϖ':'ϖ', ' ':' ',' ':' ',' ':' ', '‌':'‌','‍':'‍','‎':'‎', '‏':'‏','–':'–','—':'—', '‘':'‘','’':'’','‚':'‚', '“':'“','”':'”','„':'„', '†':'†','‡':'‡','•':'•', '…':'…','‰':'‰','′':'′', '″':'″','‹':'‹','›':'›', '‾':'‾','⁄':'⁄','€':'€', 'ℑ':'ℑ','℘':'℘','ℜ':'ℜ', '™':'™','ℵ':'ℵ','←':'←', '↑':'↑','→':'→','↓':'↓', '↔':'↔','↵':'↵','⇐':'⇐', '⇑':'⇑','⇒':'⇒','⇓':'⇓', '⇔':'⇔','∀':'∀','∂':'∂', '∃':'∃','∅':'∅','∇':'∇', '∈':'∈','∉':'∉','∋':'∋', '∏':'∏','∑':'∑','−':'−', '∗':'∗','√':'√','∝':'∝', '∞':'∞','∠':'∠','∧':'∧', '∨':'∨','∩':'∩','∪':'∪', '∫':'∫','∴':'∴','∼':'∼', '≅':'≅','≈':'≈','≠':'≠', '≡':'≡','≤':'≤','≥':'≥', '⊂':'⊂','⊃':'⊃','⊄':'⊄', '⊆':'⊆','⊇':'⊇','⊕':'⊕', '⊗':'⊗','⊥':'⊥','⋅':'⋅', '⌈':'⌈','⌉':'⌉','⌊':'⌊', '⌋':'⌋','⟨':'〈','⟩':'〉', '◊':'◊','♠':'♠','♣':'♣', '♥':'♥','♦':'♦'}; this.block_tags = ["a", "abbr", "acronym", "address", "area", "b", "base", "bdo", "big", "blockquote", "body", "button", "caption", "cite", "code", "col", "colgroup", "dd", "del", "div", "dfn", "dl", "dt", "em", "fieldset", "form", "head", "h1", "h2", "h3", "h4", "h5", "h6", "html", "i", "iframe", "ins", "kbd", "label", "legend", "li", "map", "noscript", "object", "ol", "optgroup", "option", "p", "pre", "q", "samp", "script", "select", "small", "span", "strong", "style", "sub", "sup", "table", "tbody", "td", "textarea", "tfoot", "th", "thead", "title", "tr", "tt", "ul", "var", "extends"]; this.inline_tags = ["br", "embed", "hr", "img", "input", "param"]; return this; }; WYMeditor.XhtmlSaxListener.prototype.shouldCloseTagAutomatically = function(tag, now_on_tag, closing) { var closing = closing || false; if(tag == 'td'){ if((closing && now_on_tag == 'tr') || (!closing && now_on_tag == 'td')){ return true; } } if(tag == 'option'){ if((closing && now_on_tag == 'select') || (!closing && now_on_tag == 'option')){ return true; } } return false; }; WYMeditor.XhtmlSaxListener.prototype.beforeParsing = function(raw) { this.output = ''; return raw; }; WYMeditor.XhtmlSaxListener.prototype.afterParsing = function(xhtml) { xhtml = this.replaceNamedEntities(xhtml); xhtml = this.joinRepeatedEntities(xhtml); xhtml = this.removeEmptyTags(xhtml); return xhtml; }; WYMeditor.XhtmlSaxListener.prototype.replaceNamedEntities = function(xhtml) { for (var entity in this.entities) { xhtml = xhtml.replace(entity, this.entities[entity]); } return xhtml; }; WYMeditor.XhtmlSaxListener.prototype.joinRepeatedEntities = function(xhtml) { var tags = 'em|strong|sub|sup|acronym|pre|del|address'; return xhtml.replace(new RegExp('<\/('+tags+')><\\1>' ,''),''). replace(new RegExp('(\s*<('+tags+')>\s*){2}(.*)(\s*<\/\\2>\s*){2}' ,''),'<\$2>\$3<\$2>'); }; WYMeditor.XhtmlSaxListener.prototype.removeEmptyTags = function(xhtml) { return xhtml.replace(new RegExp('<('+this.block_tags.join("|").replace(/\|td/,'')+')>(
    | | |\\s)*<\/\\1>' ,'g'),''); }; WYMeditor.XhtmlSaxListener.prototype.getResult = function() { return this.output; }; WYMeditor.XhtmlSaxListener.prototype.getTagReplacements = function() { return {'b':'strong', 'i':'em'}; }; WYMeditor.XhtmlSaxListener.prototype.addContent = function(text) { this.output += text; }; WYMeditor.XhtmlSaxListener.prototype.addComment = function(text) { if(this.remove_comments){ this.output += text; } }; WYMeditor.XhtmlSaxListener.prototype.addScript = function(text) { if(!this.remove_scripts){ this.output += text; } }; WYMeditor.XhtmlSaxListener.prototype.addCss = function(text) { if(!this.remove_embeded_styles){ this.output += text; } }; WYMeditor.XhtmlSaxListener.prototype.openBlockTag = function(tag, attributes) { this.output += this.helper.tag(tag, this.validator.getValidTagAttributes(tag, attributes), true); }; WYMeditor.XhtmlSaxListener.prototype.inlineTag = function(tag, attributes) { this.output += this.helper.tag(tag, this.validator.getValidTagAttributes(tag, attributes)); }; WYMeditor.XhtmlSaxListener.prototype.openUnknownTag = function(tag, attributes) { //this.output += this.helper.tag(tag, attributes, true); }; WYMeditor.XhtmlSaxListener.prototype.closeBlockTag = function(tag) { this.output = this.output.replace(/
    $/, '')+this._getClosingTagContent('before', tag)+""+this._getClosingTagContent('after', tag); }; WYMeditor.XhtmlSaxListener.prototype.closeUnknownTag = function(tag) { //this.output += ""; }; WYMeditor.XhtmlSaxListener.prototype.closeUnopenedTag = function(tag) { this.output += ""; }; WYMeditor.XhtmlSaxListener.prototype.avoidStylingTagsAndAttributes = function() { this.avoided_tags = ['div','span']; this.validator.skiped_attributes = ['style']; this.validator.skiped_attribute_values = ['MsoNormal','main1']; // MS Word attributes for class this._avoiding_tags_implicitly = true; }; WYMeditor.XhtmlSaxListener.prototype.allowStylingTagsAndAttributes = function() { this.avoided_tags = []; this.validator.skiped_attributes = []; this.validator.skiped_attribute_values = []; this._avoiding_tags_implicitly = false; }; WYMeditor.XhtmlSaxListener.prototype.isBlockTag = function(tag) { return !WYMeditor.Helper.contains(this.avoided_tags, tag) && WYMeditor.Helper.contains(this.block_tags, tag); }; WYMeditor.XhtmlSaxListener.prototype.isInlineTag = function(tag) { return !WYMeditor.Helper.contains(this.avoided_tags, tag) && WYMeditor.Helper.contains(this.inline_tags, tag); }; WYMeditor.XhtmlSaxListener.prototype.insertContentAfterClosingTag = function(tag, content) { this._insertContentWhenClosingTag('after', tag, content); }; WYMeditor.XhtmlSaxListener.prototype.insertContentBeforeClosingTag = function(tag, content) { this._insertContentWhenClosingTag('before', tag, content); }; WYMeditor.XhtmlSaxListener.prototype.fixNestingBeforeOpeningBlockTag = function(tag, attributes) { if(tag != 'li' && (tag == 'ul' || tag == 'ol') && this.last_tag && !this.last_tag_opened && this.last_tag == 'li'){ this.output = this.output.replace(/<\/li>$/, ''); this.insertContentAfterClosingTag(tag, ''); } }; WYMeditor.XhtmlSaxListener.prototype._insertContentWhenClosingTag = function(position, tag, content) { if(!this['_insert_'+position+'_closing']){ this['_insert_'+position+'_closing'] = []; } if(!this['_insert_'+position+'_closing'][tag]){ this['_insert_'+position+'_closing'][tag] = []; } this['_insert_'+position+'_closing'][tag].push(content); }; WYMeditor.XhtmlSaxListener.prototype._getClosingTagContent = function(position, tag) { if( this['_insert_'+position+'_closing'] && this['_insert_'+position+'_closing'][tag] && this['_insert_'+position+'_closing'][tag].length > 0){ return this['_insert_'+position+'_closing'][tag].pop(); } return ''; }; /********** CSS PARSER **********/ WYMeditor.WymCssLexer = function(parser, only_wym_blocks) { var only_wym_blocks = (typeof only_wym_blocks == 'undefined' ? true : only_wym_blocks); $.extend(this, new WYMeditor.Lexer(parser, (only_wym_blocks?'Ignore':'WymCss'))); this.mapHandler('WymCss', 'Ignore'); if(only_wym_blocks == true){ this.addEntryPattern("/\\\x2a[<\\s]*WYMeditor[>\\s]*\\\x2a/", 'Ignore', 'WymCss'); this.addExitPattern("/\\\x2a[<\/\\s]*WYMeditor[>\\s]*\\\x2a/", 'WymCss'); } this.addSpecialPattern("\\\x2e[a-z-_0-9]+[\\sa-z1-6]*", 'WymCss', 'WymCssStyleDeclaration'); this.addEntryPattern("/\\\x2a", 'WymCss', 'WymCssComment'); this.addExitPattern("\\\x2a/", 'WymCssComment'); this.addEntryPattern("\x7b", 'WymCss', 'WymCssStyle'); this.addExitPattern("\x7d", 'WymCssStyle'); this.addEntryPattern("/\\\x2a", 'WymCssStyle', 'WymCssFeddbackStyle'); this.addExitPattern("\\\x2a/", 'WymCssFeddbackStyle'); return this; }; WYMeditor.WymCssParser = function() { this._in_style = false; this._has_title = false; this.only_wym_blocks = true; this.css_settings = {'classesItems':[], 'editorStyles':[], 'dialogStyles':[]}; return this; }; WYMeditor.WymCssParser.prototype.parse = function(raw, only_wym_blocks) { var only_wym_blocks = (typeof only_wym_blocks == 'undefined' ? this.only_wym_blocks : only_wym_blocks); this._Lexer = new WYMeditor.WymCssLexer(this, only_wym_blocks); this._Lexer.parse(raw); }; WYMeditor.WymCssParser.prototype.Ignore = function(match, state) { return true; }; WYMeditor.WymCssParser.prototype.WymCssComment = function(text, status) { if(text.match(/end[a-z0-9\s]*wym[a-z0-9\s]*/mi)){ return false; } if(status == WYMeditor.LEXER_UNMATCHED){ if(!this._in_style){ this._has_title = true; this._current_item = {'title':WYMeditor.Helper.trim(text)}; }else{ if(this._current_item[this._current_element]){ if(!this._current_item[this._current_element].expressions){ this._current_item[this._current_element].expressions = [text]; }else{ this._current_item[this._current_element].expressions.push(text); } } } this._in_style = true; } return true; }; WYMeditor.WymCssParser.prototype.WymCssStyle = function(match, status) { if(status == WYMeditor.LEXER_UNMATCHED){ match = WYMeditor.Helper.trim(match); if(match != ''){ this._current_item[this._current_element].style = match; } }else if (status == WYMeditor.LEXER_EXIT){ this._in_style = false; this._has_title = false; this.addStyleSetting(this._current_item); } return true; }; WYMeditor.WymCssParser.prototype.WymCssFeddbackStyle = function(match, status) { if(status == WYMeditor.LEXER_UNMATCHED){ this._current_item[this._current_element].feedback_style = match.replace(/^([\s\/\*]*)|([\s\/\*]*)$/gm,''); } return true; }; WYMeditor.WymCssParser.prototype.WymCssStyleDeclaration = function(match) { match = match.replace(/^([\s\.]*)|([\s\.*]*)$/gm, ''); var tag = ''; if(match.indexOf(' ') > 0){ var parts = match.split(' '); this._current_element = parts[0]; var tag = parts[1]; }else{ this._current_element = match; } if(!this._has_title){ this._current_item = {'title':(!tag?'':tag.toUpperCase()+': ')+this._current_element}; this._has_title = true; } if(!this._current_item[this._current_element]){ this._current_item[this._current_element] = {'name':this._current_element}; } if(tag){ if(!this._current_item[this._current_element].tags){ this._current_item[this._current_element].tags = [tag]; }else{ this._current_item[this._current_element].tags.push(tag); } } return true; }; WYMeditor.WymCssParser.prototype.addStyleSetting = function(style_details) { for (var name in style_details){ var details = style_details[name]; if(typeof details == 'object' && name != 'title'){ this.css_settings.classesItems.push({ 'name': WYMeditor.Helper.trim(details.name), 'title': style_details.title, 'expr' : WYMeditor.Helper.trim((details.expressions||details.tags).join(', ')) }); if(details.feedback_style){ this.css_settings.editorStyles.push({ 'name': '.'+ WYMeditor.Helper.trim(details.name), 'css': details.feedback_style }); } if(details.style){ this.css_settings.dialogStyles.push({ 'name': '.'+ WYMeditor.Helper.trim(details.name), 'css': details.style }); } } } }; /********** HELPERS **********/ // Returns true if it is a text node with whitespaces only $.fn.isPhantomNode = function() { if (this[0].nodeType == 3) return !(/[^\t\n\r ]/.test(this[0].data)); return false; }; WYMeditor.isPhantomNode = function(n) { if (n.nodeType == 3) return !(/[^\t\n\r ]/.test(n.data)); return false; }; WYMeditor.isPhantomString = function(str) { return !(/[^\t\n\r ]/.test(str)); }; // Returns the Parents or the node itself // jqexpr = a jQuery expression $.fn.parentsOrSelf = function(jqexpr) { var n = this; if (n[0].nodeType == 3) n = n.parents().slice(0,1); // if (n.is(jqexpr)) // XXX should work, but doesn't (probably a jQuery bug) if (n.filter(jqexpr).size() == 1) return n; else return n.parents(jqexpr).slice(0,1); }; // String & array helpers WYMeditor.Helper = { //replace all instances of 'old' by 'rep' in 'str' string replaceAll: function(str, old, rep) { return(str.replace(new RegExp(old, "g"), rep)); }, //insert 'inserted' at position 'pos' in 'str' string insertAt: function(str, inserted, pos) { return(str.substr(0,pos) + inserted + str.substring(pos)); }, //trim 'str' string trim: function(str) { return str.replace(/^(\s*)|(\s*)$/gm,''); }, //return true if 'arr' array contains 'elem', or false contains: function(arr, elem) { for (var i = 0; i < arr.length; i++) { if (arr[i] === elem) return true; } return false; }, //return 'item' position in 'arr' array, or -1 indexOf: function(arr, item) { var ret=-1; for(var i = 0; i < arr.length; i++) { if (arr[i] == item) { ret = i; break; } } return(ret); }, //return 'item' object in 'arr' array, checking its 'name' property, or null findByName: function(arr, name) { for(var i = 0; i < arr.length; i++) { var item = arr[i]; if(item.name == name) return(item); } return(null); } }; function titleize(words) { parts = []; $.each(words.replace(/\./, '').replace(/[-_]/, ' ').split(' '), function(index, part){ parts.push(part.substring(0,1).toUpperCase() + part.substring(1)); }); return parts.join(" "); } /* * WYMeditor : what you see is What You Mean web-based editor * Copyright (c) 2008 Jean-Francois Hovinne, http://www.wymeditor.org/ * Dual licensed under the MIT (MIT-license.txt) * and GPL (GPL-license.txt) licenses. * * For further information visit: * http://www.wymeditor.org/ * * File Name: * jquery.wymeditor.explorer.js * MSIE specific class and functions. * See the documentation for more info. * * File Authors: * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg) * Bermi Ferrer (wymeditor a-t bermi dotorg) * Frédéric Palluel-Lafleur (fpalluel a-t gmail dotcom) * Jonatan Lundin (jonatan.lundin _at_ gmail.com) */ WYMeditor.WymClassExplorer = function(wym) { this._wym = wym; this._class = "className"; this._newLine = "\r\n"; }; WYMeditor.WymClassExplorer.prototype.initIframe = function(iframe) { //This function is executed twice, though it is called once! //But MSIE needs that, otherwise designMode won't work. //Weird. this._iframe = iframe; this._doc = iframe.contentWindow.document; //add css rules from options var styles = this._doc.styleSheets[0]; var aCss = eval(this._options.editorStyles); this.addCssRules(this._doc, aCss); this._doc.title = this._wym._index; //set the text direction $('html', this._doc).attr('dir', this._options.direction); //init html value $(this._doc.body).html(this._wym._html); //handle events var wym = this; this._doc.body.onfocus = function() {wym._doc.designMode = "on"; wym._doc = iframe.contentWindow.document;}; this._doc.onbeforedeactivate = function() {wym.saveCaret();}; this._doc.onkeyup = function() { wym.saveCaret(); wym.keyup(); }; this._doc.onclick = function() {wym.saveCaret();}; this._doc.body.onbeforepaste = function() { wym._iframe.contentWindow.event.returnValue = false; }; this._doc.body.onpaste = function() { wym._iframe.contentWindow.event.returnValue = false; // Trident doesn't need to intercept the paste as it can access the clipboard easily. wym.paste(window.clipboardData.getData("Text")); }; //callback can't be executed twice, so we check if(this._initialized) { //pre-bind functions if($.isFunction(this._options.preBind)) this._options.preBind(this); //bind external events this._wym.bindEvents(); //post-init functions if($.isFunction(this._options.postInit)) this._options.postInit(this); //add event listeners to doc elements, e.g. images this.listen(); } this._initialized = true; //init designMode this._doc.designMode="on"; try{ // (bermi's note) noticed when running unit tests on IE6 // Is this really needed, it trigger an unexisting property on IE6 this._doc = iframe.contentWindow.document; }catch(e){} }; WYMeditor.WymClassExplorer.prototype._exec = function(cmd,param) { switch(cmd) { case WYMeditor.INDENT: case WYMeditor.OUTDENT: // determine whether we're in a list item. if (this.findUp(this.container(), WYMeditor.LI)) { this._doc.execCommand(cmd); } break; default: if(param) this._doc.execCommand(cmd,false,param); else this._doc.execCommand(cmd); break; } this.listen(); }; WYMeditor.WymClassExplorer.prototype.selected = function() { if((caretPos = this._iframe.contentWindow.document.caretPos)!=null && caretPos.parentElement!=undefined) { return(caretPos.parentElement()); } }; WYMeditor.WymClassExplorer.prototype.saveCaret = function() { this._doc.caretPos = this._doc.selection.createRange(); }; WYMeditor.WymClassExplorer.prototype.addCssRule = function(styles, oCss) { styles.addRule(oCss.name, oCss.css); }; WYMeditor.WymClassExplorer.prototype.insert = function(html) { // Get the current selection var range = this._doc.selection.createRange(); // Check if the current selection is inside the editor if ( $(range.parentElement()).parents( this._options.iframeBodySelector ).is('*') ) { try { // Overwrite selection with provided html range.pasteHTML(html); } catch (e) { } } else { // Fall back to the internal paste function if there's no selection this.paste(html); } }; WYMeditor.WymClassExplorer.prototype.wrap = function(left, right) { // Get the current selection var range = this._doc.selection.createRange(); // Check if the current selection is inside the editor if ( $(range.parentElement()).parents( this._options.iframeBodySelector ).is('*') ) { try { // Overwrite selection with provided html range.pasteHTML(left + range.text + right); } catch (e) { } } }; WYMeditor.WymClassExplorer.prototype.unwrap = function() { // Get the current selection var range = this._doc.selection.createRange(); // Check if the current selection is inside the editor if ( $(range.parentElement()).parents( this._options.iframeBodySelector ).is('*') ) { try { // Unwrap selection var text = range.text; this._exec( 'Cut' ); range.pasteHTML( text ); } catch (e) { } } }; //keyup handler WYMeditor.WymClassExplorer.prototype.keyup = function() { this._selected_image = null; }; WYMeditor.WymClassExplorer.prototype.setFocusToNode = function(node) { var range = this._doc.selection.createRange(); range.moveToElementText(node); range.collapse(false); range.move('character',-1); range.select(); node.focus(); }; /* * WYMeditor : what you see is What You Mean web-based editor * Copyright (c) 2008 Jean-Francois Hovinne, http://www.wymeditor.org/ * Dual licensed under the MIT (MIT-license.txt) * and GPL (GPL-license.txt) licenses. * * For further information visit: * http://www.wymeditor.org/ * * File Name: * jquery.wymeditor.mozilla.js * Gecko specific class and functions. * See the documentation for more info. * * File Authors: * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg) * Volker Mische (vmx a-t gmx dotde) * Bermi Ferrer (wymeditor a-t bermi dotorg) * Frédéric Palluel-Lafleur (fpalluel a-t gmail dotcom) */ WYMeditor.WymClassMozilla = function(wym) { this._wym = wym; this._class = "class"; this._newLine = "\n"; }; WYMeditor.WymClassMozilla.prototype.initIframe = function(iframe) { this._iframe = iframe; this._doc = iframe.contentDocument; //add css rules from options var styles = this._doc.styleSheets[0]; var aCss = eval(this._options.editorStyles); this.addCssRules(this._doc, aCss); this._doc.title = this._wym._index; //set the text direction $('html', this._doc).attr('dir', this._options.direction); //init html value this.html(this._wym._html); //init designMode this.enableDesignMode(); //pre-bind functions if($.isFunction(this._options.preBind)) this._options.preBind(this); //bind external events this._wym.bindEvents(); //bind editor keydown events $(this._doc).bind("keydown", this.keydown); //bind editor keyup events $(this._doc).bind("keyup", this.keyup); //bind editor paste events $(this._doc).bind("paste", this.intercept_paste); //bind editor focus events (used to reset designmode - Gecko bug) $(this._doc).bind("focus", this.enableDesignMode); //post-init functions if($.isFunction(this._options.postInit)) this._options.postInit(this); //add event listeners to doc elements, e.g. images this.listen(); }; /* @name html * @description Get/Set the html value */ WYMeditor.WymClassMozilla.prototype.html = function(html) { if(typeof html === 'string') { //disable designMode try { this._doc.designMode = "off"; } catch(e) { }; //replace em by i and strong by bold //(designMode issue) html = html.replace(/]*)>/gi, "") .replace(/<\/em>/gi, "") .replace(/]*)>/gi, "") .replace(/<\/strong>/gi, ""); //update the html body $(this._doc.body).html(html); //re-init designMode this.enableDesignMode(); } else return($(this._doc.body).html()); }; WYMeditor.WymClassMozilla.prototype._exec = function(cmd,param) { if(!this.selected()) return(false); switch(cmd) { case WYMeditor.INDENT: case WYMeditor.OUTDENT: var focusNode = this.selected(); var sel = this._iframe.contentWindow.getSelection(); var anchorNode = sel.anchorNode; if(anchorNode.nodeName == "#text") anchorNode = anchorNode.parentNode; focusNode = this.findUp(focusNode, WYMeditor.BLOCKS); anchorNode = this.findUp(anchorNode, WYMeditor.BLOCKS); if(focusNode && focusNode == anchorNode && focusNode.tagName.toLowerCase() == WYMeditor.LI) { var ancestor = focusNode.parentNode.parentNode; if(focusNode.parentNode.childNodes.length>1 || ancestor.tagName.toLowerCase() == WYMeditor.OL || ancestor.tagName.toLowerCase() == WYMeditor.UL) this._doc.execCommand(cmd,'',null); } break; default: if(param) this._doc.execCommand(cmd,'',param); else this._doc.execCommand(cmd,'',null); } //set to P if parent = BODY var container = this.selected(); if(container.tagName.toLowerCase() == WYMeditor.BODY) this._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P); //add event handlers on doc elements this.listen(); }; /* @name selected * @description Returns the selected container */ WYMeditor.WymClassMozilla.prototype.selected = function(upgrade_text_nodes) { if (upgrade_text_nodes == null || upgrade_text_nodes.toString() != "true") { upgrade_text_nodes = false; } var sel = this._iframe.contentWindow.getSelection(); var node = sel.focusNode; if(node) { if(node.nodeName == "#text"){ if (upgrade_text_nodes && sel.toString().length > 0) { actual_node = null; parent_node = sel.focusNode.parentNode; if (parent_node != null) { for (i=0;i STRONG wym._exec(WYMeditor.BOLD); return false; } if(evt.keyCode == 73){ //CTRL+i => EMPHASIS wym._exec(WYMeditor.ITALIC); return false; } } }; //keyup handler, mainly used for cleanups WYMeditor.WymClassMozilla.prototype.keyup = function(evt) { //'this' is the doc var wym = WYMeditor.INSTANCES[this.title]; if (wym != null) { wym._selected_image = null; var container = null; if(evt.keyCode == 13 && !evt.shiftKey) { //RETURN key //cleanup

    between paragraphs $(wym._doc.body).children(WYMeditor.BR).remove(); //fix PRE bug #73 container = wym.selected(); if(container && container.tagName.toLowerCase() == WYMeditor.PRE) wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P); //create P after PRE } else if(evt.keyCode != 8 && evt.keyCode != 17 && evt.keyCode != 46 && evt.keyCode != 224 && !evt.metaKey && !evt.ctrlKey) { //NOT BACKSPACE, NOT DELETE, NOT CTRL, NOT COMMAND //text nodes replaced by P wym.format_block(); } } }; WYMeditor.WymClassMozilla.prototype.enableDesignMode = function() { if(this.designMode == "off") { try { this.designMode = "on"; this.execCommand("styleWithCSS", '', false); } catch(e) { } } }; WYMeditor.WymClassMozilla.prototype.setFocusToNode = function(node) { var range = document.createRange(); range.selectNode(node); var selected = this._iframe.contentWindow.getSelection(); selected.addRange(range); selected.collapse(node, node.childNodes.length); this._iframe.contentWindow.focus(); }; WYMeditor.WymClassMozilla.prototype.openBlockTag = function(tag, attributes) { var attributes = this.validator.getValidTagAttributes(tag, attributes); // Handle Mozilla styled spans if(tag == 'span' && attributes.style){ var new_tag = this.getTagForStyle(attributes.style); if(new_tag){ this._tag_stack.pop(); var tag = new_tag; this._tag_stack.push(new_tag); attributes.style = ''; }else{ return; } } this.output += this.helper.tag(tag, attributes, true); }; WYMeditor.WymClassMozilla.prototype.getTagForStyle = function(style) { if(/bold/.test(style)) return 'strong'; if(/italic/.test(style)) return 'em'; if(/sub/.test(style)) return 'sub'; if(/sub/.test(style)) return 'super'; return false; }; /* * WYMeditor : what you see is What You Mean web-based editor * Copyright (c) 2008 Jean-Francois Hovinne, http://www.wymeditor.org/ * Dual licensed under the MIT (MIT-license.txt) * and GPL (GPL-license.txt) licenses. * * For further information visit: * http://www.wymeditor.org/ * * File Name: * jquery.wymeditor.opera.js * Opera specific class and functions. * See the documentation for more info. * * File Authors: * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg) */ WYMeditor.WymClassOpera = function(wym) { this._wym = wym; this._class = "class"; this._newLine = "\r\n"; }; WYMeditor.WymClassOpera.prototype.initIframe = function(iframe) { this._iframe = iframe; this._doc = iframe.contentWindow.document; //add css rules from options var styles = this._doc.styleSheets[0]; var aCss = eval(this._options.editorStyles); this.addCssRules(this._doc, aCss); this._doc.title = this._wym._index; //set the text direction $('html', this._doc).attr('dir', this._options.direction); //init designMode this._doc.designMode = "on"; //init html value this.html(this._wym._html); //pre-bind functions if($.isFunction(this._options.preBind)) this._options.preBind(this); //bind external events this._wym.bindEvents(); //bind editor keydown events $(this._doc).bind("keydown", this.keydown); //bind editor events $(this._doc).bind("keyup", this.keyup); // bind paste events for when this is supported. $(this._doc).bind("paste", this.intercept_paste); //post-init functions if($.isFunction(this._options.postInit)) this._options.postInit(this); //add event listeners to doc elements, e.g. images this.listen(); }; WYMeditor.WymClassOpera.prototype._exec = function(cmd,param) { if(param) this._doc.execCommand(cmd,false,param); else this._doc.execCommand(cmd); this.listen(); }; WYMeditor.WymClassOpera.prototype.selected = function() { var sel=this._iframe.contentWindow.getSelection(); var node=sel.focusNode; if(node) { if(node.nodeName=="#text")return(node.parentNode); else return(node); } else { return(null); } }; WYMeditor.WymClassOpera.prototype.addCssRule = function(styles, oCss) { styles.insertRule(oCss.name + " {" + oCss.css + "}", styles.cssRules.length); }; //keydown handler WYMeditor.WymClassOpera.prototype.keydown = function(evt) { //'this' is the doc var wym = WYMeditor.INSTANCES[this.title]; var sel = wym._iframe.contentWindow.getSelection(); startNode = sel.getRangeAt(0).startContainer; //Get a P instead of no container if(!$(startNode).parentsOrSelf( WYMeditor.MAIN_CONTAINERS.join(","))[0] && !$(startNode).parentsOrSelf('li') && evt.keyCode != WYMeditor.KEY.ENTER && evt.keyCode != WYMeditor.KEY.LEFT && evt.keyCode != WYMeditor.KEY.UP && evt.keyCode != WYMeditor.KEY.RIGHT && evt.keyCode != WYMeditor.KEY.DOWN && evt.keyCode != WYMeditor.KEY.BACKSPACE && evt.keyCode != WYMeditor.KEY.DELETE) wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P); }; //keyup handler WYMeditor.WymClassOpera.prototype.keyup = function(evt) { //'this' is the doc var wym = WYMeditor.INSTANCES[this.title]; wym._selected_image = null; }; // TODO: implement me WYMeditor.WymClassOpera.prototype.setFocusToNode = function(node) { }; /* * WYMeditor : what you see is What You Mean web-based editor * Copyright (c) 2008 Jean-Francois Hovinne, http://www.wymeditor.org/ * Dual licensed under the MIT (MIT-license.txt) * and GPL (GPL-license.txt) licenses. * * For further information visit: * http://www.wymeditor.org/ * * File Name: * jquery.wymeditor.safari.js * Safari specific class and functions. * See the documentation for more info. * * File Authors: * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg) * Scott Lewis (lewiscot a-t gmail dotcom) */ WYMeditor.WymClassSafari = function(wym) { this._wym = wym; this._class = "class"; this._newLine = "\n"; }; WYMeditor.WymClassSafari.prototype.initIframe = function(iframe) { this._iframe = iframe; this._doc = iframe.contentDocument; //add css rules from options var styles = this._doc.styleSheets[0]; var aCss = eval(this._options.editorStyles); this.addCssRules(this._doc, aCss); this._doc.title = this._wym._index; //set the text direction $('html', this._doc).attr('dir', this._options.direction); //init designMode this._doc.designMode = "on"; //init html value this.html(this._wym._html); //pre-bind functions if($.isFunction(this._options.preBind)) this._options.preBind(this); //bind external events this._wym.bindEvents(); //bind editor keydown events $(this._doc).bind("keydown", this.keydown); //bind editor keyup events $(this._doc).bind("keyup", this.keyup); // bind paste events $(this._doc).bind("paste", this.intercept_paste); //post-init functions if($.isFunction(this._options.postInit)) this._options.postInit(this); //add event listeners to doc elements, e.g. images this.listen(); }; WYMeditor.WymClassSafari.prototype._exec = function(cmd,param) { if(!this.selected()) return(false); switch(cmd) { case WYMeditor.INDENT: case WYMeditor.OUTDENT: var focusNode = this.selected(); var sel = this._iframe.contentWindow.getSelection(); var anchorNode = sel.anchorNode; if(anchorNode.nodeName == "#text") anchorNode = anchorNode.parentNode; focusNode = this.findUp(focusNode, WYMeditor.BLOCKS); anchorNode = this.findUp(anchorNode, WYMeditor.BLOCKS); if(focusNode && focusNode == anchorNode && focusNode.tagName.toLowerCase() == WYMeditor.LI) { var ancestor = focusNode.parentNode.parentNode; if(focusNode.parentNode.childNodes.length>1 || ancestor.tagName.toLowerCase() == WYMeditor.OL || ancestor.tagName.toLowerCase() == WYMeditor.UL) this._doc.execCommand(cmd,'',null); } break; case WYMeditor.INSERT_ORDEREDLIST: case WYMeditor.INSERT_UNORDEREDLIST: this._doc.execCommand(cmd,'',null); //Safari creates lists in e.g. paragraphs. //Find the container, and remove it. var focusNode = this.selected(); var container = this.findUp(focusNode, WYMeditor.MAIN_CONTAINERS); if(container) $(container).replaceWith($(container).html()); break; default: if(param) this._doc.execCommand(cmd,'',param); else this._doc.execCommand(cmd,'',null); } //set to P if parent = BODY var container = this.selected(); if(container && container.tagName.toLowerCase() == WYMeditor.BODY) this._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P); //add event handlers on doc elements this.listen(); }; /* @name selected * @description Returns the selected container */ WYMeditor.WymClassSafari.prototype.selected = function(upgrade_text_nodes) { if (upgrade_text_nodes == null || upgrade_text_nodes.toString() != "true") { upgrade_text_nodes = false; } var sel = this._iframe.contentWindow.getSelection(); var node = sel.focusNode; if(node) { if(node.nodeName == "#text"){ if (upgrade_text_nodes && sel.toString().length > 0) { actual_node = null; parent_node = sel.focusNode.parentNode; if (parent_node != null) { for (i=0;i STRONG wym._exec(WYMeditor.BOLD); return false; } if(evt.keyCode == 73){ //CTRL+i => EMPHASIS wym._exec(WYMeditor.ITALIC); return false; } } }; //keyup handler, mainly used for cleanups WYMeditor.WymClassSafari.prototype.keyup = function(evt) { //'this' is the doc var wym = WYMeditor.INSTANCES[this.title]; wym._selected_image = null; var container = null; if(evt.keyCode == 13 && !evt.shiftKey) { //RETURN key //cleanup

    between paragraphs $(wym._doc.body).children(WYMeditor.BR).remove(); //fix PRE bug #73 container = wym.selected(); if(container && container.tagName.toLowerCase() == WYMeditor.PRE) wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P); //create P after PRE } //fix #112 if(evt.keyCode == 13 && evt.shiftKey) { wym._exec('InsertLineBreak'); } if(evt.keyCode != 8 && evt.keyCode != 17 && evt.keyCode != 46 && evt.keyCode != 224 && !evt.metaKey && !evt.ctrlKey) { //NOT BACKSPACE, NOT DELETE, NOT CTRL, NOT COMMAND //text nodes replaced by P container = wym.selected(); var name = container.tagName.toLowerCase(); //fix forbidden main containers if( name == "strong" || name == "b" || name == "em" || name == "i" || name == "sub" || name == "sup" || name == "a" || name == "span" //fix #110 ) name = container.parentNode.tagName.toLowerCase(); if(name == WYMeditor.BODY || name == WYMeditor.DIV) wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P); //fix #110 for DIV } }; WYMeditor.WymClassSafari.prototype.setFocusToNode = function(node) { var range = this._iframe.contentDocument.createRange(); range.selectNode(node); var selected = this._iframe.contentWindow.getSelection(); selected.addRange(range); selected.collapse(node, node.childNodes.length); this._iframe.contentWindow.focus(); }; WYMeditor.WymClassSafari.prototype.openBlockTag = function(tag, attributes) { var attributes = this.validator.getValidTagAttributes(tag, attributes); // Handle Safari styled spans if(tag == 'span' && attributes.style) { var new_tag = this.getTagForStyle(attributes.style); if(new_tag){ this._tag_stack.pop(); var tag = new_tag; this._tag_stack.push(new_tag); attributes.style = ''; //should fix #125 - also removed the xhtml() override if(typeof attributes['class'] == 'string') attributes['class'] = attributes['class'].replace(/apple-style-span/gi, ''); } else { return; } } this.output += this.helper.tag(tag, attributes, true); }; WYMeditor.WymClassSafari.prototype.getTagForStyle = function(style) { if(/bold/.test(style)) return 'strong'; if(/italic/.test(style)) return 'em'; if(/sub/.test(style)) return 'sub'; if(/super/.test(style)) return 'sup'; return false; };