source: branches/new_parser/wymeditor/jquery.wymeditor.js @ 326

Revision 326, 40.7 KB checked in by bermi, 5 years ago (diff)

Updating simple (and buggy) implementation for handling lists on Safari

Line 
1/*
2 * WYMeditor : what you see is What You Mean web-based editor
3 * Copyright (C) 2007 H.O.net - http://www.honet.be/
4 * Dual licensed under the MIT (MIT-license.txt)
5 * and GPL (GPL-license.txt) licenses.
6 *
7 * For further information visit:
8 *        http://www.wymeditor.org/
9 *
10 * File Name:
11 *        jquery.wymeditor.js
12 *        Main JS file with core class and functions.
13 *        See the documentation for more info.
14 *
15 * File Authors:
16 *        Jean-Francois Hovinne (jf.hovinne@wymeditor.org)
17 *        Volker Mische (vmx@gmx.de)
18 *        Scott Lewis (scott@bright-crayon.com)
19 *        Bermi Ferrer (wymeditor a-t bermi dotorg)
20 *        Daniel Reszka (d.reszka@wymeditor.org)
21 */
22
23
24/********** CONSTANTS **********/
25
26    var $j = jQuery.noConflict();
27   
28    var aWYM_INSTANCES        = new Array();
29    var sWYM_NAME             = "name";
30    var sWYM_INDEX            = "{Wym_Index}";
31    var sWYM_BASE_PATH        = "{Wym_Base_Path}";
32    var sWYM_CSS_PATH         = "{Wym_Css_Path}";
33    var sWYM_IFRAME_BASE_PATH = "{Wym_Iframe_Base_Path}";
34    var sWYM_IFRAME_DEFAULT   = "iframe/default/";
35    var sWYM_JQUERY_PATH      = "{Wym_Jquery_Path}";
36    var sWYM_TOOLS            = "{Wym_Tools}";
37    var sWYM_TOOLS_ITEMS      = "{Wym_Tools_Items}";
38    var sWYM_TOOL_NAME        = "{Wym_Tools_Name}";
39    var sWYM_TOOL_TITLE       = "{Wym_Tools_Title}";
40    var sWYM_TOOL_CLASS       = "{Wym_Tools_Class}";
41    var sWYM_CLASSES          = "{Wym_Classes}";
42    var sWYM_CLASSES_ITEMS    = "{Wym_Classes_Items}";
43    var sWYM_CLASS_NAME       = "{Wym_Class_Name}";
44    var sWYM_CLASS_TITLE      = "{Wym_Class_Title}";
45    var sWYM_CONTAINERS       = "{Wym_Containers}";
46    var sWYM_CONTAINERS_ITEMS = "{Wym_Containers_Items}";
47    var sWYM_CONTAINER_NAME   = "{Wym_Container_Name}";
48    var sWYM_CONTAINER_TITLE  = "{Wym_Containers_Title}";
49    var sWYM_CONTAINER_CLASS  = "{Wym_Container_Class}";
50    var sWYM_HTML             = "{Wym_Html}";
51    var sWYM_IFRAME           = "{Wym_Iframe}";
52    var sWYM_STATUS           = "{Wym_Status}";
53    var sWYM_DIALOG_TITLE     = "{Wym_Dialog_Title}";
54    var sWYM_DIALOG_BODY      = "{Wym_Dialog_Body}";
55    var sWYM_BODY             = "body";
56    var sWYM_STRING           = "string";
57    var sWYM_P                = "p";
58    var sWYM_H1               = "h1";
59    var sWYM_H2               = "h2";
60    var sWYM_H3               = "h3";
61    var sWYM_H4               = "h4";
62    var sWYM_H5               = "h5";
63    var sWYM_H6               = "h6";
64    var sWYM_PRE              = "pre";
65    var sWYM_BLOCKQUOTE       = "blockquote";
66    var sWYM_TD               = "td";
67    var sWYM_TH               = "th";
68    var sWYM_A                = "a";
69    var sWYM_BR               = "br";
70    var sWYM_IMG              = "img";
71    var sWYM_TABLE            = "table";
72    var sWYM_CLASS            = "class";
73    var sWYM_HREF             = "href";
74    var sWYM_SRC              = "src";
75    var sWYM_TITLE            = "title";
76    var sWYM_ALT              = "alt";
77    var sWYM_DIALOG_LINK      = "Link";
78    var sWYM_DIALOG_IMAGE     = "Image";
79    var sWYM_DIALOG_TABLE     = "Table";
80    var sWYM_DIALOG_PASTE     = "Paste_From_Word";
81    var sWYM_BOLD             = "Bold";
82    var sWYM_ITALIC           = "Italic";
83    var sWYM_CREATE_LINK      = "CreateLink";
84    var sWYM_INSERT_IMAGE     = "InsertImage";
85    var sWYM_INSERT_TABLE     = "InsertTable";
86    var sWYM_PASTE            = "Paste";
87    var sWYM_TOGGLE_HTML      = "ToggleHtml";
88    var sWYM_FORMAT_BLOCK     = "FormatBlock";
89    var sWYM_PREVIEW          = "Preview";
90   
91    var sWYM_DEFAULT_SKIN     = "default";
92
93    var aWYM_CONTAINERS = new Array(sWYM_P,sWYM_H1,sWYM_H2,sWYM_H3,sWYM_H4,
94        sWYM_H5,sWYM_H6,sWYM_PRE,sWYM_BLOCKQUOTE);
95
96    var aWYM_KEY = {
97      BACKSPACE: 8,
98      ENTER: 13,
99      END: 35,
100      HOME: 36,
101      LEFT: 37,
102      UP: 38,
103      RIGHT: 39,
104      DOWN: 40,
105      CURSOR: new Array(37, 38, 39, 40),
106      DELETE: 46
107    };
108
109    var aWYM_NODE = {
110      ELEMENT: 1,
111      ATTRIBUTE: 2,
112      TEXT: 3
113    };
114
115
116/********** JQUERY **********/
117
118/**
119 * Replace an HTML element by WYMeditor
120 *
121 * @example $j(".wymeditor").wymeditor(
122 *        {
123 *
124 *        }
125 *      );
126 * @desc Example description here
127 *
128 * @name WYMeditor
129 * @description WYMeditor is a web-based WYSIWYM XHTML editor
130 * @param Hash hash A hash of parameters
131 * @option Integer iExample Description here
132 * @option String sExample Description here
133 *
134 * @type jQuery
135 * @cat Plugins/WYMeditor
136 * @author Jean-Francois Hovinne
137 */
138$j.fn.wymeditor = function(options) {
139
140  options = $j.extend({
141
142    sHtml:       "",
143   
144    sBasePath:   false,
145   
146    sCssPath:    false,
147   
148    sIframeBasePath: false,
149   
150    sJqueryPath: false,
151   
152    sLang:       "en",
153
154    sBoxHtml:   "<div class='wym_box'>"
155              + "<div class='wym_area_top'>"
156              + sWYM_TOOLS
157              + "</div>"
158              + "<div class='wym_area_left'></div>"
159              + "<div class='wym_area_right'>"
160              + sWYM_CONTAINERS
161              + sWYM_CLASSES
162              + "</div>"
163              + "<div class='wym_area_main'>"
164              + sWYM_HTML
165              + sWYM_IFRAME
166              + sWYM_STATUS
167              + "</div>"
168              + "<div class='wym_area_bottom'>"
169              + "</div>"
170              + "</div>",
171
172    sIframeHtml:"<div class='wym_iframe wym_section'>"
173              + "<iframe "
174              + "src='"
175              + sWYM_IFRAME_BASE_PATH
176              + "wymiframe.html' "
177              + "onload='window.parent.aWYM_INSTANCES["
178              + sWYM_INDEX + "].initIframe(this)' "
179              + "></iframe>"
180              + "</div>",
181             
182    aEditorCss: [],
183
184    sToolsHtml: "<div class='wym_tools wym_section'>"
185              + "<h2>Tools</h2>"
186              + "<ul>"
187              + sWYM_TOOLS_ITEMS
188              + "</ul>"
189              + "</div>",
190             
191    sToolsItemHtml:   "<li class='"
192                        + sWYM_TOOL_CLASS
193                        + "'><a href='#' name='"
194                        + sWYM_TOOL_NAME
195                        + "'>"
196                        + sWYM_TOOL_TITLE
197                        + "</a></li>",
198
199    aToolsItems: [
200        {'name': 'Bold', 'title': 'Strong', 'css': 'wym_tools_strong'},
201        {'name': 'Italic', 'title': 'Emphasis', 'css': 'wym_tools_emphasis'},
202        {'name': 'Superscript', 'title': 'Superscript',
203            'css': 'wym_tools_superscript'},
204        {'name': 'Subscript', 'title': 'Subscript',
205            'css': 'wym_tools_subscript'},
206        {'name': 'InsertOrderedList', 'title': 'Ordered_List',
207            'css': 'wym_tools_ordered_list'},
208        {'name': 'InsertUnorderedList', 'title': 'Unordered_List',
209            'css': 'wym_tools_unordered_list'},
210        {'name': 'Indent', 'title': 'Indent', 'css': 'wym_tools_indent'},
211        {'name': 'Outdent', 'title': 'Outdent', 'css': 'wym_tools_outdent'},
212        {'name': 'Undo', 'title': 'Undo', 'css': 'wym_tools_undo'},
213        {'name': 'Redo', 'title': 'Redo', 'css': 'wym_tools_redo'},
214        {'name': 'CreateLink', 'title': 'Link', 'css': 'wym_tools_link'},
215        {'name': 'Unlink', 'title': 'Unlink', 'css': 'wym_tools_unlink'},
216        {'name': 'InsertImage', 'title': 'Image', 'css': 'wym_tools_image'},
217        {'name': 'InsertTable', 'title': 'Table', 'css': 'wym_tools_table'},
218        {'name': 'Paste', 'title': 'Paste_From_Word',
219            'css': 'wym_tools_paste'},
220        {'name': 'ToggleHtml', 'title': 'HTML', 'css': 'wym_tools_html'},
221        {'name': 'Preview', 'title': 'Preview', 'css': 'wym_tools_preview'}
222    ],
223
224    sContainersHtml:    "<div class='wym_containers wym_section'>"
225                        + "<h2>Containers</h2>"
226                        + "<ul>"
227                        + sWYM_CONTAINERS_ITEMS
228                        + "</ul>"
229                        + "</div>",
230                       
231    sContainersItemHtml:"<li class='"
232                        + sWYM_CONTAINER_CLASS
233                        + "'>"
234                        + "<a href='#' name='"
235                        + sWYM_CONTAINER_NAME
236                        + "'>"
237                        + sWYM_CONTAINER_TITLE
238                        + "</a></li>",
239                       
240    aContainersItems: [
241        {'name': 'P', 'title': 'Paragraph', 'css': 'wym_containers_p'},
242        {'name': 'H1', 'title': 'Heading_1', 'css': 'wym_containers_h1'},
243        {'name': 'H2', 'title': 'Heading_2', 'css': 'wym_containers_h2'},
244        {'name': 'H3', 'title': 'Heading_3', 'css': 'wym_containers_h3'},
245        {'name': 'H4', 'title': 'Heading_4', 'css': 'wym_containers_h4'},
246        {'name': 'H5', 'title': 'Heading_5', 'css': 'wym_containers_h5'},
247        {'name': 'H6', 'title': 'Heading_6', 'css': 'wym_containers_h6'},
248        {'name': 'PRE', 'title': 'Preformatted', 'css': 'wym_containers_pre'},
249        {'name': 'BLOCKQUOTE', 'title': 'Blockquote',
250            'css': 'wym_containers_blockquote'},
251        {'name': 'TH', 'title': 'Table_Header', 'css': 'wym_containers_th'}
252    ],
253
254    sClassesHtml:       "<div class='wym_classes wym_section'>"
255                        + "<h2>Classes</h2><ul>"
256                        + sWYM_CLASSES_ITEMS
257                        + "</ul></div>",
258
259    sClassesItemHtml:   "<li><a href='#' name='"
260                        + sWYM_CLASS_NAME
261                        + "'>"
262                        + sWYM_CLASS_TITLE
263                        + "</a></li>",
264
265    aClassesItems:      [],
266
267    sStatusHtml:        "<div class='wym_status wym_section'>"
268                        + "<h2>Status</h2>"
269                        + "</div>",
270
271    sHtmlHtml:          "<div class='wym_html wym_section'>"
272                        + "<h2>Source code</h2>"
273                        + "<textarea class='wym_html_val'></textarea>"
274                        + "</div>",
275
276    sBoxSelector:       ".wym_box",
277    sToolsSelector:     ".wym_tools",
278    sToolsListSelector: " ul",
279    sContainersSelector:".wym_containers",
280    sClassesSelector:   ".wym_classes",
281    sHtmlSelector:      ".wym_html",
282    sIframeSelector:    ".wym_iframe iframe",
283    sStatusSelector:    ".wym_status",
284    sToolSelector:      ".wym_tools a",
285    sContainerSelector: ".wym_containers a",
286    sClassSelector:     ".wym_classes a",
287    sHtmlValSelector:   ".wym_html_val",
288   
289    sHrefSelector:      ".wym_href",
290    sSrcSelector:       ".wym_src",
291    sTitleSelector:     ".wym_title",
292    sAltSelector:       ".wym_alt",
293    sTextSelector:      ".wym_text",
294   
295    sRowsSelector:      ".wym_rows",
296    sColsSelector:      ".wym_cols",
297    sCaptionSelector:   ".wym_caption",
298   
299    sSubmitSelector:    ".wym_submit",
300    sCancelSelector:    ".wym_cancel",
301    sPreviewSelector:   "",
302   
303    sDialogLinkSelector:    ".wym_dialog_link",
304    sDialogImageSelector:   ".wym_dialog_image",
305    sDialogTableSelector:   ".wym_dialog_table",
306    sDialogPasteSelector:   ".wym_dialog_paste",
307    sDialogPreviewSelector: ".wym_dialog_preview",
308   
309    sUpdateSelector:    ".wymupdate",
310    sUpdateEvent:       "click",
311   
312    sDialogFeatures:    "menubar=no,titlebar=no,toolbar=no,resizable=no"
313                      + ",width=560,height=300,top=0,left=0",
314
315    sDialogHtml:      "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN'"
316                      + " 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>"
317                      + "<html><head>"
318                      + "<link rel='stylesheet' type='text/css' media='screen'"
319                      + " href='"
320                      + sWYM_CSS_PATH
321                      + "' />"
322                      + "<title>"
323                      + sWYM_DIALOG_TITLE
324                      + "</title>"
325                      + "<script type='text/javascript'"
326                      + " src='"
327                      + sWYM_JQUERY_PATH
328                      + "'></script>"
329                      + "<script type='text/javascript'"
330                      + " src='"
331                      + sWYM_BASE_PATH
332                      + "jquery.wymeditor.js'></script>"
333                      + "</head>"
334                      + sWYM_DIALOG_BODY
335                      + "</html>",
336                     
337    sDialogLinkHtml:  "<body class='wym_dialog wym_dialog_link'"
338                      + " onload='fWYM_INIT_DIALOG(" + sWYM_INDEX + ")'"
339                      + ">"
340                      + "<form>"
341                      + "<fieldset>"
342                          + "<legend>{Link}</legend>"
343                          + "<div class='row'>"
344                          + "<label>{URL}</label>"
345                          + "<input type='text' class='wym_href' value='' size='40' />"
346                          + "</div>"
347                          + "<div class='row'>"
348                          + "<label>{Title}</label>"
349                          + "<input type='text' class='wym_title' value='' size='40' />"
350                          + "</div>"
351                          + "<div class='row row-indent'>"
352                          + "<input class='wym_submit' type='button'"
353                          + " value='{Submit}' />"
354                          + "<input class='wym_cancel' type='button'"
355                          + "value='{Cancel}' />"
356                          + "</div>"
357                      + "</fieldset>"
358                      + "</form>"
359                      + "</body>",
360   
361    sDialogImageHtml:  "<body class='wym_dialog wym_dialog_image'"
362                      + " onload='fWYM_INIT_DIALOG(" + sWYM_INDEX + ")'"
363                      + ">"
364                      + "<form>"
365                      + "<fieldset>"
366                          + "<legend>{Image}</legend>"
367                          + "<div class='row'>"
368                          + "<label>{URL}</label>"
369                          + "<input type='text' class='wym_src' value='' size='40' />"
370                          + "</div>"
371                          + "<div class='row'>"
372                          + "<label>{Alternative_Text}</label>"
373                          + "<input type='text' class='wym_alt' value='' size='40' />"
374                          + "</div>"
375                          + "<div class='row'>"
376                          + "<label>{Title}</label>"
377                          + "<input type='text' class='wym_title' value='' size='40' />"
378                          + "</div>"
379                          + "<div class='row row-indent'>"
380                          + "<input class='wym_submit' type='button'"
381                          + " value='{Submit}' />"
382                          + "<input class='wym_cancel' type='button'"
383                          + "value='{Cancel}' />"
384                          + "</div>"
385                      + "</fieldset>"
386                      + "</form>"
387                      + "</body>",
388   
389    sDialogTableHtml:  "<body class='wym_dialog wym_dialog_table'"
390                      + " onload='fWYM_INIT_DIALOG(" + sWYM_INDEX + ")'"
391                      + ">"
392                      + "<form>"
393                      + "<fieldset>"
394                          + "<legend>{Table}</legend>"
395                          + "<div class='row'>"
396                          + "<label>{Caption}</label>"
397                          + "<input type='text' class='wym_caption' value='' size='40' />"
398                          + "</div>"
399                          + "<div class='row'>"
400                          + "<label>{Number_Of_Rows}</label>"
401                          + "<input type='text' class='wym_rows' value='3' size='3' />"
402                          + "</div>"
403                          + "<div class='row'>"
404                          + "<label>{Number_Of_Cols}</label>"
405                          + "<input type='text' class='wym_cols' value='2' size='3' />"
406                          + "</div>"
407                          + "<div class='row row-indent'>"
408                          + "<input class='wym_submit' type='button'"
409                          + " value='{Submit}' />"
410                          + "<input class='wym_cancel' type='button'"
411                          + "value='{Cancel}' />"
412                          + "</div>"
413                      + "</fieldset>"
414                      + "</form>"
415                      + "</body>",
416
417    sDialogPasteHtml:  "<body class='wym_dialog wym_dialog_paste'"
418                      + " onload='fWYM_INIT_DIALOG(" + sWYM_INDEX + ")'"
419                      + ">"
420                      + "<form>"
421                      + "<fieldset>"
422                          + "<legend>{Paste_From_Word}</legend>"
423                          + "<div class='row'>"
424                          + "<textarea class='wym_text' rows='10' cols='50'></textarea>"
425                          + "</div>"
426                          + "<div class='row'>"
427                          + "<input class='wym_submit' type='button'"
428                          + " value='{Submit}' />"
429                          + "<input class='wym_cancel' type='button'"
430                          + "value='{Cancel}' />"
431                          + "</div>"
432                      + "</fieldset>"
433                      + "</form>"
434                      + "</body>",
435
436    sDialogPreviewHtml: "<body class='wym_dialog wym_dialog_preview'"
437                      + " onload='fWYM_INIT_DIALOG(" + sWYM_INDEX + ")'"
438                      + "></body>",
439                     
440    aDialogCss: [],
441                     
442    sSkin:            sWYM_DEFAULT_SKIN,
443
444    sStringDelimiterLeft: "{",
445    sStringDelimiterRight:"}",
446   
447    fPreInit: null,
448    fPreBind: null,
449    fPostInit: null,
450   
451    fPreInitDialog: null,
452    fPostInitDialog: null
453
454  }, options);
455
456  return this.each(function(i) {
457
458    new Wymeditor($j(this),i,options);
459  });
460};
461
462/* @name extend
463 * @description Returns the WYMeditor instance based on its index
464 */
465$j.extend({
466  wymeditors: function(i) {
467    return (aWYM_INSTANCES[i]);
468  },
469  wymstrings: function(sLang, sKey) {
470    return (aWYM_STRINGS[sLang][sKey]);
471  }
472});
473
474
475/********** WYMEDITOR **********/
476
477/* @name Wymeditor
478 * @description WYMeditor class
479 */
480function Wymeditor(elem,index,options) {
481
482  aWYM_INSTANCES[index] = this;
483
484  this._element = elem;
485  this._index = index;
486  this._options = options;
487  this._html = $j(elem).val();
488 
489  if(this._options.sHtml) this._html = this._options.sHtml;
490  this._options.sBasePath = this._options.sBasePath
491    || this.computeBasePath();
492  this._options.sCssPath = this._options.sCssPath
493    || this.computeCssPath();
494  this._options.sIframeBasePath = this._options.sIframeBasePath
495    || this._options.sBasePath + sWYM_IFRAME_DEFAULT;
496  this._options.sJqueryPath = this._options.sJqueryPath
497    || this.computeJqueryPath();
498 
499  if($j.isFunction(this._options.fPreInit)) this._options.fPreInit(this);
500   
501  this.init();
502 
503};
504
505
506/* @name init
507 * @description Initializes a WYMeditor instance
508 */
509Wymeditor.prototype.init = function() {
510
511  var WymClass = this.getBrowserSpecificWymInstance();
512
513  this.loadXhtmlParser(WymClass);
514
515  if(this._options.sWymCss || this._options.sWymStylesheet){
516    this.configureEditorUsingRawCss();
517  }
518 
519  this.helper = new XmlHelper();
520 
521  //extend the Wymeditor object
522  $j.extend(this, WymClass);
523 
524  //load wymbox
525  this._box = $j(this._element).hide().after(this._options.sBoxHtml).next();
526 
527  //construct the iframe
528  var sIframeHtml = this._options.sIframeHtml;
529  sIframeHtml = sIframeHtml
530    .replace(sWYM_INDEX,this._index)
531    .replace(sWYM_IFRAME_BASE_PATH, this._options.sIframeBasePath);
532 
533  //construct wymbox
534  var sBoxHtml = $j(this._box).html();
535 
536  sBoxHtml = sBoxHtml.replace(sWYM_TOOLS, this._options.sToolsHtml);
537  sBoxHtml = sBoxHtml.replace(sWYM_CONTAINERS, this._options.sContainersHtml);
538  sBoxHtml = sBoxHtml.replace(sWYM_CLASSES, this._options.sClassesHtml);
539  sBoxHtml = sBoxHtml.replace(sWYM_HTML, this._options.sHtmlHtml);
540  sBoxHtml = sBoxHtml.replace(sWYM_IFRAME, sIframeHtml);
541  sBoxHtml = sBoxHtml.replace(sWYM_STATUS, this._options.sStatusHtml);
542 
543  //construct tools list
544  var aTools = eval(this._options.aToolsItems);
545  var sTools = "";
546
547  for(var i = 0; i < aTools.length; i++) {
548    var oTool = aTools[i];
549    if(oTool.name && oTool.title)
550      sTools += this._options.sToolsItemHtml
551      .replace(sWYM_TOOL_NAME, oTool.name)
552      .replace(sWYM_TOOL_TITLE,
553          this._options.sStringDelimiterLeft
554        + oTool.title
555        + this._options.sStringDelimiterRight)
556      .replace(sWYM_TOOL_CLASS, oTool.css);
557  }
558
559  sBoxHtml = sBoxHtml.replace(sWYM_TOOLS_ITEMS, sTools);
560
561  //construct classes list
562  var aClasses = eval(this._options.aClassesItems);
563  var sClasses = "";
564
565  for(var i = 0; i < aClasses.length; i++) {
566    var oClass = aClasses[i];
567    if(oClass.name && oClass.title)
568      sClasses += this._options.sClassesItemHtml
569      .replace(sWYM_CLASS_NAME, oClass.name)
570      .replace(sWYM_CLASS_TITLE, oClass.title);
571  }
572
573  sBoxHtml = sBoxHtml.replace(sWYM_CLASSES_ITEMS, sClasses);
574 
575  //construct containers list
576  var aContainers = eval(this._options.aContainersItems);
577  var sContainers = "";
578
579  for(var i = 0; i < aContainers.length; i++) {
580    var oContainer = aContainers[i];
581    if(oContainer.name && oContainer.title)
582      sContainers += this._options.sContainersItemHtml
583      .replace(sWYM_CONTAINER_NAME, oContainer.name)
584      .replace(sWYM_CONTAINER_TITLE,
585          this._options.sStringDelimiterLeft
586        + oContainer.title
587        + this._options.sStringDelimiterRight)
588      .replace(sWYM_CONTAINER_CLASS, oContainer.css);
589  }
590
591  sBoxHtml = sBoxHtml.replace(sWYM_CONTAINERS_ITEMS, sContainers);
592
593  //l10n
594  sBoxHtml = this.replaceStrings(sBoxHtml);
595 
596  //load html in wymbox
597  $j(this._box).html(sBoxHtml);
598 
599  //hide the html value
600  $j(this._box).find(this._options.sHtmlSelector).hide();
601 
602  //enable the skin
603  this.skin();
604
605};
606
607Wymeditor.prototype.bindEvents = function() {
608
609  //copy the instance
610  var wym = this;
611 
612  //handle click event on tools buttons
613  $j(this._box).find(this._options.sToolSelector).click(function() {
614    wym.exec($j(this).attr(sWYM_NAME));
615    return(false);
616  });
617 
618  //handle click event on containers buttons
619  $j(this._box).find(this._options.sContainerSelector).click(function() {
620    wym.container($j(this).attr(sWYM_NAME));
621    return(false);
622  });
623 
624  //handle keyup event on html value: set the editor value
625  $j(this._box).find(this._options.sHtmlValSelector).keyup(function() {
626    $j(wym._doc.body).html($j(this).val());
627  });
628 
629  //handle click event on classes buttons
630  $j(this._box).find(this._options.sClassSelector).click(function() {
631 
632    var aClasses = eval(wym._options.aClassesItems);
633    var sName = $j(this).attr(sWYM_NAME);
634   
635    var oClass = aClasses.findByName(sName);
636   
637    if(oClass) {
638      jqexpr = oClass.expr;
639      wym.toggleClass(sName, jqexpr);
640    }
641    return(false);
642  });
643 
644  //handle event on update element
645  $j(this._options.sUpdateSelector)
646    .bind(this._options.sUpdateEvent, function() {
647      wym.update();
648  });
649};
650
651Wymeditor.prototype.ready = function() {
652  return(this._doc != null);
653};
654
655
656/********** METHODS **********/
657
658/* @name box
659 * @description Returns the WYMeditor container
660 */
661Wymeditor.prototype.box = function() {
662  return(this._box);
663};
664
665/* @name html
666 * @description Get/Set the html value
667 */
668Wymeditor.prototype.html = function(sHtml) {
669
670  if(sHtml) $j(this._doc.body).html(sHtml);
671  else return($j(this._doc.body).html());
672};
673
674/* @name xhtml
675 * @description Cleans up the HTML
676 */
677Wymeditor.prototype.xhtml = function() {
678    return this.parser.parse(this.html());
679};
680
681/* @name exec
682 * @description Executes a button command
683 */
684Wymeditor.prototype.exec = function(cmd) {
685 
686  //base function for execCommand
687  //open a dialog or exec
688  switch(cmd) {
689    case sWYM_CREATE_LINK:
690      var container = this.container();
691      if(container) this.dialog(sWYM_DIALOG_LINK);
692    break;
693   
694    case sWYM_INSERT_IMAGE:
695      this.dialog(sWYM_DIALOG_IMAGE);
696    break;
697   
698    case sWYM_INSERT_TABLE:
699      this.dialog(sWYM_DIALOG_TABLE);
700    break;
701   
702    case sWYM_PASTE:
703      this.dialog(sWYM_DIALOG_PASTE);
704    break;
705   
706    case sWYM_TOGGLE_HTML:
707      this.update();
708      this.toggleHtml();
709    break;
710   
711    case sWYM_PREVIEW:
712      this.dialog(sWYM_PREVIEW);
713    break;
714   
715    default:
716      this._exec(cmd);
717    break;
718  }
719};
720
721/* @name container
722 * @description Get/Set the selected container
723 */
724Wymeditor.prototype.container = function(sType) {
725
726  if(sType) {
727 
728    var container = null;
729   
730    if(sType.toLowerCase() == sWYM_TH) {
731   
732      container = this.container();
733     
734      //find the TD or TH container
735      switch(container.tagName.toLowerCase()) {
736     
737        case sWYM_TD: case sWYM_TH:
738          break;
739        default:
740          var aTypes = new Array(sWYM_TD,sWYM_TH);
741          container = this.findUp(aTypes);
742          break;
743      }
744     
745      //if it exists, switch
746      if(container!=null) {
747     
748        sType = (container.tagName.toLowerCase() == sWYM_TD)? sWYM_TH: sWYM_TD;
749        this.switchTo(container,sType);
750        this.update();
751      }
752    } else {
753 
754      //set the container type
755      var aTypes=new Array(sWYM_P,sWYM_H1,sWYM_H2,sWYM_H3,sWYM_H4,sWYM_H5,
756      sWYM_H6,sWYM_PRE,sWYM_BLOCKQUOTE);
757      container = this.findUp(aTypes);
758     
759      if(container) {
760 
761        var newNode = null;
762 
763        //blockquotes must contain a block level element
764        if(sType.toLowerCase() == sWYM_BLOCKQUOTE) {
765       
766          var blockquote = this.findUp(sWYM_BLOCKQUOTE);
767         
768          if(blockquote == null) {
769         
770            newNode = this._doc.createElement(sType);
771            container.parentNode.insertBefore(newNode,container);
772            newNode.appendChild(container);
773            this.setFocusToNode(newNode.firstChild);
774           
775          } else {
776         
777            var nodes = blockquote.childNodes;
778            var lgt = nodes.length;
779            var firstNode = null;
780           
781            if(lgt > 0) firstNode = nodes.item(0);
782            for(var x=0; x<lgt; x++) {
783              blockquote.parentNode.insertBefore(nodes.item(0),blockquote);
784            }
785            blockquote.parentNode.removeChild(blockquote);
786            if(firstNode) this.setFocusToNode(firstNode);
787          }
788        }
789       
790        else this.switchTo(container,sType);
791     
792        this.update();
793      }
794    }
795  }
796  else return(this.selected());
797};
798
799/* @name toggleClass
800 * @description Toggles class on selected element, or one of its parents
801 */
802Wymeditor.prototype.toggleClass = function(sClass, jqexpr) {
803
804  var container = (this._selected_image
805                    ? this._selected_image
806                    : $j(this.selected()));
807  container = $j(container).parentsOrSelf(jqexpr);
808  $j(container).toggleClass(sClass);
809
810  if(!$j(container).attr(sWYM_CLASS)) $j(container).removeAttr(this._class);
811
812};
813
814/* @name findUp
815 * @description Returns the first parent or self container, based on its type
816 */
817Wymeditor.prototype.findUp = function(mFilter) {
818
819  //mFilter is a string or an array of strings
820
821  var container = this.container();
822  var tagname = container.tagName.toLowerCase();
823 
824  if(typeof(mFilter) == sWYM_STRING) {
825
826    while(tagname != mFilter && tagname != sWYM_BODY) {
827   
828      container = container.parentNode;
829      tagname = container.tagName.toLowerCase();
830    }
831 
832  } else {
833 
834    var bFound = false;
835   
836    while(!bFound && tagname != sWYM_BODY) {
837      for(var i = 0; i<mFilter.length; i++) {
838        if(tagname == mFilter[i]) {
839          bFound = true;
840          break;
841        }
842      }
843      if(!bFound) {
844        container = container.parentNode;
845        tagname = container.tagName.toLowerCase();
846      }
847    }
848  }
849 
850  if(tagname != sWYM_BODY) return(container);
851  else return(null);
852};
853
854/* @name switchTo
855 * @description Switch the node's type
856 */
857Wymeditor.prototype.switchTo = function(node,sType) {
858
859  var newNode = this._doc.createElement(sType);
860  var html = $j(node).html();
861  node.parentNode.replaceChild(newNode,node);
862  $j(newNode).html(html);
863  this.setFocusToNode(newNode);
864};
865
866Wymeditor.prototype.replaceStrings = function(sVal) {
867
868  for (var key in aWYM_STRINGS[this._options.sLang]) {
869    var rExp = new RegExp(this._options.sStringDelimiterLeft + key
870    + this._options.sStringDelimiterRight, "g");
871    sVal = sVal.replace(rExp, aWYM_STRINGS[this._options.sLang][key]);
872  }
873  return(sVal);
874};
875
876Wymeditor.prototype.encloseString = function(sVal) {
877
878  return(this._options.sStringDelimiterLeft
879    + sVal
880    + this._options.sStringDelimiterRight);
881};
882
883/* @name status
884 * @description Prints a status message
885 */
886Wymeditor.prototype.status = function(sMessage) {
887
888  //print status message
889  $j(this._box).find(this._options.sStatusSelector).html(sMessage);
890};
891
892/* @name update
893 * @description Updates the element and textarea values
894 */
895Wymeditor.prototype.update = function() {
896
897  var html = this.xhtml();
898  $j(this._element).val(html);
899  $j(this._box).find(this._options.sHtmlValSelector).val(html);
900};
901
902/* @name dialog
903 * @description Opens a dialog box
904 */
905Wymeditor.prototype.dialog = function(sType) {
906 
907  var wDialog = window.open(
908    '',
909    'dialog',
910    this._wym._options.sDialogFeatures);
911
912  if(wDialog) {
913
914    var sBodyHtml = "";
915   
916    switch(sType) {
917
918      case(sWYM_DIALOG_LINK):
919        sBodyHtml = this._options.sDialogLinkHtml;
920      break;
921      case(sWYM_DIALOG_IMAGE):
922        sBodyHtml = this._options.sDialogImageHtml;
923      break;
924      case(sWYM_DIALOG_TABLE):
925        sBodyHtml = this._options.sDialogTableHtml;
926      break;
927      case(sWYM_DIALOG_PASTE):
928        sBodyHtml = this._options.sDialogPasteHtml;
929      break;
930      case(sWYM_PREVIEW):
931        sBodyHtml = this._options.sDialogPreviewHtml;
932      break;
933    }
934   
935    //construct the dialog
936    var sDialogHtml = this._options.sDialogHtml;
937    sDialogHtml = sDialogHtml
938      .replace(sWYM_BASE_PATH, this._options.sBasePath)
939      .replace(sWYM_CSS_PATH, this._options.sCssPath)
940      .replace(sWYM_JQUERY_PATH, this._options.sJqueryPath)
941      .replace(sWYM_DIALOG_TITLE, this.encloseString(sType))
942      .replace(sWYM_DIALOG_BODY, sBodyHtml)
943      .replace(sWYM_INDEX, this._index);
944     
945    sDialogHtml = this.replaceStrings(sDialogHtml);
946   
947    var doc = wDialog.document;
948    doc.write(sDialogHtml);
949    doc.close();
950  }
951};
952
953/* @name toggleHtml
954 * @description Shows/Hides the HTML
955 */
956Wymeditor.prototype.toggleHtml = function() {
957
958  $j(this._box).find(this._options.sHtmlSelector).toggle();
959};
960
961Wymeditor.prototype.uniqueStamp = function() {
962        var now=new Date();
963        return("wym-" + now.getTime());
964};
965
966Wymeditor.prototype.paste = function(sData) {
967
968  var sTmp;
969  var container = this.selected();
970       
971  //split the data, using double newlines as the separator
972  var aP = sData.split(this._newLine + this._newLine);
973  var rExp = new RegExp(this._newLine, "g");
974
975  //add a P for each item
976  for(x = aP.length - 1; x >= 0; x--) {
977    sTmp = aP[x];
978    //simple newlines are replaced by a break
979    sTmp = sTmp.replace(rExp, "<br />");
980    $j(container).after("<p>" + sTmp + "</p>");
981  }
982};
983
984Wymeditor.prototype.addCssRules = function(doc, aCss) {
985  var styles = doc.styleSheets[0];
986  if(styles) {
987    for(var i = 0; i < aCss.length; i++) {
988      var oCss = aCss[i];
989      if(oCss.name && oCss.css) this.addCssRule(styles, oCss);
990    }
991  }
992};
993
994/********** CONFIGURATION **********/
995
996Wymeditor.prototype.computeBasePath = function() {
997  return $j($j.grep($j('script'), function(s){
998    return (s.src && s.src.match(/jquery\.wymeditor\.js(\?.*)?$/ ))
999  })).attr('src').replace(/jquery\.wymeditor\.js(\?.*)?$/, '');
1000};
1001
1002Wymeditor.prototype.computeJqueryPath = function() {
1003  return $j($j.grep($j('script'), function(s){
1004    return (s.src && s.src.match(/jquery\.js(\?.*)?$/ ))
1005  })).attr('src');
1006};
1007
1008Wymeditor.prototype.computeCssPath = function() {
1009  return $j($j.grep($j('link'), function(s){
1010   return (s.href && s.href.match(/wymeditor\/skins\/(.*)screen\.css(\?.*)?$/ ))
1011  })).attr('href');
1012};
1013
1014Wymeditor.prototype.getBrowserSpecificWymInstance = function()
1015{
1016  if(this.loadBrowserSettings()){   
1017    if(eval('typeof '+this._browserDriverName+" != 'function' ")) {
1018      eval($j.ajax({url:this._options.sBasePath+'jquery.wymeditor.'+this._browserName+'.js',async:false}).responseText);
1019      window[this._browserDriverName] = eval(this._browserDriverName);
1020    }
1021    var wym = this;
1022    return eval('new '+this._browserDriverName+'(wym)');
1023  }
1024  // Todo: handle unsuported browsers
1025  return false;
1026};
1027
1028Wymeditor.prototype.loadBrowserSettings = function()
1029{
1030  var driverClasses = {'msie':'WymClassExplorer','mozilla':'WymClassMozilla',
1031  'opera':'WymClassOpera','safari':'WymClassSafari'};
1032  for(var type in $j.browser){
1033    if($j.browser[type] == true && driverClasses[type]){
1034      this._browserDriverName = driverClasses[type];
1035      this._browserName = type == 'msie' ? 'explorer' : type;
1036      return true;
1037    }
1038  }
1039  return false;
1040}
1041
1042Wymeditor.prototype.loadXhtmlParser = function(WymClass)
1043{
1044  if(typeof XhtmlSaxListener != 'function'){
1045    // This is the only way to get loaded functions in the global scope until jQuery.globalEval works in safari
1046   eval($j.ajax({url:this._options.sBasePath+'xhtml_parser.js',async:false}).responseText);
1047    window.XmlHelper = XmlHelper;
1048    window.XhtmlValidator = XhtmlValidator;
1049    window.ParallelRegex = ParallelRegex;
1050    window.StateStack = StateStack;
1051    window.Lexer = Lexer;
1052    window.XhtmlLexer = XhtmlLexer;
1053    window.XhtmlParser = XhtmlParser;
1054    window.XhtmlSaxListener = XhtmlSaxListener;
1055   
1056  }
1057  var SaxListener = new XhtmlSaxListener();
1058  SaxListener.extendObject(WymClass);
1059  this.parser = new XhtmlParser(SaxListener);
1060}
1061
1062Wymeditor.prototype.configureEditorUsingRawCss = function()
1063{
1064  if(typeof WymCssParser != 'function'){
1065    eval($j.ajax({url:this._options.sBasePath+'wym_css_parser.js',async:false}).responseText);
1066    window.WymCssLexer = WymCssLexer;
1067    window.WymCssParser = WymCssParser;
1068  }
1069  var CssParser = new WymCssParser();
1070  if(this._options.sWymStylesheet){
1071    CssParser.parse($j.ajax({url: this._options.sWymStylesheet,async:false}).responseText);
1072  }else{
1073    CssParser.parse(this._options.sWymCss, false);
1074  }
1075  if(this._options.aClassesItems.length == 0) {
1076    this._options.aClassesItems = CssParser.css_settings.aClassesItems;
1077  }
1078  if(this._options.aEditorCss.length == 0) {
1079    this._options.aEditorCss = CssParser.css_settings.aEditorCss;     
1080  }
1081  if(this._options.aDialogCss.length == 0) {
1082    this._options.aDialogCss = CssParser.css_settings.aDialogCss;
1083  }
1084}
1085
1086/********** EVENTS **********/
1087
1088Wymeditor.prototype.listen = function() {
1089
1090  $j(this._doc.body).find("*").bind("mouseup", this.mouseup);
1091};
1092
1093//mouseup handler
1094Wymeditor.prototype.mouseup = function(evt) {
1095 
1096  var wym = aWYM_INSTANCES[this.ownerDocument.title];
1097  if(this.tagName.toLowerCase() == sWYM_IMG) wym._selected_image = this;
1098  else wym._selected_image = null;
1099  evt.stopPropagation();
1100};
1101
1102/********** SKINS **********/
1103
1104Wymeditor.prototype.skin = function() {
1105
1106  switch(this._options.sSkin) {
1107 
1108    case sWYM_DEFAULT_SKIN:
1109   
1110      $j(this._box).addClass("wym_skin_default");
1111     
1112      //render following sections as panels
1113      $j(this._box).find(this._options.sClassesSelector)
1114        .addClass("wym_panel");
1115
1116      //render following sections as buttons
1117      $j(this._box).find(this._options.sToolsSelector)
1118        .addClass("wym_buttons");
1119
1120      //render following sections as dropdown menus
1121      $j(this._box).find(this._options.sContainersSelector)
1122        .addClass("wym_dropdown")
1123        .find(sWYM_H2)
1124        .append("<span>&nbsp;&gt;</span>");
1125
1126      // auto add some margin to the main area sides if left area
1127      // or right area are not empty (if they contain sections)
1128      $j(this._box).find("div.wym_area_right ul")
1129        .parents("div.wym_area_right").show()
1130        .parents(this._options.sBoxSelector)
1131        .find("div.wym_area_main")
1132        .css({"margin-right": "155px"});
1133
1134      $j(this._box).find("div.wym_area_left ul")
1135        .parents("div.wym_area_left").show()
1136        .parents(this._options.sBoxSelector)
1137        .find("div.wym_area_main")
1138        .css({"margin-left": "155px"});
1139
1140      //make hover work under IE < 7
1141      $j(this._box).find(".wym_section").hover(function(){
1142        $j(this).addClass("hover");
1143      },function(){
1144        $j(this).removeClass("hover");
1145      });
1146   
1147    break;
1148 
1149  }
1150
1151};
1152
1153/********** DIALOGS **********/
1154
1155function fWYM_INIT_DIALOG(index) {
1156
1157    var wym = window.opener.aWYM_INSTANCES[index];
1158    var doc = window.document;
1159    var oSel = wym.selected();
1160    var sStamp = wym.uniqueStamp();
1161   
1162    //pre-init functions
1163    if($j.isFunction(wym._options.fPreInitDialog))
1164      wym._options.fPreInitDialog(wym,window);
1165   
1166    //add css rules from options
1167    var styles = doc.styleSheets[0];
1168    var aCss = eval(wym._options.aDialogCss);
1169
1170    wym.addCssRules(doc, aCss);
1171   
1172    if(oSel) {
1173            $j(wym._options.sHrefSelector).val($j(oSel).attr(sWYM_HREF));
1174            $j(wym._options.sSrcSelector).val($j(oSel).attr(sWYM_SRC));
1175            $j(wym._options.sTitleSelector).val($j(oSel).attr(sWYM_TITLE));
1176            $j(wym._options.sAltSelector).val($j(oSel).attr(sWYM_ALT));
1177    }
1178
1179    $j(wym._options.sDialogLinkSelector + " "
1180        + wym._options.sSubmitSelector).click(function() {
1181       
1182        var sUrl = $j(wym._options.sHrefSelector).val();
1183        if(sUrl.length > 0) {
1184            wym._exec(sWYM_CREATE_LINK, sStamp);
1185            var link = $j(wym._doc.body).find("a[@href=" + sStamp + "]");
1186            link.attr(sWYM_HREF, sUrl);
1187            link.attr(sWYM_TITLE, $j(wym._options.sTitleSelector).val());
1188        }
1189        window.close();
1190    });
1191   
1192    $j(wym._options.sDialogImageSelector + " "
1193        + wym._options.sSubmitSelector).click(function() {
1194       
1195        var sUrl = $j(wym._options.sSrcSelector).val();
1196        if(sUrl.length > 0) {
1197            wym._exec(sWYM_INSERT_IMAGE, sStamp);
1198            var image = $j(wym._doc.body).find("img[@src=" + sStamp + "]");
1199            image.attr(sWYM_SRC, sUrl);
1200            image.attr(sWYM_TITLE, $j(wym._options.sTitleSelector).val());
1201            image.attr(sWYM_ALT, $j(wym._options.sAltSelector).val());
1202        }
1203        window.close();
1204    });
1205   
1206    $j(wym._options.sDialogTableSelector + " "
1207        + wym._options.sSubmitSelector).click(function() {
1208       
1209        var iRows = $j(wym._options.sRowsSelector).val();
1210        var iCols = $j(wym._options.sColsSelector).val();
1211       
1212        if(iRows > 0 && iCols > 0) {
1213       
1214            var table = wym._doc.createElement(sWYM_TABLE);
1215            var newRow = null;
1216                        var newCol = null;
1217                       
1218                        var sCaption = $j(wym._options.sCaptionSelector).val();
1219                       
1220                        //we create the caption
1221                        var newCaption = table.createCaption();
1222                        newCaption.innerHTML = sCaption;
1223                       
1224                        //we create the rows and cells
1225                        for(x=0; x<iRows; x++) {
1226                                newRow = table.insertRow(x);
1227                                for(y=0; y<iCols; y++) {newRow.insertCell(y);}
1228                        }
1229           
1230            if(oSel) $j(oSel).after(table);
1231        }
1232        window.close();
1233    });
1234   
1235    $j(wym._options.sDialogPasteSelector + " "
1236        + wym._options.sSubmitSelector).click(function() {
1237       
1238        var sText = $j(wym._options.sTextSelector).val();
1239        wym.paste(sText);
1240        window.close();
1241    });
1242   
1243    $j(wym._options.sDialogPreviewSelector + " "
1244        + wym._options.sPreviewSelector)
1245        .html(wym.xhtml());
1246   
1247    //cancel button
1248    $j(wym._options.sCancelSelector).mousedown(function() {
1249        window.close();
1250    });
1251   
1252    //pre-init functions
1253    if($j.isFunction(wym._options.fPostInitDialog))
1254      wym._options.fPostInitDialog(wym,window);
1255};
1256
1257
1258/********** HELPERS **********/
1259
1260// Returns true if it is a text node with whitespaces only
1261$j.fn.isPhantomNode = function() {
1262  if (this[0].nodeType == 3)
1263    return !(/[^\t\n\r ]/.test(this[0].data));
1264
1265  return false;
1266};
1267
1268function isPhantomNode(n) {
1269  if (n.nodeType == 3)
1270    return !(/[^\t\n\r ]/.test(n.data));
1271
1272  return false;
1273};
1274
1275// Returns the Parents or the node itself
1276// jqexpr = a jQuery expression
1277$j.fn.parentsOrSelf = function(jqexpr) {
1278  var n = this;
1279
1280  if (n[0].nodeType == 3)
1281    n = n.parents().lt(1);
1282
1283//  if (n.is(jqexpr)) // XXX should work, but doesn't (probably a jQuery bug)
1284  if (n.filter(jqexpr).size() == 1)
1285    return n;
1286  else
1287    return n.parents(jqexpr).lt(1);
1288};
1289
1290String.prototype.insertAt = function(sInserted, iPos) {
1291  return(this.substr(0,iPos) + sInserted + this.substring(iPos));
1292};
1293
1294// from http://forum.de.selfhtml.org/archiv/2004/3/t76079/#m438193 (2007-02-06)
1295Array.prototype.contains = function (elem) {
1296//  var i;
1297  for (var i = 0; i < this.length; i++) {
1298  if (this[i] === elem) {
1299    return true;
1300  }
1301  }
1302  return false;
1303};
1304
1305Array.prototype.indexOf = function (item) {
1306        var ret=-1;
1307        for(var i = 0; i < this.length; i++) {
1308    if (this[i] == item) {
1309      ret=i; break;
1310    }
1311  }
1312        return(ret);
1313};
1314
1315Array.prototype.findByName = function (name) {
1316  for(var i = 0; i < this.length; i++) {
1317    var oItem = this[i];
1318    if(oItem.name == name) {
1319      return(oItem);
1320    }
1321  }
1322  return(null);
1323};
1324
1325Object.prototype.extendObject = function (oSuper) {
1326  for (sProperty in oSuper) {
1327    if(sProperty != 'type'){
1328      this[sProperty] = oSuper[sProperty];
1329    }
1330  }
1331};
1332
1333String.prototype.trim = function() {
1334  return this.replace(/^(\s*)|(\s*)$/gm,'');
1335};
1336
Note: See TracBrowser for help on using the repository browser.