/**
* @version 0.5-rc1
*
* WYMeditor : what you see is What You Mean web-based editor
* Copyright (c) 2005 - 2009 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.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 a-t gmail dotcom)
*/
/*
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;
})();
jQuery.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,REL,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-rc1",
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 : "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",
REL : "rel",
ALT : "alt",
DIALOG_LINK : "Link",
DIALOG_IMAGE : "Image",
DIALOG_TABLE : "Table",
DIALOG_PASTE : "Paste_From_Word",
BOLD : "Bold",
ITALIC : "Italic",
CREATE_LINK : "CreateLink",
INSERT_IMAGE : "InsertImage",
INSERT_TABLE : "InsertTable",
INSERT_HTML : "InsertHTML",
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 = jQuery(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 + '/';
//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 jQuery(".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
*/
jQuery.fn.wymeditor = function(options) {
options = jQuery.extend({
html: "",
basePath: false,
skinPath: 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: "",
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",
htmlValSelector: ".wym_html_val",
hrefSelector: ".wym_href",
srcSelector: ".wym_src",
titleSelector: ".wym_title",
relSelector: ".wym_rel",
altSelector: ".wym_alt",
textSelector: ".wym_text",
rowsSelector: ".wym_rows",
colsSelector: ".wym_cols",
captionSelector: ".wym_caption",
summarySelector: ".wym_summary",
submitSelector: "form",
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",
dialogFeaturesPreview: "menubar=no,titlebar=no,toolbar=no,resizable=no"
+ ",scrollbars=yes,width=560,height=300,top=0,left=0",
dialogHtml: ""
+ ""
+ " "
+ ""
+ WYMeditor.DIALOG_TITLE
+ " "
+ ""
+ ""
+ ""
+ WYMeditor.DIALOG_BODY
+ "",
dialogLinkHtml: ""
+ ""
+ "",
dialogImageHtml: ""
+ ""
+ "",
dialogTableHtml: ""
+ ""
+ "",
dialogPasteHtml: ""
+ ""
+ "",
dialogPreviewHtml: "",
dialogStyles: [],
stringDelimiterLeft: "{",
stringDelimiterRight:"}",
preInit: null,
preBind: null,
postInit: null,
preInitDialog: null,
postInitDialog: null
}, options);
return this.each(function() {
new WYMeditor.editor(jQuery(this),options);
});
};
/* @name extend
* @description Returns the WYMeditor instance based on its index
*/
jQuery.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 (jQuery.browser.msie) {
var WymClass = new WYMeditor.WymClassExplorer(this);
}
else if (jQuery.browser.mozilla) {
var WymClass = new WYMeditor.WymClassMozilla(this);
}
else if (jQuery.browser.opera) {
var WymClass = new WYMeditor.WymClassOpera(this);
}
else if (jQuery.browser.safari) {
var WymClass = new WYMeditor.WymClassSafari(this);
}
if(WymClass) {
if(jQuery.isFunction(this._options.preInit)) this._options.preInit(this);
var SaxListener = new WYMeditor.XhtmlSaxListener();
jQuery.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 jQuery.extend since 1.1.4
//jQuery.extend(this, WymClass);
for (var prop in WymClass) { this[prop] = WymClass[prop]; }
//load wymbox
this._box = jQuery(this._element).hide().after(this._options.boxHtml).next().addClass('wym_box_' + this._index);
//store the instance index in wymbox and element replaced by editor instance
//but keep it compatible with jQuery < 1.2.3, see #122
if( jQuery.isFunction( jQuery.fn.data ) ) {
jQuery.data(this._box.get(0), WYMeditor.WYM_INDEX, this._index);
jQuery.data(this._element.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 = jQuery(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 && oClass.title)
var sClass = this._options.classesItemHtml;
sClass = h.replaceAll(sClass, WYMeditor.CLASS_NAME, oClass.name);
sClass = h.replaceAll(sClass, WYMeditor.CLASS_TITLE, oClass.title);
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
jQuery(this._box).html(boxHtml);
//hide the html value
jQuery(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
jQuery(this._box).find(this._options.toolSelector).click(function() {
wym._iframe.contentWindow.focus(); //See #154
wym.exec(jQuery(this).attr(WYMeditor.NAME));
return(false);
});
//handle click event on containers buttons
jQuery(this._box).find(this._options.containerSelector).click(function() {
wym.container(jQuery(this).attr(WYMeditor.NAME));
return(false);
});
//handle keyup event on html value: set the editor value
//handle focus/blur events to check if the element has focus, see #147
jQuery(this._box).find(this._options.htmlValSelector)
.keyup(function() { jQuery(wym._doc.body).html(jQuery(this).val());})
.focus(function() { jQuery(this).toggleClass('hasfocus'); })
.blur(function() { jQuery(this).toggleClass('hasfocus'); });
//handle click event on classes buttons
jQuery(this._box).find(this._options.classSelector).click(function() {
var aClasses = eval(wym._options.classesItems);
var sName = jQuery(this).attr(WYMeditor.NAME);
var oClass = WYMeditor.Helper.findByName(aClasses, sName);
if(oClass) {
var jqexpr = oClass.expr;
wym.toggleClass(sName, jqexpr);
}
wym._iframe.contentWindow.focus(); //See #154
return(false);
});
//handle event on update element
jQuery(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') jQuery(this._doc.body).html(html);
else return(jQuery(this._doc.body).html());
};
/* @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:
var container = this.container();
if(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();
break;
case WYMeditor.PREVIEW:
this.dialog(WYMeditor.PREVIEW, this._options.dialogFeaturesPreview);
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' +
( paragraphs[i].split(this._newLine).join(' ') ) +
' ';
}
// Insert where appropriate
if (container && container.tagName.toLowerCase() != WYMeditor.BODY) {
// No .last() pre jQuery 1.4
//focusNode = jQuery(html).insertAfter(container).last()[0];
paragraphs = jQuery(html, this._doc).insertAfter(container);
focusNode = paragraphs[paragraphs.length - 1];
} else {
paragraphs = jQuery(html, this._doc).appendTo(this._doc.body);
focusNode = paragraphs[paragraphs.length - 1];
}
// Do some minor cleanup (#131)
if (jQuery(container).text() == '') {
jQuery(container).remove();
}
// And remove br (if editor was empty)
jQuery('body > br', this._doc).remove();
// Restore focus
this.setFocusToNode(focusNode);
};
WYMeditor.editor.prototype.insert = function(html) {
// Do we have a selection?
var selection = this._iframe.contentWindow.getSelection(),
range,
node;
if (selection.focusNode != null) {
// Overwrite selection with provided html
range = selection.getRangeAt(0);
node = range.createContextualFragment(html);
range.deleteContents();
range.insertNode(node);
} else {
// Fall back to the internal paste function if there's no selection
this.paste(html)
}
};
WYMeditor.editor.prototype.wrap = function(left, right) {
this.insert(left + this._iframe.contentWindow.getSelection().toString() + right);
};
WYMeditor.editor.prototype.unwrap = function() {
this.insert(this._iframe.contentWindow.getSelection().toString());
};
WYMeditor.editor.prototype.setFocusToNode = function(node, toStart) {
var range = this._doc.createRange(),
selection = this._iframe.contentWindow.getSelection();
toStart = toStart ? 0 : 1;
range.selectNodeContents(node);
selection.addRange(range);
selection.collapse(node, toStart);
this._iframe.contentWindow.focus();
};
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);
}
}
};
/********** CONFIGURATION **********/
WYMeditor.editor.prototype.computeBasePath = function() {
return jQuery(jQuery.grep(jQuery('script'), function(s){
return (s.src && s.src.match(/jquery\.wymeditor(\.pack|\.min|\.packed)?\.js(\?.*)?$/ ))
})).attr('src').replace(/jquery\.wymeditor(\.pack|\.min|\.packed)?\.js(\?.*)?$/, '');
};
WYMeditor.editor.prototype.computeWymPath = function() {
return jQuery(jQuery.grep(jQuery('script'), function(s){
return (s.src && s.src.match(/jquery\.wymeditor(\.pack|\.min|\.packed)?\.js(\?.*)?$/ ))
})).attr('src');
};
WYMeditor.editor.prototype.computeJqueryPath = function() {
return jQuery(jQuery.grep(jQuery('script'), function(s){
return (s.src && s.src.match(/jquery(-(.*)){0,1}(\.pack|\.min|\.packed)?\.js(\?.*)?$/ ))
})).attr('src');
};
WYMeditor.editor.prototype.computeCssPath = function() {
return jQuery(jQuery.grep(jQuery('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(jQuery.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 jQuery.find() on the iframe body
//because of MSIE + jQuery + expando issue (#JQ1143)
//jQuery(this._doc.body).find("*").bind("mouseup", this.mouseup);
jQuery(this._doc.body).bind("mousedown", this.mousedown);
};
WYMeditor.editor.prototype.mousedown = function(evt) {
var wym = WYMeditor.INSTANCES[this.ownerDocument.title];
wym._selected_image = (evt.target.tagName.toLowerCase() == WYMeditor.IMG) ? evt.target : null;
};
/********** 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 = jQuery('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 + '$');
jQuery('link').each( function() {
if(this.href.match(rExp)) found = true;
});
//load it, using the skin path
if(!found) WYMeditor.loadCss( this._options.skinPath
+ WYMeditor.SKINS_DEFAULT_CSS );
}
//put the classname (ex. wym_skin_default) on wym_box
jQuery(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(jQuery.ajax({url:this._options.skinPath
+ 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(index) {
var wym = window.opener.WYMeditor.INSTANCES[index];
var doc = window.document;
var selected = wym.selected();
var dialogType = jQuery(wym._options.dialogTypeSelector).val();
var sStamp = wym.uniqueStamp();
switch(dialogType) {
case WYMeditor.DIALOG_LINK:
//ensure that we select the link to populate the fields
if(selected && selected.tagName && selected.tagName.toLowerCase != WYMeditor.A)
selected = jQuery(selected).parentsOrSelf(WYMeditor.A);
//fix MSIE selection if link image has been clicked
if(!selected && wym._selected_image)
selected = jQuery(wym._selected_image).parentsOrSelf(WYMeditor.A);
break;
}
//pre-init functions
if(jQuery.isFunction(wym._options.preInitDialog))
wym._options.preInitDialog(wym,window);
//add css rules from options
var styles = doc.styleSheets[0];
var aCss = eval(wym._options.dialogStyles);
wym.addCssRules(doc, aCss);
//auto populate fields if selected container (e.g. A)
if(selected) {
jQuery(wym._options.hrefSelector).val(jQuery(selected).attr(WYMeditor.HREF));
jQuery(wym._options.srcSelector).val(jQuery(selected).attr(WYMeditor.SRC));
jQuery(wym._options.titleSelector).val(jQuery(selected).attr(WYMeditor.TITLE));
jQuery(wym._options.relSelector).val(jQuery(selected).attr(WYMeditor.REL));
jQuery(wym._options.altSelector).val(jQuery(selected).attr(WYMeditor.ALT));
}
//auto populate image fields if selected image
if(wym._selected_image) {
jQuery(wym._options.dialogImageSelector + " " + wym._options.srcSelector)
.val(jQuery(wym._selected_image).attr(WYMeditor.SRC));
jQuery(wym._options.dialogImageSelector + " " + wym._options.titleSelector)
.val(jQuery(wym._selected_image).attr(WYMeditor.TITLE));
jQuery(wym._options.dialogImageSelector + " " + wym._options.altSelector)
.val(jQuery(wym._selected_image).attr(WYMeditor.ALT));
}
jQuery(wym._options.dialogLinkSelector + " "
+ wym._options.submitSelector).submit(function() {
var sUrl = jQuery(wym._options.hrefSelector).val();
if(sUrl.length > 0) {
var link;
if (selected[0] && selected[0].tagName.toLowerCase() == WYMeditor.A) {
link = selected;
} else {
wym._exec(WYMeditor.CREATE_LINK, sStamp);
link = jQuery("a[href=" + sStamp + "]", wym._doc.body);
}
link.attr(WYMeditor.HREF, sUrl)
.attr(WYMeditor.TITLE, jQuery(wym._options.titleSelector).val())
.attr(WYMeditor.REL, jQuery(wym._options.relSelector).val());
}
window.close();
});
jQuery(wym._options.dialogImageSelector + " "
+ wym._options.submitSelector).submit(function() {
var sUrl = jQuery(wym._options.srcSelector).val();
if(sUrl.length > 0) {
wym._exec(WYMeditor.INSERT_IMAGE, sStamp);
jQuery("img[src$=" + sStamp + "]", wym._doc.body)
.attr(WYMeditor.SRC, sUrl)
.attr(WYMeditor.TITLE, jQuery(wym._options.titleSelector).val())
.attr(WYMeditor.ALT, jQuery(wym._options.altSelector).val());
}
window.close();
});
jQuery(wym._options.dialogTableSelector + " "
+ wym._options.submitSelector).submit(function() {
var iRows = jQuery(wym._options.rowsSelector).val();
var iCols = jQuery(wym._options.colsSelector).val();
if(iRows > 0 && iCols > 0) {
var table = wym._doc.createElement(WYMeditor.TABLE);
var newRow = null;
var newCol = null;
var sCaption = jQuery(wym._options.captionSelector).val();
//we create the caption
var newCaption = table.createCaption();
newCaption.innerHTML = sCaption;
//we create the rows and cells
for(x=0; x
* this.tag ('br', false, true)
* # =>
* this.tag ('input', jQuery({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!"), jQuery({class : "strong"}))
* # =>
* this.contentTag("select", options, jQuery({multiple : true}))
* # => ...options...
*/
WYMeditor.XmlHelper.prototype.contentTag = function(name, content, options)
{
options = options || false;
return '<'+name+(options ? this.tagOptions(options) : '')+'>'+content+''+name+'>';
};
/*
* @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 &, {, 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",
"5":"rel",
"6":"rev",
"shape":/^(rect|rectangle|circ|circle|poly|polygon)$/,
"7":"type"
}
},
"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",
"img":
{
"attributes":[
"alt",
"src",
"height",
"ismap",
"longdesc",
"usemap",
"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",
"object":
{
"attributes":[
"archive",
"classid",
"codebase",
"codetype",
"data",
"declare",
"height",
"name",
"standby",
"type",
"usemap",
"width"
]
},
"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":
{
"0":"type",
"valuetype":/^(data|ref|object)$/,
"1":"valuetype",
"2":"value"
},
"required":[
"name"
]
},
"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("