Changeset 659 for trunk


Ignore:
Timestamp:
01/15/07 14:28:57 (13 years ago)
Author:
gogo
Message:

Make htmlarea.js just load XinhaCore?.js instead of being a copy of it.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/examples/testbed.html

    r649 r659  
    3636 
    3737  <!-- Load up the actual editor core --> 
    38   <script type="text/javascript" src="../XinhaCore.js"></script> 
     38  <script type="text/javascript" src="../htmlarea.js"></script> 
    3939 
    4040  <script type="text/javascript"> 
  • trunk/htmlarea.js

    r655 r659  
    1 /*  IMPORTANT:  
    2      
    3     THIS FILE IS A COPY OF XinhaCore.js FOR BACKWARD COMPATIBILITY PURPOSES ONLY 
    4     IF YOU MAKE CHANGES TO THE CODE, DO IT THERE! 
    5  
    6 */  
    71  
    82  /*--------------------------------------:noTabs=true:tabSize=2:indentSize=2:-- 
    9     --  Xinha (is not htmlArea) - http://xinha.gogo.co.nz/ 
    10     -- 
    11     --  Use of Xinha is granted by the terms of the htmlArea License (based on 
    12     --  BSD license)  please read license.txt in this package for details. 
    13     -- 
    14     --  Xinha was originally based on work by Mihai Bazon which is: 
    15     --      Copyright (c) 2003-2004 dynarch.com. 
    16     --      Copyright (c) 2002-2003 interactivetools.com, inc. 
    17     --      This copyright notice MUST stay intact for use. 
    18     -- 
    19     --  Developers - Coding Style: 
    20     --   For the sake of not committing needlessly conflicting changes, 
    21     -- 
    22     --   * New code to be indented with 2 spaces ("soft tab"). 
    23     --   * New code preferably uses BSD-Style Bracing 
    24     --      if ( foo ) 
    25     --      { 
    26     --        bar(); 
    27     --      } 
    28     --   * Don't change brace styles unless you're working on the non BSD-Style 
    29     --     area (so we don't get spurious changes in line numbering). 
    30     --   * Don't change indentation unless you're working on the badly indented 
    31     --     area (so we don't get spurious changes of large blocks of code). 
    32     --   * Jedit is the recommended editor, a comment of this format should be 
    33     --     included in the top 10 lines of the file (see the embedded edit mode) 
     3    --  COMPATABILITY FILE 
     4    --  htmlarea.js is now XinhaCore.js   
    345    -- 
    356    --  $HeadURL$ 
     
    389    --  $LastChangedBy$ 
    3910    --------------------------------------------------------------------------*/ 
    40  
    41 Xinha.version = 
    42 { 
    43   'Release'   : 'Trunk', 
    44   'Head'      : '$HeadURL$'.replace(/^[^:]*: (.*) \$$/, '$1'), 
    45   'Date'      : '$LastChangedDate$'.replace(/^[^:]*: ([0-9-]*) ([0-9:]*) ([+0-9]*) \((.*)\) \$/, '$4 $2 $3'), 
    46   'Revision'  : '$LastChangedRevision$'.replace(/^[^:]*: (.*) \$$/, '$1'), 
    47   'RevisionBy': '$LastChangedBy$'.replace(/^[^:]*: (.*) \$$/, '$1') 
    48 }; 
    49  
     11     
    5012if ( typeof _editor_url == "string" ) 
    5113{ 
     
    5921} 
    6022 
    61 // make sure we have a language 
    62 if ( typeof _editor_lang == "string" ) 
    63 { 
    64   _editor_lang = _editor_lang.toLowerCase(); 
    65 } 
    66 else 
    67 { 
    68   _editor_lang = "en"; 
    69 } 
    70  
    71 // skin stylesheet to load 
    72 if ( typeof _editor_skin !== "string" ) 
    73 { 
    74   _editor_skin = ""; 
    75 } 
    76  
    77 var __xinhas = []; 
    78  
    79 // browser identification 
    80 Xinha.agt       = navigator.userAgent.toLowerCase(); 
    81 Xinha.is_ie        = ((Xinha.agt.indexOf("msie") != -1) && (Xinha.agt.indexOf("opera") == -1)); 
    82 Xinha.is_opera  = (Xinha.agt.indexOf("opera") != -1); 
    83 Xinha.is_mac       = (Xinha.agt.indexOf("mac") != -1); 
    84 Xinha.is_mac_ie = (Xinha.is_ie && Xinha.is_mac); 
    85 Xinha.is_win_ie = (Xinha.is_ie && !Xinha.is_mac); 
    86 Xinha.is_gecko  = (navigator.product == "Gecko"); 
    87 Xinha.isRunLocally = document.URL.toLowerCase().search(/^file:/) != -1; 
    88 if ( Xinha.isRunLocally ) 
    89 { 
    90   alert('Xinha *must* be installed on a web server. Locally opened files (those that use the "file://" protocol) cannot properly function. Xinha will try to initialize but may not be correctly loaded.'); 
    91 } 
    92  
    93 // Creates a new Xinha object.  Tries to replace the textarea with the given 
    94 // ID with it. 
    95 function Xinha(textarea, config) 
    96 { 
    97   if ( !textarea ) 
    98   { 
    99     throw("Tried to create Xinha without textarea specified."); 
    100   } 
    101  
    102   if ( Xinha.checkSupportedBrowser() ) 
    103   { 
    104     if ( typeof config == "undefined" ) 
    105     { 
    106       this.config = new Xinha.Config(); 
    107     } 
    108     else 
    109     { 
    110       this.config = config; 
    111     } 
    112     this._htmlArea = null; 
    113  
    114     if ( typeof textarea != 'object' ) 
    115     { 
    116       textarea = Xinha.getElementById('textarea', textarea); 
    117     } 
    118     this._textArea = textarea; 
    119     this._textArea.spellcheck = false; 
    120         
    121     // Before we modify anything, get the initial textarea size 
    122     this._initial_ta_size = 
    123     { 
    124       w: textarea.style.width  ? textarea.style.width  : ( textarea.offsetWidth  ? ( textarea.offsetWidth  + 'px' ) : ( textarea.cols + 'em') ), 
    125       h: textarea.style.height ? textarea.style.height : ( textarea.offsetHeight ? ( textarea.offsetHeight + 'px' ) : ( textarea.rows + 'em') ) 
    126     }; 
    127     // Create the loading message elements 
    128     if ( this.config.showLoading ) 
    129     { 
    130       // Create and show the main loading message and the sub loading message for details of loading actions 
    131       // global element 
    132       var loading_message = document.createElement("div"); 
    133       loading_message.id = "loading_" + textarea.name; 
    134       loading_message.className = "loading"; 
    135       try 
    136       { 
    137         // how can i find the real width in pixels without % or em *and* with no visual errors ? 
    138         // for instance, a textarea with a style="width:100%" and the body padding > 0 result in a horizontal scrollingbar while loading 
    139         // A few lines above seems to indicate offsetWidth is not always set 
    140         loading_message.style.width = textarea.offsetWidth + 'px'; 
    141       } 
    142       catch (ex) 
    143       { 
    144         // offsetWidth seems not set, so let's use this._initial_ta_size.w, but sometimes it may be too huge width 
    145         loading_message.style.width = this._initial_ta_size.w; 
    146       } 
    147       loading_message.style.left = Xinha.findPosX(textarea) +  'px'; 
    148       loading_message.style.top = (Xinha.findPosY(textarea) + parseInt(this._initial_ta_size.h, 10) / 2) +  'px'; 
    149       // main static message 
    150       var loading_main = document.createElement("div"); 
    151       loading_main.className = "loading_main"; 
    152       loading_main.id = "loading_main_" + textarea.name; 
    153       loading_main.appendChild(document.createTextNode(Xinha._lc("Loading in progress. Please wait !"))); 
    154       // sub dynamic message 
    155       var loading_sub = document.createElement("div"); 
    156       loading_sub.className = "loading_sub"; 
    157       loading_sub.id = "loading_sub_" + textarea.name; 
    158       loading_sub.appendChild(document.createTextNode(Xinha._lc("Constructing main object"))); 
    159       loading_message.appendChild(loading_main); 
    160       loading_message.appendChild(loading_sub); 
    161       document.body.appendChild(loading_message); 
    162       this.setLoadingMessage("Constructing object"); 
    163     } 
    164  
    165     this._editMode = "wysiwyg"; 
    166     this.plugins = {}; 
    167     this._timerToolbar = null; 
    168     this._timerUndo = null; 
    169     this._undoQueue = [this.config.undoSteps]; 
    170     this._undoPos = -1; 
    171     this._customUndo = true; 
    172     this._mdoc = document; // cache the document, we need it in plugins 
    173     this.doctype = ''; 
    174     this.__htmlarea_id_num = __xinhas.length; 
    175     __xinhas[this.__htmlarea_id_num] = this; 
    176  
    177     this._notifyListeners = {}; 
    178  
    179     // Panels 
    180     var panels =  
    181     { 
    182       right: 
    183       { 
    184         on: true, 
    185         container: document.createElement('td'), 
    186         panels: [] 
    187       }, 
    188       left: 
    189       { 
    190         on: true, 
    191         container: document.createElement('td'), 
    192         panels: [] 
    193       }, 
    194       top: 
    195       { 
    196         on: true, 
    197         container: document.createElement('td'), 
    198         panels: [] 
    199       }, 
    200       bottom: 
    201       { 
    202         on: true, 
    203         container: document.createElement('td'), 
    204         panels: [] 
    205       } 
    206     }; 
    207  
    208     for ( var i in panels ) 
    209     { 
    210       if(!panels[i].container) { continue; } // prevent iterating over wrong type 
    211       panels[i].div = panels[i].container; // legacy 
    212       panels[i].container.className = 'panels ' + i; 
    213       Xinha.freeLater(panels[i], 'container'); 
    214       Xinha.freeLater(panels[i], 'div'); 
    215     } 
    216     // finally store the variable 
    217     this._panels = panels; 
    218  
    219     Xinha.freeLater(this, '_textArea'); 
    220   } 
    221 } 
    222  
    223 Xinha.onload = function() { }; 
    224 Xinha.init = function() { Xinha.onload(); }; 
    225  
    226 // cache some regexps 
    227 Xinha.RE_tagName  = /(<\/|<)\s*([^ \t\n>]+)/ig; 
    228 Xinha.RE_doctype  = /(<!doctype((.|\n)*?)>)\n?/i; 
    229 Xinha.RE_head     = /<head>((.|\n)*?)<\/head>/i; 
    230 Xinha.RE_body     = /<body[^>]*>((.|\n|\r|\t)*?)<\/body>/i; 
    231 Xinha.RE_Specials = /([\/\^$*+?.()|{}[\]])/g; 
    232 Xinha.RE_email    = /[_a-zA-Z\d\-\.]{3,}@[_a-zA-Z\d\-]{2,}(\.[_a-zA-Z\d\-]{2,})+/i; 
    233 Xinha.RE_url      = /(https?:\/\/)?(([a-z0-9_]+:[a-z0-9_]+@)?[a-z0-9_-]{2,}(\.[a-z0-9_-]{2,}){2,}(:[0-9]+)?(\/\S+)*)/i; 
    234  
    235 Xinha.Config = function() 
    236 { 
    237   var cfg = this; 
    238   this.version = Xinha.version.Revision; 
    239  
    240   // Width and Height 
    241   //  you may set these as follows 
    242   //  width = 'auto'      -- the width of the original textarea will be used 
    243   //  width = 'toolbar'   -- the width of the toolbar will be used 
    244   //  width = '<css measure>' -- use any css measurement, eg width = '75%' 
    245   // 
    246   //  height = 'auto'     -- the height of the original textarea 
    247   //  height = '<css measure>' -- any css measurement, eg height = '480px' 
    248   this.width  = "auto"; 
    249   this.height = "auto"; 
    250  
    251   // the next parameter specifies whether the toolbar should be included 
    252   // in the size above, or are extra to it.  If false then it's recommended 
    253   // to have explicit pixel sizes above (or on your textarea and have auto above) 
    254   this.sizeIncludesBars = true; 
    255  
    256   // the next parameter specifies whether the panels should be included 
    257   // in the size above, or are extra to it.  If false then it's recommended 
    258   // to have explicit pixel sizes above (or on your textarea and have auto above) 
    259   this.sizeIncludesPanels = true; 
    260  
    261   // each of the panels has a dimension, for the left/right it's the width 
    262   // for the top/bottom it's the height. 
    263   // 
    264   // WARNING: PANEL DIMENSIONS MUST BE SPECIFIED AS PIXEL WIDTHS 
    265   this.panel_dimensions = 
    266   { 
    267     left:   '200px', // Width 
    268     right:  '200px', 
    269     top:    '100px', // Height 
    270     bottom: '100px' 
    271   }; 
    272  
    273   // enable creation of a status bar? 
    274   this.statusBar = true; 
    275  
    276   // intercept ^V and use the Xinha paste command 
    277   // If false, then passes ^V through to browser editor widget 
    278   this.htmlareaPaste = false; 
    279  
    280   this.mozParaHandler = 'best'; // set to 'built-in', 'dirty' or 'best' 
    281                                 // built-in: will (may) use 'br' instead of 'p' tags 
    282                                 // dirty   : will use p and work good enough for the majority of cases, 
    283                                 // best    : works the best, but it's about 12kb worth of javascript 
    284                                 //   and will probably be slower than 'dirty'.  This is the "EnterParagraphs" 
    285                                 //   plugin from "hipikat", rolled in to be part of the core code 
    286  
    287   // maximum size of the undo queue 
    288   this.undoSteps = 20; 
    289  
    290   // the time interval at which undo samples are taken 
    291   this.undoTimeout = 500;       // 1/2 sec. 
    292  
    293   // set this to true if you want to explicitly right-justify when  
    294   // setting the text direction to right-to-left 
    295   this.changeJustifyWithDirection = false; 
    296  
    297   // if true then Xinha will retrieve the full HTML, starting with the 
    298   // <HTML> tag. 
    299   this.fullPage = false; 
    300  
    301   // style included in the iframe document 
    302   this.pageStyle = ""; 
    303  
    304   // external stylesheets to load (REFERENCE THESE ABSOLUTELY) 
    305   this.pageStyleSheets = []; 
    306  
    307   // specify a base href for relative links 
    308   this.baseHref = null; 
    309  
    310   // when the editor is in different directory depth as the edited page relative image sources 
    311   // will break the display of your images 
    312   // this fixes an issue where Mozilla converts the urls of images and links that are on the same server  
    313   // to relative ones (../) when dragging them around in the editor (Ticket #448) 
    314   this.expandRelativeUrl = true; 
    315    
    316   //   we can strip the base href out of relative links to leave them relative, reason for this 
    317   //   especially if you don't specify a baseHref is that mozilla at least (& IE ?) will prefix 
    318   //   the baseHref to any relative links to make them absolute, which isn't what you want most the time. 
    319   this.stripBaseHref = true; 
    320  
    321   // and we can strip the url of the editor page from named links (eg <a href="#top">...</a>) 
    322   //  reason for this is that mozilla at least (and IE ?) prefixes location.href to any 
    323   //  that don't have a url prefixing them 
    324   this.stripSelfNamedAnchors = true; 
    325  
    326   // sometimes high-ascii in links can cause problems for servers (basically they don't recognise them) 
    327   //  so you can use this flag to ensure that all characters other than the normal ascii set (actually 
    328   //  only ! through ~) are escaped in URLs to % codes 
    329   this.only7BitPrintablesInURLs = true; 
    330  
    331   // if you are putting the HTML written in Xinha into an email you might want it to be 7-bit 
    332   //  characters only.  This config option (off by default) will convert all characters consuming 
    333   //  more than 7bits into UNICODE decimal entity references (actually it will convert anything 
    334   //  below <space> (chr 20) except cr, lf and tab and above <tilde> (~, chr 7E)) 
    335   this.sevenBitClean = false; 
    336  
    337   // sometimes we want to be able to replace some string in the html comng in and going out 
    338   //  so that in the editor we use the "internal" string, and outside and in the source view 
    339   //  we use the "external" string  this is useful for say making special codes for 
    340   //  your absolute links, your external string might be some special code, say "{server_url}" 
    341   //  an you say that the internal represenattion of that should be http://your.server/ 
    342   this.specialReplacements = {}; // { 'external_string' : 'internal_string' } 
    343  
    344   // set to true if you want Word code to be cleaned upon Paste 
    345   this.killWordOnPaste = true; 
    346  
    347   // enable the 'Target' field in the Make Link dialog 
    348   this.makeLinkShowsTarget = true; 
    349  
    350   // CharSet of the iframe, default is the charset of the document 
    351   this.charSet = Xinha.is_gecko ? document.characterSet : document.charset; 
    352  
    353   // URL-s 
    354   this.imgURL = "images/"; 
    355   this.popupURL = "popups/"; 
    356  
    357   // remove tags (these have to be a regexp, or null if this functionality is not desired) 
    358   this.htmlRemoveTags = null; 
    359  
    360   // Turning this on will turn all "linebreak" and "separator" items in your toolbar into soft-breaks, 
    361   // this means that if the items between that item and the next linebreak/separator can 
    362   // fit on the same line as that which came before then they will, otherwise they will 
    363   // float down to the next line. 
    364  
    365   // If you put a linebreak and separator next to each other, only the separator will 
    366   // take effect, this allows you to have one toolbar that works for both flowToolbars = true and false 
    367   // infact the toolbar below has been designed in this way, if flowToolbars is false then it will 
    368   // create explictly two lines (plus any others made by plugins) breaking at justifyleft, however if 
    369   // flowToolbars is false and your window is narrow enough then it will create more than one line 
    370   // even neater, if you resize the window the toolbars will reflow.  Niiiice. 
    371  
    372   this.flowToolbars = true; 
    373    
    374   // set to true if you want the loading panel to show at startup 
    375   this.showLoading = false; 
    376  
    377   // set to false if you want to allow JavaScript in the content, otherwise <script> tags are stripped out 
    378   this.stripScripts = true; 
    379  
    380   // see if the text just typed looks like a URL, or email address 
    381   // and link it appropriatly 
    382   this.convertUrlsToLinks = false; 
    383  
    384   // size of color picker cells 
    385   this.colorPickerCellSize = '6px'; 
    386   // granularity of color picker cells (number per column/row) 
    387   this.colorPickerGranularity = 18; 
    388   // position of color picker from toolbar button 
    389   this.colorPickerPosition = 'bottom,right'; 
    390   // set to true to show websafe checkbox in picker 
    391   this.colorPickerWebSafe = false; 
    392   // number of recent colors to remember 
    393   this.colorPickerSaveColors = 20; 
    394  
    395   /** CUSTOMIZING THE TOOLBAR 
    396    * ------------------------- 
    397    * 
    398    * It is recommended that you customize the toolbar contents in an 
    399    * external file (i.e. the one calling Xinha) and leave this one 
    400    * unchanged.  That's because when we (InteractiveTools.com) release a 
    401    * new official version, it's less likely that you will have problems 
    402    * upgrading Xinha. 
    403    */ 
    404   this.toolbar = 
    405   [ 
    406     ["popupeditor"], 
    407     ["separator","formatblock","fontname","fontsize","bold","italic","underline","strikethrough"], 
    408     ["separator","forecolor","hilitecolor","textindicator"], 
    409     ["separator","subscript","superscript"], 
    410     ["linebreak","separator","justifyleft","justifycenter","justifyright","justifyfull"], 
    411     ["separator","insertorderedlist","insertunorderedlist","outdent","indent"], 
    412     ["separator","inserthorizontalrule","createlink","insertimage","inserttable"], 
    413     ["linebreak","separator","undo","redo","selectall","print"], (Xinha.is_gecko ? [] : ["cut","copy","paste","overwrite","saveas"]), 
    414     ["separator","killword","clearfonts","removeformat","toggleborders","splitblock","lefttoright", "righttoleft"], 
    415     ["separator","htmlmode","showhelp","about"] 
    416   ]; 
    417  
    418  
    419   this.fontname = 
    420   { 
    421     "&mdash; font &mdash;": '', 
    422     "Arial":             'arial,helvetica,sans-serif', 
    423     "Courier New":         'courier new,courier,monospace', 
    424     "Georgia":         'georgia,times new roman,times,serif', 
    425     "Tahoma":            'tahoma,arial,helvetica,sans-serif', 
    426     "Times New Roman": 'times new roman,times,serif', 
    427     "Verdana":         'verdana,arial,helvetica,sans-serif', 
    428     "impact":            'impact', 
    429     "WingDings":             'wingdings' 
    430   }; 
    431  
    432   this.fontsize = 
    433   { 
    434     "&mdash; size &mdash;": "", 
    435     "1 (8 pt)" : "1", 
    436     "2 (10 pt)": "2", 
    437     "3 (12 pt)": "3", 
    438     "4 (14 pt)": "4", 
    439     "5 (18 pt)": "5", 
    440     "6 (24 pt)": "6", 
    441     "7 (36 pt)": "7" 
    442   }; 
    443  
    444   this.formatblock = 
    445   { 
    446     "&mdash; format &mdash;": "", 
    447     "Heading 1": "h1", 
    448     "Heading 2": "h2", 
    449     "Heading 3": "h3", 
    450     "Heading 4": "h4", 
    451     "Heading 5": "h5", 
    452     "Heading 6": "h6", 
    453     "Normal"   : "p", 
    454     "Address"  : "address", 
    455     "Formatted": "pre" 
    456   }; 
    457  
    458   this.customSelects = {}; 
    459  
    460   function cut_copy_paste(e, cmd, obj) { e.execCommand(cmd); } 
    461  
    462   this.debug = true; 
    463  
    464   this.URIs = 
    465   { 
    466    "blank": "popups/blank.html", 
    467    "link": "link.html", 
    468    "insert_image": "insert_image.html", 
    469    "insert_table": "insert_table.html", 
    470    "select_color": "select_color.html", 
    471    "about": "about.html", 
    472    "help": "editor_help.html" 
    473   }; 
    474  
    475  
    476   // ADDING CUSTOM BUTTONS: please read below! 
    477   // format of the btnList elements is "ID: [ ToolTip, Icon, Enabled in text mode?, ACTION ]" 
    478   //    - ID: unique ID for the button.  If the button calls document.execCommand 
    479   //        it's wise to give it the same name as the called command. 
    480   //    - ACTION: function that gets called when the button is clicked. 
    481   //              it has the following prototype: 
    482   //                 function(editor, buttonName) 
    483   //              - editor is the Xinha object that triggered the call 
    484   //              - buttonName is the ID of the clicked button 
    485   //              These 2 parameters makes it possible for you to use the same 
    486   //              handler for more Xinha objects or for more different buttons. 
    487   //    - ToolTip: tooltip, will be translated below 
    488   //    - Icon: path to an icon image file for the button 
    489   //            OR; you can use an 18x18 block of a larger image by supllying an array 
    490   //            that has three elemtents, the first is the larger image, the second is the column 
    491   //            the third is the row.  The ros and columns numbering starts at 0 but there is 
    492   //            a header row and header column which have numbering to make life easier. 
    493   //            See images/buttons_main.gif to see how it's done. 
    494   //    - Enabled in text mode: if false the button gets disabled for text-only mode; otherwise enabled all the time. 
    495   this.btnList = 
    496   { 
    497     bold: [ "Bold", Xinha._lc({key: 'button_bold', string: ["ed_buttons_main.gif",3,2]}, 'Xinha'), false, function(e) { e.execCommand("bold"); } ], 
    498     italic: [ "Italic", Xinha._lc({key: 'button_italic', string: ["ed_buttons_main.gif",2,2]}, 'Xinha'), false, function(e) { e.execCommand("italic"); } ], 
    499     underline: [ "Underline", Xinha._lc({key: 'button_underline', string: ["ed_buttons_main.gif",2,0]}, 'Xinha'), false, function(e) { e.execCommand("underline"); } ], 
    500     strikethrough: [ "Strikethrough", Xinha._lc({key: 'button_strikethrough', string: ["ed_buttons_main.gif",3,0]}, 'Xinha'), false, function(e) { e.execCommand("strikethrough"); } ], 
    501     subscript: [ "Subscript", Xinha._lc({key: 'button_subscript', string: ["ed_buttons_main.gif",3,1]}, 'Xinha'), false, function(e) { e.execCommand("subscript"); } ], 
    502     superscript: [ "Superscript", Xinha._lc({key: 'button_superscript', string: ["ed_buttons_main.gif",2,1]}, 'Xinha'), false, function(e) { e.execCommand("superscript"); } ], 
    503  
    504     justifyleft: [ "Justify Left", ["ed_buttons_main.gif",0,0], false, function(e) { e.execCommand("justifyleft"); } ], 
    505     justifycenter: [ "Justify Center", ["ed_buttons_main.gif",1,1], false, function(e){ e.execCommand("justifycenter"); } ], 
    506     justifyright: [ "Justify Right", ["ed_buttons_main.gif",1,0], false, function(e) { e.execCommand("justifyright"); } ], 
    507     justifyfull: [ "Justify Full", ["ed_buttons_main.gif",0,1], false, function(e) { e.execCommand("justifyfull"); } ], 
    508  
    509     orderedlist: [ "Ordered List", ["ed_buttons_main.gif",0,3], false, function(e) { e.execCommand("insertorderedlist"); } ], 
    510     unorderedlist: [ "Bulleted List", ["ed_buttons_main.gif",1,3], false, function(e) { e.execCommand("insertunorderedlist"); } ], 
    511     insertorderedlist: [ "Ordered List", ["ed_buttons_main.gif",0,3], false, function(e) { e.execCommand("insertorderedlist"); } ], 
    512     insertunorderedlist: [ "Bulleted List", ["ed_buttons_main.gif",1,3], false, function(e) { e.execCommand("insertunorderedlist"); } ], 
    513  
    514     outdent: [ "Decrease Indent", ["ed_buttons_main.gif",1,2], false, function(e) { e.execCommand("outdent"); } ], 
    515     indent: [ "Increase Indent",["ed_buttons_main.gif",0,2], false, function(e) { e.execCommand("indent"); } ], 
    516     forecolor: [ "Font Color", ["ed_buttons_main.gif",3,3], false, function(e) { e.execCommand("forecolor"); } ], 
    517     hilitecolor: [ "Background Color", ["ed_buttons_main.gif",2,3], false, function(e) { e.execCommand("hilitecolor"); } ], 
    518  
    519     undo: [ "Undoes your last action", ["ed_buttons_main.gif",4,2], false, function(e) { e.execCommand("undo"); } ], 
    520     redo: [ "Redoes your last action", ["ed_buttons_main.gif",5,2], false, function(e) { e.execCommand("redo"); } ], 
    521     cut: [ "Cut selection", ["ed_buttons_main.gif",5,0], false, cut_copy_paste ], 
    522     copy: [ "Copy selection", ["ed_buttons_main.gif",4,0], false, cut_copy_paste ], 
    523     paste: [ "Paste from clipboard", ["ed_buttons_main.gif",4,1], false, cut_copy_paste ], 
    524     selectall: [ "Select all", "ed_selectall.gif", false, function(e) {e.execCommand("selectall");} ], 
    525  
    526     inserthorizontalrule: [ "Horizontal Rule", ["ed_buttons_main.gif",6,0], false, function(e) { e.execCommand("inserthorizontalrule"); } ], 
    527     createlink: [ "Insert Web Link", ["ed_buttons_main.gif",6,1], false, function(e) { e._createLink(); } ], 
    528     insertimage: [ "Insert/Modify Image", ["ed_buttons_main.gif",6,3], false, function(e) { e.execCommand("insertimage"); } ], 
    529     inserttable: [ "Insert Table", ["ed_buttons_main.gif",6,2], false, function(e) { e.execCommand("inserttable"); } ], 
    530  
    531     htmlmode: [ "Toggle HTML Source", ["ed_buttons_main.gif",7,0], true, function(e) { e.execCommand("htmlmode"); } ], 
    532     toggleborders: [ "Toggle Borders", ["ed_buttons_main.gif",7,2], false, function(e) { e._toggleBorders(); } ], 
    533     print: [ "Print document", ["ed_buttons_main.gif",8,1], false, function(e) { if(Xinha.is_gecko) {e._iframe.contentWindow.print(); } else { e.focusEditor(); print(); } } ], 
    534     saveas: [ "Save as", "ed_saveas.gif", false, function(e) { e.execCommand("saveas",false,"noname.htm"); } ], 
    535     about: [ "About this editor", ["ed_buttons_main.gif",8,2], true, function(e) { e.execCommand("about"); } ], 
    536     showhelp: [ "Help using editor", ["ed_buttons_main.gif",9,2], true, function(e) { e.execCommand("showhelp"); } ], 
    537  
    538     splitblock: [ "Split Block", "ed_splitblock.gif", false, function(e) { e._splitBlock(); } ], 
    539     lefttoright: [ "Direction left to right", ["ed_buttons_main.gif",0,4], false, function(e) { e.execCommand("lefttoright"); } ], 
    540     righttoleft: [ "Direction right to left", ["ed_buttons_main.gif",1,4], false, function(e) { e.execCommand("righttoleft"); } ], 
    541     overwrite: [ "Insert/Overwrite", "ed_overwrite.gif", false, function(e) { e.execCommand("overwrite"); } ], 
    542  
    543     wordclean: [ "MS Word Cleaner", ["ed_buttons_main.gif",5,3], false, function(e) { e._wordClean(); } ], 
    544     clearfonts: [ "Clear Inline Font Specifications", ["ed_buttons_main.gif",5,4], true, function(e) { e._clearFonts(); } ], 
    545     removeformat: [ "Remove formatting", ["ed_buttons_main.gif",4,4], false, function(e) { e.execCommand("removeformat"); } ], 
    546     killword: [ "Clear MSOffice tags", ["ed_buttons_main.gif",4,3], false, function(e) { e.execCommand("killword"); } ] 
    547   }; 
    548  
    549   /* ADDING CUSTOM BUTTONS 
    550    * --------------------- 
    551    * 
    552    * It is recommended that you add the custom buttons in an external 
    553    * file and leave this one unchanged.  That's because when we 
    554    * (InteractiveTools.com) release a new official version, it's less 
    555    * likely that you will have problems upgrading Xinha. 
    556    * 
    557    * Example on how to add a custom button when you construct the Xinha: 
    558    * 
    559    *   var editor = new Xinha("your_text_area_id"); 
    560    *   var cfg = editor.config; // this is the default configuration 
    561    *   cfg.btnList["my-hilite"] = 
    562    *    [ function(editor) { editor.surroundHTML('<span style="background:yellow">', '</span>'); }, // action 
    563    *      "Highlight selection", // tooltip 
    564    *      "my_hilite.gif", // image 
    565    *      false // disabled in text mode 
    566    *    ]; 
    567    *   cfg.toolbar.push(["linebreak", "my-hilite"]); // add the new button to the toolbar 
    568    * 
    569    * An alternate (also more convenient and recommended) way to 
    570    * accomplish this is to use the registerButton function below. 
    571    */ 
    572   // initialize tooltips from the I18N module and generate correct image path 
    573   for ( var i in this.btnList ) 
    574   { 
    575     var btn = this.btnList[i]; 
    576     // prevent iterating over wrong type 
    577     if ( typeof btn != 'object' ) 
    578     { 
    579       continue; 
    580     }  
    581     if ( typeof btn[1] != 'string' ) 
    582     { 
    583       btn[1][0] = _editor_url + this.imgURL + btn[1][0]; 
    584     } 
    585     else 
    586     { 
    587       btn[1] = _editor_url + this.imgURL + btn[1]; 
    588     } 
    589     btn[0] = Xinha._lc(btn[0]); //initialize tooltip 
    590   } 
    591  
    592 }; 
    593  
    594 /** Helper function: register a new button with the configuration.  It can be 
    595  * called with all 5 arguments, or with only one (first one).  When called with 
    596  * only one argument it must be an object with the following properties: id, 
    597  * tooltip, image, textMode, action.  Examples: 
    598  * 
    599  * 1. config.registerButton("my-hilite", "Hilite text", "my-hilite.gif", false, function(editor) {...}); 
    600  * 2. config.registerButton({ 
    601  *      id       : "my-hilite",      // the ID of your button 
    602  *      tooltip  : "Hilite text",    // the tooltip 
    603  *      image    : "my-hilite.gif",  // image to be displayed in the toolbar 
    604  *      textMode : false,            // disabled in text mode 
    605  *      action   : function(editor) { // called when the button is clicked 
    606  *                   editor.surroundHTML('<span class="hilite">', '</span>'); 
    607  *                 }, 
    608  *      context  : "p"               // will be disabled if outside a <p> element 
    609  *    }); 
    610  */ 
    611 Xinha.Config.prototype.registerButton = function(id, tooltip, image, textMode, action, context) 
    612 { 
    613   var the_id; 
    614   if ( typeof id == "string" ) 
    615   { 
    616     the_id = id; 
    617   } 
    618   else if ( typeof id == "object" ) 
    619   { 
    620     the_id = id.id; 
    621   } 
    622   else 
    623   { 
    624     alert("ERROR [Xinha.Config::registerButton]:\ninvalid arguments"); 
    625     return false; 
    626   } 
    627   // check for existing id 
    628 //  if(typeof this.customSelects[the_id] != "undefined") 
    629 //  { 
    630     // alert("WARNING [Xinha.Config::registerDropdown]:\nA dropdown with the same ID already exists."); 
    631 //  } 
    632 //  if(typeof this.btnList[the_id] != "undefined") { 
    633     // alert("WARNING [Xinha.Config::registerDropdown]:\nA button with the same ID already exists."); 
    634 //  } 
    635   switch ( typeof id ) 
    636   { 
    637     case "string": 
    638       this.btnList[id] = [ tooltip, image, textMode, action, context ]; 
    639     break; 
    640     case "object": 
    641       this.btnList[id.id] = [ id.tooltip, id.image, id.textMode, id.action, id.context ]; 
    642     break; 
    643   } 
    644 }; 
    645  
    646 Xinha.prototype.registerPanel = function(side, object) 
    647 { 
    648   if ( !side ) 
    649   { 
    650     side = 'right'; 
    651   } 
    652   this.setLoadingMessage('Register panel ' + side); 
    653   var panel = this.addPanel(side); 
    654   if ( object ) 
    655   { 
    656     object.drawPanelIn(panel); 
    657   } 
    658 }; 
    659  
    660 /** The following helper function registers a dropdown box with the editor 
    661  * configuration.  You still have to add it to the toolbar, same as with the 
    662  * buttons.  Call it like this: 
    663  * 
    664  * FIXME: add example 
    665  */ 
    666 Xinha.Config.prototype.registerDropdown = function(object) 
    667 { 
    668   // check for existing id 
    669 //  if ( typeof this.customSelects[object.id] != "undefined" ) 
    670 //  { 
    671     // alert("WARNING [Xinha.Config::registerDropdown]:\nA dropdown with the same ID already exists."); 
    672 //  } 
    673 //  if ( typeof this.btnList[object.id] != "undefined" ) 
    674 //  { 
    675     // alert("WARNING [Xinha.Config::registerDropdown]:\nA button with the same ID already exists."); 
    676 //  } 
    677   this.customSelects[object.id] = object; 
    678 }; 
    679  
    680 /** Call this function to remove some buttons/drop-down boxes from the toolbar. 
    681  * Pass as the only parameter a string containing button/drop-down names 
    682  * delimited by spaces.  Note that the string should also begin with a space 
    683  * and end with a space.  Example: 
    684  * 
    685  *   config.hideSomeButtons(" fontname fontsize textindicator "); 
    686  * 
    687  * It's useful because it's easier to remove stuff from the defaul toolbar than 
    688  * create a brand new toolbar ;-) 
    689  */ 
    690 Xinha.Config.prototype.hideSomeButtons = function(remove) 
    691 { 
    692   var toolbar = this.toolbar; 
    693   for ( var i = toolbar.length; --i >= 0; ) 
    694   { 
    695     var line = toolbar[i]; 
    696     for ( var j = line.length; --j >= 0; ) 
    697     { 
    698       if ( remove.indexOf(" " + line[j] + " ") >= 0 ) 
    699       { 
    700         var len = 1; 
    701         if ( /separator|space/.test(line[j + 1]) ) 
    702         { 
    703           len = 2; 
    704         } 
    705         line.splice(j, len); 
    706       } 
    707     } 
    708   } 
    709 }; 
    710  
    711 /** Helper Function: add buttons/drop-downs boxes with title or separator to the toolbar 
    712  * if the buttons/drop-downs boxes doesn't allready exists. 
    713  * id: button or selectbox (as array with separator or title) 
    714  * where: button or selectbox (as array if the first is not found take the second and so on) 
    715  * position: 
    716  * -1 = insert button (id) one position before the button (where) 
    717  * 0 = replace button (where) by button (id) 
    718  * +1 = insert button (id) one position after button (where) 
    719  * 
    720  * cfg.addToolbarElement(["T[title]", "button_id", "separator"] , ["first_id","second_id"], -1); 
    721 */ 
    722  
    723 Xinha.Config.prototype.addToolbarElement = function(id, where, position) 
    724 { 
    725   var toolbar = this.toolbar; 
    726   var a, i, j, o, sid; 
    727   var idIsArray = false; 
    728   var whereIsArray = false; 
    729   var whereLength = 0; 
    730   var whereJ = 0; 
    731   var whereI = 0; 
    732   var exists = false; 
    733   var found = false; 
    734   // check if id and where are arrys 
    735   if ( ( id && typeof id == "object" ) && ( id.constructor == Array ) ) 
    736   { 
    737     idIsArray = true; 
    738   } 
    739   if ( ( where && typeof where == "object" ) && ( where.constructor == Array ) ) 
    740   { 
    741     whereIsArray = true; 
    742     whereLength = where.length; 
    743         } 
    744  
    745   if ( idIsArray ) //find the button/select box in input array 
    746   { 
    747     for ( i = 0; i < id.length; ++i ) 
    748     { 
    749       if ( ( id[i] != "separator" ) && ( id[i].indexOf("T[") !== 0) ) 
    750       { 
    751         sid = id[i]; 
    752       } 
    753     } 
    754   } 
    755   else 
    756   { 
    757     sid = id; 
    758   } 
    759  
    760   for ( i = 0; !exists && !found && i < toolbar.length; ++i ) 
    761   { 
    762     a = toolbar[i]; 
    763     for ( j = 0; !found && j < a.length; ++j ) 
    764     { 
    765       // check if button/select box exists 
    766       if ( a[i] == sid ) 
    767       {  
    768         exists = true; 
    769         break; 
    770       } 
    771       if ( whereIsArray ) 
    772       { 
    773         for ( o = 0; o < whereLength; ++o ) 
    774         { 
    775           if ( a[j] == where[o] ) 
    776           { 
    777             if ( o === 0 ) 
    778             { 
    779               found = true; 
    780               j--; 
    781               break; 
    782             } 
    783             else 
    784             { 
    785               whereI = i; 
    786               whereJ = j; 
    787               whereLength = o; 
    788             } 
    789           } 
    790         } 
    791       } 
    792       else 
    793       { 
    794         // find the position to insert 
    795         if ( a[j] == where ) 
    796         {  
    797           found = true; 
    798           break; 
    799         } 
    800       } 
    801     } 
    802   } 
    803  
    804   if ( !exists ) 
    805   { 
    806     //if check found any other as the first button 
    807     if ( !found && whereIsArray ) 
    808     {  
    809       if ( where.length != whereLength ) 
    810       { 
    811         j = whereJ; 
    812         a = toolbar[whereI]; 
    813         found = true; 
    814       } 
    815     } 
    816     if ( found ) 
    817     { 
    818       // replace the found button 
    819       if ( position === 0 ) 
    820       { 
    821         if ( idIsArray) 
    822         { 
    823           a[j] = id[id.length-1]; 
    824           for ( i = id.length-1; --i >= 0; ) 
    825           { 
    826             a.splice(j, 0, id[i]); 
    827           } 
    828         } 
    829         else 
    830         { 
    831           a[j] = id; 
    832         } 
    833       } 
    834       else 
    835       {  
    836         // insert before/after the found button 
    837         if ( position < 0 ) 
    838         { 
    839           j = j + position + 1; //correct position before 
    840         } 
    841         else if ( position > 0 ) 
    842         { 
    843           j = j + position; //correct posion after 
    844         } 
    845         if ( idIsArray ) 
    846         { 
    847           for ( i = id.length; --i >= 0; ) 
    848           { 
    849             a.splice(j, 0, id[i]); 
    850           } 
    851         } 
    852         else 
    853         { 
    854           a.splice(j, 0, id); 
    855         } 
    856       } 
    857     } 
    858     else 
    859     { 
    860       // no button found 
    861       toolbar[0].splice(0, 0, "separator"); 
    862       if ( idIsArray) 
    863       { 
    864         for ( i = id.length; --i >= 0; ) 
    865         { 
    866           toolbar[0].splice(0, 0, id[i]); 
    867         } 
    868       } 
    869       else 
    870       { 
    871         toolbar[0].splice(0, 0, id); 
    872       } 
    873     } 
    874   } 
    875 }; 
    876  
    877 Xinha.Config.prototype.removeToolbarElement = Xinha.Config.prototype.hideSomeButtons; 
    878  
    879 /** Helper function: replace all TEXTAREA-s in the document with Xinha-s. */ 
    880 Xinha.replaceAll = function(config) 
    881 { 
    882   var tas = document.getElementsByTagName("textarea"); 
    883   // @todo: weird syntax, doesnt help to read the code, doesnt obfuscate it and doesnt make it quicker, better rewrite this part 
    884   for ( var i = tas.length; i > 0; (new Xinha(tas[--i], config)).generate() ) 
    885   { 
    886     // NOP 
    887   } 
    888 }; 
    889  
    890 /** Helper function: replaces the TEXTAREA with the given ID with Xinha. */ 
    891 Xinha.replace = function(id, config) 
    892 { 
    893   var ta = Xinha.getElementById("textarea", id); 
    894   return ta ? (new Xinha(ta, config)).generate() : null; 
    895 }; 
    896  
    897 // Creates the toolbar and appends it to the _htmlarea 
    898 Xinha.prototype._createToolbar = function () 
    899 { 
    900   this.setLoadingMessage('Create Toolbar'); 
    901   var editor = this;    // to access this in nested functions 
    902  
    903   var toolbar = document.createElement("div"); 
    904   // ._toolbar is for legacy, ._toolBar is better thanks. 
    905   this._toolBar = this._toolbar = toolbar; 
    906   toolbar.className = "toolbar"; 
    907   toolbar.unselectable = "1"; 
    908  
    909   Xinha.freeLater(this, '_toolBar'); 
    910   Xinha.freeLater(this, '_toolbar'); 
    911    
    912   var tb_row = null; 
    913   var tb_objects = {}; 
    914   this._toolbarObjects = tb_objects; 
    915  
    916         this._createToolbar1(editor, toolbar, tb_objects); 
    917         this._htmlArea.appendChild(toolbar);       
    918    
    919   return toolbar; 
    920 }; 
    921  
    922 // FIXME : function never used, can probably be removed from source 
    923 Xinha.prototype._setConfig = function(config) 
    924 { 
    925         this.config = config; 
    926 }; 
    927  
    928 Xinha.prototype._addToolbar = function() 
    929 { 
    930         this._createToolbar1(this, this._toolbar, this._toolbarObjects); 
    931 }; 
    932  
    933 /** 
    934  * Create a break element to add in the toolbar 
    935  * 
    936  * @return {Object} HTML element to add 
    937  * @private 
    938  */ 
    939 Xinha._createToolbarBreakingElement = function() 
    940 { 
    941   var brk = document.createElement('div'); 
    942   brk.style.height = '1px'; 
    943   brk.style.width = '1px'; 
    944   brk.style.lineHeight = '1px'; 
    945   brk.style.fontSize = '1px'; 
    946   brk.style.clear = 'both'; 
    947   return brk; 
    948 }; 
    949  
    950 // separate from previous createToolBar to allow dynamic change of toolbar 
    951 Xinha.prototype._createToolbar1 = function (editor, toolbar, tb_objects) 
    952 { 
    953   var tb_row; 
    954   // This shouldn't be necessary, but IE seems to float outside of the container 
    955   // when we float toolbar sections, so we have to clear:both here as well 
    956   // as at the end (which we do have to do). 
    957   if ( editor.config.flowToolbars ) 
    958   { 
    959     toolbar.appendChild(Xinha._createToolbarBreakingElement()); 
    960   } 
    961  
    962   // creates a new line in the toolbar 
    963   function newLine() 
    964   { 
    965     if ( typeof tb_row != 'undefined' && tb_row.childNodes.length === 0) 
    966     { 
    967       return; 
    968     } 
    969  
    970     var table = document.createElement("table"); 
    971     table.border = "0px"; 
    972     table.cellSpacing = "0px"; 
    973     table.cellPadding = "0px"; 
    974     if ( editor.config.flowToolbars ) 
    975     { 
    976       if ( Xinha.is_ie ) 
    977       { 
    978         table.style.styleFloat = "left"; 
    979       } 
    980       else 
    981       { 
    982         table.style.cssFloat = "left"; 
    983       } 
    984     } 
    985  
    986     toolbar.appendChild(table); 
    987     // TBODY is required for IE, otherwise you don't see anything 
    988     // in the TABLE. 
    989     var tb_body = document.createElement("tbody"); 
    990     table.appendChild(tb_body); 
    991     tb_row = document.createElement("tr"); 
    992     tb_body.appendChild(tb_row); 
    993  
    994     table.className = 'toolbarRow'; // meh, kinda. 
    995   } // END of function: newLine 
    996  
    997   // init first line 
    998   newLine(); 
    999  
    1000   // updates the state of a toolbar element.  This function is member of 
    1001   // a toolbar element object (unnamed objects created by createButton or 
    1002   // createSelect functions below). 
    1003   function setButtonStatus(id, newval) 
    1004   { 
    1005     var oldval = this[id]; 
    1006     var el = this.element; 
    1007     if ( oldval != newval ) 
    1008     { 
    1009       switch (id) 
    1010       { 
    1011         case "enabled": 
    1012           if ( newval ) 
    1013           { 
    1014             Xinha._removeClass(el, "buttonDisabled"); 
    1015             el.disabled = false; 
    1016           } 
    1017           else 
    1018           { 
    1019             Xinha._addClass(el, "buttonDisabled"); 
    1020             el.disabled = true; 
    1021           } 
    1022         break; 
    1023         case "active": 
    1024           if ( newval ) 
    1025           { 
    1026             Xinha._addClass(el, "buttonPressed"); 
    1027           } 
    1028           else 
    1029           { 
    1030             Xinha._removeClass(el, "buttonPressed"); 
    1031           } 
    1032         break; 
    1033       } 
    1034       this[id] = newval; 
    1035     } 
    1036   } // END of function: setButtonStatus 
    1037  
    1038   // this function will handle creation of combo boxes.  Receives as 
    1039   // parameter the name of a button as defined in the toolBar config. 
    1040   // This function is called from createButton, above, if the given "txt" 
    1041   // doesn't match a button. 
    1042   function createSelect(txt) 
    1043   { 
    1044     var options = null; 
    1045     var el = null; 
    1046     var cmd = null; 
    1047     var customSelects = editor.config.customSelects; 
    1048     var context = null; 
    1049     var tooltip = ""; 
    1050     switch (txt) 
    1051     { 
    1052       case "fontsize": 
    1053       case "fontname": 
    1054       case "formatblock": 
    1055         // the following line retrieves the correct 
    1056         // configuration option because the variable name 
    1057         // inside the Config object is named the same as the 
    1058         // button/select in the toolbar.  For instance, if txt 
    1059         // == "formatblock" we retrieve config.formatblock (or 
    1060         // a different way to write it in JS is 
    1061         // config["formatblock"]. 
    1062         options = editor.config[txt]; 
    1063         cmd = txt; 
    1064       break; 
    1065       default: 
    1066         // try to fetch it from the list of registered selects 
    1067         cmd = txt; 
    1068         var dropdown = customSelects[cmd]; 
    1069         if ( typeof dropdown != "undefined" ) 
    1070         { 
    1071           options = dropdown.options; 
    1072           context = dropdown.context; 
    1073           if ( typeof dropdown.tooltip != "undefined" ) 
    1074           { 
    1075             tooltip = dropdown.tooltip; 
    1076           } 
    1077         } 
    1078         else 
    1079         { 
    1080           alert("ERROR [createSelect]:\nCan't find the requested dropdown definition"); 
    1081         } 
    1082       break; 
    1083     } 
    1084     if ( options ) 
    1085     { 
    1086       el = document.createElement("select"); 
    1087       el.title = tooltip; 
    1088       var obj = 
    1089       { 
    1090         name    : txt, // field name 
    1091         element : el,   // the UI element (SELECT) 
    1092         enabled : true, // is it enabled? 
    1093         text    : false, // enabled in text mode? 
    1094         cmd     : cmd, // command ID 
    1095         state   : setButtonStatus, // for changing state 
    1096         context : context 
    1097       }; 
    1098        
    1099       Xinha.freeLater(obj); 
    1100        
    1101       tb_objects[txt] = obj; 
    1102        
    1103       for ( var i in options ) 
    1104       { 
    1105         // prevent iterating over wrong type 
    1106         if ( typeof(options[i]) != 'string' ) 
    1107         { 
    1108           continue; 
    1109         } 
    1110         var op = document.createElement("option"); 
    1111         op.innerHTML = Xinha._lc(i); 
    1112         op.value = options[i]; 
    1113         el.appendChild(op); 
    1114       } 
    1115       Xinha._addEvent(el, "change", function () { editor._comboSelected(el, txt); } ); 
    1116     } 
    1117     return el; 
    1118   } // END of function: createSelect 
    1119  
    1120   // appends a new button to toolbar 
    1121   function createButton(txt) 
    1122   { 
    1123     // the element that will be created 
    1124     var el, btn, obj = null; 
    1125     switch (txt) 
    1126     { 
    1127       case "separator": 
    1128         if ( editor.config.flowToolbars ) 
    1129         { 
    1130           newLine(); 
    1131         } 
    1132         el = document.createElement("div"); 
    1133         el.className = "separator"; 
    1134       break; 
    1135       case "space": 
    1136         el = document.createElement("div"); 
    1137         el.className = "space"; 
    1138       break; 
    1139       case "linebreak": 
    1140         newLine(); 
    1141         return false; 
    1142       case "textindicator": 
    1143         el = document.createElement("div"); 
    1144         el.appendChild(document.createTextNode("A")); 
    1145         el.className = "indicator"; 
    1146         el.title = Xinha._lc("Current style"); 
    1147         obj = 
    1148         { 
    1149           name  : txt, // the button name (i.e. 'bold') 
    1150           element : el, // the UI element (DIV) 
    1151           enabled : true, // is it enabled? 
    1152           active        : false, // is it pressed? 
    1153           text  : false, // enabled in text mode? 
    1154           cmd   : "textindicator", // the command ID 
    1155           state : setButtonStatus // for changing state 
    1156         }; 
    1157        
    1158         Xinha.freeLater(obj); 
    1159        
    1160         tb_objects[txt] = obj; 
    1161       break; 
    1162       default: 
    1163         btn = editor.config.btnList[txt]; 
    1164     } 
    1165     if ( !el && btn ) 
    1166     { 
    1167       el = document.createElement("a"); 
    1168       el.style.display = 'block'; 
    1169       el.href = 'javascript:void(0)'; 
    1170       el.style.textDecoration = 'none'; 
    1171       el.title = btn[0]; 
    1172       el.className = "button"; 
    1173       el.style.direction = "ltr"; 
    1174       // let's just pretend we have a button object, and 
    1175       // assign all the needed information to it. 
    1176       obj = 
    1177       { 
    1178         name : txt, // the button name (i.e. 'bold') 
    1179         element : el, // the UI element (DIV) 
    1180         enabled : true, // is it enabled? 
    1181         active : false, // is it pressed? 
    1182         text : btn[2], // enabled in text mode? 
    1183         cmd     : btn[3], // the command ID 
    1184         state   : setButtonStatus, // for changing state 
    1185         context : btn[4] || null // enabled in a certain context? 
    1186       }; 
    1187       Xinha.freeLater(el); 
    1188       Xinha.freeLater(obj); 
    1189  
    1190       tb_objects[txt] = obj; 
    1191  
    1192       // prevent drag&drop of the icon to content area 
    1193       el.ondrag = function() { return false; }; 
    1194  
    1195       // handlers to emulate nice flat toolbar buttons 
    1196       Xinha._addEvent( 
    1197         el, 
    1198         "mouseout", 
    1199         function(ev) 
    1200         { 
    1201           if ( obj.enabled ) 
    1202           { 
    1203             //Xinha._removeClass(el, "buttonHover"); 
    1204             Xinha._removeClass(el, "buttonActive"); 
    1205             if ( obj.active ) 
    1206             { 
    1207               Xinha._addClass(el, "buttonPressed"); 
    1208             } 
    1209           } 
    1210         } 
    1211       ); 
    1212  
    1213       Xinha._addEvent( 
    1214         el, 
    1215         "mousedown", 
    1216         function(ev) 
    1217         { 
    1218           if ( obj.enabled ) 
    1219           { 
    1220             Xinha._addClass(el, "buttonActive"); 
    1221             Xinha._removeClass(el, "buttonPressed"); 
    1222             Xinha._stopEvent(Xinha.is_ie ? window.event : ev); 
    1223           } 
    1224         } 
    1225       ); 
    1226  
    1227       // when clicked, do the following: 
    1228       Xinha._addEvent( 
    1229         el, 
    1230         "click", 
    1231         function(ev) 
    1232         { 
    1233           if ( obj.enabled ) 
    1234           { 
    1235             Xinha._removeClass(el, "buttonActive"); 
    1236             //Xinha._removeClass(el, "buttonHover"); 
    1237             if ( Xinha.is_gecko ) 
    1238             { 
    1239               editor.activateEditor(); 
    1240             } 
    1241             obj.cmd(editor, obj.name, obj); 
    1242             Xinha._stopEvent(Xinha.is_ie ? window.event : ev); 
    1243           } 
    1244         } 
    1245       ); 
    1246  
    1247       var i_contain = Xinha.makeBtnImg(btn[1]); 
    1248       var img = i_contain.firstChild; 
    1249       el.appendChild(i_contain); 
    1250  
    1251       obj.imgel = img;       
    1252       obj.swapImage = function(newimg) 
    1253       { 
    1254         if ( typeof newimg != 'string' ) 
    1255         { 
    1256           img.src = newimg[0]; 
    1257           img.style.position = 'relative'; 
    1258           img.style.top  = newimg[2] ? ('-' + (18 * (newimg[2] + 1)) + 'px') : '-18px'; 
    1259           img.style.left = newimg[1] ? ('-' + (18 * (newimg[1] + 1)) + 'px') : '-18px'; 
    1260         } 
    1261         else 
    1262         { 
    1263           obj.imgel.src = newimg; 
    1264           img.style.top = '0px'; 
    1265           img.style.left = '0px'; 
    1266         } 
    1267       }; 
    1268        
    1269     } 
    1270     else if( !el ) 
    1271     { 
    1272       el = createSelect(txt); 
    1273     } 
    1274  
    1275     return el; 
    1276   } 
    1277  
    1278   var first = true; 
    1279   for ( var i = 0; i < this.config.toolbar.length; ++i ) 
    1280   { 
    1281     if ( !first ) 
    1282     { 
    1283       // createButton("linebreak"); 
    1284     } 
    1285     else 
    1286     { 
    1287       first = false; 
    1288     } 
    1289     if ( this.config.toolbar[i] === null ) 
    1290     { 
    1291       this.config.toolbar[i] = ['separator']; 
    1292     } 
    1293     var group = this.config.toolbar[i]; 
    1294  
    1295     for ( var j = 0; j < group.length; ++j ) 
    1296     { 
    1297       var code = group[j]; 
    1298       var tb_cell; 
    1299       if ( /^([IT])\[(.*?)\]/.test(code) ) 
    1300       { 
    1301         // special case, create text label 
    1302         var l7ed = RegExp.$1 == "I"; // localized? 
    1303         var label = RegExp.$2; 
    1304         if ( l7ed ) 
    1305         { 
    1306           label = Xinha._lc(label); 
    1307         } 
    1308         tb_cell = document.createElement("td"); 
    1309         tb_row.appendChild(tb_cell); 
    1310         tb_cell.className = "label"; 
    1311         tb_cell.innerHTML = label; 
    1312       } 
    1313       else if ( typeof code != 'function' ) 
    1314       { 
    1315         var tb_element = createButton(code); 
    1316         if ( tb_element ) 
    1317         { 
    1318           tb_cell = document.createElement("td"); 
    1319           tb_cell.className = 'toolbarElement'; 
    1320           tb_row.appendChild(tb_cell); 
    1321           tb_cell.appendChild(tb_element); 
    1322         } 
    1323         else if ( tb_element === null ) 
    1324         { 
    1325           alert("FIXME: Unknown toolbar item: " + code); 
    1326         } 
    1327       } 
    1328     } 
    1329   } 
    1330  
    1331   if ( editor.config.flowToolbars ) 
    1332   { 
    1333     toolbar.appendChild(Xinha._createToolbarBreakingElement()); 
    1334   } 
    1335  
    1336   return toolbar; 
    1337 }; 
    1338  
    1339 // @todo : is this some kind of test not finished ? 
    1340 //         Why the hell this is not in the config object ? 
    1341 var use_clone_img = false; 
    1342 Xinha.makeBtnImg = function(imgDef, doc) 
    1343 { 
    1344   if ( !doc ) 
    1345   { 
    1346     doc = document; 
    1347   } 
    1348  
    1349   if ( !doc._xinhaImgCache ) 
    1350   { 
    1351     doc._xinhaImgCache = {}; 
    1352     Xinha.freeLater(doc._xinhaImgCache); 
    1353   } 
    1354  
    1355   var i_contain = null; 
    1356   if ( Xinha.is_ie && ( ( !doc.compatMode ) || ( doc.compatMode && doc.compatMode == "BackCompat" ) ) ) 
    1357   { 
    1358     i_contain = doc.createElement('span'); 
    1359   } 
    1360   else 
    1361   { 
    1362     i_contain = doc.createElement('div'); 
    1363     i_contain.style.position = 'relative'; 
    1364   } 
    1365  
    1366   i_contain.style.overflow = 'hidden'; 
    1367   i_contain.style.width = "18px"; 
    1368   i_contain.style.height = "18px"; 
    1369   i_contain.className = 'buttonImageContainer'; 
    1370  
    1371   var img = null; 
    1372   if ( typeof imgDef == 'string' ) 
    1373   { 
    1374     if ( doc._xinhaImgCache[imgDef] ) 
    1375     { 
    1376       img = doc._xinhaImgCache[imgDef].cloneNode(); 
    1377     } 
    1378     else 
    1379     { 
    1380       img = doc.createElement("img"); 
    1381       img.src = imgDef; 
    1382       img.style.width = "18px"; 
    1383       img.style.height = "18px"; 
    1384       if ( use_clone_img ) 
    1385       { 
    1386         doc._xinhaImgCache[imgDef] = img.cloneNode(); 
    1387       } 
    1388     } 
    1389   } 
    1390   else 
    1391   { 
    1392     if ( doc._xinhaImgCache[imgDef[0]] ) 
    1393     { 
    1394       img = doc._xinhaImgCache[imgDef[0]].cloneNode(); 
    1395     } 
    1396     else 
    1397     { 
    1398       img = doc.createElement("img"); 
    1399       img.src = imgDef[0]; 
    1400       img.style.position = 'relative'; 
    1401       if ( use_clone_img ) 
    1402       { 
    1403         doc._xinhaImgCache[imgDef[0]] = img.cloneNode(); 
    1404       } 
    1405     } 
    1406     // @todo: Using 18 dont let us use a theme with its own icon toolbar height 
    1407     //        and width. Probably better to calculate this value 18 
    1408     //        var sizeIcon = img.width / nb_elements_per_image; 
    1409     img.style.top  = imgDef[2] ? ('-' + (18 * (imgDef[2] + 1)) + 'px') : '-18px'; 
    1410     img.style.left = imgDef[1] ? ('-' + (18 * (imgDef[1] + 1)) + 'px') : '-18px'; 
    1411   } 
    1412   i_contain.appendChild(img); 
    1413   return i_contain; 
    1414 }; 
    1415  
    1416 Xinha.prototype._createStatusBar = function() 
    1417 { 
    1418   this.setLoadingMessage('Create StatusBar'); 
    1419   var statusbar = document.createElement("div"); 
    1420   statusbar.className = "statusBar"; 
    1421   this._statusBar = statusbar; 
    1422   Xinha.freeLater(this, '_statusBar'); 
    1423    
    1424   // statusbar.appendChild(document.createTextNode(Xinha._lc("Path") + ": ")); 
    1425   // creates a holder for the path view 
    1426   var div = document.createElement("span"); 
    1427   div.className = "statusBarTree"; 
    1428   div.innerHTML = Xinha._lc("Path") + ": "; 
    1429   this._statusBarTree = div; 
    1430   Xinha.freeLater(this, '_statusBarTree'); 
    1431   this._statusBar.appendChild(div); 
    1432  
    1433   div = document.createElement("span"); 
    1434   div.innerHTML = Xinha._lc("You are in TEXT MODE.  Use the [<>] button to switch back to WYSIWYG."); 
    1435   div.style.display = "none"; 
    1436   this._statusBarTextMode = div; 
    1437   Xinha.freeLater(this, '_statusBarTextMode'); 
    1438   this._statusBar.appendChild(div); 
    1439  
    1440   if ( !this.config.statusBar ) 
    1441   { 
    1442     // disable it... 
    1443     statusbar.style.display = "none"; 
    1444   } 
    1445  
    1446   return statusbar; 
    1447 }; 
    1448  
    1449 // Creates the Xinha object and replaces the textarea with it. 
    1450 Xinha.prototype.generate = function () 
    1451 { 
    1452   var i; 
    1453   var editor = this;    // we'll need "this" in some nested functions 
    1454   this.setLoadingMessage('Generate Xinha object'); 
    1455  
    1456   if ( typeof Dialog == 'undefined' ) 
    1457   { 
    1458     Xinha._loadback(_editor_url + 'dialog.js', this.generate, this ); 
    1459     return false; 
    1460   } 
    1461  
    1462   if ( typeof Xinha.Dialog == 'undefined' ) 
    1463   { 
    1464     Xinha._loadback(_editor_url + 'inline-dialog.js', this.generate, this ); 
    1465     return false; 
    1466   } 
    1467  
    1468   if ( typeof PopupWin == 'undefined' ) 
    1469   { 
    1470     Xinha._loadback(_editor_url + 'popupwin.js', this.generate, this ); 
    1471     return false; 
    1472   } 
    1473  
    1474   if ( _editor_skin !== "" ) 
    1475   { 
    1476     var found = false; 
    1477     var head = document.getElementsByTagName("head")[0]; 
    1478     var links = document.getElementsByTagName("link"); 
    1479     for(i = 0; i<links.length; i++) 
    1480     { 
    1481       if ( ( links[i].rel == "stylesheet" ) && ( links[i].href == _editor_url + 'skins/' + _editor_skin + '/skin.css' ) ) 
    1482       { 
    1483         found = true; 
    1484       } 
    1485     } 
    1486     if ( !found ) 
    1487     { 
    1488       var link = document.createElement("link"); 
    1489       link.type = "text/css"; 
    1490       link.href = _editor_url + 'skins/' + _editor_skin + '/skin.css'; 
    1491       link.rel = "stylesheet"; 
    1492       head.appendChild(link); 
    1493     } 
    1494   } 
    1495  
    1496   //backwards-compatibility: load FullScreen-Plugin if we find a "popupeditor"-button in the toolbar 
    1497   // @todo: remove the backward compatibility in release 2.0 
    1498   var toolbar = editor.config.toolbar; 
    1499   for ( i = toolbar.length; --i >= 0; ) 
    1500   { 
    1501     for ( var j = toolbar[i].length; --j >= 0; ) 
    1502     { 
    1503       if ( toolbar[i][j]=="popupeditor" ) 
    1504       { 
    1505         if ( typeof FullScreen == "undefined" ) 
    1506         { 
    1507           // why can't we use the following line instead ? 
    1508 //          Xinha.loadPlugin("FullScreen", this.generate ); 
    1509           Xinha.loadPlugin("FullScreen", function() { editor.generate(); } ); 
    1510           return false; 
    1511         } 
    1512         editor.registerPlugin('FullScreen'); 
    1513       } 
    1514     } 
    1515   } 
    1516  
    1517   // If this is gecko, set up the paragraph handling now 
    1518   if ( Xinha.is_gecko && editor.config.mozParaHandler == 'best' ) 
    1519   { 
    1520     if ( typeof EnterParagraphs == 'undefined' ) 
    1521     { 
    1522       // why can't we use the following line instead ? 
    1523 //      Xinha.loadPlugin("EnterParagraphs", this.generate ); 
    1524       Xinha.loadPlugin("EnterParagraphs", function() { editor.generate(); } ); 
    1525       return false; 
    1526     } 
    1527     editor.registerPlugin('EnterParagraphs'); 
    1528   } 
    1529  
    1530   if ( typeof Xinha.getHTML == 'undefined' ) 
    1531   { 
    1532       Xinha._loadback(_editor_url + "getHTML.js", function() { editor.generate(); } ); 
    1533           return false; 
    1534   } 
    1535    
    1536   if ( typeof Xinha.prototype._insertImage == 'undefined' ) 
    1537   { 
    1538       Xinha._loadback(_editor_url + "popups/insert_image.js", function() { editor.generate(); } ); 
    1539           return false; 
    1540   } 
    1541  
    1542   if ( typeof Xinha.prototype._createLink == 'undefined' &&  typeof Linker == 'undefined' ) 
    1543   { 
    1544       Xinha._loadback(_editor_url + "popups/link.js", function() { editor.generate(); } ); 
    1545           return false; 
    1546   } 
    1547  
    1548   // create the editor framework, yah, table layout I know, but much easier 
    1549   // to get it working correctly this way, sorry about that, patches welcome. 
    1550  
    1551   this._framework = 
    1552   { 
    1553     'table':   document.createElement('table'), 
    1554     'tbody':   document.createElement('tbody'), // IE will not show the table if it doesn't have a tbody! 
    1555     'tb_row':  document.createElement('tr'), 
    1556     'tb_cell': document.createElement('td'), // Toolbar 
    1557  
    1558     'tp_row':  document.createElement('tr'), 
    1559     'tp_cell': this._panels.top.container,   // top panel 
    1560  
    1561     'ler_row': document.createElement('tr'), 
    1562     'lp_cell': this._panels.left.container,  // left panel 
    1563     'ed_cell': document.createElement('td'), // editor 
    1564     'rp_cell': this._panels.right.container, // right panel 
    1565  
    1566     'bp_row':  document.createElement('tr'), 
    1567     'bp_cell': this._panels.bottom.container,// bottom panel 
    1568  
    1569     'sb_row':  document.createElement('tr'), 
    1570     'sb_cell': document.createElement('td')  // status bar 
    1571  
    1572   }; 
    1573   Xinha.freeLater(this._framework); 
    1574    
    1575   var fw = this._framework; 
    1576   fw.table.border = "0"; 
    1577   fw.table.cellPadding = "0"; 
    1578   fw.table.cellSpacing = "0"; 
    1579  
    1580   fw.tb_row.style.verticalAlign = 'top'; 
    1581   fw.tp_row.style.verticalAlign = 'top'; 
    1582   fw.ler_row.style.verticalAlign= 'top'; 
    1583   fw.bp_row.style.verticalAlign = 'top'; 
    1584   fw.sb_row.style.verticalAlign = 'top'; 
    1585   fw.ed_cell.style.position     = 'relative'; 
    1586  
    1587   // Put the cells in the rows        set col & rowspans 
    1588   // note that I've set all these so that all panels are showing 
    1589   // but they will be redone in sizeEditor() depending on which 
    1590   // panels are shown.  It's just here to clarify how the thing 
    1591   // is put togethor. 
    1592   fw.tb_row.appendChild(fw.tb_cell); 
    1593   fw.tb_cell.colSpan = 3; 
    1594  
    1595   fw.tp_row.appendChild(fw.tp_cell); 
    1596   fw.tp_cell.colSpan = 3; 
    1597  
    1598   fw.ler_row.appendChild(fw.lp_cell); 
    1599   fw.ler_row.appendChild(fw.ed_cell); 
    1600   fw.ler_row.appendChild(fw.rp_cell); 
    1601  
    1602   fw.bp_row.appendChild(fw.bp_cell); 
    1603   fw.bp_cell.colSpan = 3; 
    1604  
    1605   fw.sb_row.appendChild(fw.sb_cell); 
    1606   fw.sb_cell.colSpan = 3; 
    1607  
    1608   // Put the rows in the table body 
    1609   fw.tbody.appendChild(fw.tb_row);  // Toolbar 
    1610   fw.tbody.appendChild(fw.tp_row); // Left, Top, Right panels 
    1611   fw.tbody.appendChild(fw.ler_row);  // Editor/Textarea 
    1612   fw.tbody.appendChild(fw.bp_row);  // Bottom panel 
    1613   fw.tbody.appendChild(fw.sb_row);  // Statusbar 
    1614  
    1615   // and body in the table 
    1616   fw.table.appendChild(fw.tbody); 
    1617  
    1618   var xinha = this._framework.table; 
    1619   this._htmlArea = xinha; 
    1620   Xinha.freeLater(this, '_htmlArea'); 
    1621   xinha.className = "htmlarea"; 
    1622  
    1623     // create the toolbar and put in the area 
    1624   this._framework.tb_cell.appendChild( this._createToolbar() ); 
    1625  
    1626     // create the IFRAME & add to container 
    1627   var iframe = document.createElement("iframe"); 
    1628   iframe.src = _editor_url + editor.config.URIs.blank; 
    1629   this._framework.ed_cell.appendChild(iframe); 
    1630   this._iframe = iframe; 
    1631   this._iframe.className = 'xinha_iframe'; 
    1632   Xinha.freeLater(this, '_iframe'); 
    1633    
    1634     // creates & appends the status bar 
    1635   var statusbar = this._createStatusBar(); 
    1636   this._framework.sb_cell.appendChild(statusbar); 
    1637  
    1638   // insert Xinha before the textarea. 
    1639   var textarea = this._textArea; 
    1640   textarea.parentNode.insertBefore(xinha, textarea); 
    1641   textarea.className = 'xinha_textarea'; 
    1642  
    1643   // extract the textarea and insert it into the xinha framework 
    1644   Xinha.removeFromParent(textarea); 
    1645   this._framework.ed_cell.appendChild(textarea); 
    1646  
    1647  
    1648   // Set up event listeners for saving the iframe content to the textarea 
    1649   if ( textarea.form ) 
    1650   { 
    1651     // onsubmit get the Xinha content and update original textarea. 
    1652     Xinha.prependDom0Event( 
    1653       this._textArea.form, 
    1654       'submit', 
    1655       function() 
    1656       { 
    1657         editor._textArea.value = editor.outwardHtml(editor.getHTML()); 
    1658         return true; 
    1659       } 
    1660     ); 
    1661  
    1662     var initialTAContent = textarea.value; 
    1663  
    1664     // onreset revert the Xinha content to the textarea content 
    1665     Xinha.prependDom0Event( 
    1666       this._textArea.form, 
    1667       'reset', 
    1668       function() 
    1669       { 
    1670         editor.setHTML(editor.inwardHtml(initialTAContent)); 
    1671         editor.updateToolbar(); 
    1672         return true; 
    1673       } 
    1674     ); 
    1675  
    1676     //add onsubmit handlers for textareas that don't have one  
    1677     // doesn't work in IE!! 
    1678    /* if ( !textarea.form.xinha_submit ) 
    1679     { 
    1680       textarea.form.xinha_submit = textarea.form.submit; 
    1681       textarea.form.submit = function() 
    1682       { 
    1683         for ( var i = this.elements.length; i--; ) 
    1684         { 
    1685           var element = this.elements[i]; 
    1686           if ( element.type != 'textarea' ) continue; 
    1687           for ( var a = __xinhas.length; a--; ) 
    1688           { 
    1689             var editor = __xinhas[a]; 
    1690             if ( editor && editor._textArea == element) 
    1691             { 
    1692               element.value = editor.outwardHtml(editor.getHTML()); 
    1693             } 
    1694           } 
    1695         } 
    1696         this.xinha_submit(); 
    1697       }; 
    1698     }*/ 
    1699   } 
    1700  
    1701   // add a handler for the "back/forward" case -- on body.unload we save 
    1702   // the HTML content into the original textarea. 
    1703   Xinha.prependDom0Event( 
    1704     window, 
    1705     'unload', 
    1706     function() 
    1707     { 
    1708       textarea.value = editor.outwardHtml(editor.getHTML()); 
    1709       return true; 
    1710     } 
    1711   ); 
    1712  
    1713   // Hide textarea 
    1714   textarea.style.display = "none"; 
    1715  
    1716   // Initalize size 
    1717   editor.initSize(); 
    1718  
    1719   // Add an event to initialize the iframe once loaded. 
    1720   editor._iframeLoadDone = false; 
    1721   Xinha._addEvent( 
    1722     this._iframe, 
    1723     'load', 
    1724     function(e) 
    1725     { 
    1726       if ( !editor._iframeLoadDone ) 
    1727       { 
    1728         editor._iframeLoadDone = true; 
    1729         editor.initIframe(); 
    1730       } 
    1731       return true; 
    1732     } 
    1733   ); 
    1734  
    1735 }; 
    1736  
    1737 /** 
    1738  * Size the editor according to the INITIAL sizing information. 
    1739  * config.width 
    1740  *    The width may be set via three ways 
    1741  *    auto    = the width is inherited from the original textarea 
    1742  *    toolbar = the width is set to be the same size as the toolbar 
    1743  *    <set size> = the width is an explicit size (any CSS measurement, eg 100em should be fine) 
    1744  * 
    1745  * config.height 
    1746  *    auto    = the height is inherited from the original textarea 
    1747  *    <set size> = an explicit size measurement (again, CSS measurements) 
    1748  * 
    1749  * config.sizeIncludesBars 
    1750  *    true    = the tool & status bars will appear inside the width & height confines 
    1751  *    false   = the tool & status bars will appear outside the width & height confines 
    1752  * 
    1753  */ 
    1754  
    1755 Xinha.prototype.initSize = function() 
    1756 { 
    1757   this.setLoadingMessage('Init editor size'); 
    1758   var editor = this; 
    1759   var width = null; 
    1760   var height = null; 
    1761  
    1762   switch ( this.config.width ) 
    1763   { 
    1764     case 'auto': 
    1765       width = this._initial_ta_size.w; 
    1766     break; 
    1767  
    1768     case 'toolbar': 
    1769       width = this._toolBar.offsetWidth + 'px'; 
    1770     break; 
    1771  
    1772     default : 
    1773       // @todo: check if this is better : 
    1774       // width = (parseInt(this.config.width, 10) == this.config.width)? this.config.width + 'px' : this.config.width; 
    1775       width = /[^0-9]/.test(this.config.width) ? this.config.width : this.config.width + 'px'; 
    1776     break; 
    1777   } 
    1778  
    1779   switch ( this.config.height ) 
    1780   { 
    1781     case 'auto': 
    1782       height = this._initial_ta_size.h; 
    1783     break; 
    1784  
    1785     default : 
    1786       // @todo: check if this is better : 
    1787       // height = (parseInt(this.config.height, 10) == this.config.height)? this.config.height + 'px' : this.config.height; 
    1788       height = /[^0-9]/.test(this.config.height) ? this.config.height : this.config.height + 'px'; 
    1789     break; 
    1790   } 
    1791  
    1792   this.sizeEditor(width, height, this.config.sizeIncludesBars, this.config.sizeIncludesPanels); 
    1793  
    1794   // why can't we use the following line instead ? 
    1795 //  this.notifyOn('panel_change',this.sizeEditor); 
    1796   this.notifyOn('panel_change',function() { editor.sizeEditor(); }); 
    1797 }; 
    1798  
    1799 /** 
    1800  *  Size the editor to a specific size, or just refresh the size (when window resizes for example) 
    1801  *  @param width optional width (CSS specification) 
    1802  *  @param height optional height (CSS specification) 
    1803  *  @param includingBars optional boolean to indicate if the size should include or exclude tool & status bars 
    1804  */ 
    1805 Xinha.prototype.sizeEditor = function(width, height, includingBars, includingPanels) 
    1806 { 
    1807  
    1808   // We need to set the iframe & textarea to 100% height so that the htmlarea 
    1809   // isn't "pushed out" when we get it's height, so we can change them later. 
    1810   this._iframe.style.height   = '100%'; 
    1811   this._textArea.style.height = '100%'; 
    1812   this._iframe.style.width    = ''; 
    1813   this._textArea.style.width  = ''; 
    1814  
    1815   if ( includingBars !== null ) 
    1816   { 
    1817     this._htmlArea.sizeIncludesToolbars = includingBars; 
    1818   } 
    1819   if ( includingPanels !== null ) 
    1820   { 
    1821     this._htmlArea.sizeIncludesPanels = includingPanels; 
    1822   } 
    1823  
    1824   if ( width ) 
    1825   { 
    1826     this._htmlArea.style.width = width; 
    1827     if ( !this._htmlArea.sizeIncludesPanels ) 
    1828     { 
    1829       // Need to add some for l & r panels 
    1830       var rPanel = this._panels.right; 
    1831       if ( rPanel.on && rPanel.panels.length && Xinha.hasDisplayedChildren(rPanel.div) ) 
    1832       { 
    1833         this._htmlArea.style.width = (this._htmlArea.offsetWidth + parseInt(this.config.panel_dimensions.right, 10)) + 'px'; 
    1834       } 
    1835  
    1836       var lPanel = this._panels.left; 
    1837       if ( lPanel.on && lPanel.panels.length && Xinha.hasDisplayedChildren(lPanel.div) ) 
    1838       { 
    1839         this._htmlArea.style.width = (this._htmlArea.offsetWidth + parseInt(this.config.panel_dimensions.left, 10)) + 'px'; 
    1840       } 
    1841     } 
    1842   } 
    1843  
    1844   if ( height ) 
    1845   { 
    1846     this._htmlArea.style.height = height; 
    1847     if ( !this._htmlArea.sizeIncludesToolbars ) 
    1848     { 
    1849       // Need to add some for toolbars 
    1850       this._htmlArea.style.height = (this._htmlArea.offsetHeight + this._toolbar.offsetHeight + this._statusBar.offsetHeight) + 'px'; 
    1851     } 
    1852  
    1853     if ( !this._htmlArea.sizeIncludesPanels ) 
    1854     { 
    1855       // Need to add some for t & b panels 
    1856       var tPanel = this._panels.top; 
    1857       if ( tPanel.on && tPanel.panels.length && Xinha.hasDisplayedChildren(tPanel.div) ) 
    1858       { 
    1859         this._htmlArea.style.height = (this._htmlArea.offsetHeight + parseInt(this.config.panel_dimensions.top, 10)) + 'px'; 
    1860       } 
    1861  
    1862       var bPanel = this._panels.bottom; 
    1863       if ( bPanel.on && bPanel.panels.length && Xinha.hasDisplayedChildren(bPanel.div) ) 
    1864       { 
    1865         this._htmlArea.style.height = (this._htmlArea.offsetHeight + parseInt(this.config.panel_dimensions.bottom, 10)) + 'px'; 
    1866       } 
    1867     } 
    1868   } 
    1869  
    1870   // At this point we have this._htmlArea.style.width & this._htmlArea.style.height 
    1871   // which are the size for the OUTER editor area, including toolbars and panels 
    1872   // now we size the INNER area and position stuff in the right places. 
    1873   width  = this._htmlArea.offsetWidth; 
    1874   height = this._htmlArea.offsetHeight; 
    1875  
    1876   // Set colspan for toolbar, and statusbar, rowspan for left & right panels, and insert panels to be displayed 
    1877   // into thier rows 
    1878   var panels = this._panels; 
    1879   var editor = this; 
    1880   var col_span = 1; 
    1881  
    1882   function panel_is_alive(pan) 
    1883   { 
    1884     if ( panels[pan].on && panels[pan].panels.length && Xinha.hasDisplayedChildren(panels[pan].container) ) 
    1885     { 
    1886       panels[pan].container.style.display = ''; 
    1887       return true; 
    1888     } 
    1889     // Otherwise make sure it's been removed from the framework 
    1890     else 
    1891     { 
    1892       panels[pan].container.style.display='none'; 
    1893       return false; 
    1894     } 
    1895   } 
    1896  
    1897   if ( panel_is_alive('left') ) 
    1898   { 
    1899     col_span += 1;       
    1900   } 
    1901  
    1902 //  if ( panel_is_alive('top') ) 
    1903 //  { 
    1904     // NOP 
    1905 //  } 
    1906  
    1907   if ( panel_is_alive('right') ) 
    1908   { 
    1909     col_span += 1; 
    1910   } 
    1911  
    1912 //  if ( panel_is_alive('bottom') ) 
    1913 //  { 
    1914     // NOP 
    1915 //  } 
    1916  
    1917   this._framework.tb_cell.colSpan = col_span; 
    1918   this._framework.tp_cell.colSpan = col_span; 
    1919   this._framework.bp_cell.colSpan = col_span; 
    1920   this._framework.sb_cell.colSpan = col_span; 
    1921  
    1922   // Put in the panel rows, top panel goes above editor row 
    1923   if ( !this._framework.tp_row.childNodes.length ) 
    1924   { 
    1925     Xinha.removeFromParent(this._framework.tp_row); 
    1926   } 
    1927   else 
    1928   { 
    1929     if ( !Xinha.hasParentNode(this._framework.tp_row) ) 
    1930     { 
    1931       this._framework.tbody.insertBefore(this._framework.tp_row, this._framework.ler_row); 
    1932     } 
    1933   } 
    1934  
    1935   // bp goes after the editor 
    1936   if ( !this._framework.bp_row.childNodes.length ) 
    1937   { 
    1938     Xinha.removeFromParent(this._framework.bp_row); 
    1939   } 
    1940   else 
    1941   { 
    1942     if ( !Xinha.hasParentNode(this._framework.bp_row) ) 
    1943     { 
    1944       this._framework.tbody.insertBefore(this._framework.bp_row, this._framework.ler_row.nextSibling); 
    1945     } 
    1946   } 
    1947  
    1948   // finally if the statusbar is on, insert it 
    1949   if ( !this.config.statusBar ) 
    1950   { 
    1951     Xinha.removeFromParent(this._framework.sb_row); 
    1952   } 
    1953   else 
    1954   { 
    1955     if ( !Xinha.hasParentNode(this._framework.sb_row) ) 
    1956     { 
    1957       this._framework.table.appendChild(this._framework.sb_row); 
    1958     } 
    1959   } 
    1960  
    1961   // Size and set colspans, link up the framework 
    1962   this._framework.lp_cell.style.width  = this.config.panel_dimensions.left; 
    1963   this._framework.rp_cell.style.width  = this.config.panel_dimensions.right; 
    1964   this._framework.tp_cell.style.height = this.config.panel_dimensions.top; 
    1965   this._framework.bp_cell.style.height = this.config.panel_dimensions.bottom; 
    1966   this._framework.tb_cell.style.height = this._toolBar.offsetHeight + 'px'; 
    1967   this._framework.sb_cell.style.height = this._statusBar.offsetHeight + 'px'; 
    1968  
    1969   var edcellheight = height - this._toolBar.offsetHeight - this._statusBar.offsetHeight; 
    1970   if ( panel_is_alive('top') ) 
    1971   { 
    1972     edcellheight -= parseInt(this.config.panel_dimensions.top, 10); 
    1973   } 
    1974   if ( panel_is_alive('bottom') ) 
    1975   { 
    1976     edcellheight -= parseInt(this.config.panel_dimensions.bottom, 10); 
    1977   } 
    1978   this._iframe.style.height = edcellheight + 'px';   
    1979 //  this._framework.rp_cell.style.height = edcellheight + 'px'; 
    1980 //  this._framework.lp_cell.style.height = edcellheight + 'px'; 
    1981    
    1982   // (re)size the left and right panels so they are equal the editor height 
    1983 //  for(var i = 0; i < this._panels.left.panels.length; i++) 
    1984 //  { 
    1985 //    this._panels.left.panels[i].style.height = this._iframe.style.height; 
    1986 //  } 
    1987    
    1988 //  for(var i = 0; i < this._panels.right.panels.length; i++) 
    1989 //  { 
    1990 //    this._panels.right.panels[i].style.height = this._iframe.style.height; 
    1991 //  }   
    1992    
    1993   var edcellwidth = width; 
    1994   if ( panel_is_alive('left') ) 
    1995   { 
    1996     edcellwidth -= parseInt(this.config.panel_dimensions.left, 10); 
    1997   } 
    1998   if ( panel_is_alive('right') ) 
    1999   { 
    2000     edcellwidth -= parseInt(this.config.panel_dimensions.right, 10);     
    2001   } 
    2002   this._iframe.style.width = edcellwidth + 'px'; 
    2003  
    2004   this._textArea.style.height = this._iframe.style.height; 
    2005   this._textArea.style.width  = this._iframe.style.width; 
    2006       
    2007   this.notifyOf('resize', {width:this._htmlArea.offsetWidth, height:this._htmlArea.offsetHeight}); 
    2008 }; 
    2009  
    2010 Xinha.prototype.addPanel = function(side) 
    2011 { 
    2012   var div = document.createElement('div'); 
    2013   div.side = side; 
    2014   if ( side == 'left' || side == 'right' ) 
    2015   { 
    2016     div.style.width  = this.config.panel_dimensions[side]; 
    2017     if(this._iframe) div.style.height = this._iframe.style.height;      
    2018   } 
    2019   Xinha.addClasses(div, 'panel'); 
    2020   this._panels[side].panels.push(div); 
    2021   this._panels[side].div.appendChild(div); 
    2022  
    2023   this.notifyOf('panel_change', {'action':'add','panel':div}); 
    2024  
    2025   return div; 
    2026 }; 
    2027  
    2028  
    2029 Xinha.prototype.removePanel = function(panel) 
    2030 { 
    2031   this._panels[panel.side].div.removeChild(panel); 
    2032   var clean = []; 
    2033   for ( var i = 0; i < this._panels[panel.side].panels.length; i++ ) 
    2034   { 
    2035     if ( this._panels[panel.side].panels[i] != panel ) 
    2036     { 
    2037       clean.push(this._panels[panel.side].panels[i]); 
    2038     } 
    2039   } 
    2040   this._panels[panel.side].panels = clean; 
    2041   this.notifyOf('panel_change', {'action':'remove','panel':panel}); 
    2042 }; 
    2043  
    2044 Xinha.prototype.hidePanel = function(panel) 
    2045 { 
    2046   if ( panel && panel.style.display != 'none' ) 
    2047   { 
    2048     panel.style.display = 'none'; 
    2049     this.notifyOf('panel_change', {'action':'hide','panel':panel}); 
    2050   } 
    2051 }; 
    2052  
    2053 Xinha.prototype.showPanel = function(panel) 
    2054 { 
    2055   if ( panel && panel.style.display == 'none' ) 
    2056   { 
    2057     panel.style.display = ''; 
    2058     this.notifyOf('panel_change', {'action':'show','panel':panel}); 
    2059   } 
    2060 }; 
    2061  
    2062 Xinha.prototype.hidePanels = function(sides) 
    2063 { 
    2064   if ( typeof sides == 'undefined' ) 
    2065   { 
    2066     sides = ['left','right','top','bottom']; 
    2067   } 
    2068  
    2069   var reShow = []; 
    2070   for ( var i = 0; i < sides.length;i++ ) 
    2071   { 
    2072     if ( this._panels[sides[i]].on ) 
    2073     { 
    2074       reShow.push(sides[i]); 
    2075       this._panels[sides[i]].on = false; 
    2076     } 
    2077   } 
    2078   this.notifyOf('panel_change', {'action':'multi_hide','sides':sides}); 
    2079 }; 
    2080  
    2081 Xinha.prototype.showPanels = function(sides) 
    2082 { 
    2083   if ( typeof sides == 'undefined' ) 
    2084   { 
    2085     sides = ['left','right','top','bottom']; 
    2086   } 
    2087  
    2088   var reHide = []; 
    2089   for ( var i = 0; i < sides.length; i++ ) 
    2090   { 
    2091     if ( !this._panels[sides[i]].on ) 
    2092     { 
    2093       reHide.push(sides[i]); 
    2094       this._panels[sides[i]].on = true; 
    2095     } 
    2096   } 
    2097   this.notifyOf('panel_change', {'action':'multi_show','sides':sides}); 
    2098 }; 
    2099  
    2100 Xinha.objectProperties = function(obj) 
    2101 { 
    2102   var props = []; 
    2103   for ( var x in obj ) 
    2104   { 
    2105     props[props.length] = x; 
    2106   } 
    2107   return props; 
    2108 }; 
    2109  
    2110 /* 
    2111  * EDITOR ACTIVATION NOTES: 
    2112  *  when a page has multiple Xinha editors, ONLY ONE should be activated at any time (this is mostly to 
    2113  *  work around a bug in Mozilla, but also makes some sense).  No editor should be activated or focused 
    2114  *  automatically until at least one editor has been activated through user action (by mouse-clicking in 
    2115  *  the editor). 
    2116  */ 
    2117 Xinha.prototype.editorIsActivated = function() 
    2118 { 
    2119   try 
    2120   { 
    2121     return Xinha.is_gecko? this._doc.designMode == 'on' : this._doc.body.contentEditable; 
    2122   } 
    2123   catch (ex) 
    2124   { 
    2125     return false; 
    2126   } 
    2127 }; 
    2128  
    2129 Xinha._someEditorHasBeenActivated = false; 
    2130 Xinha._currentlyActiveEditor      = false; 
    2131 Xinha.prototype.activateEditor = function() 
    2132 { 
    2133   // We only want ONE editor at a time to be active 
    2134   if ( Xinha._currentlyActiveEditor ) 
    2135   { 
    2136     if ( Xinha._currentlyActiveEditor == this ) 
    2137     { 
    2138       return true; 
    2139     } 
    2140     Xinha._currentlyActiveEditor.deactivateEditor(); 
    2141   } 
    2142  
    2143   if ( Xinha.is_gecko && this._doc.designMode != 'on' ) 
    2144   { 
    2145     try 
    2146     { 
    2147       // cannot set design mode if no display 
    2148       if ( this._iframe.style.display == 'none' ) 
    2149       { 
    2150         this._iframe.style.display = ''; 
    2151         this._doc.designMode = 'on'; 
    2152         this._iframe.style.display = 'none'; 
    2153       } 
    2154       else 
    2155       { 
    2156         this._doc.designMode = 'on'; 
    2157       } 
    2158     } catch (ex) {} 
    2159   } 
    2160   else if ( !Xinha.is_gecko && this._doc.body.contentEditable !== true ) 
    2161   { 
    2162     this._doc.body.contentEditable = true; 
    2163   } 
    2164  
    2165   // We need to know that at least one editor on the page has been activated 
    2166   // this is because we will not focus any editor until an editor has been activated 
    2167   Xinha._someEditorHasBeenActivated = true; 
    2168   Xinha._currentlyActiveEditor      = this; 
    2169  
    2170   var editor = this; 
    2171   this.enableToolbar(); 
    2172 }; 
    2173  
    2174 Xinha.prototype.deactivateEditor = function() 
    2175 { 
    2176   // If the editor isn't active then the user shouldn't use the toolbar 
    2177   this.disableToolbar(); 
    2178  
    2179   if ( Xinha.is_gecko && this._doc.designMode != 'off' ) 
    2180   { 
    2181     try 
    2182     { 
    2183       this._doc.designMode = 'off'; 
    2184     } catch (ex) {} 
    2185   } 
    2186   else if ( !Xinha.is_gecko && this._doc.body.contentEditable !== false ) 
    2187   { 
    2188     this._doc.body.contentEditable = false; 
    2189   } 
    2190  
    2191   if ( Xinha._currentlyActiveEditor != this ) 
    2192   { 
    2193     // We just deactivated an editor that wasn't marked as the currentlyActiveEditor 
    2194  
    2195     return; // I think this should really be an error, there shouldn't be a situation where 
    2196             // an editor is deactivated without first being activated.  but it probably won't 
    2197             // hurt anything. 
    2198   } 
    2199  
    2200   Xinha._currentlyActiveEditor = false; 
    2201 }; 
    2202  
    2203 Xinha.prototype.initIframe = function() 
    2204 { 
    2205   this.setLoadingMessage('Init IFrame'); 
    2206   this.disableToolbar(); 
    2207   var doc = null; 
    2208   var editor = this; 
    2209   try 
    2210   { 
    2211     if ( editor._iframe.contentDocument ) 
    2212     { 
    2213       this._doc = editor._iframe.contentDocument;         
    2214     } 
    2215     else 
    2216     { 
    2217       this._doc = editor._iframe.contentWindow.document; 
    2218     } 
    2219     doc = this._doc; 
    2220     // try later 
    2221     if ( !doc ) 
    2222     { 
    2223       if ( Xinha.is_gecko ) 
    2224       { 
    2225         setTimeout(function() { editor.initIframe(); }, 50); 
    2226         return false; 
    2227       } 
    2228       else 
    2229       { 
    2230         alert("ERROR: IFRAME can't be initialized."); 
    2231       } 
    2232     } 
    2233   } 
    2234   catch(ex) 
    2235   { // try later 
    2236     setTimeout(function() { editor.initIframe(); }, 50); 
    2237   } 
    2238    
    2239   Xinha.freeLater(this, '_doc'); 
    2240    
    2241   doc.open("text/html","replace"); 
    2242   var html = ''; 
    2243   if ( !editor.config.fullPage ) 
    2244   { 
    2245     html = "<html>\n"; 
    2246     html += "<head>\n"; 
    2247     html += "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=" + editor.config.charSet + "\">\n"; 
    2248     if ( typeof editor.config.baseHref != 'undefined' && editor.config.baseHref !== null ) 
    2249     { 
    2250       html += "<base href=\"" + editor.config.baseHref + "\"/>\n"; 
    2251     } 
    2252     html += "<style title=\"table borders\">"; 
    2253     html += ".htmtableborders, .htmtableborders td, .htmtableborders th {border : 1px dashed lightgrey ! important;} \n"; 
    2254     html += "</style>\n"; 
    2255     html += "<style type=\"text/css\">"; 
    2256     html += "html, body { border: 0px; } \n"; 
    2257     html += "body { background-color: #ffffff; } \n"; 
    2258     html += "span.macro, span.macro ul, span.macro div, span.macro p {background : #CCCCCC;}\n"; 
    2259     html += "</style>\n"; 
    2260  
    2261     if ( editor.config.pageStyle ) 
    2262     { 
    2263       html += "<style type=\"text/css\">\n" + editor.config.pageStyle + "\n</style>"; 
    2264     } 
    2265  
    2266     if ( typeof editor.config.pageStyleSheets !== 'undefined' ) 
    2267     { 
    2268       for ( var i = 0; i < editor.config.pageStyleSheets.length; i++ ) 
    2269       { 
    2270         if ( editor.config.pageStyleSheets[i].length > 0 ) 
    2271         { 
    2272           html += "<link rel=\"stylesheet\" type=\"text/css\" href=\"" + editor.config.pageStyleSheets[i] + "\">"; 
    2273           //html += "<style> @import url('" + editor.config.pageStyleSheets[i] + "'); </style>\n"; 
    2274         } 
    2275       } 
    2276     } 
    2277     html += "</head>\n"; 
    2278     html += "<body>\n"; 
    2279     html +=   editor.inwardHtml(editor._textArea.value); 
    2280     html += "</body>\n"; 
    2281     html += "</html>"; 
    2282   } 
    2283   else 
    2284   { 
    2285     html = editor.inwardHtml(editor._textArea.value); 
    2286     if ( html.match(Xinha.RE_doctype) ) 
    2287     { 
    2288       editor.setDoctype(RegExp.$1); 
    2289       html = html.replace(Xinha.RE_doctype, ""); 
    2290     } 
    2291   } 
    2292   doc.write(html); 
    2293   doc.close(); 
    2294  
    2295   this.setEditorEvents(); 
    2296 }; 
    2297    
    2298 /** 
    2299  * Delay a function until the document is ready for operations. 
    2300  * See ticket:547 
    2301  * @param {object} F (Function) The function to call once the document is ready 
    2302  * @public 
    2303  */ 
    2304 Xinha.prototype.whenDocReady = function(F) 
    2305 { 
    2306   var E = this; 
    2307   if ( this._doc && this._doc.body ) 
    2308   { 
    2309     F(); 
    2310   } 
    2311   else 
    2312   { 
    2313     setTimeout(function() { E.whenDocReady(F); }, 50); 
    2314   } 
    2315 }; 
    2316  
    2317 // Switches editor mode; parameter can be "textmode" or "wysiwyg".  If no 
    2318 // parameter was passed this function toggles between modes. 
    2319 Xinha.prototype.setMode = function(mode) 
    2320 { 
    2321   var html; 
    2322   if ( typeof mode == "undefined" ) 
    2323   { 
    2324     mode = this._editMode == "textmode" ? "wysiwyg" : "textmode"; 
    2325   } 
    2326   switch ( mode ) 
    2327   { 
    2328     case "textmode": 
    2329       html = this.outwardHtml(this.getHTML()); 
    2330       this.setHTML(html); 
    2331  
    2332       // Hide the iframe 
    2333       this.deactivateEditor(); 
    2334       this._iframe.style.display   = 'none'; 
    2335       this._textArea.style.display = ''; 
    2336  
    2337       if ( this.config.statusBar ) 
    2338       { 
    2339         this._statusBarTree.style.display = "none"; 
    2340         this._statusBarTextMode.style.display = ""; 
    2341       } 
    2342  
    2343       this.notifyOf('modechange', {'mode':'text'}); 
    2344     break; 
    2345  
    2346     case "wysiwyg": 
    2347       html = this.inwardHtml(this.getHTML()); 
    2348       this.deactivateEditor(); 
    2349       this.setHTML(html); 
    2350       this._iframe.style.display   = ''; 
    2351       this._textArea.style.display = "none"; 
    2352       this.activateEditor(); 
    2353       if ( this.config.statusBar ) 
    2354       { 
    2355         this._statusBarTree.style.display = ""; 
    2356         this._statusBarTextMode.style.display = "none"; 
    2357       } 
    2358  
    2359       this.notifyOf('modechange', {'mode':'wysiwyg'}); 
    2360     break; 
    2361  
    2362     default: 
    2363       alert("Mode <" + mode + "> not defined!"); 
    2364       return false; 
    2365   } 
    2366   this._editMode = mode; 
    2367  
    2368   for ( var i in this.plugins ) 
    2369   { 
    2370     var plugin = this.plugins[i].instance; 
    2371     if ( plugin && typeof plugin.onMode == "function" ) 
    2372     { 
    2373       plugin.onMode(mode); 
    2374     } 
    2375   } 
    2376 }; 
    2377  
    2378 Xinha.prototype.setFullHTML = function(html) 
    2379 { 
    2380   var save_multiline = RegExp.multiline; 
    2381   RegExp.multiline = true; 
    2382   if ( html.match(Xinha.RE_doctype) ) 
    2383   { 
    2384     this.setDoctype(RegExp.$1); 
    2385     html = html.replace(Xinha.RE_doctype, ""); 
    2386   } 
    2387   RegExp.multiline = save_multiline; 
    2388   if ( !Xinha.is_ie ) 
    2389   { 
    2390     if ( html.match(Xinha.RE_head) ) 
    2391     { 
    2392       this._doc.getElementsByTagName("head")[0].innerHTML = RegExp.$1; 
    2393     } 
    2394     if ( html.match(Xinha.RE_body) ) 
    2395     { 
    2396       this._doc.getElementsByTagName("body")[0].innerHTML = RegExp.$1; 
    2397     } 
    2398   } 
    2399   else 
    2400   { 
    2401     var reac = this.editorIsActivated(); 
    2402     if ( reac ) 
    2403     { 
    2404       this.deactivateEditor(); 
    2405     } 
    2406     var html_re = /<html>((.|\n)*?)<\/html>/i; 
    2407     html = html.replace(html_re, "$1"); 
    2408     this._doc.open("text/html","replace"); 
    2409     this._doc.write(html); 
    2410     this._doc.close(); 
    2411     if ( reac ) 
    2412     { 
    2413       this.activateEditor(); 
    2414     } 
    2415     this.setEditorEvents(); 
    2416     return true; 
    2417   } 
    2418 }; 
    2419  
    2420 Xinha.prototype.setEditorEvents = function() 
    2421 { 
    2422   var editor=this; 
    2423   var doc=this._doc; 
    2424   editor.whenDocReady( 
    2425     function() 
    2426     { 
    2427       // if we have multiple editors some bug in Mozilla makes some lose editing ability 
    2428       Xinha._addEvents( 
    2429         doc, 
    2430         ["mousedown"], 
    2431         function() 
    2432         { 
    2433           editor.activateEditor(); 
    2434           return true; 
    2435         } 
    2436       ); 
    2437  
    2438       // intercept some events; for updating the toolbar & keyboard handlers 
    2439       Xinha._addEvents( 
    2440         doc, 
    2441         ["keydown", "keypress", "mousedown", "mouseup", "drag"], 
    2442         function (event) 
    2443         { 
    2444           return editor._editorEvent(Xinha.is_ie ? editor._iframe.contentWindow.event : event); 
    2445         } 
    2446       ); 
    2447  
    2448       // check if any plugins have registered refresh handlers 
    2449       for ( var i in editor.plugins ) 
    2450       { 
    2451         var plugin = editor.plugins[i].instance; 
    2452         Xinha.refreshPlugin(plugin); 
    2453       } 
    2454  
    2455       // specific editor initialization 
    2456       if ( typeof editor._onGenerate == "function" ) 
    2457       { 
    2458         editor._onGenerate(); 
    2459       } 
    2460  
    2461       Xinha.addDom0Event(window, 'resize', function(e) { editor.sizeEditor(); }); 
    2462       editor.removeLoadingMessage(); 
    2463     } 
    2464   ); 
    2465 }; 
    2466    
    2467 /*************************************************** 
    2468  *  Category: PLUGINS 
    2469  ***************************************************/ 
    2470  
    2471 // Create the specified plugin and register it with this Xinha 
    2472 // return the plugin created to allow refresh when necessary 
    2473 Xinha.prototype.registerPlugin = function() 
    2474 { 
    2475   var plugin = arguments[0]; 
    2476  
    2477   // @todo : try to avoid the use of eval() 
    2478   // We can only register plugins that have been succesfully loaded 
    2479   if ( plugin === null || typeof plugin == 'undefined' || (typeof plugin == 'string' && eval('typeof ' + plugin) == 'undefined') ) 
    2480   { 
    2481     return false; 
    2482   } 
    2483  
    2484   var args = []; 
    2485   for ( var i = 1; i < arguments.length; ++i ) 
    2486   { 
    2487     args.push(arguments[i]); 
    2488   } 
    2489   return this.registerPlugin2(plugin, args); 
    2490 }; 
    2491  
    2492 // this is the variant of the function above where the plugin arguments are 
    2493 // already packed in an array.  Externally, it should be only used in the 
    2494 // full-screen editor code, in order to initialize plugins with the same 
    2495 // parameters as in the opener window. 
    2496 Xinha.prototype.registerPlugin2 = function(plugin, args) 
    2497 { 
    2498   // @todo : try to avoid the use of eval() 
    2499   if ( typeof plugin == "string" ) 
    2500   { 
    2501     plugin = eval(plugin); 
    2502   } 
    2503   if ( typeof plugin == "undefined" ) 
    2504   { 
    2505     /* FIXME: This should never happen. But why does it do? */ 
    2506     return false; 
    2507   } 
    2508   var obj = new plugin(this, args); 
    2509   if ( obj ) 
    2510   { 
    2511     var clone = {}; 
    2512     var info = plugin._pluginInfo; 
    2513     for ( var i in info ) 
    2514     { 
    2515       clone[i] = info[i]; 
    2516     } 
    2517     clone.instance = obj; 
    2518     clone.args = args; 
    2519     this.plugins[plugin._pluginInfo.name] = clone; 
    2520     return obj; 
    2521   } 
    2522   else 
    2523   { 
    2524     alert("Can't register plugin " + plugin.toString() + "."); 
    2525   } 
    2526 }; 
    2527  
    2528 // static function that loads the required plugin and lang file, based on the 
    2529 // language loaded already for Xinha.  You better make sure that the plugin 
    2530 // _has_ that language, otherwise shit might happen ;-) 
    2531 Xinha.getPluginDir = function(pluginName) 
    2532 { 
    2533   return _editor_url + "plugins/" + pluginName; 
    2534 }; 
    2535  
    2536 Xinha.loadPlugin = function(pluginName, callback) 
    2537 { 
    2538   // @todo : try to avoid the use of eval() 
    2539   // Might already be loaded 
    2540   if ( eval('typeof ' + pluginName) != 'undefined' ) 
    2541   { 
    2542     if ( callback ) 
    2543     { 
    2544       callback(pluginName); 
    2545     } 
    2546     return true; 
    2547   } 
    2548  
    2549   var dir = this.getPluginDir(pluginName); 
    2550   var plugin = pluginName.replace(/([a-z])([A-Z])([a-z])/g, function (str, l1, l2, l3) { return l1 + "-" + l2.toLowerCase() + l3; }).toLowerCase() + ".js"; 
    2551   var plugin_file = dir + "/" + plugin; 
    2552  
    2553   Xinha._loadback(plugin_file, callback ? function() { callback(pluginName); } : null); 
    2554   return false; 
    2555 }; 
    2556  
    2557 Xinha._pluginLoadStatus = {}; 
    2558 Xinha._browserSpecificFunctionsLoaded = false; 
    2559 Xinha.loadPlugins = function(plugins, callbackIfNotReady) 
    2560 { 
    2561   // Rip the ones that are loaded and look for ones that have failed 
    2562   var retVal = true; 
    2563   var nuPlugins = Xinha.cloneObject(plugins); 
    2564  
    2565   if ( !Xinha._browserSpecificFunctionsLoaded ) 
    2566   { 
    2567         if (Xinha.is_ie) 
    2568         { 
    2569                 Xinha._loadback(_editor_url + "functionsIE.js",callbackIfNotReady); 
    2570         } 
    2571         else 
    2572         { 
    2573                 Xinha._loadback(_editor_url + "functionsMozilla.js",callbackIfNotReady); 
    2574         } 
    2575         return false; 
    2576   } 
    2577   while ( nuPlugins.length ) 
    2578   { 
    2579     var p = nuPlugins.pop(); 
    2580     if ( typeof Xinha._pluginLoadStatus[p] == 'undefined' ) 
    2581     { 
    2582       // Load it 
    2583       Xinha._pluginLoadStatus[p] = 'loading'; 
    2584       Xinha.loadPlugin(p, 
    2585         function(plugin) 
    2586         { 
    2587           // @todo : try to avoid the use of eval() 
    2588           if ( eval('typeof ' + plugin) != 'undefined' ) 
    2589           { 
    2590             Xinha._pluginLoadStatus[plugin] = 'ready'; 
    2591           } 
    2592           else 
    2593           { 
    2594             // Actually, this won't happen, because if the script fails 
    2595             // it will throw an exception preventing the callback from 
    2596             // running.  This will leave it always in the "loading" state 
    2597             // unfortunatly that means we can't fail plugins gracefully 
    2598             // by just skipping them. 
    2599             Xinha._pluginLoadStatus[plugin] = 'failed'; 
    2600           } 
    2601         } 
    2602       ); 
    2603       retVal = false; 
    2604     } 
    2605     else 
    2606     { 
    2607       // @todo: a simple (if) would not be better than this tortuous (switch) structure ? 
    2608       // if ( Xinha._pluginLoadStatus[p] !== 'failed' && Xinha._pluginLoadStatus[p] !== 'ready' ) 
    2609       // { 
    2610       //   retVal = false; 
    2611       // } 
    2612       switch ( Xinha._pluginLoadStatus[p] ) 
    2613       { 
    2614         case 'failed': 
    2615         case 'ready' : 
    2616         break; 
    2617  
    2618         //case 'loading': 
    2619         default       : 
    2620          retVal = false; 
    2621        break; 
    2622       } 
    2623     } 
    2624   } 
    2625  
    2626   // All done, just return 
    2627   if ( retVal ) 
    2628   { 
    2629     return true; 
    2630   }  
    2631  
    2632   // Waiting on plugins to load, return false now and come back a bit later 
    2633   // if we have to callback 
    2634   if ( callbackIfNotReady ) 
    2635   { 
    2636     setTimeout(function() { if ( Xinha.loadPlugins(plugins, callbackIfNotReady) ) { callbackIfNotReady(); } }, 150); 
    2637   } 
    2638   return retVal; 
    2639 }; 
    2640  
    2641 // refresh plugin by calling onGenerate or onGenerateOnce method. 
    2642 Xinha.refreshPlugin = function(plugin) 
    2643 { 
    2644   if ( plugin && typeof plugin.onGenerate == "function" ) 
    2645   { 
    2646     plugin.onGenerate(); 
    2647   } 
    2648   if ( plugin && typeof plugin.onGenerateOnce == "function" ) 
    2649   { 
    2650     plugin.onGenerateOnce(); 
    2651     plugin.onGenerateOnce = null; 
    2652   } 
    2653 }; 
    2654  
    2655 Xinha.loadStyle = function(style, plugin) 
    2656 { 
    2657   var url = _editor_url || ''; 
    2658   if ( typeof plugin != "undefined" ) 
    2659   { 
    2660     url += "plugins/" + plugin + "/"; 
    2661   } 
    2662   url += style; 
    2663   // @todo: would not it be better to check the first character instead of a regex ? 
    2664   // if ( typeof style == 'string' && style.charAt(0) == '/' ) 
    2665   // { 
    2666   //   url = style; 
    2667   // } 
    2668   if ( /^\//.test(style) ) 
    2669   { 
    2670     url = style; 
    2671   } 
    2672   var head = document.getElementsByTagName("head")[0]; 
    2673   var link = document.createElement("link"); 
    2674   link.rel = "stylesheet"; 
    2675   link.href = url; 
    2676   head.appendChild(link); 
    2677   //document.write("<style type='text/css'>@import url(" + url + ");</style>"); 
    2678 }; 
    2679 Xinha.loadStyle(typeof _editor_css == "string" ? _editor_css : "Xinha.css"); 
    2680  
    2681 /*************************************************** 
    2682  *  Category: EDITOR UTILITIES 
    2683  ***************************************************/ 
    2684  
    2685 Xinha.prototype.debugTree = function() 
    2686 { 
    2687   var ta = document.createElement("textarea"); 
    2688   ta.style.width = "100%"; 
    2689   ta.style.height = "20em"; 
    2690   ta.value = ""; 
    2691   function debug(indent, str) 
    2692   { 
    2693     for ( ; --indent >= 0; ) 
    2694     { 
    2695       ta.value += " "; 
    2696     } 
    2697     ta.value += str + "\n"; 
    2698   } 
    2699   function _dt(root, level) 
    2700   { 
    2701     var tag = root.tagName.toLowerCase(), i; 
    2702     var ns = Xinha.is_ie ? root.scopeName : root.prefix; 
    2703     debug(level, "- " + tag + " [" + ns + "]"); 
    2704     for ( i = root.firstChild; i; i = i.nextSibling ) 
    2705     { 
    2706       if ( i.nodeType == 1 ) 
    2707       { 
    2708         _dt(i, level + 2); 
    2709       } 
    2710     } 
    2711   } 
    2712   _dt(this._doc.body, 0); 
    2713   document.body.appendChild(ta); 
    2714 }; 
    2715  
    2716 Xinha.getInnerText = function(el) 
    2717 { 
    2718   var txt = '', i; 
    2719   for ( i = el.firstChild; i; i = i.nextSibling ) 
    2720   { 
    2721     if ( i.nodeType == 3 ) 
    2722     { 
    2723       txt += i.data; 
    2724     } 
    2725     else if ( i.nodeType == 1 ) 
    2726     { 
    2727       txt += Xinha.getInnerText(i); 
    2728     } 
    2729   } 
    2730   return txt; 
    2731 }; 
    2732  
    2733 Xinha.prototype._wordClean = function() 
    2734 { 
    2735   var editor = this; 
    2736   var stats = 
    2737   { 
    2738     empty_tags : 0, 
    2739     mso_class  : 0, 
    2740     mso_style  : 0, 
    2741     mso_xmlel  : 0, 
    2742     orig_len   : this._doc.body.innerHTML.length, 
    2743     T          : (new Date()).getTime() 
    2744   }; 
    2745   var stats_txt = 
    2746   { 
    2747     empty_tags : "Empty tags removed: ", 
    2748     mso_class  : "MSO class names removed: ", 
    2749     mso_style  : "MSO inline style removed: ", 
    2750     mso_xmlel  : "MSO XML elements stripped: " 
    2751   }; 
    2752  
    2753   function showStats() 
    2754   { 
    2755     var txt = "Xinha word cleaner stats: \n\n"; 
    2756     for ( var i in stats ) 
    2757     { 
    2758       if ( stats_txt[i] ) 
    2759       { 
    2760         txt += stats_txt[i] + stats[i] + "\n"; 
    2761       } 
    2762     } 
    2763     txt += "\nInitial document length: " + stats.orig_len + "\n"; 
    2764     txt += "Final document length: " + editor._doc.body.innerHTML.length + "\n"; 
    2765     txt += "Clean-up took " + (((new Date()).getTime() - stats.T) / 1000) + " seconds"; 
    2766     alert(txt); 
    2767   } 
    2768  
    2769   function clearClass(node) 
    2770   { 
    2771     var newc = node.className.replace(/(^|\s)mso.*?(\s|$)/ig, ' '); 
    2772     if ( newc != node.className ) 
    2773     { 
    2774       node.className = newc; 
    2775       if ( ! ( /\S/.test(node.className) ) ) 
    2776       { 
    2777         node.removeAttribute("className"); 
    2778         ++stats.mso_class; 
    2779       } 
    2780     } 
    2781   } 
    2782  
    2783   function clearStyle(node) 
    2784   { 
    2785     var declarations = node.style.cssText.split(/\s*;\s*/); 
    2786     for ( var i = declarations.length; --i >= 0; ) 
    2787     { 
    2788       if ( ( /^mso|^tab-stops/i.test(declarations[i]) ) || ( /^margin\s*:\s*0..\s+0..\s+0../i.test(declarations[i]) ) ) 
    2789       { 
    2790         ++stats.mso_style; 
    2791         declarations.splice(i, 1); 
    2792       } 
    2793     } 
    2794     node.style.cssText = declarations.join("; "); 
    2795   } 
    2796  
    2797   var stripTag = null; 
    2798   if ( Xinha.is_ie ) 
    2799   { 
    2800     stripTag = function(el) 
    2801     { 
    2802       el.outerHTML = Xinha.htmlEncode(el.innerText); 
    2803       ++stats.mso_xmlel; 
    2804     }; 
    2805   } 
    2806   else 
    2807   { 
    2808     stripTag = function(el) 
    2809     { 
    2810       var txt = document.createTextNode(Xinha.getInnerText(el)); 
    2811       el.parentNode.insertBefore(txt, el); 
    2812       Xinha.removeFromParent(el); 
    2813       ++stats.mso_xmlel; 
    2814     }; 
    2815   } 
    2816  
    2817   function checkEmpty(el) 
    2818   { 
    2819     // @todo : check if this is quicker 
    2820     //  if (!['A','SPAN','B','STRONG','I','EM','FONT'].contains(el.tagName) && !el.firstChild) 
    2821     if ( /^(a|span|b|strong|i|em|font)$/i.test(el.tagName) && !el.firstChild) 
    2822     { 
    2823       Xinha.removeFromParent(el); 
    2824       ++stats.empty_tags; 
    2825     } 
    2826   } 
    2827  
    2828   function parseTree(root) 
    2829   { 
    2830     var tag = root.tagName.toLowerCase(), i, next; 
    2831     // @todo : probably better to use String.indexOf() instead of this ugly regex 
    2832     // if ((Xinha.is_ie && root.scopeName != 'HTML') || (!Xinha.is_ie && tag.indexOf(':') !== -1)) { 
    2833     if ( ( Xinha.is_ie && root.scopeName != 'HTML' ) || ( !Xinha.is_ie && ( /:/.test(tag) ) ) ) 
    2834     { 
    2835       stripTag(root); 
    2836       return false; 
    2837     } 
    2838     else 
    2839     { 
    2840       clearClass(root); 
    2841       clearStyle(root); 
    2842       for ( i = root.firstChild; i; i = next ) 
    2843       { 
    2844         next = i.nextSibling; 
    2845         if ( i.nodeType == 1 && parseTree(i) ) 
    2846         { 
    2847           checkEmpty(i); 
    2848         } 
    2849       } 
    2850     } 
    2851     return true; 
    2852   } 
    2853   parseTree(this._doc.body); 
    2854   // showStats(); 
    2855   // this.debugTree(); 
    2856   // this.setHTML(this.getHTML()); 
    2857   // this.setHTML(this.getInnerHTML()); 
    2858   // this.forceRedraw(); 
    2859   this.updateToolbar(); 
    2860 }; 
    2861  
    2862 Xinha.prototype._clearFonts = function() 
    2863 { 
    2864   var D = this.getInnerHTML(); 
    2865  
    2866   if ( confirm(Xinha._lc("Would you like to clear font typefaces?")) ) 
    2867   { 
    2868     D = D.replace(/face="[^"]*"/gi, ''); 
    2869     D = D.replace(/font-family:[^;}"']+;?/gi, ''); 
    2870   } 
    2871  
    2872   if ( confirm(Xinha._lc("Would you like to clear font sizes?")) ) 
    2873   { 
    2874     D = D.replace(/size="[^"]*"/gi, ''); 
    2875     D = D.replace(/font-size:[^;}"']+;?/gi, ''); 
    2876   } 
    2877  
    2878   if ( confirm(Xinha._lc("Would you like to clear font colours?")) ) 
    2879   { 
    2880     D = D.replace(/color="[^"]*"/gi, ''); 
    2881     D = D.replace(/([^-])color:[^;}"']+;?/gi, '$1'); 
    2882   } 
    2883  
    2884   D = D.replace(/(style|class)="\s*"/gi, ''); 
    2885   D = D.replace(/<(font|span)\s*>/gi, ''); 
    2886   this.setHTML(D); 
    2887   this.updateToolbar(); 
    2888 }; 
    2889  
    2890 Xinha.prototype._splitBlock = function() 
    2891 { 
    2892   this._doc.execCommand('formatblock', false, 'div'); 
    2893 }; 
    2894  
    2895 Xinha.prototype.forceRedraw = function() 
    2896 { 
    2897   this._doc.body.style.visibility = "hidden"; 
    2898   this._doc.body.style.visibility = "visible"; 
    2899   // this._doc.body.innerHTML = this.getInnerHTML(); 
    2900 }; 
    2901  
    2902 // focuses the iframe window.  returns a reference to the editor document. 
    2903 Xinha.prototype.focusEditor = function() 
    2904 { 
    2905   switch (this._editMode) 
    2906   { 
    2907     // notice the try { ... } catch block to avoid some rare exceptions in FireFox 
    2908     // (perhaps also in other Gecko browsers). Manual focus by user is required in 
    2909     // case of an error. Somebody has an idea? 
    2910     case "wysiwyg" : 
    2911       try 
    2912       { 
    2913         // We don't want to focus the field unless at least one field has been activated. 
    2914         if ( Xinha._someEditorHasBeenActivated ) 
    2915         { 
    2916           this.activateEditor(); // Ensure *this* editor is activated 
    2917           this._iframe.contentWindow.focus(); // and focus it 
    2918         } 
    2919       } catch (ex) {} 
    2920     break; 
    2921     case "textmode": 
    2922       try 
    2923       { 
    2924         this._textArea.focus(); 
    2925       } catch (e) {} 
    2926     break; 
    2927     default: 
    2928       alert("ERROR: mode " + this._editMode + " is not defined"); 
    2929   } 
    2930   return this._doc; 
    2931 }; 
    2932  
    2933 // takes a snapshot of the current text (for undo) 
    2934 Xinha.prototype._undoTakeSnapshot = function() 
    2935 { 
    2936   ++this._undoPos; 
    2937   if ( this._undoPos >= this.config.undoSteps ) 
    2938   { 
    2939     // remove the first element 
    2940     this._undoQueue.shift(); 
    2941     --this._undoPos; 
    2942   } 
    2943   // use the fasted method (getInnerHTML); 
    2944   var take = true; 
    2945   var txt = this.getInnerHTML(); 
    2946   if ( this._undoPos > 0 ) 
    2947   { 
    2948     take = (this._undoQueue[this._undoPos - 1] != txt); 
    2949   } 
    2950   if ( take ) 
    2951   { 
    2952     this._undoQueue[this._undoPos] = txt; 
    2953   } 
    2954   else 
    2955   { 
    2956     this._undoPos--; 
    2957   } 
    2958 }; 
    2959  
    2960 Xinha.prototype.undo = function() 
    2961 { 
    2962   if ( this._undoPos > 0 ) 
    2963   { 
    2964     var txt = this._undoQueue[--this._undoPos]; 
    2965     if ( txt ) 
    2966     { 
    2967       this.setHTML(txt); 
    2968     } 
    2969     else 
    2970     { 
    2971       ++this._undoPos; 
    2972     } 
    2973   } 
    2974 }; 
    2975  
    2976 Xinha.prototype.redo = function() 
    2977 { 
    2978   if ( this._undoPos < this._undoQueue.length - 1 ) 
    2979   { 
    2980     var txt = this._undoQueue[++this._undoPos]; 
    2981     if ( txt ) 
    2982     { 
    2983       this.setHTML(txt); 
    2984     } 
    2985     else 
    2986     { 
    2987       --this._undoPos; 
    2988     } 
    2989   } 
    2990 }; 
    2991  
    2992 Xinha.prototype.disableToolbar = function(except) 
    2993 { 
    2994   if ( this._timerToolbar ) 
    2995   { 
    2996     clearTimeout(this._timerToolbar); 
    2997   } 
    2998   if ( typeof except == 'undefined' ) 
    2999   { 
    3000     except = [ ]; 
    3001   } 
    3002   else if ( typeof except != 'object' ) 
    3003   { 
    3004     except = [except]; 
    3005   } 
    3006  
    3007   for ( var i in this._toolbarObjects ) 
    3008   { 
    3009     var btn = this._toolbarObjects[i]; 
    3010     if ( except.contains(i) ) 
    3011     { 
    3012       continue; 
    3013     } 
    3014     // prevent iterating over wrong type 
    3015     if ( typeof(btn.state) != 'function' ) 
    3016     { 
    3017       continue; 
    3018     } 
    3019     btn.state("enabled", false); 
    3020   } 
    3021 }; 
    3022  
    3023 Xinha.prototype.enableToolbar = function() 
    3024 { 
    3025   this.updateToolbar(); 
    3026 }; 
    3027  
    3028 if ( !Array.prototype.contains ) 
    3029 { 
    3030   Array.prototype.contains = function(needle) 
    3031   { 
    3032     var haystack = this; 
    3033     for ( var i = 0; i < haystack.length; i++ ) 
    3034     { 
    3035       if ( needle == haystack[i] ) 
    3036       { 
    3037         return true; 
    3038       } 
    3039     } 
    3040     return false; 
    3041   }; 
    3042 } 
    3043  
    3044 if ( !Array.prototype.indexOf ) 
    3045 { 
    3046   Array.prototype.indexOf = function(needle) 
    3047   { 
    3048     var haystack = this; 
    3049     for ( var i = 0; i < haystack.length; i++ ) 
    3050     { 
    3051       if ( needle == haystack[i] ) 
    3052       { 
    3053         return i; 
    3054       } 
    3055     } 
    3056     return null; 
    3057   }; 
    3058 } 
    3059  
    3060 // FIXME : this function needs to be splitted in more functions. 
    3061 // It is actually to heavy to be understable and very scary to manipulate 
    3062 // updates enabled/disable/active state of the toolbar elements 
    3063 Xinha.prototype.updateToolbar = function(noStatus) 
    3064 { 
    3065   var doc = this._doc; 
    3066   var text = (this._editMode == "textmode"); 
    3067   var ancestors = null; 
    3068   if ( !text ) 
    3069   { 
    3070     ancestors = this.getAllAncestors(); 
    3071     if ( this.config.statusBar && !noStatus ) 
    3072     { 
    3073       this._statusBarTree.innerHTML = Xinha._lc("Path") + ": "; // clear 
    3074       for ( var i = ancestors.length; --i >= 0; ) 
    3075       { 
    3076         var el = ancestors[i]; 
    3077         if ( !el ) 
    3078         { 
    3079           // hell knows why we get here; this 
    3080           // could be a classic example of why 
    3081           // it's good to check for conditions 
    3082           // that are impossible to happen ;-) 
    3083           continue; 
    3084         } 
    3085         var a = document.createElement("a"); 
    3086         a.href = "javascript:void(0)"; 
    3087         a.el = el; 
    3088         a.editor = this; 
    3089         Xinha.addDom0Event( 
    3090           a, 
    3091           'click', 
    3092           function() { 
    3093             this.blur(); 
    3094             this.editor.selectNodeContents(this.el); 
    3095             this.editor.updateToolbar(true); 
    3096             return false; 
    3097           } 
    3098         ); 
    3099         Xinha.addDom0Event( 
    3100           a, 
    3101           'contextmenu', 
    3102           function() 
    3103           { 
    3104             // TODO: add context menu here 
    3105             this.blur(); 
    3106             var info = "Inline style:\n\n"; 
    3107             info += this.el.style.cssText.split(/;\s*/).join(";\n"); 
    3108             alert(info); 
    3109             return false; 
    3110           } 
    3111         ); 
    3112         var txt = el.tagName.toLowerCase(); 
    3113         if (typeof el.style != 'undefined') a.title = el.style.cssText; 
    3114         if ( el.id ) 
    3115         { 
    3116           txt += "#" + el.id; 
    3117         } 
    3118         if ( el.className ) 
    3119         { 
    3120           txt += "." + el.className; 
    3121         } 
    3122         a.appendChild(document.createTextNode(txt)); 
    3123         this._statusBarTree.appendChild(a); 
    3124         if ( i !== 0 ) 
    3125         { 
    3126           this._statusBarTree.appendChild(document.createTextNode(String.fromCharCode(0xbb))); 
    3127         } 
    3128       } 
    3129     } 
    3130   } 
    3131  
    3132   for ( var cmd in this._toolbarObjects ) 
    3133   { 
    3134     var btn = this._toolbarObjects[cmd]; 
    3135     var inContext = true; 
    3136     // prevent iterating over wrong type 
    3137     if ( typeof(btn.state) != 'function' ) 
    3138     { 
    3139       continue; 
    3140     } 
    3141     if ( btn.context && !text ) 
    3142     { 
    3143       inContext = false; 
    3144       var context = btn.context; 
    3145       var attrs = []; 
    3146       if ( /(.*)\[(.*?)\]/.test(context) ) 
    3147       { 
    3148         context = RegExp.$1; 
    3149         attrs = RegExp.$2.split(","); 
    3150       } 
    3151       context = context.toLowerCase(); 
    3152       var match = (context == "*"); 
    3153       for ( var k = 0; k < ancestors.length; ++k ) 
    3154       { 
    3155         if ( !ancestors[k] ) 
    3156         { 
    3157           // the impossible really happens. 
    3158           continue; 
    3159         } 
    3160         if ( match || ( ancestors[k].tagName.toLowerCase() == context ) ) 
    3161         { 
    3162           inContext = true; 
    3163           for ( var ka = 0; ka < attrs.length; ++ka ) 
    3164           { 
    3165             if (!ancestors[k].getAttribute(attrs[ka])) 
    3166             { 
    3167               inContext = false; 
    3168               break; 
    3169             } 
    3170           } 
    3171           if ( inContext ) 
    3172           { 
    3173             break; 
    3174           } 
    3175         } 
    3176       } 
    3177     } 
    3178     btn.state("enabled", (!text || btn.text) && inContext); 
    3179     if ( typeof cmd == "function" ) 
    3180     { 
    3181       continue; 
    3182     } 
    3183     // look-it-up in the custom dropdown boxes 
    3184     var dropdown = this.config.customSelects[cmd]; 
    3185     if ( ( !text || btn.text ) && ( typeof dropdown != "undefined" ) ) 
    3186     { 
    3187       dropdown.refresh(this); 
    3188       continue; 
    3189     } 
    3190     switch (cmd) 
    3191     { 
    3192       case "fontname": 
    3193       case "fontsize": 
    3194         if ( !text ) 
    3195         { 
    3196           try 
    3197           { 
    3198             var value = ("" + doc.queryCommandValue(cmd)).toLowerCase(); 
    3199             if ( !value ) 
    3200             { 
    3201               btn.element.selectedIndex = 0; 
    3202               break; 
    3203             } 
    3204  
    3205             // HACK -- retrieve the config option for this 
    3206             // combo box.  We rely on the fact that the 
    3207             // variable in config has the same name as 
    3208             // button name in the toolbar. 
    3209             var options = this.config[cmd]; 
    3210             var sIndex = 0; 
    3211             for ( var j in options ) 
    3212             { 
    3213             // FIXME: the following line is scary. 
    3214               if ( ( j.toLowerCase() == value ) || ( options[j].substr(0, value.length).toLowerCase() == value ) ) 
    3215               { 
    3216                 btn.element.selectedIndex = sIndex; 
    3217                 throw "ok"; 
    3218               } 
    3219               ++sIndex; 
    3220             } 
    3221             btn.element.selectedIndex = 0; 
    3222           } catch(ex) {} 
    3223         } 
    3224       break; 
    3225  
    3226       // It's better to search for the format block by tag name from the 
    3227       //  current selection upwards, because IE has a tendancy to return 
    3228       //  things like 'heading 1' for 'h1', which breaks things if you want 
    3229       //  to call your heading blocks 'header 1'.  Stupid MS. 
    3230       case "formatblock": 
    3231         var blocks = []; 
    3232         for ( var indexBlock in this.config.formatblock ) 
    3233         { 
    3234           // prevent iterating over wrong type 
    3235           if ( typeof this.config.formatblock[indexBlock] == 'string' ) 
    3236           { 
    3237             blocks[blocks.length] = this.config.formatblock[indexBlock]; 
    3238           } 
    3239         } 
    3240  
    3241         var deepestAncestor = this._getFirstAncestor(this._getSelection(), blocks); 
    3242         if ( deepestAncestor ) 
    3243         { 
    3244           for ( var x = 0; x < blocks.length; x++ ) 
    3245           { 
    3246             if ( blocks[x].toLowerCase() == deepestAncestor.tagName.toLowerCase() ) 
    3247             { 
    3248               btn.element.selectedIndex = x; 
    3249             } 
    3250           } 
    3251         } 
    3252         else 
    3253         { 
    3254           btn.element.selectedIndex = 0; 
    3255         } 
    3256       break; 
    3257  
    3258       case "textindicator": 
    3259         if ( !text ) 
    3260         { 
    3261           try 
    3262           { 
    3263             var style = btn.element.style; 
    3264             style.backgroundColor = Xinha._makeColor(doc.queryCommandValue(Xinha.is_ie ? "backcolor" : "hilitecolor")); 
    3265             if ( /transparent/i.test(style.backgroundColor) ) 
    3266             { 
    3267               // Mozilla 
    3268               style.backgroundColor = Xinha._makeColor(doc.queryCommandValue("backcolor")); 
    3269             } 
    3270             style.color = Xinha._makeColor(doc.queryCommandValue("forecolor")); 
    3271             style.fontFamily = doc.queryCommandValue("fontname"); 
    3272             style.fontWeight = doc.queryCommandState("bold") ? "bold" : "normal"; 
    3273             style.fontStyle = doc.queryCommandState("italic") ? "italic" : "normal"; 
    3274           } catch (ex) { 
    3275             // alert(e + "\n\n" + cmd); 
    3276           } 
    3277         } 
    3278       break; 
    3279  
    3280       case "htmlmode": 
    3281         btn.state("active", text); 
    3282       break; 
    3283  
    3284       case "lefttoright": 
    3285       case "righttoleft": 
    3286         var eltBlock = this.getParentElement(); 
    3287         while ( eltBlock && !Xinha.isBlockElement(eltBlock) ) 
    3288         { 
    3289           eltBlock = eltBlock.parentNode; 
    3290         } 
    3291         if ( eltBlock ) 
    3292         { 
    3293           btn.state("active", (eltBlock.style.direction == ((cmd == "righttoleft") ? "rtl" : "ltr"))); 
    3294         } 
    3295       break; 
    3296  
    3297       default: 
    3298         cmd = cmd.replace(/(un)?orderedlist/i, "insert$1orderedlist"); 
    3299         try 
    3300         { 
    3301           btn.state("active", (!text && doc.queryCommandState(cmd))); 
    3302         } catch (ex) {} 
    3303       break; 
    3304     } 
    3305   } 
    3306   // take undo snapshots 
    3307   if ( this._customUndo && !this._timerUndo ) 
    3308   { 
    3309     this._undoTakeSnapshot(); 
    3310     var editor = this; 
    3311     this._timerUndo = setTimeout(function() { editor._timerUndo = null; }, this.config.undoTimeout); 
    3312   } 
    3313  
    3314   // Insert a space in certain locations, this is just to make editing a little 
    3315   // easier (to "get out of" tags), it's not essential. 
    3316   // TODO: Make this work for IE? 
    3317   // TODO: Perhaps should use a plain space character, I'm not sure. 
    3318   //  OK, I've disabled this temporarily, to be honest, I can't rightly remember what the 
    3319   //  original problem was I was trying to solve with it.  I think perhaps that EnterParagraphs 
    3320   //  might solve the problem, whatever the hell it was.  I'm going senile, I'm sure. 
    3321   // @todo : since this part is disabled since a long time, does it still need to be in the source ? 
    3322   if( 0 && Xinha.is_gecko ) 
    3323   { 
    3324     var s = this._getSelection(); 
    3325     // If the last character in the last text node of the parent tag 
    3326     // and the parent tag is not a block tag 
    3327     if ( s && s.isCollapsed && s.anchorNode && 
    3328          s.anchorNode.parentNode.tagName.toLowerCase() != 'body' && 
    3329          s.anchorNode.nodeType == 3 && s.anchorOffset == s.anchorNode.length && 
    3330          !( s.anchorNode.parentNode.nextSibling && s.anchorNode.parentNode.nextSibling.nodeType == 3 ) && 
    3331          !Xinha.isBlockElement(s.anchorNode.parentNode) ) 
    3332     { 
    3333       // Insert hair-width-space after the close tag if there isn't another text node on the other side 
    3334       // It could also work with zero-width-space (\u200B) but I don't like it so much. 
    3335       // Perhaps this won't work well in various character sets and we should use plain space (20)? 
    3336       try 
    3337       { 
    3338         s.anchorNode.parentNode.parentNode.insertBefore(this._doc.createTextNode('\t'), s.anchorNode.parentNode.nextSibling); 
    3339       } 
    3340       catch(ex) {} // Disregard 
    3341     } 
    3342   } 
    3343  
    3344   // check if any plugins have registered refresh handlers 
    3345   for ( var indexPlugin in this.plugins ) 
    3346   { 
    3347     var plugin = this.plugins[indexPlugin].instance; 
    3348     if ( plugin && typeof plugin.onUpdateToolbar == "function" ) 
    3349     { 
    3350       plugin.onUpdateToolbar(); 
    3351     } 
    3352   } 
    3353  
    3354 }; 
    3355  
    3356  
    3357 // moved Xinha.prototype.insertNodeAtSelection() to browser specific file 
    3358 // moved Xinha.prototype.getParentElement() to browser specific file 
    3359  
    3360 // Returns an array with all the ancestor nodes of the selection. 
    3361 Xinha.prototype.getAllAncestors = function() 
    3362 { 
    3363   var p = this.getParentElement(); 
    3364   var a = []; 
    3365   while ( p && (p.nodeType == 1) && ( p.tagName.toLowerCase() != 'body' ) ) 
    3366   { 
    3367     a.push(p); 
    3368     p = p.parentNode; 
    3369   } 
    3370   a.push(this._doc.body); 
    3371   return a; 
    3372 }; 
    3373  
    3374 // Returns the deepest ancestor of the selection that is of the current type 
    3375 Xinha.prototype._getFirstAncestor = function(sel, types) 
    3376 { 
    3377   var prnt = this._activeElement(sel); 
    3378   if ( prnt === null ) 
    3379   { 
    3380     try 
    3381     { 
    3382       prnt = (Xinha.is_ie ? this._createRange(sel).parentElement() : this._createRange(sel).commonAncestorContainer); 
    3383     } 
    3384     catch(ex) 
    3385     { 
    3386       return null; 
    3387     } 
    3388   } 
    3389  
    3390   if ( typeof types == 'string' ) 
    3391   { 
    3392     types = [types]; 
    3393   } 
    3394  
    3395   while ( prnt ) 
    3396   { 
    3397     if ( prnt.nodeType == 1 ) 
    3398     { 
    3399       if ( types === null ) 
    3400       { 
    3401         return prnt; 
    3402       } 
    3403       if ( types.contains(prnt.tagName.toLowerCase()) ) 
    3404       { 
    3405         return prnt; 
    3406       } 
    3407       if ( prnt.tagName.toLowerCase() == 'body' ) 
    3408       { 
    3409         break; 
    3410       } 
    3411       if ( prnt.tagName.toLowerCase() == 'table' ) 
    3412       { 
    3413         break; 
    3414       } 
    3415     } 
    3416     prnt = prnt.parentNode; 
    3417   } 
    3418  
    3419   return null; 
    3420 }; 
    3421  
    3422 // moved Xinha.prototype._activeElement() to browser specific file 
    3423 // moved Xinha.prototype._selectionEmpty() to browser specific file 
    3424  
    3425 Xinha.prototype._getAncestorBlock = function(sel) 
    3426 { 
    3427   // Scan upwards to find a block level element that we can change or apply to 
    3428   var prnt = (Xinha.is_ie ? this._createRange(sel).parentElement : this._createRange(sel).commonAncestorContainer); 
    3429  
    3430   while ( prnt && ( prnt.nodeType == 1 ) ) 
    3431   { 
    3432     switch ( prnt.tagName.toLowerCase() ) 
    3433     { 
    3434       case 'div': 
    3435       case 'p': 
    3436       case 'address': 
    3437       case 'blockquote': 
    3438       case 'center': 
    3439       case 'del': 
    3440       case 'ins': 
    3441       case 'pre': 
    3442       case 'h1': 
    3443       case 'h2': 
    3444       case 'h3': 
    3445       case 'h4': 
    3446       case 'h5': 
    3447       case 'h6': 
    3448       case 'h7': 
    3449         // Block Element 
    3450         return prnt; 
    3451  
    3452       case 'body': 
    3453       case 'noframes': 
    3454       case 'dd': 
    3455       case 'li': 
    3456       case 'th': 
    3457       case 'td': 
    3458       case 'noscript' : 
    3459         // Halting element (stop searching) 
    3460         return null; 
    3461  
    3462       default: 
    3463         // Keep lookin 
    3464         break; 
    3465     } 
    3466   } 
    3467  
    3468   return null; 
    3469 }; 
    3470  
    3471 Xinha.prototype._createImplicitBlock = function(type) 
    3472 { 
    3473   // expand it until we reach a block element in either direction 
    3474   // then wrap the selection in a block and return 
    3475   var sel = this._getSelection(); 
    3476   if ( Xinha.is_ie ) 
    3477   { 
    3478     sel.empty(); 
    3479   } 
    3480   else 
    3481   { 
    3482     sel.collapseToStart(); 
    3483   } 
    3484  
    3485   var rng = this._createRange(sel); 
    3486  
    3487   // Expand UP 
    3488  
    3489   // Expand DN 
    3490 }; 
    3491  
    3492  
    3493 // moved Xinha.prototype.selectNodeContents() to browser specific file 
    3494 // moved Xinha.prototype.insertHTML() to browser specific file 
    3495  
    3496 /** 
    3497  *  Call this function to surround the existing HTML code in the selection with 
    3498  *  your tags.  FIXME: buggy!  This function will be deprecated "soon". 
    3499  * @todo: when will it be deprecated ? Can it be removed already ? 
    3500  */ 
    3501 Xinha.prototype.surroundHTML = function(startTag, endTag) 
    3502 { 
    3503   var html = this.getSelectedHTML(); 
    3504   // the following also deletes the selection 
    3505   this.insertHTML(startTag + html + endTag); 
    3506 }; 
    3507  
    3508 // moved  Xinha.prototype.getSelectedHTML() to browser specific file 
    3509  
    3510 /// Return true if we have some selection 
    3511 Xinha.prototype.hasSelectedText = function() 
    3512 { 
    3513   // FIXME: come _on_ mishoo, you can do better than this ;-) 
    3514   return this.getSelectedHTML() !== ''; 
    3515 }; 
    3516  
    3517 // moved Xinha.prototype._createLink() to popups/link.js 
    3518 // moved Xinha.prototype._insertImage() to popups/insert_image.js 
    3519  
    3520 // Called when the user clicks the Insert Table button 
    3521 Xinha.prototype._insertTable = function() 
    3522 { 
    3523   var sel = this._getSelection(); 
    3524   var range = this._createRange(sel); 
    3525   var editor = this;    // for nested functions 
    3526   this._popupDialog( 
    3527     editor.config.URIs.insert_table, 
    3528     function(param) 
    3529     { 
    3530       // user must have pressed Cancel 
    3531       if ( !param ) 
    3532       { 
    3533         return false; 
    3534       } 
    3535       var doc = editor._doc; 
    3536       // create the table element 
    3537       var table = doc.createElement("table"); 
    3538       // assign the given arguments 
    3539  
    3540       for ( var field in param ) 
    3541       { 
    3542         var value = param[field]; 
    3543         if ( !value ) 
    3544         { 
    3545           continue; 
    3546         } 
    3547         switch (field) 
    3548         { 
    3549           case "f_width": 
    3550             table.style.width = value + param.f_unit; 
    3551           break; 
    3552           case "f_align": 
    3553             table.align = value; 
    3554           break; 
    3555           case "f_border": 
    3556             table.border = parseInt(value, 10); 
    3557           break; 
    3558           case "f_spacing": 
    3559             table.cellSpacing = parseInt(value, 10); 
    3560           break; 
    3561           case "f_padding": 
    3562             table.cellPadding = parseInt(value, 10); 
    3563           break; 
    3564         } 
    3565       } 
    3566       var cellwidth = 0; 
    3567       if ( param.f_fixed ) 
    3568       { 
    3569         cellwidth = Math.floor(100 / parseInt(param.f_cols, 10)); 
    3570       } 
    3571       var tbody = doc.createElement("tbody"); 
    3572       table.appendChild(tbody); 
    3573       for ( var i = 0; i < param.f_rows; ++i ) 
    3574       { 
    3575         var tr = doc.createElement("tr"); 
    3576         tbody.appendChild(tr); 
    3577         for ( var j = 0; j < param.f_cols; ++j ) 
    3578         { 
    3579           var td = doc.createElement("td"); 
    3580           // @todo : check if this line doesnt stop us to use pixel width in cells 
    3581           if (cellwidth) 
    3582           { 
    3583             td.style.width = cellwidth + "%"; 
    3584           } 
    3585           tr.appendChild(td); 
    3586           // Browsers like to see something inside the cell (&nbsp;). 
    3587           td.appendChild(doc.createTextNode('\u00a0')); 
    3588         } 
    3589       } 
    3590       if ( Xinha.is_ie ) 
    3591       { 
    3592         range.pasteHTML(table.outerHTML); 
    3593       } 
    3594       else 
    3595       { 
    3596         // insert the table 
    3597         editor.insertNodeAtSelection(table); 
    3598       } 
    3599       return true; 
    3600     }, 
    3601     null 
    3602   ); 
    3603 }; 
    3604  
    3605 /*************************************************** 
    3606  *  Category: EVENT HANDLERS 
    3607  ***************************************************/ 
    3608  
    3609 // el is reference to the SELECT object 
    3610 // txt is the name of the select field, as in config.toolbar 
    3611 Xinha.prototype._comboSelected = function(el, txt) 
    3612 { 
    3613   this.focusEditor(); 
    3614   var value = el.options[el.selectedIndex].value; 
    3615   switch (txt) 
    3616   { 
    3617     case "fontname": 
    3618     case "fontsize": 
    3619       this.execCommand(txt, false, value); 
    3620     break; 
    3621     case "formatblock": 
    3622       // Mozilla inserts an empty tag (<>) if no parameter is passed   
    3623       if ( !value ) 
    3624       { 
    3625         this.updateToolbar(); 
    3626         break; 
    3627       } 
    3628       if( !Xinha.is_gecko || value !== 'blockquote' ) 
    3629       { 
    3630         value = "<" + value + ">"; 
    3631       } 
    3632       this.execCommand(txt, false, value); 
    3633     break; 
    3634     default: 
    3635       // try to look it up in the registered dropdowns 
    3636       var dropdown = this.config.customSelects[txt]; 
    3637       if ( typeof dropdown != "undefined" ) 
    3638       { 
    3639         dropdown.action(this); 
    3640       } 
    3641       else 
    3642       { 
    3643         alert("FIXME: combo box " + txt + " not implemented"); 
    3644       } 
    3645     break; 
    3646   } 
    3647 }; 
    3648  
    3649 /** 
    3650  * Open a popup to select the hilitecolor or forecolor 
    3651  * 
    3652  * @param {String} cmdID The commande ID (hilitecolor or forecolor) 
    3653  * @private 
    3654  */ 
    3655 Xinha.prototype._colorSelector = function(cmdID) 
    3656 { 
    3657   var editor = this;    // for nested functions 
    3658    
    3659   if ( typeof colorPicker == 'undefined' ) 
    3660   { 
    3661     Xinha._loadback(_editor_url + 'popups/color_picker.js', function () {editor._colorSelector(cmdID)}); 
    3662     return false; 
    3663   } 
    3664    
    3665   var btn = editor._toolbarObjects[cmdID].element; 
    3666   var initcolor; 
    3667   if ( cmdID == 'hilitecolor' ) 
    3668   { 
    3669     if ( Xinha.is_ie ) 
    3670     { 
    3671       cmdID = 'backcolor'; 
    3672       initcolor = Xinha._colorToRgb(editor._doc.queryCommandValue("backcolor")); 
    3673     } 
    3674     else 
    3675     { 
    3676       initcolor = Xinha._colorToRgb(editor._doc.queryCommandValue("hilitecolor")); 
    3677     } 
    3678     // @todo : useCSS is deprecated, see ticket #619 
    3679     //[wymsy]: mozilla bug #279330 has been fixed, I don't think we need this any more 
    3680   /*  if ( Xinha.is_gecko ) 
    3681     { 
    3682       try 
    3683       { 
    3684      //   editor._doc.execCommand('useCSS', false, false); //switch on useCSS (mozilla bug #279330) 
    3685       } catch (ex) {} 
    3686     }*/ 
    3687   } 
    3688   else 
    3689   { 
    3690         initcolor = Xinha._colorToRgb(editor._doc.queryCommandValue("forecolor")); 
    3691   } 
    3692   var cback = function(color) { editor._doc.execCommand(cmdID, false, color); }; 
    3693   if ( Xinha.is_ie ) 
    3694   { 
    3695     var range = editor._createRange(editor._getSelection()); 
    3696     cback = function(color) 
    3697     { 
    3698       range.select(); 
    3699       editor._doc.execCommand(cmdID, false, color); 
    3700     }; 
    3701   } 
    3702   var picker = new colorPicker( 
    3703   { 
    3704         cellsize:editor.config.colorPickerCellSize, 
    3705         callback:cback, 
    3706         granularity:editor.config.colorPickerGranularity, 
    3707         websafe:editor.config.colorPickerWebSafe, 
    3708         savecolors:editor.config.colorPickerSaveColors 
    3709   }); 
    3710   picker.open(editor.config.colorPickerPosition, btn, initcolor); 
    3711 }; 
    3712  
    3713 // the execCommand function (intercepts some commands and replaces them with 
    3714 // our own implementation) 
    3715 Xinha.prototype.execCommand = function(cmdID, UI, param) 
    3716 { 
    3717   var editor = this;    // for nested functions 
    3718   this.focusEditor(); 
    3719   cmdID = cmdID.toLowerCase(); 
    3720   // @todo : useCSS is deprecated, see ticket #619 
    3721   if ( Xinha.is_gecko ) 
    3722   { 
    3723     try 
    3724     { 
    3725       this._doc.execCommand('useCSS', false, true); //switch useCSS off (true=off) 
    3726     } catch (ex) {} 
    3727   } 
    3728   switch (cmdID) 
    3729   { 
    3730     case "htmlmode": 
    3731       this.setMode(); 
    3732     break; 
    3733  
    3734     case "hilitecolor": 
    3735     case "forecolor": 
    3736       this._colorSelector(cmdID); 
    3737     break; 
    3738  
    3739     case "createlink": 
    3740       this._createLink(); 
    3741     break; 
    3742  
    3743     case "undo": 
    3744     case "redo": 
    3745       if (this._customUndo) 
    3746       { 
    3747         this[cmdID](); 
    3748       } 
    3749       else 
    3750       { 
    3751         this._doc.execCommand(cmdID, UI, param); 
    3752       } 
    3753     break; 
    3754  
    3755     case "inserttable": 
    3756       this._insertTable(); 
    3757     break; 
    3758  
    3759     case "insertimage": 
    3760       this._insertImage(); 
    3761     break; 
    3762  
    3763     case "about": 
    3764       this._popupDialog(editor.config.URIs.about, null, this); 
    3765     break; 
    3766  
    3767     case "showhelp": 
    3768       this._popupDialog(editor.config.URIs.help, null, this); 
    3769     break; 
    3770  
    3771     case "killword": 
    3772       this._wordClean(); 
    3773     break; 
    3774  
    3775     case "cut": 
    3776     case "copy": 
    3777     case "paste": 
    3778     try 
    3779     { 
    3780       this._doc.execCommand(cmdID, UI, param); 
    3781       if ( this.config.killWordOnPaste ) 
    3782       { 
    3783         this._wordClean(); 
    3784       } 
    3785     } 
    3786     catch (ex) 
    3787     { 
    3788       if ( Xinha.is_gecko ) 
    3789       { 
    3790         alert(Xinha._lc("The Paste button does not work in Mozilla based web browsers (technical security reasons). Press CTRL-V on your keyboard to paste directly.")); 
    3791       } 
    3792     } 
    3793     break; 
    3794     case "lefttoright": 
    3795     case "righttoleft": 
    3796       if (this.config.changeJustifyWithDirection)  
    3797       { 
    3798         this._doc.execCommand((cmdID == "righttoleft") ? "justifyright" : "justifyleft", UI, param); 
    3799       } 
    3800       var dir = (cmdID == "righttoleft") ? "rtl" : "ltr"; 
    3801       var el = this.getParentElement(); 
    3802       while ( el && !Xinha.isBlockElement(el) ) 
    3803       { 
    3804         el = el.parentNode; 
    3805       } 
    3806       if ( el ) 
    3807       { 
    3808         if ( el.style.direction == dir ) 
    3809         { 
    3810           el.style.direction = ""; 
    3811         } 
    3812         else 
    3813         { 
    3814           el.style.direction = dir; 
    3815         } 
    3816       } 
    3817     break; 
    3818     default: 
    3819       try 
    3820       { 
    3821         this._doc.execCommand(cmdID, UI, param); 
    3822       } 
    3823       catch(ex) 
    3824       { 
    3825         if ( this.config.debug ) 
    3826         { 
    3827           alert(e + "\n\nby execCommand(" + cmdID + ");"); 
    3828         } 
    3829       } 
    3830     break; 
    3831   } 
    3832  
    3833   this.updateToolbar(); 
    3834   return false; 
    3835 }; 
    3836  
    3837 /** A generic event handler for things that happen in the IFRAME's document. 
    3838  * @todo: this function is *TOO* generic, it needs to be splitted in more specific handlers 
    3839  * This function also handles key bindings. */ 
    3840 Xinha.prototype._editorEvent = function(ev) 
    3841 { 
    3842   var editor = this; 
    3843   var keyEvent = (Xinha.is_ie && ev.type == "keydown") || (!Xinha.is_ie && ev.type == "keypress"); 
    3844  
    3845   //call events of textarea 
    3846   if ( typeof editor._textArea['on'+ev.type] == "function" ) 
    3847   { 
    3848     editor._textArea['on'+ev.type](); 
    3849   } 
    3850  
    3851   if ( Xinha.is_gecko && keyEvent && ev.ctrlKey &&  this._unLink && this._unlinkOnUndo ) 
    3852   { 
    3853     if ( String.fromCharCode(ev.charCode).toLowerCase() == 'z' ) 
    3854     { 
    3855       Xinha._stopEvent(ev); 
    3856       this._unLink(); 
    3857       editor.updateToolbar(); 
    3858       return; 
    3859     } 
    3860   } 
    3861  
    3862   if ( keyEvent ) 
    3863   { 
    3864     for ( var i in editor.plugins ) 
    3865     { 
    3866       var plugin = editor.plugins[i].instance; 
    3867       if ( plugin && typeof plugin.onKeyPress == "function" ) 
    3868       { 
    3869         if ( plugin.onKeyPress(ev) ) 
    3870         { 
    3871           return false; 
    3872         } 
    3873       } 
    3874     } 
    3875   } 
    3876  
    3877   if ( keyEvent && ev.ctrlKey && !ev.altKey ) 
    3878   { 
    3879         this._shortCuts(ev); 
    3880   } 
    3881   else if ( keyEvent && Xinha.is_gecko ) 
    3882   { 
    3883     this.mozKey( ev, keyEvent ); 
    3884   } 
    3885  
    3886   // update the toolbar state after some time 
    3887   if ( editor._timerToolbar ) 
    3888   { 
    3889     clearTimeout(editor._timerToolbar); 
    3890   } 
    3891   editor._timerToolbar = setTimeout( 
    3892     function() 
    3893     { 
    3894       editor.updateToolbar(); 
    3895       editor._timerToolbar = null; 
    3896     }, 
    3897     250); 
    3898 }; 
    3899  
    3900 // handles ctrl + key shortcuts  
    3901 Xinha.prototype._shortCuts = function (ev) 
    3902 { 
    3903   var editor = this; 
    3904   var sel = null; 
    3905   var range = null; 
    3906   var key = String.fromCharCode(Xinha.is_ie ? ev.keyCode : ev.charCode).toLowerCase(); 
    3907   var cmd = null; 
    3908   var value = null; 
    3909   switch (key) 
    3910   { 
    3911     case 'a': 
    3912     if ( !Xinha.is_ie ) 
    3913     { 
    3914       // KEY select all 
    3915       sel = this._getSelection(); 
    3916       sel.removeAllRanges(); 
    3917       range = this._createRange(); 
    3918       range.selectNodeContents(this._doc.body); 
    3919       sel.addRange(range); 
    3920       Xinha._stopEvent(ev); 
    3921     } 
    3922     break; 
    3923  
    3924     // simple key commands follow 
    3925  
    3926     case 'b': cmd = "bold"; break; 
    3927     case 'i': cmd = "italic"; break; 
    3928     case 'u': cmd = "underline"; break; 
    3929     case 's': cmd = "strikethrough"; break; 
    3930     case 'l': cmd = "justifyleft"; break; 
    3931     case 'e': cmd = "justifycenter"; break; 
    3932     case 'r': cmd = "justifyright"; break; 
    3933     case 'j': cmd = "justifyfull"; break; 
    3934     case 'z': cmd = "undo"; break; 
    3935     case 'y': cmd = "redo"; break; 
    3936     case 'v': 
    3937     if ( Xinha.is_ie || editor.config.htmlareaPaste ) 
    3938     { 
    3939       cmd = "paste"; 
    3940     } 
    3941     break; 
    3942     case 'n': 
    3943     cmd = "formatblock"; 
    3944     value = Xinha.is_ie ? "<p>" : "p"; 
    3945     break; 
    3946  
    3947     case '0': cmd = "killword"; break; 
    3948  
    3949     // headings 
    3950     case '1': 
    3951     case '2': 
    3952     case '3': 
    3953     case '4': 
    3954     case '5': 
    3955     case '6': 
    3956     cmd = "formatblock"; 
    3957     value = "h" + key; 
    3958     if ( Xinha.is_ie ) 
    3959     { 
    3960       value = "<" + value + ">"; 
    3961     } 
    3962     break; 
    3963   } 
    3964   if ( cmd ) 
    3965   { 
    3966     // execute simple command 
    3967     this.execCommand(cmd, false, value); 
    3968     Xinha._stopEvent(ev); 
    3969   } 
    3970 }; 
    3971  
    3972 Xinha.prototype.convertNode = function(el, newTagName) 
    3973 { 
    3974   var newel = this._doc.createElement(newTagName); 
    3975   while ( el.firstChild ) 
    3976   { 
    3977     newel.appendChild(el.firstChild); 
    3978   } 
    3979   return newel; 
    3980 }; 
    3981  
    3982 // moved Xinha.prototype.checkBackspace() to browser specific file 
    3983  
    3984 /** The idea here is 
    3985  * 1. See if we are in a block element 
    3986  * 2. If we are not, then wrap the current "block" of text into a paragraph 
    3987  * 3. Now that we have a block element, select all the text between the insertion point 
    3988  *    and just AFTER the end of the block 
    3989  *    eg <p>The quick |brown fox jumped over the lazy dog.</p>| 
    3990  *                     --------------------------------------- 
    3991  * 4. Extract that from the document, making 
    3992  *       <p>The quick </p> 
    3993  *    and a document fragment with 
    3994  *       <p>brown fox jumped over the lazy dog.</p> 
    3995  * 5. Reinsert it just after the block element 
    3996  *       <p>The quick </p><p>brown fox jumped over the lazy dog.</p> 
    3997  * 
    3998  * Along the way, allow inserting blank paragraphs, which will look like <p><br/></p> 
    3999  */ 
    4000  
    4001 Xinha.prototype.dom_checkInsertP = function() 
    4002 { 
    4003   var p, body; 
    4004   // Get the insertion point, we'll scrub any highlighted text the user wants rid of while we are there. 
    4005   var sel = this._getSelection(); 
    4006   var range = this._createRange(sel); 
    4007   if ( !range.collapsed ) 
    4008   { 
    4009     range.deleteContents(); 
    4010   } 
    4011   this.deactivateEditor(); 
    4012   //sel.removeAllRanges(); 
    4013   //sel.addRange(range); 
    4014  
    4015   var SC = range.startContainer; 
    4016   var SO = range.startOffset; 
    4017   var EC = range.endContainer; 
    4018   var EO = range.endOffset; 
    4019  
    4020   // If the insertion point is character 0 of the 
    4021   // document, then insert a space character that we will wrap into a paragraph 
    4022   // in a bit. 
    4023   if ( SC == EC && SC == body && !SO && !EO ) 
    4024   { 
    4025     p = this._doc.createTextNode(" "); 
    4026     body.insertBefore(p, body.firstChild); 
    4027     range.selectNodeContents(p); 
    4028     SC = range.startContainer; 
    4029     SO = range.startOffset; 
    4030     EC = range.endContainer; 
    4031     EO = range.endOffset; 
    4032   } 
    4033  
    4034   // See if we are in a block element, if so, great. 
    4035   p = this.getAllAncestors(); 
    4036  
    4037   var block = null; 
    4038   body = this._doc.body; 
    4039   for ( var i = 0; i < p.length; ++i ) 
    4040   { 
    4041     if ( Xinha.isParaContainer(p[i]) ) 
    4042     { 
    4043       break; 
    4044     } 
    4045     else if ( Xinha.isBlockElement(p[i]) && ! ( /body|html/i.test(p[i].tagName) ) ) 
    4046     { 
    4047       block = p[i]; 
    4048       break; 
    4049     } 
    4050   } 
    4051  
    4052   // If not in a block element, we'll have to turn some stuff into a paragraph 
    4053   if ( !block ) 
    4054   { 
    4055     // We want to wrap as much stuff as possible into the paragraph in both directions 
    4056     // from the insertion point.  We start with the start container and walk back up to the 
    4057     // node just before any of the paragraph containers. 
    4058     var wrap = range.startContainer; 
    4059     while ( wrap.parentNode && !Xinha.isParaContainer(wrap.parentNode) ) 
    4060     { 
    4061       wrap = wrap.parentNode; 
    4062     } 
    4063     var start = wrap; 
    4064     var end   = wrap; 
    4065  
    4066     // Now we walk up the sibling list until we hit the top of the document 
    4067     // or an element that we shouldn't put in a p (eg other p, div, ul, ol, table) 
    4068     while ( start.previousSibling ) 
    4069     { 
    4070       if ( start.previousSibling.tagName ) 
    4071       { 
    4072         if ( !Xinha.isBlockElement(start.previousSibling) ) 
    4073         { 
    4074           start = start.previousSibling; 
    4075         } 
    4076         else 
    4077         { 
    4078           break; 
    4079         } 
    4080       } 
    4081       else 
    4082       { 
    4083         start = start.previousSibling; 
    4084       } 
    4085     } 
    4086  
    4087     // Same down the list 
    4088     while ( end.nextSibling ) 
    4089     { 
    4090       if ( end.nextSibling.tagName ) 
    4091       { 
    4092         if ( !Xinha.isBlockElement(end.nextSibling) ) 
    4093         { 
    4094           end = end.nextSibling; 
    4095         } 
    4096         else 
    4097         { 
    4098           break; 
    4099         } 
    4100       } 
    4101       else 
    4102       { 
    4103         end = end.nextSibling; 
    4104       } 
    4105     } 
    4106  
    4107     // Select the entire block 
    4108     range.setStartBefore(start); 
    4109     range.setEndAfter(end); 
    4110  
    4111     // Make it a paragraph 
    4112     range.surroundContents(this._doc.createElement('p')); 
    4113  
    4114     // Which becomes the block element 
    4115     block = range.startContainer.firstChild; 
    4116  
    4117     // And finally reset the insertion point to where it was originally 
    4118     range.setStart(SC, SO); 
    4119   } 
    4120  
    4121   // The start point is the insertion point, so just move the end point to immediatly 
    4122   // after the block 
    4123   range.setEndAfter(block); 
    4124  
    4125   // Extract the range, to split the block 
    4126   // If we just did range.extractContents() then Mozilla does wierd stuff 
    4127   // with selections, but if we clone, then remove the original range and extract 
    4128   // the clone, it's quite happy. 
    4129   var r2 = range.cloneRange(); 
    4130   sel.removeRange(range); 
    4131   var df = r2.extractContents(); 
    4132  
    4133   if ( df.childNodes.length === 0 ) 
    4134   { 
    4135     df.appendChild(this._doc.createElement('p')); 
    4136     df.firstChild.appendChild(this._doc.createElement('br')); 
    4137   } 
    4138  
    4139   if ( df.childNodes.length > 1 ) 
    4140   { 
    4141     var nb = this._doc.createElement('p'); 
    4142     while ( df.firstChild ) 
    4143     { 
    4144       var s = df.firstChild; 
    4145       df.removeChild(s); 
    4146       nb.appendChild(s); 
    4147     } 
    4148     df.appendChild(nb); 
    4149   } 
    4150  
    4151   // If the original block is empty, put a &nsbp; in it. 
    4152   // @fixme: why using a regex instead of : if (block.innerHTML.trim() == '') ? 
    4153   if ( ! ( /\S/.test(block.innerHTML) ) ) 
    4154   { 
    4155     block.innerHTML = "&nbsp;"; 
    4156   } 
    4157  
    4158   p = df.firstChild; 
    4159   // @fixme: why using a regex instead of : if (p.innerHTML.trim() == '') ? 
    4160   if ( ! ( /\S/.test(p.innerHTML) ) ) 
    4161   { 
    4162     p.innerHTML = "<br />"; 
    4163   } 
    4164  
    4165   // If the new block is empty and it's a heading, make it a paragraph 
    4166   // note, the new block is empty when you are hitting enter at the end of the existing block 
    4167   if ( ( /^\s*<br\s*\/?>\s*$/.test(p.innerHTML) ) && ( /^h[1-6]$/i.test(p.tagName) ) ) 
    4168   { 
    4169     df.appendChild(this.convertNode(p, "p")); 
    4170     df.removeChild(p); 
    4171   } 
    4172  
    4173   var newblock = block.parentNode.insertBefore(df.firstChild, block.nextSibling); 
    4174  
    4175   // Select the range (to set the insertion) 
    4176   // collapse to the start of the new block 
    4177   //  (remember the block might be <p><br/></p>, so if we collapsed to the end the <br/> would be noticable) 
    4178  
    4179   //range.selectNode(newblock.firstChild); 
    4180   //range.collapse(true); 
    4181  
    4182   this.activateEditor(); 
    4183  
    4184   sel = this._getSelection(); 
    4185   sel.removeAllRanges(); 
    4186   sel.collapse(newblock,0); 
    4187  
    4188   // scroll into view 
    4189   this.scrollToElement(newblock); 
    4190  
    4191   //this.forceRedraw(); 
    4192  
    4193 }; 
    4194  
    4195 Xinha.prototype.scrollToElement = function(e) 
    4196 { 
    4197   if ( Xinha.is_gecko ) 
    4198   { 
    4199     var top  = 0; 
    4200     var left = 0; 
    4201     while ( e ) 
    4202     { 
    4203       top  += e.offsetTop; 
    4204       left += e.offsetLeft; 
    4205       if ( e.offsetParent && e.offsetParent.tagName.toLowerCase() != 'body' ) 
    4206       { 
    4207         e = e.offsetParent; 
    4208       } 
    4209       else 
    4210       { 
    4211         e = null; 
    4212       } 
    4213     } 
    4214     this._iframe.contentWindow.scrollTo(left, top); 
    4215   } 
    4216 }; 
    4217  
    4218 // retrieve the HTML 
    4219 Xinha.prototype.getHTML = function() 
    4220 { 
    4221   var html = ''; 
    4222   switch ( this._editMode ) 
    4223   { 
    4224     case "wysiwyg": 
    4225       if ( !this.config.fullPage ) 
    4226       { 
    4227         html = Xinha.getHTML(this._doc.body, false, this); 
    4228       } 
    4229       else 
    4230       { 
    4231         html = this.doctype + "\n" + Xinha.getHTML(this._doc.documentElement, true, this); 
    4232       } 
    4233     break; 
    4234     case "textmode": 
    4235       html = this._textArea.value; 
    4236     break; 
    4237     default: 
    4238       alert("Mode <" + this._editMode + "> not defined!"); 
    4239       return false; 
    4240   } 
    4241   return html; 
    4242 }; 
    4243  
    4244 Xinha.prototype.outwardHtml = function(html) 
    4245 { 
    4246   html = html.replace(/<(\/?)b(\s|>|\/)/ig, "<$1strong$2"); 
    4247   html = html.replace(/<(\/?)i(\s|>|\/)/ig, "<$1em$2"); 
    4248   html = html.replace(/<(\/?)strike(\s|>|\/)/ig, "<$1del$2"); 
    4249    
    4250   // replace window.open to that any clicks won't open a popup in designMode 
    4251   html = html.replace("onclick=\"try{if(document.designMode &amp;&amp; document.designMode == 'on') return false;}catch(e){} window.open(", "onclick=\"window.open("); 
    4252  
    4253   // Figure out what our server name is, and how it's referenced 
    4254   var serverBase = location.href.replace(/(https?:\/\/[^\/]*)\/.*/, '$1') + '/'; 
    4255  
    4256   // IE puts this in can't figure out why 
    4257   html = html.replace(/https?:\/\/null\//g, serverBase); 
    4258  
    4259   // Make semi-absolute links to be truely absolute 
    4260   //  we do this just to standardize so that special replacements knows what 
    4261   //  to expect 
    4262   html = html.replace(/((href|src|background)=[\'\"])\/+/ig, '$1' + serverBase); 
    4263  
    4264   html = this.outwardSpecialReplacements(html); 
    4265  
    4266   html = this.fixRelativeLinks(html); 
    4267  
    4268   if ( this.config.sevenBitClean ) 
    4269   { 
    4270     html = html.replace(/[^ -~\r\n\t]/g, function(c) { return '&#'+c.charCodeAt(0)+';'; }); 
    4271   } 
    4272  
    4273   // ticket:56, the "greesemonkey" plugin for Firefox adds this junk, 
    4274   // so we strip it out.  Original submitter gave a plugin, but that's 
    4275   // a bit much just for this IMHO - james 
    4276   if ( Xinha.is_gecko ) 
    4277   { 
    4278     html = html.replace(/<script[\s]*src[\s]*=[\s]*['"]chrome:\/\/.*?["']>[\s]*<\/script>/ig, ''); 
    4279   } 
    4280   //prevent execution of JavaScript (Ticket #685) 
    4281   html = html.replace(/(<script[^>]*)(freezescript)/gi,"$1javascript"); 
    4282  
    4283   return html; 
    4284 }; 
    4285  
    4286 Xinha.prototype.inwardHtml = function(html) 
    4287 { 
    4288   // Midas uses b and i instead of strong and em, um, hello, 
    4289   // mozilla, this is the 21st century calling! 
    4290   if ( Xinha.is_gecko ) 
    4291   { 
    4292     html = html.replace(/<(\/?)strong(\s|>|\/)/ig, "<$1b$2"); 
    4293     html = html.replace(/<(\/?)em(\s|>|\/)/ig, "<$1i$2");     
    4294   } 
    4295    
    4296   // Both IE and Gecko use strike instead of del (#523) 
    4297   html = html.replace(/<(\/?)del(\s|>|\/)/ig, "<$1strike$2"); 
    4298  
    4299   // replace window.open to that any clicks won't open a popup in designMode 
    4300   html = html.replace("onclick=\"window.open(", "onclick=\"try{if(document.designMode &amp;&amp; document.designMode == 'on') return false;}catch(e){} window.open("); 
    4301  
    4302   html = this.inwardSpecialReplacements(html); 
    4303  
    4304   html = html.replace(/(<script[^>]*)(javascript)/gi,"$1freezescript"); 
    4305  
    4306   // For IE's sake, make any URLs that are semi-absolute (="/....") to be 
    4307   // truely absolute 
    4308   var nullRE = new RegExp('((href|src|background)=[\'"])/+', 'gi'); 
    4309   html = html.replace(nullRE, '$1' + location.href.replace(/(https?:\/\/[^\/]*)\/.*/, '$1') + '/'); 
    4310  
    4311   html = this.fixRelativeLinks(html); 
    4312   return html; 
    4313 }; 
    4314  
    4315 Xinha.prototype.outwardSpecialReplacements = function(html) 
    4316 { 
    4317   for ( var i in this.config.specialReplacements ) 
    4318   { 
    4319     var from = this.config.specialReplacements[i]; 
    4320     var to   = i; // why are declaring a new variable here ? Seems to be better to just do : for (var to in config) 
    4321     // prevent iterating over wrong type 
    4322     if ( typeof from.replace != 'function' || typeof to.replace != 'function' ) 
    4323     { 
    4324       continue; 
    4325     }  
    4326     // alert('out : ' + from + '=>' + to); 
    4327     var reg = new RegExp(from.replace(Xinha.RE_Specials, '\\$1'), 'g'); 
    4328     html = html.replace(reg, to.replace(/\$/g, '$$$$')); 
    4329     //html = html.replace(from, to); 
    4330   } 
    4331   return html; 
    4332 }; 
    4333  
    4334 Xinha.prototype.inwardSpecialReplacements = function(html) 
    4335 { 
    4336   // alert("inward"); 
    4337   for ( var i in this.config.specialReplacements ) 
    4338   { 
    4339     var from = i; // why are declaring a new variable here ? Seems to be better to just do : for (var from in config) 
    4340     var to   = this.config.specialReplacements[i]; 
    4341     // prevent iterating over wrong type 
    4342     if ( typeof from.replace != 'function' || typeof to.replace != 'function' ) 
    4343     { 
    4344       continue; 
    4345     } 
    4346     // alert('in : ' + from + '=>' + to); 
    4347     // 
    4348     // html = html.replace(reg, to); 
    4349     // html = html.replace(from, to); 
    4350     var reg = new RegExp(from.replace(Xinha.RE_Specials, '\\$1'), 'g'); 
    4351     html = html.replace(reg, to.replace(/\$/g, '$$$$')); // IE uses doubled dollar signs to escape backrefs, also beware that IE also implements $& $_ and $' like perl. 
    4352   } 
    4353   return html; 
    4354 }; 
    4355  
    4356  
    4357 Xinha.prototype.fixRelativeLinks = function(html) 
    4358 { 
    4359   if ( typeof this.config.expandRelativeUrl != 'undefined' && this.config.expandRelativeUrl )  
    4360   var src = html.match(/(src|href)="([^"]*)"/gi); 
    4361   var b = document.location.href; 
    4362   if ( src ) 
    4363   { 
    4364     var url,url_m,relPath,base_m,absPath 
    4365     for ( var i=0;i<src.length;++i ) 
    4366     { 
    4367       url = src[i].match(/(src|href)="([^"]*)"/i); 
    4368       url_m = url[2].match( /\.\.\//g ); 
    4369       if ( url_m ) 
    4370       { 
    4371         relPath = new RegExp( "(.*?)(([^\/]*\/){"+ url_m.length+"})[^\/]*$" ); 
    4372         base_m = b.match( relPath ); 
    4373         absPath = url[2].replace(/(\.\.\/)*/,base_m[1]); 
    4374         html = html.replace( new RegExp(url[2].replace( Xinha.RE_Specials, '\\$1' ) ),absPath ); 
    4375       } 
    4376     } 
    4377   } 
    4378    
    4379   if ( typeof this.config.stripSelfNamedAnchors != 'undefined' && this.config.stripSelfNamedAnchors ) 
    4380   { 
    4381     var stripRe = new RegExp(document.location.href.replace(/&/g,'&amp;').replace(Xinha.RE_Specials, '\\$1') + '(#[^\'" ]*)', 'g'); 
    4382     html = html.replace(stripRe, '$1'); 
    4383   } 
    4384  
    4385   if ( typeof this.config.stripBaseHref != 'undefined' && this.config.stripBaseHref ) 
    4386   { 
    4387     var baseRe = null; 
    4388     if ( typeof this.config.baseHref != 'undefined' && this.config.baseHref !== null ) 
    4389     { 
    4390       baseRe = new RegExp( "((href|src|background)=\")(" + this.config.baseHref.replace( Xinha.RE_Specials, '\\$1' ) + ")", 'g' ); 
    4391     } 
    4392     else 
    4393     { 
    4394       baseRe = new RegExp( "((href|src|background)=\")(" + document.location.href.replace( /([^\/]*\/?)$/, '' ).replace( Xinha.RE_Specials, '\\$1' ) + ")", 'g' ); 
    4395     } 
    4396  
    4397     html = html.replace(baseRe, '$1'); 
    4398   } 
    4399  
    4400 //  if ( Xinha.is_ie ) 
    4401 //  { 
    4402     // This is now done in inward & outward 
    4403     // Don't know why but IE is doing this (putting http://null/ on links?! 
    4404     // alert(html); 
    4405     // var nullRE = new RegExp('https?:\/\/null\/', 'g'); 
    4406     // html = html.replace(nullRE, location.href.replace(/(https?:\/\/[^\/]*\/).*/, '$1')); 
    4407     // alert(html); 
    4408 //  } 
    4409  
    4410   return html; 
    4411 }; 
    4412  
    4413 // retrieve the HTML (fastest version, but uses innerHTML) 
    4414 Xinha.prototype.getInnerHTML = function() 
    4415 { 
    4416   if ( !this._doc.body ) 
    4417   { 
    4418     return ''; 
    4419   } 
    4420   var html = ""; 
    4421   switch ( this._editMode ) 
    4422   { 
    4423     case "wysiwyg": 
    4424       if ( !this.config.fullPage ) 
    4425       { 
    4426         // return this._doc.body.innerHTML; 
    4427         html = this._doc.body.innerHTML; 
    4428       } 
    4429       else 
    4430       { 
    4431         html = this.doctype + "\n" + this._doc.documentElement.innerHTML; 
    4432       } 
    4433     break; 
    4434     case "textmode" : 
    4435       html = this._textArea.value; 
    4436     break; 
    4437     default: 
    4438       alert("Mode <" + this._editMode + "> not defined!"); 
    4439       return false; 
    4440   } 
    4441  
    4442   return html; 
    4443 }; 
    4444  
    4445 // completely change the HTML inside 
    4446 Xinha.prototype.setHTML = function(html) 
    4447 { 
    4448   if ( !this.config.fullPage ) 
    4449   { 
    4450     this._doc.body.innerHTML = html; 
    4451   } 
    4452   else 
    4453   { 
    4454     this.setFullHTML(html); 
    4455   } 
    4456   this._textArea.value = html; 
    4457 }; 
    4458  
    4459 // sets the given doctype (useful when config.fullPage is true) 
    4460 Xinha.prototype.setDoctype = function(doctype) 
    4461 { 
    4462   this.doctype = doctype; 
    4463 }; 
    4464  
    4465 /*************************************************** 
    4466  *  Category: UTILITY FUNCTIONS 
    4467  ***************************************************/ 
    4468  
    4469 // variable used to pass the object to the popup editor window. 
    4470 Xinha._object = null; 
    4471  
    4472 // function that returns a clone of the given object 
    4473 Xinha.cloneObject = function(obj) 
    4474 { 
    4475   if ( !obj ) 
    4476   { 
    4477     return null; 
    4478   } 
    4479  
    4480   var newObj = {}; 
    4481  
    4482   // check for array objects 
    4483   if ( obj.constructor.toString().match( /\s*function Array\(/ ) ) 
    4484   { 
    4485     newObj = obj.constructor(); 
    4486   } 
    4487  
    4488   // check for function objects (as usual, IE is fucked up) 
    4489   if ( obj.constructor.toString().match( /\s*function Function\(/ ) ) 
    4490   { 
    4491     newObj = obj; // just copy reference to it 
    4492   } 
    4493   else 
    4494   { 
    4495     for ( var n in obj ) 
    4496     { 
    4497       var node = obj[n]; 
    4498       if ( typeof node == 'object' ) 
    4499       { 
    4500         newObj[n] = Xinha.cloneObject(node); 
    4501       } 
    4502       else 
    4503       { 
    4504         newObj[n] = node; 
    4505       } 
    4506     } 
    4507   } 
    4508  
    4509   return newObj; 
    4510 }; 
    4511  
    4512 // FIXME!!! this should return false for IE < 5.5 
    4513 Xinha.checkSupportedBrowser = function() 
    4514 { 
    4515   if ( Xinha.is_gecko ) 
    4516   { 
    4517     if ( navigator.productSub < 20021201 ) 
    4518     { 
    4519       alert("You need at least Mozilla-1.3 Alpha.\nSorry, your Gecko is not supported."); 
    4520       return false; 
    4521     } 
    4522     if ( navigator.productSub < 20030210 ) 
    4523     { 
    4524       alert("Mozilla < 1.3 Beta is not supported!\nI'll try, though, but it might not work."); 
    4525     } 
    4526   } 
    4527   return Xinha.is_gecko || Xinha.is_ie; 
    4528 }; 
    4529  
    4530 // selection & ranges 
    4531  
    4532 // moved Xinha.prototype._getSelection() to browser specific file 
    4533 // moved Xinha.prototype._createRange()  to browser specific file 
    4534  
    4535 // event handling 
    4536  
    4537 /** Event Flushing 
    4538  *  To try and work around memory leaks in the rather broken 
    4539  *  garbage collector in IE, Xinha.flushEvents can be called 
    4540  *  onunload, it will remove any event listeners (that were added 
    4541  *  through _addEvent(s)) and clear any DOM-0 events. 
    4542  */ 
    4543 Xinha._eventFlushers = []; 
    4544 Xinha.flushEvents = function() 
    4545 { 
    4546   var x = 0; 
    4547   // @todo : check if Array.prototype.pop exists for every supported browsers 
    4548   var e = Xinha._eventFlushers.pop(); 
    4549   while ( e ) 
    4550   { 
    4551     try 
    4552     { 
    4553       if ( e.length == 3 ) 
    4554       { 
    4555         Xinha._removeEvent(e[0], e[1], e[2]); 
    4556         x++; 
    4557       } 
    4558       else if ( e.length == 2 ) 
    4559       { 
    4560         e[0]['on' + e[1]] = null; 
    4561         e[0]._xinha_dom0Events[e[1]] = null; 
    4562         x++; 
    4563       } 
    4564     } 
    4565     catch(ex) 
    4566     { 
    4567       // Do Nothing 
    4568     } 
    4569     e = Xinha._eventFlushers.pop(); 
    4570   } 
    4571    
    4572   /*  
    4573     // This code is very agressive, and incredibly slow in IE, so I've disabled it. 
    4574      
    4575     if(document.all) 
    4576     { 
    4577       for(var i = 0; i < document.all.length; i++) 
    4578       { 
    4579         for(var j in document.all[i]) 
    4580         { 
    4581           if(/^on/.test(j) && typeof document.all[i][j] == 'function') 
    4582           { 
    4583             document.all[i][j] = null; 
    4584             x++; 
    4585           } 
    4586         } 
    4587       } 
    4588     } 
    4589   */ 
    4590    
    4591   // alert('Flushed ' + x + ' events.'); 
    4592 }; 
    4593  
    4594 if ( document.addEventListener ) 
    4595 { 
    4596   Xinha._addEvent = function(el, evname, func) 
    4597   { 
    4598     el.addEventListener(evname, func, true); 
    4599     Xinha._eventFlushers.push([el, evname, func]); 
    4600   }; 
    4601   Xinha._removeEvent = function(el, evname, func) 
    4602   { 
    4603     el.removeEventListener(evname, func, true); 
    4604   }; 
    4605   Xinha._stopEvent = function(ev) 
    4606   { 
    4607     ev.preventDefault(); 
    4608     ev.stopPropagation(); 
    4609   }; 
    4610 } 
    4611 else if ( document.attachEvent ) 
    4612 { 
    4613   Xinha._addEvent = function(el, evname, func) 
    4614   { 
    4615     el.attachEvent("on" + evname, func); 
    4616     Xinha._eventFlushers.push([el, evname, func]); 
    4617   }; 
    4618   Xinha._removeEvent = function(el, evname, func) 
    4619   { 
    4620     el.detachEvent("on" + evname, func); 
    4621   }; 
    4622   Xinha._stopEvent = function(ev) 
    4623   { 
    4624     try 
    4625     { 
    4626       ev.cancelBubble = true; 
    4627       ev.returnValue = false; 
    4628     } 
    4629     catch (ex) 
    4630     { 
    4631       // Perhaps we could try here to stop the window.event 
    4632       // window.event.cancelBubble = true; 
    4633       // window.event.returnValue = false; 
    4634     } 
    4635   }; 
    4636 } 
    4637 else 
    4638 { 
    4639   Xinha._addEvent = function(el, evname, func) 
    4640   { 
    4641     alert('_addEvent is not supported'); 
    4642   }; 
    4643   Xinha._removeEvent = function(el, evname, func) 
    4644   { 
    4645     alert('_removeEvent is not supported'); 
    4646   }; 
    4647   Xinha._stopEvent = function(ev) 
    4648   { 
    4649     alert('_stopEvent is not supported'); 
    4650   }; 
    4651 } 
    4652  
    4653 Xinha._addEvents = function(el, evs, func) 
    4654 { 
    4655   for ( var i = evs.length; --i >= 0; ) 
    4656   { 
    4657     Xinha._addEvent(el, evs[i], func); 
    4658   } 
    4659 }; 
    4660  
    4661 Xinha._removeEvents = function(el, evs, func) 
    4662 { 
    4663   for ( var i = evs.length; --i >= 0; ) 
    4664   { 
    4665     Xinha._removeEvent(el, evs[i], func); 
    4666   } 
    4667 }; 
    4668  
    4669 /** 
    4670  * Adds a standard "DOM-0" event listener to an element. 
    4671  * The DOM-0 events are those applied directly as attributes to 
    4672  * an element - eg element.onclick = stuff; 
    4673  * 
    4674  * By using this function instead of simply overwriting any existing 
    4675  * DOM-0 event by the same name on the element it will trigger as well 
    4676  * as the existing ones.  Handlers are triggered one after the other 
    4677  * in the order they are added. 
    4678  * 
    4679  * Remember to return true/false from your handler, this will determine 
    4680  * whether subsequent handlers will be triggered (ie that the event will 
    4681  * continue or be canceled). 
    4682  * 
    4683  */ 
    4684  
    4685 Xinha.addDom0Event = function(el, ev, fn) 
    4686 { 
    4687   Xinha._prepareForDom0Events(el, ev); 
    4688   el._xinha_dom0Events[ev].unshift(fn); 
    4689 }; 
    4690  
    4691  
    4692 /** 
    4693  * See addDom0Event, the difference is that handlers registered using 
    4694  * prependDom0Event will be triggered before existing DOM-0 events of the 
    4695  * same name on the same element. 
    4696  */ 
    4697  
    4698 Xinha.prependDom0Event = function(el, ev, fn) 
    4699 { 
    4700   Xinha._prepareForDom0Events(el, ev); 
    4701   el._xinha_dom0Events[ev].push(fn); 
    4702 }; 
    4703  
    4704 /** 
    4705  * Prepares an element to receive more than one DOM-0 event handler 
    4706  * when handlers are added via addDom0Event and prependDom0Event. 
    4707  */ 
    4708 Xinha._prepareForDom0Events = function(el, ev) 
    4709 { 
    4710   // Create a structure to hold our lists of event handlers 
    4711   if ( typeof el._xinha_dom0Events == 'undefined' ) 
    4712   { 
    4713     el._xinha_dom0Events = {}; 
    4714     Xinha.freeLater(el, '_xinha_dom0Events'); 
    4715   } 
    4716  
    4717   // Create a list of handlers for this event type 
    4718   if ( typeof el._xinha_dom0Events[ev] == 'undefined' ) 
    4719   { 
    4720     el._xinha_dom0Events[ev] = [ ]; 
    4721     if ( typeof el['on'+ev] == 'function' ) 
    4722     { 
    4723       el._xinha_dom0Events[ev].push(el['on'+ev]); 
    4724     } 
    4725  
    4726     // Make the actual event handler, which runs through 
    4727     // each of the handlers in the list and executes them 
    4728     // in the correct context. 
    4729     el['on'+ev] = function(event) 
    4730     { 
    4731       var a = el._xinha_dom0Events[ev]; 
    4732       // call previous submit methods if they were there. 
    4733       var allOK = true; 
    4734       for ( var i = a.length; --i >= 0; ) 
    4735       { 
    4736         // We want the handler to be a member of the form, not the array, so that "this" will work correctly 
    4737         el._xinha_tempEventHandler = a[i]; 
    4738         if ( el._xinha_tempEventHandler(event) === false ) 
    4739         { 
    4740           el._xinha_tempEventHandler = null; 
    4741           allOK = false; 
    4742           break; 
    4743         } 
    4744         el._xinha_tempEventHandler = null; 
    4745       } 
    4746       return allOK; 
    4747     }; 
    4748  
    4749     Xinha._eventFlushers.push([el, ev]); 
    4750   } 
    4751 }; 
    4752  
    4753 Xinha.prototype.notifyOn = function(ev, fn) 
    4754 { 
    4755   if ( typeof this._notifyListeners[ev] == 'undefined' ) 
    4756   { 
    4757     this._notifyListeners[ev] = []; 
    4758     Xinha.freeLater(this, '_notifyListeners'); 
    4759   } 
    4760   this._notifyListeners[ev].push(fn); 
    4761 }; 
    4762  
    4763 Xinha.prototype.notifyOf = function(ev, args) 
    4764 { 
    4765   if ( this._notifyListeners[ev] ) 
    4766   { 
    4767     for ( var i = 0; i < this._notifyListeners[ev].length; i++ ) 
    4768     { 
    4769       this._notifyListeners[ev][i](ev, args); 
    4770     } 
    4771   } 
    4772 }; 
    4773  
    4774 Xinha._removeClass = function(el, className) 
    4775 { 
    4776   if ( ! ( el && el.className ) ) 
    4777   { 
    4778     return; 
    4779   } 
    4780   var cls = el.className.split(" "); 
    4781   var ar = []; 
    4782   for ( var i = cls.length; i > 0; ) 
    4783   { 
    4784     if ( cls[--i] != className ) 
    4785     { 
    4786       ar[ar.length] = cls[i]; 
    4787     } 
    4788   } 
    4789   el.className = ar.join(" "); 
    4790 }; 
    4791  
    4792 Xinha._addClass = function(el, className) 
    4793 { 
    4794   // remove the class first, if already there 
    4795   Xinha._removeClass(el, className); 
    4796   el.className += " " + className; 
    4797 }; 
    4798  
    4799 Xinha._hasClass = function(el, className) 
    4800 { 
    4801   if ( ! ( el && el.className ) ) 
    4802   { 
    4803     return false; 
    4804   } 
    4805   var cls = el.className.split(" "); 
    4806   for ( var i = cls.length; i > 0; ) 
    4807   { 
    4808     if ( cls[--i] == className ) 
    4809     { 
    4810       return true; 
    4811     } 
    4812   } 
    4813   return false; 
    4814 }; 
    4815  
    4816 Xinha._blockTags = " body form textarea fieldset ul ol dl li div " + 
    4817 "p h1 h2 h3 h4 h5 h6 quote pre table thead " + 
    4818 "tbody tfoot tr td th iframe address blockquote "; 
    4819 Xinha.isBlockElement = function(el) 
    4820 { 
    4821   return el && el.nodeType == 1 && (Xinha._blockTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1); 
    4822 }; 
    4823  
    4824 Xinha._paraContainerTags = " body td th caption fieldset div"; 
    4825 Xinha.isParaContainer = function(el) 
    4826 { 
    4827   return el && el.nodeType == 1 && (Xinha._paraContainerTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1); 
    4828 }; 
    4829  
    4830 // These are all the tags for which the end tag is not optional or  
    4831 // forbidden, taken from the list at: 
    4832 //   http://www.w3.org/TR/REC-html40/index/elements.html 
    4833 Xinha._closingTags = " a abbr acronym address applet b bdo big blockquote button caption center cite code del dfn dir div dl em fieldset font form frameset h1 h2 h3 h4 h5 h6 i iframe ins kbd label legend map menu noframes noscript object ol optgroup pre q s samp script select small span strike strong style sub sup table textarea title tt u ul var "; 
    4834  
    4835 Xinha.needsClosingTag = function(el) 
    4836 { 
    4837   return el && el.nodeType == 1 && (Xinha._closingTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1); 
    4838 }; 
    4839  
    4840 // performs HTML encoding of some given string 
    4841 Xinha.htmlEncode = function(str) 
    4842 { 
    4843   if ( typeof str.replace == 'undefined' ) 
    4844   { 
    4845     str = str.toString(); 
    4846   } 
    4847   // we don't need regexp for that, but.. so be it for now. 
    4848   str = str.replace(/&/ig, "&amp;"); 
    4849   str = str.replace(/</ig, "&lt;"); 
    4850   str = str.replace(/>/ig, "&gt;"); 
    4851   str = str.replace(/\xA0/g, "&nbsp;"); // Decimal 160, non-breaking-space 
    4852   str = str.replace(/\x22/g, "&quot;"); 
    4853   // \x22 means '"' -- we use hex reprezentation so that we don't disturb 
    4854   // JS compressors (well, at least mine fails.. ;) 
    4855   return str; 
    4856 }; 
    4857  
    4858 // moved Xinha.getHTML() to getHTML.js  
    4859 Xinha.prototype.stripBaseURL = function(string) 
    4860 { 
    4861   if ( this.config.baseHref === null || !this.config.stripBaseHref ) 
    4862   { 
    4863     return string; 
    4864   } 
    4865   // strip host-part of URL which is added by MSIE to links relative to server root 
    4866   var baseurl = this.config.baseHref.replace(/^(https?:\/\/[^\/]+)(.*)$/, '$1'); 
    4867   var basere = new RegExp(baseurl); 
    4868   return string.replace(basere, ""); 
    4869 }; 
    4870  
    4871 String.prototype.trim = function() 
    4872 { 
    4873   return this.replace(/^\s+/, '').replace(/\s+$/, ''); 
    4874 }; 
    4875  
    4876 // creates a rgb-style color from a number 
    4877 Xinha._makeColor = function(v) 
    4878 { 
    4879   if ( typeof v != "number" ) 
    4880   { 
    4881     // already in rgb (hopefully); IE doesn't get here. 
    4882     return v; 
    4883   } 
    4884   // IE sends number; convert to rgb. 
    4885   var r = v & 0xFF; 
    4886   var g = (v >> 8) & 0xFF; 
    4887   var b = (v >> 16) & 0xFF; 
    4888   return "rgb(" + r + "," + g + "," + b + ")"; 
    4889 }; 
    4890  
    4891 // returns hexadecimal color representation from a number or a rgb-style color. 
    4892 Xinha._colorToRgb = function(v) 
    4893 { 
    4894   if ( !v ) 
    4895   { 
    4896     return ''; 
    4897   } 
    4898   var r,g,b; 
    4899   // @todo: why declaring this function here ? This needs to be a public methode of the object Xinha._colorToRgb 
    4900   // returns the hex representation of one byte (2 digits) 
    4901   function hex(d) 
    4902   { 
    4903     return (d < 16) ? ("0" + d.toString(16)) : d.toString(16); 
    4904   } 
    4905  
    4906   if ( typeof v == "number" ) 
    4907   { 
    4908     // we're talking to IE here 
    4909     r = v & 0xFF; 
    4910     g = (v >> 8) & 0xFF; 
    4911     b = (v >> 16) & 0xFF; 
    4912     return "#" + hex(r) + hex(g) + hex(b); 
    4913   } 
    4914  
    4915   if ( v.substr(0, 3) == "rgb" ) 
    4916   { 
    4917     // in rgb(...) form -- Mozilla 
    4918     var re = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/; 
    4919     if ( v.match(re) ) 
    4920     { 
    4921       r = parseInt(RegExp.$1, 10); 
    4922       g = parseInt(RegExp.$2, 10); 
    4923       b = parseInt(RegExp.$3, 10); 
    4924       return "#" + hex(r) + hex(g) + hex(b); 
    4925     } 
    4926     // doesn't match RE?!  maybe uses percentages or float numbers 
    4927     // -- FIXME: not yet implemented. 
    4928     return null; 
    4929   } 
    4930  
    4931   if ( v.substr(0, 1) == "#" ) 
    4932   { 
    4933     // already hex rgb (hopefully :D ) 
    4934     return v; 
    4935   } 
    4936  
    4937   // if everything else fails ;) 
    4938   return null; 
    4939 }; 
    4940  
    4941 // modal dialogs for Mozilla (for IE we're using the showModalDialog() call). 
    4942  
    4943 // receives an URL to the popup dialog and a function that receives one value; 
    4944 // this function will get called after the dialog is closed, with the return 
    4945 // value of the dialog. 
    4946 Xinha.prototype._popupDialog = function(url, action, init) 
    4947 { 
    4948   Dialog(this.popupURL(url), action, init); 
    4949 }; 
    4950  
    4951 // paths 
    4952  
    4953 Xinha.prototype.imgURL = function(file, plugin) 
    4954 { 
    4955   if ( typeof plugin == "undefined" ) 
    4956   { 
    4957     return _editor_url + file; 
    4958   } 
    4959   else 
    4960   { 
    4961     return _editor_url + "plugins/" + plugin + "/img/" + file; 
    4962   } 
    4963 }; 
    4964  
    4965 Xinha.prototype.popupURL = function(file) 
    4966 { 
    4967   var url = ""; 
    4968   if ( file.match(/^plugin:\/\/(.*?)\/(.*)/) ) 
    4969   { 
    4970     var plugin = RegExp.$1; 
    4971     var popup = RegExp.$2; 
    4972     if ( ! ( /\.html$/.test(popup) ) ) 
    4973     { 
    4974       popup += ".html"; 
    4975     } 
    4976     url = _editor_url + "plugins/" + plugin + "/popups/" + popup; 
    4977   } 
    4978   else if ( file.match(/^\/.*?/) ) 
    4979   { 
    4980     url = file; 
    4981   } 
    4982   else 
    4983   { 
    4984     url = _editor_url + this.config.popupURL + file; 
    4985   } 
    4986   return url; 
    4987 }; 
    4988  
    4989 /** 
    4990  * FIX: Internet Explorer returns an item having the _name_ equal to the given 
    4991  * id, even if it's not having any id.  This way it can return a different form 
    4992  * field even if it's not a textarea.  This workarounds the problem by 
    4993  * specifically looking to search only elements having a certain tag name. 
    4994  */ 
    4995 Xinha.getElementById = function(tag, id) 
    4996 { 
    4997   var el, i, objs = document.getElementsByTagName(tag); 
    4998   for ( i = objs.length; --i >= 0 && (el = objs[i]); ) 
    4999   { 
    5000     if ( el.id == id ) 
    5001     { 
    5002       return el; 
    5003     } 
    5004   } 
    5005   return null; 
    5006 }; 
    5007  
    5008  
    5009 /** Use some CSS trickery to toggle borders on tables */ 
    5010  
    5011 Xinha.prototype._toggleBorders = function() 
    5012 { 
    5013   var tables = this._doc.getElementsByTagName('TABLE'); 
    5014   if ( tables.length !== 0 ) 
    5015   { 
    5016    if ( !this.borders ) 
    5017    { 
    5018     name = "bordered"; 
    5019     this.borders = true; 
    5020    } 
    5021    else 
    5022    { 
    5023      name = ""; 
    5024      this.borders = false; 
    5025    } 
    5026  
    5027    for ( var i=0; i < tables.length; i++ ) 
    5028    { 
    5029      if ( this.borders ) 
    5030      { 
    5031         // flashing the display forces moz to listen (JB:18-04-2005) - #102 
    5032         if ( Xinha.is_gecko ) 
    5033         { 
    5034           tables[i].style.display="none"; 
    5035           tables[i].style.display="table"; 
    5036         } 
    5037         Xinha._addClass(tables[i], 'htmtableborders'); 
    5038      } 
    5039      else 
    5040      { 
    5041        Xinha._removeClass(tables[i], 'htmtableborders'); 
    5042      } 
    5043    } 
    5044   } 
    5045   return true; 
    5046 }; 
    5047  
    5048  
    5049 Xinha.addClasses = function(el, classes) 
    5050 { 
    5051   if ( el !== null ) 
    5052   { 
    5053     var thiers = el.className.trim().split(' '); 
    5054     var ours   = classes.split(' '); 
    5055     for ( var x = 0; x < ours.length; x++ ) 
    5056     { 
    5057       var exists = false; 
    5058       for ( var i = 0; exists === false && i < thiers.length; i++ ) 
    5059       { 
    5060         if ( thiers[i] == ours[x] ) 
    5061         { 
    5062           exists = true; 
    5063         } 
    5064       } 
    5065       if ( exists === false ) 
    5066       { 
    5067         thiers[thiers.length] = ours[x]; 
    5068       } 
    5069     } 
    5070     el.className = thiers.join(' ').trim(); 
    5071   } 
    5072 }; 
    5073  
    5074 Xinha.removeClasses = function(el, classes) 
    5075 { 
    5076   var existing    = el.className.trim().split(); 
    5077   var new_classes = []; 
    5078   var remove      = classes.trim().split(); 
    5079  
    5080   for ( var i = 0; i < existing.length; i++ ) 
    5081   { 
    5082     var found = false; 
    5083     for ( var x = 0; x < remove.length && !found; x++ ) 
    5084     { 
    5085       if ( existing[i] == remove[x] ) 
    5086       { 
    5087         found = true; 
    5088       } 
    5089     } 
    5090     if ( !found ) 
    5091     { 
    5092       new_classes[new_classes.length] = existing[i]; 
    5093     } 
    5094   } 
    5095   return new_classes.join(' '); 
    5096 }; 
    5097  
    5098 /** Alias these for convenience */ 
    5099 Xinha.addClass       = Xinha._addClass; 
    5100 Xinha.removeClass    = Xinha._removeClass; 
    5101 Xinha._addClasses    = Xinha.addClasses; 
    5102 Xinha._removeClasses = Xinha.removeClasses; 
    5103  
    5104 /** Use XML HTTPRequest to post some data back to the server and do something 
    5105  * with the response (asyncronously!), this is used by such things as the tidy functions 
    5106  */ 
    5107 Xinha._postback = function(url, data, handler) 
    5108 { 
    5109   var req = null; 
    5110   if ( Xinha.is_ie ) 
    5111   { 
    5112    req = new ActiveXObject("Microsoft.XMLHTTP"); 
    5113   } 
    5114   else 
    5115   { 
    5116    req = new XMLHttpRequest(); 
    5117   } 
    5118  
    5119   var content = ''; 
    5120   if (typeof data == 'string') 
    5121   { 
    5122     content = data; 
    5123   } 
    5124   else if(typeof data == "object") 
    5125   { 
    5126     for ( var i in data ) 
    5127     { 
    5128       content += (content.length ? '&' : '') + i + '=' + encodeURIComponent(data[i]); 
    5129     } 
    5130   } 
    5131  
    5132   function callBack() 
    5133   { 
    5134     if ( req.readyState == 4 ) 
    5135     { 
    5136       if ( req.status == 200 || Xinha.isRunLocally && req.status == 0 ) 
    5137       { 
    5138         if ( typeof handler == 'function' ) 
    5139         { 
    5140           handler(req.responseText, req); 
    5141         } 
    5142       } 
    5143       else 
    5144       { 
    5145         alert('An error has occurred: ' + req.statusText); 
    5146       } 
    5147     } 
    5148   } 
    5149  
    5150   req.onreadystatechange = callBack; 
    5151  
    5152   req.open('POST', url, true); 
    5153   req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8'); 
    5154   //alert(content); 
    5155   req.send(content); 
    5156 }; 
    5157  
    5158 Xinha._getback = function(url, handler) 
    5159 { 
    5160   var req = null; 
    5161   if ( Xinha.is_ie ) 
    5162   { 
    5163    req = new ActiveXObject("Microsoft.XMLHTTP"); 
    5164   } 
    5165   else 
    5166   { 
    5167    req = new XMLHttpRequest(); 
    5168   } 
    5169  
    5170   function callBack() 
    5171   { 
    5172     if ( req.readyState == 4 ) 
    5173     { 
    5174       if ( req.status == 200 || Xinha.isRunLocally && req.status == 0 ) 
    5175       { 
    5176         handler(req.responseText, req); 
    5177       } 
    5178       else 
    5179       { 
    5180         alert('An error has occurred: ' + req.statusText); 
    5181       } 
    5182     } 
    5183   } 
    5184  
    5185   req.onreadystatechange = callBack; 
    5186   req.open('GET', url, true); 
    5187   req.send(null); 
    5188 }; 
    5189  
    5190 Xinha._geturlcontent = function(url) 
    5191 { 
    5192   var req = null; 
    5193   if ( Xinha.is_ie ) 
    5194   { 
    5195    req = new ActiveXObject("Microsoft.XMLHTTP"); 
    5196   } 
    5197   else 
    5198   { 
    5199    req = new XMLHttpRequest(); 
    5200   } 
    5201  
    5202   // Synchronous! 
    5203   req.open('GET', url, false); 
    5204   req.send(null); 
    5205   if ( req.status == 200 || Xinha.isRunLocally && req.status == 0 ) 
    5206   { 
    5207     return req.responseText; 
    5208   } 
    5209   else 
    5210   { 
    5211     return ''; 
    5212   } 
    5213  
    5214 }; 
    5215  
    5216 /** 
    5217  * Unless somebody already has, make a little function to debug things 
    5218  */ 
    5219 if ( typeof dump == 'undefined' ) 
    5220 { 
    5221   function dump(o) 
    5222   { 
    5223     var s = ''; 
    5224     for ( var prop in o ) 
    5225     { 
    5226       s += prop + ' = ' + o[prop] + '\n'; 
    5227     } 
    5228     var x = window.open("", "debugger"); 
    5229     x.document.write('<pre>' + s + '</pre>'); 
    5230   } 
    5231 } 
    5232  
    5233 Xinha.arrayContainsArray = function(a1, a2) 
    5234 { 
    5235   var all_found = true; 
    5236   for ( var x = 0; x < a2.length; x++ ) 
    5237   { 
    5238     var found = false; 
    5239     for ( var i = 0; i < a1.length; i++ ) 
    5240     { 
    5241       if ( a1[i] == a2[x] ) 
    5242       { 
    5243         found = true; 
    5244         break; 
    5245       } 
    5246     } 
    5247     if ( !found ) 
    5248     { 
    5249       all_found = false; 
    5250       break; 
    5251     } 
    5252   } 
    5253   return all_found; 
    5254 }; 
    5255  
    5256 Xinha.arrayFilter = function(a1, filterfn) 
    5257 { 
    5258   var new_a = [ ]; 
    5259   for ( var x = 0; x < a1.length; x++ ) 
    5260   { 
    5261     if ( filterfn(a1[x]) ) 
    5262     { 
    5263       new_a[new_a.length] = a1[x]; 
    5264     } 
    5265   } 
    5266   return new_a; 
    5267 }; 
    5268  
    5269 Xinha.uniq_count = 0; 
    5270 Xinha.uniq = function(prefix) 
    5271 { 
    5272   return prefix + Xinha.uniq_count++; 
    5273 }; 
    5274  
    5275 /** New language handling functions **/ 
    5276  
    5277  
    5278 /** Load a language file. 
    5279  *  This function should not be used directly, Xinha._lc will use it when necessary. 
    5280  * @param context Case sensitive context name, eg 'Xinha', 'TableOperations', ... 
    5281  */ 
    5282 Xinha._loadlang = function(context) 
    5283 { 
    5284   var url, lang; 
    5285   if ( typeof _editor_lcbackend == "string" ) 
    5286   { 
    5287     //use backend 
    5288     url = _editor_lcbackend; 
    5289     url = url.replace(/%lang%/, _editor_lang); 
    5290     url = url.replace(/%context%/, context); 
    5291   } 
    5292   else 
    5293   { 
    5294     //use internal files 
    5295     if ( context != 'Xinha') 
    5296     { 
    5297       url = _editor_url+"plugins/"+context+"/lang/"+_editor_lang+".js"; 
    5298     } 
    5299     else 
    5300     { 
    5301       url = _editor_url+"lang/"+_editor_lang+".js"; 
    5302     } 
    5303   } 
    5304  
    5305   var langData = Xinha._geturlcontent(url); 
    5306   if ( langData !== "" ) 
    5307   { 
    5308     try 
    5309     { 
    5310       eval('lang = ' + langData); 
    5311     } 
    5312     catch(ex) 
    5313     { 
    5314       alert('Error reading Language-File ('+url+'):\n'+Error.toString()); 
    5315       lang = {}; 
    5316     } 
    5317   } 
    5318   else 
    5319   { 
    5320     lang = {}; 
    5321   } 
    5322  
    5323   return lang; 
    5324 }; 
    5325  
    5326 /** Return a localised string. 
    5327  * @param string    English language string. It can also contain variables in the form "Some text with $variable=replaced text$".  
    5328  *                  This replaces $variable in "Some text with $variable" with "replaced text" 
    5329  * @param context   Case sensitive context name, eg 'Xinha' (default), 'TableOperations'... 
    5330  * @param replace   Replace $variables in String, eg {foo: 'replaceText'} ($foo in string will be replaced) 
    5331  */ 
    5332 Xinha._lc = function(string, context, replace) 
    5333 { 
    5334   var ret; 
    5335   var m = null; 
    5336   if (typeof string == 'string') m = string.match(/\$(.*?)=(.*?)\$/g); 
    5337   if (m)  
    5338   { 
    5339     if (!replace) replace = {}; 
    5340     for (var i = 0;i<m.length;i++) 
    5341     { 
    5342       var n = m[i].match(/\$(.*?)=(.*?)\$/); 
    5343       replace[n[1]] = n[2]; 
    5344       string = string.replace(n[0],'$'+n[1]); 
    5345     } 
    5346   } 
    5347   if ( _editor_lang == "en" ) 
    5348   { 
    5349     if ( typeof string == 'object' && string.string ) 
    5350     { 
    5351       ret = string.string; 
    5352     } 
    5353     else 
    5354     { 
    5355       ret = string; 
    5356     } 
    5357   } 
    5358   else 
    5359   { 
    5360     if ( typeof Xinha._lc_catalog == 'undefined' ) 
    5361     { 
    5362       Xinha._lc_catalog = [ ]; 
    5363     } 
    5364  
    5365     if ( typeof context == 'undefined' ) 
    5366     { 
    5367       context = 'Xinha'; 
    5368     } 
    5369  
    5370     if ( typeof Xinha._lc_catalog[context] == 'undefined' ) 
    5371     { 
    5372       Xinha._lc_catalog[context] = Xinha._loadlang(context); 
    5373     } 
    5374  
    5375     var key; 
    5376     if ( typeof string == 'object' && string.key ) 
    5377     { 
    5378       key = string.key; 
    5379     } 
    5380     else if ( typeof string == 'object' && string.string ) 
    5381     { 
    5382       key = string.string; 
    5383     } 
    5384     else 
    5385     { 
    5386       key = string; 
    5387     } 
    5388  
    5389     if ( typeof Xinha._lc_catalog[context][key] == 'undefined' ) 
    5390     { 
    5391       if ( context=='Xinha' ) 
    5392       { 
    5393         // Indicate it's untranslated 
    5394         if ( typeof string == 'object' && string.string ) 
    5395         { 
    5396           ret = string.string; 
    5397         } 
    5398         else 
    5399         { 
    5400           ret = string; 
    5401         } 
    5402       } 
    5403       else 
    5404       { 
    5405         //if string is not found and context is not Xinha try if it is in Xinha 
    5406         return Xinha._lc(string, 'Xinha', replace); 
    5407       } 
    5408     } 
    5409     else 
    5410     { 
    5411       ret = Xinha._lc_catalog[context][key]; 
    5412     } 
    5413   } 
    5414  
    5415   if ( typeof string == 'object' && string.replace ) 
    5416   { 
    5417     replace = string.replace; 
    5418   } 
    5419   if ( typeof replace != "undefined" ) 
    5420   { 
    5421     for ( var i in replace ) 
    5422     { 
    5423       ret = ret.replace('$'+i, replace[i]); 
    5424     } 
    5425   } 
    5426  
    5427   return ret; 
    5428 }; 
    5429  
    5430 Xinha.hasDisplayedChildren = function(el) 
    5431 { 
    5432   var children = el.childNodes; 
    5433   for ( var i = 0; i < children.length; i++ ) 
    5434   { 
    5435     if ( children[i].tagName ) 
    5436     { 
    5437       if ( children[i].style.display != 'none' ) 
    5438       { 
    5439         return true; 
    5440       } 
    5441     } 
    5442   } 
    5443   return false; 
    5444 }; 
    5445  
    5446 /** 
    5447  * Load a javascript file by inserting it in the HEAD tag and eventually call a function when loaded 
    5448  * @param {string} U (Url)      Source url of the file to load 
    5449  * @param {object} C {Callback} Callback function to launch once ready (optional) 
    5450  * @param {object} O (scOpe)    Application scope for the callback function (optional) 
    5451  * @param {object} B (Bonus}    Arbitrary object send as a param to the callback function (optional) 
    5452  * @public 
    5453  */ 
    5454 Xinha._loadback = function(U, C, O, B) 
    5455 { 
    5456   var T = Xinha.is_ie ? "onreadystatechange" : "onload"; 
    5457   var S = document.createElement("script"); 
    5458   S.type = "text/javascript"; 
    5459   S.src = U; 
    5460   if ( C ) 
    5461   { 
    5462     S[T] = function() 
    5463     { 
    5464       if ( Xinha.is_ie && ! ( /loaded|complete/.test(window.event.srcElement.readyState) ) ) 
    5465       { 
    5466         return; 
    5467       } 
    5468       C.call(O ? O : this, B); 
    5469       S[T] = null; 
    5470     }; 
    5471   } 
    5472   document.getElementsByTagName("head")[0].appendChild(S); 
    5473 }; 
    5474  
    5475 Xinha.collectionToArray = function(collection) 
    5476 { 
    5477   var array = [ ]; 
    5478   for ( var i = 0; i < collection.length; i++ ) 
    5479   { 
    5480     array.push(collection.item(i)); 
    5481   } 
    5482   return array; 
    5483 }; 
    5484  
    5485 if ( !Array.prototype.append ) 
    5486 { 
    5487   Array.prototype.append  = function(a) 
    5488   { 
    5489     for ( var i = 0; i < a.length; i++ ) 
    5490     { 
    5491       this.push(a[i]); 
    5492     } 
    5493     return this; 
    5494   }; 
    5495 } 
    5496  
    5497 Xinha.makeEditors = function(editor_names, default_config, plugin_names) 
    5498 { 
    5499   if ( typeof default_config == 'function' ) 
    5500   { 
    5501     default_config = default_config(); 
    5502   } 
    5503  
    5504   var editors = {}; 
    5505   for ( var x = 0; x < editor_names.length; x++ ) 
    5506   { 
    5507     var editor = new Xinha(editor_names[x], Xinha.cloneObject(default_config)); 
    5508     editor.registerPlugins(plugin_names); 
    5509     editors[editor_names[x]] = editor; 
    5510   } 
    5511   return editors; 
    5512 }; 
    5513  
    5514 Xinha.startEditors = function(editors) 
    5515 { 
    5516   for ( var i in editors ) 
    5517   { 
    5518     if ( editors[i].generate ) 
    5519     { 
    5520       editors[i].generate(); 
    5521     } 
    5522   } 
    5523 }; 
    5524  
    5525 Xinha.prototype.registerPlugins = function(plugin_names) 
    5526 { 
    5527   if ( plugin_names ) 
    5528   { 
    5529     for ( var i = 0; i < plugin_names.length; i++ ) 
    5530     { 
    5531       this.setLoadingMessage('Register plugin $plugin', 'Xinha', {'plugin': plugin_names[i]}); 
    5532       this.registerPlugin(eval(plugin_names[i])); 
    5533     } 
    5534   } 
    5535 }; 
    5536  
    5537 /** Utility function to base64_encode some arbitrary data, uses the builtin btoa() if it exists (Moz) */ 
    5538  
    5539 Xinha.base64_encode = function(input) 
    5540 { 
    5541   var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 
    5542   var output = ""; 
    5543   var chr1, chr2, chr3; 
    5544   var enc1, enc2, enc3, enc4; 
    5545   var i = 0; 
    5546  
    5547   do 
    5548   { 
    5549     chr1 = input.charCodeAt(i++); 
    5550     chr2 = input.charCodeAt(i++); 
    5551     chr3 = input.charCodeAt(i++); 
    5552  
    5553     enc1 = chr1 >> 2; 
    5554     enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 
    5555     enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 
    5556     enc4 = chr3 & 63; 
    5557  
    5558     if ( isNaN(chr2) ) 
    5559     { 
    5560       enc3 = enc4 = 64; 
    5561     } 
    5562     else if ( isNaN(chr3) ) 
    5563     { 
    5564       enc4 = 64; 
    5565     } 
    5566  
    5567     output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4); 
    5568   } while ( i < input.length ); 
    5569  
    5570   return output; 
    5571 }; 
    5572  
    5573 /** Utility function to base64_decode some arbitrary data, uses the builtin atob() if it exists (Moz) */ 
    5574  
    5575 Xinha.base64_decode = function(input) 
    5576 { 
    5577   var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 
    5578   var output = ""; 
    5579   var chr1, chr2, chr3; 
    5580   var enc1, enc2, enc3, enc4; 
    5581   var i = 0; 
    5582  
    5583   // remove all characters that are not A-Z, a-z, 0-9, +, /, or = 
    5584   input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); 
    5585  
    5586   do 
    5587   { 
    5588     enc1 = keyStr.indexOf(input.charAt(i++)); 
    5589     enc2 = keyStr.indexOf(input.charAt(i++)); 
    5590     enc3 = keyStr.indexOf(input.charAt(i++)); 
    5591     enc4 = keyStr.indexOf(input.charAt(i++)); 
    5592  
    5593     chr1 = (enc1 << 2) | (enc2 >> 4); 
    5594     chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); 
    5595     chr3 = ((enc3 & 3) << 6) | enc4; 
    5596  
    5597     output = output + String.fromCharCode(chr1); 
    5598  
    5599     if ( enc3 != 64 ) 
    5600     { 
    5601       output = output + String.fromCharCode(chr2); 
    5602     } 
    5603     if ( enc4 != 64 ) 
    5604     { 
    5605       output = output + String.fromCharCode(chr3); 
    5606     } 
    5607   } while ( i < input.length ); 
    5608  
    5609   return output; 
    5610 }; 
    5611  
    5612 Xinha.removeFromParent = function(el) 
    5613 { 
    5614   if ( !el.parentNode ) 
    5615   { 
    5616     return; 
    5617   } 
    5618   var pN = el.parentNode; 
    5619   pN.removeChild(el); 
    5620   return el; 
    5621 }; 
    5622  
    5623 Xinha.hasParentNode = function(el) 
    5624 { 
    5625   if ( el.parentNode ) 
    5626   { 
    5627     // When you remove an element from the parent in IE it makes the parent 
    5628     // of the element a document fragment.  Moz doesn't. 
    5629     if ( el.parentNode.nodeType == 11 ) 
    5630     { 
    5631       return false; 
    5632     } 
    5633     return true; 
    5634   } 
    5635  
    5636   return false; 
    5637 }; 
    5638  
    5639 // moved Xinha.getOuterHTML() to browser specific file 
    5640  
    5641 // detect the size of visible area 
    5642 // when calling from a popup window, call Xinha.viewportSize(window) to get the size of the popup 
    5643 Xinha.viewportSize = function(scope) 
    5644 { 
    5645   scope = (scope) ? scope : window; 
    5646   var x,y; 
    5647   if (window.innerHeight) // all except Explorer 
    5648   { 
    5649     x = scope.innerWidth; 
    5650     y = scope.innerHeight; 
    5651   } 
    5652   else if (document.documentElement && document.documentElement.clientHeight) 
    5653   // Explorer 6 Strict Mode 
    5654   { 
    5655     x = scope.document.documentElement.clientWidth; 
    5656     y = scope.document.documentElement.clientHeight; 
    5657   } 
    5658   else if (document.body) // other Explorers 
    5659   { 
    5660     x = scope.document.body.clientWidth; 
    5661     y = scope.document.body.clientHeight; 
    5662   } 
    5663   return {'x':x,'y':y}; 
    5664 }; 
    5665  
    5666 // find X position of an element 
    5667 Xinha.findPosX = function(obj) 
    5668 { 
    5669   var curleft = 0; 
    5670   if ( obj.offsetParent ) 
    5671   { 
    5672     while ( obj.offsetParent ) 
    5673     { 
    5674       curleft += obj.offsetLeft; 
    5675       obj = obj.offsetParent; 
    5676     } 
    5677   } 
    5678   else if ( obj.x ) 
    5679   { 
    5680     curleft += obj.x; 
    5681   } 
    5682   return curleft; 
    5683 }; 
    5684  
    5685 // find Y position of an element 
    5686 Xinha.findPosY = function(obj) 
    5687 { 
    5688   var curtop = 0; 
    5689   if ( obj.offsetParent ) 
    5690   { 
    5691     while ( obj.offsetParent ) 
    5692     { 
    5693       curtop += obj.offsetTop; 
    5694       obj = obj.offsetParent; 
    5695     } 
    5696   } 
    5697   else if ( obj.y ) 
    5698   { 
    5699     curtop += obj.y; 
    5700   } 
    5701   return curtop; 
    5702 }; 
    5703  
    5704 Xinha.prototype.setLoadingMessage = function(string, context, replace) 
    5705 { 
    5706   if ( !this.config.showLoading || !document.getElementById("loading_sub_" + this._textArea.name) ) 
    5707   { 
    5708     return; 
    5709   } 
    5710   var elt = document.getElementById("loading_sub_" + this._textArea.name); 
    5711   elt.innerHTML = Xinha._lc(string, context, replace); 
    5712 }; 
    5713  
    5714 Xinha.prototype.removeLoadingMessage = function() 
    5715 { 
    5716   if ( !this.config.showLoading || !document.getElementById("loading_" + this._textArea.name) ) 
    5717   { 
    5718     return ; 
    5719   } 
    5720   document.body.removeChild(document.getElementById("loading_" + this._textArea.name)); 
    5721 }; 
    5722  
    5723 Xinha.toFree = []; 
    5724 Xinha.freeLater = function(obj,prop) 
    5725 { 
    5726   Xinha.toFree.push({o:obj,p:prop}); 
    5727 }; 
    5728  
    5729 /** 
    5730  * Release memory properties from object 
    5731  * @param {object} object The object to free memory 
    5732  * @param (string} prop   The property to release (optional) 
    5733  * @private 
    5734  */ 
    5735 Xinha.free = function(obj, prop) 
    5736 { 
    5737   if ( obj && !prop ) 
    5738   { 
    5739     for ( var p in obj ) 
    5740     { 
    5741       Xinha.free(obj, p); 
    5742     } 
    5743   } 
    5744   else if ( obj ) 
    5745   { 
    5746     try { obj[prop] = null; } catch(x) {} 
    5747   } 
    5748 }; 
    5749  
    5750 /** IE's Garbage Collector is broken very badly.  We will do our best to  
    5751  *   do it's job for it, but we can't be perfect. 
    5752  */ 
    5753  
    5754 Xinha.collectGarbageForIE = function()  
    5755  
    5756   Xinha.flushEvents();    
    5757   for ( var x = 0; x < Xinha.toFree.length; x++ ) 
    5758   { 
    5759     if ( !Xinha.toFree[x].o ) 
    5760     { 
    5761       alert("What is " + x + ' ' + Xinha.toFree[x].o); 
    5762     } 
    5763     Xinha.free(Xinha.toFree[x].o, Xinha.toFree[x].p); 
    5764     Xinha.toFree[x].o = null; 
    5765   } 
    5766 }; 
    5767 // backward compatibility 
    5768 HTMLArea = Xinha; 
    5769 Xinha.init(); 
    5770 Xinha.addDom0Event(window,'unload',Xinha.collectGarbageForIE); 
     23document.write('<script type="text/javascript" src="'+_editor_url+'XinhaCore.js"></script>'); 
Note: See TracChangeset for help on using the changeset viewer.