Changeset 761 for branches


Ignore:
Timestamp:
02/24/07 02:18:05 (13 years ago)
Author:
ray
Message:

updated branch

Location:
branches/ray
Files:
20 deleted
339 edited
96 copied

Legend:

Unmodified
Added
Removed
  • branches/ray/contrib/lc_parse_strings.php

    • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
    r354 r761  
    11<?php 
    2 //die("this script is disabled for security"); 
     2die("this script is disabled for security"); 
    33 
    44/** 
  • branches/ray/contrib/php-xinha.php

    • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
  • branches/ray/examples/Extended.html

    • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
  • branches/ray/examples/custom.css

    • Property svn:keywords changed from LastChangedDate LastChangedRevision LastChangedBy HeadURL to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
  • branches/ray/examples/dynamic.css

    • Property svn:keywords changed from LastChangedDate LastChangedRevision LastChangedBy HeadURL to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
  • branches/ray/examples/ext_example-body.html

    • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
    r612 r761  
    44 
    55  <!-- --------------------------------------------------------------------- 
    6     --  $HeadURL: http://svn.xinha.python-hosting.com/trunk/examples/ext_example-body.html $ 
    7     --  $LastChangedDate: 2005-07-27 16:43:19 +0200 (Mi, 27 Jul 2005) $ 
    8     --  $LastChangedRevision: 287 $ 
    9     --  $LastChangedBy: gocher $ 
     6    --  $HeadURL$ 
     7    --  $LastChangedDate$ 
     8    --  $LastChangedRevision$ 
     9    --  $LastChangedBy$ 
    1010    ------------------------------------------------------------------------ --> 
    1111 
    1212  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
    1313  <title>Example of Xinha</title> 
    14   <link rel="stylesheet" href="full_example.css" /> 
     14  <link rel="stylesheet" type="text/css" href="full_example.css" /> 
    1515 
    1616  <script type="text/javascript"> 
     
    2828    // in this example we do a little regular expression to find the absolute path. 
    2929    _editor_url  = document.location.href.replace(/examples\/ext_example-body\.html.*/, '') 
    30     //moved _editor_lang & _editor_skin to init function because of error thrown when frame document not ready   
     30    //moved _editor_lang & _editor_skin to init function because of error thrown when frame document not ready 
    3131  </script> 
    3232 
    3333  <!-- Load up the actual editor core --> 
    34   <script type="text/javascript" src="../htmlarea.js"></script> 
     34  <script type="text/javascript" src="../XinhaCore.js"></script> 
    3535 
    3636  <script type="text/javascript"> 
     
    5555 
    5656      // THIS BIT OF JAVASCRIPT LOADS THE PLUGINS, NO TOUCHING  :) 
    57       if(!HTMLArea.loadPlugins(xinha_plugins, xinha_init)) return; 
     57      if(!Xinha.loadPlugins(xinha_plugins, xinha_init)) return; 
    5858 
    5959// What are the names of the textareas you will be turning into editors? 
     
    8888// Create a default configuration to be used by all the editors. 
    8989      settings = top.frames["menu"].settings; 
    90       xinha_config = new HTMLArea.Config(); 
     90      xinha_config = new Xinha.Config(); 
    9191      xinha_config.width = settings.width; 
    9292      xinha_config.height = settings.height; 
     
    153153      } 
    154154 
    155       if(typeof InsertPicture != 'undefined') { 
    156         // Path for InsertPicture plugin 
    157         InsertPicture.PicturePath = '/schmal/pictures/'; 
    158       } 
    159  
    160155      if(typeof Filter != 'undefined') { 
    161156        xinha_config.Filters = ["Word", "Paragraph"]; 
     
    164159// First create editors for the textareas. 
    165160// You can do this in two ways, either 
    166 //   xinha_editors   = HTMLArea.makeEditors(xinha_editors, xinha_config, xinha_plugins); 
     161//   xinha_editors   = Xinha.makeEditors(xinha_editors, xinha_config, xinha_plugins); 
    167162// if you want all the editor objects to use the same set of plugins, OR; 
    168 //   xinha_editors = HTMLArea.makeEditors(xinha_editors, xinha_config); 
     163//   xinha_editors = Xinha.makeEditors(xinha_editors, xinha_config); 
    169164//   xinha_editors['myTextarea0'].registerPlugins(['Stylist','FullScreen']); 
    170165//   xinha_editors['myTextarea1'].registerPlugins(['CSS','SuperClean']); 
    171166// if you want to use a different set of plugins for one or more of the editors. 
    172       xinha_editors = HTMLArea.makeEditors(xinha_editors, xinha_config, xinha_plugins); 
     167      xinha_editors = Xinha.makeEditors(xinha_editors, xinha_config, xinha_plugins); 
    173168 
    174169// If you want to change the configuration variables of any of the editors, 
     
    179174 
    180175// Finally we "start" the editors, this turns the textareas into Xinha editors. 
    181       HTMLArea.startEditors(xinha_editors); 
     176      Xinha.startEditors(xinha_editors); 
    182177    } 
    183178 
     
    194189 
    195190    window.onload = xinha_init; 
    196 //    window.onunload = HTMLArea.collectGarbageForIE; 
     191//    window.onunload = Xinha.collectGarbageForIE; 
    197192  </script> 
    198193</head> 
  • branches/ray/examples/ext_example-dest.php

    • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
  • branches/ray/examples/ext_example-menu.php

    • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
    r560 r761  
    141141        // capture some window's events 
    142142        function capwin(w) { 
    143 //              HTMLArea._addEvent(w, "click", Dialog._parentEvent); 
    144 //              HTMLArea._addEvent(w, "mousedown", Dialog._parentEvent); 
    145 //              HTMLArea._addEvent(w, "focus", Dialog._parentEvent); 
     143//              Xinha._addEvent(w, "click", Dialog._parentEvent); 
     144//              Xinha._addEvent(w, "mousedown", Dialog._parentEvent); 
     145//              Xinha._addEvent(w, "focus", Dialog._parentEvent); 
    146146        }; 
    147147        // release the captured events 
    148148        function relwin(w) { 
    149 //              HTMLArea._removeEvent(w, "click", Dialog._parentEvent); 
    150 //              HTMLArea._removeEvent(w, "mousedown", Dialog._parentEvent); 
    151 //              HTMLArea._removeEvent(w, "focus", Dialog._parentEvent); 
     149//              Xinha._removeEvent(w, "click", Dialog._parentEvent); 
     150//              Xinha._removeEvent(w, "mousedown", Dialog._parentEvent); 
     151//              Xinha._removeEvent(w, "focus", Dialog._parentEvent); 
    152152        }; 
    153153        capwin(window); 
     
    235235          <option value="no">Norwegian</option> 
    236236          <option value="pl">Polish</option> 
     237          <option value="ja">Japanese</option> 
    237238          </select> 
    238239        </label> 
  • branches/ray/examples/ext_example.html

    • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
    r433 r761  
    44    --  Xinha example frameset. 
    55    -- 
    6     --  $HeadURL: http://svn.xinha.python-hosting.com/trunk/examples/full_example.html $ 
    7     --  $LastChangedDate: 2005-06-02 11:14:41 +0200 (Thu, 02 Jun 2005) $ 
    8     --  $LastChangedRevision: 212 $ 
    9     --  $LastChangedBy: gocher $ 
     6    --  $HeadURL$ 
     7    --  $LastChangedDate$ 
     8    --  $LastChangedRevision$ 
     9    --  $LastChangedBy$ 
    1010    ---------------------------------------------------------------------------> 
    1111 
  • branches/ray/examples/full_example.css

    • Property svn:keywords changed from LastChangedDate LastChangedRevision LastChangedBy HeadURL to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
  • branches/ray/examples/simple_example.html

    • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
    r534 r761  
    1212 *  eg: _editor_url = "/path/to/xinha/"; 
    1313 * You may try a relative URL if you wish] 
    14  *  eg: _editor_url = "../"; 
     14 *  eg: _editor_url = "../";   
    1515 * in this example we do a little regular expression to find the absolute path. 
    1616 ************************************************************************/ 
     
    2020</script> 
    2121<!-- Load up the actual editor core --> 
    22 <script type="text/javascript" src="../htmlarea.js"></script> 
     22<script type="text/javascript" src="../XinhaCore.js"></script> 
    2323<script type="text/javascript"> 
    2424/************************************************************************ 
     
    5555{ 
    5656  // THIS BIT OF JAVASCRIPT LOADS THE PLUGINS, NO TOUCHING  :) 
    57   if(!HTMLArea.loadPlugins(xinha_plugins, xinha_init)) return; 
     57  if(!Xinha.loadPlugins(xinha_plugins, xinha_init)) return; 
    5858  /************************************************************************* 
    5959   * We create a default configuration to be used by all the editors. 
     
    6464   * for the configuration parameters 
    6565   ************************************************************************/ 
    66   var xinha_config = new HTMLArea.Config(); 
     66  var xinha_config = new Xinha.Config(); 
    6767  /************************************************************************ 
    6868   * We first create editors for the textareas. 
    6969   * You can do this in two ways, either 
    7070   * 
    71    *   xinha_editors   = HTMLArea.makeEditors(xinha_editors, xinha_config, xinha_plugins); 
     71   *   xinha_editors   = Xinha.makeEditors(xinha_editors, xinha_config, xinha_plugins); 
    7272   * 
    7373   * if you want all the editor objects to use the same set of plugins, OR; 
    7474   * 
    75    *   xinha_editors = HTMLArea.makeEditors(xinha_editors, xinha_config); 
     75   *   xinha_editors = Xinha.makeEditors(xinha_editors, xinha_config); 
    7676   *   xinha_editors['myTextArea'].registerPlugins(['Stylist','FullScreen']); 
    7777   *   xinha_editors['anotherOne'].registerPlugins(['CSS','SuperClean']); 
     
    8080   * editors. 
    8181   ************************************************************************/ 
    82   xinha_editors = HTMLArea.makeEditors(xinha_editors, xinha_config, xinha_plugins); 
     82  xinha_editors = Xinha.makeEditors(xinha_editors, xinha_config, xinha_plugins); 
    8383  /************************************************************************ 
    8484   * If you want to change the configuration variables of any of the 
     
    9898   * Xinha editors. 
    9999   ************************************************************************/ 
    100   HTMLArea.startEditors(xinha_editors); 
     100  Xinha.startEditors(xinha_editors); 
    101101} 
    102102window.onload = xinha_init; 
  • branches/ray/examples/stylist.css

    • Property svn:keywords changed from LastChangedDate LastChangedRevision LastChangedBy HeadURL to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
  • branches/ray/examples/testbed.html

    • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
    r277 r761  
    1414    --  use of the file as a boilerplate. 
    1515    -- 
    16     --  $HeadURL: svn://gogo@xinha.gogo.co.nz/repository/trunk/examples/full_example-body.html $ 
    17     --  $LastChangedDate: 2005-03-05 21:42:32 +1300 (Sat, 05 Mar 2005) $ 
    18     --  $LastChangedRevision: 35 $ 
    19     --  $LastChangedBy: gogo $ 
     16    --  $HeadURL$ 
     17    --  $LastChangedDate$ 
     18    --  $LastChangedRevision$ 
     19    --  $LastChangedBy$ 
    2020    ---------------------------------------------------------------------------> 
    2121 
     
    5555      xinha_plugins = xinha_plugins ? xinha_plugins : 
    5656      [ 
    57          
     57        'CharacterMap', 'SpellChecker', 'Linker' 
    5858      ]; 
    5959             // THIS BIT OF JAVASCRIPT LOADS THE PLUGINS, NO TOUCHING  :) 
    60              if(!HTMLArea.loadPlugins(xinha_plugins, xinha_init)) return; 
     60             if(!Xinha.loadPlugins(xinha_plugins, xinha_init)) return; 
    6161 
    6262      /** STEP 2 *************************************************************** 
     
    7777       * If you want to modify the default config you might do something like this. 
    7878       * 
    79        *   xinha_config = new HTMLArea.Config(); 
     79       *   xinha_config = new Xinha.Config(); 
    8080       *   xinha_config.width  = 640; 
    8181       *   xinha_config.height = 420; 
     
    8383       *************************************************************************/ 
    8484 
    85        xinha_config = xinha_config ? xinha_config : new HTMLArea.Config(); 
     85       xinha_config = xinha_config ? xinha_config : new Xinha.Config(); 
     86       xinha_config.fullPage = true; 
     87       xinha_config.CharacterMap.mode = 'panel'; 
    8688/* 
    8789       // We can load an external stylesheet like this - NOTE : YOU MUST GIVE AN ABSOLUTE URL 
     
    101103       * You can do this in two ways, either 
    102104       * 
    103        *   xinha_editors   = HTMLArea.makeEditors(xinha_editors, xinha_config, xinha_plugins); 
     105       *   xinha_editors   = Xinha.makeEditors(xinha_editors, xinha_config, xinha_plugins); 
    104106       * 
    105107       * if you want all the editor objects to use the same set of plugins, OR; 
    106108       * 
    107        *   xinha_editors = HTMLArea.makeEditors(xinha_editors, xinha_config); 
     109       *   xinha_editors = Xinha.makeEditors(xinha_editors, xinha_config); 
    108110       *   xinha_editors['myTextArea'].registerPlugins(['Stylist','FullScreen']); 
    109111       *   xinha_editors['anotherOne'].registerPlugins(['CSS','SuperClean']); 
     
    113115       ************************************************************************/ 
    114116 
    115       xinha_editors   = HTMLArea.makeEditors(xinha_editors, xinha_config, xinha_plugins); 
     117      xinha_editors   = Xinha.makeEditors(xinha_editors, xinha_config, xinha_plugins); 
    116118 
    117119      /** STEP 4 *************************************************************** 
     
    131133       ************************************************************************/ 
    132134 
    133       HTMLArea.startEditors(xinha_editors); 
     135      Xinha.startEditors(xinha_editors); 
    134136      window.onload = null; 
    135137    } 
    136138 
    137139    window.onload   = xinha_init; 
    138     // window.onunload = HTMLArea.collectGarbageForIE; 
     140    // window.onunload = Xinha.collectGarbageForIE; 
    139141  </script> 
    140142</head> 
     
    144146  <form action="javascript:var x = document.getElementById('editors_here');alert(x.myTextArea.value);" id="editors_here" onsubmit="alert(this.myTextArea.value);"> 
    145147    <textarea id="myTextArea" name="myTextArea" style="width:100%;height:320px;"> 
     148      &lt;html&gt; 
     149      &lt;head&gt; 
     150        &lt;title&gt;Hello&lt;/title&gt; 
     151        &lt;style type="text/css"&gt; 
     152          li { color:red; } 
     153        &lt;/style&gt; 
     154      &lt;/head&gt; 
     155      &lt;body&gt; 
     156      &lt;img src="http://xinha.python-hosting.com/trac/logo.jpg" usemap="#m1"&gt; 
     157      &lt;map name="m1"&gt; 
     158      &lt;area shape="rect" coords="137,101,255,124" href="http://www.mydomain.com"&gt; 
     159      &lt;/map&gt; 
     160 
    146161      &lt;p&gt; 
    147162        Lorem ipsum dolor sit amet, consectetuer adipiscing elit. 
     
    162177        &lt;li&gt; Nunc sit amet metus in tortor semper mattis. &lt;/li&gt; 
    163178      &lt;/ul&gt; 
     179      &lt;/body&gt; 
     180      &lt;/html&gt; 
    164181    </textarea> 
    165182 
  • branches/ray/htmlarea.js

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