4 * WYMeditor : what you see is What You Mean web-based editor
5 * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/
6 * Dual licensed under the MIT (MIT-license.txt)
7 * and GPL (GPL-license.txt) licenses.
9 * For further information visit:
10 * http://www.wymeditor.org/
12 * File: jquery.wymeditor.js
14 * Main JS file with core classes and functions.
15 * See the documentation for more info.
19 * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg)
20 * Volker Mische (vmx a-t gmx dotde)
21 * Scott Lewis (lewiscot a-t gmail dotcom)
22 * Bermi Ferrer (wymeditor a-t bermi dotorg)
23 * Daniel Reszka (d.reszka a-t wymeditor dotorg)
24 * Jonatan Lundin (jonatan.lundin _at_ gmail.com)
29 Global WYMeditor namespace.
31 if(!WYMeditor) var WYMeditor = {};
33 //Wrap the Firebug console in WYMeditor.console
35 if ( !window.console || !console.firebug ) {
36 var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",
37 "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];
39 WYMeditor.console = {};
40 for (var i = 0; i < names.length; ++i)
41 WYMeditor.console[names[i]] = function() {}
43 } else WYMeditor.console = window.console;
46 jQuery.extend(WYMeditor, {
49 Constants: Global WYMeditor constants.
51 VERSION - Defines WYMeditor version.
52 INSTANCES - An array of loaded WYMeditor.editor instances.
53 STRINGS - An array of loaded WYMeditor language pairs/values.
54 SKINS - An array of loaded WYMeditor skins.
55 NAME - The "name" attribute.
56 INDEX - A string replaced by the instance index.
57 WYM_INDEX - A string used to get/set the instance index.
58 BASE_PATH - A string replaced by WYMeditor's base path.
59 SKIN_PATH - A string replaced by WYMeditor's skin path.
60 WYM_PATH - A string replaced by WYMeditor's main JS file path.
61 SKINS_DEFAULT_PATH - The skins default base path.
62 SKINS_DEFAULT_CSS - The skins default CSS file.
63 LANG_DEFAULT_PATH - The language files default path.
64 IFRAME_BASE_PATH - A string replaced by the designmode iframe's base path.
65 IFRAME_DEFAULT - The iframe's default base path.
66 JQUERY_PATH - A string replaced by the computed jQuery path.
67 DIRECTION - A string replaced by the text direction (rtl or ltr).
68 LOGO - A string replaced by WYMeditor logo.
69 TOOLS - A string replaced by the toolbar's HTML.
70 TOOLS_ITEMS - A string replaced by the toolbar items.
71 TOOL_NAME - A string replaced by a toolbar item's name.
72 TOOL_TITLE - A string replaced by a toolbar item's title.
73 TOOL_CLASS - A string replaced by a toolbar item's class.
74 CLASSES - A string replaced by the classes panel's HTML.
75 CLASSES_ITEMS - A string replaced by the classes items.
76 CLASS_NAME - A string replaced by a class item's name.
77 CLASS_TITLE - A string replaced by a class item's title.
78 CONTAINERS - A string replaced by the containers panel's HTML.
79 CONTAINERS_ITEMS - A string replaced by the containers items.
80 CONTAINER_NAME - A string replaced by a container item's name.
81 CONTAINER_TITLE - A string replaced by a container item's title.
82 CONTAINER_CLASS - A string replaced by a container item's class.
83 HTML - A string replaced by the HTML view panel's HTML.
84 IFRAME - A string replaced by the designmode iframe.
85 STATUS - A string replaced by the status panel's HTML.
86 DIALOG_TITLE - A string replaced by a dialog's title.
87 DIALOG_BODY - A string replaced by a dialog's HTML body.
88 BODY - The BODY element.
89 STRING - The "string" type.
95 UL,OL,LI - HTML elements string representation.
97 TITLE,ALT - HTML attributes string representation.
98 DIALOG_LINK - A link dialog type.
99 DIALOG_IMAGE - An image dialog type.
100 DIALOG_TABLE - A table dialog type.
101 DIALOG_PASTE - A 'Paste from Word' dialog type.
102 BOLD - Command: (un)set selection to <strong>.
103 ITALIC - Command: (un)set selection to <em>.
104 CREATE_LINK - Command: open the link dialog or (un)set link.
105 INSERT_IMAGE - Command: open the image dialog or insert an image.
106 INSERT_TABLE - Command: open the table dialog.
107 PASTE - Command: open the paste dialog.
108 INDENT - Command: nest a list item.
109 OUTDENT - Command: unnest a list item.
110 TOGGLE_HTML - Command: display/hide the HTML view.
111 FORMAT_BLOCK - Command: set a block element to another type.
112 PREVIEW - Command: open the preview dialog.
113 UNLINK - Command: unset a link.
114 INSERT_UNORDEREDLIST- Command: insert an unordered list.
115 INSERT_ORDEREDLIST - Command: insert an ordered list.
116 MAIN_CONTAINERS - An array of the main HTML containers used in WYMeditor.
117 BLOCKS - An array of the HTML block elements.
118 KEY - Standard key codes.
128 INDEX : "{Wym_Index}",
129 WYM_INDEX : "wym_index",
130 BASE_PATH : "{Wym_Base_Path}",
131 CSS_PATH : "{Wym_Css_Path}",
132 WYM_PATH : "{Wym_Wym_Path}",
133 SKINS_DEFAULT_PATH : "skins/",
134 SKINS_DEFAULT_CSS : "skin.css",
135 SKINS_DEFAULT_JS : "skin.js",
136 LANG_DEFAULT_PATH : "lang/",
137 IFRAME_BASE_PATH : "{Wym_Iframe_Base_Path}",
138 IFRAME_DEFAULT : "iframe/default/",
139 JQUERY_PATH : "{Wym_Jquery_Path}",
140 DIRECTION : "{Wym_Direction}",
142 TOOLS : "{Wym_Tools}",
143 TOOLS_ITEMS : "{Wym_Tools_Items}",
144 TOOL_NAME : "{Wym_Tool_Name}",
145 TOOL_TITLE : "{Wym_Tool_Title}",
146 TOOL_CLASS : "{Wym_Tool_Class}",
147 CLASSES : "{Wym_Classes}",
148 CLASSES_ITEMS : "{Wym_Classes_Items}",
149 CLASS_NAME : "{Wym_Class_Name}",
150 CLASS_TITLE : "{Wym_Class_Title}",
151 CONTAINERS : "{Wym_Containers}",
152 CONTAINERS_ITEMS : "{Wym_Containers_Items}",
153 CONTAINER_NAME : "{Wym_Container_Name}",
154 CONTAINER_TITLE : "{Wym_Containers_Title}",
155 CONTAINER_CLASS : "{Wym_Container_Class}",
157 IFRAME : "{Wym_Iframe}",
158 STATUS : "{Wym_Status}",
159 DIALOG_TITLE : "{Wym_Dialog_Title}",
160 DIALOG_BODY : "{Wym_Dialog_Body}",
172 BLOCKQUOTE : "blockquote",
187 DIALOG_LINK : "Link",
188 DIALOG_IMAGE : "Image",
189 DIALOG_TABLE : "Table",
190 DIALOG_PASTE : "Paste_From_Word",
193 CREATE_LINK : "CreateLink",
194 INSERT_IMAGE : "InsertImage",
195 INSERT_TABLE : "InsertTable",
196 INSERT_HTML : "InsertHTML",
200 TOGGLE_HTML : "ToggleHtml",
201 FORMAT_BLOCK : "FormatBlock",
204 INSERT_UNORDEREDLIST: "InsertUnorderedList",
205 INSERT_ORDEREDLIST : "InsertOrderedList",
207 MAIN_CONTAINERS : new Array("p","h1","h2","h3","h4","h5","h6","pre","blockquote"),
209 BLOCKS : new Array("address", "blockquote", "div", "dl",
210 "fieldset", "form", "h1", "h2", "h3", "h4", "h5", "h6", "hr",
211 "noscript", "ol", "p", "pre", "table", "ul", "dd", "dt",
212 "li", "tbody", "td", "tfoot", "th", "thead", "tr"),
223 CURSOR: new Array(37, 38, 39, 40),
234 Class: WYMeditor.editor
235 WYMeditor editor main class, instanciated for each editor occurrence.
238 editor : function(elem, options) {
241 Constructor: WYMeditor.editor
243 Initializes main values (index, elements, paths, ...)
244 and call WYMeditor.editor.init which initializes the editor.
248 elem - The HTML element to be replaced by the editor.
249 options - The hash of options.
257 <WYMeditor.editor.init>
260 //store the instance in the INSTANCES array and store the index
261 this._index = WYMeditor.INSTANCES.push(this) - 1;
262 //store the element replaced by the editor
263 this._element = elem;
265 this._options = options;
266 //store the element's inner value
267 this._html = jQuery(elem).val();
269 //store the HTML option, if any
270 if(this._options.html) this._html = this._options.html;
271 //get or compute the base path (where the main JS file is located)
272 this._options.basePath = this._options.basePath
273 || this.computeBasePath();
274 //get or set the skin path (where the skin files are located)
275 this._options.skinPath = this._options.skinPath
276 || this._options.basePath + WYMeditor.SKINS_DEFAULT_PATH
277 + this._options.skin + '/';
278 //get or compute the main JS file location
279 this._options.wymPath = this._options.wymPath
280 || this.computeWymPath();
281 //get or set the language files path
282 this._options.langPath = this._options.langPath
283 || this._options.basePath + WYMeditor.LANG_DEFAULT_PATH;
284 //get or set the designmode iframe's base path
285 this._options.iframeBasePath = this._options.iframeBasePath
286 || this._options.basePath + WYMeditor.IFRAME_DEFAULT;
287 //get or compute the jQuery JS file location
288 this._options.jQueryPath = this._options.jQueryPath
289 || this.computeJqueryPath();
291 //initialize the editor instance
299 /********** JQUERY **********/
302 * Replace an HTML element by WYMeditor
304 * @example jQuery(".wymeditor").wymeditor(
309 * @desc Example description here
312 * @description WYMeditor is a web-based WYSIWYM XHTML editor
313 * @param Hash hash A hash of parameters
314 * @option Integer iExample Description here
315 * @option String sExample Description here
318 * @cat Plugins/WYMeditor
319 * @author Jean-Francois Hovinne
321 jQuery.fn.wymeditor = function(options) {
323 options = jQuery.extend({
333 iframeBasePath: false,
349 boxHtml: "<div class='wym_box'>"
350 + "<div class='wym_area_top'>"
353 + "<div class='wym_area_left'></div>"
354 + "<div class='wym_area_right'>"
355 + WYMeditor.CONTAINERS
358 + "<div class='wym_area_main'>"
363 + "<div class='wym_area_bottom'>"
368 logoHtml: "<a class='wym_wymeditor_link' "
369 + "href='http://www.wymeditor.org/'>WYMeditor</a>",
371 iframeHtml:"<div class='wym_iframe wym_section'>"
374 + WYMeditor.IFRAME_BASE_PATH
376 + "onload='this.contentWindow.parent.WYMeditor.INSTANCES["
377 + WYMeditor.INDEX + "].initIframe(this)'"
383 toolsHtml: "<div class='wym_tools wym_section'>"
386 + WYMeditor.TOOLS_ITEMS
390 toolsItemHtml: "<li class='"
391 + WYMeditor.TOOL_CLASS
392 + "'><a href='#' name='"
393 + WYMeditor.TOOL_NAME
395 + WYMeditor.TOOL_TITLE
397 + WYMeditor.TOOL_TITLE
401 {'name': 'Bold', 'title': 'Strong', 'css': 'wym_tools_strong'},
402 {'name': 'Italic', 'title': 'Emphasis', 'css': 'wym_tools_emphasis'},
403 {'name': 'Superscript', 'title': 'Superscript',
404 'css': 'wym_tools_superscript'},
405 {'name': 'Subscript', 'title': 'Subscript',
406 'css': 'wym_tools_subscript'},
407 {'name': 'InsertOrderedList', 'title': 'Ordered_List',
408 'css': 'wym_tools_ordered_list'},
409 {'name': 'InsertUnorderedList', 'title': 'Unordered_List',
410 'css': 'wym_tools_unordered_list'},
411 {'name': 'Indent', 'title': 'Indent', 'css': 'wym_tools_indent'},
412 {'name': 'Outdent', 'title': 'Outdent', 'css': 'wym_tools_outdent'},
413 {'name': 'Undo', 'title': 'Undo', 'css': 'wym_tools_undo'},
414 {'name': 'Redo', 'title': 'Redo', 'css': 'wym_tools_redo'},
415 {'name': 'CreateLink', 'title': 'Link', 'css': 'wym_tools_link'},
416 {'name': 'Unlink', 'title': 'Unlink', 'css': 'wym_tools_unlink'},
417 {'name': 'InsertImage', 'title': 'Image', 'css': 'wym_tools_image'},
418 {'name': 'InsertTable', 'title': 'Table', 'css': 'wym_tools_table'},
419 {'name': 'Paste', 'title': 'Paste_From_Word',
420 'css': 'wym_tools_paste'},
421 {'name': 'ToggleHtml', 'title': 'HTML', 'css': 'wym_tools_html'},
422 {'name': 'Preview', 'title': 'Preview', 'css': 'wym_tools_preview'}
425 containersHtml: "<div class='wym_containers wym_section'>"
426 + "<h2>{Containers}</h2>"
428 + WYMeditor.CONTAINERS_ITEMS
432 containersItemHtml:"<li class='"
433 + WYMeditor.CONTAINER_CLASS
435 + "<a href='#' name='"
436 + WYMeditor.CONTAINER_NAME
438 + WYMeditor.CONTAINER_TITLE
442 {'name': 'P', 'title': 'Paragraph', 'css': 'wym_containers_p'},
443 {'name': 'H1', 'title': 'Heading_1', 'css': 'wym_containers_h1'},
444 {'name': 'H2', 'title': 'Heading_2', 'css': 'wym_containers_h2'},
445 {'name': 'H3', 'title': 'Heading_3', 'css': 'wym_containers_h3'},
446 {'name': 'H4', 'title': 'Heading_4', 'css': 'wym_containers_h4'},
447 {'name': 'H5', 'title': 'Heading_5', 'css': 'wym_containers_h5'},
448 {'name': 'H6', 'title': 'Heading_6', 'css': 'wym_containers_h6'},
449 {'name': 'PRE', 'title': 'Preformatted', 'css': 'wym_containers_pre'},
450 {'name': 'BLOCKQUOTE', 'title': 'Blockquote',
451 'css': 'wym_containers_blockquote'},
452 {'name': 'TH', 'title': 'Table_Header', 'css': 'wym_containers_th'}
455 classesHtml: "<div class='wym_classes wym_section'>"
456 + "<h2>{Classes}</h2><ul>"
457 + WYMeditor.CLASSES_ITEMS
460 classesItemHtml: "<li><a href='#' name='"
461 + WYMeditor.CLASS_NAME
463 + WYMeditor.CLASS_TITLE
468 statusHtml: "<div class='wym_status wym_section'>"
469 + "<h2>{Status}</h2>"
472 htmlHtml: "<div class='wym_html wym_section'>"
473 + "<h2>{Source_Code}</h2>"
474 + "<textarea class='wym_html_val'></textarea>"
477 boxSelector: ".wym_box",
478 toolsSelector: ".wym_tools",
479 toolsListSelector: " ul",
480 containersSelector:".wym_containers",
481 classesSelector: ".wym_classes",
482 htmlSelector: ".wym_html",
483 iframeSelector: ".wym_iframe iframe",
484 iframeBodySelector:".wym_iframe",
485 statusSelector: ".wym_status",
486 toolSelector: ".wym_tools a",
487 containerSelector: ".wym_containers a",
488 classSelector: ".wym_classes a",
489 htmlValSelector: ".wym_html_val",
491 hrefSelector: ".wym_href",
492 srcSelector: ".wym_src",
493 titleSelector: ".wym_title",
494 altSelector: ".wym_alt",
495 textSelector: ".wym_text",
497 rowsSelector: ".wym_rows",
498 colsSelector: ".wym_cols",
499 captionSelector: ".wym_caption",
500 summarySelector: ".wym_summary",
502 submitSelector: ".wym_submit",
503 cancelSelector: ".wym_cancel",
506 dialogTypeSelector: ".wym_dialog_type",
507 dialogLinkSelector: ".wym_dialog_link",
508 dialogImageSelector: ".wym_dialog_image",
509 dialogTableSelector: ".wym_dialog_table",
510 dialogPasteSelector: ".wym_dialog_paste",
511 dialogPreviewSelector: ".wym_dialog_preview",
513 updateSelector: ".wymupdate",
514 updateEvent: "click",
516 dialogFeatures: "menubar=no,titlebar=no,toolbar=no,resizable=no"
517 + ",width=560,height=300,top=0,left=0",
518 dialogFeaturesPreview: "menubar=no,titlebar=no,toolbar=no,resizable=no"
519 + ",scrollbars=yes,width=560,height=300,top=0,left=0",
521 dialogHtml: "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN'"
522 + " 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>"
524 + WYMeditor.DIRECTION
526 + "<link rel='stylesheet' type='text/css' media='screen'"
531 + WYMeditor.DIALOG_TITLE
533 + "<script type='text/javascript'"
535 + WYMeditor.JQUERY_PATH
537 + "<script type='text/javascript'"
542 + WYMeditor.DIALOG_BODY
545 dialogLinkHtml: "<body class='wym_dialog wym_dialog_link'"
546 + " onload='WYMeditor.INIT_DIALOG(" + WYMeditor.INDEX + ")'"
550 + "<input type='hidden' class='wym_dialog_type' value='"
551 + WYMeditor.DIALOG_LINK
553 + "<legend>{Link}</legend>"
554 + "<div class='row'>"
555 + "<label>{URL}</label>"
556 + "<input type='text' class='wym_href' value='' size='40' />"
558 + "<div class='row'>"
559 + "<label>{Title}</label>"
560 + "<input type='text' class='wym_title' value='' size='40' />"
562 + "<div class='row row-indent'>"
563 + "<input class='wym_submit' type='button'"
564 + " value='{Submit}' />"
565 + "<input class='wym_cancel' type='button'"
566 + "value='{Cancel}' />"
572 dialogImageHtml: "<body class='wym_dialog wym_dialog_image'"
573 + " onload='WYMeditor.INIT_DIALOG(" + WYMeditor.INDEX + ")'"
577 + "<input type='hidden' class='wym_dialog_type' value='"
578 + WYMeditor.DIALOG_IMAGE
580 + "<legend>{Image}</legend>"
581 + "<div class='row'>"
582 + "<label>{URL}</label>"
583 + "<input type='text' class='wym_src' value='' size='40' />"
585 + "<div class='row'>"
586 + "<label>{Alternative_Text}</label>"
587 + "<input type='text' class='wym_alt' value='' size='40' />"
589 + "<div class='row'>"
590 + "<label>{Title}</label>"
591 + "<input type='text' class='wym_title' value='' size='40' />"
593 + "<div class='row row-indent'>"
594 + "<input class='wym_submit' type='button'"
595 + " value='{Submit}' />"
596 + "<input class='wym_cancel' type='button'"
597 + "value='{Cancel}' />"
603 dialogTableHtml: "<body class='wym_dialog wym_dialog_table'"
604 + " onload='WYMeditor.INIT_DIALOG(" + WYMeditor.INDEX + ")'"
608 + "<input type='hidden' class='wym_dialog_type' value='"
609 + WYMeditor.DIALOG_TABLE
611 + "<legend>{Table}</legend>"
612 + "<div class='row'>"
613 + "<label>{Caption}</label>"
614 + "<input type='text' class='wym_caption' value='' size='40' />"
616 + "<div class='row'>"
617 + "<label>{Summary}</label>"
618 + "<input type='text' class='wym_summary' value='' size='40' />"
620 + "<div class='row'>"
621 + "<label>{Number_Of_Rows}</label>"
622 + "<input type='text' class='wym_rows' value='3' size='3' />"
624 + "<div class='row'>"
625 + "<label>{Number_Of_Cols}</label>"
626 + "<input type='text' class='wym_cols' value='2' size='3' />"
628 + "<div class='row row-indent'>"
629 + "<input class='wym_submit' type='button'"
630 + " value='{Submit}' />"
631 + "<input class='wym_cancel' type='button'"
632 + "value='{Cancel}' />"
638 dialogPasteHtml: "<body class='wym_dialog wym_dialog_paste'"
639 + " onload='WYMeditor.INIT_DIALOG(" + WYMeditor.INDEX + ")'"
642 + "<input type='hidden' class='wym_dialog_type' value='"
643 + WYMeditor.DIALOG_PASTE
646 + "<legend>{Paste_From_Word}</legend>"
647 + "<div class='row'>"
648 + "<textarea class='wym_text' rows='10' cols='50'></textarea>"
650 + "<div class='row'>"
651 + "<input class='wym_submit' type='button'"
652 + " value='{Submit}' />"
653 + "<input class='wym_cancel' type='button'"
654 + "value='{Cancel}' />"
660 dialogPreviewHtml: "<body class='wym_dialog wym_dialog_preview'"
661 + " onload='WYMeditor.INIT_DIALOG(" + WYMeditor.INDEX + ")'"
666 stringDelimiterLeft: "{",
667 stringDelimiterRight:"}",
678 return this.each(function() {
680 new WYMeditor.editor(jQuery(this),options);
685 * @description Returns the WYMeditor instance based on its index
688 wymeditors: function(i) {
689 return (WYMeditor.INSTANCES[i]);
694 /********** WYMeditor **********/
697 * @description WYMeditor class
701 * @description Initializes a WYMeditor instance
703 WYMeditor.editor.prototype.init = function() {
705 //load subclass - browser specific
706 //unsupported browsers: do nothing
707 if (jQuery.browser.msie) {
708 var WymClass = new WYMeditor.WymClassExplorer(this);
710 else if (jQuery.browser.mozilla) {
711 var WymClass = new WYMeditor.WymClassMozilla(this);
713 else if (jQuery.browser.opera) {
714 var WymClass = new WYMeditor.WymClassOpera(this);
716 else if (jQuery.browser.safari) {
717 var WymClass = new WYMeditor.WymClassSafari(this);
722 if(jQuery.isFunction(this._options.preInit)) this._options.preInit(this);
724 var SaxListener = new WYMeditor.XhtmlSaxListener();
725 jQuery.extend(SaxListener, WymClass);
726 this.parser = new WYMeditor.XhtmlParser(SaxListener);
728 if(this._options.styles || this._options.stylesheet){
729 this.configureEditorUsingRawCss();
732 this.helper = new WYMeditor.XmlHelper();
734 //extend the Wymeditor object
735 //don't use jQuery.extend since 1.1.4
736 //jQuery.extend(this, WymClass);
737 for (var prop in WymClass) { this[prop] = WymClass[prop]; }
740 this._box = jQuery(this._element).hide().after(this._options.boxHtml).next().addClass('wym_box_' + this._index);
742 //store the instance index in wymbox and element replaced by editor instance
743 //but keep it compatible with jQuery < 1.2.3, see #122
744 if( jQuery.isFunction( jQuery.fn.data ) ) {
745 jQuery.data(this._box.get(0), WYMeditor.WYM_INDEX, this._index);
746 jQuery.data(this._element.get(0), WYMeditor.WYM_INDEX, this._index);
749 var h = WYMeditor.Helper;
751 //construct the iframe
752 var iframeHtml = this._options.iframeHtml;
753 iframeHtml = h.replaceAll(iframeHtml, WYMeditor.INDEX, this._index);
754 iframeHtml = h.replaceAll(iframeHtml, WYMeditor.IFRAME_BASE_PATH, this._options.iframeBasePath);
757 var boxHtml = jQuery(this._box).html();
759 boxHtml = h.replaceAll(boxHtml, WYMeditor.LOGO, this._options.logoHtml);
760 boxHtml = h.replaceAll(boxHtml, WYMeditor.TOOLS, this._options.toolsHtml);
761 boxHtml = h.replaceAll(boxHtml, WYMeditor.CONTAINERS,this._options.containersHtml);
762 boxHtml = h.replaceAll(boxHtml, WYMeditor.CLASSES, this._options.classesHtml);
763 boxHtml = h.replaceAll(boxHtml, WYMeditor.HTML, this._options.htmlHtml);
764 boxHtml = h.replaceAll(boxHtml, WYMeditor.IFRAME, iframeHtml);
765 boxHtml = h.replaceAll(boxHtml, WYMeditor.STATUS, this._options.statusHtml);
767 //construct tools list
768 var aTools = eval(this._options.toolsItems);
771 for(var i = 0; i < aTools.length; i++) {
772 var oTool = aTools[i];
773 if(oTool.name && oTool.title)
774 var sTool = this._options.toolsItemHtml;
775 var sTool = h.replaceAll(sTool, WYMeditor.TOOL_NAME, oTool.name);
776 sTool = h.replaceAll(sTool, WYMeditor.TOOL_TITLE, this._options.stringDelimiterLeft
778 + this._options.stringDelimiterRight);
779 sTool = h.replaceAll(sTool, WYMeditor.TOOL_CLASS, oTool.css);
783 boxHtml = h.replaceAll(boxHtml, WYMeditor.TOOLS_ITEMS, sTools);
785 //construct classes list
786 var aClasses = eval(this._options.classesItems);
789 for(var i = 0; i < aClasses.length; i++) {
790 var oClass = aClasses[i];
791 if(oClass.name && oClass.title)
792 var sClass = this._options.classesItemHtml;
793 sClass = h.replaceAll(sClass, WYMeditor.CLASS_NAME, oClass.name);
794 sClass = h.replaceAll(sClass, WYMeditor.CLASS_TITLE, oClass.title);
798 boxHtml = h.replaceAll(boxHtml, WYMeditor.CLASSES_ITEMS, sClasses);
800 //construct containers list
801 var aContainers = eval(this._options.containersItems);
802 var sContainers = "";
804 for(var i = 0; i < aContainers.length; i++) {
805 var oContainer = aContainers[i];
806 if(oContainer.name && oContainer.title)
807 var sContainer = this._options.containersItemHtml;
808 sContainer = h.replaceAll(sContainer, WYMeditor.CONTAINER_NAME, oContainer.name);
809 sContainer = h.replaceAll(sContainer, WYMeditor.CONTAINER_TITLE,
810 this._options.stringDelimiterLeft
812 + this._options.stringDelimiterRight);
813 sContainer = h.replaceAll(sContainer, WYMeditor.CONTAINER_CLASS, oContainer.css);
814 sContainers += sContainer;
817 boxHtml = h.replaceAll(boxHtml, WYMeditor.CONTAINERS_ITEMS, sContainers);
820 boxHtml = this.replaceStrings(boxHtml);
822 //load html in wymbox
823 jQuery(this._box).html(boxHtml);
825 //hide the html value
826 jQuery(this._box).find(this._options.htmlSelector).hide();
834 WYMeditor.editor.prototype.bindEvents = function() {
839 //handle click event on tools buttons
840 jQuery(this._box).find(this._options.toolSelector).click(function() {
841 wym._iframe.contentWindow.focus(); //See #154
842 wym.exec(jQuery(this).attr(WYMeditor.NAME));
846 //handle click event on containers buttons
847 jQuery(this._box).find(this._options.containerSelector).click(function() {
848 wym.container(jQuery(this).attr(WYMeditor.NAME));
852 //handle keyup event on html value: set the editor value
853 //handle focus/blur events to check if the element has focus, see #147
854 jQuery(this._box).find(this._options.htmlValSelector)
855 .keyup(function() { jQuery(wym._doc.body).html(jQuery(this).val());})
856 .focus(function() { jQuery(this).toggleClass('hasfocus'); })
857 .blur(function() { jQuery(this).toggleClass('hasfocus'); });
859 //handle click event on classes buttons
860 jQuery(this._box).find(this._options.classSelector).click(function() {
862 var aClasses = eval(wym._options.classesItems);
863 var sName = jQuery(this).attr(WYMeditor.NAME);
865 var oClass = WYMeditor.Helper.findByName(aClasses, sName);
868 var jqexpr = oClass.expr;
869 wym.toggleClass(sName, jqexpr);
871 wym._iframe.contentWindow.focus(); //See #154
875 //handle event on update element
876 jQuery(this._options.updateSelector)
877 .bind(this._options.updateEvent, function() {
882 WYMeditor.editor.prototype.ready = function() {
883 return(this._doc != null);
887 /********** METHODS **********/
890 * @description Returns the WYMeditor container
892 WYMeditor.editor.prototype.box = function() {
897 * @description Get/Set the html value
899 WYMeditor.editor.prototype.html = function(html) {
901 if(typeof html === 'string') jQuery(this._doc.body).html(html);
902 else return(jQuery(this._doc.body).html());
906 * @description Cleans up the HTML
908 WYMeditor.editor.prototype.xhtml = function() {
909 return this.parser.parse(this.html());
913 * @description Executes a button command
915 WYMeditor.editor.prototype.exec = function(cmd) {
917 //base function for execCommand
918 //open a dialog or exec
920 case WYMeditor.CREATE_LINK:
921 var container = this.container();
922 if(container || this._selected_image) this.dialog(WYMeditor.DIALOG_LINK);
925 case WYMeditor.INSERT_IMAGE:
926 this.dialog(WYMeditor.DIALOG_IMAGE);
929 case WYMeditor.INSERT_TABLE:
930 this.dialog(WYMeditor.DIALOG_TABLE);
933 case WYMeditor.PASTE:
934 this.dialog(WYMeditor.DIALOG_PASTE);
937 case WYMeditor.TOGGLE_HTML:
941 //partially fixes #121 when the user manually inserts an image
942 if(!jQuery(this._box).find(this._options.htmlSelector).is(':visible'))
946 case WYMeditor.PREVIEW:
947 this.dialog(WYMeditor.PREVIEW, this._options.dialogFeaturesPreview);
957 * @description Get/Set the selected container
959 WYMeditor.editor.prototype.container = function(sType) {
963 var container = null;
965 if(sType.toLowerCase() == WYMeditor.TH) {
967 container = this.container();
969 //find the TD or TH container
970 switch(container.tagName.toLowerCase()) {
972 case WYMeditor.TD: case WYMeditor.TH:
975 var aTypes = new Array(WYMeditor.TD,WYMeditor.TH);
976 container = this.findUp(this.container(), aTypes);
980 //if it exists, switch
981 if(container!=null) {
983 sType = (container.tagName.toLowerCase() == WYMeditor.TD)? WYMeditor.TH: WYMeditor.TD;
984 this.switchTo(container,sType);
989 //set the container type
990 var aTypes=new Array(WYMeditor.P,WYMeditor.H1,WYMeditor.H2,WYMeditor.H3,WYMeditor.H4,WYMeditor.H5,
991 WYMeditor.H6,WYMeditor.PRE,WYMeditor.BLOCKQUOTE);
992 container = this.findUp(this.container(), aTypes);
998 //blockquotes must contain a block level element
999 if(sType.toLowerCase() == WYMeditor.BLOCKQUOTE) {
1001 var blockquote = this.findUp(this.container(), WYMeditor.BLOCKQUOTE);
1003 if(blockquote == null) {
1005 newNode = this._doc.createElement(sType);
1006 container.parentNode.insertBefore(newNode,container);
1007 newNode.appendChild(container);
1008 this.setFocusToNode(newNode.firstChild);
1012 var nodes = blockquote.childNodes;
1013 var lgt = nodes.length;
1014 var firstNode = null;
1016 if(lgt > 0) firstNode = nodes.item(0);
1017 for(var x=0; x<lgt; x++) {
1018 blockquote.parentNode.insertBefore(nodes.item(0),blockquote);
1020 blockquote.parentNode.removeChild(blockquote);
1021 if(firstNode) this.setFocusToNode(firstNode);
1025 else this.switchTo(container,sType);
1031 else return(this.selected());
1034 /* @name toggleClass
1035 * @description Toggles class on selected element, or one of its parents
1037 WYMeditor.editor.prototype.toggleClass = function(sClass, jqexpr) {
1039 var container = (this._selected_image
1040 ? this._selected_image
1041 : jQuery(this.selected()));
1042 container = jQuery(container).parentsOrSelf(jqexpr);
1043 jQuery(container).toggleClass(sClass);
1045 if(!jQuery(container).attr(WYMeditor.CLASS)) jQuery(container).removeAttr(this._class);
1050 * @description Returns the first parent or self container, based on its type
1052 WYMeditor.editor.prototype.findUp = function(node, filter) {
1054 //filter is a string or an array of strings
1058 var tagname = node.tagName.toLowerCase();
1060 if(typeof(filter) == WYMeditor.STRING) {
1062 while(tagname != filter && tagname != WYMeditor.BODY) {
1064 node = node.parentNode;
1065 tagname = node.tagName.toLowerCase();
1072 while(!bFound && tagname != WYMeditor.BODY) {
1073 for(var i = 0; i < filter.length; i++) {
1074 if(tagname == filter[i]) {
1080 node = node.parentNode;
1081 tagname = node.tagName.toLowerCase();
1086 if(tagname != WYMeditor.BODY) return(node);
1089 } else return(null);
1093 * @description Switch the node's type
1095 WYMeditor.editor.prototype.switchTo = function(node,sType) {
1097 var newNode = this._doc.createElement(sType);
1098 var html = jQuery(node).html();
1099 node.parentNode.replaceChild(newNode,node);
1100 jQuery(newNode).html(html);
1101 this.setFocusToNode(newNode);
1104 WYMeditor.editor.prototype.replaceStrings = function(sVal) {
1105 //check if the language file has already been loaded
1106 //if not, get it via a synchronous ajax call
1107 if(!WYMeditor.STRINGS[this._options.lang]) {
1109 eval(jQuery.ajax({url:this._options.langPath
1110 + this._options.lang + '.js', async:false}).responseText);
1112 WYMeditor.console.error("WYMeditor: error while parsing language file.");
1117 //replace all the strings in sVal and return it
1118 for (var key in WYMeditor.STRINGS[this._options.lang]) {
1119 sVal = WYMeditor.Helper.replaceAll(sVal, this._options.stringDelimiterLeft + key
1120 + this._options.stringDelimiterRight,
1121 WYMeditor.STRINGS[this._options.lang][key]);
1126 WYMeditor.editor.prototype.encloseString = function(sVal) {
1128 return(this._options.stringDelimiterLeft
1130 + this._options.stringDelimiterRight);
1134 * @description Prints a status message
1136 WYMeditor.editor.prototype.status = function(sMessage) {
1138 //print status message
1139 jQuery(this._box).find(this._options.statusSelector).html(sMessage);
1143 * @description Updates the element and textarea values
1145 WYMeditor.editor.prototype.update = function() {
1147 var html = this.xhtml();
1148 jQuery(this._element).val(html);
1149 jQuery(this._box).find(this._options.htmlValSelector).not('.hasfocus').val(html); //#147
1153 * @description Opens a dialog box
1155 WYMeditor.editor.prototype.dialog = function( dialogType, dialogFeatures, bodyHtml ) {
1157 var features = dialogFeatures || this._wym._options.dialogFeatures;
1158 var wDialog = window.open('', 'dialog', features);
1164 switch( dialogType ) {
1166 case(WYMeditor.DIALOG_LINK):
1167 sBodyHtml = this._options.dialogLinkHtml;
1169 case(WYMeditor.DIALOG_IMAGE):
1170 sBodyHtml = this._options.dialogImageHtml;
1172 case(WYMeditor.DIALOG_TABLE):
1173 sBodyHtml = this._options.dialogTableHtml;
1175 case(WYMeditor.DIALOG_PASTE):
1176 sBodyHtml = this._options.dialogPasteHtml;
1178 case(WYMeditor.PREVIEW):
1179 sBodyHtml = this._options.dialogPreviewHtml;
1183 sBodyHtml = bodyHtml;
1186 var h = WYMeditor.Helper;
1188 //construct the dialog
1189 var dialogHtml = this._options.dialogHtml;
1190 dialogHtml = h.replaceAll(dialogHtml, WYMeditor.BASE_PATH, this._options.basePath);
1191 dialogHtml = h.replaceAll(dialogHtml, WYMeditor.DIRECTION, this._options.direction);
1192 dialogHtml = h.replaceAll(dialogHtml, WYMeditor.CSS_PATH, this._options.skinPath + WYMeditor.SKINS_DEFAULT_CSS);
1193 dialogHtml = h.replaceAll(dialogHtml, WYMeditor.WYM_PATH, this._options.wymPath);
1194 dialogHtml = h.replaceAll(dialogHtml, WYMeditor.JQUERY_PATH, this._options.jQueryPath);
1195 dialogHtml = h.replaceAll(dialogHtml, WYMeditor.DIALOG_TITLE, this.encloseString( dialogType ));
1196 dialogHtml = h.replaceAll(dialogHtml, WYMeditor.DIALOG_BODY, sBodyHtml);
1197 dialogHtml = h.replaceAll(dialogHtml, WYMeditor.INDEX, this._index);
1199 dialogHtml = this.replaceStrings(dialogHtml);
1201 var doc = wDialog.document;
1202 doc.write(dialogHtml);
1208 * @description Shows/Hides the HTML
1210 WYMeditor.editor.prototype.toggleHtml = function() {
1211 jQuery(this._box).find(this._options.htmlSelector).toggle();
1214 WYMeditor.editor.prototype.uniqueStamp = function() {
1215 var now = new Date();
1216 return("wym-" + now.getTime());
1219 WYMeditor.editor.prototype.paste = function(sData) {
1222 var container = this.selected();
1224 //split the data, using double newlines as the separator
1225 var aP = sData.split(this._newLine + this._newLine);
1226 var rExp = new RegExp(this._newLine, "g");
1228 //add a P for each item
1229 if(container && container.tagName.toLowerCase() != WYMeditor.BODY) {
1230 for(x = aP.length - 1; x >= 0; x--) {
1232 //simple newlines are replaced by a break
1233 sTmp = sTmp.replace(rExp, "<br />");
1234 jQuery(container).after("<p>" + sTmp + "</p>");
1237 for(x = 0; x < aP.length; x++) {
1239 //simple newlines are replaced by a break
1240 sTmp = sTmp.replace(rExp, "<br />");
1241 jQuery(this._doc.body).append("<p>" + sTmp + "</p>");
1247 WYMeditor.editor.prototype.insert = function(html) {
1248 // Do we have a selection?
1249 if (this._iframe.contentWindow.getSelection().focusNode != null) {
1250 // Overwrite selection with provided html
1251 this._exec( WYMeditor.INSERT_HTML, html);
1253 // Fall back to the internal paste function if there's no selection
1258 WYMeditor.editor.prototype.wrap = function(left, right) {
1259 // Do we have a selection?
1260 if (this._iframe.contentWindow.getSelection().focusNode != null) {
1261 // Wrap selection with provided html
1262 this._exec( WYMeditor.INSERT_HTML, left + this._iframe.contentWindow.getSelection().toString() + right);
1266 WYMeditor.editor.prototype.unwrap = function() {
1267 // Do we have a selection?
1268 if (this._iframe.contentWindow.getSelection().focusNode != null) {
1270 this._exec( WYMeditor.INSERT_HTML, this._iframe.contentWindow.getSelection().toString() );
1274 WYMeditor.editor.prototype.addCssRules = function(doc, aCss) {
1275 var styles = doc.styleSheets[0];
1277 for(var i = 0; i < aCss.length; i++) {
1279 if(oCss.name && oCss.css) this.addCssRule(styles, oCss);
1284 /********** CONFIGURATION **********/
1286 WYMeditor.editor.prototype.computeBasePath = function() {
1287 return jQuery(jQuery.grep(jQuery('script'), function(s){
1288 return (s.src && s.src.match(/jquery\.wymeditor(\.pack|\.min|\.packed)?\.js(\?.*)?$/ ))
1289 })).attr('src').replace(/jquery\.wymeditor(\.pack|\.min|\.packed)?\.js(\?.*)?$/, '');
1292 WYMeditor.editor.prototype.computeWymPath = function() {
1293 return jQuery(jQuery.grep(jQuery('script'), function(s){
1294 return (s.src && s.src.match(/jquery\.wymeditor(\.pack|\.min|\.packed)?\.js(\?.*)?$/ ))
1298 WYMeditor.editor.prototype.computeJqueryPath = function() {
1299 return jQuery(jQuery.grep(jQuery('script'), function(s){
1300 return (s.src && s.src.match(/jquery(-(.*)){0,1}(\.pack|\.min|\.packed)?\.js(\?.*)?$/ ))
1304 WYMeditor.editor.prototype.computeCssPath = function() {
1305 return jQuery(jQuery.grep(jQuery('link'), function(s){
1306 return (s.href && s.href.match(/wymeditor\/skins\/(.*)screen\.css(\?.*)?$/ ))
1310 WYMeditor.editor.prototype.configureEditorUsingRawCss = function() {
1312 var CssParser = new WYMeditor.WymCssParser();
1313 if(this._options.stylesheet){
1314 CssParser.parse(jQuery.ajax({url: this._options.stylesheet,async:false}).responseText);
1316 CssParser.parse(this._options.styles, false);
1319 if(this._options.classesItems.length == 0) {
1320 this._options.classesItems = CssParser.css_settings.classesItems;
1322 if(this._options.editorStyles.length == 0) {
1323 this._options.editorStyles = CssParser.css_settings.editorStyles;
1325 if(this._options.dialogStyles.length == 0) {
1326 this._options.dialogStyles = CssParser.css_settings.dialogStyles;
1330 /********** EVENTS **********/
1332 WYMeditor.editor.prototype.listen = function() {
1334 //don't use jQuery.find() on the iframe body
1335 //because of MSIE + jQuery + expando issue (#JQ1143)
1336 //jQuery(this._doc.body).find("*").bind("mouseup", this.mouseup);
1338 jQuery(this._doc.body).bind("mousedown", this.mousedown);
1339 var images = this._doc.body.getElementsByTagName("img");
1340 for(var i=0; i < images.length; i++) {
1341 jQuery(images[i]).bind("mousedown", this.mousedown);
1345 WYMeditor.editor.prototype.mousedown = function(evt) {
1347 var wym = WYMeditor.INSTANCES[this.ownerDocument.title];
1348 wym._selected_image = (this.tagName.toLowerCase() == WYMeditor.IMG) ? this : null;
1349 evt.stopPropagation();
1352 /********** SKINS **********/
1355 * Function: WYMeditor.loadCss
1356 * Loads a stylesheet in the document.
1359 * href - The CSS path.
1361 WYMeditor.loadCss = function(href) {
1363 var link = document.createElement('link');
1364 link.rel = 'stylesheet';
1367 var head = jQuery('head').get(0);
1368 head.appendChild(link);
1372 * Function: WYMeditor.editor.loadSkin
1373 * Loads the skin CSS and initialization script (if needed).
1375 WYMeditor.editor.prototype.loadSkin = function() {
1377 //does the user want to automatically load the CSS (default: yes)?
1378 //we also test if it hasn't been already loaded by another instance
1379 //see below for a better (second) test
1380 if(this._options.loadSkin && !WYMeditor.SKINS[this._options.skin]) {
1382 //check if it hasn't been already loaded
1383 //so we don't load it more than once
1384 //(we check the existing <link> elements)
1387 var rExp = new RegExp(this._options.skin
1388 + '\/' + WYMeditor.SKINS_DEFAULT_CSS + '$');
1390 jQuery('link').each( function() {
1391 if(this.href.match(rExp)) found = true;
1394 //load it, using the skin path
1395 if(!found) WYMeditor.loadCss( this._options.skinPath
1396 + WYMeditor.SKINS_DEFAULT_CSS );
1399 //put the classname (ex. wym_skin_default) on wym_box
1400 jQuery(this._box).addClass( "wym_skin_" + this._options.skin );
1402 //does the user want to use some JS to initialize the skin (default: yes)?
1403 //also check if it hasn't already been loaded by another instance
1404 if(this._options.initSkin && !WYMeditor.SKINS[this._options.skin]) {
1406 eval(jQuery.ajax({url:this._options.skinPath
1407 + WYMeditor.SKINS_DEFAULT_JS, async:false}).responseText);
1410 //init the skin, if needed
1411 if(WYMeditor.SKINS[this._options.skin]
1412 && WYMeditor.SKINS[this._options.skin].init)
1413 WYMeditor.SKINS[this._options.skin].init(this);
1418 /********** DIALOGS **********/
1420 WYMeditor.INIT_DIALOG = function(index) {
1422 var wym = window.opener.WYMeditor.INSTANCES[index];
1423 var doc = window.document;
1424 var selected = wym.selected();
1425 var dialogType = jQuery(wym._options.dialogTypeSelector).val();
1426 var sStamp = wym.uniqueStamp();
1428 switch(dialogType) {
1430 case WYMeditor.DIALOG_LINK:
1431 //ensure that we select the link to populate the fields
1432 if(selected && selected.tagName && selected.tagName.toLowerCase != WYMeditor.A)
1433 selected = jQuery(selected).parentsOrSelf(WYMeditor.A);
1435 //fix MSIE selection if link image has been clicked
1436 if(!selected && wym._selected_image)
1437 selected = jQuery(wym._selected_image).parentsOrSelf(WYMeditor.A);
1442 //pre-init functions
1443 if(jQuery.isFunction(wym._options.preInitDialog))
1444 wym._options.preInitDialog(wym,window);
1446 //add css rules from options
1447 var styles = doc.styleSheets[0];
1448 var aCss = eval(wym._options.dialogStyles);
1450 wym.addCssRules(doc, aCss);
1452 //auto populate fields if selected container (e.g. A)
1454 jQuery(wym._options.hrefSelector).val(jQuery(selected).attr(WYMeditor.HREF));
1455 jQuery(wym._options.srcSelector).val(jQuery(selected).attr(WYMeditor.SRC));
1456 jQuery(wym._options.titleSelector).val(jQuery(selected).attr(WYMeditor.TITLE));
1457 jQuery(wym._options.altSelector).val(jQuery(selected).attr(WYMeditor.ALT));
1460 //auto populate image fields if selected image
1461 if(wym._selected_image) {
1462 jQuery(wym._options.dialogImageSelector + " " + wym._options.srcSelector)
1463 .val(jQuery(wym._selected_image).attr(WYMeditor.SRC));
1464 jQuery(wym._options.dialogImageSelector + " " + wym._options.titleSelector)
1465 .val(jQuery(wym._selected_image).attr(WYMeditor.TITLE));
1466 jQuery(wym._options.dialogImageSelector + " " + wym._options.altSelector)
1467 .val(jQuery(wym._selected_image).attr(WYMeditor.ALT));
1470 jQuery(wym._options.dialogLinkSelector + " "
1471 + wym._options.submitSelector).click(function() {
1473 var sUrl = jQuery(wym._options.hrefSelector).val();
1474 if(sUrl.length > 0) {
1476 wym._exec(WYMeditor.CREATE_LINK, sStamp);
1478 jQuery("a[href=" + sStamp + "]", wym._doc.body)
1479 .attr(WYMeditor.HREF, sUrl)
1480 .attr(WYMeditor.TITLE, jQuery(wym._options.titleSelector).val());
1486 jQuery(wym._options.dialogImageSelector + " "
1487 + wym._options.submitSelector).click(function() {
1489 var sUrl = jQuery(wym._options.srcSelector).val();
1490 if(sUrl.length > 0) {
1492 wym._exec(WYMeditor.INSERT_IMAGE, sStamp);
1494 jQuery("img[src$=" + sStamp + "]", wym._doc.body)
1495 .attr(WYMeditor.SRC, sUrl)
1496 .attr(WYMeditor.TITLE, jQuery(wym._options.titleSelector).val())
1497 .attr(WYMeditor.ALT, jQuery(wym._options.altSelector).val());
1502 jQuery(wym._options.dialogTableSelector + " "
1503 + wym._options.submitSelector).click(function() {
1505 var iRows = jQuery(wym._options.rowsSelector).val();
1506 var iCols = jQuery(wym._options.colsSelector).val();
1508 if(iRows > 0 && iCols > 0) {
1510 var table = wym._doc.createElement(WYMeditor.TABLE);
1514 var sCaption = jQuery(wym._options.captionSelector).val();
1516 //we create the caption
1517 var newCaption = table.createCaption();
1518 newCaption.innerHTML = sCaption;
1520 //we create the rows and cells
1521 for(x=0; x<iRows; x++) {
1522 newRow = table.insertRow(x);
1523 for(y=0; y<iCols; y++) {newRow.insertCell(y);}
1526 //set the summary attr
1527 jQuery(table).attr('summary',
1528 jQuery(wym._options.summarySelector).val());
1530 //append the table after the selected container
1531 var node = jQuery(wym.findUp(wym.container(),
1532 WYMeditor.MAIN_CONTAINERS)).get(0);
1533 if(!node || !node.parentNode) jQuery(wym._doc.body).append(table);
1534 else jQuery(node).after(table);
1539 jQuery(wym._options.dialogPasteSelector + " "
1540 + wym._options.submitSelector).click(function() {
1542 var sText = jQuery(wym._options.textSelector).val();
1547 jQuery(wym._options.dialogPreviewSelector + " "
1548 + wym._options.previewSelector)
1552 jQuery(wym._options.cancelSelector).mousedown(function() {
1556 //pre-init functions
1557 if(jQuery.isFunction(wym._options.postInitDialog))
1558 wym._options.postInitDialog(wym,window);
1562 /********** XHTML LEXER/PARSER **********/
1566 * @description Use these methods to generate XML and XHTML compliant tags and
1567 * escape tag attributes correctly
1568 * @author Bermi Ferrer - http://bermi.org
1569 * @author David Heinemeier Hansson http://loudthinking.com
1571 WYMeditor.XmlHelper = function()
1573 this._entitiesDiv = document.createElement('div');
1581 * Returns an empty HTML tag of type *name* which by default is XHTML
1582 * compliant. Setting *open* to true will create an open tag compatible
1583 * with HTML 4.0 and below. Add HTML attributes by passing an attributes
1584 * array to *options*. For attributes with no value like (disabled and
1585 * readonly), give it a value of true in the *options* array.
1591 * this.tag ('br', false, true)
1593 * this.tag ('input', jQuery({type:'text',disabled:true }) )
1594 * # => <input type="text" disabled="disabled" />
1596 WYMeditor.XmlHelper.prototype.tag = function(name, options, open)
1598 options = options || false;
1599 open = open || false;
1600 return '<'+name+(options ? this.tagOptions(options) : '')+(open ? '>' : ' />');
1606 * Returns a XML block tag of type *name* surrounding the *content*. Add
1607 * XML attributes by passing an attributes array to *options*. For attributes
1608 * with no value like (disabled and readonly), give it a value of true in
1609 * the *options* array. You can use symbols or strings for the attribute names.
1611 * this.contentTag ('p', 'Hello world!' )
1612 * # => <p>Hello world!</p>
1613 * this.contentTag('div', this.contentTag('p', "Hello world!"), jQuery({class : "strong"}))
1614 * # => <div class="strong"><p>Hello world!</p></div>
1615 * this.contentTag("select", options, jQuery({multiple : true}))
1616 * # => <select multiple="multiple">...options...</select>
1618 WYMeditor.XmlHelper.prototype.contentTag = function(name, content, options)
1620 options = options || false;
1621 return '<'+name+(options ? this.tagOptions(options) : '')+'>'+content+'</'+name+'>';
1625 * @name cdataSection
1627 * Returns a CDATA section for the given +content+. CDATA sections
1628 * are used to escape blocks of text containing characters which would
1629 * otherwise be recognized as markup. CDATA sections begin with the string
1630 * <tt><![CDATA[</tt> and } with (and may not contain) the string
1633 WYMeditor.XmlHelper.prototype.cdataSection = function(content)
1635 return '<![CDATA['+content+']]>';
1642 * Returns the escaped +xml+ without affecting existing escaped entities.
1644 * this.escapeOnce( "1 > 2 & 3")
1645 * # => "1 > 2 & 3"
1647 WYMeditor.XmlHelper.prototype.escapeOnce = function(xml)
1649 return this._fixDoubleEscape(this.escapeEntities(xml));
1653 * @name _fixDoubleEscape
1655 * Fix double-escaped entities, such as &amp;, &#123;, etc.
1657 WYMeditor.XmlHelper.prototype._fixDoubleEscape = function(escaped)
1659 return escaped.replace(/&([a-z]+|(#\d+));/ig, "&$1;");
1665 * Takes an array like the one generated by Tag.parseAttributes
1666 * [["src", "http://www.editam.com/?a=b&c=d&f=g"], ["title", "Editam, <Simplified> CMS"]]
1667 * or an object like {src:"http://www.editam.com/?a=b&c=d&f=g", title:"Editam, <Simplified> CMS"}
1668 * and returns a string properly escaped like
1669 * ' src = "http://www.editam.com/?a=b&c=d&f=g" title = "Editam, <Simplified> CMS"'
1670 * which is valid for strict XHTML
1672 WYMeditor.XmlHelper.prototype.tagOptions = function(options)
1675 xml._formated_options = '';
1677 for (var key in options) {
1678 var formated_options = '';
1679 var value = options[key];
1680 if(typeof value != 'function' && value.length > 0) {
1682 if(parseInt(key) == key && typeof value == 'object'){
1683 key = value.shift();
1684 value = value.pop();
1686 if(key != '' && value != ''){
1687 xml._formated_options += ' '+key+'="'+xml.escapeOnce(value)+'"';
1691 return xml._formated_options;
1695 * @name escapeEntities
1697 * Escapes XML/HTML entities <, >, & and ". If seccond parameter is set to false it
1698 * will not escape ". If set to true it will also escape '
1700 WYMeditor.XmlHelper.prototype.escapeEntities = function(string, escape_quotes)
1702 this._entitiesDiv.innerHTML = string;
1703 this._entitiesDiv.textContent = string;
1704 var result = this._entitiesDiv.innerHTML;
1705 if(typeof escape_quotes == 'undefined'){
1706 if(escape_quotes != false) result = result.replace('"', '"');
1707 if(escape_quotes == true) result = result.replace('"', ''');
1713 * Parses a string conatining tag attributes and values an returns an array formated like
1714 * [["src", "http://www.editam.com"], ["title", "Editam, Simplified CMS"]]
1716 WYMeditor.XmlHelper.prototype.parseAttributes = function(tag_attributes)
1718 // Use a compounded regex to match single quoted, double quoted and unquoted attribute pairs
1720 var matches = tag_attributes.split(/((=\s*")(")("))|((=\s*\')(\')(\'))|((=\s*[^>\s]*))/g);
1721 if(matches.toString() != tag_attributes){
1722 for (var k in matches) {
1724 if(typeof v != 'function' && v.length != 0){
1725 var re = new RegExp('(\\w+)\\s*'+v);
1726 if(match = tag_attributes.match(re) ){
1727 var value = v.replace(/^[\s=]+/, "");
1728 var delimiter = value.charAt(0);
1729 delimiter = delimiter == '"' ? '"' : (delimiter=="'"?"'":'');
1730 if(delimiter != ''){
1731 value = delimiter == '"' ? value.replace(/^"|"+$/g, '') : value.replace(/^'|'+$/g, '');
1733 tag_attributes = tag_attributes.replace(match[0],'');
1734 result.push([match[1] , value]);
1743 * XhtmlValidator for validating tag attributes
1745 * @author Bermi Ferrer - http://bermi.org
1747 WYMeditor.XhtmlValidator = {
1795 "accesskey":/^(\w){1}$/,
1796 "tabindex":/^(\d)+$/
1891 "rel":/^(alternate|designates|stylesheet|start|next|prev|contents|index|glossary|copyright|chapter|section|subsection|appendix|help|bookmark| |shortcut|icon)+$/,
1892 "rev":/^(alternate|designates|stylesheet|start|next|prev|contents|index|glossary|copyright|chapter|section|subsection|appendix|help|bookmark| |shortcut|icon)+$/,
1893 "shape":/^(rect|rectangle|circ|circle|poly|polygon)$/,
1907 "nohref":/^(true|false)$/,
1908 "shape":/^(rect|rectangle|circ|circle|poly|polygon)$/
1947 "disabled":/^(disabled)$/,
1948 "type":/^(button|reset|submit)$/,
1960 "align":/^(right|left|center|justify)$/,
1964 "valign":/^(top|middle|bottom|baseline)$/,
1973 "align":/^(right|left|center|justify)$/,
1977 "valign":/^(top|middle|bottom|baseline)$/,
1987 "datetime":/^([0-9]){8}/
2005 "2":"accept-charset",
2007 "method":/^(get|post)$/
2055 "checked":/^(checked)$/,
2056 "disabled":/^(disabled)$/,
2057 "maxlength":/^(\d)+$/,
2059 "readonly":/^(readonly)$/,
2062 "type":/^(button|checkbox|file|hidden|image|password|radio|reset|submit|text)$/,
2072 "datetime":/^([0-9]){8}/
2092 "media":/^(all|braille|print|projection|screen|speech|,|;| )+$/i,
2093 //next comment line required by Opera!
2094 /*"rel":/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i,*/
2095 "rel":/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i,
2096 "rev":/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i,
2116 "http-equiv":/^(content\-type|expires|refresh|set\-cookie)$/i,
2148 "disabled": /^(disabled)$/
2159 "disabled":/^(disabled)$/,
2160 "selected":/^(selected)$/,
2171 "valuetype":/^(data|ref|object)$/,
2191 "type":/^(text\/ecmascript|text\/javascript|text\/jscript|text\/vbscript|text\/vbs|text\/xml)$/,
2193 "defer":/^(defer)$/,
2204 "disabled":/^(disabled)$/,
2205 "multiple":/^(multiple)$/,
2219 "media":/^(screen|tty|tv|projection|handheld|print|braille|aural|all)$/
2234 "frame":/^(void|above|below|hsides|lhs|rhs|vsides|box|border)$/,
2235 "rules":/^(none|groups|rows|cols|all)$/,
2244 "align":/^(right|left|center|justify)$/,
2247 "valign":/^(top|middle|bottom|baseline)$/
2255 "align":/^(left|right|center|justify|char)$/,
2259 "colspan":/^(\d)+$/,
2261 "rowspan":/^(\d)+$/,
2262 "scope":/^(col|colgroup|row|rowgroup)$/,
2263 "valign":/^(top|middle|bottom|baseline)$/
2285 "align":/^(right|left|center|justify)$/,
2288 "valign":/^(top|middle|bottom)$/,
2297 "align":/^(left|right|center|justify|char)$/,
2301 "colspan":/^(\d)+$/,
2303 "rowspan":/^(\d)+$/,
2304 "scope":/^(col|colgroup|row|rowgroup)$/,
2305 "valign":/^(top|middle|bottom|baseline)$/
2312 "align":/^(right|left|center|justify)$/,
2315 "valign":/^(top|middle|bottom|baseline)$/
2323 "align":/^(right|left|center|justify|char)$/,
2326 "valign":/^(top|middle|bottom|baseline)$/
2334 // Temporary skiped attributes
2335 skiped_attributes : [],
2336 skiped_attribute_values : [],
2338 getValidTagAttributes: function(tag, attributes)
2340 var valid_attributes = {};
2341 var possible_attributes = this.getPossibleTagAttributes(tag);
2342 for(var attribute in attributes) {
2343 var value = attributes[attribute];
2344 var h = WYMeditor.Helper;
2345 if(!h.contains(this.skiped_attributes, attribute) && !h.contains(this.skiped_attribute_values, value)){
2346 if (typeof value != 'function' && h.contains(possible_attributes, attribute)) {
2347 if (this.doesAttributeNeedsValidation(tag, attribute)) {
2348 if(this.validateAttribute(tag, attribute, value)){
2349 valid_attributes[attribute] = value;
2352 valid_attributes[attribute] = value;
2357 return valid_attributes;
2359 getUniqueAttributesAndEventsForTag : function(tag)
2363 if (this._tags[tag] && this._tags[tag]['attributes']) {
2364 for (k in this._tags[tag]['attributes']) {
2365 result.push(parseInt(k) == k ? this._tags[tag]['attributes'][k] : k);
2370 getDefaultAttributesAndEventsForTags : function()
2373 for (var key in this._events){
2374 result.push(this._events[key]);
2376 for (var key in this._attributes){
2377 result.push(this._attributes[key]);
2381 isValidTag : function(tag)
2383 if(this._tags[tag]){
2386 for(var key in this._tags){
2387 if(this._tags[key] == tag){
2393 getDefaultAttributesAndEventsForTag : function(tag)
2395 var default_attributes = [];
2396 if (this.isValidTag(tag)) {
2397 var default_attributes_and_events = this.getDefaultAttributesAndEventsForTags();
2399 for(var key in default_attributes_and_events) {
2400 var defaults = default_attributes_and_events[key];
2401 if(typeof defaults == 'object'){
2402 var h = WYMeditor.Helper;
2403 if ((defaults['except'] && h.contains(defaults['except'], tag)) || (defaults['only'] && !h.contains(defaults['only'], tag))) {
2407 var tag_defaults = defaults['attributes'] ? defaults['attributes'] : defaults['events'];
2408 for(var k in tag_defaults) {
2409 default_attributes.push(typeof tag_defaults[k] != 'string' ? k : tag_defaults[k]);
2414 return default_attributes;
2416 doesAttributeNeedsValidation: function(tag, attribute)
2418 return this._tags[tag] && ((this._tags[tag]['attributes'] && this._tags[tag]['attributes'][attribute]) || (this._tags[tag]['required'] &&
2419 WYMeditor.Helper.contains(this._tags[tag]['required'], attribute)));
2421 validateAttribute : function(tag, attribute, value)
2423 if ( this._tags[tag] &&
2424 (this._tags[tag]['attributes'] && this._tags[tag]['attributes'][attribute] && value.length > 0 && !value.match(this._tags[tag]['attributes'][attribute])) || // invalid format
2425 (this._tags[tag] && this._tags[tag]['required'] && WYMeditor.Helper.contains(this._tags[tag]['required'], attribute) && value.length == 0) // required attribute
2429 return typeof this._tags[tag] != 'undefined';
2431 getPossibleTagAttributes : function(tag)
2433 if (!this._possible_tag_attributes) {
2434 this._possible_tag_attributes = {};
2436 if (!this._possible_tag_attributes[tag]) {
2437 this._possible_tag_attributes[tag] = this.getUniqueAttributesAndEventsForTag(tag).concat(this.getDefaultAttributesAndEventsForTag(tag));
2439 return this._possible_tag_attributes[tag];
2445 * Compounded regular expression. Any of
2446 * the contained patterns could match and
2447 * when one does, it's label is returned.
2449 * Constructor. Starts with no patterns.
2450 * @param boolean case True for case sensitive, false
2453 * @author Marcus Baker (http://lastcraft.com)
2454 * @author Bermi Ferrer (http://bermi.org)
2456 WYMeditor.ParallelRegex = function(case_sensitive)
2458 this._case = case_sensitive;
2459 this._patterns = [];
2467 * Adds a pattern with an optional label.
2468 * @param string pattern Perl style regex, but ( and )
2469 * lose the usual meaning.
2470 * @param string label Label of regex to be returned
2474 WYMeditor.ParallelRegex.prototype.addPattern = function(pattern, label)
2476 label = label || true;
2477 var count = this._patterns.length;
2478 this._patterns[count] = pattern;
2479 this._labels[count] = label;
2484 * Attempts to match all patterns at once against
2486 * @param string subject String to match against.
2488 * @return boolean True on success.
2489 * @return string match First matched portion of
2493 WYMeditor.ParallelRegex.prototype.match = function(subject)
2495 if (this._patterns.length == 0) {
2498 var matches = subject.match(this._getCompoundedRegex());
2503 var match = matches[0];
2504 for (var i = 1; i < matches.length; i++) {
2506 return [this._labels[i-1], match];
2509 return [true, matches[0]];
2513 * Compounds the patterns into a single
2514 * regular expression separated with the
2515 * "or" operator. Caches the regex.
2516 * Will automatically escape (, ) and / tokens.
2517 * @param array patterns List of patterns in order.
2520 WYMeditor.ParallelRegex.prototype._getCompoundedRegex = function()
2522 if (this._regex == null) {
2523 for (var i = 0, count = this._patterns.length; i < count; i++) {
2524 this._patterns[i] = '(' + this._untokenizeRegex(this._tokenizeRegex(this._patterns[i]).replace(/([\/\(\)])/g,'\\$1')) + ')';
2526 this._regex = new RegExp(this._patterns.join("|") ,this._getPerlMatchingFlags());
2532 * Escape lookahead/lookbehind blocks
2534 WYMeditor.ParallelRegex.prototype._tokenizeRegex = function(regex)
2537 replace(/\(\?(i|m|s|x|U)\)/, '~~~~~~Tk1\$1~~~~~~').
2538 replace(/\(\?(\-[i|m|s|x|U])\)/, '~~~~~~Tk2\$1~~~~~~').
2539 replace(/\(\?\=(.*)\)/, '~~~~~~Tk3\$1~~~~~~').
2540 replace(/\(\?\!(.*)\)/, '~~~~~~Tk4\$1~~~~~~').
2541 replace(/\(\?\<\=(.*)\)/, '~~~~~~Tk5\$1~~~~~~').
2542 replace(/\(\?\<\!(.*)\)/, '~~~~~~Tk6\$1~~~~~~').
2543 replace(/\(\?\:(.*)\)/, '~~~~~~Tk7\$1~~~~~~');
2547 * Unscape lookahead/lookbehind blocks
2549 WYMeditor.ParallelRegex.prototype._untokenizeRegex = function(regex)
2552 replace(/~~~~~~Tk1(.{1})~~~~~~/, "(?\$1)").
2553 replace(/~~~~~~Tk2(.{2})~~~~~~/, "(?\$1)").
2554 replace(/~~~~~~Tk3(.*)~~~~~~/, "(?=\$1)").
2555 replace(/~~~~~~Tk4(.*)~~~~~~/, "(?!\$1)").
2556 replace(/~~~~~~Tk5(.*)~~~~~~/, "(?<=\$1)").
2557 replace(/~~~~~~Tk6(.*)~~~~~~/, "(?<!\$1)").
2558 replace(/~~~~~~Tk7(.*)~~~~~~/, "(?:\$1)");
2563 * Accessor for perl regex mode flags to use.
2564 * @return string Perl regex flags.
2567 WYMeditor.ParallelRegex.prototype._getPerlMatchingFlags = function()
2569 return (this._case ? "m" : "mi");
2575 * States for a stack machine.
2577 * Constructor. Starts in named state.
2578 * @param string start Starting state name.
2580 * @author Marcus Baker (http://lastcraft.com)
2581 * @author Bermi Ferrer (http://bermi.org)
2583 WYMeditor.StateStack = function(start)
2585 this._stack = [start];
2590 * Accessor for current state.
2591 * @return string State.
2594 WYMeditor.StateStack.prototype.getCurrent = function()
2596 return this._stack[this._stack.length - 1];
2600 * Adds a state to the stack and sets it
2601 * to be the current state.
2602 * @param string state New state.
2605 WYMeditor.StateStack.prototype.enter = function(state)
2607 this._stack.push(state);
2611 * Leaves the current state and reverts
2612 * to the previous one.
2613 * @return boolean False if we drop off
2614 * the bottom of the list.
2617 WYMeditor.StateStack.prototype.leave = function()
2619 if (this._stack.length == 1) {
2628 WYMeditor.LEXER_ENTER = 1;
2629 WYMeditor.LEXER_MATCHED = 2;
2630 WYMeditor.LEXER_UNMATCHED = 3;
2631 WYMeditor.LEXER_EXIT = 4;
2632 WYMeditor.LEXER_SPECIAL = 5;
2636 * Accepts text and breaks it into tokens.
2637 * Some optimisation to make the sure the
2638 * content is only scanned by the PHP regex
2639 * parser once. Lexer modes must not start
2640 * with leading underscores.
2642 * Sets up the lexer in case insensitive matching
2644 * @param Parser parser Handling strategy by reference.
2645 * @param string start Starting handler.
2646 * @param boolean case True for case sensitive.
2648 * @author Marcus Baker (http://lastcraft.com)
2649 * @author Bermi Ferrer (http://bermi.org)
2651 WYMeditor.Lexer = function(parser, start, case_sensitive)
2653 start = start || 'accept';
2654 this._case = case_sensitive || false;
2656 this._parser = parser;
2657 this._mode = new WYMeditor.StateStack(start);
2658 this._mode_handlers = {};
2659 this._mode_handlers[start] = start;
2664 * Adds a token search pattern for a particular
2665 * parsing mode. The pattern does not change the
2667 * @param string pattern Perl style regex, but ( and )
2668 * lose the usual meaning.
2669 * @param string mode Should only apply this
2670 * pattern when dealing with
2671 * this type of input.
2674 WYMeditor.Lexer.prototype.addPattern = function(pattern, mode)
2676 var mode = mode || "accept";
2677 if (typeof this._regexes[mode] == 'undefined') {
2678 this._regexes[mode] = new WYMeditor.ParallelRegex(this._case);
2680 this._regexes[mode].addPattern(pattern);
2681 if (typeof this._mode_handlers[mode] == 'undefined') {
2682 this._mode_handlers[mode] = mode;
2687 * Adds a pattern that will enter a new parsing
2688 * mode. Useful for entering parenthesis, strings,
2690 * @param string pattern Perl style regex, but ( and )
2691 * lose the usual meaning.
2692 * @param string mode Should only apply this
2693 * pattern when dealing with
2694 * this type of input.
2695 * @param string new_mode Change parsing to this new
2699 WYMeditor.Lexer.prototype.addEntryPattern = function(pattern, mode, new_mode)
2701 if (typeof this._regexes[mode] == 'undefined') {
2702 this._regexes[mode] = new WYMeditor.ParallelRegex(this._case);
2704 this._regexes[mode].addPattern(pattern, new_mode);
2705 if (typeof this._mode_handlers[new_mode] == 'undefined') {
2706 this._mode_handlers[new_mode] = new_mode;
2711 * Adds a pattern that will exit the current mode
2712 * and re-enter the previous one.
2713 * @param string pattern Perl style regex, but ( and )
2714 * lose the usual meaning.
2715 * @param string mode Mode to leave.
2718 WYMeditor.Lexer.prototype.addExitPattern = function(pattern, mode)
2720 if (typeof this._regexes[mode] == 'undefined') {
2721 this._regexes[mode] = new WYMeditor.ParallelRegex(this._case);
2723 this._regexes[mode].addPattern(pattern, "__exit");
2724 if (typeof this._mode_handlers[mode] == 'undefined') {
2725 this._mode_handlers[mode] = mode;
2730 * Adds a pattern that has a special mode. Acts as an entry
2731 * and exit pattern in one go, effectively calling a special
2732 * parser handler for this token only.
2733 * @param string pattern Perl style regex, but ( and )
2734 * lose the usual meaning.
2735 * @param string mode Should only apply this
2736 * pattern when dealing with
2737 * this type of input.
2738 * @param string special Use this mode for this one token.
2741 WYMeditor.Lexer.prototype.addSpecialPattern = function(pattern, mode, special)
2743 if (typeof this._regexes[mode] == 'undefined') {
2744 this._regexes[mode] = new WYMeditor.ParallelRegex(this._case);
2746 this._regexes[mode].addPattern(pattern, '_'+special);
2747 if (typeof this._mode_handlers[special] == 'undefined') {
2748 this._mode_handlers[special] = special;
2753 * Adds a mapping from a mode to another handler.
2754 * @param string mode Mode to be remapped.
2755 * @param string handler New target handler.
2758 WYMeditor.Lexer.prototype.mapHandler = function(mode, handler)
2760 this._mode_handlers[mode] = handler;
2764 * Splits the page text into tokens. Will fail
2765 * if the handlers report an error or if no
2766 * content is consumed. If successful then each
2767 * unparsed and parsed token invokes a call to the
2769 * @param string raw Raw HTML text.
2770 * @return boolean True on success, else false.
2773 WYMeditor.Lexer.prototype.parse = function(raw)
2775 if (typeof this._parser == 'undefined') {
2779 var length = raw.length;
2781 while (typeof (parsed = this._reduce(raw)) == 'object') {
2782 var raw = parsed[0];
2783 var unmatched = parsed[1];
2784 var matched = parsed[2];
2785 var mode = parsed[3];
2787 if (! this._dispatchTokens(unmatched, matched, mode)) {
2794 if (raw.length == length) {
2797 length = raw.length;
2803 return this._invokeParser(raw, WYMeditor.LEXER_UNMATCHED);
2807 * Sends the matched token and any leading unmatched
2808 * text to the parser changing the lexer to a new
2809 * mode if one is listed.
2810 * @param string unmatched Unmatched leading portion.
2811 * @param string matched Actual token match.
2812 * @param string mode Mode after match. A boolean
2813 * false mode causes no change.
2814 * @return boolean False if there was any error
2818 WYMeditor.Lexer.prototype._dispatchTokens = function(unmatched, matched, mode)
2820 mode = mode || false;
2822 if (! this._invokeParser(unmatched, WYMeditor.LEXER_UNMATCHED)) {
2826 if (typeof mode == 'boolean') {
2827 return this._invokeParser(matched, WYMeditor.LEXER_MATCHED);
2829 if (this._isModeEnd(mode)) {
2830 if (! this._invokeParser(matched, WYMeditor.LEXER_EXIT)) {
2833 return this._mode.leave();
2835 if (this._isSpecialMode(mode)) {
2836 this._mode.enter(this._decodeSpecial(mode));
2837 if (! this._invokeParser(matched, WYMeditor.LEXER_SPECIAL)) {
2840 return this._mode.leave();
2842 this._mode.enter(mode);
2844 return this._invokeParser(matched, WYMeditor.LEXER_ENTER);
2848 * Tests to see if the new mode is actually to leave
2849 * the current mode and pop an item from the matching
2851 * @param string mode Mode to test.
2852 * @return boolean True if this is the exit mode.
2855 WYMeditor.Lexer.prototype._isModeEnd = function(mode)
2857 return (mode === "__exit");
2861 * Test to see if the mode is one where this mode
2862 * is entered for this token only and automatically
2863 * leaves immediately afterwoods.
2864 * @param string mode Mode to test.
2865 * @return boolean True if this is the exit mode.
2868 WYMeditor.Lexer.prototype._isSpecialMode = function(mode)
2870 return (mode.substring(0,1) == "_");
2874 * Strips the magic underscore marking single token
2876 * @param string mode Mode to decode.
2877 * @return string Underlying mode name.
2880 WYMeditor.Lexer.prototype._decodeSpecial = function(mode)
2882 return mode.substring(1);
2886 * Calls the parser method named after the current
2887 * mode. Empty content will be ignored. The lexer
2888 * has a parser handler for each mode in the lexer.
2889 * @param string content Text parsed.
2890 * @param boolean is_match Token is recognised rather
2891 * than unparsed data.
2894 WYMeditor.Lexer.prototype._invokeParser = function(content, is_match)
2897 if (!/ +/.test(content) && ((content === '') || (content == false))) {
2900 var current = this._mode.getCurrent();
2901 var handler = this._mode_handlers[current];
2903 eval('result = this._parser.' + handler + '(content, is_match);');
2908 * Tries to match a chunk of text and if successful
2909 * removes the recognised chunk and any leading
2910 * unparsed data. Empty strings will not be matched.
2911 * @param string raw The subject to parse. This is the
2912 * content that will be eaten.
2913 * @return array/boolean Three item list of unparsed
2914 * content followed by the
2915 * recognised token and finally the
2916 * action the parser is to take.
2917 * True if no match, false if there
2918 * is a parsing error.
2921 WYMeditor.Lexer.prototype._reduce = function(raw)
2923 var matched = this._regexes[this._mode.getCurrent()].match(raw);
2924 var match = matched[1];
2925 var action = matched[0];
2927 var unparsed_character_count = raw.indexOf(match);
2928 var unparsed = raw.substr(0, unparsed_character_count);
2929 raw = raw.substring(unparsed_character_count + match.length);
2930 return [raw, unparsed, match, action];
2938 * This are the rules for breaking the XHTML code into events
2939 * handled by the provided parser.
2941 * @author Marcus Baker (http://lastcraft.com)
2942 * @author Bermi Ferrer (http://bermi.org)
2944 WYMeditor.XhtmlLexer = function(parser)
2946 jQuery.extend(this, new WYMeditor.Lexer(parser, 'Text'));
2948 this.mapHandler('Text', 'Text');
2958 WYMeditor.XhtmlLexer.prototype.init = function()
2962 WYMeditor.XhtmlLexer.prototype.addTokens = function()
2964 this.addCommentTokens('Text');
2965 this.addScriptTokens('Text');
2966 this.addCssTokens('Text');
2967 this.addTagTokens('Text');
2970 WYMeditor.XhtmlLexer.prototype.addCommentTokens = function(scope)
2972 this.addEntryPattern("<!--", scope, 'Comment');
2973 this.addExitPattern("-->", 'Comment');
2976 WYMeditor.XhtmlLexer.prototype.addScriptTokens = function(scope)
2978 this.addEntryPattern("<script", scope, 'Script');
2979 this.addExitPattern("</script>", 'Script');
2982 WYMeditor.XhtmlLexer.prototype.addCssTokens = function(scope)
2984 this.addEntryPattern("<style", scope, 'Css');
2985 this.addExitPattern("</style>", 'Css');
2988 WYMeditor.XhtmlLexer.prototype.addTagTokens = function(scope)
2990 this.addSpecialPattern("<\\s*[a-z0-9:\-]+\\s*>", scope, 'OpeningTag');
2991 this.addEntryPattern("<[a-z0-9:\-]+"+'[\\\/ \\\>]+', scope, 'OpeningTag');
2992 this.addInTagDeclarationTokens('OpeningTag');
2994 this.addSpecialPattern("</\\s*[a-z0-9:\-]+\\s*>", scope, 'ClosingTag');
2998 WYMeditor.XhtmlLexer.prototype.addInTagDeclarationTokens = function(scope)
3000 this.addSpecialPattern('\\s+', scope, 'Ignore');
3002 this.addAttributeTokens(scope);
3004 this.addExitPattern('/>', scope);
3005 this.addExitPattern('>', scope);
3009 WYMeditor.XhtmlLexer.prototype.addAttributeTokens = function(scope)
3011 this.addSpecialPattern("\\s*[a-z-_0-9]*:?[a-z-_0-9]+\\s*(?=\=)\\s*", scope, 'TagAttributes');
3013 this.addEntryPattern('=\\s*"', scope, 'DoubleQuotedAttribute');
3014 this.addPattern("\\\\\"", 'DoubleQuotedAttribute');
3015 this.addExitPattern('"', 'DoubleQuotedAttribute');
3017 this.addEntryPattern("=\\s*'", scope, 'SingleQuotedAttribute');
3018 this.addPattern("\\\\'", 'SingleQuotedAttribute');
3019 this.addExitPattern("'", 'SingleQuotedAttribute');
3021 this.addSpecialPattern('=\\s*[^>\\s]*', scope, 'UnquotedAttribute');
3029 * This XHTML parser will trigger the events available on on
3030 * current SaxListener
3032 * @author Bermi Ferrer (http://bermi.org)
3034 WYMeditor.XhtmlParser = function(Listener, mode)
3036 var mode = mode || 'Text';
3037 this._Lexer = new WYMeditor.XhtmlLexer(this);
3038 this._Listener = Listener;
3041 this._last_match = '';
3042 this._current_match = '';
3047 WYMeditor.XhtmlParser.prototype.parse = function(raw)
3049 this._Lexer.parse(this.beforeParsing(raw));
3050 return this.afterParsing(this._Listener.getResult());
3053 WYMeditor.XhtmlParser.prototype.beforeParsing = function(raw)
3055 if(raw.match(/class="MsoNormal"/) || raw.match(/ns = "urn:schemas-microsoft-com/)){
3056 // Usefull for cleaning up content pasted from other sources (MSWord)
3057 this._Listener.avoidStylingTagsAndAttributes();
3059 return this._Listener.beforeParsing(raw);
3062 WYMeditor.XhtmlParser.prototype.afterParsing = function(parsed)
3064 if(this._Listener._avoiding_tags_implicitly){
3065 this._Listener.allowStylingTagsAndAttributes();
3067 return this._Listener.afterParsing(parsed);
3071 WYMeditor.XhtmlParser.prototype.Ignore = function(match, state)
3076 WYMeditor.XhtmlParser.prototype.Text = function(text)
3078 this._Listener.addContent(text);
3082 WYMeditor.XhtmlParser.prototype.Comment = function(match, status)
3084 return this._addNonTagBlock(match, status, 'addComment');
3087 WYMeditor.XhtmlParser.prototype.Script = function(match, status)
3089 return this._addNonTagBlock(match, status, 'addScript');
3092 WYMeditor.XhtmlParser.prototype.Css = function(match, status)
3094 return this._addNonTagBlock(match, status, 'addCss');
3097 WYMeditor.XhtmlParser.prototype._addNonTagBlock = function(match, state, type)
3100 case WYMeditor.LEXER_ENTER:
3101 this._non_tag = match;
3103 case WYMeditor.LEXER_UNMATCHED:
3104 this._non_tag += match;
3106 case WYMeditor.LEXER_EXIT:
3109 this._Listener.addComment(this._non_tag+match);
3112 this._Listener.addScript(this._non_tag+match);
3115 this._Listener.addCss(this._non_tag+match);
3122 WYMeditor.XhtmlParser.prototype.OpeningTag = function(match, state)
3125 case WYMeditor.LEXER_ENTER:
3126 this._tag = this.normalizeTag(match);
3127 this._tag_attributes = {};
3129 case WYMeditor.LEXER_SPECIAL:
3130 this._callOpenTagListener(this.normalizeTag(match));
3132 case WYMeditor.LEXER_EXIT:
3133 this._callOpenTagListener(this._tag, this._tag_attributes);
3138 WYMeditor.XhtmlParser.prototype.ClosingTag = function(match, state)
3140 this._callCloseTagListener(this.normalizeTag(match));
3144 WYMeditor.XhtmlParser.prototype._callOpenTagListener = function(tag, attributes)
3146 var attributes = attributes || {};
3147 this.autoCloseUnclosedBeforeNewOpening(tag);
3149 if(this._Listener.isBlockTag(tag)){
3150 this._Listener._tag_stack.push(tag);
3151 this._Listener.fixNestingBeforeOpeningBlockTag(tag, attributes);
3152 this._Listener.openBlockTag(tag, attributes);
3153 this._increaseOpenTagCounter(tag);
3154 }else if(this._Listener.isInlineTag(tag)){
3155 this._Listener.inlineTag(tag, attributes);
3157 this._Listener.openUnknownTag(tag, attributes);
3158 this._increaseOpenTagCounter(tag);
3160 this._Listener.last_tag = tag;
3161 this._Listener.last_tag_opened = true;
3162 this._Listener.last_tag_attributes = attributes;
3165 WYMeditor.XhtmlParser.prototype._callCloseTagListener = function(tag)
3167 if(this._decreaseOpenTagCounter(tag)){
3168 this.autoCloseUnclosedBeforeTagClosing(tag);
3170 if(this._Listener.isBlockTag(tag)){
3171 var expected_tag = this._Listener._tag_stack.pop();
3172 if(expected_tag == false){
3174 }else if(expected_tag != tag){
3177 this._Listener.closeBlockTag(tag);
3179 this._Listener.closeUnknownTag(tag);
3182 this._Listener.closeUnopenedTag(tag);
3184 this._Listener.last_tag = tag;
3185 this._Listener.last_tag_opened = false;
3188 WYMeditor.XhtmlParser.prototype._increaseOpenTagCounter = function(tag)
3190 this._Listener._open_tags[tag] = this._Listener._open_tags[tag] || 0;
3191 this._Listener._open_tags[tag]++;
3194 WYMeditor.XhtmlParser.prototype._decreaseOpenTagCounter = function(tag)
3196 if(this._Listener._open_tags[tag]){
3197 this._Listener._open_tags[tag]--;
3198 if(this._Listener._open_tags[tag] == 0){
3199 this._Listener._open_tags[tag] = undefined;
3206 WYMeditor.XhtmlParser.prototype.autoCloseUnclosedBeforeNewOpening = function(new_tag)
3208 this._autoCloseUnclosed(new_tag, false);
3211 WYMeditor.XhtmlParser.prototype.autoCloseUnclosedBeforeTagClosing = function(tag)
3213 this._autoCloseUnclosed(tag, true);
3216 WYMeditor.XhtmlParser.prototype._autoCloseUnclosed = function(new_tag, closing)
3218 var closing = closing || false;
3219 if(this._Listener._open_tags){
3220 for (var tag in this._Listener._open_tags) {
3221 var counter = this._Listener._open_tags[tag];
3222 if(counter > 0 && this._Listener.shouldCloseTagAutomatically(tag, new_tag, closing)){
3223 this._callCloseTagListener(tag, true);
3229 WYMeditor.XhtmlParser.prototype.getTagReplacements = function()
3231 return this._Listener.getTagReplacements();
3234 WYMeditor.XhtmlParser.prototype.normalizeTag = function(tag)
3236 tag = tag.replace(/^([\s<\/>]*)|([\s<\/>]*)$/gm,'').toLowerCase();
3237 var tags = this._Listener.getTagReplacements();
3244 WYMeditor.XhtmlParser.prototype.TagAttributes = function(match, state)
3246 if(WYMeditor.LEXER_SPECIAL == state){
3247 this._current_attribute = match;
3252 WYMeditor.XhtmlParser.prototype.DoubleQuotedAttribute = function(match, state)
3254 if(WYMeditor.LEXER_UNMATCHED == state){
3255 this._tag_attributes[this._current_attribute] = match;
3260 WYMeditor.XhtmlParser.prototype.SingleQuotedAttribute = function(match, state)
3262 if(WYMeditor.LEXER_UNMATCHED == state){
3263 this._tag_attributes[this._current_attribute] = match;
3268 WYMeditor.XhtmlParser.prototype.UnquotedAttribute = function(match, state)
3270 this._tag_attributes[this._current_attribute] = match.replace(/^=/,'');
3279 * @author Bermi Ferrer (http://bermi.org)
3281 WYMeditor.XhtmlSaxListener = function()
3284 this.helper = new WYMeditor.XmlHelper();
3285 this._open_tags = {};
3286 this.validator = WYMeditor.XhtmlValidator;
3287 this._tag_stack = [];
3288 this.avoided_tags = [];
3291 ' ':' ','¡':'¡','¢':'¢',
3292 '£':'£','¤':'¤','¥':'¥',
3293 '¦':'¦','§':'§','¨':'¨',
3294 '©':'©','ª':'ª','«':'«',
3295 '¬':'¬','­':'­','®':'®',
3296 '¯':'¯','°':'°','±':'±',
3297 '²':'²','³':'³','´':'´',
3298 'µ':'µ','¶':'¶','·':'·',
3299 '¸':'¸','¹':'¹','º':'º',
3300 '»':'»','¼':'¼','½':'½',
3301 '¾':'¾','¿':'¿','À':'À',
3302 'Á':'Á','Â':'Â','Ã':'Ã',
3303 'Ä':'Ä','Å':'Å','Æ':'Æ',
3304 'Ç':'Ç','È':'È','É':'É',
3305 'Ê':'Ê','Ë':'Ë','Ì':'Ì',
3306 'Í':'Í','Î':'Î','Ï':'Ï',
3307 'Ð':'Ð','Ñ':'Ñ','Ò':'Ò',
3308 'Ó':'Ó','Ô':'Ô','Õ':'Õ',
3309 'Ö':'Ö','×':'×','Ø':'Ø',
3310 'Ù':'Ù','Ú':'Ú','Û':'Û',
3311 'Ü':'Ü','Ý':'Ý','Þ':'Þ',
3312 'ß':'ß','à':'à','á':'á',
3313 'â':'â','ã':'ã','ä':'ä',
3314 'å':'å','æ':'æ','ç':'ç',
3315 'è':'è','é':'é','ê':'ê',
3316 'ë':'ë','ì':'ì','í':'í',
3317 'î':'î','ï':'ï','ð':'ð',
3318 'ñ':'ñ','ò':'ò','ó':'ó',
3319 'ô':'ô','õ':'õ','ö':'ö',
3320 '÷':'÷','ø':'ø','ù':'ù',
3321 'ú':'ú','û':'û','ü':'ü',
3322 'ý':'ý','þ':'þ','ÿ':'ÿ',
3323 'Œ':'Œ','œ':'œ','Š':'Š',
3324 'š':'š','Ÿ':'Ÿ','ƒ':'ƒ',
3325 'ˆ':'ˆ','˜':'˜','Α':'Α',
3326 'Β':'Β','Γ':'Γ','Δ':'Δ',
3327 'Ε':'Ε','Ζ':'Ζ','Η':'Η',
3328 'Θ':'Θ','Ι':'Ι','Κ':'Κ',
3329 'Λ':'Λ','Μ':'Μ','Ν':'Ν',
3330 'Ξ':'Ξ','Ο':'Ο','Π':'Π',
3331 'Ρ':'Ρ','Σ':'Σ','Τ':'Τ',
3332 'Υ':'Υ','Φ':'Φ','Χ':'Χ',
3333 'Ψ':'Ψ','Ω':'Ω','α':'α',
3334 'β':'β','γ':'γ','δ':'δ',
3335 'ε':'ε','ζ':'ζ','η':'η',
3336 'θ':'θ','ι':'ι','κ':'κ',
3337 'λ':'λ','μ':'μ','ν':'ν',
3338 'ξ':'ξ','ο':'ο','π':'π',
3339 'ρ':'ρ','ς':'ς','σ':'σ',
3340 'τ':'τ','υ':'υ','φ':'φ',
3341 'χ':'χ','ψ':'ψ','ω':'ω',
3342 'ϑ':'ϑ','ϒ':'ϒ','ϖ':'ϖ',
3343 ' ':' ',' ':' ',' ':' ',
3344 '‌':'‌','‍':'‍','‎':'‎',
3345 '‏':'‏','–':'–','—':'—',
3346 '‘':'‘','’':'’','‚':'‚',
3347 '“':'“','”':'”','„':'„',
3348 '†':'†','‡':'‡','•':'•',
3349 '…':'…','‰':'‰','′':'′',
3350 '″':'″','‹':'‹','›':'›',
3351 '‾':'‾','⁄':'⁄','€':'€',
3352 'ℑ':'ℑ','℘':'℘','ℜ':'ℜ',
3353 '™':'™','ℵ':'ℵ','←':'←',
3354 '↑':'↑','→':'→','↓':'↓',
3355 '↔':'↔','↵':'↵','⇐':'⇐',
3356 '⇑':'⇑','⇒':'⇒','⇓':'⇓',
3357 '⇔':'⇔','∀':'∀','∂':'∂',
3358 '∃':'∃','∅':'∅','∇':'∇',
3359 '∈':'∈','∉':'∉','∋':'∋',
3360 '∏':'∏','∑':'∑','−':'−',
3361 '∗':'∗','√':'√','∝':'∝',
3362 '∞':'∞','∠':'∠','∧':'∧',
3363 '∨':'∨','∩':'∩','∪':'∪',
3364 '∫':'∫','∴':'∴','∼':'∼',
3365 '≅':'≅','≈':'≈','≠':'≠',
3366 '≡':'≡','≤':'≤','≥':'≥',
3367 '⊂':'⊂','⊃':'⊃','⊄':'⊄',
3368 '⊆':'⊆','⊇':'⊇','⊕':'⊕',
3369 '⊗':'⊗','⊥':'⊥','⋅':'⋅',
3370 '⌈':'⌈','⌉':'⌉','⌊':'⌊',
3371 '⌋':'⌋','⟨':'〈','⟩':'〉',
3372 '◊':'◊','♠':'♠','♣':'♣',
3373 '♥':'♥','♦':'♦'};
3375 this.block_tags = ["a", "abbr", "acronym", "address", "area", "b",
3376 "base", "bdo", "big", "blockquote", "body", "button",
3377 "caption", "cite", "code", "col", "colgroup", "dd", "del", "div",
3378 "dfn", "dl", "dt", "em", "fieldset", "form", "head", "h1", "h2",
3379 "h3", "h4", "h5", "h6", "html", "i", "ins",
3380 "kbd", "label", "legend", "li", "map", "noscript",
3381 "object", "ol", "optgroup", "option", "p", "param", "pre", "q",
3382 "samp", "script", "select", "small", "span", "strong", "style",
3383 "sub", "sup", "table", "tbody", "td", "textarea", "tfoot", "th",
3384 "thead", "title", "tr", "tt", "ul", "var", "extends"];
3387 this.inline_tags = ["br", "hr", "img", "input"];
3392 WYMeditor.XhtmlSaxListener.prototype.shouldCloseTagAutomatically = function(tag, now_on_tag, closing)
3394 var closing = closing || false;
3396 if((closing && now_on_tag == 'tr') || (!closing && now_on_tag == 'td')){
3400 if(tag == 'option'){
3401 if((closing && now_on_tag == 'select') || (!closing && now_on_tag == 'option')){
3408 WYMeditor.XhtmlSaxListener.prototype.beforeParsing = function(raw)
3414 WYMeditor.XhtmlSaxListener.prototype.afterParsing = function(xhtml)
3416 xhtml = this.replaceNamedEntities(xhtml);
3417 xhtml = this.joinRepeatedEntities(xhtml);
3418 xhtml = this.removeEmptyTags(xhtml);
3419 xhtml = this.removeBrInPre(xhtml);
3423 WYMeditor.XhtmlSaxListener.prototype.replaceNamedEntities = function(xhtml)
3425 for (var entity in this.entities) {
3426 xhtml = xhtml.replace(new RegExp(entity, 'g'), this.entities[entity]);
3431 WYMeditor.XhtmlSaxListener.prototype.joinRepeatedEntities = function(xhtml)
3433 var tags = 'em|strong|sub|sup|acronym|pre|del|address';
3434 return xhtml.replace(new RegExp('<\/('+tags+')><\\1>' ,''),'').
3435 replace(new RegExp('(\s*<('+tags+')>\s*){2}(.*)(\s*<\/\\2>\s*){2}' ,''),'<\$2>\$3<\$2>');
3438 WYMeditor.XhtmlSaxListener.prototype.removeEmptyTags = function(xhtml)
3440 return xhtml.replace(new RegExp('<('+this.block_tags.join("|").replace(/\|td/,'').replace(/\|th/, '')+')>(<br \/>| | |\\s)*<\/\\1>' ,'g'),'');
3443 WYMeditor.XhtmlSaxListener.prototype.removeBrInPre = function(xhtml)
3445 var matches = xhtml.match(new RegExp('<pre[^>]*>(.*?)<\/pre>','gmi'));
3447 for(var i=0; i<matches.length; i++) {
3448 xhtml = xhtml.replace(matches[i], matches[i].replace(new RegExp('<br \/>', 'g'), String.fromCharCode(13,10)));
3454 WYMeditor.XhtmlSaxListener.prototype.getResult = function()
3459 WYMeditor.XhtmlSaxListener.prototype.getTagReplacements = function()
3461 return {'b':'strong', 'i':'em'};
3464 WYMeditor.XhtmlSaxListener.prototype.addContent = function(text)
3466 this.output += text;
3469 WYMeditor.XhtmlSaxListener.prototype.addComment = function(text)
3471 if(this.remove_comments){
3472 this.output += text;
3476 WYMeditor.XhtmlSaxListener.prototype.addScript = function(text)
3478 if(!this.remove_scripts){
3479 this.output += text;
3483 WYMeditor.XhtmlSaxListener.prototype.addCss = function(text)
3485 if(!this.remove_embeded_styles){
3486 this.output += text;
3490 WYMeditor.XhtmlSaxListener.prototype.openBlockTag = function(tag, attributes)
3492 this.output += this.helper.tag(tag, this.validator.getValidTagAttributes(tag, attributes), true);
3495 WYMeditor.XhtmlSaxListener.prototype.inlineTag = function(tag, attributes)
3497 this.output += this.helper.tag(tag, this.validator.getValidTagAttributes(tag, attributes));
3500 WYMeditor.XhtmlSaxListener.prototype.openUnknownTag = function(tag, attributes)
3502 //this.output += this.helper.tag(tag, attributes, true);
3505 WYMeditor.XhtmlSaxListener.prototype.closeBlockTag = function(tag)
3507 this.output = this.output.replace(/<br \/>$/, '')+this._getClosingTagContent('before', tag)+"</"+tag+">"+this._getClosingTagContent('after', tag);
3510 WYMeditor.XhtmlSaxListener.prototype.closeUnknownTag = function(tag)
3512 //this.output += "</"+tag+">";
3515 WYMeditor.XhtmlSaxListener.prototype.closeUnopenedTag = function(tag)
3517 this.output += "</"+tag+">";
3520 WYMeditor.XhtmlSaxListener.prototype.avoidStylingTagsAndAttributes = function()
3522 this.avoided_tags = ['div','span'];
3523 this.validator.skiped_attributes = ['style'];
3524 this.validator.skiped_attribute_values = ['MsoNormal','main1']; // MS Word attributes for class
3525 this._avoiding_tags_implicitly = true;
3528 WYMeditor.XhtmlSaxListener.prototype.allowStylingTagsAndAttributes = function()
3530 this.avoided_tags = [];
3531 this.validator.skiped_attributes = [];
3532 this.validator.skiped_attribute_values = [];
3533 this._avoiding_tags_implicitly = false;
3536 WYMeditor.XhtmlSaxListener.prototype.isBlockTag = function(tag)
3538 return !WYMeditor.Helper.contains(this.avoided_tags, tag) && WYMeditor.Helper.contains(this.block_tags, tag);
3541 WYMeditor.XhtmlSaxListener.prototype.isInlineTag = function(tag)
3543 return !WYMeditor.Helper.contains(this.avoided_tags, tag) && WYMeditor.Helper.contains(this.inline_tags, tag);
3546 WYMeditor.XhtmlSaxListener.prototype.insertContentAfterClosingTag = function(tag, content)
3548 this._insertContentWhenClosingTag('after', tag, content);
3551 WYMeditor.XhtmlSaxListener.prototype.insertContentBeforeClosingTag = function(tag, content)
3553 this._insertContentWhenClosingTag('before', tag, content);
3556 WYMeditor.XhtmlSaxListener.prototype.fixNestingBeforeOpeningBlockTag = function(tag, attributes)
3558 if(tag != 'li' && (tag == 'ul' || tag == 'ol') && this.last_tag && !this.last_tag_opened && this.last_tag == 'li'){
3559 this.output = this.output.replace(/<\/li>$/, '');
3560 this.insertContentAfterClosingTag(tag, '</li>');
3564 WYMeditor.XhtmlSaxListener.prototype._insertContentWhenClosingTag = function(position, tag, content)
3566 if(!this['_insert_'+position+'_closing']){
3567 this['_insert_'+position+'_closing'] = [];
3569 if(!this['_insert_'+position+'_closing'][tag]){
3570 this['_insert_'+position+'_closing'][tag] = [];
3572 this['_insert_'+position+'_closing'][tag].push(content);
3575 WYMeditor.XhtmlSaxListener.prototype._getClosingTagContent = function(position, tag)
3577 if( this['_insert_'+position+'_closing'] &&
3578 this['_insert_'+position+'_closing'][tag] &&
3579 this['_insert_'+position+'_closing'][tag].length > 0){
3580 return this['_insert_'+position+'_closing'][tag].pop();
3586 /********** CSS PARSER **********/
3589 WYMeditor.WymCssLexer = function(parser, only_wym_blocks)
3591 var only_wym_blocks = (typeof only_wym_blocks == 'undefined' ? true : only_wym_blocks);
3593 jQuery.extend(this, new WYMeditor.Lexer(parser, (only_wym_blocks?'Ignore':'WymCss')));
3595 this.mapHandler('WymCss', 'Ignore');
3597 if(only_wym_blocks == true){
3598 this.addEntryPattern("/\\\x2a[<\\s]*WYMeditor[>\\s]*\\\x2a/", 'Ignore', 'WymCss');
3599 this.addExitPattern("/\\\x2a[<\/\\s]*WYMeditor[>\\s]*\\\x2a/", 'WymCss');
3602 this.addSpecialPattern("[\\sa-z1-6]*\\\x2e[a-z-_0-9]+", 'WymCss', 'WymCssStyleDeclaration');
3604 this.addEntryPattern("/\\\x2a", 'WymCss', 'WymCssComment');
3605 this.addExitPattern("\\\x2a/", 'WymCssComment');
3607 this.addEntryPattern("\x7b", 'WymCss', 'WymCssStyle');
3608 this.addExitPattern("\x7d", 'WymCssStyle');
3610 this.addEntryPattern("/\\\x2a", 'WymCssStyle', 'WymCssFeedbackStyle');
3611 this.addExitPattern("\\\x2a/", 'WymCssFeedbackStyle');
3616 WYMeditor.WymCssParser = function()
3618 this._in_style = false;
3619 this._has_title = false;
3620 this.only_wym_blocks = true;
3621 this.css_settings = {'classesItems':[], 'editorStyles':[], 'dialogStyles':[]};
3625 WYMeditor.WymCssParser.prototype.parse = function(raw, only_wym_blocks)
3627 var only_wym_blocks = (typeof only_wym_blocks == 'undefined' ? this.only_wym_blocks : only_wym_blocks);
3628 this._Lexer = new WYMeditor.WymCssLexer(this, only_wym_blocks);
3629 this._Lexer.parse(raw);
3632 WYMeditor.WymCssParser.prototype.Ignore = function(match, state)
3637 WYMeditor.WymCssParser.prototype.WymCssComment = function(text, status)
3639 if(text.match(/end[a-z0-9\s]*wym[a-z0-9\s]*/mi)){
3642 if(status == WYMeditor.LEXER_UNMATCHED){
3643 if(!this._in_style){
3644 this._has_title = true;
3645 this._current_item = {'title':WYMeditor.Helper.trim(text)};
3647 if(this._current_item[this._current_element]){
3648 if(!this._current_item[this._current_element].expressions){
3649 this._current_item[this._current_element].expressions = [text];
3651 this._current_item[this._current_element].expressions.push(text);
3655 this._in_style = true;
3660 WYMeditor.WymCssParser.prototype.WymCssStyle = function(match, status)
3662 if(status == WYMeditor.LEXER_UNMATCHED){
3663 match = WYMeditor.Helper.trim(match);
3665 this._current_item[this._current_element].style = match;
3667 }else if (status == WYMeditor.LEXER_EXIT){
3668 this._in_style = false;
3669 this._has_title = false;
3670 this.addStyleSetting(this._current_item);
3675 WYMeditor.WymCssParser.prototype.WymCssFeedbackStyle = function(match, status)
3677 if(status == WYMeditor.LEXER_UNMATCHED){
3678 this._current_item[this._current_element].feedback_style = match.replace(/^([\s\/\*]*)|([\s\/\*]*)$/gm,'');
3683 WYMeditor.WymCssParser.prototype.WymCssStyleDeclaration = function(match)
3685 match = match.replace(/^([\s\.]*)|([\s\.*]*)$/gm, '');
3688 if(match.indexOf('.') > 0){
3689 var parts = match.split('.');
3690 this._current_element = parts[1];
3693 this._current_element = match;
3696 if(!this._has_title){
3697 this._current_item = {'title':(!tag?'':tag.toUpperCase()+': ')+this._current_element};
3698 this._has_title = true;
3701 if(!this._current_item[this._current_element]){
3702 this._current_item[this._current_element] = {'name':this._current_element};
3705 if(!this._current_item[this._current_element].tags){
3706 this._current_item[this._current_element].tags = [tag];
3708 this._current_item[this._current_element].tags.push(tag);
3714 WYMeditor.WymCssParser.prototype.addStyleSetting = function(style_details)
3716 for (var name in style_details){
3717 var details = style_details[name];
3718 if(typeof details == 'object' && name != 'title'){
3720 this.css_settings.classesItems.push({
3721 'name': WYMeditor.Helper.trim(details.name),
3722 'title': style_details.title,
3723 'expr' : WYMeditor.Helper.trim((details.expressions||details.tags).join(', '))
3725 if(details.feedback_style){
3726 this.css_settings.editorStyles.push({
3727 'name': '.'+ WYMeditor.Helper.trim(details.name),
3728 'css': details.feedback_style
3732 this.css_settings.dialogStyles.push({
3733 'name': '.'+ WYMeditor.Helper.trim(details.name),
3734 'css': details.style
3741 /********** HELPERS **********/
3743 // Returns true if it is a text node with whitespaces only
3744 jQuery.fn.isPhantomNode = function() {
3745 if (this[0].nodeType == 3)
3746 return !(/[^\t\n\r ]/.test(this[0].data));
3751 WYMeditor.isPhantomNode = function(n) {
3752 if (n.nodeType == 3)
3753 return !(/[^\t\n\r ]/.test(n.data));
3758 WYMeditor.isPhantomString = function(str) {
3759 return !(/[^\t\n\r ]/.test(str));
3762 // Returns the Parents or the node itself
3763 // jqexpr = a jQuery expression
3764 jQuery.fn.parentsOrSelf = function(jqexpr) {
3767 if (n[0].nodeType == 3)
3768 n = n.parents().slice(0,1);
3770 // if (n.is(jqexpr)) // XXX should work, but doesn't (probably a jQuery bug)
3771 if (n.filter(jqexpr).size() == 1)
3774 return n.parents(jqexpr).slice(0,1);
3777 // String & array helpers
3779 WYMeditor.Helper = {
3781 //replace all instances of 'old' by 'rep' in 'str' string
3782 replaceAll: function(str, old, rep) {
3783 var rExp = new RegExp(old, "g");
3784 return(str.replace(rExp, rep));
3787 //insert 'inserted' at position 'pos' in 'str' string
3788 insertAt: function(str, inserted, pos) {
3789 return(str.substr(0,pos) + inserted + str.substring(pos));
3793 trim: function(str) {
3794 return str.replace(/^(\s*)|(\s*)$/gm,'');
3797 //return true if 'arr' array contains 'elem', or false
3798 contains: function(arr, elem) {
3799 for (var i = 0; i < arr.length; i++) {
3800 if (arr[i] === elem) return true;
3805 //return 'item' position in 'arr' array, or -1
3806 indexOf: function(arr, item) {
3808 for(var i = 0; i < arr.length; i++) {
3809 if (arr[i] == item) {
3817 //return 'item' object in 'arr' array, checking its 'name' property, or null
3818 findByName: function(arr, name) {
3819 for(var i = 0; i < arr.length; i++) {
3821 if(item.name == name) return(item);
3829 * WYMeditor : what you see is What You Mean web-based editor
3830 * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/
3831 * Dual licensed under the MIT (MIT-license.txt)
3832 * and GPL (GPL-license.txt) licenses.
3834 * For further information visit:
3835 * http://www.wymeditor.org/
3838 * jquery.wymeditor.explorer.js
3839 * MSIE specific class and functions.
3840 * See the documentation for more info.
3843 * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg)
3844 * Bermi Ferrer (wymeditor a-t bermi dotorg)
3845 * Frédéric Palluel-Lafleur (fpalluel a-t gmail dotcom)
3846 * Jonatan Lundin (jonatan.lundin _at_ gmail.com)
3849 WYMeditor.WymClassExplorer = function(wym) {
3852 this._class = "className";
3853 this._newLine = "\r\n";
3857 WYMeditor.WymClassExplorer.prototype.initIframe = function(iframe) {
3859 //This function is executed twice, though it is called once!
3860 //But MSIE needs that, otherwise designMode won't work.
3863 this._iframe = iframe;
3864 this._doc = iframe.contentWindow.document;
3866 //add css rules from options
3867 var styles = this._doc.styleSheets[0];
3868 var aCss = eval(this._options.editorStyles);
3870 this.addCssRules(this._doc, aCss);
3872 this._doc.title = this._wym._index;
3874 //set the text direction
3875 jQuery('html', this._doc).attr('dir', this._options.direction);
3878 jQuery(this._doc.body).html(this._wym._html);
3883 this._doc.body.onfocus = function()
3884 {wym._doc.designMode = "on"; wym._doc = iframe.contentWindow.document;};
3885 this._doc.onbeforedeactivate = function() {wym.saveCaret();};
3886 this._doc.onkeyup = function() {
3890 this._doc.onclick = function() {wym.saveCaret();};
3892 this._doc.body.onbeforepaste = function() {
3893 wym._iframe.contentWindow.event.returnValue = false;
3896 this._doc.body.onpaste = function() {
3897 wym._iframe.contentWindow.event.returnValue = false;
3898 wym.paste(window.clipboardData.getData("Text"));
3901 //callback can't be executed twice, so we check
3902 if(this._initialized) {
3904 //pre-bind functions
3905 if(jQuery.isFunction(this._options.preBind)) this._options.preBind(this);
3907 //bind external events
3908 this._wym.bindEvents();
3910 //post-init functions
3911 if(jQuery.isFunction(this._options.postInit)) this._options.postInit(this);
3913 //add event listeners to doc elements, e.g. images
3917 this._initialized = true;
3920 this._doc.designMode="on";
3922 // (bermi's note) noticed when running unit tests on IE6
3923 // Is this really needed, it trigger an unexisting property on IE6
3924 this._doc = iframe.contentWindow.document;
3928 WYMeditor.WymClassExplorer.prototype._exec = function(cmd,param) {
3932 case WYMeditor.INDENT: case WYMeditor.OUTDENT:
3934 var container = this.findUp(this.container(), WYMeditor.LI);
3936 var ancestor = container.parentNode.parentNode;
3937 if(container.parentNode.childNodes.length>1
3938 || ancestor.tagName.toLowerCase() == WYMeditor.OL
3939 || ancestor.tagName.toLowerCase() == WYMeditor.UL)
3940 this._doc.execCommand(cmd);
3944 if(param) this._doc.execCommand(cmd,false,param);
3945 else this._doc.execCommand(cmd);
3952 WYMeditor.WymClassExplorer.prototype.selected = function() {
3954 var caretPos = this._iframe.contentWindow.document.caretPos;
3955 if(caretPos!=null) {
3956 if(caretPos.parentElement!=undefined)
3957 return(caretPos.parentElement());
3961 WYMeditor.WymClassExplorer.prototype.saveCaret = function() {
3963 this._doc.caretPos = this._doc.selection.createRange();
3966 WYMeditor.WymClassExplorer.prototype.addCssRule = function(styles, oCss) {
3968 styles.addRule(oCss.name, oCss.css);
3971 WYMeditor.WymClassExplorer.prototype.insert = function(html) {
3973 // Get the current selection
3974 var range = this._doc.selection.createRange();
3976 // Check if the current selection is inside the editor
3977 if ( jQuery(range.parentElement()).parents( this._options.iframeBodySelector ).is('*') ) {
3979 // Overwrite selection with provided html
3980 range.pasteHTML(html);
3983 // Fall back to the internal paste function if there's no selection
3988 WYMeditor.WymClassExplorer.prototype.wrap = function(left, right) {
3990 // Get the current selection
3991 var range = this._doc.selection.createRange();
3993 // Check if the current selection is inside the editor
3994 if ( jQuery(range.parentElement()).parents( this._options.iframeBodySelector ).is('*') ) {
3996 // Overwrite selection with provided html
3997 range.pasteHTML(left + range.text + right);
4002 WYMeditor.WymClassExplorer.prototype.unwrap = function() {
4004 // Get the current selection
4005 var range = this._doc.selection.createRange();
4007 // Check if the current selection is inside the editor
4008 if ( jQuery(range.parentElement()).parents( this._options.iframeBodySelector ).is('*') ) {
4011 var text = range.text;
4012 this._exec( 'Cut' );
4013 range.pasteHTML( text );
4019 WYMeditor.WymClassExplorer.prototype.keyup = function() {
4020 this._selected_image = null;
4023 WYMeditor.WymClassExplorer.prototype.setFocusToNode = function(node) {
4024 var range = this._doc.selection.createRange();
4025 range.moveToElementText(node);
4026 range.collapse(false);
4027 range.move('character',-1);
4033 * WYMeditor : what you see is What You Mean web-based editor
4034 * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/
4035 * Dual licensed under the MIT (MIT-license.txt)
4036 * and GPL (GPL-license.txt) licenses.
4038 * For further information visit:
4039 * http://www.wymeditor.org/
4042 * jquery.wymeditor.mozilla.js
4043 * Gecko specific class and functions.
4044 * See the documentation for more info.
4047 * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg)
4048 * Volker Mische (vmx a-t gmx dotde)
4049 * Bermi Ferrer (wymeditor a-t bermi dotorg)
4050 * Frédéric Palluel-Lafleur (fpalluel a-t gmail dotcom)
4053 WYMeditor.WymClassMozilla = function(wym) {
4056 this._class = "class";
4057 this._newLine = "\n";
4060 WYMeditor.WymClassMozilla.prototype.initIframe = function(iframe) {
4062 this._iframe = iframe;
4063 this._doc = iframe.contentDocument;
4065 //add css rules from options
4067 var styles = this._doc.styleSheets[0];
4068 var aCss = eval(this._options.editorStyles);
4070 this.addCssRules(this._doc, aCss);
4072 this._doc.title = this._wym._index;
4074 //set the text direction
4075 jQuery('html', this._doc).attr('dir', this._options.direction);
4078 this.html(this._wym._html);
4081 this.enableDesignMode();
4083 //pre-bind functions
4084 if(jQuery.isFunction(this._options.preBind)) this._options.preBind(this);
4086 //bind external events
4087 this._wym.bindEvents();
4089 //bind editor keydown events
4090 jQuery(this._doc).bind("keydown", this.keydown);
4092 //bind editor keyup events
4093 jQuery(this._doc).bind("keyup", this.keyup);
4095 //bind editor focus events (used to reset designmode - Gecko bug)
4096 jQuery(this._doc).bind("focus", this.enableDesignMode);
4098 //post-init functions
4099 if(jQuery.isFunction(this._options.postInit)) this._options.postInit(this);
4101 //add event listeners to doc elements, e.g. images
4106 * @description Get/Set the html value
4108 WYMeditor.WymClassMozilla.prototype.html = function(html) {
4110 if(typeof html === 'string') {
4112 //disable designMode
4113 try { this._doc.designMode = "off"; } catch(e) { };
4115 //replace em by i and strong by bold
4116 //(designMode issue)
4117 html = html.replace(/<em(\b[^>]*)>/gi, "<i$1>")
4118 .replace(/<\/em>/gi, "</i>")
4119 .replace(/<strong(\b[^>]*)>/gi, "<b$1>")
4120 .replace(/<\/strong>/gi, "</b>");
4122 //update the html body
4123 jQuery(this._doc.body).html(html);
4125 //re-init designMode
4126 this.enableDesignMode();
4128 else return(jQuery(this._doc.body).html());
4131 WYMeditor.WymClassMozilla.prototype._exec = function(cmd,param) {
4133 if(!this.selected()) return(false);
4137 case WYMeditor.INDENT: case WYMeditor.OUTDENT:
4139 var focusNode = this.selected();
4140 var sel = this._iframe.contentWindow.getSelection();
4141 var anchorNode = sel.anchorNode;
4142 if(anchorNode.nodeName == "#text") anchorNode = anchorNode.parentNode;
4144 focusNode = this.findUp(focusNode, WYMeditor.BLOCKS);
4145 anchorNode = this.findUp(anchorNode, WYMeditor.BLOCKS);
4147 if(focusNode && focusNode == anchorNode
4148 && focusNode.tagName.toLowerCase() == WYMeditor.LI) {
4150 var ancestor = focusNode.parentNode.parentNode;
4152 if(focusNode.parentNode.childNodes.length>1
4153 || ancestor.tagName.toLowerCase() == WYMeditor.OL
4154 || ancestor.tagName.toLowerCase() == WYMeditor.UL)
4155 this._doc.execCommand(cmd,'',null);
4162 if(param) this._doc.execCommand(cmd,'',param);
4163 else this._doc.execCommand(cmd,'',null);
4166 //set to P if parent = BODY
4167 var container = this.selected();
4168 if(container.tagName.toLowerCase() == WYMeditor.BODY)
4169 this._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P);
4171 //add event handlers on doc elements
4177 * @description Returns the selected container
4179 WYMeditor.WymClassMozilla.prototype.selected = function() {
4181 var sel = this._iframe.contentWindow.getSelection();
4182 var node = sel.focusNode;
4184 if(node.nodeName == "#text") return(node.parentNode);
4186 } else return(null);
4189 WYMeditor.WymClassMozilla.prototype.addCssRule = function(styles, oCss) {
4191 styles.insertRule(oCss.name + " {" + oCss.css + "}",
4192 styles.cssRules.length);
4196 //keydown handler, mainly used for keyboard shortcuts
4197 WYMeditor.WymClassMozilla.prototype.keydown = function(evt) {
4200 var wym = WYMeditor.INSTANCES[this.title];
4201 var container = null;
4204 if(evt.keyCode == 66){
4206 wym._exec(WYMeditor.BOLD);
4209 if(evt.keyCode == 73){
4210 //CTRL+i => EMPHASIS
4211 wym._exec(WYMeditor.ITALIC);
4216 else if(evt.keyCode == 13) {
4219 container = wym.selected();
4220 if(container && container.tagName.toLowerCase() == WYMeditor.PRE) {
4221 evt.preventDefault();
4222 wym.insert('<p></p>');
4228 //keyup handler, mainly used for cleanups
4229 WYMeditor.WymClassMozilla.prototype.keyup = function(evt) {
4232 var wym = WYMeditor.INSTANCES[this.title];
4234 wym._selected_image = null;
4235 var container = null;
4237 if(evt.keyCode == 13 && !evt.shiftKey) {
4240 //cleanup <br><br> between paragraphs
4241 jQuery(wym._doc.body).children(WYMeditor.BR).remove();
4244 else if(evt.keyCode != 8
4245 && evt.keyCode != 17
4246 && evt.keyCode != 46
4247 && evt.keyCode != 224
4251 //NOT BACKSPACE, NOT DELETE, NOT CTRL, NOT COMMAND
4252 //text nodes replaced by P
4254 container = wym.selected();
4255 var name = container.tagName.toLowerCase();
4257 //fix forbidden main containers
4267 ) name = container.parentNode.tagName.toLowerCase();
4269 if(name == WYMeditor.BODY) wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P);
4273 WYMeditor.WymClassMozilla.prototype.enableDesignMode = function() {
4274 if(this.designMode == "off") {
4276 this.designMode = "on";
4277 this.execCommand("styleWithCSS", '', false);
4282 WYMeditor.WymClassMozilla.prototype.setFocusToNode = function(node) {
4283 var range = document.createRange();
4284 range.selectNode(node);
4285 var selected = this._iframe.contentWindow.getSelection();
4286 selected.addRange(range);
4287 selected.collapse(node, node.childNodes.length);
4288 this._iframe.contentWindow.focus();
4291 WYMeditor.WymClassMozilla.prototype.openBlockTag = function(tag, attributes)
4293 var attributes = this.validator.getValidTagAttributes(tag, attributes);
4295 // Handle Mozilla styled spans
4296 if(tag == 'span' && attributes.style){
4297 var new_tag = this.getTagForStyle(attributes.style);
4299 this._tag_stack.pop();
4301 this._tag_stack.push(new_tag);
4302 attributes.style = '';
4308 this.output += this.helper.tag(tag, attributes, true);
4311 WYMeditor.WymClassMozilla.prototype.getTagForStyle = function(style) {
4313 if(/bold/.test(style)) return 'strong';
4314 if(/italic/.test(style)) return 'em';
4315 if(/sub/.test(style)) return 'sub';
4316 if(/sub/.test(style)) return 'super';
4321 * WYMeditor : what you see is What You Mean web-based editor
4322 * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/
4323 * Dual licensed under the MIT (MIT-license.txt)
4324 * and GPL (GPL-license.txt) licenses.
4326 * For further information visit:
4327 * http://www.wymeditor.org/
4330 * jquery.wymeditor.opera.js
4331 * Opera specific class and functions.
4332 * See the documentation for more info.
4335 * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg)
4338 WYMeditor.WymClassOpera = function(wym) {
4341 this._class = "class";
4342 this._newLine = "\r\n";
4345 WYMeditor.WymClassOpera.prototype.initIframe = function(iframe) {
4347 this._iframe = iframe;
4348 this._doc = iframe.contentWindow.document;
4350 //add css rules from options
4351 var styles = this._doc.styleSheets[0];
4352 var aCss = eval(this._options.editorStyles);
4354 this.addCssRules(this._doc, aCss);
4356 this._doc.title = this._wym._index;
4358 //set the text direction
4359 jQuery('html', this._doc).attr('dir', this._options.direction);
4362 this._doc.designMode = "on";
4365 this.html(this._wym._html);
4367 //pre-bind functions
4368 if(jQuery.isFunction(this._options.preBind)) this._options.preBind(this);
4370 //bind external events
4371 this._wym.bindEvents();
4373 //bind editor keydown events
4374 jQuery(this._doc).bind("keydown", this.keydown);
4376 //bind editor events
4377 jQuery(this._doc).bind("keyup", this.keyup);
4379 //post-init functions
4380 if(jQuery.isFunction(this._options.postInit)) this._options.postInit(this);
4382 //add event listeners to doc elements, e.g. images
4386 WYMeditor.WymClassOpera.prototype._exec = function(cmd,param) {
4388 if(param) this._doc.execCommand(cmd,false,param);
4389 else this._doc.execCommand(cmd);
4394 WYMeditor.WymClassOpera.prototype.selected = function() {
4396 var sel=this._iframe.contentWindow.getSelection();
4397 var node=sel.focusNode;
4399 if(node.nodeName=="#text")return(node.parentNode);
4401 } else return(null);
4404 WYMeditor.WymClassOpera.prototype.addCssRule = function(styles, oCss) {
4406 styles.insertRule(oCss.name + " {" + oCss.css + "}",
4407 styles.cssRules.length);
4411 WYMeditor.WymClassOpera.prototype.keydown = function(evt) {
4414 var wym = WYMeditor.INSTANCES[this.title];
4415 var sel = wym._iframe.contentWindow.getSelection();
4416 startNode = sel.getRangeAt(0).startContainer;
4418 //Get a P instead of no container
4419 if(!jQuery(startNode).parentsOrSelf(
4420 WYMeditor.MAIN_CONTAINERS.join(","))[0]
4421 && !jQuery(startNode).parentsOrSelf('li')
4422 && evt.keyCode != WYMeditor.KEY.ENTER
4423 && evt.keyCode != WYMeditor.KEY.LEFT
4424 && evt.keyCode != WYMeditor.KEY.UP
4425 && evt.keyCode != WYMeditor.KEY.RIGHT
4426 && evt.keyCode != WYMeditor.KEY.DOWN
4427 && evt.keyCode != WYMeditor.KEY.BACKSPACE
4428 && evt.keyCode != WYMeditor.KEY.DELETE)
4429 wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P);
4434 WYMeditor.WymClassOpera.prototype.keyup = function(evt) {
4437 var wym = WYMeditor.INSTANCES[this.title];
4438 wym._selected_image = null;
4441 // TODO: implement me
4442 WYMeditor.WymClassOpera.prototype.setFocusToNode = function(node) {
4447 * WYMeditor : what you see is What You Mean web-based editor
4448 * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/
4449 * Dual licensed under the MIT (MIT-license.txt)
4450 * and GPL (GPL-license.txt) licenses.
4452 * For further information visit:
4453 * http://www.wymeditor.org/
4456 * jquery.wymeditor.safari.js
4457 * Safari specific class and functions.
4458 * See the documentation for more info.
4461 * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg)
4462 * Scott Lewis (lewiscot a-t gmail dotcom)
4465 WYMeditor.WymClassSafari = function(wym) {
4468 this._class = "class";
4469 this._newLine = "\n";
4472 WYMeditor.WymClassSafari.prototype.initIframe = function(iframe) {
4474 this._iframe = iframe;
4475 this._doc = iframe.contentDocument;
4477 //add css rules from options
4479 var styles = this._doc.styleSheets[0];
4480 var aCss = eval(this._options.editorStyles);
4482 this.addCssRules(this._doc, aCss);
4484 this._doc.title = this._wym._index;
4486 //set the text direction
4487 jQuery('html', this._doc).attr('dir', this._options.direction);
4490 this._doc.designMode = "on";
4493 this.html(this._wym._html);
4495 //pre-bind functions
4496 if(jQuery.isFunction(this._options.preBind)) this._options.preBind(this);
4498 //bind external events
4499 this._wym.bindEvents();
4501 //bind editor keydown events
4502 jQuery(this._doc).bind("keydown", this.keydown);
4504 //bind editor keyup events
4505 jQuery(this._doc).bind("keyup", this.keyup);
4507 //post-init functions
4508 if(jQuery.isFunction(this._options.postInit)) this._options.postInit(this);
4510 //add event listeners to doc elements, e.g. images
4514 WYMeditor.WymClassSafari.prototype._exec = function(cmd,param) {
4516 if(!this.selected()) return(false);
4520 case WYMeditor.INDENT: case WYMeditor.OUTDENT:
4522 var focusNode = this.selected();
4523 var sel = this._iframe.contentWindow.getSelection();
4524 var anchorNode = sel.anchorNode;
4525 if(anchorNode.nodeName == "#text") anchorNode = anchorNode.parentNode;
4527 focusNode = this.findUp(focusNode, WYMeditor.BLOCKS);
4528 anchorNode = this.findUp(anchorNode, WYMeditor.BLOCKS);
4530 if(focusNode && focusNode == anchorNode
4531 && focusNode.tagName.toLowerCase() == WYMeditor.LI) {
4533 var ancestor = focusNode.parentNode.parentNode;
4535 if(focusNode.parentNode.childNodes.length>1
4536 || ancestor.tagName.toLowerCase() == WYMeditor.OL
4537 || ancestor.tagName.toLowerCase() == WYMeditor.UL)
4538 this._doc.execCommand(cmd,'',null);
4543 case WYMeditor.INSERT_ORDEREDLIST: case WYMeditor.INSERT_UNORDEREDLIST:
4545 this._doc.execCommand(cmd,'',null);
4547 //Safari creates lists in e.g. paragraphs.
4548 //Find the container, and remove it.
4549 var focusNode = this.selected();
4550 var container = this.findUp(focusNode, WYMeditor.MAIN_CONTAINERS);
4551 if(container) jQuery(container).replaceWith(jQuery(container).html());
4557 if(param) this._doc.execCommand(cmd,'',param);
4558 else this._doc.execCommand(cmd,'',null);
4561 //set to P if parent = BODY
4562 var container = this.selected();
4563 if(container && container.tagName.toLowerCase() == WYMeditor.BODY)
4564 this._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P);
4566 //add event handlers on doc elements
4571 * @description Returns the selected container
4573 WYMeditor.WymClassSafari.prototype.selected = function() {
4575 var sel = this._iframe.contentWindow.getSelection();
4576 var node = sel.focusNode;
4578 if(node.nodeName == "#text") return(node.parentNode);
4580 } else return(null);
4583 WYMeditor.WymClassSafari.prototype.addCssRule = function(styles, oCss) {
4585 styles.insertRule(oCss.name + " {" + oCss.css + "}",
4586 styles.cssRules.length);
4590 //keydown handler, mainly used for keyboard shortcuts
4591 WYMeditor.WymClassSafari.prototype.keydown = function(evt) {
4594 var wym = WYMeditor.INSTANCES[this.title];
4597 if(evt.keyCode == 66){
4599 wym._exec(WYMeditor.BOLD);
4602 if(evt.keyCode == 73){
4603 //CTRL+i => EMPHASIS
4604 wym._exec(WYMeditor.ITALIC);
4610 //keyup handler, mainly used for cleanups
4611 WYMeditor.WymClassSafari.prototype.keyup = function(evt) {
4614 var wym = WYMeditor.INSTANCES[this.title];
4616 wym._selected_image = null;
4617 var container = null;
4619 if(evt.keyCode == 13 && !evt.shiftKey) {
4622 //cleanup <br><br> between paragraphs
4623 jQuery(wym._doc.body).children(WYMeditor.BR).remove();
4626 container = wym.selected();
4627 if(container && container.tagName.toLowerCase() == WYMeditor.PRE)
4628 wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P); //create P after PRE
4632 if(evt.keyCode == 13 && evt.shiftKey) {
4633 wym._exec('InsertLineBreak');
4637 && evt.keyCode != 17
4638 && evt.keyCode != 46
4639 && evt.keyCode != 224
4643 //NOT BACKSPACE, NOT DELETE, NOT CTRL, NOT COMMAND
4644 //text nodes replaced by P
4646 container = wym.selected();
4647 var name = container.tagName.toLowerCase();
4649 //fix forbidden main containers
4658 name == "span" //fix #110
4660 ) name = container.parentNode.tagName.toLowerCase();
4662 if(name == WYMeditor.BODY || name == WYMeditor.DIV) wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P); //fix #110 for DIV
4666 WYMeditor.WymClassSafari.prototype.setFocusToNode = function(node) {
4667 var range = this._iframe.contentDocument.createRange();
4668 range.selectNode(node);
4669 var selected = this._iframe.contentWindow.getSelection();
4670 selected.addRange(range);
4671 selected.collapse(node, node.childNodes.length);
4672 this._iframe.contentWindow.focus();
4675 WYMeditor.WymClassSafari.prototype.openBlockTag = function(tag, attributes)
4677 var attributes = this.validator.getValidTagAttributes(tag, attributes);
4679 // Handle Safari styled spans
4680 if(tag == 'span' && attributes.style) {
4681 var new_tag = this.getTagForStyle(attributes.style);
4683 this._tag_stack.pop();
4685 this._tag_stack.push(new_tag);
4686 attributes.style = '';
4688 //should fix #125 - also removed the xhtml() override
4689 if(typeof attributes['class'] == 'string')
4690 attributes['class'] = attributes['class'].replace(/apple-style-span/gi, '');
4697 this.output += this.helper.tag(tag, attributes, true);
4700 WYMeditor.WymClassSafari.prototype.getTagForStyle = function(style) {
4702 if(/bold/.test(style)) return 'strong';
4703 if(/italic/.test(style)) return 'em';
4704 if(/sub/.test(style)) return 'sub';
4705 if(/super/.test(style)) return 'sup';